I have the following models:
class Tag(TimeStampModel):
name = models.CharField(unique=True, max_length=100)
slug = models.SlugField(max_length=100, unique=True, blank=True)
featured = models.BooleanField(default=False, blank=True)
class Deal(VoteModel, models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='deals',
on_delete=models.CASCADE)
title = models.CharField(max_length=1024, blank=False, null=False)
slug = models.SlugField(max_length=1024, unique=True, blank=True)
description = models.TextField(blank=False, null=False)
poster = models.ImageField(blank=True)
tags = models.ManyToManyField(
Tag, blank=True)
And the following serializers:
class TagSerializer(serializers.ModelSerializer):
class Meta:
model = Tag
fields = ['id', 'name', 'slug', 'featured', 'created_at']
class DealSerializer(serializers.ModelSerializer):
user = UserSerializer(many=False, read_only=True)
tags = TagSerializer(many=True, read_only=True)
tags_ids = serializers.PrimaryKeyRelatedField(many=True, write_only=True, queryset=Tag.objects.all())
class Meta:
model = Deal
fields = '__all__'
Views
class DealList(viewsets.ModelViewSet, VoteMixin):
serializer_class = DealSerializer
permission_classes = [IsOwnerOrAdminOrReadOnly]
def get_queryset(self):
return Deal.objects.all()
def perform_create(self, serializer):
serializer.save(user=self.request.user)
I am able to get the data and also post it, but because of the many-to-many field (tags), I seem to have some issues as a Deal may have tags that only exist (created beforehand, and cannot be created through a post request to the Deal).
I send data as the following:
{
title: 'some title',
all_other_fields: 'some data',
tags_ids: [2, 4]
}
The tags are sent as an array of tag ids, but I get an error as the following:
"Incorrect type. Expected pk value, received str."
I only added the tags_ids so I could perform write operations on it as I couldn't figure out a way to use the field tags as both a read and write field that would return an object on read, and accept an id on write.
I have read through many posts here on Stackoverflow, but nothing that would work for me yet. Any help would be very appreciated. Thank you.
Try changing your serializer like this
class DealSerializer(serializers.ModelSerializer):
user = UserSerializer(many=False, read_only=True)
tags = serializers.PrimaryKeyRelatedField(many=True, queryset=Tag.objects.all())
class Meta:
model = Deal
fields = '__all__'
Try doing it this way.
class DealSerializer(serializers.ModelSerializer):
user = UserSerializer(read_only=True)
tags = TagSerializer(read_only=True)
class Meta:
model = Deal
fields = ('user', 'title', 'slug', 'description', 'poster', 'tags')
Related
I'm using Django Rest Framework with DjangoFilterBackend to filter through Publications. Every publication can have multiple authors. My api call to filter the api for authors looks like:
/api/v1/publication/?author=1&author=2
This gives me every publication that either author 1 or author 2 has been assigned to. Instead I want to only see the publications that both have published. In other words it should be a logic and, not or.
My code is the following:
models.py
class Publication(models.Model):
id = models.BigAutoField(primary_key=True)
title = models.CharField(max_length=400)
author = models.ManyToManyField(Author, blank=False)
type = models.ForeignKey(
Type, on_delete=models.PROTECT, null=False, default=1)
label = models.ManyToManyField(Label, blank=True)
date = models.DateField(blank=False, null=False)
url = models.URLField(null=True, blank=True)
journal = models.CharField(max_length=400, blank=True)
bibtex = models.TextField(blank=True)
public = models.BooleanField(default=False)
updated = models.DateTimeField(auto_now=True)
created = models.DateTimeField(auto_now_add=True)
file = models.FileField(upload_to=upload_path, blank=True, null=True)
class Meta:
ordering = ['-date']
def __str__(self):
return self.title
views.py
class PublicationFilter(django_filters.FilterSet):
author = django_filters.ModelMultipleChoiceFilter(
queryset=Author.objects.all())
class Meta:
model = Publication
fields = {
'title': ["exact"],
'author': ["exact"]
}
class PublicationView(viewsets.ModelViewSet):
queryset = Publication.objects.prefetch_related(
'author', 'label').select_related('type')
serializer_class = PublicationSerializer
filter_backends = [DjangoFilterBackend,
SearchFilter, OrderingFilter]
filterset_fields = ['title', 'date', 'journal', 'label', 'author']
search_fields = ['title']
ordering_fields = ['date', 'title']
serializers.py
class PublicationSerializer(ModelSerializer):
type = TypeSerializer(read_only=False, many=False)
label = LabelSerializer(read_only=False, many=True)
author = AuthorSerializer(read_only=False, many=True)
class Meta:
model = Publication
fields = ['id', 'title', 'date',
'url', 'journal', 'label', 'author', 'type', 'date', 'bibtex', 'file']
I think if you will pass only one author field in the url query, but separate the values with a comma you will end up with a list value in the django-filter.
/api/v1/publication/?author=1,2
Than you can override filter_author method in your PublicationFilter class and build the query filter manually there.
See here under method: https://django-filter.readthedocs.io/en/stable/ref/filters.html#method
You can build the query with a help of the Q object:
How to dynamically compose an OR query filter in Django?
I have two serializers that represent comments and their nested comments. I'm provide a queryset to viewset with annotated field likes. But my problem is that field only working in parent serializer. When i add this field to nested serializer i got error
Got AttributeError when attempting to get a value for field likes on serializer CommentChildrenSerializer.
The serializer field might be named incorrectly and not match any attribute or key on the Comment instance.
Original exception text was: 'Comment' object has no attribute 'likes'.
Here is some my code. Thanks
Models.py
class Post(models.Model):
author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
title = models.CharField(max_length=200)
slug = models.SlugField(blank=True)
body = models.TextField()
tags = TaggableManager(blank=True)
pub_date = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ['-pub_date']
class Comment(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE,
related_name='comments')
author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
text = models.TextField(max_length=500)
pub_date = models.DateTimeField(auto_now=True)
parent = models.ForeignKey('self', blank=True, null=True,
on_delete=models.CASCADE, related_name='children')
class Meta:
ordering = ['-pub_date']
class Vote(models.Model):
comment = models.ForeignKey(Comment, on_delete=models.CASCADE,
related_name='votes')
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
choice = models.BooleanField(null=True)
Serializers.py
class PostRetrieveSerializer(PostSerializer):
comments = CommentSerializer(read_only=True, many=True)
author = AuthorInfoSerializer(serializers.ModelSerializer)
class Meta:
model = Post
fields = ['id', 'author', 'slug', 'title', 'body', 'tags', 'pub_date', 'comments']
class CommentChildrenSerializer(serializers.ModelSerializer):
author = AuthorInfoSerializer(read_only=True)
likes = serializers.IntegerField()
class Meta:
model = Comment
fields = ['author', 'id', 'text', 'pub_date', 'parent', 'likes']
class CommentSerializer(serializers.ModelSerializer):
author = AuthorInfoSerializer(read_only=True)
children = CommentChildrenSerializer(many=True)
likes = serializers.IntegerField()
class Meta:
ordering = ['pub_date']
model = Comment
fields = ['author', 'id', 'text', 'pub_date', 'children', 'likes']
Views.py
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all().prefetch_related(
Prefetch('comments', queryset=Comment.objects.filter(parent__isnull=True)
.annotate(likes=Count('votes__choice'))))
serializer_class = PostSerializer
permission_classes = [IsOwnerOrAdminOrReadOnly]
pagination_class = PostPagination
lookup_field = 'slug'
def get_serializer_class(self):
""""
Attach related comments
when get post detail
"""
if self.action == 'retrieve':
return PostRetrieveSerializer
return self.serializer_class
def perform_create(self, serializer):
serializer.save(author=self.request.user)
maybe you can do something like this, adding the like field in each child comment.
queryset = Post.objects.all()\
.prefetch_related(
Prefetch(
'comments',
queryset=Comment.objects\
.filter(parent__isnull=True)\
.annotate(likes=Count('votes__choice'))\
.prefetch_related(
'children',
queryset=Comments.objects.all()\
.annotate(likes=Count('votes__choice'))
)
)
)
I hope this help you.
Regards!
On your model level, add a custom property like the below.
class Comment(models.Model):
...
class Meta:
ordering = ['-pub_date']
#property
def likes(self):
return self.votes.count()
On your serializer add SerializerMethodField
class CommentChildrenSerializer(serializers.ModelSerializer):
author = AuthorInfoSerializer(read_only=True)
likes = serializers.SerializerMethodField() # Change here
class Meta:
model = Comment
fields = ['author', 'id', 'text', 'pub_date', 'parent', 'likes']
# method for the SerializerMethodField
def get_likes(self, obj):
return obj.likes
Update both Comment related serializers. I believe this approach is simpler than the current implementation.
i want to count comments for every single Post
in models.py:
class Post(models.Model):
body = models.TextField(max_length=10000)
date = models.DateTimeField(auto_now_add=True, blank=True)
user = models.ForeignKey(User, on_delete=models.CASCADE)
liked_by = models.ManyToManyField(User, blank=True, related_name='liked_by')
class Meta:
ordering = ['-date']
class Comment(models.Model):
body = models.TextField(max_length=1000)
date = models.DateTimeField(auto_now_add=True, blank=True)
user = models.ForeignKey(User, on_delete=models.CASCADE)
post = models.ForeignKey(Post, on_delete=models.CASCADE)
class Meta:
ordering = ['-date']
in serializers.py:
class CommentSerializer(serializers.ModelSerializer):
class Meta:
model = Comment
fields = '__all__'
class PostSerializer(serializers.ModelSerializer):
#comments = CommentSerializer()
user = UserSerializers()
total_likes = serializers.SerializerMethodField()
liked_by = SimpleUserSerializer(many=True, read_only=True)
total_comments = serializers.SerializerMethodField()
class Meta:
model = Post
fields = ('body','date','user', 'total_likes', 'liked_by','total_comments')
def get_total_likes(self, instance):
return instance.liked_by.count()
def get_total_comments(self, instance):
return instance.comments.count()
when i run this code, it shows, AttributeError: 'Post' object has no attribute 'comments'.
how do i count comments of a post?
Since you haven't configured the related_name, Django uses the default related_name and hence you should access the reveres FK using comment_set instead of comments
Thus, the get_total_comments(...) method should look like
def get_total_comments(self, instance):
return instance.comment_set.count()
Reference
What is related_name used for in Django?
I'm trying to build an API for a picture gallery using Django and Django Rest Framework for the API.
I have set up the following two models:
class Album(models.Model):
name = models.CharField(max_length=100)
description = models.TextField(null=True, blank=True)
start_date = models.DateField()
end_date = models.DateField(null=True, blank=True)
parent_album = models.ForeignKey('self',
null=True,
blank=True,
on_delete=models.CASCADE
)
class Picture(models.Model):
path = models.ImageField()
album = models.ForeignKey('Album', on_delete=models.CASCADE)
For the serializers, I have defined the following, using the official Django DRF doc:
class PictureSerializer(serializers.ModelSerializer):
class Meta:
model = Picture
fields = '__all__'
class AlbumSerializer(serializers.ModelSerializer):
pictures = PictureSerializer(many=True, read_only=True)
class Meta:
model = Album
fields = '__all__'
Now, I have some objects already defined, so I wanted to try in a shell:
>>> album = Album.objects.get(pk=1)
>>> len(Picture.objects.filter(album__exact=Album.objects.get(pk=1)))
3
>>> AlbumSerializer(instance=album).data
{'id': 1, 'name': 'First album', 'description': '', 'start_date': '2019-08-15', 'end_date': None, 'parent_album': None}
>>> AlbumSerializer()
AlbumSerializer():
id = IntegerField(label='ID', read_only=True)
pictures = PictureSerializer(many=True, read_only=True):
id = IntegerField(label='ID', read_only=True)
path = ImageField(max_length=100)
album = PrimaryKeyRelatedField(queryset=Album.objects.all())
name = CharField(max_length=100)
description = CharField(allow_blank=True, allow_null=True, required=False, style={'base_template': 'textarea.html'})
start_date = DateField()
end_date = DateField(allow_null=True, required=False)
parent_album = PrimaryKeyRelatedField(allow_null=True, queryset=Album.objects.all(), required=False)
You will notice that there are no pictures in the serialized data, but the field is there when I print the serializer, and that's my problem... Have I missed something somewhere?
Thanks for the help!
Your problem is due to the related name given to ForeignKeys.
Right now with the code you provided the name backwards relation name should be picture_set. So for the serializer to work you should change it do the following:
class AlbumSerializer(serializers.ModelSerializer):
picture_set = PictureSerializer(many=True, read_only=True)
class Meta:
model = Album
fields = '__all__'
But, if you want to display as pictures you can also do the following:
class AlbumSerializer(serializers.ModelSerializer):
pictures = PictureSerializer(many=True, read_only=True, source='picture_set')
class Meta:
model = Album
fields = '__all__'
Finally, if you want to get rid of the picture_set you should change your code to the following:
models.py
class Picture(models.Model):
path = models.ImageField()
album = models.ForeignKey('Album', on_delete=models.CASCADE, related_name='pictures')
serializers.py
class AlbumSerializer(serializers.ModelSerializer):
pictures = PictureSerializer(many=True, read_only=True)
class Meta:
model = Album
fields = '__all__'
These are my models and serializers. I want a representation of Question Model along with a list of people the question was asked to.
I am trying this:
#api_view(['GET', 'PATCH'])
def questions_by_id(request,user,pk):
question = Question.objects.get(pk=pk)
if request.method == 'GET':
serializer = QuestionSerializer(question)
return Response(serializer.data)
But I get an empty dictionary ({}). However when I remove the asked field from QuestionSerializer I get a complete representation of Question along with Places serialized nicely. What am I missing ?
class AskedToSerializer(serializers.ModelSerializer):
class Meta:
model = AskedTo
fields = ('to_user', 'answered')
class QuestionSerializer(serializers.ModelSerializer):
class Meta:
model = Question
places = PlaceSerializer(many=True, required=False)
asked = AskedToSerializer(source='askedto_set', many=True)
fields = ('id', 'created_on', 'title', 'places', 'answered','asked')
extra_kwargs = {'created_by': {'read_only': True}}
class Question(BaseModel):
title = models.CharField(max_length=200, null=False)
places = models.ManyToManyField(Place, blank=True)
answered = models.BooleanField(default=False)
class AskedTo(BaseModel):
ques = models.ForeignKey(Question, on_delete=models.CASCADE)
to_user = models.ForeignKey(settings.AUTH_USER_MODEL)
replied = models.BooleanField(default=False)
class Place(models.Model):
g_place_id = models.CharField(max_length=20,primary_key=True)
json = models.TextField(null=True)
name = models.CharField(max_length=40)
I figured it out. There were two errors.
Changed this:
class AskedToSerializer(serializers.ModelSerializer):
class Meta:
model = AskedTo
fields = ('to_user', 'answered')
to this (notice the change in fields, fields on model and serializer didn't match)
class AskedToSerializer(serializers.ModelSerializer):
class Meta:
model = AskedTo
fields = ('to_user', 'replied')
Secondly, I needed to define any extra fields outside class Meta
class QuestionSerializer(serializers.ModelSerializer):
places = PlaceSerializer(many=True, required=False)
asked = AskedToSerializer(source='askedto_set', many=True)
class Meta:
model = Question
fields = ('id', 'created_on', 'title', 'places', 'answered','asked')
extra_kwargs = {'created_by': {'read_only': True}}
Notice the change in definition of places and asked.
In my case, I have this models.py:
class Section(models.Model):
title = models.CharField(max_length=500)
description = models.TextField(blank=True)
user = models.ForeignKey(Profile, related_name="sections", on_delete=models.CASCADE)
class Feed(models.Model):
title = models.CharField(max_length=500)
link_rss = models.URLField(max_length=500)
link_web = models.URLField(max_length=500)
description = models.TextField(blank=True)
language = models.CharField(max_length=50, blank=True)
logo = models.URLField(blank=True)
sections = models.ManyToManyField(Section, related_name="feeds")
And I completed the serializers.py this:
class FeedSerializer(serializers.ModelSerializer):
sections = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
class Meta:
model = Feed
fields = '__all__'
class SectionSerializer(serializers.ModelSerializer):
feeds = FeedSerializer(many=True, read_only=True)
class Meta:
model = Section
exclude = ('user',)