I have a class called Team that has wins and losses.
I want to be able to sort teams by a win_pct which should be the ratio of wins to total games played (i.e. wins.count()/(wins.count() + losses.count())).
class Team(TimestampModerated):
name = models.CharField(max_length=80, blank=False, null=False)
wins = models.ManyToManyField('self', blank=True, symmetrical=False, related_name='related_wins')
losses = models.ManyToManyField('self', blank=True, symmetrical=False, related_name='related_losses')
def num_wins(self):
return self.wins.count()
def num_losses(self):
return self.losses.count()
def win_pct(self):
return self.wins.count()/(self.wins.count() + self.losses.count())
So, in my view I have something like this:
#list_route
def teams_list(self, request):
teams = Team.objects.all().order_by('-win_pct')
page = self.paginate_queryset(teams)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(teams, many=True)
return Response(
data=serializer.data
)
But it just gives an error:
django.core.exceptions.FieldError: Cannot resolve keyword 'win_pct' into a field. Choices are: created, id, losses, moderation_code, related_losses, related_wins, name, updated, wins
Try below code
from django.db.models import Count
teams = Team.objects.all().annotate(
win_pct=(Count('wins')/(Count('wins')+Count('losses')))).order_by('win_pct')
Related
I have product serializer which return category_offer_price & product_offer_price,
before getting this response I want to compare both price and only return whichever is highest price.
#Serilaizer.py
class ProductSerializer(ModelSerializer):
category = CategorySerializer()
product_offer_price = SerializerMethodField()
category_offer_price = SerializerMethodField()
class Meta:
model = Products
fields = [
"id",
"product_name",
"slug",
"category",
"description",
"category_offer_price",
"product_offer_price",
"base_price",
"stock",
"is_available",
"created_date",
"images",
"images_two",
"images_three",
]
def get_product_offer_price(self, obj):
try:
product_offer = ProductOffer.objects.get(product=obj)
if product_offer.is_active:
offer_price = product_offer.product_offer_price()
return offer_price
except Exception:
pass
return None
def get_category_offer_price(self, obj):
try:
category_offer = CategoryOffer.objects.get(category=obj.category)
if category_offer.is_active:
offer_price = category_offer.category_offer_price(obj)
return offer_price
except Exception:
pass
return None
#Models.py
class Products(models.Model):
category = models.ForeignKey(Category, on_delete=models.CASCADE)
product_name = models.CharField(max_length=50, unique=True)
slug = models.SlugField(max_length=100, unique=True)
description = models.TextField(max_length=500)
base_price = models.IntegerField()
images = models.ImageField(upload_to="photos/products")
images_two = models.ImageField(upload_to="photos/products")
images_three = models.ImageField(upload_to="photos/products")
stock = models.IntegerField()
is_available = models.BooleanField(default=True)
created_date = models.DateTimeField(auto_now_add=True)
modified_date = models.DateTimeField(auto_now=True)
class Meta:
verbose_name_plural = "Products"
def __str__(self):
return self.product_name
I'd like to know is it possible to compare serializer fields in a serializer class?
You can move into one method, to validate your field. Also, substitute your try:except with get-object-or-404 method and your serializer fields with all value since you are using everything, to have a much cleaner code.
from django.shortcuts import get_object_or_404
class ProductSerializer(ModelSerializer):
category = CategorySerializer()
price = SerializerMethodField()
class Meta:
model = Products
fields = '__all__'
def get_price(self, obj):
product_offer = get_object_or_404(ProductOffer, product=obj)
category_offer = get_object_or_404(CategoryOffer, category=obj.category)
if product_offer.is_active and category_offer.is_active:
if product_offer.product_offer_price() > category_offer.category_offer_price(obj):
return product_offer.product_offer_price()
else:
return category_offer.category_offer_price(obj)
elif product_offer.is_active and not category_offer.is_active:
return product_offer.product_offer_price()
elif category_offer.is_active and not product_offer.is_active:
return category_offer.category_offer_price(obj)
EDIT: As you can see I used the classic if/else in this solution, although since Python 3.10 you can use the Match case statement to substitute these conditions chain.
In case of objects do not exist:
class ProductSerializer(ModelSerializer):
category = CategorySerializer()
price = SerializerMethodField()
class Meta:
model = Products
fields = '__all__'
def get_price(self, obj):
try:
product_offer = ProductOffer.objects.filter(product=obj).first()
category_offer = CategoryOffer.objects.filter(category=obj.category).first()
if not product_offer and not category_offer:
return obj.base_price
elif not category_offer:
return product_offer.product_offer_price()
elif not product_offer:
return category_offer.category_offer_price(obj)
elif category_offer and product_offer:
if category_offer.is_active and not product_offer.is_active:
return category_offer.category_offer_price(obj)
elif product_offer.is_active and not category_offer.is_active:
return product_offer.product_offer_price()
elif category_offer.is_active and product_offer.is_active:
if category_offer.category_offer_price(obj) > product_offer.product_offer_price():
return category_offer.category_offer_price(obj)
else:
return product_offer.product_offer_price()
except:
return obj.base_price
Although, to be honest, if there could be no objects then the is_active field is redundant.
You can override to_representation()
Example:
class ProductSerializer(ModelSerializer):
category = CategorySerializer()
product_offer_price = SerializerMethodField()
category_offer_price = SerializerMethodField()
...
...
def to_representation(self, instance):
data = super().to_representation(instance)
# access required fields like this
product_offer_price = data['product_offer_price']
category_offer_price = data['category_offer_price']
# do calculations here and returning the desired field as `calculated_price`
if category_offer_price > product_offer_price:
data['calculated_price'] = category_offer_price
else:
data['calculated_price'] = product_offer_price
return data
Not sure it s what you want but you could use a field of type SerializerMethodField which allow you to add a computed field that you could call category_offer_higher_price. Its value is computed by a function that return the highest one. See following link : https://www.django-rest-framework.org/api-guide/fields/#serializermethodfield
I'm trying to get an item with the most occurrences of a foreign key (votes), inside of a queryset (Questions, Choices).
I.E. I need to get the most popular vote to set the 'winner' attribute in the JsonResponse.
Any help on how I can figure this out?
Here is my view.
allchoices = [{
'id':i.id,
'question':i.question.id,
'choice_text':i.choice_text,
'votes':i.voter_set.all().count()
} for i in poll_choices]
return JsonResponse({
"votes":choice.voter_set.all().count(),
'winner':True,
'success':True,
'allchoices':allchoices
},safe=False,)
These are my models:
class Voter(models.Model):
# Authentication of anonymous users
choice = models.ForeignKey('PollChoice', on_delete=models.CASCADE)
question = models.ForeignKey('PollQuestion', on_delete=models.CASCADE)
class PollChoice(models.Model):
question = models.ForeignKey('PollQuestion', on_delete=models.CASCADE)
choice_text = models.CharField(max_length=200)
def __str__(self):
return self.choice_text
class PollQuestion(models.Model):
question = models.CharField(max_length=200)
created = models.DateTimeField(auto_now_add=True)
creator = models.ForeignKey('poll_creator',/\
on_delete=models.CASCADE, null=False, blank=False)
uuid = ShortUUIDField(length=8, max_length=12)
def poll_choices(self):
return self.pollchoice_set.all().annotate(voters=/\
models.Count(models.F('voter'))).order_by('-voters')
def choices(self):
return self.pollchoice_set.all()
def __str__(self):
return f'{self.question}'
You can order by the number of related Voters, with:
from django.db.models import Count
poll_choice = PollChoice.objects.alias(
num_voters=Count('voter')
).latest('num_voters')
this will retrieve the PollChoice with the largest amount of related Voters. You can filter the PollChoice further, for example to only consider PollChoices for a certain PollQuestion with:
from django.db.models import Count
poll_choice = PollChoice.objects.filter(question=my_question).alias(
num_voters=Count('voter')
).latest('num_voters')
I have made a Django model form but the problem is in my logic I am using something else and now I want to figure out a way to validate it by either defining a Meta class and choosing the fields that I want to display to the user but of course this won't validate the form.
Now I want to know if there is a way to validate the form without touching the models and pass the data required for the logic and after take care of the information needed for the data of the model to be saved.
Here is the models:
from django.db import models
from django.db import models
from django.contrib.auth.models import User
# Create your models here.
class RoomCategory(models.Model):
name = models.CharField(max_length=59)
price = models.IntegerField()
beds = models.PositiveIntegerField()
capacity = models.PositiveIntegerField()
size = models.CharField(max_length=59)
def __str__(self):
return self.name
class Room(models.Model):
room_number = models.CharField(max_length=60)
room_category = models.ForeignKey(RoomCategory, on_delete=models.CASCADE)
def __str__(self):
return f"The room {self.room_number} {self.room_category} has a maximum of {self.room_category.capacity} person and cost {self.room_category.price}/night "
class Booking(models.Model):
customer = models.ForeignKey(User, on_delete=models.CASCADE)
room = models.ForeignKey(RoomCategory, on_delete=models.CASCADE)
check_in = models.DateField()
check_out = models.DateField()
adults = models.PositiveSmallIntegerField()
children = models.PositiveSmallIntegerField()
def __str__(self):
return f"{self.customer} has booked for {self.room} from {self.check_in} to {self.check_out}"
Here is the form:
class BookingForm(forms.ModelForm):
class Meta:
model = Booking
fields = ['room', 'check_in', 'check_out', 'adults', 'children']
here is the views.py
data = form.cleaned_data
roomlist = Room.objects.filter(room_category__name=data['room'])
available_rooms = []
for room in roomlist:
if data['adults'] + data['children'] > room.room_category.capacity:
return HttpResponse(f'Sorry !! But this category of room cannot handle more than {room.room_category.capacity}')
else:
if check_availability(room.room_category.name, data['check_in'], data['check_out'], data['adults'], data['children']):
available_rooms.append(room)
if len(available_rooms) > 0:
room = available_rooms[0]
new_booking = Booking.objects.create(
customer=self.request.user,
room=room,
check_in=data['check_in'],
check_out=data['check_out'],
adults=data['adults'],
children=data['children']
)
new_booking.save()
return HttpResponse(new_booking)
else:
return HttpResponse('All the rooms of this type are not available')
It is not printing the data means that the form is not valid and it fall down to the else statement.
You can validate any field in the form by writing a method in this way : def clean_(field_name) i.e def clean_room(self) read more:
https://docs.djangoproject.com/en/3.1/ref/forms/validation/#cleaning-a-specific-field-attribute
I have these models:
Organisation
Student
Course
Enrollment
A Student belongs to an Organisation
A Student can enrol on 1 or more courses
So an Enrollment record basically consists of a given Course and a given Student
from django.db import models
from model_utils.models import TimeStampedModel
class Organisation(TimeStampedModel):
objects = models.Manager()
name = models.CharField(max_length=50)
def __str__(self):
return self.name
class Student(TimeStampedModel):
objects = models.Manager()
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
email = models.EmailField(unique=True)
organisation = models.ForeignKey(to=Organisation, on_delete=models.SET_NULL, default=None, null=True)
def __str__(self):
return self.email
class Course(TimeStampedModel):
objects = models.Manager()
language = models.CharField(max_length=30)
level = models.CharField(max_length=2)
def __str__(self):
return self.language + ' ' + self.level
class Meta:
unique_together = ("language", "level")
class EnrollmentManager(models.Manager):
def org_students_enrolled(self, organisation):
return self.filter(student__organisation__name=organisation).all()
class Enrollment(TimeStampedModel):
objects = EnrollmentManager()
course = models.ForeignKey(to=Course, on_delete=models.CASCADE, default=None, null=False, related_name='enrollments')
student = models.ForeignKey(to=Student, on_delete=models.CASCADE, default=None, null=False, related_name='enrollments')
enrolled = models.DateTimeField()
last_booking = models.DateTimeField()
credits_total = models.SmallIntegerField(default=10)
credits_balance = models.DecimalField(max_digits=5, decimal_places=2)
Notice the custom EnrollmentManager that allows me to find all students who are enrolled from a given organisation.
How can I add a custom Manager to retrieve all the courses from a given organisation whose students are enrolled?
What I have tried
I thought to create a CourseManager and somehow query/filter from that side of the relationship:
class CourseManager(models.Manager):
def org_courses_enrolled(self, organisation):
return self.filter(enrollment__student__organisation__name=organisation).all()
This works, but it gives me the same 100 enrollment records :(
What I am trying to get is:
based on a given organisation
find all students who are enrolled
and then (DISTINCT?) to get the list of enrolled courses for that org
This is the view:
class OrganisationCoursesView(mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet):
serializer_class = CourseSerializer
queryset = Course.objects.get_courses(1)
and the url:
# The below should allow: /api/v1/organisations/1/courses/
router.register('api/v1/organisations/(?P<organisation_pk>\d+)/courses', OrganisationCoursesView, 'organisation courses')
UPDATE 1
Based on the answer from h1dd3n I tried this next:
class CourseManager(models.Manager):
def get_queryset(self):
return super(CourseManager, self).get_queryset()
def get_courses(self, organisation):
return self.get_queryset().filter(student__organisation_id=organisation)
but that throws an error (as I expected it would):
FieldError: Cannot resolve keyword 'student' into field. Choices are:
courses, created, id, language, level, modified, progress
UPDATE 2 - getting closer!
Ok with help from #AKX's comments:
class CourseManager(models.Manager):
def get_queryset(self):
return super(CourseManager, self).get_queryset()
def get_courses(self, organisation):
return self.get_queryset().filter(courses__student__organisation_id=organisation)
now DOES return courses, but it returns a copy for each enrolled student. So now I need to group them so each record only appears one time...
First you need to change self.filter to self.get_queryset().filter() or make a seperate method in the manager.
def get_queryset(self):
return super(CourseManager, self).get_queryset()
In manager create a function
def get_courses(self,organisation):
return self.get_queryset.filter(student__oraganisation=organisation)
This should return the students and you don't need to call .all() - the filtered qs either way returns you all the objects that it finds.
EDIT
Try this:
class CourseManager(models.Manager):
def get_queryset(self):
return super(CourseManager, self).get_queryset()
def get_courses(self, organisation):
return self.get_queryset().filter( \
enrollments__student__organisation_id=organisation).distinct()
UPDATE 2
You can try and play around with from django.db.models import Q https://docs.djangoproject.com/en/3.0/topics/db/queries/
or with annotate based on this answer Get distinct values of Queryset by field where you filter out each student so it would appear once.
I am a beginner of django-rest-framework. I am trying hard to understand the serializer and good api design. I dont want to dive directly into viewset and generics view like ListView, RetrieveAPIView and etc. I want to understand the serializer and APIView clearly so i drew the following criteria to solve them. However i could only solve 2 of the problem from below.
Here is the list of problems i drew to hone my rest api skill
"""
return a list of rent
"""
"""
return a list of rent or specific rent if token is give n
"""
"""
return a list of rent contacted by specific buyer
"""
"""
return a list of buyer that has contacted a specific rent
"""
Here is my model of Rent, Galleries and Contact. Rental and contact are separate app. Contact is to contact a rent owner to buy or rent the space listed by that owner.
class Rental(models.Model):
user = models.ForeignKey(User)
name = models.CharField(max_length=300, blank=False, null=False)
phone_number = models.PositiveIntegerField(null=False, blank=False)
rate = models.FloatField()
class Gallery(models.Model):
rent = models.ForeignKey(Rental, related_name="galleries")
image = models.FileField(upload_to=user_directory_path, null=True, blank=True)
tag = models.CharField(max_length=1, choices=TAGS, null=True, blank=True)
BUYER_CHOICES = (
('B', 'Buy'),
('R', 'Rent'),
)
class Contact(models.Model):
buyer = models.ForeignKey(User)
rental = models.ForeignKey(Rental, related_name="rent")
email_id = models.EmailField(blank=False, null=False)
buyer_choice = models.CharField(choices=BUYER_CHOICES, max_length=1, null=False, blank=False)
class GallerySerializer(serializers.ModelSerializer):
class Meta:
model = Gallery
fields = ('image', 'tag',)
class RentalSerializer(serializers.ModelSerializer):
user = serializers.ReadOnlyField(source='user.username')
galleries = GallerySerializer(many=True)
class Meta:
model = Rental
fields = ('__all__')
UPDATED RentAPIView CODE AS PER #Remi
class RentAPIView(APIView):
serializer_class = RentalSerializer
parser_classes = (FormParser, MultiPartParser,)
def get(self, request, token=None, format=None):
"""
Returns a list of rents (or just rent if token is given)
"""
reply = {}
try:
rents = Rental.objects.filter(user=request.user)
# code is updated here
buyer_id = self.request.query_params.get('buyer_id', None)
if buyer_id:
rents = rents.filter(user__contact_set__buyer_id=buyer_id)
if token:
rent = rents.get(id=token)
reply['data'] = RentalSerializer(rent).data
else:
reply['data'] = RentalSerializer(rents, many=True).data
except Rental.DoesNotExist:
return error.RequestedResourceNotFound().as_response()
except:
return error.UnknownError().as_response()
else:
return Response(reply, status.HTTP_200_OK)
Sorry for posting long code. I just wanted to show i tried my best to design effective rest api but could not solve the last 2 problem.
Can anyone kindly help me to understand on how to handle the following criteria?
I would strongly suggest using the viewsets. In your case, the ModelViewSet would do, if you override your get_queryset function in order to prefilter your queryset by anything you want. For Example:
class RentAPIViewset(viewsets.ModelViewSet):
serializer_class = RentalSerializer
def get_queryset(self):
# if requesting something like /api/rent?buyer_id=1
buyer_id = self.request.query_params.get('buyer_id', None)
if buyer_id:
return Rental.objects.filter(rent__buyer__id=buyer_id)
return Rental.objects.all()
This would be much cleaner uses the core-functionality of DRF. That being said, back to your question, you could use some of the logic above to extend your api view:
with query params (you could also go with the kwargs approach like you did with the token):
class RentAPIView(APIView):
serializer_class = RentalSerializer
parser_classes = (FormParser, MultiPartParser,)
def get(self, request, token=None, format=None):
"""
Returns a list of rents (or just rent if token is given)
"""
reply = {}
try:
# first of your two remaining problems
# if requesting something like /api/rent?buyer_id=1
buyer_id = self.request.query_params.get('buyer_id', None)
rents = Rental.objects.filter(user=request.user)
if buyer_id:
rents = rents.filter(rent__buyer__id=buyer_id)
if token:
rent = rents.get(id=token)
reply['data'] = RentalSerializer(rent).data
else:
reply['data'] = RentalSerializer(rents, many=True).data
except Rental.DoesNotExist:
return error.RequestedResourceNotFound().as_response()
except:
return error.UnknownError().as_response()
else:
return Response(reply, status.HTTP_200_OK)
The last of your problems doesn't return Rent-Objects, but rather User objects, so this should be done in a differen API view.