Я использую 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
, у второго модель CompanyBankAccount
.В users/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__)
Буду очень признателен за любые предположения, почему это происходит и как этого можно избежать (кроме удаления метакласса и абстрактной модели сериализатора)
Заранее спасибо.