How do I reference other FIELD using foreign key in Django? - python

So I'm using Django and have a foreignkey field. Let me show you the model first.
class Book(models.Model):
objects = models.Manager()
title = models.CharField(max_length = 30)
author = models.CharField(max_length = 20)
class Content(models.Model):
objects = models.Manager()
source = models.ForeignKey("Book", related_name='book', on_delete=models.CASCADE)
key_line = models.CharField(max_length = 100, null=True)
I used serializer to load the api to my React front end. But then, the source field is displayed as integer, which probably is the id of Book model.
However what I want to do is load the title of each book in the source field.
Any advice?
FYI, other codes.
views.py
#api_view(['GET'])
def each_book(request, pk):
this_book = Content.objects.get(pk=pk)
serialized = ContentSerializer(this_book, context={'request':request})
return Response(serialized.data)
serializers.py
class ContentSerializer(serializers.ModelSerializer):
class Meta:
model = Content
fields = '__all__'

You could just pass book to the context field and call it like:
#api_view(['GET'])
def each_book(request, pk):
this_book = Content.objects.get(pk=pk)
serialized = ContentSerializer(this_book, context={'request':request, 'book': this_book})
return Response(serialized.data)
Then
class ContentSerializer(serializers.ModelSerializer):
book_title = serializers.SerializerMethodField()
class Meta:
model = Content
fields = '__all__'
def get_book_title(self, obj): # Note that obj is `content` in this case.
return self.context['book'].title
Make sure you include it in your fields too. Not sure if it works with __all__. If it doesn't, then just explicitly write all your fields out with the book_title field included.

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.

How to limit the number of records in the Serializer

Hello everyone, how to limit the number of results using a serializer?
In short, there is a table of comments, which can contain different types of posts.
class CourseComment(models.Model):
user = models.ForeignKey(User, on_delete=models.PROTECT)
content = models.TextField()
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id')
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
and here related table
class CourseMessage(models.Model):
course_id = models.ForeignKey(Course, on_delete=models.PROTECT)
author_id = models.ForeignKey(User, on_delete=models.PROTECT)
text = models.TextField() # RAW Format must exclude specials chars before publish
is_pinned = models.BooleanField(default=False)
comments = GenericRelation('CourseComment')
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
I made the serializer according to the documentation
https://www.django-rest-framework.org/api-guide/relations/
class CourseMessages(serializers.ModelSerializer):
user = Author(source='authorid', read_only=True)
files = MessageFiles(source='coursemessageattachedfile_set', many=True)
message_comments = MessageComments(source='comments', many=True, read_only=True)
class Meta:
model = CourseMessage
fields = ['text', 'updated_at', 'user', 'files', 'message_comments']
class MessageComments(serializers.RelatedField):
def to_representation(self, value):
ct = ContentType.objects.get_for_model(value)
serializer = Comments(value, read_only=True, source='last_comments')
return serializer.data
class Comments(serializers.ModelSerializer):
author = Author(source='user', read_only=True)
class Meta:
model = CourseComment
fields = ['content', 'author']
Everything works well, but I would like to see first 3 comments.
Maybe someone has encountered such a problem, or can advise how to do it better.
I get this data for the RetrieveAPIView detail page. The first three comments are required for display on the front.
requirements
Django==3.2.5
djangorestframework==3.12.4
Thanks in advance :)
I think you gain nothing by instructing the serializer to return a subset of a resultset. I think the best way is to filter the subset when making the query, or filtering the array after serializing:
result_set = CourseComment.objects.all()[:3] # model
three_first = Comment(result_set, many=True).data # serializer
or
result_set = CourseComment.objects.all() # model
three_first = Comment(result_set, many=True).data[:3] # serializer
Or you can even send a subset of the resultset to the serializer:
result_set = CourseComment.objects.all() # model
three_first = Comment(result_set[:3], many=True).data # serializer
I think what you are looking for is the SerializerMethodField()
So your code should look like this:
class CourseMessages(serializers.ModelSerializer):
user = Author(source='authorid', read_only=True)
files = MessageFiles(source='coursemessageattachedfile_set', many=True)
message_comments = serializers.SerializerMethodField()
def get_message_comments(self, obj):
message_comments = Comments.objects.all()[:3]
return MessageComments(message_comments, source='comments', many=True, read_only=True).data
class Meta:
model = CourseMessage
fields = ['text', 'updated_at', 'user', 'files', 'message_comments']
If you are using a queryset as:
class ViewName(generics.RetriveAPIView):
permission_classes = [IsAuthenticated]
serializer_class = Comments
queryset = CourseComment.objects.all()
then you can use inside your list function:
def list(self, request, *args, **kwargs):
serializer = Comments(self.get_queryset().order_by('-id')[:3], many = True)
Hope it will work for you.

How do I serialize ManyToMany Field data in Django?

So I'm working on Django back-end and React front-end. I have a many-to-many field in one of my models, and trying to serialize.
I've googled some related issues and tried things out, but none worked.
Let me show you my code first.
models.py
class Category(models.Model):
objects = models.Manager
category = models.CharField(max_length = 20)
class Content(models.Model):
objects = models.Manager()
title = models.CharField(max_length = 100, null=True)
category = models.ManyToManyField(Category)
serializers.py
class ContentSerializer(serializers.ModelSerializer):
category_name = serializers.SerializerMethodField()
class Meta:
model = Content
fields = [
'title', 'category_name'
]
def get_category_name(self, obj):
categories = self.context['book'].category.all()
return categories
Finally, views.py
#api_view(['GET'])
def each_book(request, pk):
this_book = Content.objects.get(pk=pk)
serialized = ContentSerializer(
this_book,
context={
'request':request,
'book' : this_book
},
)
return Response(serialized.data)
I keep on getting error saying Object of type Category is not JSON serializable. What do you think is the problem? Thanks a lot in advance. :)
---- LATEST CODE
models.py
class Category(models.Model):
category = models.CharField(max_length = 20)
class Content(models.Model):
title = models.CharField(max_length = 100, null=True)
category = models.ManyToManyField(Category)
serializers.py
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = ['category']
class ContentSerializer(serializers.ModelSerializer):
category_name = CategorySerializer(many=True)
class Meta:
model = Content
fields = [
'category_name'
]

Django Rest Framework - Create foreign key object on POST

I have a simple DRF REST API that I want to use to create blog articles. I want to be able to add tags to those blog articles so users can search tags and see related articles. However, the tags may not exist yet. I have created an Article Model with a ForeignKey field to a Tag Model like this:
class Tag(models.Model):
name = models.CharField(max_length=32)
def _str__(self):
return self.name
class Meta:
ordering = ('name',)
class Article(models.Model):
title = models.CharField(max_length=256)
author = models.ForeignKey(User, on_delete=models.CASCADE)
content = models.TextField()
date = models.DateTimeField(auto_now_add=True)
tags = models.ForeignKey(Tag, on_delete=models.CASCADE, blank=True, default=None)
def __str__(self):
return self.title
class Meta:
ordering = ('date', 'id')
Ideally what I want is to be able to POST a new Article with a set of tags, and if any of the tags don't exist, create them in the DB. However, as it is currently, the tags need to already exist to be added to the Article. Visually, DRF shows this as a dropdown that is populated with pre-existing tags:
How can I add or create multiple Tags from my Article API endpoint?
EDIT: As requested, I've added my views.py
views.py:
from api.blog.serializers import ArticleSerializer, TagSerializer
from rest_framework import viewsets
# /api/blog/articles
class ArticleView(viewsets.ModelViewSet):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
# /api/blog/tags
class TagView(viewsets.ModelViewSet):
queryset = Tag.objects.all()
serializer_class = TagSerializer
For completeness, here are my serializers from my REST API's serializers.py.
serializers.py:
class ArticleSerializer(serializers.ModelSerializer):
class Meta:
model = Article
fields = '__all__'
class TagSerializer(serializers.ModelSerializer):
class Meta:
model = Tag
fields = '__all__'
urls.py:
from rest_framework import routers
router = routers.DefaultRouter()
router.register('articles', views.ArticleView)
router.register('tags', views.TagView)
urlpatterns = [
path('', include(router.urls)),
]
Overriding the create() method of the serializer as
class ArticleSerializer(serializers.ModelSerializer):
tags = serializers.CharField()
class Meta:
model = Article
fields = '__all__'
def create(self, validated_data):
tag = validated_data.pop('tags')
tag_instance, created = Tag.objects.get_or_create(name=tag)
article_instance = Article.objects.create(**validated_data, tags=tag_instance)
return article_instance
Okay, thanks to #JPG for their help. This is what I've ended up with. It allows users to add space delimited tags into a CharField on the /api/blog/article endpoint. When a POST request is performed, the tags are split on spaces, get_or_create()d (for this to work I needed to make Tag.name the primary key), and then added to the Article with article.tags.set(tag_list). As #JPG and #Martins suggested, a ManyToManyField() was the best way to do this.
Here is my full code:
serializers.py:
class ArticleSerializer(serializers.ModelSerializer):
class TagsField(serializers.CharField):
def to_representation(self, tags):
tags = tags.all()
return "".join([(tag.name + " ") for tag in tags]).rstrip(' ')
tags = TagsField()
class Meta:
model = Article
fields = '__all__'
def create(self, validated_data):
tags = validated_data.pop('tags') # Removes the 'tags' entry
tag_list = []
for tag in tags.split(' '):
tag_instance, created = Tag.objects.get_or_create(name=tag)
tag_list += [tag_instance]
article = Article.objects.create(**validated_data)
print(tag_list)
article.tags.set(tag_list)
article.save()
return article
class TagSerializer(serializers.ModelSerializer):
class Meta:
model = Tag
fields = '__all__'
Note that I had to create a custom TagField() and override to_representation(). This is because if I used a regular serializer.CharField() tags were displayed as: "Blog.tag.None" instead of the tag values, like this:
models.py:
class Tag(models.Model):
name = models.CharField(max_length=32, primary_key=True)
def __str__(self):
return self.name
class Meta:
ordering = ('name',)
class Article(models.Model):
title = models.CharField(max_length=256)
author = models.ForeignKey(User, on_delete=models.CASCADE)
content = models.TextField()
date = models.DateTimeField(auto_now_add=True)
tags = models.ManyToManyField(Tag)
def __str__(self):
return self.title
class Meta:
ordering = ('date', 'id')

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