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)
Related
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 = [...]
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.e. we have SomeSeries with several SomeDecors, where ForeignKey of SomeDecor points to SomeSeries. I want both to be abstract and later instantiate several pairs of it (with it's own tables in db). Is it possible?
I.e.
class SomeSeries(models.Model):
class Meta:
abstract = True
vendor = models.ForeignKey(Vendor)
name = models.CharField(max_length=255, default='')
def __unicode__(self):
return "{} {}".format(self.vendor, self.name)
class SomeDecor(WithFileFields):
class Meta:
abstract = True
series = models.ForeignKey(SomeSeries) # some magic here to make ForeignKey to abstract model
texture = models.ImageField()
# -------------------------------------------
class PlinthSeries(SomeSeries): pass
class PlinthDecor(SomeDecor): pass
# Some magic to make PlinthDecor.series points to PlinthSeries
EDIT
Actually I don't want complicity of polymorphic relations, I want pure abstract models just to save typing (what abstract models are initially for). Suppose in my case the simplest way is to exclude ForeignKey from base model and type it only in all inherited models:
class SomeSeries(models.Model):
class Meta:
abstract = True
#...
class SomeDecor(WithFileFields):
class Meta:
abstract = True
series = None #?
#..
texture = models.ImageField()
def do_anything_with_series(self): pass
class PlinthSeries(SomeSeries): pass
class PlinthDecor(SomeDecor): pass
series = models.ForeignKey(PlinthSeries)
You can't create ForeignKey referencing abstract model. It's, even, doesn't make any sense, because ForeignKey translates into Foreign Key Constraint which have to reference existing table.
As a workaround, you can create GenericForeignKey field.
You can not do it because if you create two class inherit from your abstract class to what class your foreignkey should do? for first or for second?
So you need to create GenericForeignKey or not do any field and only after create model inherits from your abstract model add your foreign key.
Basically, I want to filter out inactive users from a related field of a ModelSerializer. I tried Dynamically limiting queryset of related field as well as the following:
class MySerializer(serializers.ModelSerializer):
users = serializers.PrimaryKeyRelatedField(queryset=User.objects.filter(active=True), many=True)
class Meta:
model = MyModel
fields = ('users',)
Neither of these approaches worked for just filtering the queryset. I want to do this for a nested related Serializer class as a field (but couldn't even get it to work with a RelatedField).
How do I filter queryset for nested relation?
I'll be curious to see a better solution as well. I've used a custom method in my serializer to do that. It's a bit more verbose but at least it's explicit.
Some pseudo code where a GarageSerializer would filter the nested relation of cars:
class MyGarageSerializer(...):
users = serializers.SerializerMethodField('get_cars')
def get_cars(self, garage):
cars_queryset = Car.objects.all().filter(Q(garage=garage) | ...).select_related()
serializer = CarSerializer(instance=cars_queryset, many=True, context=self.context)
return serializer.data
Obviously replace the queryset with whatever you want. You don't always need the to give the context (I used it to retrieve some query parameters in the nested serializer) and you probably don't need the .select_related (that was an optimisation).
One way to do this is to create a method on the Model itself and reference it in the serializer:
#Models.py
class MyModel(models.Model):
#...
def my_filtered_field (self):
return self.othermodel_set.filter(field_a = 'value_a').order_by('field_b')[:10]
#Serialziers.py
class MyModelSerialzer(serializers.ModelSerializer):
my_filtered_field = OtherModelSerializer (many=True, read_only=True)
class Meta:
model = MyModel
fields = [
'my_filtered_field' ,
#Other fields ...
]
Another way to avoid the SerializerMethodField solution and therefore still allow writing to the serializer as well would be to subclass the RelatedField and do the filtering there.
To only allow active users as values for the field, the example would look like:
class ActiveUsersPrimaryKeyField(serializers.PrimaryKeyRelatedField):
def get_queryset(self):
return super().get_queryset().filter(active=True)
class MySerializer(serializers.ModelSerializer):
users = ActiveUsersPrimaryKeyField(many=True)
class Meta:
model = MyModel
fields = ('users',)
Also see this response.
Note that this only restricts the set of input values to active users, though, i.e. only when creating or updating model instances, inactive users will be disallowed.
If you also use your serializer for reading and MyModel already has a relation to a user that has become inactive in the meantime, it will still be serialized. To prevent this, one way is to filter the relation using django's Prefetch objects. Basically, you'll filter out inactive users before they even get into the serializer:
from django.db.models import Prefetch
# Fetch a model instance, eagerly prefetching only those users that are active
model_with_active_users = MyModel.objects.prefetch_related(
Prefetch("users", queryset=User.objects.filter(active=True))
).first()
# serialize the data with the serializer defined above and see that only active users are returned
data = MyModelSerializer(model_with_active_users).data
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