I'm new to Django Rest Framework. I'm trying to get my ListAPI to show various fields of my Quiz (and related) models. It's working fine, except for my attempt_number field. I'm getting the right queryset, but I'm not sure how to get only the relevant value for every query. Users can take every quiz as many times as they want, and I want to show the queryset for each attempt, since the score etc. will be different.
My model setup is as follows:
class Quiz(models.Model):
title = models.CharField(max_length=15)
slug = models.SlugField(blank=True)
questions_count = models.IntegerField(default=0)
class Question(models.Model):
quiz = models.ForeignKey(Quiz, on_delete=models.CASCADE)
label = models.CharField(max_length=1000)
class Choice(models.Model):
question = models.ForeignKey(Question, on_delete=models.CASCADE)
answer = models.CharField(max_length=100)
is_correct = models.BooleanField('Correct answer', default=False)
class QuizTaker(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
quiz = models.ForeignKey(Quiz, on_delete=models.CASCADE)
correct_answers = models.IntegerField(default=0)
completed = models.BooleanField(default=False)
attempt_number = models.PositiveIntegerField(default=0)
My serializer for the ListAPI looks as follows:
class MyQuizListSerializer(serializers.ModelSerializer):
attempt = serializers.SerializerMethodField()
# etc..
class Meta:
model = Quiz
fields = "__all__"
def get_attempt(self, obj):
try:
quiztaker = QuizTaker.objects.filter(user=self.context['request'].user,
quiz=obj)
for attempt in quiztaker:
attempt_number = attempt.attempt_number
return attempt_number
If I do it like this, I always get the last value for attempt_number (because the loop overwrites the value). So then I tried to append it to a list instead, like this:
a = []
for attempt in quiztaker:
attempt_number = attempt.attempt_number
a.append(attempt_number)
return a
But then I get the list of attempts for every query, instead of the attempt number for each query. I.e. I get the following three times (because in this case there are three attempts):
{
"id": 4,
"attempt": [
1,
2,
3
]
},
But instead what I want is (and the same for attempt 2 and 3 etc.):
{
"id": 4,
"attempt": 1
},
So I tried doing it like this:
return a[attempt_number-1]
Hoping it would give me index zero for attempt number 1, 1 for 2, etc. But then I still just get the last attempt number (3 in this case). How can I solve this?
I also tried just using an IntegerField instead of a SerializerMethodField as follows:
attempt = serializers.IntegerField(read_only=True, source='quiztaker.attempt_number')
But it returned nothing.
If I correctly understood you, you want the list of attempts added to each quiz object.
{
"id": 4,
"attempts": [{
"id": 1,
"attempt_number": 1,
},
{
"id": 2,
"attempt_number": 2,
}...]
}
In that case, you should have a separate serializer for the QuizTaker model and serialize the objects in the SerializerMethodField.
class QuizTakerSerializer(serializers.ModelSerializer):
class Meta:
model = QuizTaker
fields = ('id', 'attempt_number')
class MyQuizListSerializer(serializers.ModelSerializer):
attempts = serializers.SerializerMethodField()
# etc..
class Meta:
model = Quiz
fields = "__all__"
def get_attempts(self, obj):
quiztakers = QuizTaker.objects.filter(user=self.context['request'].user,quiz=obj)
return QuizTakerSerializer(quiztakers, many=True).data
Honestly, your question is not very clear and it would help to edit it and make it clearer, giving the JSON structure you want to achieve. I also suspect your intended use of queryset isn't the actual Django meaning for a container of ORM objects.
Related
I have the tables: User, Event and EventTicket. I have to return total transactions and total tickets sold of each event. I am trying to achieve this using Django serializers. Using only serializers, I need to find the following:
1. total tickets sold: count of items in EventTicket table for each event
2. total transactions: sum of total_amount of items in EventTicket table for each event where payment_status: 1
models.py:
class User(AbstractUser):
first_name = models.CharField(max_length=50,blank=False)
middle_name = models.CharField(max_length=50,blank=True)
last_name = models.CharField(max_length=50,blank=True)
class Event(models.Model):
name = models.CharField(max_length=100, null=False, blank=False)
user = models.ForeignKey(User, related_name='%(class)s_owner', on_delete=models.CASCADE)
description = models.TextField(null=False, blank=False)
date_time = models.DateTimeField()
class EventTicket(models.Model):
user = models.ForeignKey(User,on_delete=models.CASCADE)
event = models.ForeignKey(Event,on_delete=models.CASCADE, related_name='event_tickets')
payment_status = models.SmallIntegerField(default=0, null=False, blank=False) ## payment_success: 1, payment_failed: 0
total_amount = models.FloatField()
date_time = models.DateTimeField()
My desired output is:
"ticket_details": [
{
"event_id": 1,
"event_name": Event-1,
"total_transactions": 10000, ## Sum of all ticket amounts of event_id: 1, where payment_status: 1
"total_tickets": 24, ## Count of all tickets that belong to event_id: 1
},
{
"event_id": 2,
"event_name": Event-2,
"total_transactions": 10000, ## Sum of all ticket amounts of event_id: 2, where payment_status: 1
"total_tickets": 24, ## Count of all tickets that belong to event_id: 2
}]
This is what I have done:
serializers.py:
class EventListSerializer(serializers.ModelSerializer):
# ticket_id = serializers.IntegerField(source='id')
# event_id = serializers.PrimaryKeyRelatedField(source='event', read_only=True)
# event_name = serializers.PrimaryKeyRelatedField(source='event.name', read_only=True)
total_transactions = serializers.SerializerMethodField()
total_tickets = serializers.SerializerMethodField()
class Meta:
model = Event
fields = ('total_transactions', 'total_tickets')
def get_total_transactions(self, obj):
return obj.event_tickets.all().aggregate(sum('total_amount'))['total_amount__sum']
def get_total_tickets(self, obj):
return obj.event_tickets.all().count()
No matter whatever I try, I am always getting this error: 'EventTicket' object has no attribute 'event_tickets'
Firstly nice work on your question it is well-written! I don't see anything wrong with your code as it stands out, but the error message does say something that contradicts the code you've posted.
'EventTicket' object has no attribute 'event_tickets'
What this is basically saying is that in your serializer, although in the class Meta you've stated that model = Event for some reason the obj param in your serializer method is of type EventTicket, not of type Event.
It looks like you've set the correct related_name in your EventTicket model for the event field, nice.
This leads me to believe that your code is correct but the server seems to be running either an old version of the code, or is using a different serializer. Can we confirm that if you put a print statement in, the server actually prints it out in the serializer method?
The error says that obj is the instance of EventTicket. I guess.. at your view layer, you are calling EventListSerializer for EventTicket object. You have to call the serializer only for Event object.
I would like to store some data in one of my database field. The data is added to that field while deserialization with POST method. Later when I want to show data with GET method I don't want that one field to be presented.
When I do POST I deserialize that string:
{
"car_id": 3,
"rating": 3
}
Later in views.py I do the deserialization while POST:
#api_view(['POST'])
def car_rate(request):
if request.method == 'POST':
rate_data = JSONParser().parse(request)
rate_serializer = CarRateSerializer(data=rate_data)
if rate_serializer.is_valid():
try:
car_obj = Car.objects.get(pk=rate_data['car_id'])
except Car.DoesNotExist:
return JsonResponse({'message': 'The car with given ID does not exist!'}, status=status.HTTP_404_NOT_FOUND)
# check if rate is from 1 to 5
r = rate_serializer.validated_data['rating']
if int(r) >= 1 and int(r) <= 5:
rate_serializer.save()
return JsonResponse({'message':'The rate is in the scope!'})
else:
return JsonResponse({'message':'The rate is NOT in the scope!'})
return JsonResponse(rate_serializer.errors)
And there is my models.py:
class Car(models.Model):
make = models.CharField(max_length=15)
model = models.CharField(max_length=15)
avg_rating = models.FloatField(default=0)
def __str__(self): # print it when Car instance is needed
return self.make
class CarRate(models.Model):
car_id = models.ForeignKey(Car, related_name='rates',
on_delete=models.CASCADE,
default=0)
rating = models.PositiveIntegerField(default=0)
The code does works (somehow). For now there can be added rates for one car (multiple rates) with POST moethods. I store the rates in CarRate class and later it will be used to calculate the average rate for a car. I just simply don't want to print it out with GET.
This is my output right now:
{
"id": 2,
"make": "Volkswagen",
"model": "Golf",
"rates": [
4,
4,
2,
3
],
"avg_rating": 0.0
},
I simply want the rates field to be invisible while printing.
I read about defer() method and tried it out, but nothing happened. Any help?
If you absolutely don't want that field to be in your database ever, then you can simply remove that field from the Serializer field option (You named that CarRateSerializer)
But if you want that to be in your database but you don't want that to show as output, you can use extra_kwargs with 'write_only': True in your serializer class. I'm giving you an example I used for one of my projects
class TopicSerializer(serializers.ModelSerializer):
class Meta:
model = Topic
fields = ['id','title', 'totalMarks', 'status', 'categoryID']
extra_kwargs = {'categoryID': {'write_only': True}}
for your code, you can add this line of code below fields in that class Meta of your CarRateSerializer
extra_kwargs = {'rating': {'write_only': True}}
I hope this should solve your issue
Just remove rating field from, CarRateSerializer OR you can create a new Serializer for CarRate.
I followed suggestion from this question
But i need to name one field of query_set to date filed of another object
My models are
class Choice(models.Model):
question = models.ForeignKey(Question, related_name='choice', on_delete=models.CASCADE)
choice_text = models.CharField(max_length=200)
votes = models.IntegerField(default=0)
def __str__(self):
return self.choice_text
class ChoiceWithTime(models.Model):
choiceTime = models.ForeignKey(Choice,related_name='choiceTime', on_delete=models.CASCADE)
choice_date=models.DateField()
My view
class QuestionChoicesViewSet(viewsets.ModelViewSet):
queryset = Choice.objects.all()
serializer_class = ChoiceDateSerializer
def get_queryset(self):
return Choice.objects.values('choiceTime__choice_date','choice_text').annotate(
total_votes=Count('choiceTime__choice_date'),
)
I need to count number of submission in particular dates
I don't know how to name choiceTime__choice_date that serializer recognizes field in query set
class ChoiceDateSerializer(serializers.ModelSerializer):
choiceTime__choice_date = serializers.DateTimeField()
total_votes = serializers.IntegerField()
class Meta:
model = Choice
fields = ('id', 'choice_text','total_votes','choiceTime__choice_date')
i receive
{
"choice_text": "ant tower",
"total_votes": 3,
"choiceTime__choice_date": "2017-04-20"
}
But i want to recieve
{
"choice_text": "ant tower",
"total_votes": 3,
"choice_date": "2017-04-20"
}
Tried different options with no success. Definitely i am missing the point.
For my purposes it is working, but i want to have well written API.
2 option change time submission model?
class ChoiceWithTime(models.Model):
choiceTime = models.ForeignKey(Choice,related_name='choiceTime', on_delete=models.CASCADE)
choice_date=models.DateField()
coutner = models.IntegerField(default=0)
Is 2 option considers to be better approach to my particular problem? Thanks!
You are receiving a json object, which you add its key value.
for vote_detail in data:
if vote_detail.choiceTime__choice_date:
vote_detail.choice_date=vote_detail.choiceTime__choice_date
then serialize and save, a quick solution.
You could also add to your model the name that you want to call it. That's closer to backend and maybe worth delving into.
from django.db.models import Count,F
If anybody finds this problem and this is easiest answer i came up to.
As it was suggested before passing to serializer change value using model package functions
class QuestionChoicesViewSet(viewsets.ModelViewSet):
queryset = Choice.objects.all()
serializer_class = ChoiceDateSerializer
def get_queryset(self):
return Choice.objects.all().annotate(choice_date=F('choiceTime__choice_date')).values('choice_date','choice_text').annotate(
total_votes=Count('choiceTime__choice_date'),
)
In my app which use DRF, I want to use model serializer with multiple related objects.
models.py:
class JobType(models.Model):
name = models.CharField(null=False, max_length=250)
class Offer(models.Model):
user = models.ForeignKey(User, null=False)
job_type = models.ForeignKey(JobType, null=False)
salary = models.DecimalField(null=False, max_digits=8,
decimal_places=2)
serializers.py:
class JobTypeSerializer(serializers.ModelSerializer):
class Meta:
model = JobType
fields = ('id', 'name')
class OfferSerializer(serializers.ModelSerializer):
job_type = JobTypeSerializer()
class Meta:
model = Offer
fields = (
'salary', 'job_type', 'user'
)
views.py:
class SalaryViewSet(viewsets.ModelViewSet):
queryset = Salary.objects.all()
serializer_class = SalaryFullSerializer
What I want to achieve:
when I do GET request on my api/offers I want to have something like:
[
{
"salary": 1000,
"user: 1,
"job_type": {
"id": 1,
"name": "Developer",
}
}
]
so, basically, when GET offers is made, I want to have nested related object with all it's properties.
On other hand, when POST offers is made, I want to limit JobType choices.
When I've removed job_type = JobTypeSerializer() from OfferSerializer I had nice dropdown with available choices (in DRF debug). But it caused that GET on offers returned only JobOffer's ID in results.
How can I achieve desired behavior?
You can use different serializer for POST and GET requests.
Override get_serializer_class
def get_serializer_class(self):
if self.request.method == 'POST':
return SalaryPostSerializer
return SalaryFullSerializer
Basically I have models like this:
class Playlist(models.Model):
key = models.CharField(max_length=255,blank=True, unique=True)
user = models.ForeignKey(User)
title = models.CharField(max_length=200)
pub_date = models.DateTimeField(auto_now_add=True)
videos = models.ManyToManyField(Video, through='PlaylistVideo')
class PlaylistVideo(models.Model):
playlist = models.ForeignKey(Playlist)
video = models.ForeignKey(Video)
position = models.IntegerField()
class Video(models.Model):
title = models.CharField(max_length=255,blank=True)
description = models.TextField(blank=True)
thumb = models.URLField(blank=True)
duration = models.IntegerField(default=0)
Now I want an API to return PLAYLISTS like this... But Videos should be sorted by POSITION in
PlaylistVideo Model
{
"key": "h8x3",
"title": "First Playlist",
"pub_date": "2012-10-11T17:00:26Z",
"videos": [
{
....
},
{
....
}
]
},
How should I got about it?
Not too sure if you've managed to solve your issue, but I've came across this myself, and managed to make it work like by doing something like this:
create a custom serializer like this:
class PlaylistVideoSerializer(serializers.HyperlinkedModelSerializer):
title = serializers.ReadOnlyField(video.title)
description = serializers.ReadOnlyField(video.description)
thumb = serializers.ReadOnlyField(video.thumb)
duration = serializers.ReadOnlyField(video.duration)
class Meta:
# your associative entity here
model = PlaylistVideo
fields = ('title', 'description', 'thumb', 'duration')
Here, I'm assuming you'd like to display all the fields under videos entity/table. Feel free to adjust to your needs/likings.
Then, all you need to do is this
class PlaylistSerializer(serializers.ModelSerializer):
videos = PlaylistVideoSerializer(source='playlistvideo_set', many=True)
class Meta:
model = Playlist
fields = ('key', 'title', 'pub_dates', 'videos')
Note: Always make sure that the source is referencing to the associative entity. Else you'd get a list of empty json.
Hope this would help anyone out there facing similar issue.
You can do it like this:
class PlaylistVideoList(generics.ListCreateAPIView):
serializer_class = PlaylistVideoSerializer
queryset = PlaylistVideo.objects.all().order_by('position')
in serializers.py:
class PlaylistVideoSerializer(serializers.ModelSerializer):
class Meta:
model = PlaylistVideo
We need to add some documentation on 'through' relationships really.
In the meantime, this discussion may help:
https://groups.google.com/forum/#!topic/django-rest-framework/xzOhjILq3xA/discussion