django rest framework posting object with relations fails to create - python

I cannot make the Django rest framework to create the object with dependency. In short here is the simplified configuration that works for other part update, remove and create Employee. But it fails when creating Case with ForeignKey to Employee.
models.py
class Employee(models.Model):
firstName = models.CharField(max_length=50)
lastName = models.CharField(max_length=50)
class Case(models.Model):
title = models.CharField(max_length=100)
author = models.ForeignKey(Employee, related_name='cases_created')
serializers.py
class SCase (serializers.HyperlinkedModelSerializer):
author = SEmployeeList(required = True)
class Meta:
model = models.Case
fields = ('url', 'title', 'author', 'id')
class SEmployee(serializers.HyperlinkedModelSerializer):
cases_created = SCaseList(many=True, required=False)
class Meta:
model = models.Employee
fields = ('url', 'firstName', 'lastName','cases_created', 'id')
views.py
class EmployeeViewSet(viewsets.ModelViewSet):
queryset = models.Employee.objects.all()
serializer_class = serializers.SEmployee
class CaseViewSet(viewsets.ModelViewSet):
queryset = models.Case.objects.all()
serializer_class = serializers.SCase
The issue is when I create the object case, that must have reference to author.
Passing existing objects creates the Case and new employee
POST
{"author":{"url":"http://localhost:8000/restbase/employee/23/",
"firstName":"Mark",
"lastName":"Hilton",
"cases_created":[],
"id":23},
"title":"New Case"
}
Passing only URL for and existing author fails because creating author fails.
POST
{"author":"http://127.0.0.1:8000/restbase/employee/23/","title":"New Case"}
RESPONSE
{"author": [{"non_field_errors": ["Invalid data"]}]}
Passing only URL as an field of author brings following resposne
POST
{"author":{"url":"http://127.0.0.1:8000/restbase/employee/23/"},"title":"New Case"}
RESPONSE
{"author": [{"lastName": ["This field is required."], "firstName": ["This field is required."]}]}

After the investigation deeper there is a working solution. The author cannot be serialized.
class SCase (serializers.HyperlinkedModelSerializer):
# removed line: author = SEmployeeList(required = True)
class Meta:
model = models.Case
fields = ('url', 'title', 'author', 'id')
I still do not know how to use detailed information and create depended element

Related

Correct Django serializer implementation for many to many field

I have two django models that are related with a many to many field:
class Member(models.Model):
user = models.OneToOneField(to=settings.AUTH_USER_MODEL)
date_of_birth = models.DateField()
bio = models.TextField()
class Book(models.Model):
name = models.CharField(max_length=255)
author= models.CharField(max_length=255)
description = models.TextField()
read_by = models.ManyToManyField(to=Member, related_name='books_read')
The serializers for these models are:
class MemberSerializer(serializers.Model):
id = serializers.IntegerField(read_only=True)
user_id = serializers.IntegerField(read_only=True)
class Meta:
model = Member
fields = ['id', 'user_id', 'date_of_birth', 'bio']
class BookSerializer(serializers.Model):
id = serializers.IntegerField(read_only=True)
class Meta:
model = Book
fields = ['id', 'name', 'author', 'bio']
I want to create an endpoint to be able to add a book to a member. The only way I could write a serializer for it was:
class BookIdSerializer(serializers.Model):
class Meta:
model = Book
fields = ['id']
def update(self, **kwargs):
# logic to add book with passed id to the authenticated user's member profile
This however feels very wrong for two obvious reasons:
1 - There is an entire serializer object just to receive a book id
2 - It is not even generic because it performs a very specific function of adding a book with passed book id to a member
I am sure there is a better way of doing this. Kindly guide me if you know.
You can use a PrimaryKeyRelatedField:
class MemberSerializer(serializers.Model):
id = serializers.IntegerField(read_only=True)
user_id = serializers.IntegerField(read_only=True)
books = serializers.PrimaryKeyRelatedField(many=True, queryset=Book.objects.all())
class Meta:
model = Member
fields = ['id', 'user_id', 'date_of_birth', 'bio', 'books']
This will place the related books in an array, and you can edit that array to add and remove books, allowing adding or removing many at a time.
See How to use PrimaryKeyRelatedField to update categories on a many-to-many relationship for a related question/answer.

getting null in object of many to many relation object django

I'm saving an object with many to many relations in the database but when I'm fetching it so so it returns me IDs of that relational object but I want to the whole object instead, so I have added a property in the serializer and after that, it returns me the object.name only and the value of it is null, I don't know why it is behaving like this? anyone can help?
views.py
queryset = Tag.objects.filter(project_id=project_id).all()
serializer = TagSerializer(queryset, many=True)
return Response(serializer.data)
serializers.py
class TagSerializer(serializers.ModelSerializer):
class Meta:
model = Tag
# fields = '__all__'
fields = ['id', 'name', 'description', 'singleline']
singleline = SinglelineSerializer(read_only=True)
models.py
class Tag(models.Model):
name = models.CharField(max_length=255, default='')
description = models.CharField(max_length=255, default='')
singleline = models.ManyToManyField(Singleline)
class Meta:
db_table = 'tags'
A Tag can have many Singlelines, you need to work with many=True since it is a collection:
class TagSerializer(serializers.ModelSerializer):
singleline = SinglelineSerializer(read_only=True, many=True)
class Meta:
model = Tag
fields = ['id', 'name', 'description', 'singleline']
You can improve efficiency of the view by prefetcing the singleline relation and thus load all the related Singlelines in bulk:
queryset = Tag.objects.filter(project_id=project_id).prefetch_related('singleline')
serializer = TagSerializer(queryset, many=True)
return Response(serializer.data)

How to make a ManyToMany serialization to be able to create a object in the POST request?

I'm trying to create a serialize to handle a ManyToMany relation, but it's not working. I have read the documentation and I probably doing something wrong. Also I have read the answers here.
Here are my models.
class Author(models.Model):
name = models.CharField(verbose_name="Name", max_length=255)
class Book(models.Model):
author = models.ForeignKey(
Author,
related_name="id_author",
blank=True,
null=True,
on_delete=models.PROTECT)
price = models.FloatField(verbose_name="Price")
class FiscalDocument(models.Model):
seller = models.ForeignKey(
User,
related_name="user_id",
on_delete=models.PROTECT)
book = models.ManyToManyField(Book)
My serializer:
class AuthorSerializer(serializers.ModelSerializer):
class Meta:
model = Author
fields = ('id', 'name')
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = ('id', 'author', 'price')
def to_representation(self, instance):
response = super().to_representation(instance)
response['author'] = AuthorSerializer(instance.author).data
return response
class FiscalDocumentSerializer(serializers.ModelSerializer):
book = BookSerializer()
class Meta:
model = FiscalDocument
fields = ('id', 'seller', 'book')
def create(self, validated_data):
book_data = validated_data.pop('book')
fiscal_document = FiscalDocument.objects.create(**validated_data)
Book.objects.create(FiscalDocument=FiscalDocument,**medicine_data)
return fiscal_document
When I try to access the endpoint of the FiscalDocument, django-rest-framework is throwing an error:
Got AttributeError when attempting to get a value for field price on serializer BookSerializer. The serializer field might be named incorrectly and not match any attribute or key on the ManyRelatedManager instance. Original exception text was: ManyRelatedManager object has no attribute price.
If anyone can help XD.

Django Rest Framework Serializer Doesn't Display ALL Fields

I have a problem where DRF isn't displaying all of my fields correctly for a model class / reference table (specifically the primary key).
My Model Class looks like this (very simple):
class UnitOfIssue(models.Model):
code = models.CharField(max_length=2, primary_key=True)
description = models.CharField(max_length=16)
class Meta:
ordering = ('code',)
def __str__(self):
return "{0} - {1}".format(self.code, self.description)
My Serializer Looks like this:
class UnitOfIssueSerializer(serializers.HyperlinkedModelSerializer):
"""
"""
url = serializers.HyperlinkedIdentityField(
read_only=True,
view_name='unitofissue-detail',
format='html',
lookup_field='code')
class Meta:
model = UnitOfIssue
fields = ('code', 'description', 'url')
# fields = '__all__'
And I'm using a generic view:
class UnitOfIssueDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = UnitOfIssue.objects.all()
serializer_class = UnitOfIssueSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
lookup_field = 'code'
In order for the UnitOfIssue primary key code to be displayed in the auto-generated UI, I have to define fields = ('code', 'description', 'url') in the serializer instead of fields = '__all__'.
I want to just be able to use the '__all__' syntax but I can't figure out what's going wrong.
Also, I'm using Django==1.11.13 and djangorestframework==3.8.2
This issue plagued me for weeks, and yet it was such a simple error. I fixed it by changing the serializer base class from:
class UnitOfIssueSerializer(serializers.HyperlinkedModelSerializer):
to:
class UnitOfIssueSerializer(serializers.ModelSerializer):

Django rest framework serializing many to many field

How do I serialize a many-to-many field into list of something, and return them through rest framework? In my example below, I try to return the post together with a list of tags associated with it.
models.py
class post(models.Model):
tag = models.ManyToManyField(Tag)
text = models.CharField(max_length=100)
serializers.py
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = ("text", "tag"??)
views.py
class PostViewSet(viewsets.ReadOnlyModelViewSet):
queryset = Post.objects.all()
serializer_class = PostSerializer
You will need a TagSerializer, whose class Meta has model = Tag. After TagSerializer is created, modify the PostSerializer with many=True for a ManyToManyField relation:
class PostSerializer(serializers.ModelSerializer):
tag = TagSerializer(read_only=True, many=True)
class Meta:
model = Post
fields = ('tag', 'text',)
Answer is for DRF 3
This is what I did, let´s suppose a Book can have more than one author and an Author can have more than one book:
On Model:
class Author(models.Model):
name = models.CharField(max_length=100, default="")
last_name = models.IntegerField(default=0)
class Book(models.Model):
authors = models.ManyToManyField(Author, related_name="book_list", blank=True)
name = models.CharField(max_length=100, default="")
published = models.BooleanField(default=True)
On Serializers:
class BookSerializer(serializers.ModelSerializer):
authors = serializers.PrimaryKeyRelatedField(queryset=Author.objects.all(), many=True)
class Meta:
model = Book
fields = ('id', 'name', 'published', 'authors')
class AuthorSerializer(serializers.ModelSerializer):
book_list = BookSerializer(many=True, read_only=True)
class Meta:
model = Author
fields = ('id', 'name', 'last_name', 'book_list')
Adding to #Brian's answer
"tags": [{"name": "tag1"}] can be simplified to "tags": ["tag1", "tag2",...] in this way:
class TagListingField(serializers.RelatedField):
def to_representation(self, value):
return value.name
class PostSerializer(serializers.ModelSerializer):
tag = TagListingField(many=True, read_only=True)
class Meta:
...
More info here: https://www.django-rest-framework.org/api-guide/relations/#custom-relational-fields
The default ModelSerializer uses primary keys for relationships. However, you can easily generate nested representations using the Meta depth attribute:
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = ("text", "tag")
depth = 1
As mentioned in the documentation :
The depth option should be set to an integer value that indicates the depth of relationships that should be traversed before reverting to a flat representation.
This works for me.
tag = TagSerializer(source="tag", read_only=True, many=True)
Django 2.0
For many to many field, if you want specific one:
class QuestionSerializer(serializers.ModelSerializer):
topics_list = serializers.SerializerMethodField()
def get_topics_list(self, instance):
names = []
a = instance.topics.get_queryset()
for i in a:
names.append(i.desc)
return names
class Meta:
model = Question
fields = ('topics_list',)
In the serializer on init method you can pass the queryset to the field and rest_framework valide the ids on that queryset
1) first extend your serializer from serializers.ModelSerializer
class YourSerializer(serializers.ModelSerializer):
2) include the field on the meta class
class YourSerializer(serializers.ModelSerializer):
class Meta:
fields = (..., 'your_field',)
3) in the init method:
def __init__(self, *args, **kwargs):
super(YourSerializer, self).__init__(*args, **kwargs)
self.fields['your_field].queryset = <the queryset of your field>
You can limit the queryset for that field under any argument using filter or exclude like normally you do. In case that you want include all just use .objects.all()
models.py
class Tag(models.Model):
name = models.CharField(max_length=100)
# ===============
# ... rest of the fields ...
class Post(models.Model):
tag = models.ManyToManyField(Tag)
text = models.CharField(max_length=100)
serialiazers.py
class TagSerializer(serializers.ModelSerializer):
class Meta:
model = Tag
fields = '__all__'
class PostSerializer(serializers.ModelSerializer):
tags = TagSerializer(many=True, read_only=True)
class Meta:
model = Post
fields = ("text", "tag")
views.py
## FUNCTION BASED VIEW
def fbvPost_ListView(request):
# list
if request.method == "GET":
posts = Post.objects.all()
serializer = PostSerializer(instance=posts, many=True)
return JsonResponse(serializer.data, safe=False)
return JsonResponse({"success": False})
# ===========================================================
## CLASS BASED VIEW
class cbvPost_ListView(viewsets.ReadOnlyModelViewSet):
queryset = Post.objects.all()
serializer_class = PostSerializer
NB: Tag, Post are two models & we need to serialize them. Here, Post model have a dependency of Tag models, so here we explicitly mention it, [tags = TagSerializer(many=True, read_only=True)] or its return it's primary field value.
DETAILS HERE
Hi I will be showing many to many for update and create. The context is the event can have many dances and dances can have many event.
The request will be as followed.
{
"competition": 2,
"title": "the title",
"dances":[ {"id":1},{"id":2}],
"description": "the desc"
}
The Create Function will be as followed.
def create(self, validated_data):
try:
dance_ids = []
for dance in self.initial_data['dances']:
if 'id' not in dance:
raise serializers.ValidationError({'detail': 'key error'})
dance_ids.append(dance['id'])
new_event = models.Event.objects.create(**validated_data)
if dance_ids:
for dance_id in dance_ids:
new_event.dances.add(dance_id)
new_event.save()
return new_event
except Exception as e:
raise serializers.ValidationError({'detail': e})
The Update Function will be as followed.
def update(self, instance, validated_data):
# Delete all records of genres.
try:
for current_genre in instance.dances.all():
instance.dances.remove(current_genre)
# Repopulate genres into instance.
for dance in self.initial_data['dances']:
if 'id' not in dance:
raise serializers.ValidationError({'detail': 'key error'})
dance_obj = models.Dance.objects.get(pk=dance['id'])
instance.dances.add(dance_obj)
event_updated = super().update(instance, validated_data)
return event_updated
except Exception as e:
raise serializers.ValidationError({'detail': e})
If you want to just do "dances":[1,2] instead, just make some amendments to the
for dance in self.initial_data['dances']:
if 'id' not in dance:
raise serializers.ValidationError({'detail': 'key error'})
dance_ids.append(dance['id'])
part. I hope this will be able to help yall out! :)
First, Tag needs its own serializer too
class TagSerializer(serializers.ModelSerializer):
class Meta:
model = Tag
fields = '__all__'
Then in your PostSerializer, add one line
class PostSerializer(serializers.ModelSerializer):
tag = TagSerializer(read_only=True, many=True).data
class Meta:
model = Post
fields = ("text", "tag")
This will make it so your Tag field in Post is an array of tag ids. if you don't put the ".data" part, it will put all of the attributes of tag, which is probably too much in most cases
You can use serializers.SlugRelatedField() or serializers.StringRelatedField(many=True) Serializer relations
In your case :
class PostSerializer(serializers.ModelSerializer):
tag = serializers.StringRelatedField(many=True) # this will return a list
class Meta:
model = Post
fields = ('tag', 'text',)

Categories