Django rest ModelViewSet many-to-many create - django

I have my model relationships as following: A Reader will have a Wishlist and a Wishlist will have many Books:
class Reader(models.Model):
user = models.OneToOneField(User)
...
# A library has many readers
which_library = models.ForeignKey('Library', related_name='readers', on_delete=models.CASCADE)
class Book(models.Model):
book_id = models.AutoField(primary_key=True)
title = models.CharField(max_length=30)
...
# A library has many books
which_library = models.ForeignKey('Library', related_name='books', on_delete=models.CASCADE)
# Record the date whenever a new book is added, it will be helpful for showing new arrivals
when_added = models.DateTimeField(auto_now_add=True, blank=True, null= True)
reader = models.ManyToManyField('Reader', related_name='wishlist')
My serializers:
class ReaderSerializer(serializers.ModelSerializer):
username = serializers.CharField(source='user.username')
email = serializers.CharField(source='user.email')
password = serializers.CharField(source='user.password')
class Meta:
model = Reader
#fields = '__all__'
#depth = 1
fields = ('id', 'username', 'email', 'password', 'phone', 'address', 'dob', 'which_library')
def update(self, instance, validated_data):
...
instance.which_library = validated_data.get('which_library', instance.which_library)
instance.save()
return instance
def create(self, validated_data):
user_data = validated_data.pop('user')
user = User.objects.create(**user_data)
user.set_password(user_data['password'])
user.save()
reader = Reader.objects.create(user=user, **validated_data)
return reader
class BookSerializer(serializers.ModelSerializer):
wishlist = ReaderSerializer(many=True, read_only=True)
class Meta:
model = Book
fields = '__all__'
I can already perform CRUD operations with Reader, I want to now add books to a specific Reader's wishlist. My view:
class ReaderViewSet(viewsets.ModelViewSet):
serializer_class = ReaderSerializer
def get_queryset(self):
readers = Reader.objects.filter(which_library=self.kwargs.get('library_id'))
return readers
#detail_route(methods=['post'])
def wishlist(self):
return Response('OK')
URL that I hit:
router.register(r'readers/(?P<library_id>[0-9]+)', ReaderViewSet, base_name='readers')
Here I am expecting that on hitting api/readers/<library_id>/<book_id>/wishlist/addI will be able to perform
add operation to the Wishlist.
How can I achieve that?

You can use detail_route's argument url_path to change url of endpoint. Also you can add additional arguments like book_id directly to detail_routed method, so your method can look like this:
#detail_route(methods=['post'], url_path='(?P<book_id>[0-9]+)/wishlist/add')
def wishlist(self, library_id=None, book_id=None):
reader = self.request.user.reader
book = Book.objects.get(pk=book_id)
reader.wishlist.add(book)
return Response('OK')
And it should be accessible from api/readers/<library_id>/<book_id>/wishlist/add url.

Related

TypeError: Object of type ManyRelatedManager is not JSON serializable in django rest framework

I am trying to add some students to a teacher class using their ids as primary key but I am getting above error.
I have models of Teachers and Students like this.
class Student(TimeStampAbstractModel):
user = models.OneToOneField(User, related_name="student", on_delete=models.CASCADE)
college_name = models.CharField(max_length=255, default="", blank=True)
address = models.CharField(max_length=255, default="", blank=True)
def __str__(self):
return self.user.name
class Teacher(TimeStampAbstractModel):
user = models.OneToOneField(User, related_name="teacher", on_delete=models.CASCADE)
address = models.CharField(max_length=255, default="", blank=True)
students_in_class = models.ManyToManyField(Student,related_name="teacher")
def __str__(self):
return self.user.name
Here a teacher model can have many students in a class with thier ids. I have used an put api call to add the students to the teacher in one click.
My view:
from rest_framework import status
class AddtoClassView(APIView):
def put(self,request,pk,*args,**kwargs):
id =pk
teacher = Teacher.objects.get(id=id)
serializer = TeacherSerializer(teacher,data=request.data)
if serializer.is_valid():
serializer.save()
print("iam if")
return Response({
"message":"Student has been added to class.",
"data": serializer.data
},status=status.HTTP_200_OK)
# else:
print("iam else")
return Response(serializer.data)
My serializer:
class TeacherSerializer(serializers.ModelSerializer):
students_in_class = serializers.PrimaryKeyRelatedField(
read_only= True
)
address = serializers.CharField(required=False)
# user = serializers.PrimaryKeyRelatedField(read_only=True)
class Meta:
model = Teacher
fields = ["address","students_in_class"]
# fields = '__all__'
def update(self, instance, validated_data):
instance.address = validated_data.get("address")
instance.save()
stu = validated_data.get("students_in_class")
print(stu)
if stu is not None:
print("iam stu")
instance.students_in_class.add(stu)
instance.save()
super(TeacherSerializer,self).update(instance, validated_data)
return instance
Here I have used students_in_class as pk field ( i still havent understand when to use integarfield and when to use pk field). I know since i am adding the ids to the student_in_class field i am not supposed to use it as read_only = true, however i had to use otherwise it generates error. How to solve this? Also, i dont really know which fields to define as which in serializer class.
Updated code:
class TeacherSerializer(serializers.ModelSerializer):
# students_in_class = serializers.PrimaryKeyRelatedField(
# many = True, read_only= True
# )
students_in_class = serializers.ListField(
source="students_in_class.all",
child=serializers.PrimaryKeyRelatedField(queryset=Student.objects.all()),
)
address = serializers.CharField(required=False)
# user = serializers.PrimaryKeyRelatedField(read_only=True)
class Meta:
model = Teacher
fields = ["address","students_in_class"]
# fields = '__all__'
def update(self, instance, validated_data):
instance.address = validated_data['students_in_class']['all']
instance.save()
stu = validated_data.get("students_in_class")
print(stu)
if stu is not None:
print("iam stu")
instance.students_in_class.add(stu)
instance.save()
super(TeacherSerializer,self).update(instance, validated_data)
return instance
Since you are using m2m field, you need list of ids for students_in_class. So the solution will be something like this. (Disclaimer: Code not tested).
class TeacherSerializer(serializers.ModelSerializer):
students_in_class = serializers.ListField(
source="students_in_class.all",
child=serializers.PrimaryKeyRelatedField(queryset=Student.objects.all()),
)
Serialization error will be solved because now you have included students_in_class.all as source. You need to access the data with something like this: validated_data['students_in_class']['all']
If you want to serialize your output in different way, you could set students_in_class as write_only and override serializer representation as needed.:
class TeacherSerializer(serializers.ModelSerializer):
students_in_class = serializers.ListField(
child=serializers.PrimaryKeyRelatedField(queryset=Student.objects.all()),
write_only=True
)
# your code
def to_representation(self, instance):
ret = super().to_representation(instance)
ret['students_in_class'] = StudentSerializer(instance.students_in_class.all(), many=True).data
return ret
The following code worked:
class TeacherSerializer(serializers.ModelSerializer):
students_in_class = serializers.PrimaryKeyRelatedField(
many = True,queryset=Student.objects.all()
)
address = serializers.CharField(required=False)
class Meta:
model = Teacher
fields = ["address","students_in_class"]
def update(self, instance, validated_data):
instance.address = validated_data.get("address")
instance.save()
stu = validated_data.pop("students_in_class")
for stus in stu:
instance.students_in_class.add(stus)
instance.save()
super(TeacherSerializer,self).update(instance, validated_data)
return instance

Add extra value before save serializer

My form sends data to django-rest-framework, but the form contains two fields, and I want to save 5 fields in the database, other fields I calculate on my own (they are not sent by the form). How can I add additional values before saving?
so, form send 'user' and 'comment' values, I want add 'article', 'ip_address' before save to DB
models.py
class Comments(models.Model):
article = models.ForeignKey(Articles, on_delete=models.CASCADE)
user = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
comment = models.TextField(verbose_name=_('Comment'))
submit_date = models.DateTimeField(_('Created'), auto_now_add=True)
ip_address = models.CharField(_('IP address'), max_length=50)
is_public = models.BooleanField(verbose_name=_('Publish'), default=False)
serializers.py
class CommentsSerializer(serializers.ModelSerializer):
user = serializers.ReadOnlyField(source='user.first_name')
class Meta:
model = Comments
fields = ('user', 'comment')
views.py
class AddCommentViewSet(viewsets.ModelViewSet):
queryset = Comments.objects.all()
serializer_class = CommentsSerializer
You have to override create() method:
class CommentsSerializer(serializers.ModelSerializer):
user = serializers.ReadOnlyField(source='user.first_name')
class Meta:
model = Comments
fields = ('user', 'comment')
def create(self, validated_data):
new_comment = models.Comment()
new_comment.user = validated_data['user']
new_comment.comment = validated_data['comment']
new_comment.article = get_your_article_somehow()
new_comment.ip_address = get_your_ip_address_somehow()
new_comment.save()
return new_comment

Django rest nested serializer fails

My model relationships are: A 'Reader' will have a single 'Wishlist' and a 'Wishlist' will contain many 'Book's. I want to create an empty Wishlist automatically during Reader object instance creation.
My Models:
class Wishlist(models.Model):
wishlist_id = models.AutoField(primary_key=True)
class Reader(models.Model):
user = models.OneToOneField(User)
phone = models.CharField(max_length=30)
address = models.CharField(max_length=80)
dob = models.DateField(auto_now=False, auto_now_add=False, null=True, blank=True)
# A library has many readers
which_library = models.ForeignKey('Library', related_name='readers', on_delete=models.CASCADE)
wishlist = models.OneToOneField(Wishlist, null=True, blank=True)
class Book(models.Model):
book_id = models.AutoField(primary_key=True)
title = models.CharField(max_length=30)
which_wishlist = models.ForeignKey('Wishlist', related_name='my_wishlist', on_delete=models.CASCADE, null=True, blank=True)
And serializer:
class ReaderSerializer(serializers.ModelSerializer):
username = serializers.CharField(source='user.username')
email = serializers.CharField(source='user.email')
password = serializers.CharField(source='user.password')
class Meta:
model = Reader
#fields = '__all__'
#depth = 1
fields = ('id', 'username', 'email', 'password', 'phone', 'address', 'dob', 'which_library', 'wishlist')
def update(self, instance, validated_data):
instance.user.email = validated_data.get('user.email', instance.user.email)
instance.user.password = validated_data.get('user.password', instance.user.password)
instance.phone = validated_data.get('phone', instance.phone)
instance.address = validated_data.get('address', instance.address)
instance.dob = validated_data.get('dob', instance.dob)
instance.which_library = validated_data.get('which_library', instance.which_library)
instance.wishlist = validated_data.get('wishlist', instance.wishlist)
instance.save()
return instance
def create(self, validated_data):
user_data = validated_data.pop('user')
user = User.objects.create(**user_data)
user.set_password(user_data['password'])
user.save()
wishlist_data = validated_data.pop('wishlist')
reader = Reader.objects.create(**validated_data)
Wishlist.objects.create(reader=reader, **wishlist_data)
return reader
My view that handles the creation:
#api_view(['GET', 'POST'])
def reader(request, library_id):
"""
List all readers in a specific library, or create a new
"""
if request.method == 'GET':
...
elif request.method == 'POST':
serializer = ReaderSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
else:
return Response(
serializer.errors, status=status.HTTP_400_BAD_REQUEST)
I am making a POST call to create a Reader with following JSON data:
{
"username": "sample",
"email": "sample#gmail.com",
"password": "012345",
"phone": "012345",
"address": "sample address",
"which_library": "2",
"wishlist": []
}
Where it gives me following error:
{
"wishlist": [
"Incorrect type. Expected pk value, received list."
]
}
What am I doing wrong?
I would throw away the Wishlist model completely as it does not hold any data.
class Book(models.Model):
book_id = models.AutoField(primary_key=True)
title = models.CharField(max_length=30)
reader = models.ManyToManyField('Reader', related_name='wishlist')
This should make the whole thing easier as you no longer need to create the wishlist automagically and I guess your problem will desolve in air.
If you need the Model (e.g. you expect data to arise later) you might use through on the ManyToManyField. Details can be found in the docs
UPDATE
I rethought the advise regarding the through model. This approach would not reflect your current state as it would have one entry per relation not per reader. Nevertheless I would recommend the simplification of your models as suggested above.

Trouble POSTing multiple related objects at once to api created with django rest framework

I'm making a ToDo app but having difficulties getting the api to allow a user to create a new list with multiple items via one api call. Each list belongs to a specific "room".
I get 400 Bad Request. If I leave the 'todo_items' off the POST data it works fine to create the ToDoList object.
Also, if I remove "user" from the Meta fields attribute for the CreateToDoItemSerializer, it'll create both the ToDoList object and the ToDoItem objects, but the "content" for each ToDoItem will be an empty string. Inside the create method of NewToDoListSerializer, the validated_data is returning a list of empty OrderedDict() objects for the key "todo_items". I'm not sure what to make of that.
my models:
class Room(models.Model):
name = models.CharField(max_length=255)
description = models.TextField(null=True, blank=True)
slug = models.SlugField(max_length=255, blank=True)
user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="rooms")
class ToDoList(models.Model):
title = models.CharField(max_length=255)
slug = models.SlugField(max_length=255, blank=True)
room = models.ForeignKey(Room, related_name="todo_lists")
user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="todo_lists")
class ToDoItem(models.Model):
todo_list = models.ForeignKey(ToDoList, related_name="todo_items")
user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="replies")
content = FroalaField(options={'placeholder': '''Just start writing...
Highlight any text to bring up the editor.'''})
my serializers
class CreateTodoItemSerializer(serializers.ModelSerializer):
user = serializers.PrimaryKeyRelatedField(many=False, read_only=False, queryset=User.objects.all())
class Meta:
model = ToDoItem
fields = ['pk', 'user', 'content']
def create(self, validated_data):
reply = ToDo.objects.create(**validated_data)
class NewToDoListSerializer(serializers.ModelSerializer):
room = serializers.PrimaryKeyRelatedField(many=False, read_only=False, queryset=Room.objects.all())
user = UserSerializer(read_only=True)
todo_items = CreateTodoItemSerializer(many=True, read_only=False)
class Meta:
model = ToDoList
fields = ['pk', 'slug', 'title', 'user', 'room', 'todo_items']
lookup_field = "slug"
depth = 1
def create(self, validated_data):
todo_items_data = validated_data.pop('todo_items')
todo_list = ToDoList.objects.create(**validated_data)
for todo_item_data in todo_items_data:
todo_item = ToDo.objects.create(user=todo_list.user, todo_list=todo_list, **todo_item_data)
my viewset (the relevant bits):
class ToDoListViewSet(viewsets.ModelViewSet):
queryset = ToDoList.objects.all()
serializer_class = ToDoListSerializer
authentication_classes = [TokenAuthentication, SessionAuthentication]
permission_classes = [IsAuthenticated]
renderer_classes = (renderers.TemplateHTMLRenderer, renderers.JSONRenderer, renderers.BrowsableAPIRenderer)
template_name = "react_base.html"
lookup_field = "slug"
def create(self, request, **kwargs):
self.serializer_class = NewToDoListSerializer
return super(ToDoListViewSet, self).create(request, **kwargs)
def perform_create(self, serializer):
instance = serializer.save(user=self.request.user)
the data I'm POSTing:
todoListTitle, todoItemContent, moreTodoItemContent are all strings. this.props.room.pk is an integer. this.props.csrfmiddlewaretoken is the csrfmiddlewaretoken
var newToDoListData = {
"room": this.props.room.pk,
"title": todoListTitle,
"todo_items": [{"content": todoItemContent}, {"content": moreTodoItemContent}],
"csrfmiddlewaretoken": this.props.csrfmiddlewaretoken
};
You need to make the todo_items in your serializer required = false. I am not sure I understand what your second issue is.
class NewToDoListSerializer(serializers.ModelSerializer):
room = serializers.PrimaryKeyRelatedField(many=False, read_only=False, queryset=Room.objects.all())
user = UserSerializer(read_only=True)
todo_items = CreateTodoItemSerializer(many=True, required=False)
class Meta:
model = ToDoList
fields = ['pk', 'slug', 'title', 'user', 'room', 'todo_items']
lookup_field = "slug"
depth = 1
def create(self, validated_data):
todo_items_data = validated_data.pop('todo_items')
todo_list = ToDoList.objects.create(**validated_data)
for todo_item_data in todo_items_data:
todo_item = ToDo.objects.create(user=todo_list.user, todo_list=todo_list, **todo_item_data)

DjangoRestFramework - how to serialize a specific ForiegnKey field rather than PK value

This is my model:
class Post(models.Model):
owner = models.ForeignKey(User, related_name="%(app_label)s%(class)s_set")
usersVoted = models.ManyToManyField(User, blank=True)
post = models.CharField(max_length=400)
and this is my serializer:
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = ('owner', 'usersVoted', 'post')
read_only_fields = ('owner', 'usersVoted')
def create(self, validated_data):
drop = Drop(
owner = User.objects.get(username='TestUser'),
post = validated_data['post'],
)
Owner has a ForeignKey to the default Django User model. Currently when I serialize a post, owner is the pk value of the user. How do I make owner the username of the user instead?
You can do like:
class PostSerializer(serializers.ModelSerializer):
owner = serializers.ReadOnlyField(source="owner.username")
class Meta:
model = Post
fields = ('owner', 'usersVoted', 'post')
read_only_fields = ('owner', 'usersVoted')
def create(self, validated_data):
drop = Drop(
owner = User.objects.get(username='TestUser'),
post = validated_data['post'],
)
You should use the SlugRelatedField provided that the username is unique. This will make the fields read/write which is very handy.
Documentation is available at http://www.django-rest-framework.org/api-guide/relations/#slugrelatedfield