I'm using Django 2.x.
I have two models
class AmountGiven(models.Model):
contact = models.ForeignKey(Contact, on_delete=models.PROTECT)
amount = models.FloatField(help_text='Amount given to the contact')
interest_rate = models.FloatField(blank=True, default=None, null=True)
given_date = models.DateField(default=timezone.now)
total_due = models.FloatField(blank=True, default=0.0, editable=False)
class AmountReturned(models.Model):
amount_given = models.ForeignKey(AmountGiven, on_delete=models.CASCADE, blank=True)
amount = models.FloatField()
return_date = models.DateField(default=date.today)
Use case
There can be multiple records of the amount given to a contact
There can be multiple records of the returned amount for an amount given
Now, I want to get total_due amount for a particular contact. This includes
total_payable = total_given + interest
total_due = total_payable - total_returned
To calculate total_due and interest, I have defined few property methods in the AmountGiven model.
#property
def interest_to_pay(self):
if self.interest_rate:
simple_interest_amount = ...
return simple_interest_amount
return 0
#property
def total_payable(self):
return self.amount + self.interest_to_pay
#property
def amount_due(self):
total_due = self.total_payable - self.total_returned
self.total_due = total_due
self.save()
return total_due
#property
def total_returned(self):
returned_amount = self.amountreturned_set.aggregate(total_returned=Sum('amount'))['total_returned']
if not returned_amount:
returned_amount = 0
return returned_amount
In Contact model, there is a property method to get the total due amount for the contact.
#property
def amount_due(self):
total_due = 0
for due in self.amountgiven_set.all():
total_due += due.amount_due
return total_due
Query
ContactSerializer
class ContactMinSerializer(serializers.ModelSerializer):
class Meta:
model = Contact
fields = (
'id', 'first_name', 'amount_due', 'created', 'modified'
)
Since amount_due property is being used in the ContactSerializer, amount_due property is called everytime a contact is call and thus results in nested DB queries.
How can I optimise the above scenario in the application to reduce the DB queries while getting contact or list of contacts? Specially two properties amount_due and total_returned.
amount_due() updates the total_due field in the table, every time it is called.
Edit 2
class ContactViewSet(LoggingMixin, viewsets.ModelViewSet):
serializer_class = ContactMinSerializer
def get_queryset(self):
return Contact.objects.filter(user=self.request.user).annotate(
total_due=Sum(
F('amountgiven_set__total_payable')
- F('amountgiven_set__total_returned')
)
).order_by('first_name')
You're looking for annotations.
Your viewset should define a queryset as follows:
from django.db.models import F, Sum
Contact.objects.annotate(
total_due=Sum(
F('amountgiven_set__total_payable')
- F('amountgiven_set__total_returned')
)
)
Then define a MethodSerializer field on your serializer to account for it.
class ContactMinSerializer(serializers.ModelSerializer):
total_due = serializers.SerializerMethodField()
def get_total_due(self, obj):
return return self.total_due
class Meta:
model = Contact
fields = (
'id', 'first_name', 'created', 'modified',
'total_due',
)
Related
I have a model:
class Film(models.Model):
title = models.CharField(max_length=250)
starring = models.CharField(max_length=250)
description = models.CharField(max_length=500)
rating = models.CharField(max_length=2, choices=(('1','U'),('2','PG'),('3','12A'),('4','12'),('5','15'),('6','18')),default='18')
length = models.IntegerField()
def __str__(self):
return f"{self.title}, {self.rating}"
and a serialiser:
class FilmSerializer(serializers.ModelSerializer):
class Meta:
model = Film
fields = ('title','description','starring','rating','length')
def to_representation(self, instance):
data = super().to_representation(instance)
hours = math.floor(int(data['length']) / 60)
minutes = int(data['length']) % 60
data['length'] = f"{hours}h {minutes}m"
return data
and an api view:
class FilmList(ListAPIView):
queryset = Film.objects.all()
serializer_class = FilmSerializer
filter_backends = (DjangoFilterBackend,)
filterset_fields = ('rating',)
When i use the Django Rest frame work I can filter by the rating, but only if i remove the choices definition from the model. When the choices definition is present on the 'rating' then the filter returns nothing at all.
I would actually like to use that to filter on the 'rating.' Is there a way round this?
thanks
I'm trying to calculate the total price of a Recipe. To optimize DB queries I'm trying to use Django's ORM capabilities to perform the fewest requests.
My models.py is like this:
class BaseRecipe(models.Model):
title = models.CharField(_('Base recipe title'), max_length=255,
user = models.ForeignKey(User, null=True, on_delete=models.CASCADE, related_name='user_base_recipes')
class Meta:
ordering = ['title']
def __str__(self):
return self.title
class IngredientBaseRecipe(models.Model):
base_recipe = models.ForeignKey(BaseRecipe, on_delete=models.CASCADE, related_name='ingredients')
name = models.CharField(_('Name'), max_length=255)
products = models.ManyToManyField(Product)
quantity = models.FloatField(_('Quantity'), default=0.0)
class Meta:
ordering = ['-id']
def __str__(self):
return self.name
class Product(models.Model):
name = models.CharField(_('Name'), max_length=255, help_text=_('Product name'))
price = models.FloatField(_('Sale price'), default=0)
class Meta:
ordering = ['name', ]
indexes = [models.Index(fields=['name',]), ]
Then in my Viewset I'm trying to get the BaseRecipes queryset with an annotated field that shows the sum of the ingredients prices. I get to the point to obtain the ingredients price, but I'm stucked trying to sum them in the BaseRecipe query set:
min_price = (
Product.objects.filter(name=OuterRef('name'))
.annotate(min_price=Min('price'))
.values('min_price')
)
ingredients_price = (
IngredientBaseRecipe.objects
.filter(base_recipe=OuterRef('id'))
.annotate(price=Subquery(min_price))
.order_by()
.annotate(total=Sum(F('price') * F('quantity')))
.values('total')
)
queryset = BaseRecipe.objects.filter(user=self.request.user) \
.annotate(cost=Sum(ingredients_price))
return queryset
Thanks a lot for your help!
Finally I got a solution by doing the Sum in the Subquery:
min_price = (
Product.objects.filter(name=OuterRef('name'))
.annotate(min_price=Min('price'))
.order_by()
.values('min_price')
)
ingredients_price = (
IngredientBaseRecipe.objects
.filter(base_recipe__pk=OuterRef('id'))
.prefetch_related('products')
.select_related('base_recipe')
.annotate(price=Subquery(min_unit_price))
.values('base_recipe__pk')
.order_by()
.annotate(total=ExpressionWrapper(Sum(F('price') * F('quantity')), output_field=FloatField()))
.values('total')
)
queryset = BaseRecipe.objects.filter(user=self.request.user) \
.prefetch_related('ingredients', 'ingredients__products') \
.annotate(cost=Subquery(ingredients_price))
return queryset
I Have two models clinic and ClinicCredits:
I want a list of clinics withsum of available balance
The Problem is if i use annotate i have to loop for avalibale alance with queryset of cliniccredits :
class Clinic(models.Model):
"""
clinic model
"""
user = models.OneToOneField(User, related_name="clinic", primary_key=True,
on_delete=models.CASCADE)
def __str__(self):
return self.name
class Meta:
db_table = 'clinic'
class ClinicCredits(models.Model):
"""
credit details for clinic
"""
clinic = models.ForeignKey(Clinic, on_delete=models.CASCADE, related_name='c_credits')
credits = models.FloatField('credits', default=0.0)
balance = models.FloatField('balance', default=0.0, help_text="balance after deduction of credits")
def __str__(self):
return self.clinic.name
class Meta:
db_table = 'clinic_credits'
here is my query to fetch clinic:
clinics = Clinic.objects.filter(practice_id__in=user.dietitian.practiceid).order_by(
'user__date_joined').prefetch_related(Prefetch('c_credits',ClinicCredits.objects.filter(balance__gt=0),'credit_'))
and how can i use aggregate in this condition or is there some oter way to retreive clinic list with their available credits.
You can try like this using annotate(...):
clinics = Clinic.objects.filter(
practice_id__in=user.dietitian.practiceid
).order_by('user__date_joined').prefetch_related(
Prefetch(
'c_credits',
ClinicCredits.objects.filter(balance__gt=0),
'credit_'
)
).annotate(
total_cred=Sum('c_credits__balance')
)
I'm using Django 2.0 and Django REST Framework.
I have two models contact and transaction as below
contact model
class Contact(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100, blank=True, null=True)
amount given model
class AmountGiven(models.Model):
contact = models.ForeignKey(Contact, on_delete=models.PROTECT)
amount = models.FloatField(help_text='Amount given to the contact')
interest_rate = models.FloatField(blank=True, default=None, null=True, help_text='% of interest to be calculated')
_given_date = models.DateTimeField(
db_column='given_date',
default=timezone.now,
help_text='Date and time when amount was given to the contact'
)
def __str__(self):
return str(self.amount)
#property
def given_date(self):
return self._given_date
#given_date.setter
def given_date(self, value):
self._given_date = value
#property
def interest_to_pay(self):
if self.interest_rate:
datetime_diff = datetime.now(get_localzone()) - self.given_date
days = datetime_diff.days
duration_in_year = days/365
simple_interest_amount = (self.amount * duration_in_year * self.interest_rate)/100
return simple_interest_amount
return 0
#property
def total_payable(self):
return self.amount + self.interest_to_pay
#property
def amount_due(self):
returned_amount = 0
for returned in self.amountreturned_set.all():
returned_amount += returned.amount
return self.total_payable - returned_amount
and ContactSerializer
class ContactSerializer(serializers.HyperlinkedModelSerializer):
url = serializers.HyperlinkedRelatedField(
view_name='contacts:detail',
read_only=True
)
user = serializers.CurrentUserDefault()
amount_due = ReadOnlyField(source='amountgiven__amount_due')
class Meta:
model = Contact
fields = ('url', 'id', 'first_name', 'last_name', 'full_name', 'amount_due')
and in views.py
class ContactViewSet(viewsets.ModelViewSet):
serializer_class = ContactSerializer
permission_classes = (IsAuthenticated, AdminAuthenticationPermission,)
def get_queryset(self):
return Contact.objects.filter(user=self.request.user)
def perform_create(self, serializer):
serializer.save(user=self.request.user)
But there is no field as amount_due and url in the response returned while making the request to /contacts/ endpoint with GET method.
Based on your comment, you want the sum of all the amounts(please edit your question). so you should use annotate in your queryset:
from django.db.models import Sum
def get_queryset(self):
return Contact.objects.filter(user=self.request.user).annotate(amount_due=Sum('amountgiven_set__amount'))
(I recommend using modelManager for the queryset and the filtering instead of doing it here)
and add a field like this to your serializer:
amount_due = serializer.IntegerFiled()
Your modeling doesn't allow you to access amount_due in the way which you'd like.
Your Contact model does not have amountgiven attribute.
It does however have amountgiven_set which you can use to obtain a queryset of amountgiven for given contact.
But there can be multiple ones so you need to decide which amount_due you want to display in your serializer.
You can use SerializerMethodField to serializer the value of amount_due which you would like:
class ContactSerializer(serializers.HyperlinkedModelSerializer):
amount_due = serializers.SerializerMethodField()
def get_amount_due(self, obj):
amountgiven = obj.amountgiven_set.first()
return amountgiven.amount_due
But again, as i already mentioned - amountgiven_set returns a queryset where you can have multiple objects.
In case you are sure you have only one for given contact, you can use first() as in my example to get it.
I have three models like following:
from model_utils.managers import InheritanceManager
class Product(models.Model):
name = models.CharField(max_length=50, blank=False, verbose_name="Type here name",)
class Pub(Product):
product = models.OneToOneField(Product, parent_link=True, )
seating_capacity = models.IntegerField(null=False, verbose_name="Seating capacity of the Pub",)
class Restaurant(Product):
product = models.OneToOneField(Product, parent_link=True, )
food_speciality = MultiSelectField(choices = MY_CHOICES)
I have implemented django-cart and have attached Product as my product model. I return all the products to my frontend. This basically sends product objects with only product specific attributes to the frontend, and hence it is hard to distinguish which product is Pub and which one is Restaurant.
How can I handle this on backend itself? Is there any way to extract/send also the type of products?
This is how my view looks like:
#api_view(('GET',))
def show(request):
cart = Cart(request.session)
products = cart.products
serializer = ProductSerializer(products, many=True)
return Response(serializer.data)
It returns for example:
[{"id":1,"name":"Shreyas","price":"45000.00000","avg_user_rating":"4.50000","city":1},{"id":4,"name":"Phadake","price":"350.00000","avg_user_rating":"5.00000","city":2}]
Serializer:
class ProductSerializer(serializers.ModelSerializer):
category = serializers.ReadOnlyField()
class Meta:
model = Product
fields = '__all__'
Option 1
Add the Meta class with verbose_name to you models:
class Pub(Product):
product = models.OneToOneField(Product, parent_link=True, )
seating_capacity = models.IntegerField(null=False, verbose_name="Seating capacity of the Pub",)
class Meta:
verbose_name = 'Pub'
class Restaurant(Product):
product = models.OneToOneField(Product, parent_link=True, )
food_speciality = MultiSelectField(choices = MY_CHOICES)
class Meta:
verbose_name = 'Restaurant'
Add these lines to ProductSerializer:
category = serializers.SerializerMethodField()
def get_category(self, obj):
return obj._meta.verbose_name
Option 2
Alternatively, you can add a property to each model.
class Pub(Product):
product = models.OneToOneField(Product, parent_link=True, )
seating_capacity = models.IntegerField(null=False, verbose_name="Seating capacity of the Pub",)
#property
def category(self):
return 'Pub'
class Restaurant(Product):
product = models.OneToOneField(Product, parent_link=True, )
food_speciality = MultiSelectField(choices = MY_CHOICES)
#property
def category(self):
return 'Restaurant'
Then add this line to ProductSerializer:
category = serializers.ReadOnlyField()
Option 3
Of course, you also have the option do this if you don't want to add Meta or properties to your models:
def get_category(self, obj):
return obj.__class__.__name__
But then you would have the restriction that every category would be equal to the class's name, which might be a problem.