web-dev-qa-db-ja.com

Django REST Frameworkでのシリアライザ検証の順序

状況

Django REST Framework's ModelSerializerで検証を操作しているときに、Meta.modelフィールドが常に検証されることに気づきました必ずしもそうする必要がない場合は、Userモデルのシリアル化について次の例を見てください。

  1. ユーザーを作成するエンドポイントがあります。そのため、passwordフィールドとconfirm_passwordフィールドがあります。 2つのフィールドが一致しない場合、ユーザーを作成できません。同様に、要求されたusernameがすでに存在する場合、ユーザーを作成できません。
  2. ユーザーは、上記の各フィールドに不適切な値をPOSTします。
  3. validateの実装がシリアライザで行われ(以下を参照)、一致しないpasswordおよびconfirm_passwordフィールドをキャッチします

validateの実装:

def validate(self, data):
    if data['password'] != data.pop('confirm_password'):
        raise serializers.ValidationError("Passwords do not match")
    return data

問題

ValidationErrorvalidateによって引き上げられた場合でも、ModelSerializerはデータベースに照会して、usernameがすでに使用されているかどうかを確認します。これは、エンドポイントから返されるエラーリストで明らかです。モデルエラーと非フィールドエラーの両方が存在します。

したがって、非フィールド検証が終了してデータベースへの呼び出しを保存するまでモデル検証を回避する方法を知りたいのです。

ソリューションで試行

私はDRFのソースを調べてこれがどこで起こっているのかを理解しようとしましたが、これを機能させるためにオーバーライドする必要があるものを見つけることができませんでした。

27
nmagerko

usernameフィールドに_unique=True_が設定されている可能性が高いため、Django RESTフレームワークは、新しいユーザー名は一意です。実際にこれを確認するには、repr(serializer())を実行します。これにより、バリデーターを含む、自動生成されたすべてのフィールドが表示されます。

検証は、文書化されていない特定の順序で実行されます

  1. _serializer.to_internal_value_ および _field.run_validators_ )と呼ばれるフィールドの逆シリアル化
  2. _serializer.validate_[field]_ は各フィールドに対して呼び出されます
  3. シリアライザレベルのバリデータが呼び出されます( _serializer.run_validation_ の後に _serializer.run_validators_
  4. _serializer.validate_ が呼び出されます

したがって、表示されている問題は、シリアライザレベルの検証の前にフィールドレベルの検証が呼び出されることです。お勧めしませんが、シリアライザーのメタで_extra_kwargs_を設定することで、フィールドレベルのバリデーターを削除できます。

_class Meta:
    extra_kwargs = {
        "username": {
            "validators": [],
        },
    }
_

ただし、自動的に生成された追加のバリデーターとともに、独自の検証でuniqueチェックを再実装する必要があります。

65
Kevin Brown

上記のソリューションが機能しないと思います。私の場合、モデルには「first_name」と「last_name」のフィールドがありますが、APIは「name」のみを受け取ります。

Metaクラスで 'extra_kwargs'と 'validators'を設定しても効果がないようで、first_nameとlast_nameは常に必須であると見なされ、バリデーターは常に呼び出されます。 first_name/last_name文字フィールドをオーバーロードできません

anotherrepfor_first_name = serializers.CharField(source=first_name, required=False)

名前が意味をなすように。何時間もイライラした後、ModelSerializerインスタンスでバリデーターをオーバーライドできる唯一の方法は、次のようにクラス初期化子をオーバーライドすることでした(誤ったインデントは許します)。

class ContactSerializer(serializers.ModelSerializer):
name = serializers.CharField(required=True)

class Meta:
    model = Contact
    fields = [ 'name', 'first_name', 'last_name', 'email', 'phone', 'question' ]

def __init__(self, *args, **kwargs):
    self.fields['first_name'] = serializers.CharField(required=False, allow_null=True, allow_blank=True)
    self.fields['last_name'] = serializers.CharField(required=False, allow_null=True, allow_blank=True)
    return super(ContactSerializer, self).__init__(*args, **kwargs)

def create(self, validated_data):
    return Contact.objects.create()

def validate(self, data):
    """
    Remove name after getting first_name, last_name
    """
    missing = []
    for k in ['name', 'email', 'question']:
        if k not in self.fields:
            missing.append(k)
    if len(missing):
        raise serializers.ValidationError("Ooops! The following fields are required: %s" % ','.join(missing))
    from nameparser import HumanName
    names = HumanName(data['name'])
    names.capitalize()
    data['last_name'] = names.last
    if re.search(r'\w+', names.middle):
        data['first_name'] = ' '.join([names.first, names.middle]) 
    else:
        data['first_name'] = names.first
    del(data['name'])

    return data

現在のドキュメントでは、文字フィールドでの空白とnullの許可はノーノーですが、これはモデルではなくシリアライザであり、APIがあらゆる種類のカウボーイによって呼び出されるため、ベースをカバーする必要があります。

2
MagicLAMP

また、シリアライザの検証中に制御フローがどのように流れるかを理解しようとしていて、djangorestframework-3.10.3のソースコードを注意深く調べた後、以下の要求フロー図を思いつきました。フローとフローで何が起こるかを理解しましたが、ソースから調べることができるので、あまり詳しく説明することはありません。

不完全なメソッドシグネチャは無視してください。どのメソッドがどのクラスで呼び出されるかにのみ焦点を当てます。

DRF_Validation_Control_Flow

シリアライザクラス(MySerializer(serializers.Serializer))にオーバーライドされた_is_valid_メソッドがある場合、my_serializer.is_valid()を呼び出すと、次のようになります。

  1. MySerializer.is_valid()が実行されます。
  2. スーパークラス(BaseSerializer)_is_valid_メソッド(super(MySerializer, self).is_valid(raise_exception)メソッド内のMySerializer.is_valid()など)を呼び出していると仮定すると、それが呼び出されます。
  3. MySerializerが_serializers.Serializer_を拡張しているため、_serializer.Serializers_のrun_validation()メソッドが呼び出されます。これは、最初のデータ辞書のみを検証しています。そのため、フィールドレベルの検証はまだ開始していません。
  4. 次に、_validate_empty_values_の_fields.Field_が呼び出されます。これは、data全体で再び発生し、単一のフィールドでは発生しません。
  5. 次に、_Serializer.to_internal_method_が呼び出されます。
  6. 次に、シリアライザーで定義された各フィールドをループします。そして各フィールドに対して、最初にfield.run_validation()メソッドを呼び出します。フィールドがField.run_validation()メソッドをオーバーライドした場合、それが最初に呼び出されます。 CharFieldの場合はオーバーライドされ、Field基本クラスの_run_validation_メソッドを呼び出します。図のステップ6-2。
  7. そのフィールドで再びField.validate_empty_values()を呼び出します
  8. 次に、フィールドのタイプの_to_internal_value_が呼び出されます。
  9. これでField.run_validators()メソッドが呼び出されました。これは、_validators = []_フィールドオプションを指定してフィールドに追加するバリデーターが1つずつ実行される場所だと思います
  10. これがすべて完了すると、Serializer.to_internal_value()メソッドに戻ります。ここで、forループ内の各フィールドに対して上記を実行していることを思い出してください。これで、シリアライザーで記述したカスタムフィールドバリデーター(_validate_field_name_などのメソッド)が実行されます。前の手順のいずれかで例外が発生した場合、カスタムバリデーターは実行されません。
  11. read_only_defaults()
  12. 検証データをデフォルトで更新する
  13. オブジェクトレベルのバリデーターを実行します。オブジェクトのvalidate()メソッドはここで実行されると思います。
0
railomaya