Issue
I've written the following code in django and DRF:
class ServiceModelMetaClass(serializers.SerializerMetaclass, type):
SERVICE_MODELS = {
"email": EmailSubscriber,
"push": PushSubscriber
}
def __call__(cls, *args, **kwargs):
service = kwargs.get("data", {}).get("service")
cls.Meta.subscriber_model = cls.SERVICE_MODELS.get(service)
return super().__call__(*args, **kwargs)
class InterListsActionsSerializer(serializers.Serializer, metaclass=ServiceModelMetaClass):
source_list_id = serializers.IntegerField()
target_list_id = serializers.IntegerField()
subscriber_ids = serializers.IntegerField(many=True, required=False)
account_id = serializers.CharField()
service = serializers.ChoiceField(choices=("email", "push"))
class Meta:
subscriber_model: Model = None
def move(self):
model = self.Meta.subscriber_model
# Rest of the method code.
The purpose of this code is that this serializer might need doing operation on different models based on the service that the user wants to use. So I wrote this metaclass to prevent writing duplicate code and simply change the subscriber_model
based on user's needs.
Now as you might know, serializers.Serializer
uses a metaclass by its own, serializers.SerializerMetaclass
. If I don't use this metaclass for creating my metaclass, it results in the following error:
TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases.
But when I try making my ServiceModelMetaClass
metaclass to inherit from serializers.SerializerMetaclass
it gives me this error:
File "/project-root/segment_management/serializers/subscriber_list.py", line 33, in <module>
class InterListsActionsSerializer(serializers.Serializer, metaclass=ServiceModelMetaClass):
File "/project-root/segment_management/serializers/subscriber_list.py", line 36, in InterListsActionsSerializer
subscriber_ids = serializers.IntegerField(many=True, required=False)
File "/project-root/.venv/lib/python3.10/site-packages/rest_framework/fields.py", line 894, in __init__
super().__init__(**kwargs)
TypeError: Field.__init__() got an unexpected keyword argument 'many'
What should I do to fix this problem or maybe a better alternative approach that keeps the code clean without using metaclass? Thanks in advance.
Solution
The problem is not the metaclass, or at least it is not the primary problem. The main problem is the many=True
for your IntegerField
. I looked it up, and apparently a field can not have many=True
, only a Serializer
can. Indeed, if we inspect the source code, we see [GitHub]:
def __new__(cls, *args, **kwargs): # We override this method in order to automatically create # `ListSerializer` classes instead when `many=True` is set. if kwargs.pop('many', False): return cls.many_init(*args, **kwargs) return super().__new__(cls, *args, **kwargs)
and the .many_init(…)
essentially creates the field as a child field, and wraps it in a ListSerializer
[GitHub]:
@classmethod def many_init(cls, *args, **kwargs): # … child_serializer = cls(*args, **kwargs) list_kwargs = { 'child': child_serializer, } # … meta = getattr(cls, 'Meta', None) list_serializer_class = getattr(meta, 'list_serializer_class', ListSerializer) return list_serializer_class(*args, **list_kwargs)
But we can appy essentially the same trick with a field, by using a ListField
[drf-doc]:
class InterListsActionsSerializer(
serializers.Serializer, metaclass=ServiceModelMetaClass
):
source_list_id = serializers.IntegerField()
target_list_id = serializers.IntegerField()
subscriber_ids = serializers.ListField(
child=serializers.IntegerField(), required=False
)
account_id = serializers.CharField()
service = serializers.ChoiceField(choices=('email', 'push'))
class Meta:
subscriber_model: Model = None
def move(self):
model = self.Meta.subscriber_model
Answered By - willeM_ Van Onsem
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.