ModelSerializer using model property - django-rest-framework

I'm trying to serialize a model containing a property field that I also want to serialize.
models.py:
class MyModel(models.Model):
name = models.CharField(max_length=100)
slug = models.AutoSlugField(populate_from='name')
#property
def ext_link(self):
return "/".join([settings.EXT_BASE_URL, self.slug])
serializers.py:
class MyModelSerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
fields = ('name', 'ext_link')
When trying to get to the related URL, I'm getting a serializer exception (KeyError) on the ext_link property.
How can I serialize the ext_link property?

Because it's not a model field, it needs to be added explicitly to the serializer class
class MyModelSerializer(serializers.ModelSerializer):
ext_link = serializers.Field()
class Meta:
model = MyModel
fields = ('name', 'ext_link')

as #Robert Townley's comment, this work with version 3.8.2:
class MyModelSerializer(serializers.ModelSerializer):
ext_link = serializers.ReadOnlyField()
class Meta:
model = MyModel
fields = "__all__"

The accepted answer doesn't seem to work for me, nor does the ReadOnlyField.
However, I have had success when I use a field that corresponds to the return type of my property function.
So for the example, I would do this:
class MyModelSerializer(serializers.ModelSerializer):
ext_link = serializers.CharField()
class Meta:
model = MyModel
fields = ('name', 'ext_link')
I've been able to do this with ListField, DictField, and IntegerField as well.

Another thing you might want to do is add a property that its contents are not a string. Let's say you have a model called Person and another one called Food that look like this (we assume that each food is the favorite of only one person, making it a OneToMany connection):
class Person(models.Model):
name = models.CharField(max_length=255)
#property
def favorite_foods(self):
return Food.objects.filter(person=self.pk)
class Food(models.Model):
name = models.CharField(max_length=255)
persons_favorite = models.ForeignKey(Person, on_delete=models.CASCADE)
If you want to add favorite_foods in Person's serializer all you have to do is:
class PersonSerializer(serializers.ModelSerializer):
favorite_foods = FoodSerializer(read_only=True, many=True)
class Meta:
model = Person
fields = ('name', 'favorite_foods')

Related

Atrribute from nested relations is not read in serializer

I'm now using DRF as a backend of my project.
i have product model like this
class product(models.Model):
product_name = models.CharField(max_length=160)
i have category model like this
class category(models.Model):
category_name = models.CharField(max_length=60)
category_icon = models.ImageField(upload_to='category)
because 1 product can have multiple category and a lot of image I create
class product_category(models.Model):
product = models.ForeignKey(product, on_delete=models.CASCADE, related_name='product_collections')
category = models.ForeignKey(category, on_delete=models.CASCADE, related_name='category_collections')
and the last model
class product_image(models.Model):
product = models.ForeignKey(product, on_delete=models.CASCADE,related_name='image_collections')
product_img = models.ImageField(upload_to='product')
Now I have Serializer like this
class ProductCategorySerializer(serializers.ModelSerializer):
category_name = serializers.CharField(source='category.category_name')
class Meta:
model = product_category
fields = ('product_id','category_id','category_name')
class ProductImageSerializer(serializers.ModelSerializer):
class Meta:
model = product_images
fields = ('product_img',)
class ProductSerializer(serializers.ModelSerializer):
category_collections = CategoryProductSerializers(many=True)
image_collections = ProductImageSerializer(many=True)
class Meta:
model = product
fields = ('id','product_name','image_collections','category_collections')
From that serializer DRF will return
Error like this
Got AttributeError when attempting to get a value for field category_collections on serializer ProductSerializer.
The serializer field might be named incorrectly and not match any attribute or key on the product instance.
but if i remove that category_collections field like this
class ProductSerializer(serializers.ModelSerializer):
# category_collections = CategoryProductSerializers(many=True)
image_collections = ProductImageSerializer(many=True)
class Meta:
model = product
fields = ('id','product_name','image_collections')
Everything is going fine, whats wrong with that categories collection, is my eye not seeing the mistake ?

Custome the display's field in django REST

I use Django REST and I would know if it is possible to customise the display of attributes in the json response.
Exemple :
class MyModel(models.Model):
name = models.CharField(max_length=300)
and my serializer :
class MyModelSerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
fields = ['name']
But instead to see {'name' : 'its value'}, I would see {'My customed model name' : 'its value'}.
Do you think that it's possible?
Thank you very much.
You can override the to_representation method of the serializer to change the name of the field:
class MyModelSerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
fields = ['name']
def to_representation(self, instance):
ret = super().to_representation(instance)
# ret is an OrderedDict, so this will change the order of the result.
ret['custom_name'] = ret.pop('name')
return ret
def to_internal_value(self, data):
# if you want to write to the serializer using your custom name.
data['name'] = data.pop('custom_name')
return super().to_internal_value(data)
One way you could do it is using a SerializerMethodField (https://www.django-rest-framework.org/api-guide/fields/#serializermethodfield)
class MyModelSerializer(serializers.ModelSerializer):
my_customed_model_name = serializers.SerializerMethodField()
def get_my_customed_model_name(self, obj):
return obj.name
class Meta:
model = MyModel
Although if you want the field name to have spaces in it, this solution won't work for you.
You can do this in this way
class MyModelSerializer(serializers.ModelSerializer):
other_name = serializers.CharField(source='name')
class Meta:
model = MyModel
fields = ['other_name']

How to serialize a property from another model in django rest?

I have two models and two serializers. I'm trying to use a property from one this models.
I already tried using foo = serializers.ReadOnlyField() but doesn't work.
My example:
class ExampleOne(models.Model):
example_three = models.ForeignKey(
"foo.ExampleThree",
on_delete=models.CASCADE,
related_name="ones",
)
#property
def foo(self):
return bar
class ExampleTwo(models.Model):
example_four = models.ForeignKey(
"foo.ExampleFour",
on_delete=models.CASCADE,
related_name="twos",
)
class ExampleThree(models.Model):
example_four = models.ForeignKey(
"foo.ExampleFour",
on_delete=models.CASCADE,
related_name="threes",
)
class ExampleFour(models.Model):
...
fields=...
...
class ExampleOneSerializer(serializers.Serializer):
class Meta:
model = ExampleOne
fields = "__all__"
class ExampleTwoSerializer(serializers.Serializer):
foo = serializers.ReadOnlyField()
class Meta:
model = ExampleTwo
fields = "__all__"
Does anyone have any idea how I can get this porperty from another serializer?

How can i add #property field to django rest serializer with '__all__'

I have model
class A(models.Model):
is_enable = models.BooleanField(default=False)
title = models.CharField(max_length=255, blank=True, null=True)
updated_date = models.DateTimeField(auto_now=True)
show_count = models.IntegerField(default=0)
answers_count = models.IntegerField(default=0)
audience = JSONField()
events = JSONField()
rules = JSONField()
message = models.TextField(blank=True)
#property
def conversion(self):
if self.show_count == 0:
return 0.0
return (self.answers_count / self.show_count) * 100
And i have serializer
class ASerializer(serializers.ModelSerializer):
audience = serializers.JSONField()
events = serializers.JSONField()
rules = serializers.JSONField()
class Meta:
model = Trigger
fields = '__all__'
I want to add to response #property conversion` field
and I want to do something like this in serializer
class Meta:
model = Trigger
fields = '__all__' + conversion
I know that i can make something like this
class Meta:
model = Trigger
fields = ('is_enable', 'title' ... 'conversion')
But i want add all fields and conversion field and do this more beautiful
you can use somethig like this:
class ModelMixin:
#classmethod
def _get_model_fields(cls):
all_fields = cls._meta.get_fields()
fields = [i.name for i in all_fields if i.__class__.__name__ not in ['ManyToManyRel', 'GenericRelation', 'ManyToOneRel']]
return fields
#classmethod
def get_serializer_fields(cls):
fields = cls._get_model_fields()
return fields
class A(ModelMixin, models.Model):
...
class ASerializer(ModelSerializer):
custom_field = serializers.JSONField()
class Meta:
model = A
fields = A.get_serializer_fields()
fields.append('cusom_field')
Might still not be as clean as you want it to be, but using <model>._meta.fields you can get all fields of that model, and using the name property of each field you can retrieve the names that refer to them.
As the serializers file is interpreted as python, it supports code execution, meaning you can use all python functionalities when setting properties, including a for loop, which gives us the ability to do this:
fields = (tuple((f.name for f in A._meta.fields)) + ('conversion',))
Needed this for my own model as well and there it seemed to work, so hopefully so does it for you.
You can create a SerializerMethodField in your serializer and later define a method in serializer itself that can call your functions you defined in models using #property
class ASerializer(serializers.ModelSerializer):
audience = serializers.JSONField()
events = serializers.JSONField()
rules = serializers.JSONField()
conversion = serializers.SerializerMethodField()
class Meta:
model = Trigger
fields = '__all__'
def get_conversion(self,obj):
return obj.conversion
This should allow you to keep your code beautiful without modifying much of your code.

Is there more better cancel nested ManyToMany field unique check

# models.py
class Student(models.Model):
name = models.CharField( max_length=256)
student_numer = models.CharField(max_length=256)
teachers = models.ManyToManyField(Teacher)
class Teacher(models.Model):
name = models.CharField(max_length=256)
# serializers.py
class StudentSerializer(serializers.ModelSerializer):
class Meta:
model = Student
fields = ('name', 'student_number', )
class TeacherSerializer(serializers.ModelSerializer):
students = StudentSerializer(many=True)
class Meta:
model = Teacher
fields = '__all__'
def update(self, instance, validated_data):
students = validated_data.pop('students')
# here I want delete all old students, and add all new students
# of course, I have more better way to update it, but here just for simple.
But in fact, if I update teacher instance, and if the students not change, it will
raise: student with this student_numer already exist.
I know why, because StudentSerializer's student_numer field has the validator check unique.
And I can add some code like this to fixed this problem:
class StudentSerializer(serializers.ModelSerializer):
class Meta:
model = Student
fields = ('name', 'student_number', )
extra_kwargs = {
'student_numer': {
'validators':[]
}
}
Now I want to know is there any more better way ??

Resources