Django REST Framework relationship serialization - python

I've been banging my head against this issue and know I have to be missing something simple.
I'm trying to learn Django REST Framework and having issues setting the foreign keys of a new object to existing other objects when POSTing JSON to the server.
models.py
class Genre(models.Model):
name = models.CharField(max_length=200)
class Author(models.Model):
first_name = models.CharField(max_length=200)
last_name = models.CharField(max_length=200)
def full_name(self):
return self.first_name + ' ' + self.last_name
class Book(models.Model):
title = models.CharField(max_length=200)
genre = models.ForeignKey(Genre)
isbn = models.CharField(max_length=15, default='')
summary = models.CharField(max_length=500, null=True)
author = models.ForeignKey(Author)
serializers.py
class AuthorSerializer(serializers.ModelSerializer):
class Meta:
model = Author
fields = ('id', 'first_name', 'last_name',)
class GenreSerializer(serializers.ModelSerializer):
class Meta:
model = Genre
fields = ('id', 'name',)
class BookSerializer(serializers.ModelSerializer):
author = AuthorSerializer(read_only=True)
genre = GenreSerializer(read_only=True)
class Meta:
model = Book
fields = ('id','url', 'author', 'genre', 'title', 'isbn', 'summary',)
What I'm trying to is create a new book related to an existing Author and Genre. So, given some JSON like
{"title": "Title",
"author": {"id":1}
"genre" : {"id":2}
...
}
I want to create a new book and have its Genre and Author set to the appropriate entities that are already in the database.
I've tried to change the author and genre fields on BookSerializer to serializers.PrimaryKeyRelatedField() and scoured the docs and SO for answers, including this one. I've tried to change the fields in the JSON to "author": 1 or "genre_id": 2 but I can't seem to get it working. I keep getting django.db.utils.IntegrityError: books_book.genre_id may not be NULL.
I am using a DRF ModelViewSet for the views if that makes a difference.
What am I missing here?

You are getting Integrity error because it's expecting the author instance but you are sending the pk related to author. Try this
serializers.py
class BookSerializer(serializers.ModelSerializer):
author = AuthorSerializer(read_only=True)
genre = GenreSerializer(read_only=True)
class Meta:
model = Book
fields = ('id','url', 'author', 'genre', 'title', 'isbn', 'summary',)
def create(self, validated_data):
author_id = self.initial_data.pop("author")
genre_id = self.initial_data.pop("genre")
author = Author.objects.get(id=author_id)
genre = Genre.objects.get(id=genre_id)
book = Book.objects.create(author=author, genre=genre, **validated_data)
return book

Related

Unable to get related data from ManyToManyField

I'm trying to fetch related objects from below two models.
Following django models with ManyToManyField relationship.
Book
class Book(models.Model):
authors = models.ManyToManyField(
to=Author, verbose_name="Authors", related_name="books_author"
)
bookshelves = models.ManyToManyField(
to=Bookshelf, verbose_name="Bookshelf", related_name="books_shelves"
)
copyright = models.NullBooleanField()
download_count = models.PositiveIntegerField(blank=True, null=True)
book_id = models.PositiveIntegerField(unique=True, null=True)
languages = models.ManyToManyField(
to=Language, verbose_name=_("Languages"), related_name="books_languages"
)
Author
class Author(models.Model):
birth_year = models.SmallIntegerField(blank=True, null=True)
death_year = models.SmallIntegerField(blank=True, null=True)
name = models.CharField(max_length=128)
def __str__(self):
return self.name
class Meta:
verbose_name = _("Author")
verbose_name_plural = _("Author")
I have to fetch all the Auhtors with their related books. I have tried a lot of different ways none is working for me.
First way : using prefetch_related
class AuthorListAPIView(APIErrorsMixin, generics.ListAPIView):
serializer_class = AuthorSerializer
queryset = Author.objects.exclude(name__isnull=True)
def get_queryset(self):
auths = queryset.prefetch_related(Prefetch("books_author"))
Second way using related_name 'books_auhtor'
class AuthorListAPIView(APIErrorsMixin, generics.ListAPIView):
serializer_class = AuthorSerializer
queryset = Author.objects.exclude(name__isnull=True)
def get_queryset(self):
auths = queryset.books_author.all()
None of the above ways worked for me. I want to prepare a list of Authors and their associated books.
For ex:-
[{'Author1':['Book1','Book2'],... }]
Prefetching is not necessary, but can be used to boost efficiency, you can work with:
class AuthorListAPIView(APIErrorsMixin, generics.ListAPIView):
serializer_class = AuthorWithBooksSerializer
queryset = Author.objects.exclude(name=None).prefetch_related('books_author')
In the AuthorWithBooksSerializer, you can then add the data of the books, for example:
from rest_framework import serializers
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = ('book_id', 'copyright')
class AuthorWithBooksSerializer(serializers.ModelSerializer):
books = BookSerializer(source='books_author', many=True)
class Meta:
model = Author
fields = ('name', 'books')
Here the books will use the BookSerializer and thus encode a list of dictionaries.
While you can use the name of the author as object key, I strongly advise against this: it makes the object less accessible since the keys are no longer fixed and if these contain spaces, it can also result in more trouble obtaining the value(s) associated with a given attribute name.

Django restframework serializer with joining tables

I'm creating a django API for a discord bot for sounds recorded management.
Here are my models (simplified) :
class Member(models.Model):
name = models.CharField("Discord Name", max_length=100, unique=True)
discord_id = models.BigIntegerField("Discord ID", unique=True, blank=True, null=True)
class Sound(models.Model):
id = models.AutoField("Sound ID", primary_key=True)
file = models.FileField("Sound", upload_to='sounds/')
added_by = models.ForeignKey(Member, verbose_name="Author", on_delete=models.SET_NULL, blank=True, null=True)
tags = TaggableManager("Tags", blank=True)
class MemberinSound(models.Model):
sound = models.ForeignKey(Sound, on_delete=models.CASCADE, related_name="membersinsound")
personality = models.ForeignKey(Member, related_name='membersinsound', on_delete=models.CASCADE)
Explanations : a sound is uploaded by any member (added_by), with multiples optional tags, and personality that appears in this sound (MemberinSound).
You can imagine this 3 tables like (player, match, and playersinmatch).
My view to get a specific sound is :
class SpecificSound(APIView):
def get(self, request, id: int, formate=None, **kwargs):
sound = Sound.objects.select_related().get(id=id)
membersinsound = sound.membersinsound.all()
serializer = SoundSerializer(sound, many=False)
return Response(serializer.data)
And serializers.py :
class MemberInSoundSerializer(serializers.ModelSerializer):
class Meta:
model = MemberinSound
fields = [
'personality'
]
class SoundSerializer(serializers.ModelSerializer):
personality = MemberInSoundSerializer(many=True, read_only=True)
class Meta:
model = Sound
fields = [
'id',
'title',
'volume',
'file',
'stream_friendly',
'is_active',
'personality',
]
def to_representation(self, instance):
representation = super().to_representation(instance)
representation['path'] = instance.file.path
return representation
First problem I got is in my view where I send to my serializer only Sound model (without MemberinSound), I don't find how to get this in a one shot query. Maybe using annotate options ? Thought it could be possible with something like backward relationships shown in the documentation.
First time I use the restframework, maybe I made others mistakes.
For infos, I'm using Django 4.0.3 ; djangorestframework 3.13.1 and Python 3.8 with SQLITE3.

how can i get category name instead of the ID

I'm working on a small project using Django Rest Framework, I have two models ( contacts and category)
So a contact can be in a category, I have a foreign key between the models, I would like to know how can I get data category name instead of getting the id number.
This is my code :
class Category(models.Model):
cat_name = models.CharField(blank=False, max_length=255)
comment = models.CharField(blank=False, max_length=255)
private = models.BooleanField(default=False)
allowed = models.BooleanField(default=False)
def __str__(self):
return self.name
class Contact(models.Model):
category = models.ForeignKey(Category, on_delete=models.DO_NOTHING)
first_name = models.CharField(max_length=60)
last_name = models.CharField(max_length=60)
My serializer
class ContactSerializer(serializers.ModelSerializer):
class Meta:
model = Contact
fields = "__all__"
Result I get :
"first_name": "John",
"last_name": "Doe",
"category": 1 ( i want to get the name of the category instead of the id )
This is one possible solution
class ContactSerializer(serializers.ModelSerializer):
class Meta:
model = Contact
fields = "__all__"
def to_representation(self, obj):
return {
"first_name": obj.first_name,
"last_name": obj.last_name,
"category": obj.category.cat_name
}
Try this:
class ContactSerializer(serializers.ModelSerializer):
category_name = serializers.SerializerMethodField('get_category_name')
def get_category_name(self, obj):
if obj.category_id:
return obj.category.cat_name
return ""
class Meta:
model = Contact
fields = "__all__"
I got into same situation.I think there is no need to write another function if you can achieve this by one line of code and adding it to fields using source.You can also try this:
class ContactSerializer(serializers.ModelSerializer):
category = serializers.CharField(source="category.cat_name", read_only=True)
class Meta:
model = Contact
fields = ['first_name','last_name', 'category']

Unable to POST JSON data from multiple select element with Django REST Framework

I would like to be able to send an AJAX POST request to my API endpoint to create a new instance of my Asset model with multiple Category instances referenced in my Asset model, hence the many-to-many field type in my Asset model.
I'm able to successfully POST and create new Asset instances, however my category field won't accept any data at all. The category field remains empty when a new Asset instance is created. I think it has something to do with my CategorySerializer. I'm still learning how to use Django REST Framework so I'd appreciate if I could get some help figuring out how to work with serializers in Django REST Framework.
I've already tried modifying the AssetSerializer create method to handle parsing the JSON and validating the data but that hasn't worked. I've also tried other solutions suggested in other posts I've found on StackOverflow but haven't found anything that works for my situation.
Here's my serializers.py file:
class CategorySerializer(serializers.ModelSerializer):
name = serializers.CharField(required=False, read_only=True)
class Meta:
model = Category
fields = ('id', 'name')
class AssetSerializer(serializers.ModelSerializer):
name = serializers.CharField(allow_null=True)
description = serializers.CharField(allow_null=True)
manufacturer = serializers.CharField(allow_null=True)
uid = serializers.UUIDField(read_only=True, allow_null=True)
borrower = BorrowerSerializer(allow_null=True, read_only=True)
condition = serializers.ChoiceField(choices=Asset.CONDITION_TYPE, default='g', allow_null=True)
owner = serializers.ReadOnlyField(source='owner.username')
return_date = serializers.DateField(allow_null=True)
checked_out = serializers.BooleanField(allow_null=True)
category = CategorySerializer(required=False, many=True)
class Meta:
model = Asset
fields = ('uid',
'name',
'manufacturer',
'model',
'description',
'owner',
'condition',
'category',
'borrower',
'checked_out',
'return_date',
'is_dueback',
)
def update(self, instance, validated_data):
instance.borrower = validated_data.get('borrower', instance.borrower)
instance.return_date = validated_data.get('return_date', instance.return_date)
instance.checked_out = validated_data.get('checked_out', instance.checked_out)
instance.name = validated_data.get('name', instance.name)
instance.manufacturer = validated_data.get('manufacturer', instance.manufacturer)
instance.model = validated_data.get('model', instance.model)
instance.description = validated_data.get('description', instance.description)
instance.condition = validated_data.get('condition', instance.condition)
instance.category = validated_data.get('category', instance.category)
instance.save()
return instance
def create(self, validated_data):
return Asset.objects.create(**validated_data)
Here's my Asset model:
class Asset(models.Model):
"""Model representing an Asset"""
uid = models.UUIDField(primary_key=True, default=uuid.uuid4)
name = models.CharField(max_length=200)
manufacturer = models.CharField(max_length=64)
model = models.CharField(max_length=128)
description = models.TextField()
category = models.ManyToManyField(Category)
owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
borrower = models.ForeignKey(Borrower, on_delete=models.CASCADE, null=True, blank=True)
checked_out = models.BooleanField(default=False)
return_date = models.DateField(null=True, blank=True)
CONDITION_TYPE = (
('e', 'Excellent'),
('g', 'Good'),
('f', 'Fair'),
('p', 'Poor'),
)
condition = models.CharField(
max_length=1,
choices=CONDITION_TYPE,
blank=True,
help_text='Asset condition')
class Meta:
ordering = ['return_date']
#property
def is_dueback(self):
if self.return_date and date.today() > self.return_date:
return True
return False
def display_category(self):
"""Create a string for the Category. This is required to display category in Admin."""
return ', '.join(category.name for category in self.category.all())
display_category.short_description = 'Category'
def __str__(self):
return f'{self.uid} - {self.name}'
def get_absolute_url(self):
return reverse('asset-detail', args=[str(self.uid)])
Here's my Category model:
class Category(models.Model):
"""Model representing an Asset category"""
name = models.CharField(max_length=128)
def __str__(self):
return self.name
I'd appreciate any help you could provide. Thank you in advance.
i'm almost new in DRF but i try to help. why you writing all the field in serializer when you using ModelsSerializer? not need to telling ModelSerializer what type of field should be because you are pointing to model in class Meta and DRF know about fields and type and etc . second about allow_null=True in serializer, when Model haven't null=True you can't except DRF can create a not null-able field for instance with null=True so if you wnt a field can be null just add null=True in Model class . for your problem about ManytoMantry field try to use Primary key relation for ManyToMany fields in your serializers then pass id of Category instances in list:
class AssetSerializer(serializers.ModelSerializer):
borrower = BorrowerSerializer(allow_null=True, read_only=True)
category = serializers.PrimaryKeyRelatedField(many=True, queryset=Category.objects.all())
class Meta:
model = Asset
fields = ('uid',
'name',
'manufacturer',
'model',
'description',
'owner',
'condition',
'category',
'borrower',
'checked_out',
'return_date',
'is_dueback',
)
read_only_fields = ( 'uid' , ) # this fields will be read_only
depending on how you using this serializer in your view for save and update have difference way. if your view is generics class so will do create and update itself by POST and PUT method .and for other class view that isn't belong to generics DRF view you can using serializer.save() to create a new instance.wish help you.
pass data something like:
{
"name" : "foo",
"manufacture" : "foo",
.
.
.
"category" : [1,2,3,24,65]
}

Django many-to-many serialization

I want to create a model (Source) with many-to-many relation to the another model (Tag) and create a Source objects without duplicating Tag instance in database.
Here is my models:
class Tag(models.Model):
name = models.CharField(max_length=50, null=False, default='source')
def __unicode__(self):
return self.name
class Source(models.Model):
title = models.CharField(max_length=200)
author = models.CharField(max_length=200)
language = models.CharField(max_length=50)
color = models.CharField(max_length=50, default='white')
isFile = models.BooleanField(default=False)
link = models.TextField(default='')
file = models.FileField(upload_to='uploads/', null=True)
tags = models.ManyToManyField('Tag')
class Meta:
ordering = ('title',)
Here is my serializers:
class TagSerializers(serializers.HyperlinkedModelSerializer):
class Meta:
model = Tag
fields = ('name',)
class SourceSerializers(serializers.ModelSerializer):
tags = TagSerializers(many=True)
class Meta:
model = Source
fields = ('title', 'author', 'language', 'color', 'isFile', 'link', 'file', 'tags')
def create(self, validated_data):
tags_data = validated_data.pop('tags')
source = Source.objects.create(**validated_data)
for tag in tags_data:
t = Tag.objects.create()
t.name = tag.get("name")
t.save()
source.tags.add(t)
source.save()
return source
But when I try to create Source object via http request - the object is created, but without any references to Tags. After some researching I found that validated_data in create(self, validated_data) doesn't contains "tags" field, also I found that validate function of TagSerializer not invoked at any time. What I'm doing wrong?
Use get_or_create method to create Tag object.
def create(self, validated_data):
tags_data = validated_data.pop('tags')
source = Source.objects.create(**validated_data)
for tag in tags_data:
name = tag.get("name")
t = Tag.objects.get_or_create(name=name)
t.save()
source.tags.add(t)
source.save()
return source
Seems the problem was in my requests, without many-to-many relation we can use form-data and all is good, but when we add mant-to-many relation we can't use form-data anymore and have to use only application\json

Categories

Resources