2016-04-04 25 views
5

Sto cercando di integrare django validators 1.9 con i serializzatori del framework django rest. Ma l''utente' serializzato (del framework django rest) non è compatibile con i validatori di django.integrare validatori di password django con framework django validate_password

Ecco la serializers.py

import django.contrib.auth.password_validation as validators 
from rest_framework import serializers 

    class RegisterUserSerializer(serializers.ModelSerializer): 

     password = serializers.CharField(style={'input_type': 'password'}, write_only=True) 

     class Meta: 
      model = User 
      fields = ('id', 'username', 'email, 'password') 

     def validate_password(self, data): 
      validators.validate_password(password=data, user=User) 
      return data 

     def create(self, validated_data): 
      user = User.objects.create_user(**validated_data) 
      user.is_active = False 
      user.save() 
      return user 

sono riuscito ad ottenere MinimumLengthValidator e NumericPasswordValidator corretto perché sia ​​la funzione di convalida non usare 'utente' nel convalidare. Il codice sorgente è here

Estratto dal codice sorgente django:

def validate(self, password, user=None): 
     if password.isdigit(): 
      raise ValidationError(
       _("This password is entirely numeric."), 
       code='password_entirely_numeric', 
      ) 

Per altri validatori come UserAttributeSimilarityValidator, la funzione utilizza un altro argomento 'utente' in validazione ('user' è Django modello User, se io' m non è sbagliato)

Estratto dal codice sorgente django:

def validate(self, password, user=None): 
     if not user: 
      return 

     for attribute_name in self.user_attributes: 
      value = getattr(user, attribute_name, None) 

Come posso cambiare serializ Ed utente in quello che Django validatori (UserAttributeSimilarityValidator) può vedere

Estratto dal codice sorgente django:

def validate(self, password, user=None): 
     if not user: 
      return 

     for attribute_name in self.user_attributes: 
      value = getattr(user, attribute_name, None) 
      if not value or not isinstance(value, string_types): 
       continue 

Modifica

Django Resto quadro può ottenere tutti i Django di built-in convalida della password (ma è come un hack). Ecco un problema:

Il validationError è come questo

[ValidationError ([ 'Questa password è troppo corta Si deve contenere almeno 8 caratteri..']), ValidationError ([ 'Questa password è interamente numerico. '])]

La convalida non contiene un campo. framework Django resto lo vedono come

{ 
    "non_field_errors": [ 
     "This password is too short. It must contain at least 8 characters.", 
     "This password is entirely numeric." 
    ] 
} 

Come posso iniettare un campo al raise ValidationError

risposta

8

Come lei ha ricordato, quando si convalidare il password in validate_password metodo che utilizza UserAttributeSimilarityValidator validatore, non devi dell'oggetto user.

Quello che suggerisco che invece di fare la convalida a livello di campo, si deve eseguire object-level validation implementando validate metodo sul serializzatore:

import sys 
from django.core import exceptions 
import django.contrib.auth.password_validation as validators 

class RegisterUserSerializer(serializers.ModelSerializer): 

    # rest of the code 

    def validate(self, data): 
     # here data has all the fields which have validated values 
     # so we can create a User instance out of it 
     user = User(**data) 

     # get the password from the data 
     password = data.get('password') 

     errors = dict() 
     try: 
      # validate the password and catch the exception 
      validators.validate_password(password=password, user=User) 

     # the exception raised here is different than serializers.ValidationError 
     except exceptions.ValidationError as e: 
      errors['password'] = list(e.messages) 

     if errors: 
      raise serializers.ValidationError(errors) 

     return super(RegisterUserSerializer, self).validate(data) 
+0

Ci sono un paio di errore di battitura nel codice. A proposito, la tua soluzione funziona bene sulla validazione, ma il framework django non può ottenere il nome del campo da qui i non_field_errors ---> { "non_field_errors": [ "La password è troppo simile all'email.", " Questa password è troppo corta. Deve contenere almeno 8 caratteri. " ] } – momokjaaaaa

+0

In realtà non ho testato il codice, quindi potrebbero esserci alcuni refusi qua e là ma ciò non dovrebbe impedirti di utilizzare e testare correttamente questo codice. Riguardo a 'NON_FIELD_ERRORS' ho aggiornato la mia risposta e ora gli errori evidenziati dovrebbero contenere il nome del campo' password' come chiave per quegli errori. – AKS

+0

Ricordare che '** data' potrebbe non includere tutti i campi obbligatori per creare l'oggetto utente quando si eseguono aggiornamenti parziali (' PATCH'). – faph

6

È possibile accedere all'oggetto utente attraverso self.instance sull'oggetto serializer, anche quando fare convalida a livello di campo.Qualcosa di simile dovrebbe funzionare:

from django.contrib.auth import password_validation 

def validate_password(self, value): 
    password_validation.validate_password(value, self.instance) 
    return value 
+0

Questo sarà disponibile solo se l'utente è stato creato, il che non sarà il caso quando si registra un nuovo utente e si convalida la pre-creazione della password. – tbm

1

Utilizzare i serializzatori! Hai un metodo validate_fieldname!

class UserSerializer(serializers.ModelSerializer): 

    class Meta: 
     model = User 
     fields = (
      'id', 'username', 'password', 'first_name', 'last_name', 'email' 
     ) 
     extra_kwargs = { 
      'password': {'write_only': True}, 
      'username': {'read_only': True} 
     } 

    def validate_password(self, value): 
     try: 
      validate_password(value) 
     except ValidationError as exc: 
      raise serializers.ValidationError(str(exc)) 
     return value 

    def create(self, validated_data): 
     validated_data = self.check_for_unique_email(validated_data) 
     validated_data.setdefault('username', validated_data['email']) 
     user = super().create(validated_data) 
     user.set_password(validated_data['password']) 

     user.is_active = False 
     user.save() 
     return user 

    def update(self, instance, validated_data): 
     validated_data = self.check_for_unique_email(validated_data) 
     user = super().update(instance, validated_data) 
     if 'password' in validated_data: 
      user.set_password(validated_data['password']) 
      user.save() 
     return user 
0

Al momento della creazione di un nuovo utente (registrazione) allora self.instance sarà nessuno, funzionerà quando vostro stanno riposando la password, password di modifica o aggiornamento dei dati degli utenti con password. Ma se si desidera controllare la password non dovrebbe essere simile alla tua e-mail o nome utente allora avete bisogno di includere "SequenceMatcher" nella vostra convalida

data = self.get_initial() 
username = data.get("username") 
email = data.get("email") 
password = data.get("password") 
max_similarity = 0.7 
if SequenceMatcher(a=password.lower(), b=username.lower()).quick_ratio() > max_similarity: 
    raise serializers.ValidationError("The password is too similar to the username.") 
if SequenceMatcher(a=password.lower(), b=email.lower()).quick_ratio() > max_similarity: 
    raise serializers.ValidationError("The password is too similar to the email.") 
Problemi correlati