I have a few Serializers that share a few fields like meta_id, category_id and so on.
Obviously I could just declare these on the serializer as a SerializerMethodField individually but I would like to find a way to reuse the logic, either with a Mixin, Decorator, or inheritance.
How can I declare a base serializer and inherit it— while still inheriting from serializers.ModelSerializer? So that I can reuse the get_meta_id and make sure it shows up in the fields?
class Foo(serializers.ModelSerializer, somethingHere?):
meta_id = Serializers.SerializerMethodField()
class Meta:
model = Foo
fields = [...]
def get_meta_id(self, obj):
...
Is it possible to just pass two parameters into the class
You can crete a Base serializer and use inheritence for other serializers.Like that:
class BaseSerializer(serializers.Serializer):
#your codes and extra fields
test_field = serializer.SerializerMethodField()
def get_test_field(self.obj):
return 'test' # or you can use obj instance here
class ExtendedSerializer(BaseSerializer,serializers.ModelSerializer):
#your extra fields
class Meta:
fields = BaseSerializer.Meta.fields + () # you can add your additional fields here
class BaseSerializer(serializers.ModelSerializer): # The BaseSerializer class inherit everthing from ModelSerializer
class Meta(serializers.ModelSerializer.Meta): # The BaseSerializer.Meta inherits everything from ModelSerializer.Meta
def get_meta_id(self, obj):
return self.meta_id
class Foo(BaseSerializer):
meta_id = Serializers.SerializerMethodField()
class Meta(BaseSerializer.Meta): # Here you should get the ModelSerializer.Meta + your custom get_meta_id
model = Foo
fields = [...]
Related
For a ForeignKey relationship, how can I change the serializer used based off some criteria?
# models.py
class Relationship(models.Model):
...
class Item(models.Model):
relationships = ForeignKey(Relationship)
class OddPKSerializer(serializers.Serializer):
...
class EvenPKSerializer(serializers.Serializer):
...
class ItemSerializer(serializer.Serializer):
# relationships = OddPKSerializer and/or EvenPKSerializer(many=True)
# depending on some criteria
class Meta:
model = Item
fields = ["relationships"]
# Where for example, OddPKSerializer is used if
# the specific relationship object has an odd pk
# and EvenPKSerializer is used for even pks
In my actual use case, I'm looking to serialize differently based on the ForeignKey object's class. (I'm using proxy models so a single ForeignKey field can point to different classes.)
I've tried using SerializerMethodField but that only seems to act on the "Item" object and not the "Relationship" objects that I'm looking to serialize.
This works:
class OddPKSerializer(serializers.Serializer):
...
class EvenPKSerializer(serializers.Serializer):
...
class SwitchSerializer():
item = serializers.SerializerMethodField()
def get_item(self, obj):
if obj.pk/2 == 0:
return EvenPKSerializer(obj).data
return OddPKSerializer(obj).data
class ItemSerializer():
item = SwitchSerializer(many=True)
There is an abstract model that defines an interface for two child models.
I've been asked to create an API endpoint that will return instances from those child models (including only the common fields defined thanks to the interface father class).
The problem raises when defining the Serializer.Meta.model attribute.
Anyway, code is always clearer:
models.py
class Children(Model):
class Meta:
abstract = True
def get_foo(self):
raise NotImplementedError
class Daughter(Children):
def get_foo(self):
return self.xx
class Son(Children):
def get_foo(self):
return self.yy
api/views.py
class ChildrenApiView(ListAPIView):
serializer_class = ChildrenSerializer
def get_queryset(self):
daughters = Daughter.objects.all()
sons = Son.objects.all()
return list(daughters) + list(sons)
serializers.py
class ChildrenSerializer(ModelSerializer):
foo = CharField(source="get_foo", read_only=True)
class Meta:
model = Children # <========= HERE IS THE PROBLEM
fields = ('foo',)
Some thoughts;
I know I'm not able to point out to the abstract model Children (wrote it for showing the inntention)
I tried to leave ChildrenSerializer.Meta.model empty
Seems that I can choose whichever Daughter or Son but not sure if that solution has any side-effect or is the way to go.
Tried to create DaughterSerializer & SonSerializer and use the method get_serializer_class(self) at the view, but wasn't able to make it run
I would probabaly not have a model serializer, and instead have a standard Serializer, with all the fields that you want to return in the view.
This will make it applicable for both Son and Daughter.
So the serializer would be something like:
class ChildrenSerializer(serializers.Serializer):
foo = CharField(source="get_foo", read_only=True)
I have two models, one contains the other in a foreignKey relationship, I wanted to make an API that would return the combine of these two models, so I attempted to use nested Serializers to add the related model as well, but the data are not all on the same level, the related models is a object inside the first.
Here are the Models
class ModelOne(models.Model):
last_counter = models.IntegerField()
class ModelTwo(models.Model):
model_one = models.ForeignKey(ModelOne, on_delete=models.CASCADE)
category = models.CharField(max_length=64)
counter_type = models.CharField(max_length=32)
Here are the serializers
class ModelOneSerializer(serializers.ModelSerializer):
class Meta:
model = ModelOne
fields = "__all__"
class ModelTwoSerializer(serializers.ModelSerializer):
model_one= ModelOneSerializer(read_only=True)
class Meta:
model = ModelTwo
fields = "__all__"
This would return from the API in the form of
{
"category" : ...,
"counter_type" : ...,
"model_one" : {
"last_counter" : ...
}
}
But I don't want the response to be like that, I want it more like this
{
"category" : ...,
"counter_type" : ...,
"last_counter" : ...,
}
Is there a way to achieve this through serializers?
Use SerializerMethodField
from rest_framework.fields import SerializerMethodField
class ModelTwoSerializer(serializers.ModelSerializer):
last_counter = SerializerMethodField()
class Meta:
model = ModelTwo
fields = "__all__"
def get_last_counter(self, obj):
return ModelOneSerializer(obj.model_one).data['last_counter']
When creating custom fields(field_one for example) with SerializerMethodField, you have to create a method called get_field_one, for this method to be automatically detected by the serializer.
You can achieve what you want to do using SerializerMethodField from drf fields:
SerializerMethodField is a read-only field that computes its value at request processing time, by calling a method on the serializer class it is attached to. For example for your case it will look like this. Notice that the computed last_counter is added on the serialized model fields.
from rest_framework.fields import SerializerMethodField
class ModelTwoSerializer(serializers.ModelSerializer):
last_counter = serializers.SerializerMethodField()
class Meta:
model = ModelTwo
fields = ["category", "counter_type", "last_counter"]
def get_last_counter(self, obj):
return int(obj.model_one.last_counter)
SerializerMethodField accepts method_name, but it’s usually more convenient to use the default pattern for naming those methods, which is get_. Just make sure you‘re not overburdening your method fields with any heavy-lifting operations.
You can read more on the official documentation:enter link description here
I want to serialize a model, but want to include an additional field that requires doing some database lookups on the model instance to be serialized:
class FooSerializer(serializers.ModelSerializer):
my_field = ... # result of some database queries on the input Foo object
class Meta:
model = Foo
fields = ('id', 'name', 'myfield')
What is the right way to do this? I see that you can pass in extra "context" to the serializer, is the right answer to pass in the additional field in a context dictionary?
With that approach, the logic of getting the field I need would not be self-contained with the serializer definition, which is ideal since every serialized instance will need my_field. Elsewhere in the DRF serializers documentation it says "extra fields can correspond to any property or callable on the model". Are "extra fields" what I'm talking about?
Should I define a function in Foo's model definition that returns my_field value, and in the serializer I hook up my_field to that callable? What does that look like?
Happy to clarify the question if necessary.
I think SerializerMethodField is what you're looking for:
class FooSerializer(serializers.ModelSerializer):
my_field = serializers.SerializerMethodField('is_named_bar')
def is_named_bar(self, foo):
return foo.name == "bar"
class Meta:
model = Foo
fields = ('id', 'name', 'my_field')
http://www.django-rest-framework.org/api-guide/fields/#serializermethodfield
You can change your model method to property and use it in serializer with this approach.
class Foo(models.Model):
. . .
#property
def my_field(self):
return stuff
. . .
class FooSerializer(ModelSerializer):
my_field = serializers.ReadOnlyField(source='my_field')
class Meta:
model = Foo
fields = ('my_field',)
Edit: With recent versions of rest framework (I tried 3.3.3), you don't need to change to property. Model method will just work fine.
With the last version of Django Rest Framework, you need to create a method in your model with the name of the field you want to add. No need for #property and source='field' raise an error.
class Foo(models.Model):
. . .
def foo(self):
return 'stuff'
. . .
class FooSerializer(ModelSerializer):
foo = serializers.ReadOnlyField()
class Meta:
model = Foo
fields = ('foo',)
if you want read and write on your extra field, you can use a new custom serializer, that extends serializers.Serializer, and use it like this
class ExtraFieldSerializer(serializers.Serializer):
def to_representation(self, instance):
# this would have the same as body as in a SerializerMethodField
return 'my logic here'
def to_internal_value(self, data):
# This must return a dictionary that will be used to
# update the caller's validation data, i.e. if the result
# produced should just be set back into the field that this
# serializer is set to, return the following:
return {
self.field_name: 'Any python object made with data: %s' % data
}
class MyModelSerializer(serializers.ModelSerializer):
my_extra_field = ExtraFieldSerializer(source='*')
class Meta:
model = MyModel
fields = ['id', 'my_extra_field']
i use this in related nested fields with some custom logic
My response to a similar question (here) might be useful.
If you have a Model Method defined in the following way:
class MyModel(models.Model):
...
def model_method(self):
return "some_calculated_result"
You can add the result of calling said method to your serializer like so:
class MyModelSerializer(serializers.ModelSerializer):
model_method_field = serializers.CharField(source='model_method')
p.s. Since the custom field isn't really a field in your model, you'll usually want to make it read-only, like so:
class Meta:
model = MyModel
read_only_fields = (
'model_method_field',
)
If you want to add field dynamically for each object u can use to_represention.
class FooSerializer(serializers.ModelSerializer):
class Meta:
model = Foo
fields = ('id', 'name',)
def to_representation(self, instance):
representation = super().to_representation(instance)
if instance.name!='': #condition
representation['email']=instance.name+"#xyz.com"#adding key and value
representation['currency']=instance.task.profile.currency #adding key and value some other relation field
return representation
return representation
In this way you can add key and value for each obj dynamically
hope u like it
This worked for me.
If we want to just add an additional field in ModelSerializer, we can
do it like below, and also the field can be assigned some val after
some calculations of lookup. Or in some cases, if we want to send the
parameters in API response.
In model.py
class Foo(models.Model):
"""Model Foo"""
name = models.CharField(max_length=30, help_text="Customer Name")
In serializer.py
class FooSerializer(serializers.ModelSerializer):
retrieved_time = serializers.SerializerMethodField()
#classmethod
def get_retrieved_time(self, object):
"""getter method to add field retrieved_time"""
return None
class Meta:
model = Foo
fields = ('id', 'name', 'retrieved_time ')
Hope this could help someone.
class Demo(models.Model):
...
#property
def property_name(self):
...
If you want to use the same property name:
class DemoSerializer(serializers.ModelSerializer):
property_name = serializers.ReadOnlyField()
class Meta:
model = Product
fields = '__all__' # or you can choose your own fields
If you want to use different property name, just change this:
new_property_name = serializers.ReadOnlyField(source='property_name')
As Chemical Programer said in this comment, in latest DRF you can just do it like this:
class FooSerializer(serializers.ModelSerializer):
extra_field = serializers.SerializerMethodField()
def get_extra_field(self, foo_instance):
return foo_instance.a + foo_instance.b
class Meta:
model = Foo
fields = ('extra_field', ...)
DRF docs source
Even though, this is not what author has wanted, it still can be considered useful for people here:
If you are using .save() ModelSerializer's method, you can pass **kwargs into it. By this, you can save multiple dynamic values.
i.e. .save(**{'foo':'bar', 'lorem':'ipsum'})
Add the following in serializer class:
def to_representation(self, instance):
representation = super().to_representation(instance)
representation['package_id'] = "custom value"
return representation
If I have two forms, based on different base classes (say, Form and ModelForm), but I want to use a few fields in both, can I reuse them in a DRY way?
Consider the following scenario:
class AfricanSwallowForm(forms.ModelForm):
airspeed_velocity = forms.IntegerField(some_important_details_here)
is_migratory = forms.BooleanField(more_important_details)
class Meta:
model = AfricanBird
class EuropeanSwallowForm(forms.Form):
airspeed_velocity = forms.IntegerField(some_important_details_here)
is_migratory = forms.BooleanField(more_important_details)
....is there a way I can just reuse the fields airspeed_velocity and is_migratory? Imagine I have a couple dozen of these type of forms. The code will be soaking if I write these over and over again.
(Assume, for the purposes of this question, that I can't or won't turn airspeed_velocity and is_migratory into fields of the model AfricanBird.)
You could use multiple inheritance aka mixins, to factor out the fields that are used in both Form and ModelForm.
class SwallowFormFields:
airspeed_velocity = forms.IntegerField( ... )
is_migratory = forms.BooleanField( ... )
class AfricanSwallowForm(forms.ModelForm, SwallowFormFields):
class Meta:
model = AfricanBird
class EuropeanSwallowForm(forms.Form, SwallowFormFields):
pass
UPDATE:
Since this does not work with Django metaprogramming, you either need to create a custom __init__ constructor that adds the inherited fields to the object's fields list or you can add the references explicitly inside the class definition:
class SwallowFormFields:
airspeed_velocity = forms.IntegerField()
is_migratory = forms.BooleanField()
class AfricanSwallowForm(forms.ModelForm):
airspeed_velocity = SwallowFormFields.airspeed_velocity
is_migratory = SwallowFormFields.is_migratory
class Meta:
model = AfricanSwallow
class EuropeanSwallowForm(forms.Form):
airspeed_velocity = SwallowFormFields.airspeed_velocity
is_migratory = SwallowFormFields.is_migratory
UPDATE:
Of course you don't have to nest your shared fields into a class -- you could also simply define them as globals ...
airspeed_velocity = forms.IntegerField()
is_migratory = forms.BooleanField()
class AfricanSwallowForm(forms.ModelForm):
airspeed_velocity = airspeed_velocity
is_migratory = is_migratory
class Meta:
model = AfricanSwallow
class EuropeanSwallowForm(forms.Form):
airspeed_velocity = airspeed_velocity
is_migratory = is_migratory
UPDATE:
Okay, if you really want to DRY to the max, you have to go with the metaclasses.
So here is how you may do it:
from django.forms.models import ModelForm, ModelFormMetaclass
from django.forms.forms import get_declared_fields, DeclarativeFieldsMetaclass
from django.utils.copycompat import deepcopy
class MixinFormMetaclass(ModelFormMetaclass, DeclarativeFieldsMetaclass):
def __new__(cls, name, bases, attrs):
# default __init__ that calls all base classes
def init_all(self, *args, **kwargs):
for base in bases:
super(base, self).__init__(*args, **kwargs)
attrs.setdefault('__init__', init_all)
# collect declared fields
attrs['declared_fields'] = get_declared_fields(bases, attrs, False)
# create the class
new_cls = super(MixinFormMetaclass, cls).__new__(cls, name, bases, attrs)
return new_cls
class MixinForm(object):
__metaclass__ = MixinFormMetaclass
def __init__(self, *args, **kwargs):
self.fields = deepcopy(self.declared_fields)
You can now derive your collections of formfields from MixinForm like this:
class SwallowFormFields(MixinForm):
airspeed_velocity = forms.IntegerField()
is_migratory = forms.BooleanField()
class MoreFormFields(MixinForm):
is_endangered = forms.BooleanField()
Then add them to the list of base classes like this:
class EuropeanSwallowForm(forms.Form, SwallowFormFields, MoreFormFields):
pass
class AfricanSwallowForm(forms.ModelForm, SwallowFormFields):
class Meta:
model = AfricanSwallow
So what does it do?
The metaclass collects all the fields declared in your MixinForm
It then adds custom __init__ constructors, to make sure that the __init__ method of the MixinForm gets magically called. (Otherwise you would have to call it explicitly.)
MixinForm.__init__ copies the declared fields int the field attribute
Please note that I am neither a Python guru nor a django developer, and that metaclasses are dangerous. So if you encounter weird behaviour better stick with the more verbose approach above :)
Good Luck!
How about a factory-style approach?
def form_factory(class_name, base, field_dict):
always_has = {
'airspeed_velocity': forms.IntegerField(some_important_details_here),
'is_migratory': forms.BooleanField(more_important_details)
}
always_has.update(field_dict)
return type(class_name, (base,), always_has)
def meta_factory(form_model):
class Meta:
model = form_model
return Meta
AfricanSwallowForm = form_factory('AfricanSwallowForm', forms.ModelForm, {
'other' = forms.IntegerField(some_important_details_here),
'Meta': meta_factory(AfricanBird),
})
EuropeanSwallowForm = form_factory('EuropeanSwallowForm', forms.Form, {
'and_a_different' = forms.IntegerField(some_important_details_here),
})
For that matter, you could modify the factory function here to look into an existing form class and pick out the attributes you want, so that you don't lose the declarative syntax...
class SwallowForm(forms.Form):
airspeed_velocity = forms.IntegerField()
is_migratory = forms.BooleanField()
class AfricanSwallowForm(forms.ModelForm, SwallowForm):
class Meta:
model = AfricanSwallow
class EuropeanSwallowForm(forms.Form, SwallowForm):
...
Should work.
I have some long running code that works and has the fields attr that looks like this.
languages_field = forms.ModelMultipleChoiceField(
queryset=Language.objects.all(),
widget=forms.CheckboxSelectMultiple,
required=False
)
class FooLanguagesForm(forms.ModelForm):
languages = languages_field
class Meta:
model = Foo
fields = ('languages', )
Notice that I still use the fields tuple in Meta. The field shows up in the fields dict on the form instance whether or not it is in the Meta.fields.
I have a regular form that uses this pattern as well:
genres_field = forms.ModelMultipleChoiceField(
queryset=blah,
widget=forms.CheckboxSelectMultiple,
#required=False,
)
class FooGenresForm(forms.Form):
genres = genres_field
I see that my fields dict is working.
In [6]: f = FooLanguagesForm()
In [7]: f.fields
Out[7]: {'languages': <django.forms.models.ModelMultipleChoiceField object at 0x1024be450>}
In [8]: f2 = FooGenresForm()
In [9]: f2.fields
Out[9]: {'genres': <django.forms.models.ModelMultipleChoiceField object at 0x1024be3d0>}
Create a subclass of the IntegerField
class AirspeedField(forms.IntegerField):
def __init__():
super(AirspeedField, self).__init__(some_important_details_here)
I have just made a snippet that resolves this issue in a DRY way:
https://djangosnippets.org/snippets/10523/
It uses crispy-form, but the same idea can be used without crispy-forms. The idea is to use multiple forms under the same form tag.