I have a couple of Django models, Channel and Recording. Channels have a one-to-many relationship with recordings, such that a channel may have many recordings. Within the Channel model, I currently have 2 fields, num_recordings, and storage_size, which are right now created as Integers, but which I want to have dynamically generated as the result of a DB query. E.g. num_recordings should be the current count of recordings on a given channel, and storage_size should be the sum of a field "size" for each of the recordings on a given channel.
I'm using Django rest framework to give a JSON representation of the models, and want it so that when I query the Channel, I see these two fields as Integers, but don't want to have to calculate these separately, e.g. it would be ideal if when you query the Channel endpoint, it would perform a count on the recording relationship, and return that as "num_recordings" and a sum on the recording.size field for all recordings in the relationship and report that on the storage_size field.
How can I do this?
You can approach it by benefiting from the related_name attribute of ForeignKey fields in Django and standard Python #property decorator.
At first, let's assume that you have defined your relationship in models.py like this:
class Channel(models.Model):
...
class Recording(models.Model):
channel = models.ForeignKey(Channel, on_delete=..., related_name='recordings')
# ________________________________________________________________^
# This is the reverse relation name, e.g. channel.recordings
From now on, you can access the related recordings of a specific channel from the channel itself:
>>> channel = Channel.objects.create(...)
>>> recording1 = Recording.objects.create(channel=channel, ...)
>>> recording2 = Recording.objects.create(channel=channel, ...)
>>> channel.recordings.all()
<QuerySet [<Recording: 1>, <Recording: 2>]>
>>> channel.recordings.count()
2
You can use those methods in your Channel model directly:
class Channel(models.Model):
...
#property
def num_of_recordings(self):
return self.recordings.count()
# __________^ (related_name)
Now for your storage_size, you can aggregate it via the aggregate() method of QuerySet and the Sum aggregation function:
from django.db.models import Sum
class Channel(models.Model):
...
#property
def storage_size(self):
return self.recordings.aggregate(storage_size=Sum('size'))['storage_size']
And now the final Channel model:
class Channel(models.Model):
...
#property
def num_of_recordings(self):
return self.recordings.count()
#property
def storage_size(self):
return self.recordings.aggregate(storage_size=Sum('size'))['storage_size']
The last step is to add these new properties num_of_recordings and storage_size in your channel serializer class in order to display them. Since they are decorated with #property, they are read-only by design and thus are calculated dynamically based on the related recordings.
Related
I want to get the numbers of values in Two ManyToMany Field and compain both to one Integer Field by function (def)
class Video(models.Model):
viewers = models.ManyToManyField(Account, related_name='video_views')
viewers_by_ip = models.ManyToManyField(UsersByIP, default='192.168.0.1', blank=True)
viewers_count = models.IntegerField('Here I want to get the number of both field')
You many do it without saving sum of count viewers_by_ip and viewers into database, actually you have to ways.
Make a property method that will calculate it pet each object:
class Video(models.Model):
#property
def total_viewes_count(self):
return self.viewers.count() + self.viewers_by_ip.count()
Add a annotation to queryset which getting model object for you:
from django.db.models import Count
Video.objects.annotate(
total_viewes_count=Count('viewers')+Count('viewers_by_ip')
).get(pk=<Video's primary key>)
in both cases you may use it in template like that:
{{ video.total_viewes_count }}
I'm unable to find the difference between two dates in my form.
models.py:
class Testing(models.Model):
Planned_Start_Date = models.DateField()
Planned_End_Date = models.DateField()
Planned_Duration = models.IntegerField(default=Planned_Start_Date - Planned_End_Date)
difference between the date has to calculated and it should stored in the database but It doesn't works
default is a callable function that is just used on the class level, so you can't use it to do what you want. You should override the model's save() method (or better, implement a pre_save signal handler to populate the field just before the object is saved:
def save(self, **kwargs):
self.Planned_Duration = self.Planned_End_Date - self.Planned_Start_Date
super().save(**kwargs)
But why do you save a computed property to the database? This column is unnecessary. Both for querying (you can easily use computed queries on the start and end date) as for retrieving, you're wasting db space.
# if you need the duration just define a property
#property
def planned_duration(self):
return self.Planned_End_Date - self.Planned_Start_Date
# if you need to query tasks which last more than 2 days
Testing.objects.filter(Planned_End_Date__gt=F('Planned_Start_Date') + datetime.timedelta(days=2))
Note: Python conventions would recommend you name your fields using snake_case (planned_duration, planned_end_date, planned_start_date). Use CamelCase for classes (TestingTask). Don't mix the two.
I am working on creating a cocktail recipe app as a learning exercise.
I am trying to create a filter through Django's Rest Framework that accepts a string of ingredient IDs through a query parameter (?=ingredients_exclusive=1,3,4), and then searches for all recipes that have all of those ingredients. I would like to search for “All cocktails that have both rum and grenadine” and then also, separately “All cocktails that have rum, and all cocktails that have grendaine.”
The three models in my app are Recipes, RecipeIngredients, and IngredientTypes. Recipes (Old Fashioned) have multiple RecipeIngredients (2oz of Whiskey), and RecipeIngredients are all of a Ingredient Type (Whiskey). I will eventually change the RecipeIngredient to a through model depending on how far I decide to take this.
The list can be of a variable length, so I cannot just chain together filter functions. I have to loop through the list of ids and then build a Q().
However, I'm having some issues. Through the Django Shell, I have done this:
>>> x = Recipe.objects.all()
>>> q = Q(ingredients__ingredient_type=3) & Q(ingredients__ingredient_type=7)
>>> x.filter(q)
<QuerySet []>
>>> x.filter(ingredients__ingredient_type=3).filter(ingredients__ingredient_type=7)
<QuerySet [<Recipe: Rum and Tonic>]>
So here's my question: Why is the Q object that ANDs the two queries different than the chained filters of same object?
I've read through the "Complex lookups with Q objects" in the Django documentation and it doesn't seem to help.
Just for reference, here are my filters in Filters.py.
The "OR" version of this command is working properly:
class RecipeFilterSet(FilterSet):
ingredients_inclusive = django_filters.CharFilter(method='filter_by_ingredients_inclusive')
ingredients_exclusive = django_filters.CharFilter(method='filter_by_ingredients_exclusive')
def filter_by_ingredients_inclusive(self, queryset, name, value):
ingredients = value.split(',')
q_object = Q()
for ingredient in ingredients:
q_object |= Q(ingredients__ingredient_type=ingredient)
return queryset.filter(q_object).distinct()
def filter_by_ingredients_exclusive(self, queryset, name, value):
ingredients = value.split(',')
q_object = Q()
for ingredient in ingredients:
q_object &= Q(ingredients__ingredient_type=ingredient)
return queryset.filter(q_object).distinct()
class Meta:
model = Recipe
fields = ()
I've also included my models below:
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models
class IngredientType(models.Model):
name = models.CharField(max_length=256)
CATEGORY_CHOICES = (
('LIQUOR', 'Liquor'),
('SYRUP', 'Syrup'),
('MIXER', 'Mixer'),
)
category = models.CharField(
max_length=128, choices=CATEGORY_CHOICES, default='MIXER')
def __str__(self):
return self.name
class Recipe(models.Model):
name = models.CharField(max_length=256)
def __str__(self):
return self.name
class RecipeIngredient(models.Model):
ingredient_type = models.ForeignKey(IngredientType, on_delete=models.CASCADE, related_name="ingredients")
quantity = models.IntegerField(default=0)
quantity_type = models.CharField(max_length=256)
recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE, related_name="ingredients")
#property
def ingredient_type_name(self):
return self.ingredient_type.name
#property
def ingredient_type_category(self):
return self.ingredient_type.category
def __str__(self):
return f'{self.quantity}{self.quantity_type} of {self.ingredient_type}'
Any help would be very much appreciated!
The difference between the two approaches to filter() is described in Spanning multi-valued relationships:
Everything inside a single filter() call is applied simultaneously to filter out items matching all those requirements.... For multi-valued relations, they apply to any object linked to the primary model, not necessarily those objects that were selected by an earlier filter() call.
The example in the documentation makes it more clear. I'll rewrite it in terms of your problem:
To select all recipes that contain an ingredient with both type 3 and type 7, we would write:
Recipe.objects.filter(ingredients__ingredient_type=3, ingredients__ingredient_type=7)
That is of course impossible in your model, so this would return an empty queryset, just like your Q example with AND.
To select all recipes that contain an ingredient with type 3 as well as an ingredient with type 7, we would write:
Recipe.objects.filter(ingredients__ingredient_type=3).filter(ingredients__ingredient_type=7)
It's not especially intuitive, but they needed a way to distinguish these two cases and this is what they came up with.
Back to your problem, the OR case can be made simpler by using the in operator:
Recipe.objects.filter(ingredients__ingredient_type__in=[3, 7]).distinct()
The AND case is complicated because it's a condition that involves multiple rows. A simple approach would be to just take the OR version above and further process it in Python to find the subset that has all the ingredients.
A query approach that should work involves annotation with Count. This is untested, but something like:
Recipe.objects.annotate(num_ingredients=Count("ingredients",
filter=Q(ingredients__ingredient_type__in=[3, 7]))
.filter(num_ingredients=2)
Another approach to the AND case for Django 1.11+ would be to use the relatively new QuerySet intersection() method. As per the docs, this method:
Uses SQL’s INTERSECT operator to return the shared elements of two or more QuerySets.
So given an arbitrary list of IngredientType primary keys, you could create a filter() query for each pk (let's call these subqueries) and then spread that list (the * operator) into the intersection() method.
Like so:
# the base `QuerySet` and `IngredientType` pks to filter on
queryset = Recipe.objects.all()
ingredient_type_pks = [3, 7]
# build the list of subqueries
subqueries = []
for pk in ingredient_type_pks:
subqueries.append(queryset.filter(ingredients__ingredient_type__pk=pk))
# spread the subqueries into the `intersection` method
return queryset.intersection(*subqueries).distinct()
I added distinct() in there just to be safe and avoid duplicate results, but I am actually not certain whether it's necessary. Will have to test and update this post later.
I have room booking application with a Booking model that has a simple ForeignKey relationship to my Room model. Here's a super-brief idea of what I'm dealing with:
class Room(..):
floor = models.IntegerField()
number = models.IntegerField()
...
class Booking(..):
room = models.ForeignKey('Room')
...
I've been using django-import-export on the Booking model to let the admin take backups of this data in a way they can import into Excel. It's been really useful for quickly giving them data on demand without costing them my time.
My problem is that the room relationship is ignored; it pumps out the room_id instead of following the relationship. I understand how this makes sense for the sake of importing data but for all practical external purposes, the Room's primary_key is completely useless. People looking at Booking data need to know which floor and number Room it's for.
Is there a simple way to add extra data to the django-import-export's data to essentially denormalise those fields through?
See Getting started in documentation.
When defining ModelResource fields it is possible to follow model relationships:
class BookResource(resources.ModelResource):
class Meta:
model = Book
fields = ('author__name',)
In your case fields would be ('room__floor', 'room__number',)
It's actually not that painful! We need to override a couple of the ImportExportModelAdmin functions on our ModelAdmin to add the extra data to the query, and then pull it through.
class BookingAdmin(ImportExportModelAdmin):
...
def get_resource_class(self):
from import_export.fields import Field
rc = super(BookingAdmin, self).get_resource_class()
for f in ('room_floor', 'room_number'):
rc.fields[f] = Field(f, f, readonly=True)
return rc
def get_export_queryset(self, request):
rs = super(BookingAdmin, self).get_export_queryset(request)
return rs.annotate(
room_floor=F('room__floor'),
room_number=F('room__number')
)
No idea what happens on import though. I've set the fields to readonly so they should be ignored. Thankfully we're past the point where importing matters.
Is it possible to annotate with complex value?
Like if I have table
class Test(models.model):
value = models.PositiveIntegerField(_('value'))
next = 5
import math
Test.objects.annotate(new_field=math.sqrt(next-value)/math.atan(value))
No, you can't pass math functions into annotate().
If you want to do this calculation in Test model then create a method:
class Test(models.model):
value = models.PositiveIntegerField(_('value'))
def calc_value(self, next):
return math.sqrt(next-self.value)/math.atan(self.value))
for t in Test.objects.all():
print t.value. t.calc_value(5)
But if you want to use this calculation to order the queryset then you have to do the math at SQL level:
next = 5
Test.objects.extra(select={'new_field': 'SQRT(%d-value)/ATAN(value)' % next}) \
.order_by('new_field'))
To filter the queryset by new field use where argument of the same extra() method:
Test.objects.extra(select={'new_field': 'SQRT(%d-value)/ATAN(value)' % next},
where=['new_field > 10'])
SQLite doesn't support math functions by default but with Postgres and MySQL this code should work just fine.
No, annotations can only be done on django aggregations.
Annotates each object in the QuerySet with the provided list of aggregate values (averages, sums, etc) that have been computed over the objects that are related to the objects in the QuerySet.