django-rest-framework many-to-many relations. Create if not exists - python

I try to send the following data to my django application:
{
"hashtags": ["hashtag"],
"title": "title",
"message": "message"
}
and i get this response:
{
"hashtags": [
{
"non_field_errors": [
"Invalid data. Expected a dictionary, but got int."
]
}
]
}
I have the following view defined in views.py
class PostList(generics.ListCreateAPIView):
queryset = Post.objects.all()
serializer_class = PostSerializer
permission_classes = IsAuthorizedOwnerOrReadOnly,
the models are defined like this:
class Post(models.Model):
ambassador = models.OneToOneField("User")
publish_date = models.DateTimeField(auto_now_add=True, null=True, blank=True)
hashtags = models.ManyToManyField("PostHashtags", related_query_name="posts")
title = models.CharField(max_length=TEXT_SHORT, null=True, blank=True)
message = models.CharField(max_length=TEXT_MIDDLE, null=True, blank=True)
class PostHashtags(models.Model):
hashtag = models.CharField(max_length=TEXT_SHORT, null=False)
def __unicode__(self):
return self.hashtag
and i define the serializers like this:
class PostHashtagSerializer(serializers.ModelSerializer):
class Meta:
model = PostHashtags
fields = ("hashtag",)
class PostSerializer(serializers.ModelSerializer):
hashtags = PostHashtagSerializer(many=True)
class Meta:
model = Post
fields = ("id", "hashtags", "title", "message",)
read_only_fields = ("id", 'account_name',)
It seems like the hashtags are not created automatically using my current serialisation config. Is there a way to have my hashtags created if they do not yet exist, and if they do exist, have the Post use that same hashtag? In that case, how should my serialisers be defined?
EDIT 1:
After GwynBleidD's suggestions I now get the following error:
The `.create()` method does not support writable nestedfields by default.
Write an explicit `.create()` method for serializer PostSerializer , or set `read_only=True` on nested serializer fields.
Does anyone have a suggestion for such a create method?
EDIT 2: solved it using the following serialisers
class PostHashtagSerializer(serializers.ModelSerializer):
hashtag = serializers.CharField()
class Meta:
model = PostHashtags
fields = ("hashtag",)
class PostSerializer(serializers.ModelSerializer):
hashtags = PostHashtagSerializer(many=True,)
class Meta:
model = Post
fields = ("ambassador", "hashtags", "title", "message",)
def create(self, validated_data):
hashtags_data = validated_data.pop('hashtags')
post = Post.objects.create(**validated_data)
for hashtag in hashtags_data:
ht = PostHashtags.objects.create()
ht.hashtag = hashtag.get("hashtag")
ht.save()
post.hashtags.add(ht)
post.save()
return post

Hashtags are not string, but dict in that example. You have to submit:
{
"hashtags": [{"hashtag": "hashtag"}],
"title": "title",
"message": "message"
}

Related

POST with an ID and GET the nested object does not work in django at the same time in ModelSerializer

I have spent already a couple of days researching about this issue in similar questions and I am not able to get a solution. This should be something simple, I have a model:
model.py
class Item(models.Model):
"""Class to represent an item..."""
label = models.TextField(null=True)
name = models.TextField()
category = models.ForeignKey( "Category", on_delete=models.SET_NULL,
null=True, default=DEFAULT_CATEGORY_ID)
class Category(models.Model):
"""Class to represent the category of an Item. Like plants, bikes..."""
name = models.TextField()
description = models.TextField(null=True)
view.py
class ItemViewset(viewsets.ModelViewSet): # pylint: disable=too-many-ancestors
"""API Endpoint to return the list of items"""
queryset = Item.objects.all()
serializer_class = ItemSerializer
serializer.py
class ItemSerializer(serializers.ModelSerializer):
"""Serializer for Item."""
category = CategorySerializer(read_only=True)
class Meta: # pylint: disable=too-few-public-methods
"""Class to represent metadata of the object."""
model = Item
fields = [ 'id', 'label', 'name', 'category']
read_only_fields = ['id']
# def to_representation(self, instance):
# ret = super().to_representation(instance)
# ret['category'] = CategorySerializer(instance.category).data
# return ret
def create(self, request):
# Look up objects by arbitrary attributes.
# You can check here if your students are participating
# the classes and have taken the subjects they sign up for.
category = get_object_or_404(Category(), id=request.data.get('category'))
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save(category=category)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
If I have the line category = CategorySerializer(read_only=True) commented the nested get won't work, for example for a curl to an object. I will get just de id of the structure. If I uncomment it like it is in the example I get the right response:
{
"id": 60,
"label": null,
"name": "Delete me",
"category": {
"id": 1,
"name": "IT"
}
}
But then the post with id will not work, the category will be set to null.
How can I get both working at the same time?
Update: This is the post I want to do:
{
"label": "00000003",
"name": "Delete me",
"category": 1,
}
You can override the to_represention method of your serializer
class ItemSerializer(serializers.ModelSerializer):
"""Serializer for Item."""
class Meta: # pylint: disable=too-few-public-methods
"""Class to represent metadata of the object."""
model = Item
fields = [ 'id', 'label', 'name', 'category']
read_only_fields = ['id']
def to_representation(self, instance):
data = super().to_representation(instance)
data["category"] = CategorySerializer(instance.category).data
return data

Django: How to change field name in nested serializer

Currently, I have this serializer:
class TokenSerializer(serializers.ModelSerializer):
"""
Serializer for Token model
"""
user = UserDataSerializer(many=False, read_only=True)
class Meta:
model = TokenModel
fields = ('key', 'user')
And this is the response I get:
{
"key": "d1de7dd82f2b987a6d9f35f1d033876e164f7132",
"user": {
"username": "peter258",
"first_name": "Peter",
"last_name": "Jones",
"email": "peter.jones#gmail.com"
}
}
I would like to change the response so instead of saying "user" it says "data" but when I change the serializer to something like this, I only get the "key" in the response:
class TokenSerializer(serializers.ModelSerializer):
"""
Serializer for Token model
"""
data = UserDataSerializer(many=False, read_only=True)
class Meta:
model = TokenModel
fields = ('key', 'data')
How do you properly change the name of the "user" field inside nested serializers?
You can achieve this by providing a source argument to your UserDataSerializer, which expects the name of the attribute that will be used to populate the field.
class TokenSerializer(serializers.ModelSerializer):
"""
Serializer for Token model
"""
data = UserDataSerializer(many=False, read_only=True, source="user")
class Meta:
model = TokenModel
fields = ('key', 'data')

Django Rest Framework: Nested serializers returning a dictionary but list is desired

I am trying to implement a basic Question Answer app. Here is my Questions model:
class Questions(models.Model):
created = models.DateTimeField(auto_now_add=True)
question = models.CharField(max_length=1000, blank=True, default='')
asked_by = models.ForeignKey(User, related_name='question_user', null=True, default=None, on_delete=models.CASCADE)
upvote_by = models.ManyToManyField(User, default=None, related_name='advicesv1_upvote_question')
Now, I want to send the usernames/first_names of the people in 'asked_by' and 'upvote_by' instead of ids which is sent by default. To implement this I used a nested serializer like this:
class QuesUserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('username',)
class NewQuestionSerializer(serializers.ModelSerializer):
question_id = serializers.IntegerField(source='id', required=False)
asked_by = QuesUserSerializer(read_only=True)
upvote_by = QuesUserSerializer(many=True, read_only=True)
class Meta:
model = Questions
fields = ('question_id', 'created', 'question', 'asked_by', 'upvote_by',)
due to this, my current response is like this:
{
"question_id": 182,
"created": "2017-03-07T10:53:16.241533Z",
"question": "hey what's up?",
"asked_by": {
"username": "testuser141"
},
"upvote_by": [
{
"username": "testuser"
},
{
"username": "testuser1"
}
]
},
But my desired response is:
{
"question_id": 182,
"created": "2017-03-07T10:53:16.241533Z",
"question": "hey what's up?",
"asked_by": "testuser141",
"upvote_by": ["testuser", "testuser1",....],
},
What is the best way to achieve this using django restframework serializers?
You can override the to_representation method of the nested serializer so that your QuesUserSerializer returns a string for each user, rather than an object.
class QuesUserSerializer(serializers.ModelSerializer):
class Meta:
model = User
def to_representation(self, obj):
return obj.username
try to override to representation method, like this:
class NewQuestionSerializer(serializers.ModelSerializer):
question_id = serializers.IntegerField(source='id', required=False)
asked_by = QuesUserSerializer(read_only=True)
upvote_by = QuesUserSerializer(many=True, read_only=True)
class Meta:
model = Questions
fields = ('question_id', 'created', 'question', 'asked_by', 'upvote_by',)
def to_representation(self, obj):
data = super(NewQuestionSerializer, self).to_representation(obj)
data['asked_by']= obj.asked_by.username
data['upvoted_by'] = obj.upvote_by.all().values_list('username', flat=True)
return data
this only changue the representation of object, but not the logic of save o update...
Although natbot1 answer works, a more elegant solution is to use a slugrelatedfield

Creating a Serialiser for a Reverse Generic Relationship

Within my Page Serialisers PageSerializer shown below, I would like to get the Generic relationship from Collection and
show all Collection Items (which is a many-to-many) nested within PageSerializer.
I want to achieve an output something like this....
"results": [
{
"url": "http://127.0.0.1:8000/v1/page/00c8015e-9b03...",
"title": "Test Page",
"collections":
{
"title": "Test Collection",
[
{
"image": "http://www.demo.com/test.png",
"video": None
},
{
"image": None,
"video": "http://www.demo.com/test.mpeg"
}
]
}
}
]
}
This is what I currenrlty have....
class Page(models.Model):
title = models.CharField(max_length=80)
class PageSerializer(serializers.ModelSerializer):
class Meta:
model = Page
fields = ("title", )
class Collection(models.Model):
title = models.CharField(max_length=80, help_text="Title of collection")
content_type = models.ForeignKey(ContentType)
object_id = models.UUIDField()
content_object = generic.GenericForeignKey('content_type', 'object_id')
collection_items = models.ManyToManyField('collections.CollectionItem')
class CollectionItem(models.Model):
image = models.ImageField(upload_to='/test')
video = models.URLField(max_length=512, blank=True, null=True)
Becuase the Generic relationship is on the Collection Model how can this be done in DRF?
I was thinking of creating a method on the Page model itself which gets the data and adding this to the serializer.
I'm sure there is a better way. I have looked at http://www.django-rest-framework.org/api-guide/relations/#generic-relationships
but it only describes getting the relationship the other way around.
Create a GenericRelation on your Page model for example:
class Page(models.Model):
title = models.CharField(max_length=80)
stuff = GenericRelation('app_name_model_here')
Then used your nested serializer like this...
class PageSerializer(serializers.ModelSerializer):
stuff = YOURColltionserializer(many=True)
class Meta:
model = Page
fields = ("title", "stuff" )
Once you have defined your YOURColltionserializer this will work as excepted.
First of all, your JSON is incorrect, the propher way is something like this:
{
"results":[
{
"url":"http://127.0.0.1:8000/v1/page/00c8015e-9b03...",
"title":"Test Page",
"collections":{
"title":"Test Collection",
"collection_items":[
{
"image":"http://www.demo.com/test.png",
"video":null
},
{
"image":null,
"video":"http://www.demo.com/test.mpeg"
}
]
}
}
]
}
To achieve this You should define serializer like this:
class CollectionItemSerializer(serializers.ModelSerializer):
class Meta:
model = CollectionItem
fields = ['image', 'video']
class CollectionSerializer(serializers.ModelSerializer):
collection_items = CollectionItemSerializer(many=True, read_only=True)
class Meta:
model = Collection
fields = ['title', 'collection_items']
class PageSerializer(serializers.Serializer):
title = serializes.CharField(max_length=200)
url = serializers.SerializerMethodField()
collections = CollectionSerializer(many=true, read_only=True)
def get_url(self, obj):
return self.context.request.path_info
class ResultSerializer(serializers.Serializer):
result = PageSerializer(read_only=True)
Then in your view:
page = Page.objects.get(...)
collections = Collection.objects.filter(...)
result = {
'title': page.title,
'collections':collections
}
serializer = ResultSerializer(
{'result':result},
context={'request': request}
}
return Response(serializer.data)

ModelViewSet - Update nested field

I'm currently working on Django with Django Rest Framwork.
I can't update my object within nested object field.
serializer.py
class OwnerSerializer(serializers.ModelSerializer):
class Meta:
model = Owner
fields = ('id', 'name')
class CarSerializer(serializers.ModelSerializer):
owner = ownerSerializer(many=False, read_only=False)
class Meta:
model = Car
fields = ('id', 'name', 'owner')
view.py
class OwnerViewSet(viewsets.ModelViewSet):
queryset = Owner.objects.all()
serializer_class = OwnerSerializer
class CarViewSet(viewsets.ModelViewSet):
serializer_class = CarSerializer
queryset = Car.objects.all()
def create(self, request):
serialized = self.serializer_class(data=request.DATA)
if serialized.is_valid():
serialized.save()
return Response(status=HTTP_202_ACCEPTED)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
When I do this :
Request URL:http://localhost:9000/api/v1/cars/1/?format=json
Request Method:PUT
Request Paylod :
{
"id":1,
"name": "TEST",
"ower": {
"id":1,
"name": "owner_test"
}
}
I get the following Response :
The `.update()` method does not support writable nestedfields by default.
Write an explicit `.update()` method for serializer `app.serializers.CarSerializer`,
or set `read_only=True` on nested serializer fields.
Knowing :
I want to keep the owner serialization on GET;
We can imagine the car nested by another object and ect...
How can I do if i want to change the owner when I update the car.
A little late, but, Try this,
class OwnerSerializer(serializers.ModelSerializer):
class Meta:
model = Owner
fields = ('id', 'name')
extra_kwargs = {
'id': {
'read_only': False,
'required': True
}
} #very important
def create(self, validated_data):
# As before.
...
def update(self, instance, validated_data):
# Update the instance
instance.some_field = validated_data['some_field']
instance.save()
# Delete any detail not included in the request
owner_ids = [item['owner_id'] for item in validated_data['owners']]
for owner in cars.owners.all():
if owner.id not in owner_ids:
owner.delete()
# Create or update owner
for owner in validated_data['owners']:
ownerObj = Owner.objects.get(pk=item['id'])
if ownerObje:
ownerObj.some_field=item['some_field']
....fields...
else:
ownerObj = Owner.create(car=instance,**owner)
ownerObj.save()
return instance
Just in-case someone stumbles on this
had the same error in my case but setting read_only to True fixed it for me.
owner = ownerSerializer(many=False, read_only=True)
Note that, this field won't appear in the form when posting data to the api.

Categories