Remove results with field==0 from serializer response - django

I use ModelSerializer to return serialized data of a model:
class CategorySerializer(serializers.ModelSerializer):
category_count = serializers.SerializerMethodField('_category_count')
def _category_count(self, obj):
category_obj = Category.objects.get(id=obj.id)
questions_count = Question.objects.filter(category=category_obj).count()
return questions_count
class Meta:
model = Category
fields = ["id", "category", "category_count"]
What I get write now is here:
[{
"category": "orzecznictwo lekarskie",
"category_count": 0,
"id": 9
},
{
"category": "zdrowie publiczne",
"category_count": 1,
"id": 10
}
]
I want to remove objects with category_count == 0 from serialization
[
{
"category": "zdrowie publiczne",
"category_count": 1,
"id": 10
}
]
Thank you for your help.
EDIT:
class Question(models.Model):
category = models.ForeignKey(Category, on_delete=models.CASCADE)
question = models.TextField(blank=True, null=True)
(...)
class Category(models.Model):
category = models.TextField()
category_slug = models.CharField(max_length=50)

You're generating the field inside the serializer and then want to discard the object. That's not how things work: serializers merely change representation of data. Which data is displayed is determined in views.
class CategoryListView(generics.ListAPIView):
def get_queryset(self):
return models.Category.objects.annotate(
question_count=Count("question_set")
).exclude(question_count=0)
And in the serializer, you don't have to create the count any more and add the 'question_count' to Meta.fields.
Recommended reading.
Edit:
Forgot that DRF will check the field on the model and fail, so you need to declare the field:
class CategoryWithQuestionCount(serializers.ModelSerializer):
question_count = serializers.IntegerField(read_only=True)
class Meta:
model = models.Category
fields = ("pk", "category", "question_count")

I'm not sure about removing it in the ModelSerializer. But you can try overriding get_queryset() or changing the queryset in your View:
queryset = Class.objects.exclude(dzial_liczba_pytan=0)

Related

How to display category and subcategory in post serializer django-rest-framework

I need to extract categories and subcategories in Post serializer, because I need to put pagination, if I put pagination in category view, different amount of posts will come, so I need to put pagination in Post view, I need to return response so that it looks like this
and I want to create rest api to return nested json like this
[
{
"id": 1,
"title": "Taomlar",
"subcat": [
{
id: 2,
title: "Milliy",
post: [
{
id: 1,
title: 'Palov',
summa: 300000,
...
},
{
id: 2,
title: 'Palov',
summa: 300000,
...
},
]
},
]
}
]
models.py
class Category(Base):
title = models.CharField(max_length=200)
parent = models.ForeignKey('self', on_delete=models.CASCADE, null=True, blank=True, related_name='children')
def __str__(self):
return self.title
class Post(Base):
title = models.CharField(max_length=225)
category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name='post')
serializers.py
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = '__all__'
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = '__all__'
Can anyone please give me the solution for this problem
As much as I understand you need to get each Category with it's related posts.
Overwriting like this :
class CategorySerializer(serializers.ModelSerializer):
posts = serializers.SerializerMethodField()
class Meta:
model = Category
fields = (
'id',
'title',
'posts',
)
def get_posts(self, obj):
serializer = PostSerializer(obj.posts.all(), context=self.context, many = True)
return serializer.data
you can read more about nested serializer in Django Rest Framework
you should make it in this way
class CategorySerializer(serializers.ModelSerializer):
post_serializer = PostSerializer(many=True)
class Meta:
model = Category
fields = ['your_field', 'post_serializer']
this a basic example, try to read more about it to know how you can implement nested relations

Django serializer ManyToMany retrun array of names instead of an array of IDs

My models:
class Category(models.Model):
name = models.Charfield()..
code = models.Charfield()...
class Movie(models.Model):
name = models.Charfield()..
categories = models.ManyToManyField(Category)
My serializer
class MovieSerializer(serializers.ModelSerialier):
class Meta:
model= Movie
fields = ['name',
'categories',
]
When i get a Movie by id it returns:
[
{"name": "My Movie",
"categories": [1, 2, 3],
}
]
I want my response to be :
....
"categories": ["Fun", "Adventure", ...]
What I've tried
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = ['name']
def to_representation(self, instance):
return instance.name
It gives me the expected result, but i dont think it is a clean way to do it
Option 1
You can use your models __str__ method for this. Just use a StringRelatedField:
class MovieSerializer(serializers.ModelSerialier):
categories = serializers.StringRelatedField(many=True)
class Meta:
model= Movie
fields = ['name', 'categories']
and add the __str__ method to your category model:
class Category(models.Model):
name = models.Charfield()
code = models.Charfield()
def __str__(self):
return self.name
Option 2
Use a custom RelatedField, like described in the docs:
class CategoryListingField(serializers.RelatedField):
def to_representation(self, value):
return value.name
class MovieSerializer(serializers.ModelSerialier):
categories = CategoryListingField(many=True)
class Meta:
model= Movie
fields = ['name', 'categories']
This is close to what you did, just a little bit cleaner, as a RelatedField is designed to accomplish exactly that. Using a custom serializer is not the way to go here, since you dont want to serialize the object itself, but only a specific field from it. Per default, this is the pk (or id), but by specifying your own related field, you can change it to whatever you want.
Add depth = 1 to your serializer.
This will give you something like:
[
{
"name": "My Movie",
"categories": [
{ "id": 1, "name": "Fun", "code": "category_code" }
]
}
]
then you can extract whatever you want from this object.

Rest Framework deserialize one field and serialize model object

Hi I want to deserialize only using 1 field. However, I want to serialize it as an object depending on the model.
Suppose I have:
#models.py
class Product(models.Model):
name = models.CharField()
amount = models.IntegerField()
description = models.TextField()
class Query(models.Model):
name = models.CharField()
product = models.ForeignKey(Product)
...
#serializers.py
class ProductSerializer(serializers.ModelSerializer):
class Meta:
model = Product
fields = '__all__'
class QuerySerializer(serializers.ModelSerializer):
product = ProductSerializer()
class Meta:
model = Query
fields = '__all__'
I want to POST/deserialize something like this on the QuerySerializer:
{
"name": "Query 1",
"product": "Banana",
...
}
and I want something like this in return for serializer:
{
"name": "Query 1",
"product": {
"name": "Banana",
"amount": 1,
"description": "Banana description"
}
...
}
I know a way is overriding to_internal_value but I do not like it since it messes up with ValidationErrrors.
I also get this as a result:
{'product': {'non_field_errors':
['Invalid data. Expected a dictionary, but got str.']}}
First of all, make the name field of Product as unique to avoid unnecessary complications.
class Product(models.Model):
name = models.CharField(unique=True)
amount = models.IntegerField()
description = models.TextField()
and change your serializer as,
class QuerySerializer(serializers.ModelSerializer):
product = serializers.CharField(write_only=True)
class Meta:
model = Query
fields = '__all__'
def create(self, validated_data):
product_name = validated_data.pop('product')
product_instance = Product.objects.get(name=product_name)
return Query.objects.create(product=product_instance, **validated_data)
def to_representation(self, instance):
rep = super().to_representation(instance)
rep['product'] = ProductSerializer(instance.product).data
return rep
Reference: DRF: Simple foreign key assignment with nested serializers?

Django Rest Framework POST and GET Nested Serializers

I've been developing my own API for a Kanban-Style Project Board. I have attached a UML diagram to show how the "boards" application is organised.
My Application's Model UML Diagram
My problem is that when I want to create a new Card I want to be able to create the card with a list of Labels by Primary Keys passed in the POST parameters, like so:
{
"title": "Test Card",
"description": "This is a Test Card!",
"created_by": 1,
"labels": [1,2]
}
Another requirement I have is that I would like to retrieve the serialized labels as part of the card object, like so:
{
"id": 1,
"board": 1,
"title": "Some Card",
"description": "The description of Some Card.",
"created_by": 1,
"assignees": [
{
"id": 1,
"username": "test1",
"email": "test1_user#hotmail.co.uk"
}
],
"labels": [
{
"id": 1,
"board": 1,
"title": "Pink Label",
"color": "#f442cb"
}
],
"comment_set": []
}
I am going to assume that to achieve this difference in POST and GET functionality I am going to have to have 2 different serializers?
However, the main question of this post has to do with the creation logic from the POST data as mentioned above. I keep getting errors like this:
{
"labels": [
{
"non_field_errors": [
"Invalid data. Expected a dictionary, but got int."
]
},
{
"non_field_errors": [
"Invalid data. Expected a dictionary, but got int."
]
}
]
}
I have tried many different combinations of DRF Serializers in my CardSerializer but always end up with error messages that have the same format as above: "Expected but got ". Any help or pointers, even someone telling me that this is bad REST design for example, would be greatly appreciated! :)
EDIT: I should add that in the case I change the CardSerializer labels field from a LabelSerializer to a PrimaryKeyRelatedField (as the comment in the code shows) I receive the following error:
Direct assignment to the forward side of a many-to-many set is prohibited. Use labels.set() instead.
Here are the relevant parts of my source code:
models.py
class Card(models.Model):
"""Represents a card."""
# Parent
board = models.ForeignKey(Board, on_delete=models.CASCADE)
column = models.ForeignKey(Column, on_delete=models.CASCADE, null=True)
# Fields
title = models.CharField(max_length=255, null=False)
description = models.TextField()
assignees = models.ManyToManyField(User, blank=True, related_name='card_assignees')
labels = models.ManyToManyField(Label, blank=True, related_name='card_labels')
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(blank=True, null=True) # Blank for django-admin
created_by = models.ForeignKey(User, on_delete=models.CASCADE, related_name='card_created_by')
views.py
class CardList(generics.ListCreateAPIView):
queryset = Card.objects.all()
serializer_class = CardSerializer
def get_queryset(self):
columns = Column.objects.filter(board_id=self.kwargs['board_pk'])
queryset = Card.objects.filter(column__in=columns)
return queryset
def post(self, request, *args, **kwargs):
board = Board.objects.get(pk=kwargs['board_pk'])
post_data = {
'title': request.data.get('title'),
'description': request.data.get('description'),
'created_by': request.data.get('created_by'),
'assignees': request.data.get('assignees'),
'labels': request.data.get('labels'),
}
serializer = CardSerializer(data=post_data, context={'board': board})
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status.HTTP_201_CREATED)
return Response(serializer.errors, status.HTTP_400_BAD_REQUEST)
serializers.py
class UserSerializer(serializers.ModelSerializer):
"""Serializer to map the User instance to JSON."""
class Meta:
model = User
fields = ('id', 'username', 'email')
class CommentSerializer(serializers.ModelSerializer):
"""Serializer to map the Comment instance to JSON."""
class Meta:
model = Comment
fields = '__all__'
class LabelSerializer(serializers.ModelSerializer):
"""Serializer to map the Label instance to JSON."""
class Meta:
model = Label
fields = ('id', 'board', 'title', 'color')
class CardSerializer(serializers.ModelSerializer):
"""Serializer to map the Card instance to JSON."""
assignees = UserSerializer(many=True, read_only=True)
labels = LabelSerializer(many=True)
comment_set = CommentSerializer(many=True, read_only=True)
# assignees = PrimaryKeyRelatedField(many=True, read_only=True)
# labels = PrimaryKeyRelatedField(many=True, queryset=Label.objects.all())
def create(self, validated_data):
board = self.context['board']
card = Card.objects.create(
board=board,
**validated_data
)
return card
class Meta:
model = Card
fields = ('id', 'board', 'title', 'description', 'created_by', 'assignees', 'labels', 'comment_set')
read_only_fields = ('id', 'board')
If anyone stuck on the same problem, then here is the solution. I think you need to create the two serializer classes, one for the get request and another for the post request. And call the required serializer from viewset as below,
class MyModelViewSet(viewsets.MyModelViewSet):
queryset = MyModel.objects.all()
serializer_class = MyModelSerializer # default serializer, you can change this to MyModelListSerializer as well
action_serializers = {
'list': MyModelListSerializer, # get request serializer
'create': MyModelCreateSerializer # post request serializer
}
def get_serializer_class(self):
if hasattr(self, 'action_serializers'):
return self.action_serializers.get(self.action, self.serializer_class)
return super(MyModelViewSet, self).get_serializer_class()
here is the example of the MyModelListSerializer and MyModelCreateSerializer,
# Used for the get request
class MyModelListSerializer(serializers.ModelSerializer):
assignees = AssigneesSerializer(read_only=True, many=True)
labels = LabelsSerializer(read_only=True, many=True)
class Meta:
model = MyModel
fields = '__all__'
# Used for the post request
class MyModelCreateSerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
fields = "__all__"
I managed to find a solution, however this may not conform to best practice. If anyone could clarify this that would be great. However, for now:
I changed the create function in the CardSerializer to the following:
def create(self, validated_data):
board = self.context['board']
labels_data = validated_data.pop('labels')
card = Card.objects.create(
board=board,
**validated_data
)
card.labels.set(labels_data)
return card
The card.labels.set(labels_data) line means I bypass the following error message:
Direct assignment to the forward side of a many-to-many set is prohibited.
Which is why I am uncertain if it is the "correct" thing to do but it seems to work for now.

filter multiple objects and returns 'new field' in Serializer

Just like Youtube, I want to see what categories my post(outfit) is in.
My approach is...
Get whole list of categories belong to user
Get whole list of categories belong to user and the post
compare them and return JSON
While building a Seriailizer, I feel like I'm totally stock. So frustraing... :(
Please feel free to change my approach if you have any better idea.
Expected JSON outputis below
{
...
"categories": [
{
"id": 1,
"name": "asd"
"added": True <- since the post is added to 'asd'
},
{
"id": 2,
"name": "workout"
"added": True
},
...
{
"id": 5,
"name": "bgm"
"added": False <- since the post is not added to 'bgm'
},
]
}
Here is views.py
class OutfitDetailView(generics.RetrieveAPIView):
queryset = Outfit.objects.all()
serializer_class = OutfitDetailSerializer
lookup_field = 'pk'
Here is serializers.py (this is a problem)
class OutfitDetailSerializer(serializers.ModelSerializer):
...
def get_categories(self, obj):
# Right Here! Get whole categories associated with user
all_categories = Category.objects.filter(owner=self.context['request'].user)
# And the categories associated with user AND the post
categories = obj.categories.filter(owner=self.context['request'].user)
return CategorySerializer(categories, many=True).data
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = (
'id',
'name',
'added' <- here I want to add 'added' field but I don't know how.
)
In case you need a model
class Outfit(models.Model):
...
user = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True)
content = models.CharField(max_length=30)
...
class Category(models.Model):
name = models.CharField(max_length=20)
owner = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True)
outfits = models.ManyToManyField(Outfit, related_name="categories", blank=True)
main_img = models.ImageField(
upload_to=upload_location_category,
null=True,
blank=True)
...
if i understand you correct, you can try:
class OutfitDetailSerializer(serializers.ModelSerializer):
def get_categories(self, obj):
user = self.context['request'].user
categories = Category.objects.filter(owner=user)
added = categories.extra(select={'added': '1'}).filter(outfits__pk=obj.pk)
added = list(added.values('added', 'name', 'id'))
added_f = categories.extra(select={'added': '0'}).exclude(outfits__pk=obj.pk)
added_f = list(added_f.values('added', 'name', 'id'))
categories = added + added_f
return CategorySerializer(categories, many=True).data
Note in the values you need add all fields you need for the CategorySerializer
class CategorySerializer(serializers.ModelSerializer):
added = serializers.BooleanField()
class Meta:
model = Category
fields = (
'id',
'name',
'added'
)