I already have a general idea of how it should be done. The only issue that I face now is how to actually send the data. I don't want to create new Projects I just want to add them to the notifications. How do I pass the data, the actual JSON?
class NotificationsScheduleSerializer(ModelSerializer):
projects = ProjectSerializer(many=True) # Thats the Many2Many Field
user = HiddenField(default=CurrentUserDefault())
class Meta:
model = NotificationsSchedule
fields = [
"pk",
"projects",
"period",
"week_day",
"created_at",
"time",
"report_type",
"user",
]
def create(self, validated_data):
breakpoint() # I don't ever get "projects" in validated_data just Empty OrderedDict
projects_data = validated_data.pop("projects", [])
notification = NotificationsSchedule.objects.create(**validated_data)
return notification
class ProjectSerializer(ModelSerializer):
class Meta:
model = Project
fields = ["pk", "name"]
I want to be able to pass something like this.
{
"projects": [290, 289],
"period": "daily",
"week_day": 2,
"time": "16:02:00",
"report_type": "word_report"
}
But it expects dict instead.
"non_field_errors": [
"Invalid data. Expected a dictionary, but got int."
]
You have to set read_only,
projects = ProjectSerializer(many=True, read_only=True)
And when creating Notifications ,
notification = NotificationsSchedule.objects.create(**validated_data)
notification.projects.add(*self.initial_data.get("projects"))
notification.save()
Related
in my post requests to OrderProduct model, i want to only have to pass order.id and product.id and it works... untill i add a serializer to retrieve product.name. It might be because i didnt understand documentation about nested requests, but im unable to advance further into my project :(
[
{
"id": 2,
"order": 1,
"product": 1,
}
]
^ here's how it looks without nested serializer, and thats the data that i wanna have to input
[
{
"id": 2,
"order": 1,
"product": {
"id": 1,
"name": "gloomhaven",
},
},
^ here's how it looks after i add an additional serializer. I pretty much want these nested fields to be read only, with me still being able to send simple post requests
here are my serializers
class OrderProductSerializer(serializers.ModelSerializer):
product = Product()
class Meta:
model = OrderProduct
fields = [
"id",
"order",
"product"]
class Product(serializers.ModelSerializer):
class Meta:
model = Product
fields = (
"id",
"name")
Is there any way for me to accomplish this? Thank you for trying to help!
Just overwrite to_representation method of the serializer
def to_representation(self, instance):
response = super().to_representation(instance)
response['other_field'] = instance.id# also response['other_field'] = otherSerializer(instance.model)
return response
This can solve your problem
I think you are missing many=True
class OrderProductSerializer(serializers.ModelSerializer):
product = Product(many=True)
class Meta:
model = OrderProduct
fields = [
"id",
"order",
"product"]
{
"product_name": "CRVRVgfhghg",
"product_price": "0.01",
"product_location": "KIKUYU,KENYA",
"product_description": "VFVFVFVFVFVF",
"product_category_name": "livestock",
"product_farmer_name": "james",
"product_category_data": {
"product_category_name": "livestock",
"product_category_description": "livestock one"
},
"product_product_file_data": {
"product_file_name": "ok"
}
}
i have three tables: product_category,product and product_product_files...what i want is to populate all the three tables at once using one view and url pattern... is there a way i can do this using serializers??
I think what you are looking for is the following documentation DRF writable nested serializers.
Looking at this you'll see that they state the following:
'By default nested serializers are read-only. If you want to support write-operations to a nested serializer field you'll need to create create() and/or update() methods in order to explicitly specify how the child relationships should be saved:'
This is the example they use:
class TrackSerializer(serializers.ModelSerializer):
class Meta:
model = Track
fields = ['order', 'title', 'duration']
class AlbumSerializer(serializers.ModelSerializer):
tracks = TrackSerializer(many=True)
class Meta:
model = Album
fields = ['album_name', 'artist', 'tracks']
def create(self, validated_data):
tracks_data = validated_data.pop('tracks')
album = Album.objects.create(**validated_data)
for track_data in tracks_data:
Track.objects.create(album=album, **track_data)
return album
The data they put in then should look like this:
data = {
'album_name': 'The Grey Album',
'artist': 'Danger Mouse',
'tracks': [
{'order': 1, 'title': 'Public Service Announcement', 'duration': 245},
{'order': 2, 'title': 'What More Can I Say', 'duration': 264},
{'order': 3, 'title': 'Encore', 'duration': 159},
],
Looking at your code-snippet I would guess these models are related in a One-to-one relationship. In this case many=False which is also the default. You could do that for both models.
I think you would be able to get to the right code with this information, if not please let me know.
I have a model where users can upvote other users for specific topics. Something like:
#models.py
Class Topic(models.Model):
name = models.StringField()
def __str__(self):
return str(self.name)
Class UserUpvotes(models.Model):
"""Holds total upvotes by user and topic"""
user = models.ForeignKey(User)
topic= models.ForeignKey(Topic)
upvotes = models.PositiveIntegerField(default=0)
Using DRF, I have an API that returns the following: topic_id, topic_name, and upvotes, which is the total upvotes for a given topic.
One of the project requirements is for the API to use these field names specifically: topic_id, topic_name, and upvotes
#serializers.py
class TopicUpvotesSerializer(serializers.ModelSerializer):
topic_name = serializers.StringRelatedField(source="topic")
class Meta:
model = UserUpvotes
fields = ["topic_id", "topic_name", "upvotes"]
My trouble is aggregating these fields. I'm filtering the UserUpvotes by user or team and then aggregating by topic.
Desired output
This is the result I want to get. When I don't perform any aggregations (and there are views where this will be the case), it works.
[
{
"topic_id": 3,
"topic_name": "Korean Studies",
"upvotes": 14
},
{
"topic_id": 12,
"topic_name": "Inflation",
"upvotes": 3
},
]
At first, I tried creating a TopicSerializer, and then assigning it to the topic field in TopicUpvotesSerializer. But then, the resulting json would have a nested "topic" field and the aggragation would fail.
Attempt 1
#views.py
def get_queryset(self):
return (
UserUpvotes.objects.filter(user__team=team)
.values("topic")
.annotate(upvotes=models.Sum("upvotes"))
.order_by("-upvotes")
)
My problem is that the topic_id and topic_name fields are not showing. I get something like:
[
{
"topic_name": "3",
"upvotes": 14
},
{
"topic_name": "12",
"upvotes": 3
},
]
Attempt 2
Another queryset attempt:
# views.py
def get_queryset(self):
return (
UserUpvotes.objects.filter(user__team=team)
.values("topic__id", "topic__name")
.annotate(upvotes=models.Sum("upvotes"))
.order_by("-upvotes")
)
Which yields:
[
{
"upvotes": 14
},
{
"upvotes": 3
},
]
The aggregation worked on the queryset level, but the serializer failed to find the correct fields.
Attempt 3
This was the closest I got:
# views.py
def get_queryset(self):
return (
UserUpvotes.objects.filter(user__team=team)
.values("topic__id", "topic__name")
.annotate(upvotes=models.Sum("upvotes"))
.values("topic_id", "topic", "upvotes")
.order_by("-upvotes")[:n]
)
[
{
"topic_name": 3,
"topic_name": "3",
"upvotes": 14
},
{
"topic_name": 12,
"topic_name": "12",
"upvotes": 3
},
]
I have no idea why "topic_name" is simply transforming the "topic_id" into a string, instead of calling the string method.
Work with a serializer for the topic:
class TopicSerializer(serializers.ModelSerializer):
upvotes = serializers.IntegerField(read_only=True)
class Meta:
model = Topic
fields = ['id', 'name', 'upvotes']
then in the ModelViewSet, you annotate:
from django.db.models import Sum
from rest_framework.viewsets import ModelViewSet
class TopicViewSet(ModelViewSet):
serializer_class = TopicSerializer
queryset = Topic.objects.annotate(upvotes=Sum('userupvotes__upvotes'))
Desired output
This is the result I want to get. When I don't perform any aggregations (and there are views where this will be the case), it works.
[
{
"topic_name": 3,
"topic_name": "Korean Studies",
"upvotes": 14
},
{
"topic_name": 12,
"topic_name": "Inflation",
"upvotes": 3
},
]
The serialized FK will always give you the ID of the related model. I am not sure why you name it topic_name if that is equal to an ID. Now, if you really want to get the name field of the Topic model
in the topic_name = serializers.StringRelatedField(source="topic") you should give it a source="topic.name"
However, if you trying to get the ID of the relation you can still use ModelSerializer :
class TopicUpvotesSerializer(serializers.ModelSerializer):
class Meta:
model = UserUpvotes
fields = "__all__"
#willem-van-onsem's answer is the correct one for the problem as I had put it.
But... I had another use case (sorry! ◑﹏◐), for when the Users API used UserUpvotes serializer as a nested field. So I had to find another solution. This is was I eventually ended up with. I'm posting in case it helps anyone.
class UserUpvotesSerializer(serializers.ModelSerializer):
topic_name = serializers.SerializerMethodField()
def get_topic_name (self, obj):
try:
_topic_name = obj.topic.name
except TypeError:
_topic_name = obj.get("skill__name", None)
return _topic_name
class Meta:
model = UserUpvotes
fields = ["topic_id", "topic_name", "upvotes"]
I still have no idea why the SerializerMethodField works and the StringRelatedField field doesn't. It feels like a bug?
Anyways, the rub here is that, after the values().annotate() aggregation, obj is no longer a QuerySet, but a dict. So accessing namedirectly will give you a 'UserUpvotes' object is not subscriptable error.
I don’t know if there are any other edge cases I should be aware of (this is when I REALLY miss type hints in Django), but it works so far
I have a model like this
class Status(models.Model):
is_working = models.BooleanField(
default=None,
null=True
)
is_at_home = models.BooleanField(
default=None,
null=True
)
with the corresponding serializer
class StatusSerializer(ModelSerializer):
class Meta:
model = Status
fields = [
"is_working",
"is_at_home"
]
using the default ModelViewSet
class StatusViewSet(viewsets.ModelViewSet):
"""
"""
serializer_class = StatusSerializer
queryset = Status.objects.all()
Whenever I partially update a Status, by e.g calling the put method on the API, all other fields are reset to False instead of keeping their old value.
Say, I have a Status that looks like this:
{
"id": 1,
"is_working": null,
"is_at_home": null,
}
If I call put using the following JSON:
{
"is_working": true
}
my data now looks like this
{
"id": 1,
"is_working": true,
"is_at_home": false <-- GOT UPDATED
}
I however, just want to update the is_working field, so that my desired result would be:
{
"id": 1,
"is_working": true,
"is_at_home": null
}
This probably has to do with the fact that HTML forms don't supply a value for unchecked fields. I, however, don't use any HTML forms as I'm purely consuming the API through JSON requests.
Using the serializer as is, I'd need to perform an extra get request prior to updating the model, just to get the state of the fields I don't want to update.
Is there a way around that?
First off, for partially updating you need to have a PATCH request and not PUT.
Since you are using ModelViewSet drf should automatically recognize that and set partial=True and update only the fields which were sent in the api payload.
ModelViewSet doc- DRF
You can make your is_at_home field read only in serializer as PUT method is updating it. Try this in your serializer.Hope this will work for you.
class StatusSerializer(ModelSerializer):
class Meta:
model = Status
fields = [
"is_working",
"is_at_home"
]
extra_kwargs = {
'id': {'read_only': True},
'is_at_home': {'read_only': True},
}
Suppose for below ModelSerializer class
class UserSongSerializer(serializers.ModelSerializer):
user = serializers.SerializerMethodField()
song_likes = serializers.ReadOnlyField() # This is model's property field
song_shares = serializers.ReadOnlyField()
song_plays = serializers.ReadOnlyField()
song_price = serializers.ReadOnlyField()
genre = GenreSerializer(many=True,required=False,context={'key':5})
language = LanguageSerializer(many=True, required=False)
Passing specific context kwarg like below
genre = GenreSerializer(many=True,required=False,context={'fields':['name']})
Since I want to retrieve only name field in Genre model class in some specific cases, I overrided GenreSerializer class's get_fields_name method so that I can mention specific fields only when required via context
class GenreSerializer(serializers.ModelSerializer):
def get_field_names(self, *args, **kwargs):
"""
Overriding ModelSerializer get_field_names method for getting only specific fields in serializer when mentioned in SerializerClass arguments
"""
field_names = self.context.get('fields', None)
if field_names:
return field_names
return super(GenreSerializer, self).get_field_names(*args, **kwargs)
class Meta:
model = Genre
fields = '__all__'
However, I am unable to get any 'fields' (getting None) key inside overrided get_fields_name method. I know of other ways as well like using StringRelatedField but that would change the output representation to
"genre":[
"Pop",
"Rock"
]
Whereas, I want to stick to my original representation
"genre": [
{
"id": 3,
"name": "Pop",
"created_date": "2018-09-05T17:05:59.705422+05:30",
"updated_date": "2018-09-20T14:43:02.062107+05:30",
"status": false
},
{
"id": 4,
"name": "Rock",
"created_date": "2018-09-05T17:06:06.889047+05:30",
"updated_date": "2018-09-17T16:45:22.684044+05:30",
"status": true
},
{
"id": 5,
"name": "Classical",
"created_date": "2018-09-05T17:06:14.216260+05:30",
"updated_date": "2018-09-05T17:06:14.275082+05:30",
"status": true
}
]
UPDATE - What I want is like this
"genre": [
{
"name": "Pop"
},
{
"name": "Rock"
},
{
"name": "Classical"
}
]
Contexts are meant to be set to the root serializer only.
Whenever UserSongSerializer will be instantiated it'll override the nested genre context.
If you are using generic views, you'll want to override the view's get_serializer_context and add your own context there. It's documented at the bottom of the methods section
PS: context are "shared" to serializers, fields, validators.
PPS: Don't alter context after it's been set you it's going to be sort of undefined behavior.