Неожиданное переопределение метакласса для сериализаторов DRF - PullRequest
0 голосов
/ 18 мая 2018

Я использую Django 1.11.12 и djangorestframework == 3.7.7 и python3.6.3

У меня есть несколько моделей, унаследованных от одной абстрактной модели:

class SoftDeleteModel(models.Model):
    deleted = models.DateTimeField(blank=True, null=True)

    class Meta:
        abstract=True

И естьдесятки моделей, унаследованных от него.Мне нужно, чтобы это удаленное поле отображалось только для администратора.

Стараясь не повторять одну и ту же логику снова и снова, я написал метакласс и абстрактный класс, чтобы упростить обработку в сериализаторах.Большинство частей пользовательского метакласса взяты из serializer.SerializerMetaclass.

class SoftDeletionMetaclass(serializers.SerializerMetaclass):

    @classmethod
    def _get_declared_fields(cls, bases, attrs):
        fields = [(field_name, attrs.pop(field_name))
                for field_name, obj in list(attrs.items())
                if isinstance(obj, Field)]
        fields.append(("deleted", serializers.SerializerMethodField(read_only=True)))
        fields.sort(key=lambda x: x[1]._creation_counter)
        # If this class is subclassing another Serializer, add that Serializer's
        # fields.  Note that we loop over the bases in *reverse*. This is necessary
        # in order to maintain the correct order of fields.
        for base in reversed(bases):
            if hasattr(base, '_declared_fields'):
                fields = [
                    (field_name, obj) for field_name, obj
                    in base._declared_fields.items()
                    if field_name not in attrs
                ] + fields
        return OrderedDict(fields)

    def __new__(cls, name, bases, attrs):
        attrs['_declared_fields'] = cls._get_declared_fields(bases, attrs)
        meta = attrs.get('Meta')
        if meta:
            if isinstance(meta.fields, tuple):
                meta.fields += ("deleted", )
            else:
                meta.fields.append("deleted")
            attrs["Meta"] = meta
        return super(serializers.SerializerMetaclass, cls).__new__(cls, name, bases, attrs)

И я написал абстрактный сериализатор, который использует этот метакласс

class SoftDeletionSerializer(metaclass=SoftDeletionMetaclass):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # Not Admin user shouldn't see deleted field
        if (not self.context.get("request") or not hasattr(self.context.get("request"), 'user')
            or not self.context.get("request").user or
                not self.context.get("request").user.is_admin_role):
            self.fields.pop("deleted")

    def get_deleted(self, obj):
        return bool(obj.deleted)

В общем, он работает нормально.Но я обнаружил конкретную ситуацию, которая вызывает странную ошибку.

Есть два приложения users и companies.У первого модель UserBankAccount, у второго модель CompanyBankAccountusers/serializers.py у меня есть:

class AbstractBankAccountSerializer(serializers.ModelSerializer):
    public_name = serializers.SerializerMethodField(read_only=True)

    class Meta:
        fields = [
            'id',
            'bank_name',
            'account_name',
            'owner_name',
            'bic',
            'iban',
            'public_name',
        ]

    def get_public_name(self, obj):
        return obj.public_name

class UserBankAccountSerializer(AbstractBankAccountSerializer):
    """Serialize data from the UserBankAccount."""

    class Meta(AbstractBankAccountSerializer.Meta):
        model = UserBankAccount

UserBankAccount не наследуется от SoftDeleteModel и не имеет поля deleted.

Но CompanyBankAccount наследуется отSoftDeleteModel.Поэтому я использую свой сериализатор с пользовательским метаклассом.companies/serialziers.py:

class CompanyBankAccountSerializer(SoftDeletionSerializer, AbstractBankAccountSerializer):
    class Meta(AbstractBankAccountSerializer.Meta):
        model = CompanyBankAccount

И вот тут начинаются проблемы.Теперь, когда я пытаюсь использовать UserBankAccountSerializer, я получаю странные ошибки ровно один раз после вызова.Например, этот код:

serialzier=UserBankAccountSerializer(data=request.data)
if serializer.is_valid(raise_exception=True):
    ...

вызовет ошибку ImproperlyConfigured('Field name удалено is not valid for model UserBankAccount .',) Но если я помещу туда ipdb и сначала запусту его (он генерирует ошибку), то язапустите его еще раз, и он вернет True, как и ожидалось.

Я пытался использовать mro для проверки

ipdb> serializer.__class__.mro()

[<class 'apps.bankaccounts.serializers.UserBankAccountSerializer'>, <class 'apps.bankaccounts.serializers.AbstractBankAccountSerializer'>, <class 'rest_framework.serializers.ModelSerializer'>, <class 'rest_framework.serializers.Serializer'>, <class 'rest_framework.serializers.BaseSerializer'>, <class 'rest_framework.fields.Field'>, <class 'object'>] Но никаких следовиз SoftDeletionSerializer.

Дело в том, что если я удаляю SoftDeletionSerializer из CompanyBankAccountSerializer - тогда все работает, ошибок в UserBankAccountSerializer нет.Кажется, что метакласс, используемый в SoftDeletionSerializer, влияет на UserBankAccountSerializer через AbstractBankAccountSerializer, но я не могу понять, почему это происходит, и как мне этого избежать.

Я также пытался отловить ошибкуи распечатайте его полностью с помощью traceback.Вот что я получаю

File "/home/user/projects/project/.venv/lib/python3.6/site-packages/rest_framework/views.py", line 491, in dispatch
    response = handler(request, *args, **kwargs)
File "/home/user/projects/project/companies/views.py", line 83, in add_bank_account
    if serializer.is_valid(raise_exception=True):
File "/home/user/projects/project/.venv/lib/python3.6/site-packages/rest_framework/serializers.py", line 236, in is_valid
    self._validated_data = self.run_validation(self.initial_data)
File "/home/user/projects/project/.venv/lib/python3.6/site-packages/rest_framework/serializers.py", line 434, in run_validation
    value = self.to_internal_value(data)
File "/home/user/projects/project/.venv/lib/python3.6/site-packages/rest_framework/serializers.py", line 458, in to_internal_value
    fields = self._writable_fields
File "/home/user/projects/project/.venv/lib/python3.6/site-packages/django/utils/functional.py", line 35, in __get__
    res = instance.__dict__[self.name] = self.func(instance)
File "/home/user/projects/project/.venv/lib/python3.6/site-packages/rest_framework/serializers.py", line 369, in _writable_fields
    field for field in self.fields.values()
File "/home/user/projects/project/.venv/lib/python3.6/site-packages/rest_framework/serializers.py", line 362, in fields
    for key, value in self.get_fields().items():
File "/home/user/projects/project/.venv/lib/python3.6/site-packages/rest_framework/serializers.py", line 1021, in get_fields
    source, info, model, depth
File "/home/user/projects/project/.venv/lib/python3.6/site-packages/rest_framework/serializers.py", line 1166, in build_field
    return self.build_unknown_field(field_name, model_class)
File "/home/user/projects/project/.venv/lib/python3.6/site-packages/rest_framework/serializers.py", line 1278, in build_unknown_field
    (field_name, model_class.__name__)

Буду очень признателен за любые предположения, почему это происходит и как этого можно избежать (кроме удаления метакласса и абстрактной модели сериализатора)

Заранее спасибо.

...