DRF - Filter queryset in PrimaryKeyRelatedField - python

I've the following models:
class ModelX(models.Model):
STATUS_CHOICES = (
(0, 'ABC'),
(1, 'DEF'),
)
status = models.IntegerField(choices=STATUS_CHOICES)
user = models.ForeignKey(Users)
class Users(models.Model):
phone_number = models.Charfield()
and the serializer for ModelX is :
class ModelXSerializer(serializers.ModelSerializer):
phone_number = serializers.PrimaryKeyRelatedField(
source='user', queryset=Users.objects.get(phone_number=phone_number))
class Meta:
model = ModelX
fields = ('phone_number',)
In the request for creating the ModelX record, I get phone_number instead of the user_id. Now, I've to fire a filter query in order get the user instance. How do I do that, ie Users.objects.get(phone_number=phone_number)
Also, when creating a record, the status field will always be 0. The client wont post the status parameter in the body. Its the internal logic. Is there a way in serializers that can set the status field to 0 automatically. Please dont recommend to set this field as default=0 in models. There's some logic behind this. This is just a shorter version of the problem statement.

you can try such version of your serializer, with custom create method:
class ModelXSerializer(serializers.ModelSerializer):
phone_number = serializers.CharField(source='user.phone_number')
class Meta:
model = ModelX
fields = ('phone_number',)
def create(self, validated_data):
phone_number = validated_data['phone_number']
user = Users.objects.get(phone_number=phone_number)
instance = ModelX.objects.create(status=0, user=user)
return instance
details about the source argument.

You can add it in validate function:
class ModelXSerializer(serializers.ModelSerializer):
def validate(self, attrs):
attrs = super(ModelXSerializer, self).validate(attrs)
attrs['status'] = 0
return attrs

Related

How to get model data to appear as a field in another model's response

These are simplified versions of my models (the user model is just an id and name)
class Convo(models.Model):
owner = models.ForeignKey(User, on_delete=models.CASCADE, related_name='convo_owner')
users = models.ManyToManyField(User, through='Convo_user')
class Convo_user (models.Model):
user = models.ForeignKey(UserProfile, on_delete=models.CASCADE)
convo = models.ForeignKey(Convo, on_delete=models.CASCADE)
class Comments(models.Model):
name = models.CharField(max_length=255)
content = models.TextField(max_length=1024)
convo = models.ForeignKey(Convo, on_delete=models.CASCADE)
This is my view
class ConvoViewSet(viewsets.ModelViewSet):
serializer_class = serializers.ConvoSerializer
def get_queryset(self):
return None
def list(self, request):
curr_user = request.user.id
# Collecting the list of conversations
conversations = models.Conversation.object.filter(ConvoUser__user_id=request.user.id)
#Getting list of conversation id's
conv_ids = list(conversations.values_list('id', flat=True).order_by('id'))
#Getting list of relevant comments
comments = models.Comments.objects.filter(conversation_id__in=conv_ids)
return Response(self.get_serializer(conversations, many=True).data)
And my current serializer
class ConvoSerializer(serializers.ModelSerializer):
"""A serializer for messaging objects"""
# access = AccessSerializer(many=True)
# model = models.Comments
# fields = ('id', 'name', 'content', 'convo_id')
class Meta:
model = models.Convo
fields = ('id', 'owner_id')
The current response I get is of the form
[
{
"id": 1,
"owner_id": 32
}, ...
]
But I would like to add a comments field that shows all the properties of comments into the response, so basically everything in the second queryset (called comments) and I'm not sure how to go about this at all. (I retrieve the comments in the way I do because I'm trying to minimize the calls to the database). Would I need to create a new view for comments, make its own serializer and then somehow combine them into the serializer for the convo?
The way you've set up your models, you can access the comments of each Convo through Django's ORM by using convo_object.comments_set.all(), so you could set up your ConvoSerializer to access that instance's comments, like this:
class ConvoSerializer(serializers.ModelSerializer):
"""A serializer for messaging objects"""
comments_set = CommentSerializer(many=True)
class Meta:
model = models.Convo
fields = ('id', 'owner_id', 'comments_set')
and then you define your CommentSerializer like:
class CommentSerializer(serializers.ModelSerializer):
class Meta:
model = models.Comments
fields = ('id', 'name', 'content')
No data appears because my serializers are using the default database, not sure why but a step forward
EDIT:
Django: Database used for prefetch_related is not the same that the parent query Provided me the correct answer, I was able to choose the database with this method because for some reason inner queries use the default DB

Django Rest Framework: Saving ForeignKey inside OneToOne model

I have 2 models that are OneToOne related and model that is FK to 2nd model
models.py
class Legal(TimeStampedModel):
name = models.CharField('Name', max_length=255, blank=True)
class LegalCard(TimeStampedModel):
legal = models.OneToOneField('Legal', related_name='legal_card', on_delete=models.CASCADE)
branch = models.ForeignKey('Branch', related_name='branch', null=True)
post_address = models.CharField('Post address', max_length=255, blank=True)
class Branch(TimeStampedModel):
name = models.CharField('Name',max_length=511)
code = models.CharField('Code', max_length=6)
Using DRF I made them to behave as single model so I can create or update both:
serializer.py
class LegalSerializer(serializers.ModelSerializer):
branch = serializers.IntegerField(source='legal_card.branch', allow_null=True, required=False)
post_address = serializers.CharField(source='legal_card.post_address', allow_blank=True, required=False)
class Meta:
model = Legal
fields = ('id',
'name',
'branch',
'post_address',
)
depth = 2
def create(self, validated_data):
legal_card_data = validated_data.pop('legal_card', None)
legal = super(LegalSerializer, self).create(validated_data)
self.update_or_create_legal_card(legal, legal_card_data)
return legal
def update(self, instance, validated_data):
legal_card_data = validated_data.pop('legal_card', None)
self.update_or_create_legal_card(instance, legal_card_data)
return super(LegalSerializer, self).update(instance, validated_data)
def update_or_create_legal_card(self, legal, legal_card_data):
LegalCard.objects.update_or_create(legal=legal, defaults=legal_card_data)
views.py
class LegalDetailView(generics.RetrieveUpdateDestroyAPIView):
queryset = Legal.objects.all()
serializer_class = LegalSerializer
I'm trying to save this by sending FK as integer (I just want to post id of the branch), but I receive error
ValueError: Cannot assign "2": "LegalCard.branch" must be a "Branch" instance.
Is there any way to pass over only ID of the branch?
Thank you
In Django, if you only need the FK value, you can use the FK value that is already on the object you've got rather than getting the related object.
Assume you have a Legal and Branch object with id's as 1. Then you can save a LegalCard object by:
LegalCard(legal_id=1,branch_id=1,post_address="Istanbul Street No:1")
Just use legal_card.branch_id instead of legal_card.branch to get just an id, not a related object.
And depth = 1

Django Rest Framework Custom Serializer Method

I'm trying to create a custom serializer method that counts the number of passed and failed quizzes from my QuizResults model. A failed quiz is under .7 and a passed quiz is .7 or over.
I want to be able to look into the Users QuizResult and count the number of passed quizzes(.7 or over). I would then duplicate the method to count the failed quizzes (under .7).
So far I don't have much idea on how to do so. I want to be able to grab the percent_correct field of the model and do a calculation and add it to a field in the serializer called "quiz_passed".
Here is my QuizResult model:
class QuizResult(models.Model):
quiz = models.ForeignKey(Quiz)
user = models.ForeignKey(User, related_name='quiz_parent')
percent_correct = models.FloatField(validators=[MinValueValidator(0.0), MaxValueValidator(1.0)])
date = models.DateTimeField(auto_now_add=True)
def __str__(self):
return 'Quiz Results for : ' + self.quiz.title
Here is my serializer:
class ProfileSerializer(serializers.HyperlinkedModelSerializer):
todo_count = serializers.IntegerField(source='todo_parent.count', read_only=True)
discussion_count = serializers.IntegerField(source='comment_parent.count', read_only=True)
quiz_passed = serializers.SerializerMethodField()
class Meta:
model = User
fields = ('todo_count', 'discussion_count', 'quiz_passed', 'username', )
def get_quiz_passed(self, obj):
return passed
Any help is appreciated.
Edit:
I extended the User model and added a model method like you suggested.
class Profile(User):
def get_quizzes_passed_count(self):
return self.quiz_parent.filter(percent_correct__gte=0.8).count()
I then added your suggestion to my ProfileSerializer.
class ProfileSerializer(serializers.HyperlinkedModelSerializer):
todo_count = serializers.IntegerField(source='todo_parent.count', read_only=True)
discussion_count = serializers.IntegerField(source='comment_parent.count', read_only=True)
num_quizzes_passed = serializers.ReadOnlyField(source="get_quizzes_passed_count")
class Meta:
model = Profile
fields = ('todo_count', 'discussion_count', 'num_quizzes_passed', 'username')
Unfortunately when I add this nothing appears in the framework once these have been added. Any suggestions? Thank you.
You can use a model method on the user model to count that user's number of passed quizzes:
class User(models.model):
# rest of your User attributes
def get_quizzes_passed_count(self):
return self.quiz_parent.filter(percent_correct__gte=0.7).count()
Then add that to your serializer using a DRF ReadOnlyField to serialize that method:
class ProfileSerializer(serializers.HyperlinkedModelSerializer):
todo_count = serializers.IntegerField(
source='todo_parent.count', read_only=True
)
discussion_count = serializers.IntegerField(
source='comment_parent.count', read_only=True
)
quiz_passed = serializers.SerializerMethodField()
num_quizzes_passed = serializers.ReadOnlyField(source="get_quizzes_passed_count")
class Meta:
model = User
fields = ('todo_count', 'discussion_count', 'quiz_passed', 'username', )
def get_quiz_passed(self, obj):
return passed
You can duplicate this for the number of failed quizzes.

unique_together of two field messes with read_only_fields

I have this code for rating lessons, user and lesson should be added automatically from request authorization and URL:
#views.py
class RatingViewSet(
mixins.ListModelMixin,
mixins.CreateModelMixin,
viewsets.GenericViewSet
):
permission_classes = [permissions.IsAuthenticated]
serializer_class = RatingSerializer
def perform_create(self, serializer):
lessonInstance = Lesson.objects.get(id = self.kwargs['lessonID'])
serializer.save(user=self.request.user, lesson = lessonInstance)
def get_queryset(self):
lessonID = self.kwargs['lessonID']
return Rating.objects.filter(user=self.request.user, lesson=lessonID)
#serializers.py
class RatingSerializer(serializers.ModelSerializer):
class Meta:
model = Rating
fields = ('id', 'lesson','user', 'difficulty')
read_only_fields = ('id', 'user','lesson')
#models.py
class Rating(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL)
lesson = models.ForeignKey('lessons.Lesson')
difficulty = models.IntegerField()
class meta:
unique_together('user','lesson')
I want to have max 1 rating per user/lesson, hence unique_together('user','lesson'). But there is a problem: as long as that constraint is in the code, requests without user or lesson fields get denied with field required error, even though they are read_only.
(If I migrate with unique_together('user','lesson'), then delete that line it works, but as soon as it's there I get errors.)
I want to keep that bit of code there so I don't accidentally remove the unique_together constraint on later migrations.
This is a special-case that requires a different approach. Here's what django-rest-framework documentation (see the Note) says about this case:
The right way to deal with this is to specify the field explicitly on
the serializer, providing both the read_only=True and default=…
keyword arguments.
In your case, you need to explicitly define the user and lesson fields on your RatingSerializer, like this:
class RatingSerializer(serializers.ModelSerializer):
user = serializers.PrimaryKeyRelatedField(read_only=True, default=serializers.CurrentUserDefault()) # gets the user from request
lesson = serializers.PrimaryKeyRelatedField(read_only=True, default=None) # or any other appropriate value
class Meta:
model = Rating
fields = ('id', 'lesson','user', 'difficulty')
Good luck!
If a field is read_only=True then the validated_data will ignore data of it => Cause error required field, read more at doc
I also met this issue in a similar context, then tried #iulian's answer above but with no luck!
This combo read_only + default behavior is not supported anymore, check this
I resolved this issue by 2 solutions:
My model:
class Friendship(TimeStampedModel):
"""Model present Friendship request"""
from_user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='friendship_from_user')
to_user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='friendship_to_user')
class Meta:
unique_together = ('from_user', 'to_user')
Solution 1. Write your own CurrentUserDefault class to get the user id then set to default attribute data of serializer(Ref from #51940976)
class CurrentUserDefault(object):
def set_context(self, serializer_field):
self.user_id = serializer_field.context['request'].user.id
def __call__(self):
return self.user_id
class FriendshipSerializer(serializers.ModelSerializer):
from_user_id = serializers.HiddenField(default=CurrentUserDefault())
class Meta:
model = Friendship
fields = ('id', 'from_user', 'from_user_id', 'to_user', 'status')
extra_kwargs = {
'from_user': {'read_only': True},
}
Solution 2. Override the create method of serializer to set data for user id(Ref from this)
class FriendshipSerializer(serializers.ModelSerializer):
class Meta:
model = Friendship
fields = ('id', 'from_user', 'to_user', 'status')
extra_kwargs = {
'from_user': {'read_only': True},
}
def create(self, validated_data):
"""Override create to provide a user via request.user by default.
This is require since the read_only `user` filed is not included by default anymore since
https://github.com/encode/django-rest-framework/pull/5886.
"""
if 'user' not in validated_data:
validated_data['from_user'] = self.context['request'].user
return super(FriendshipSerializer, self).create(validated_data)
I hope this helps!

Post to Django rest framework API but always get a 'This field is required' error

I'm using Django rest framework 3.2.1, the GET is perfect, but POST does not work.
This is the Model:
class Sample(models.Model):
ownerBuilding = models.ForeignKey(Building)
coordinateX = models.IntegerField(default=0, help_text='X coordinate for this sampling point located in a building')
coordinateY = models.IntegerField(default=0, help_text='Y coordinate for this sampling point located in a building')
creation_Time = models.DateTimeField(auto_now_add=True)
description = models.TextField(null=True,
help_text='additional description for this sample point.')
class Meta:
unique_together = ('ownerBuilding', 'coordinateX','coordinateY')
def __str__(self):
return "Sample for building " + str(self.ownerBuilding)
The Serializer:
class SampleSerializer(serializers.HyperlinkedModelSerializer):
ownerBuilding = serializers.HyperlinkedRelatedField(many=False, read_only=True, view_name='building-detail')
class Meta:
model = Sample
fields = ('url', 'ownerBuilding', 'coordinateX', 'coordinateY', 'creation_Time','description')
The View:
class SampleList(generics.ListCreateAPIView):
queryset = Sample.objects.all()
serializer_class = SampleSerializer
permission_classes = (permissions.IsAuthenticated, IsTechniciansGroupOrReadOnly,)
def get_queryset(self):
queryset = Sample.objects.all()
ownerBuildingId = self.request.query_params.get('ownerBuildingId', None)
if ownerBuildingId is not None:
queryset = queryset.filter(ownerBuilding=ownerBuildingId)
return queryset
When I test the POST to this API with data:
{"ownerBuilding":"http://rest.xxxxxxxx.xyz:8090/buildings/1/","coordinateX":33,"coordinateY":44,"description":"5dfadfasdfsadf5"}
I always get this error:
{
"ownerBuilding": [
"This field is required."
]
}
anyone could help?
the http://rest.xxxxxxxx.xyz:8090/buildings/1/ exists.
[edit0]:
if I POST with:
{"coordinateX":33,"coordinateY":44,"description":"5dfadfasdfsadf5"}
I still get the same result.
Serializer fields are required by default.
Also, DRF ModelSerializer (and HyperlinkedModelSerializer) adds UniqueTogetherValidators for all model's unique_together constraints. This implicitly makes all fields in the constraint required, with exception for fields for which defaults are set. See doc.
ownerBuilding is read only on the serializer:
ownerBuilding = serializers.HyperlinkedRelatedField(many=False, \
read_only=True, view_name='building-detail')
but you don't provide a defualt nor set the value manually, so this field is treated as empty, hence the error message.
Either remove the read_only or set a default.

Categories

Resources