Django ORM: count a subset of related items - django

I am looking to find a way to annotate a queryset with the counts of a subset of related items. Below is a subset of my models:
class Person(models.Model):
Name = models.CharField(max_length = 255)
PracticeAttended = models.ManyToManyField('Practice',
through = 'PracticeRecord')
class Club(models.Model):
Name = models.CharField(max_length = 255)
Slug = models.SlugField()
Members = models.ManyToManyField('Person')
class PracticeRecord(PersonRecord):
Person = models.ForeignKey(Person)
Practice = models.ForeignKey(Practice)
class Practice(models.Model):
Club = models.ForeignKey(Club, default = None, null = True)
Date = models.DateField()
I'm looking to make a queryset which annotates the number of club specific practices attended by a person. I can already find the total number of practices by that person with a query of Person.objects.all().annotate(Count('PracticeRecord'))
However I would like someway to annotate the number of practices that a person attends for a specific club.
I would prefer something using the django ORM without having to resort to writing raw SQL.
Thanks.

However I would like someway to annotate the number of practices that a person attends for a specific club.
Let us see.
First, find the specific club.
club = Club.objects.get(**conditions)
Next, filter all Persons who have practiced at this club.
persons = Person.objects.filter(practicerecord__Practice__Club = club)
Now, annotate with the count.
q = persons.annotate(count = Count('practicerecord'))
Edit
I was able to successfully make this work in my test setup: Django 1.2.3, Python 2.6.4, Postgresql 8.4, Ubuntu Karmic.
PS: It is a Good Idea™ to use lower case names for the fields. This makes it far easier to use the double underscore (__) syntax to chain fields. For e.g. in your case Django automatically creates practicerecord for each Person. When you try to access other fields of PracticeRecord through this field you have to remember to use title case.
If you had used lower case names, you could have written:
persons = Person.objects.filter(practicerecord__practice__club = club)
# ^^ ^^
which looks far more uniform.
PPS: It is Count('practicerecord') (note the lower case).

I'm afraid that raw sql is the only option here. Anyway it's not that scary and hard to manage if you put it to model manager.

Related

How to set the range of the primary-key of django objects based on the foreign key range and is it advisable?

I have the below models:
class Author(model):
name = CharField(...)
class Post(mode):
author = ForeignKey(Author)
title = CharField(...)
Suppose we have at most 100 authors. So the primary key of the autors would be in the range of 1 to 100
I want the primary keys of the post model be based on the primary key of the author of the post.
I mean, if the author's primary key is 34, then his/her posts primary keys be 34000001, 34000002, 34000003
is advisable to do this and how can I do it?
This is generally not advisable and might tempt you to follow (or invent) antipatterns. If your goal is to be able to easily access a particular author's posts, for example, you would be safest using Django's normal ORM patterns. For example:
# models.py
class Author(model):
name = CharField(...)
class Post(model):
author = ForeignKey(Author, related_name='posts')
title = CharField(...)
Then anywhere you want, you can...
sally = Author.objects.get(id=1)
sallys_posts = author.posts.all()
This would be much safer than things you might otherwise be tempted to do such as Post.objects.filter(pk__startswith=sally.pk) which would be something that I think would lead to a large number of bugs down the line, and would also mean that you miss out on a lot of normal pattern Django ORM benefits.

Many to Many Exclude on Multiple Objects

I have the following models:
class Deal(models.Model):
date = models.DateTimeField(auto_now_add=True)
retailer = models.ForeignKey(Retailer, related_name='deals')
description = models.CharField(max_length=255)
...etc
class CustomerProfile(models.Model):
saved_deals = models.ManyToManyField(Deal, related_name='saved_by_customers', null=True, blank=True)
dismissed_deals = models.ManyToManyField(Deal, related_name='dismissed_by_customers', null=True, blank=True)
What I want to do is retrieve deals for a customer, but I don't want to include deals that they have dismissed.
I'm having trouble wrapping my head around the many-to-many relationship and am having no luck figuring out how to do this query. I'm assuming I should use an exclude on Deal.objects() but all the examples I see for exclude are excluding one item, not what amounts to multiple items.
When I naively tried just:
deals = Deal.objects.exclude(customer.saved_deals).all()
I get the error: "'ManyRelatedManager' object is not iterable"
If I say:
deals = Deal.objects.exclude(customer.saved_deals.all()).all()
I get "Too many values to unpack" (though I feel I should note there are only 5 deals and 2 customers in the database right now)
We (our client) presumes that he/she will have thousands of customers and tens of thousands of deals in the future, so I'd like to stay performance oriented as best I can. If this setup is incorrect, I'd love to know a better way.
Also, I am running django 1.5 as this is deployed on App Engine (using CloudSQL)
Where am I going wrong?
Suggest you use customer.saved_deals to get the list of deal ids to exclude (use values_list to quickly convert to a flat list).
This should save you excluding by a field in a joined table.
deals = Deals.exclude( id__in=customer.saved_deals.values_list('id', flat=True) )
You'd want to change this:
deals = Deal.objects.exclude(customer.saved_deals).all()
To something like this:
deals = Deal.objects.exclude(customer__id__in=[1,2,etc..]).all()
Basically, customer is the many-to-many foreign key, so you can't use it directly with an exclude.
Deals saved and deals dismissed are two fields describing almost same thing. There is also a risk too much columns may be used in database if these two field are allowed to store Null values. It's worth to consider remove dismissed_deals at all, and use saved_deal only with True or False statement.
Another thing to think about is move saved_deals out of CustomerProfile class to Deals class. Saved_deals are about Deals so it can prefer to live in Deals class.
class Deal(models.Model):
saved = models.BooleandField()
...
A real deal would have been made by one customer / buyer rather then few. A real customer can have milions of deals, so relating deals to customer would be good way.
class Deal(models.Model):
saved = models.BooleanField()
customer = models.ForeignKey(CustomerProfile)
....
What I want to do is retrieve deals for a customer, but I don't want to include deals that they have dismissed.
deals_for_customer = Deals.objects.all().filter(customer__name = "John")
There is double underscore between customer and name (customer__name), which let to filter model_name (customer is related to CustomerProfile which is model name) and name of field in that model (assuming CutomerProfile class has name attribute)
deals_saved = deals_for_customer.filter(saved = True)
That's it. I hope I could help. Let me know if not.

Embed product-variance logic into Django models

I wonder how I would model my Products model to auto-create (and that the admin-App would also understand it) variants of a Product based on it's variant-parts.
My Products have;
Colors
Sizes
and can probably get more features in the future.
How would I model my Product class to generate all variants of the Product?
Say I would create a new Product in Colors Red Blue Green and in Sizes XS S M L XL.
class Product(models.Model):
name = models.CharField(max_length=200)
class Color(models.Model):
product = models.ForeignKey(Product)
name = models.CharField(max_length=200)
class Size(models.Model):
product = models.ForeignKey(Product)
name = models.CharField(max_length=200)
class FutureVariant(models.Model):
product = models.ForeignKey(Product)
name = models.CharField(max_length=200)
# etc.
Now when I would need a smart method that when I would auto-create all color-size-[FUTURE VARIANT] for that product.
So I would tell Django;
Create new Product
In the colors Red Blue Green
In the sizes XS S M L XL
And the Product class would go and produce Products with all possible combinations in the products_product table.
I'm almost sure that this has design flaws. But I'm just curious how to put this logic in the ORM, and not to write weird procedural code, which would probably go against the DRY principal.
In Database logic I would think of something like this;
PRODUCTS
- id
- name
PRODUCTS_VARIANTS_COLORS
- id
- name
- html_code
PRODUCTS_VARIANTS_SIZES
- id
- name
PRODUCTS_VARIANTS_TABLES
- table_name
- table_id
PRODUCTS_VARIANTS
- product_id
- variant_table
- variant_id
This way I could make endless variant tables, as long as I would register them in my PRODUCTS_VARIANTS_TABLES and store their name as relevant. PRODUCTS_VARIANTS would hold all the the variants of the product, including combinations of them all. I am also aiming to have a selection-phase where the user can chose (in a HTML checkbox-list) which variants it does and doesn't want.
The problem (I think) is that this would not really comply with a logic in the ORM.
I don't know if you are asking about alternatives or just looking to make your way work, but what about splitting a product from it's attributes?
So instead of having separate models for attributes, you just have an Attribute model. This way you are future-proofing your database so you can easily add more attributes (like if you have products with a height and width instead of just color or size).
class AttributeBase(models.Model):
label = models.CharField(max_length=255) # e.g. color, size, shape, etc.
...
class Attribute(models.Model):
base = models.ForeignKey('AttributeBase', related_name='attributes')
value = models.CharField(max_length=255) # e.g. red, L, round, etc.
internal_value = models.CharField(max_length=255, null=True, blank=True) # other values you may need e.g. #ff0000, etc.
...
class ProductAttribute(Attribute):
product = models.ForeignKey('Product', related_name='attributes')
It now becomes very easy to create all attributes for a product...
class Product(models.Model):
...
def add_all_attributes(self):
for attribute in Attribute.objects.all():
self.attributes.add(attribute)
now when you use product.add_all_attributes() that product will contain every attribute. AND you can even make it add attributes of a certain AttributeBase
def add_all_attributes_for_base(self, label):
base = AttributeBase.objects.get(label=label)
for attribute in base.attributes.all():
self.attributes.add(attribute)
You could write something as:
class Product(models.Model):
#classmethod
def create_variants(cls):
# compute all possible combinations
combinations = ...
for combination in combinations:
Product.objects.create(**combination)
Creating all the combinations would indeed happen through registering the possible variants and their possible values.
Note that ORM is there to help you map Django objects to database records, it doesn't help you with producing the database records (read: Django models) that you wish to save.

Django: working with a multiple foreign key model

Django foreign keys are driving me crazy! I'm new to Django, and I've been working on a solution to what I know must be a very simple problem for over three weeks with no success. I've searched for the answers to my questions, but little has helped.
I have a model similar to the following to support each person's ability to have multiple phone numbers and addresses:
class Person(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
<...>
class Phone(models.Model):
person = models.ForeignKey(Person)
<...>
number = PhoneNumberField()
class Address(models.Model):
person = models.ForeignKey(Person)
<...>
zipcode = models.CharField(max_length=10)
I have two questions:
1) When joining Person, Phone, and Address is this the most efficient way?
person = Person.objects.get(pk=1)
phone = Phone.objects.get(person=person)
address = Address.objects.get(person=person)
2) When serializing this model to JSON I'm using Wad of Stuff Django Serializers version 1.1.0. The following code returns only Person data, yet I need Person and the related Phone and Address. What is wrong?
print serializers.serialize('json', Person.objects.all(), indent=4, relations=('phone', 'address',))
Thank you so much for any help you can give!
Edit: To clarify, I believe my inability to replicate the following using Django's ORM is at the root of my problems (or misunderstandings):
select * from person
left join phone
on phone.person_id = person.id
left join address
on address.person_id = person.id
where person.id = 1
1) No.
person = Person.objects.get(pk=1)
phones = person.phone_set.all()
addresses = person.address_set.all()
Also read the docs for select_related
1) You should be able to use this to get the person and his phones/addresses, since it is not a ManyToMany relationship:
person = Person.objects.get(id=1)
phones = Phone.objects.filter(person__id=person.id)
addresses = Address.objects.filter(person__id=person.id)
Most important here is that you don't want to use get() it will throw an error if more than one record is returned. Get() is for getting one single record, filter() is for multiple.
2) I'm not sure here, but you may need to use separate JSON dictionaries for your person, phones, and addresses. I am not familiar with Wad of Stuff, you may want to look at the source code to see what serializers.serialize() is expecting, and which arguments are defining what you get. Sorry, I can't be of help there.

Django Filter Return Many Values

I'm new to django and I think this is a simple question -
I have an intermediate class which is coded as follows -
class Link_Book_Course(models.Model):
book = models.ForeignKey(Book)
course = models.ForeignKey(Course)
image = models.CharField(max_length = 200, null=True)
rating = models.CharField(max_length = 200,null=True)
def __unicode__(self):
return self.title
def save(self):
self.date_created = datetime.now()
super(Link_Book_Course,self).save()
I'm making this call as I'd like to have to have all of the authors of the books (Book is another model with author as a CharField)
storeOfAuthorNames = Link_Book_Course.objects.filter(book__author)
However, it doesn't return a querySet of all of the authors, in fact, it throws an error.
I think it's because book__author has multiple values- how can I get all of them?
Thanks!
I don't think you're using the right queryset method. filter() filters by its arguments - so the expected usage is:
poe = Author.objects.get(name='Edgar Allen Poe')
course_books_by_poe = Link_Book_Course.objects.filter(book__author=poe)
It looks like you're trying to pull a list of the names all the authors of books used in a particular course (or maybe all courses?). Maybe you're looking for .values() or values_list()?
all_authors_in_courses = Link_Book_Course.objects.values_list(
'book__author', flat=True
).distinct()
(Edit: Updated per #ftartaggia's suggestion)
As others already explained, the use of filter method is to get a subset of the whole set of objects and does not return instances of other models (no matter if related objects or so)
If you want to have Author models instances back from django ORM and you can use aggregation APIs then you might want to do something like this:
from django.db.models import Count
Author.objects.annotate(num_books=Count('book')).filter(num_books__gt=1)
the filter method you are trying to use translates more or less into SQL like this:
SELECT * FROM Link_Book_Course INNER JOIN Book ON (...) WHERE Book.author = ;
So as you see your query has an incomplete where clause.
Anyway, it's not the query you are looking for.
What about something like (assuming author is a simple text field of Book and you want only authors of books referred from Link_Book_Course instances):
Book.objects.filter(pk__in=Link_Book_Course.objects.all().values_list("book", flat=True)).values_list("author", flat=True)
To start with, a filter statement filters on a field matching some pattern. So if Book has a simple ForeignKey to Author, you could have
storeOfAuthorNames = Link_Book_Course.objects.filter(book__author="Stephen King"), but not just
storeOfAuthorNames = Link_Book_Course.objects.filter(book__author).
Once you get past that, I am guessing Book has Author as a ManyToManyField, not a ForeignKey (because a book can have multiple authors, and an author can publish multiple books?) In that case, just filter(book__author="Stephen King") will still not be enough. Try Link_Book_Course.objects.filter(book_author__in=myBookObject.author.all())