Serializing/deserializing a foreign key in django rest framework - django

I'm using the Django rest framework to create an API. Say I have the following models:
class Category(models.Model):
name = models.CharField(max_length=100)
def __unicode__(self):
return self.name
class Item(models.Model):
name = models.CharField(max_length=100)
category = models.ForeignKey(Category, related_name='items')
def __unicode__(self):
return self.name
I want to write a serializer for Item such that it returns "category_name" during serialization (i.e. read) but then also takes "category_name" back and converts it into category during deserialization (i.e. write) [you can assume that category_name is unique in its table]
I was able to find this link which solves the problem for reads but doesn't work for writes.
class ItemSerializer(serializers.ModelSerializer):
category_name = serializers.CharField(source='category.name')
class Meta:
model = Item
fields = ('id', 'name', 'category_name')
Is there a way to modify it such that it can be used to populate new items via an API?

SlugRelatedField is what you're looking for. This is both read and write.
class ItemSerializer(serializers.ModelSerializer):
category = serializers.SlugRelatedField(
slug_field='name',
queryset=Category.objects.all()
)
class Meta:
model = Item
fields = ('id', 'name', 'category')
Precision: you need to make your category.name unique=True in your models.py.

Related

Property field not appearing in django serializer

I have a property inside a Django model, I have to show it inside the serializer. I put the field inside the serializer, but it's not coming up in the response.
class Example(models.Model):
field_1 = models.ForeignKey(
Modelabc, on_delete=models.CASCADE, null=True, related_name="abc"
)
field_2 = models.ForeignKey(
Modelxyz,
on_delete=models.CASCADE,
null=True,
related_name="xyz",
)
name = models.CharField(max_length=25, blank=True)
#property
def fullname(self):
if self.name is not None:
return "%s%s%s" % (self.field_1.name, self.field_2.name, self.name)
return "%s%s" % (self.field_1.name, self.field_2.name)
Serializer is like this:
class ExampleSerializer(serializers.ModelSerializer):
fullname = serializers.ReadonlyField()
class Meta:
model = Example
fields = [
"id",
"fullname",]
When I call the get API for this, the fullname is not being displayed in the api response. What is the issue?
#property attributes are not included in Django Serializer fields as only Django model fields are shown. I normally use the following workaround for this.
Create a SerializerMethodField.
Return object.property from the method.
So, your Serializer class would be:
class ExampleSerializer(serializers.ModelSerializer):
fullname = serializers.SerializerMethodField()
class Meta:
model = OnlineClass
fields = [
"id",
"fullname",
]
def get_fullname(self, object):
return object.fullname
I think, in ExampleSerializer class, the model should be Example not OnlineClass and the fields should contain all the fields inside the model.

Can't render nested relationship in Django Rest Framework

The problem is I have a 'details' field which should render into a nested relationship with it's parent serializer. I have tried a bunch of stuff and nothing seems to be working.
Here's my models:
class BusinessOrderModel(OrderToModel):
reference = models.IntegerField()
business_num = models.ForeignKey('BusinessModel', on_delete=models.CASCADE)
def __str__(self):
return str(self.reference)
class BusinessModel(models.Model):
Business_num = models.IntegerField(primary_key=True)
def __str__(self):
return str(self.Business_num)
class DetailModel(models.Model):
id = models.AutoField(primary_key=True)
detail = models.TextField()
order = models.ForeignKey('BusinessOrderModel', on_delete=models.CASCADE)
and here's my serializers which aren't working:
class DetailSerializer(serializers.ModelSerializer):
class Meta:
model = DetailModel
fields = ('id', 'detail')
class BusinessOrderSerializer(serializers.ModelSerializer):
details = DetailSerializer(many=True)
class Meta:
model = BusinessOrderModel
fields = ('reference', 'business_num', 'details')
I've tried many different things but I get this error:
Got AttributeError when attempting to get a value for field details
on serializer BusinessOrderSerializer. The serializer field might be
named incorrectly and not match any attribute or key on the
BusinessOrderModel instance. Original exception text was:
'BusinessOrderModel' object has no attribute 'details'.
Any help is much appreciated.
Thank you very much.
Using details to lookup reverse relationships only works if you set it as the related_name. The default for BusinessOrderModel to DetailModel will be detailmodel_set.
To make it accessible by calling details you should make this change:
class DetailModel(models.Model):
id = models.AutoField(primary_key=True)
detail = models.TextField()
order = models.ForeignKey('BusinessOrderModel', related_name="details", on_delete=models.CASCADE)
Now you can use DetailModel.objects.get(id=1).details.all()
You can also customize the query in your serializer:
class BusinessOrderSerializer(serializers.ModelSerializer):
details = SerializerMethodField()
class Meta:
model = BusinessOrderModel
fields = ('reference', 'business_num', 'details')
def get_details(self, obj):
return DetailSerializer(obj.details.filter(), many=True).data

DRF ListSerializer and ListField

I use django rest in my project and until now for list of objects I used ListSerializer, when I needed to have min length and max length of list I googled and reached to ListField.
Before that my code worked fined without any error and misbehavior. Now I use ListField for my list field serializer, But I didn't get when to use ListSerializer? Can someone explain the difference between ListSerializer and FieldSerializer?
My sample code with ListSerializer:
tags = serializers.ListSerializer(child=serializers.CharField(allow_blank=False), required=False)
My sample code with ListField:
open_hour = serializers.ListField(child=serializers.DictField(), max_length=7, min_length=7)
Disclaimer: This answer is not complete
Can someone explain the difference between ListSerializer and
FieldSerializer?
I assume the question is difference between serializers.ListSerializer and serializers.ListField
Suppose we have two models as
class Musician(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
instrument = models.CharField(max_length=100)
def __str__(self):
return f'{self.first_name} {self.last_name}'
class Album(models.Model):
artist = models.ForeignKey(Musician, on_delete=models.CASCADE)
name = models.CharField(max_length=100)
release_date = models.DateField()
num_stars = models.IntegerField()
def __str__(self):
return f'{self.name} : {self.artist}'
and serializer as
class AlbumSerializer(serializers.ModelSerializer):
artist = serializers.StringRelatedField()
class Meta:
fields = '__all__'
model = Album
class MusicianSerializer(serializers.ModelSerializer):
AlbumSerializer(many=True, source='album_set')
class Meta:
fields = '__all__'
model = Musician
ListSerializer
As stated in official DRF doc
When a serializer is instantiated and many=True is passed, a
ListSerializer instance will be created. The serializer class then
becomes a child of the parent ListSerializer
For example, we could re-write the MusicianSerializer with ListSerializer as
class MusicianSerializer(serializers.ModelSerializer):
albums = serializers.ListSerializer(child=AlbumSerializer(), source='album_set')
class Meta:
fields = '__all__'
model = Musician
it would produce the results same as before. But, if we are trying to use ListField instead of ListSerializer It will raise an error
'RelatedManager' object is not iterable
When I checked the source code, I found that both ListSerializer and ListField are inherited from the same class (parent and grand parent are same)
I ran into this same problem and I believe I found a solution!
The trick is you need to create a new Serializer that inherits the ListSerializer class and override the to_representation() method to output your desired format.
If you look at the DRF source code for ListSerializer you can see the default to_representation() method looks like the following...
def to_representation(self, data):
"""
List of object instances -> List of dicts of primitive datatypes.
"""
# Dealing with nested relationships, data can be a Manager,
# so, first get a queryset from the Manager if needed
iterable = data.all() if isinstance(data, models.Manager) else data
return [
self.child.to_representation(item) for item in iterable
]
Example
models
class Musician(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
instrument = models.CharField(max_length=100)
def __str__(self):
return f'{self.first_name} {self.last_name}'
class Album(models.Model):
artist = models.ForeignKey(Musician, on_delete=models.CASCADE)
name = models.CharField(max_length=100)
release_date = models.DateField()
num_stars = models.IntegerField()
def __str__(self):
return f'{self.name} : {self.artist}'
serializers
class AlbumSerializer(serializers.ModelSerializer):
artist = serializers.StringRelatedField()
class Meta:
fields = '__all__'
model = Album
class AlbumKeyValueSerializer(serializers.ListSerializer):
def to_representation(self, data):
reaction_count_set = {}
for item in data.all():
reaction_count_set[item.name] = item.artist
return reaction_count_set
class MusicianSerializer(serializers.ModelSerializer):
AlbumKeyValueSerializer(child=AlbumSerializer(), source='album_set')
class Meta:
fields = '__all__'
model = Musician

Store the IDs in the Parent Model of the Child Models which are currently related via ForeignKeys

I have three models, currently i am using an url like so to do updates and get content:
http://localhost:8000/manuscripts-api/manuscriptlibrary/28/
My question relates to the approach i should use so that i can include in my ManuscriptItem model the IDs of the related Libraries and Settings. What kind of fields can i add to the ManuscriptItem model that does this?
My models:
class ManuscriptItem(models.Model):
"""Represents a single manuscript's content"""
author = models.ForeignKey('accounts_api.UserProfile', on_delete=models.CASCADE)
title = models.CharField(max_length=255, blank=True)
content = models.CharField(max_length=99999999, blank=True)
def __str__(self):
"""Django uses when it needs to convert the object to a string"""
return str(self.id)
class ManuscriptLibrary(models.Model):
"""Represents a single manuscript's library"""
manuscript = models.OneToOneField(ManuscriptItem, on_delete=models.CASCADE)
bookmarks = models.CharField(max_length=99999999)
history = models.CharField(max_length=99999999)
def __str__(self):
"""Django uses when it needs to convert the object to a string"""
return str(self.manuscript)
class ManuscriptSettings(models.Model):
"""Represents a single manuscript's settings"""
manuscript = models.OneToOneField(ManuscriptItem, on_delete=models.CASCADE)
citation_suggestions = models.BooleanField(default=False)
terminology_suggestions = models.BooleanField(default=False)
paper_suggestions = models.BooleanField(default=False)
def __str__(self):
"""Django uses when it needs to convert the object to a string"""
return str(self.manuscript)
My serializers:
class ManuscriptItemSerializer(serializers.ModelSerializer):
"""A serializer for manuscript items."""
class Meta:
model = models.ManuscriptItem
fields = ('id', 'author', 'title', 'content')
extra_kwargs = {'author': {'read_only': True}}
class ManuscriptLibrarySerializer(serializers.ModelSerializer):
"""A serializer for a manuscript's library."""
class Meta:
model = models.ManuscriptLibrary
fields = ('id', 'manuscript', 'bookmarks', 'history')
class ManuscriptSettingsSerializer(serializers.ModelSerializer):
"""A serializer for a manuscript's settings."""
class Meta:
model = models.ManuscriptSettings
fields = ('id', 'manuscript', 'citation_suggestions', 'terminology_suggestions', 'paper_suggestions')
You don't necessarily need to add any new fields to the ManuscriptItem model. You can access the id of the related ManuscriptLibrary and ManuscriptSettings objects by defining the related_name property of the foreign key.
class ManuscriptLibrary(models.Model):
manuscript = models.OneToOneField(ManuscriptItem, on_delete=models.CASCADE, related_name='library')
class ManuscriptSettings(models.Model):
manuscript = models.OneToOneField(ManuscriptItem, on_delete=models.CASCADE, related_name='setting')
Once this is migrated, you can use manuscript_item.library to access the related library object, and manuscript_item.setting to access the related setting. Accessing ids can be done via manuscript_item.library.id.
Edit: To display the ids in the serialized object, you can modify your ManuscriptItemSerializer as given below
class ManuscriptItemSerializer(serializers.ModelSerializer):
library = ManuscriptLibrarySerializer(required=False)
setting = ManuscriptSettingsSerializer(required=False)
class Meta:
model = models.ManuscriptItem
fields = ('id', 'author', 'title', 'content', 'library', 'setting', )
by the docs one_to_one
your ManuscriptItem instance has two property manuscriptlibrary -- instance of the ManuscriptLibrary model and manuscriptsettings instance of the ManuscriptSettings model. So you can get the id by manuscriptlibrary.pk and manuscriptsettings.pk, but may be best solution for greater readability you can use related_name as arjun27 write.

ManyToManyField Serializer throws "This field must be unique" error

I am trying to create a Many-To-Many relationship between two models- Author and Book. My use-case is that I should be able to add a new book to the database with an author that already exists in the database.
models.py
class Author(models.Model):
author_id = models.CharField(max_length=20, primary_key=True)
name = models.CharField(blank=True, null=True)
def __unicode__(self):
return self.name
class Meta:
ordering = ('author_id',)
class Book(models.Model):
title = models.CharField(max_length=50, primary_key=True)
authors = models.ManyToManyField(Author)
def __unicode__(self):
return self.title
class Meta:
ordering = ('title',)
serializers.py
class AuthorSerializer(serializers.ModelSerializer):
class Meta:
model = Author
fields = ('author_id', 'name')
class BookSerializer(serializers.ModelSerializer):
authors = AuthorSerializer(many=True)
class Meta:
model = Book
fields = ('title', 'authors')
def create(self, validated_data):
book = Book.objects.create(name=validated_data['title'])
for item in validated_data['authors']:
author = Author.objects.get(author_id=item['author_id'])
book.authors.add(author)
return book
Let's say my Author table already has an Author:
1, George RR Martin
Now if I want to add a new book with an existing author, this is the request I send using httpie:
http -j POST http://localhost/books title="The Winds of Winter" authors:='[{"author_id":"1"}]'
and when I do, I get this error:
Output Error
{
"authors": [
{
"author_id": [
"This field must be unique."
]
}
]
}
It seems like the AuthorSerializer is being called which checks the provided author_id against the ones in the database already and throws this error.
Any help on this would be appreciated.
Is there a specific reason you have to use a custom PK field?
Django automatically creates primary key fields for you. If you simply delete that field from your model and your serializer (and create/run a migration on your database), you won't have to specify the pk in your POST call from your frontend, and Django will create an AutoField that auto-increments your model's id:
class Author(models.Model):
# Remove this line and run makemigrations.
# author_id = models.CharField(max_length=20, primary_key=True)
name = models.CharField(blank=True, null=True)
def __unicode__(self):
return self.name
class Meta:
ordering = ('author_id',)
If not, consider using an models.AutoField rather than models.CharField for your primary key field, and again, don't include this in your POST call.
Note, that if you already have a big database created, you might have to do some intricate work in your migration, a la this answer: