Walidacja w Django Rest Framework

0

Od niedawna uczę się Pythona i Django i kiedy chcąc dodać w aplikacji RestAPI skorzystałem z frameworka Django Rest Framework utknąłem w momencie walidacji danych odebranych od użytkownika. Problem jest taki, że o ile standardowe walidatory typu czy pole jest wymagane itd. w serializerze działają poprawnie, tzn. w jsonie zwracanym w odpowiedzi na to co użytkownik przysłał widzę informację o tych błędach w danych o tyle kompletnie nie potrafię zmusić do działania własnych reguł sprawdzających te dane.
Po przeszukaniu sporej części internetu praktycznie wszędzie widzę tekst, że żeby dodać własne reguły wystarczy dopisać metodę

def validate(self, data):

w serializerze, który chcemy walidować. Problem jest taki, że ta metoda nie jest nigdy wywoływana, a wg zapewnień m.in. na stronie twórców frameworka powinno się to dziać po wywołaniu metody serializer.is_valid()


Serializer wygląda u mnie tak (tutaj obcięty z nieistotnych w tym momencie pól):
```python
class RegisterSerializer(serializers.Serializer):
    password1 = serializers.CharField(required=True)
    password2 = serializers.CharField(required=True)
    
    def validate(self, data):
        if data.get('password1') != data.get('password2'):
            raise serializers.ValidationError("Errrrrorr")
        return data

Potem mam taką metodę odbierającą dane:

@api_view(['POST'])
def register(request):
    if request.method == 'POST':
        serializer = RegisterSerializer(data=request.POST)
        if serializer.is_valid():
            serializer.save()
            
            # ...
            
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

Próbowałem też metodę is_valid() wywoływać z parametrem* raise_exception* ustawionym na True,

Siedzę nad tym już 2 dzień i ciągle po wysłaniu niepasujących haseł nie dostaję w zmiennej *serializer.errors *informacji o tym, że są niepoprawne. Jeśli ktoś ma pomysł co tu jest nie tak, albo patrzy na to i płacze, że ktokolwiek ośmielił się takie głupoty robić, a to przecież powinno zupełnie inaczej wyglądać to proszę żeby się podzielił spostrzeżeniami :)

Mam nadzieję, że nie zapomniałem wkleić jakiegoś istotnego dla sprawy fragmentu kodu, w razie czego uzupełnię.

Pozdrawiam,
Marek

2

A w jaki sposób wysyłasz zapytanie do serwera? Jeżeli nie podajesz nagłówka z typem zawartości dane które chcesz przetworzyć mogą się różnić od oczekiwanych. Przede wszystkim zacznij od debugowania kodu. Poradnik dla Pythona: https://docs.python.org/2/library/pdb.html

Ogranicza się to do wstawienia linii:
import pdb; pdb.set_trace()
i obserwowaniu wyjścia, po uruchomieniu pdb korzystasz z niego jak z interpretera (po wpisaniu nazwy zmiennej możesz sprawdzić jej wartość).

Na Twoim miejscu za tą linią:

serializer = RegisterSerializer(data=request.POST)

postawiłbym breakpoint i sprawdził zawartość request.POST.

Sprawdziłem Twój kod u siebie i dla zapytania:

curl http://127.0.0.1:8001/register/ -X POST -d '{"password1":"test", "password2":"test2"}' -H "Content-type: application/json"

Po zmianie:

serializer = RegisterSerializer(data=request.POST)

na:

serializer = RegisterSerializer(data=request.data)

Prawidłowo zwraca błąd:
{"non_field_errors":["Errrrrrorr"]}

Możesz również ustawić sobie breakpoint w metodzie "validate" i przekonasz się że jest wywoływana i możesz sprawdzić tam przekazane dane.

0

@racuh zapytanie wysyłałem przez curl właśnie, ale wtopa, że nie ustawiłem content-type i siedziałbym z tym jeszcze tydzień pewnie :D Dzięki wielkie, zaraz to sprawdzę. Chociaż w takim razie dziwi mnie, że standardowe błędy walidacji zwracało, ale na razie nie wnikam.

Czyli tak jak sądziłem głupota po mojej stronie to była jednak, ale w sumie dobrze, że nic gorszego.

A debuger na pewno przyda się na przyszłość.

EDIT: To jeszcze tylko szybkie pytanie. Dlaczego własna walidacja danych nie jest zwracana dopóki dane nie przejdą standardowych walidatorów. Tzn. jeśli mam ustawione jakieś pole jako wymagane i dodatkowo chociażby taki warunek jak w pierwszym poście i przesyłam pustą wartość + różne hasła to Django zwraca mi tylko błąd, że pole jest puste pomijając całkowicie to co ręcznie sprawdzam. Dopiero kiedy prześlę niepuste pola to dostaję błąd, że hasło się nie zgadza?

2

Korzystanie z debuggera zdecydowanie ułatwia pracę. Jeszcze po poczytania: https://mike.tig.as/blog/2010/09/14/pdb/
Właściwie sam sobie odpowiedziałeś na pytanie. Jeśli spojrzysz do kodu biblioteki Django REST:

def is_valid(self, raise_exception=False):
        assert not hasattr(self, 'restore_object'), (
            'Serializer `%s.%s` has old-style version 2 `.restore_object()` '
            'that is no longer compatible with REST framework 3. '
            'Use the new-style `.create()` and `.update()` methods instead.' %
            (self.__class__.__module__, self.__class__.__name__)
        )

        assert hasattr(self, 'initial_data'), (
            'Cannot call `.is_valid()` as no `data=` keyword argument was '
            'passed when instantiating the serializer instance.'
        )

        if not hasattr(self, '_validated_data'):
            try:
                self._validated_data = self.run_validation(self.initial_data)
            except ValidationError as exc:
                self._validated_data = {}
                self._errors = exc.detail
            else:
                self._errors = {}

        if self._errors and raise_exception:
            raise ValidationError(self.errors)

        return not bool(self._errors)

Następnie:

def run_validation(self, data=empty):
        """
        We override the default `run_validation`, because the validation
        performed by validators and the `.validate()` method should
        be coerced into an error dictionary with a 'non_fields_error' key.
        """
        (is_empty_value, data) = self.validate_empty_values(data)
        if is_empty_value:
            return data

        value = self.to_internal_value(data)
        try:
            self.run_validators(value)
            value = self.validate(value)
            assert value is not None, '.validate() should return the validated data'
        except (ValidationError, DjangoValidationError) as exc:
            raise ValidationError(detail=get_validation_error_detail(exc))

        return value

Tak więc najpierw uruchamiane są standardowe walidatory a później te w nadpisanej metodzie "validate".

0

Dzięki wielkie, tak to jest jak się jednocześnie zaczyna korzystać z nowego systemu, języka i bibliotek (normalnie robię w C# i ASP.NET) i próbuje się wszystko ogarnąć. Z debuggera dla pythona na pewno muszę szybko nauczyć się korzystać.

1 użytkowników online, w tym zalogowanych: 0, gości: 1