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
Related
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.
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.
Assuming im using the default django model, a Post model (code below) and a SavedPost model that links a User to a Post (if the certain user with the certain post exists then that post is saved for that user) and a Follower model that links 2 user (similar to SavedPost).
What im trying to do: An API that for a user, they get all posts for the users they follow, in addition each of these posts has an extra 'field' to say if that post is saved or not.
class Post(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
post_type = models.CharField(max_length=1, choices=[('B', 'Blog'), ('V', 'Video')], default='B')
file_path = models.URLField(null=True)
title = models.CharField(max_length=255)
description = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class SavedPost(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
post = models.ForeignKey(Post, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
# A user can save a post only once.
unique_together = ('user', 'post')
class Follower(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="user")
follower = models.ForeignKey(User, on_delete=models.CASCADE, related_name="follower")
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
# A user can follow another user only once
unique_together = ('user', 'follower')
Post serilializer:
class PostSerializer(serializers.ModelSerializer):
"""
Nested serializer for post using SimpleUser and Kingdom.
"""
class Meta:
model = Post
fields = ('id', 'user', 'post_type', 'file_path',
'title', 'description', 'created_at', 'updated_at')
def to_representation(self, instance):
data = super().to_representation(instance)
data['user'] = UserSerializer(
User.objects.get(pk=data['user'])).data
return data
API View:
#permission_classes([IsAuthenticated,])
#api_view(['GET'])
def get_following(request):
user = request.user
following = Follower.objects.filter(follower=user).values('user')
# saved_posts = SavedPost.objects.filter(user=user, post__user__in=following).order_by('-post__created_at')
posts = Post.objects.filter(user__in=following).order_by('-created_at')
serializer = PostSerializer(posts, many=True, context={'request': request})
return JsonResponse(serializer.data, safe=False)
So far with the view I made I can get all the posts that the request.user follows but it doesnt say if they are saved or not. I am looking for say 'is_saved' boolean on post to say if that post is saved for that user or not.
Any help/method to do this appreciated. Thank you.
Use serializers.SerializerMethodField as
class PostSerializer(serializers.ModelSerializer):
is_saved = serializers.SerializerMethodField()
def get_is_saved(self, post_instance):
return SavedPost.objects.filter(user=post_instance.user, post=post_instance).exists()
class Meta:
model = Post
fields = ['id', 'user', 'post_type', 'file_path',
'title', 'description', 'created_at', 'updated_at', 'is_saved']
def to_representation(self, instance):
data = super().to_representation(instance)
data['user'] = UserSerializer(
User.objects.get(pk=data['user'])).data
return data
First of all, just to be clear, I will be defining the related_name option for the ForeignKeys in SavedPost - it's up to you to decide whether to implement this or not:
class SavedPost(models.Model):
user = models.ForeignKey(User, related_name="saved", on_delete=models.CASCADE)
post = models.ForeignKey(Post, related_name="saved", on_delete=models.CASCADE)
...
Now, in your PostSerializer, you could add this field (remember to add it to the fields variable in the Meta inner class - that is if you're using ModelSerializer):
class PostSerializer(serializers.ModelSerializer):
saved = SavedPostSerializer(many=True)
...
To finish it off, define your SavedPostSerializer - above PostSerializer, if in the same file/module:
class SavedPostSerializer(serializers.ModelSerializer):
class Meta:
model = SavedPost
fields = "__all__"
With this, your json should have a nested field with the saved key containing an array of SavedPosts, if there are any related to the Posts retrieved.
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]
}
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')