check if object in ManyToMany field django rest framework - python

here is my models.py:
class Post(models.Model):
id = models.AutoField(primary_key=True)
body = models.TextField(max_length=10000)
date = models.DateTimeField(auto_now_add=True, blank=True)
user = models.ForeignKey(User, on_delete=models.CASCADE)
liked_by = models.ManyToManyField(User, blank=True, related_name='liked_by')
class Meta:
ordering = ['-date']
serializers.py:
class PostSerializer(serializers.ModelSerializer):
user = UserSerializers()
total_likes = serializers.SerializerMethodField()
total_comments = serializers.SerializerMethodField()
class Meta:
model = Post
fields = ('id','body','date','user','total_likes','total_comments')
def get_total_likes(self, instance):
return instance.liked_by.count()
def get_total_comments(self, instance):
return instance.comment_set.count()
Q1: how do i check if a user exists in ManyToManyField of a single post?
Q2: shouldn't i use ManyToManyField in drf? then which would be better?

I don't have enough reps to comment, but if you have a post instance and a user instance, you could do something like:
post.liked_by.filter(id=user.id).exists()
Does that help you or are you asking where you should be implementing this? e.g. in your view or serializer etc...

Related

Django REST Framework, Serializers: Additional data?

Good day,
I would like to ask, if there's a possibility to gain additional data inside my serializers?
These are my models...
models.py
class Chair(models.Model):
name = models.CharField(max_length=100, null=False, blank=False, unique=True)
bookable = models.BooleanField(default=False)
user_created = models.CharField(max_length=100)
date_created = models.DateField(auto_now_add=True)
class Booking(models.Model):
chair = models.ForeignKey(Chair, on_delete=models.CASCADE)
day = models.DateField()
user_name = models.CharField(max_length=100)
user_created = models.CharField(max_length=100)
date_created = models.DateField(auto_now_add=True)
and these my serializers...
serializers.py
class BookingSerializer(serializers.ModelSerializer):
class Meta:
model = Booking
fields = '__all__'
class ChairSerializer(serializers.ModelSerializer):
class Meta:
model = Chair
fields = '__all__'
When making a request inside js like this...
views.py
#api_view(['GET'])
def bookings_by_date(request, pk):
bookings = Booking.objects.filter(day=pk)
serializer = BookingSerializer(bookings, many=True)
return Response(serializer.data)
script.js
let url = '...here's my url for Booking...';
fetch(url)
.then((resp) => resp.json())
.then(function(data) {
// do something here
});
...I would like to get not only the id of the Chair (models.Foreignkey), but also it's name. My first thought was doing something like this...
class ChairSerializer(serializers.ModelSerializer):
class Meta:
model = Chair
fields = [
...
'chair',
'chair__name',
...
]
...but this doesn't seem to work! Does anyone know a solution for my problem? Thanks for all your help and have a great weekend!
You can use one of this two ways:
1-) Using SerializerMethodField. You can add readonly fields with this way. You should add get_<field_name> method or give a method name that you want to run for this field with name keyword. You can look the document for more details.
class BookingSerializer(serializers.ModelSerializer):
chair__name = serializers.SerializerMethodField()
class Meta:
model = Booking
fields = '__all__'
def get_chair_name(self, obj):
return obj.chair.name
2-) Using CharField with source attribute:
You can define basically this field fill from where.
class BookingSerializer(serializers.ModelSerializer):
chair__name = serializers.CharField(source='chair__name')
class Meta:
model = Booking
fields = '__all__'

Getting AttribueError by SlugRelatedField despite the object being saved

I am creating an API to save class teachers. Now all the fields in the ClassTeacher model are foreign fields so I am using a SlugRelatedField in the serializer. It looks like SlugRelatedField does not support attribute lookup like this "user__username" and raises attribute error HOWEVER the object is still being saved.
models.py
class ClassTeacher(models.Model):
teacher = models.ForeignKey(Teacher, on_delete=models.CASCADE)
class_name = models.ForeignKey(Classes, on_delete=models.CASCADE)
school_id = models.ForeignKey(School, on_delete=models.CASCADE)
serializers.py
class ClassTeacherSerializer(ModelSerializer):
teacher = SlugRelatedField(slug_field='user__username', queryset=Teacher.objects.all()) <---- this is causing the error
class_name = SlugRelatedField(slug_field='class_name', queryset=Classes.objects.all())
school_id = SlugRelatedField(slug_field='school_id__username', queryset=School.objects.all()) <---- and I am assuming that this will too
class Meta:
model = ClassTeacher
fields = '__all__'
I tried adding a #property in the Teacher model to retrieve the username and use the property in the slug_field but that did not work too.
How can I save the object without getting the error?
EDIT 1:
teachers/models.py
class Teacher(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
name = models.CharField(max_length=30)
photo = models.URLField()
teacher/serializers.py
class TeacherSerializer(ModelSerializer):
class Meta:
model = Teacher
fields = '__all__'
school/models.py
class School(models.Model):
school_id = models.OneToOneField(User, on_delete=models.CASCADE)
principal = models.CharField(max_length=50)
name = models.CharField(max_length=50)
photo = models.URLField()
school/serializers.py
class SchoolSerializer(ModelSerializer):
class Meta:
model = School
fields = '__all__'
EDIT 2:
Here's how I used the #property by referring from here:
teacher/models.py
class Teacher(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
name = models.CharField(max_length=30)
photo = models.URLField()
#Here's the extra property part
#property
def username(self):
return self.user.username
classteacher/serializers.py
class ClassTeacherSerializer(ModelSerializer):
#Here I changed user__username to just username as mentioned in the above link
teacher = SlugRelatedField(slug_field='username', queryset=Teacher.objects.all())
class_name = SlugRelatedField(slug_field='class_name', queryset=Classes.objects.all())
school_id = SlugRelatedField(slug_field='school_id__username', queryset=School.objects.all())
class Meta:
model = ClassTeacher
fields = '__all__'
try renaming serializer field from teacher to user and using slug_field='username'
You can use #property for example
class User(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
name = models.CharField(max_length=30)
photo = models.URLField()
#property
def get_username(self):
return Teacher.objects.filter(user_id=self.id)
and than inside your ClassTeacherSerializer use slug_field='username'
let me know if it works.

Django Rest Framework: 'RelatedManager' object has no attribute 'body' error when using nested serializer

With Django DRF, I am trying to display the comments for a particular post in a blog using a nested serializer.
I have run into the following error:
'RelatedManager' object has no attribute 'body'
Here is my code:
comment model:
class Comment(models.Model):
#adapted from https://blog.logrocket.com/use-django-rest-framework-to-build-a-blog/
created = models.DateTimeField(auto_now_add=True)
body = models.TextField(blank=False)
user_id = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='comments', on_delete=models.CASCADE)
post = models.ForeignKey('Posts', related_name='comments', on_delete=models.CASCADE)
#property
def time_left(self):
return self.post.time_left
Post model:
class Posts(models.Model):
title = models.CharField(max_length=100)
topic = MultiSelectField(choices=TOPIC_CHOICES)
creation_timestamp = models.DateTimeField(auto_now_add=True)
expiration_timestamp = models.DateTimeField(default=expiration)
body = models.CharField(max_length=255)
user_id = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) #related_name='posts'
likes = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name="likes",blank=True)
dislikes = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name="dislikes",blank=True)
#property
def is_expired(self):
#taken from https://stackoverflow.com/questions/41505243/how-to-automatically-change-model-fields-in-django
if now() > self.expiration_timestamp:
return True
return False
#property
def time_left(self):
return self.expiration_timestamp - now()
serializers.py:
class CommentSerializer(serializers.ModelSerializer):
time_left = serializers.ReadOnlyField()
class Meta:
model = Comment
fields = ('created', 'body', 'user_id', 'post','time_left')
class PostsSerializer(serializers.ModelSerializer):
is_expired = serializers.ReadOnlyField()
time_left = serializers.ReadOnlyField()
comments = CommentSerializer(source='comments.body',) ########## THIS IS THE PROBLEMATIC LINE #######
class Meta:
#make sure that the relevant fields are read only
model = Posts
fields = ('comments','title','topic','creation_timestamp','expiration_timestamp','body','user_id','likes','dislikes','is_expired','time_left')
I believe the problematic line is the following one from serializers.py:
comments = CommentSerializer(source='comments.body',)
I think you are confusing the different keywords that a serializer can receive. Here I leave you an answer so that you understand it perfectly.
Your comments variable should look like this.
comments = CommentSerializer(many=True)

count total object in foreign key - drf

i want to count comments for every single Post
in models.py:
class Post(models.Model):
body = models.TextField(max_length=10000)
date = models.DateTimeField(auto_now_add=True, blank=True)
user = models.ForeignKey(User, on_delete=models.CASCADE)
liked_by = models.ManyToManyField(User, blank=True, related_name='liked_by')
class Meta:
ordering = ['-date']
class Comment(models.Model):
body = models.TextField(max_length=1000)
date = models.DateTimeField(auto_now_add=True, blank=True)
user = models.ForeignKey(User, on_delete=models.CASCADE)
post = models.ForeignKey(Post, on_delete=models.CASCADE)
class Meta:
ordering = ['-date']
in serializers.py:
class CommentSerializer(serializers.ModelSerializer):
class Meta:
model = Comment
fields = '__all__'
class PostSerializer(serializers.ModelSerializer):
#comments = CommentSerializer()
user = UserSerializers()
total_likes = serializers.SerializerMethodField()
liked_by = SimpleUserSerializer(many=True, read_only=True)
total_comments = serializers.SerializerMethodField()
class Meta:
model = Post
fields = ('body','date','user', 'total_likes', 'liked_by','total_comments')
def get_total_likes(self, instance):
return instance.liked_by.count()
def get_total_comments(self, instance):
return instance.comments.count()
when i run this code, it shows, AttributeError: 'Post' object has no attribute 'comments'.
how do i count comments of a post?
Since you haven't configured the related_name, Django uses the default related_name and hence you should access the reveres FK using comment_set instead of comments
Thus, the get_total_comments(...) method should look like
def get_total_comments(self, instance):
return instance.comment_set.count()
Reference
What is related_name used for in Django?

Object of type 'ListSerializer' is not JSON serializable

I want to get all customer data and responses and also remarks.
This is model.py
class Customer(models.Model):
name = models.CharField(max_length=200)
email_address = models.CharField(max_length=200)
phone_number = models.CharField(max_length=20)
age = models.SmallIntegerField(default=14)
remarks = models.ManyToManyField(Remark,null=True,blank=True)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return str(self.id)
class Response(models.Model):
question = models.ForeignKey(Question)
customer = models.ForeignKey(Customer)
response_text = models.CharField(max_length=100, null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
uuid = models.UUIDField()
def __str__(self):
return str(self.id)
This is serializers.py
class ResponseSerializer(ModelSerializer):
class Meta:
model = Response
fields = '__all__'
class RemarksSerializer(ModelSerializer):
class Meta:
model = Remark
fields = '__all__'
class CustomerInformationSerializer(ModelSerializer):
remarks = RemarksSerializer(many=True)
responses = serializers.SerializerMethodField()
def get_responses(self, obj):
responses = Response.objects.filter(customer=obj)
return ResponseSerializer(responses, many=True)
class Meta:
model = Customer
fields = ('name', 'email_address', 'phone_number', 'age', 'remarks', 'responses')
This is services.py
def customer_information(company_id=1):
cus = Customer.objects.filter(remarks__company_id=company_id)
return CustomerInformationSerializer(cus, many=True).data
This is views.py
class CustomerInformationView(APIView):
def get(self, request):
company_id = request.GET.get('company_id', 1)
resp = {'data': customer_information(company_id)}
return Response(data=resp, status=status.HTTP_200_OK)
This is url.py
url(r'^customer/$', CustomerInformationView.as_view()),
I'm having this problem. How can I solve this. Kindly guide me.
get function in your view should return responses.data, insted of responsed.
SIDE NOTE
First, let me point you to a resource that I think is GREAT for anything dealing with Django REST Framework:
Classy Django REST Framework. It is a fantastic resource because you can easily dig right into the source code to see how you may or may not need to override default operations.
MY ANSWER
What I suggest is that instead of using the APIView, you use ListAPIView.
It would look something like this:
from rest_framework.generics import ListAPIView
class Customer(models.Model):
name = models.CharField(max_length=200)
email_address = models.CharField(max_length=200)
phone_number = models.CharField(max_length=20)
age = models.SmallIntegerField(default=14)
remarks = models.ManyToManyField(Remark,null=True,blank=True)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return str(self.id)
class Response(models.Model):
question = models.ForeignKey(Question)
customer = models.ForeignKey(Customer, related_name='responses')
response_text = models.CharField(max_length=100, null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
uuid = models.UUIDField()
def __str__(self):
return str(self.id)
class ResponseSerializer(ModelSerializer):
class Meta:
model = Response
fields = '__all__'
class RemarksSerializer(ModelSerializer):
class Meta:
model = Remark
fields = '__all__'
class CustomerInformationSerializer(ModelSerializer):
remarks = RemarksSerializer(many=True)
responses = ResponseSerializer(many=True)
class Meta:
model = Customer
fields = ('name', 'email_address', 'phone_number', 'age', 'remarks', 'responses')
class CustomerInformationView(ListAPIView):
queryset = Customer.objects.all()
serializer_class = CustomerInformationSerializer
lookup_field = 'remarks__company'
Note the change that I made by adding related_name to the customer field on your Response model. See Django documentation for more information on related_name. In short, it adds responses as a field name on your Customer model so that you can travel backwards through that relationship.
This is not tested, but this should be a better strategy to do what you want without having to have a get_responses method, or a services.py.
Some there might be error because of missing "/" at the end of path like "event-api"=incorrect and "event-api/" correct. That worked for me. Hope you also have same problem.
Incorrect: path('event-api',views.event_view,name="event-view")
Correct: path('event-api/',views.event_view,name="event-view")

Categories