Represent json data with PrimaryKeyRelatedField in django - django

I'm designing a mailbox with Django. My code is as follows:
#models.py
class Post(models.Model):
text = models.CharField(max_length=256)
sender = models.ForeignKey(User)
receiver = models.ForeignKey(User)
class Comment(models.Model):
post = models.ForeignKey(Post)
text = models.CharField(max_length=256)
#serializers.py
class CommentSerializer(serializers.ModelSerializer):
post = serializers.PrimaryKeyRelatedField()
class Meta:
model = Comment
fields = [
'id',
'text',
'post'
]
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = [
'id',
'text',
'sender',
'receiver',
]
class MainUserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['username', 'email']
I tried to customize serializer and have a serializer as follows:
class PostSerializer(serializers.Field):
def to_representation(self, value):
return PostSerializer(value, context={'request': self.context['request']}).data
def to_internal_value(self, id):
try:
id = int(id)
except ValueError:
raise serializers.ValidationError("Id should be int.")
try:
post = Post.objects.get(pk=id)
except User.DoesNotExist:
raise serializers.ValidationError("Such a post does not exist")
return user
I want to represent comment objects like this
{
"post":{
"text" = "Hello"
"sender" = 1
"receiver" = 2
}
"text": "Greate"
}
My code works great but The problem is it doesn't show the Combo Box for selecting the post. I also tried to customize the PrimaryKeyRelatedField's to_represent method in this way:
class PostSerializer(serializers.PrimaryKeyRelatedField):
def to_representation(self, value):
post_id = super(PostSerializer, self).to_representation(value)
post = Post.objects.get(pk=user_id)
return PostSerializer(
user, {"context":self.context['request']}
).data
but it says the unhashable type: 'ReturnDict' and as I understand we could return anything but simple things such as int or string. Is there a way to do this?

Related

Django Rest Framework - URL With Query Parameters in Serializer

I have a Story and Post models, where a Post belongs to a Story. I want a URL to get all Posts associated with a given Story.
I was able to override the get_queryset of my PostViewSet in order to filter posts by story with URLs like http://localhost:8000/posts/?story=1/. This works beautifully if I type in the URL directly. Now I want to return this kind of url in my StorySerializer. I would like to be able to get Story responses that look like this
[
{
"url": "http://localhost:8000/stories/1/",
"title": "Hero's Journey",
"openings": 0,
"date_created": "2020-06-28T16:53:35.150630Z",
"posts": "http://localhost:8000/posts/?story=1/"
},
{
"url": "http://localhost:8000/stories/2/",
"title": "Halo 3",
"openings": 0,
"date_created": "2020-06-28T18:17:12.973586Z",
"posts": "http://localhost:8000/posts/?story=2/"
}
]
Is there DRF support for this kind of thing? I was trying to use a HyperlinkedIdentityField with 'post-list' View in my StorySerializer, but I couldn't find a combination of parameters that would work. The current exception I get is
AttributeError: 'Story' object has no attribute 'posts'
Serializers
class StorySerializer(serializers.HyperlinkedModelSerializer):
posts = serializers.HyperlinkedIdentityField(
view_name = 'post-list',
many=True,
lookup_field = 'pk',
lookup_url_kwarg = 'story',
)
class Meta:
model = models.Story
fields = ['url', 'title', 'openings', 'date_created', 'posts']
class PostSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = models.Post
fields = ['url', 'story', 'user', 'text', 'date_created']
Views
class StoryViewSet(viewsets.ModelViewSet):
queryset = models.Story.objects.all()
serializer_class = serializers.StorySerializer
class PostViewSet(viewsets.ModelViewSet):
queryset = models.Post.objects.all()
serializer_class = serializers.PostSerializer
def get_queryset(self):
queryset = self.queryset
story_id = self.request.query_params.get('story', None)
if story_id is not None:
queryset = queryset.filter(story=story_id)
return queryset
Models
class Story(models.Model):
title = models.CharField(max_length=100)
date_created = models.DateTimeField(default=timezone.now)
openings = models.PositiveSmallIntegerField(default=0)
participant = models.ManyToManyField(User)
class Post(models.Model):
text = models.CharField(max_length=300)
user = models.ForeignKey(
User,
on_delete=models.PROTECT)
story = models.ForeignKey(
Story,
on_delete=models.PROTECT)
date_created = models.DateTimeField(default=timezone.now)
I was able to find a great solution here, overriding the get_url method to map 'pk' value to 'story' directly.
https://stackoverflow.com/a/27584761/7308261
from rest_framework.reverse import reverse
import urllib
class StoryPostsHyperlinkedIdentityField(serializers.HyperlinkedIdentityField):
def get_url(self, obj, view_name, request, format):
lookup_field_value = getattr(obj, self.lookup_field, None)
result = '{}?{}'.format(
reverse(view_name, kwargs={}, request=request, format=format),
urllib.parse.urlencode({'story': lookup_field_value})
)
return result
class StorySerializer(serializers.HyperlinkedModelSerializer):
posts = StoryPostsHyperlinkedIdentityField(
view_name='post-list',
)
class Meta:
model = models.Story
fields = ['url', 'title', 'openings', 'date_created', 'posts']

DRF Serializer Error: AttributeError: 'FeedPostSerializer' object has no attribute 'auth_user'

I'm creating a newsfeed in an APP.
A user is logged in and sees the posting of other users.
Therefore I need two user models (user + auth_users)
Now I want to add a boolean field that shows if a post is already liked or not.
I already looked at the documentation and other posts here but I can´t find a solution.
The auth_user is shown in the response but I can´t included it in the get_already_liked function
class NewsPostSerializer(serializers.HyperlinkedModelSerializer):
user = UserSerializer(read_only=True)
auth_user = serializers.PrimaryKeyRelatedField(
read_only=True,
default=serializers.CurrentUserDefault()
)
attachments = AttachmentSerializer(read_only=True, many=True)
already_liked = serializers.SerializerMethodField()
def get_already_liked(self, request):
liking_kwargs = {
'post_id': request.id,
'user_id': self.auth_user
}
if LikePost.objects.filter(**liking_kwargs).exists():
return True
else:
return False
class Meta:
model = Post
read_only_fields = (
'id', "user", 'creation_time_stamp', 'auth_user', 'ready_liked',
)
fields = (
'id', 'user', 'creation_time_stamp', 'last_update_time_stamp',
'description', 'attachments', 'already_liked', 'auth_user',
)
UPDATE:
In another post I found a solution. My code looks now like this and works:
class NewsPostSerializer(serializers.HyperlinkedModelSerializer):
user = UserSerializer(read_only=True)
attachments = PostAttachmentSerializer(read_only=True, many=True)
already_liked = serializers.SerializerMethodField()
def get_already_liked(self, obj):
user = self.context['request'].user.id
liking_kwargs = {
'post_id': obj.id,
'user_id': user
}
if LikePost.objects.filter(**liking_kwargs).exists():
return True
else:
return False
class Meta:
model = Post
read_only_fields = (
'id', "user", 'creation_time_stamp', 'attachments', 'already_liked',
)
fields = (
'id', 'user', 'creation_time_stamp', 'last_update_time_stamp',
'description', 'attachments', 'already_liked',
)
Thanks to Marco and Shakil
I would change the approach here a little bit. For the like counter I'd use something like a model in the database that has a foreign key to the picture and another foreign key to the user, with that in place you can use the reverse relations between the models. For instance:
class Post(models.Model):
...
...
def number_of_likes(self):
return len(self.likes.all()) # dont know if sintax is right, but the idea is to get all the instance of likes that is related to this post. (reverse relation)
class Like(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE ,related_name='likes')
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='all_likes')
So, what I recommend is to insert the counter of likes inside the model of the user, and then you can start playing around with this in the serializers.
Your serializerMethodField's arguments are wrong. This should be like this
def get_already_liked(self, obj):
request = self.context.get('request')
liking_kwargs = {
'post_id': request.id, // I have a doubt, is this request.data['id']
'user_id': obj.auth_user
}
if LikePost.objects.filter(**liking_kwargs).exists():
return True
else:
return False

Rest Call gives error : Incorrect type. Expected pk value, received str

This post has an update below.
I currently have these two models. I am trying to create a job using CreateAPIView. Before I show the view here are my models
class modelJobCategory(models.Model):
description = models.CharField(max_length=200, unique=True)
other = models.CharField(max_length=200, unique=False , blank=True , null=True)
class modelJob(models.Model):
category = models.ManyToManyField(modelJobCategory,null=True,default=None,blank=True)
description = models.CharField(max_length=200, unique=False)
These two are my serializers
class Serializer_CreateJobCategory(ModelSerializer):
class Meta:
model = modelJobCategory
fields = [
'description',
]
class Serializer_CreateJob(ModelSerializer):
class Meta:
model = modelJob
category = Serializer_CreateJobCategory
fields = [
'category',
'description',
]
def create(self, validated_data):
job = modelJob.objects.create(user=user,category=?,...) #How to get category ?
return job
Now this is my view
class CreateJob_CreateAPIView(CreateAPIView):
serializer_class = Serializer_CreateJob
queryset = modelJob.objects.all()
def post(self, request, format=None):
serializer = Serializer_CreateJob(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Now I am passing the following JSON
{
"category" :{
"description": "Foo"
},
"description" : "World"
}
However I get the exception
{
"category": [
"Incorrect type. Expected pk value, received str."
]
}
I came across the same question here and it mentions i need to define a slug field which I am not sure where. Any suggestion on how I can fix this ?
Update:
So my create Job serializer looks like this now however it returns back the error
Got AttributeError when attempting to get a value for field category
on serializer Serializer_CreateJob. The serializer field might be
named incorrectly and not match any attribute or key on the modelJob
instance. Original exception text was: 'ManyRelatedManager' object has
no attribute 'description'.
class Serializer_CreateJob(ModelSerializer):
category = serializers.CharField(source='category.description')
class Meta:
model = modelJob
category = Serializer_CreateJobCategory()
fields = [
'category',
'description',
]
def create(self, validated_data):
category_data = validated_data.pop('category')
category = modelJobCategory.objects.get(description=category_data['description'])
job = modelJob.objects.create(description=validated_data["description"])
job.category.add(category)
job.save()
return job
Any suggestions on how I can fix this now ?
Can you try this?
class Serializer_CreateJob(ModelSerializer):
category = serializers.SlugRelatedField(
many=True,
queryset=modelJobCategory.objects.all(),
slug_field='description'
)
class Meta:
model = modelJob
fields = [
'category',
'description',
]
Try to explicitly define category field and use source=category.description like this:
from rest_framework import serializers
class Serializer_CreateJob(ModelSerializer):
category = serializers.CharField(source='category.description')
class Meta:
model = modelJob
category = Serializer_CreateJobCategory
fields = [
'category',
'description',
]
def create(self, validated_data):
category_data = validated_data.pop('category')
category = Category.objects.get(description=category_data['description'])
job = modelJob.objects.create(description=validated_data['description'],category=category,...) #categy object found by it's description
return job

How to save a modelSerializer that has relations? - django

I want to save a sent json data to db by django-rest-framework.
the problem is, not saving the relation and returns error.
The bellow snippet is my models:
class Profile(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL, related_name='profile', on_delete=models.CASCADE)
name = models.CharField(max_length=30)
family = models.CharField(max_length=50)
class Klass(models.Model):
title = models.CharField(max_length=50)
description = models.CharField(max_length=500)
teacher = models.ForeignKey(Profile, related_name='teacher', on_delete=models.CASCADE)
I use below serializer for serializing/deserializing the Klass model.
class ProfileSerializer(serializers.ModelSerializer):
class Meta:
model = Profile
fields = ('pk', 'name', 'family')
class KlassSerializer(serializers.ModelSerializer):
teacher = ProfileSerializer()
class Meta:
model = Klass
fields = ('id', 'title', 'description', 'teacher')
now when I prepare a JSON object and send it to the view, it returns error. the below is the view class:
class KlassView(APIView):
"""for SELECT, INSERT Queries"""
def get(self, request, pk):
# somthing
#csrf_exempt
def post(self,request, pk=None):
"""For Creating A Class"""
serializer = KlassSerializer(data=request.data)
if serializer.is_valid():
teacher = ProfileSerializer(request.data['teacher']['pk'])
serializer.teacher = teacher.data
serializer.save()
return Response({'data': serializer.data})
else:
return Response({'data': serializer.errors})
and the error is:
The .create() method does not support writable nested fields by default.
Write an explicit .create() method for serializer mainp.serializers.KlassSerializer, or set read_only=True on nested serializer fields.
How can I save relation in KlassSerializer in order to save to db?
At first change your serializer like below:
class KlassSerializer(serializers.ModelSerializer):
# teacher = ProfileSerializer() # No need to this!
class Meta:
model = Klass
# fields = ('id', 'title', 'description', 'teacher')
fields = ('id', 'title', 'description') # Omit teacher
Then get profile from requested user and pass it to your serializer:
def post(self,request, pk=None):
"""For Creating A Class"""
serializer = KlassSerializer(data=request.data)
if serializer.is_valid():
teacher = ProfileSerializer(request.data['teacher']['pk'])
serializer.teacher = teacher.data
serializer.save(teacher=request.user.profile) # Retrieve teacher and stroe
return Response({'data': serializer.data})
else:
return Response({'data': serializer.errors})
Just override the create method of ModelSerializer in KlassSerializer.
class KlassSerializer(serializers.ModelSerializer):
teacher = ProfileSerializer()
class Meta:
model = Klass
fields = ('id', 'title', 'description', 'teacher')
def create(self, validated_data):
profile = Profile.objects.filter(pk=validated_data['teacher']['pk'])
if profile:
k = Klass()
k.teacher = profile
...

django rest framework create nested objects "Models" by POST

I'm trying POST a new a Nested Object, the problem is just create the "top" object (Playlist), but don't create the "ChannelItem"...
My Models:
class Playlist(models.Model):
provider = models.IntegerField()
channel_id = models.CharField(max_length=100)
channel_version = models.CharField(blank=True, max_length=100)
start = models.DateTimeField()
url = models.CharField(max_length=500)
class ChannelItem(models.Model):
playlist = models.ForeignKey(Playlist, editable=False, related_name='channelitems')
content_id = models.CharField(max_length=100)
content_version = models.CharField(blank=True, max_length=100)
My Serializer:
class ChannelItemSerializer(serializers.ModelSerializer):
class Meta:
model = ChannelItem
fields = ('content_id', 'content_version')
exclude = ('id')
depth = 1
class PlaylistSerializer(serializers.ModelSerializer):
class Meta:
model = Playlist
fields = ('id', 'provider', 'channel_id', 'channel_version', 'start',
'url', 'channelitems')
depth = 2
channelitems = ChannelItemSerializer()
I use the curl to post the following data :
'{"provider":125,"channel_id":"xyz", "channel_version":"xsqt",
"start":"2012-12-17T11:04:35","url":"http://192.168.1.83:8080/maaaaa",
"channelitems":[{"content_id":"0.flv", "content_version":"ss"},
{"content_id":"1.flv","content_version":"ss"}]}' http://localhost:8000/playlist_scheduler/playlists/
I receive the message:
HTTP/1.1 201 CREATED
Content-Type: application/json
Transfer-Encoding: chunked
Date: Mon, 17 Dec 2012 20:12:54 GMT
Server: 0.0.0.0
{"id": 25, "provider": 125, "channel_id": "xyz", "channel_version": "xsqt",
"start":"2012-12-17T11:04:35", "url": "http://localhost:8080/something",
"channelitems": []}
Nested representations do not currently support read-write, and should instead be read-only.
You should probably look into using a flat representation instead, using pk or hyperlinked relations.
If you need the nested representation, you may want to consider having two separate endpoints - a flat writable endpoint, and a nested read-only endpoint.
If someone needs a quick-and-dirty solution for that, I came up with this one I'll be temporary using in a project:
class NestedManyToManyField(serializers.WritableField):
def to_native(self, value):
serializer = self.Meta.serializer(value.all(), many=True, context=self.context)
return serializer.data
def from_native(self, data):
serializer = self.Meta.serializer(data=data, many=True, context=self.context)
serializer.is_valid()
serializer.save()
return serializer.object
class Meta:
serializer = None
Then create your own subclass of NestedManyToManyField:
class TopicNestedSerializer(NestedManyToManyField):
class Meta:
serializer = MyOriginalSerializer
An example of MyOriginalSerializer:
class MyOriginalSerializer(serializers.ModelSerializer):
class Meta:
model = models.MyModel
fields = ('id', 'title',)
This works fine for me so far. But be aware there are clean fixes coming:
https://github.com/tomchristie/django-rest-framework/issues/960
https://github.com/tomchristie/django-rest-framework/pull/817
after a long effort I made a first version that funcinasse ...
I believe that with some improvement could be included within the ModelSerializer
class ChannelItemSerializer(serializers.ModelSerializer):
class Meta:
model = ChannelItem
fields = ('id', 'content_id', 'content_version')
def field_from_native(self, data, files, field_name, into):
try:
if self._use_files:
_files = files[field_name]
else:
_data = data[field_name]
except KeyError:
if getattr(self, 'default', None):
_data = self.default
else:
if getattr(self, 'required', None):
raise ValidationError(self.error_messages['required'])
return
if type(_data) is list:
into[field_name] = []
for item in _data:
into[field_name].append(self._custom_from_native(item))
else:
into[field_name] = self._custom_from_native(_data)
def _custom_from_native(self, data):
self._errors = {}
if data is not None:
attrs = self.restore_fields(data, None)
attrs = self.perform_validation(attrs)
else:
self._errors['non_field_errors'] = ['No input provided']
if not self._errors:
return self.restore_object(attrs, instance=getattr(self, 'object', None))
class PlaylistSerializer(serializers.ModelSerializer):
class Meta:
model = Playlist
fields = ('id', 'provider', 'channel_id', 'channel_version', 'start', 'url', 'channel_items')
depth = 1
channel_items = ChannelItemSerializer()
def restore_object(self, attrs, instance=None):
self.foreign_data = {}
for (obj, model) in self.opts.model._meta.get_all_related_objects_with_model():
field_name = obj.field.related_query_name()
if field_name in attrs:
self.foreign_data[field_name] = attrs.pop(field_name)
return super(PlaylistSerializer, self).restore_object(attrs, instance)
def save(self, save_m2m=True):
super(PlaylistSerializer, self).save(save_m2m)
if getattr(self, 'foreign_data', None):
for accessor_name, object_list in self.foreign_data.items():
setattr(self.object, accessor_name, object_list)
self.foreign_data = {}
return self.object
For me, I have a hybrid workaround that I'm OK with. Namely, create a view that has:
the ManyToMany field in its un-nested serializer form
alias the nested ManyToMany field into a variable with _objs as the suffix and specify it as as read only
when you PUT back to the server reconcile the two aliased fields and store the result in the un-nested serializer field
e.g.
class MSerializer(serializers.HyperlinkedModelSerializer):
foo_objs = TempSensorSerializer(source='foos', many=True, allow_add_remove=True,required=False,read_only=True)
class Meta:
model = M
fields = ('url', 'foos', 'foo_objs')
I don't love this solution, but it beats trying to separately query and collate the nested fields after retrieving the initial container.