Django: working with a multiple foreign key model - django

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.

Related

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.

Most efficient way to exclude questions asked by blocked users in Django app

I have a Q&A app that allows users to block/hide other users they find annoying or offensive. When a current_user views all questions – Question.objects.all() – I would like to exclude from the queryset all questions asked by users that have been blocked by current_user – Block.objects.filter(user_is_blocking=current_user)
What’s the most efficient way to do this? If it makes a difference to your answer, I’ll be applying the same exclusion list to other models, like Answers, that also include User as a FK.
Models:
class Question(models.Model):
user = models.ForeignKey(User)
question = models.CharField()
class Block(models.Model):
user_is_blocking = models.ForeignKey(User, related_name="user_blocking")
user_is_blocked = models.ForeignKey(User, related_name="user_blocked")
UPDATE:
I got this working thanks to sk1p. I then wanted to make the exclude bi-directional - so I only see questions from users I have not blocked AND who have not blocked me. I did that as follows for anyone interested in combining subqueries:
from itertools import chain
i_am_blocking = User.objects.filter(user_blocked__user_is_blocking=request.user)
is_blocking_me = User.objects.filter(user_blocking__user_is_blocked=request.user)
blocked_users = list(chain(i_am_blocking, is_blocking_me))
questions = Question.objects.exclude(user__in=blocked_users)
...
You can use the __in field lookup:
class Question(models.Model):
user = models.ForeignKey(User)
question = models.CharField()
class Block(models.Model):
user_blocking = models.ForeignKey(User, related_name="blocks_set")
user_blocked = models.ForeignKey(User, related_name="blocks_received")
blocked_users = User.objects.filter(blocks_received__user_blocking=current_user)
questions = Question.objects.exclude(user__in=blocked_users)
(I took the liberty of renaming your related names and fields to understand the problem better, you don't have to change your names of course...)
The __in lookup will be translated into a subquery, so the database will do most of the work.
The actual performance then depends on your database. The Django documentation warns about bad optimization by MySQL, in particular, so I'd recommend running the resulting query against a copy of your production database, and have a look at the query plan with EXPLAIN or EXPLAIN ANALYZE (on PostgreSQL).
If the subquery-based approach doesn't work well with your database, you can rewrite it as a raw SQL query and compare performance:
SELECT q.*
FROM app_question q,
auth_user author
LEFT JOIN app_block b ON (b.user_blocked_id = author.id)
LEFT JOIN auth_user ON (b.user_blocking_id = auth_user.id AND auth_user.id = %s)
WHERE author.id = q.user_id AND auth_user.id IS NULL;
Proof-of-concept SQLFiddle
The idea is to join the block table to the question's author, and if one exists, discard the question. The %s is the parameter for the current user's id; be sure to not use string formatting!

Database design under Django

I have a probably quite basic question: I am currently setting up a database for students and their marks in my courses. I currently have two main classes in my models.py: Student (containing their name, id, email address etc) and Course (containing an id, the year it is running in and the assessment information - for example "Essay" "40%" "Presentation" "10%" "Exam" "50%"). And, of course, Student has a ManyToMany field so that I can assign students to courses and vice versa. I have to be able to add and modify these things.
Now, obviously, I would like to be able to add the marks for the students in the different assignments (which are different from course to course). As I am very unexperienced in database programming, I was hoping one of you could give me a tip how to set this up within my models.
Thanks,
Tobi
Perhaps the way to go about it is to have a separate class for assignment, something like this.
class Assignment(models.Model):
ASSIGNMENT_TYPES = (
('essay', "Essay"),
...
)
ASSIGNMENT_GRADES = (
('a+', "A+"),
('a', "A"),
...
)
student = models.ForeignKey("Student")
course = models.ForeignKey("Course")
assignment_type = models.CharField(choices=ASSIGNMENT_TYPES, max_length=15, default='essay')
progress = models.IntegerField()
grade = models.CharField(choices=ASSIGNMENT_GRADES, max_length=3, default="a+")
This way you have one assignment connected to one student and one course. It can be modified relatively easy if you have multiple students per one assignment, by adding another class (for example StudentGroup) and including it in the model.
Hope that this helps :)
Create a model called "Assessments", which has a foreign key to Course. In addition ,create a field called "Assessment Type", another called "Assessment Result" and a final one called "Assesment Date". Should look like this:
ASSESSMENTS = (('E','Essay'),('P','Presentation'))
class Assessment(models.MOdel):
course = models.ForeignKey('Course')
assessment = models.CharField(choices=ASESSMENTS)
result = models.CharField(max_length=250)
taken_on = models.DateField()
overall_result = models.BooleanField()
is_complete = models.BooleanField()
Each time there is an exam, you fill in a record in this table for each assessment taken. You can use the overall result as a flag to see if the student has passed or failed, and the is_complete to see if there are any exams pending for a course.
You should look at models.py file of classcomm,
a content management system written in Django for delivering and managing Courses on the Web.
It has following Models
Department
Course
Instructor
Mentor
Enrollment
Assignment
DueDateOverride
Submission
Grade
ExtraCredit
Information
Resource
Announcement
You may not need such a complex relationship for you case, but it's wort looking into it's models design.
You can find more details on homepage of this project.

Figuring out how to design my model and using "through"

I'm trying to figure out how to design my model. I've been going over the documentation, and it ultimately seems like I should be using the "through" attribute, but I just can't figure out how to get it to work how I want.
If someone could take a look and point out what I'm missing, that would be really helpful. I have pasted my model below.
This is what I am trying to do:
1) Have a list of server types
2) Each server type will need to have different parts available to that specific server type
3) The asset has a FK to the servermodel, which has a M2M to the parts specific to that server type.
My question is, how can each "Asset" store meta data for each "Part" specific to that "Asset"? For example, each "Asset" should have it's own last_used data for the part that's assigned to it.
Thanks! :)
class Part(models.Model):
part_description = models.CharField(max_length=30,unique=1)
last_used = models.CharField(max_length=30)
def __unicode__(self):
return self.part_description
class ServerModel(models.Model):
server_model = models.CharField(max_length=30,unique=1)
parts = models.ManyToManyField(Part)
def __unicode__(self):
return self.server_model
class Asset(models.Model):
server_model = models.ForeignKey(ServerModel)
serial_number = models.CharField(max_length=10,unique=1)
def __unicode__(self):
return self.server_model.server_model
EDIT:
Thank you for the help!
I may have not explained myself clearly, though. It's probably my confusing model names.
Example:
ServerModel stores the type of server being used, say "Dell Server 2000".
The "Dell Server 2000" should be assigned specific parts:
"RAM"
"HARD DISK"
"CDROM"
Then, I should be able to create 10x Assets with a FK to the ServerModel. Now, each of these assets should be able to mark when the "RAM" part was last used for this specific asset.
I'm not sure I exactly understand what you want to do, but basically you can solve that with a "through" model, as you expected:
import datetime
class Part(models.Model):
name = models.CharField(max_length=30,unique=1)
class ServerModel(models.Model):
server_model = models.CharField(max_length=30,unique=1)
parts = models.ManyToManyField(Part,through='Asset')
class Asset(models.Model):
server_model = models.ForeignKey(ServerModel)
part = models.ForeignKey(Part)
serial_number = models.CharField(max_length=10,unique=1)
used = models.DateTimeField(default=datetime.datetime.now())
First thing to notice is the relation of the parts to the servermodel using the "through"-model: that way for each Part instance assigned to the "parts"-property of a ServerModel instance a new Asset instance is created (Phew - hope that doesn't sound too complicated). At the time of creation the "used"-property of the Asset instance is set to the current date and time (thats what default=datetime.datetime.now() does).
If you do that, you can then just query the database for the last asset containing your part. That queryset can then be sorted by the "used" property of the Asset model, which is the date when the Asset instance has been created.
ServerModel.objects.filter(parts__name='ThePartYouAreLookingFor').order_by('asset__used')
I'm not absolutely sure if the queryset is correct, so if someone finds huge nonsense in it, feel free to edit ;)
edit:
The models above do not exactly that. But you do not even need a through model for what you want:
class ServerModel(models.Model):
server_model = models.CharField(max_length=30,unique=1)
parts = models.ManyToManyField(Part)
class Asset(models.Model):
server_model = models.ForeignKey(ServerModel)
parts = models.ForeignKey(Part)
serial_number = models.CharField(max_length=10,unique=1)
used = models.DateTimeField(default=datetime.datetime.now())
Basically you can just add assets and then query all assets that have a RAM in parts.
Asset.objects.filter(parts__contains='RAM').order_by('used')
Get the date of the first (or last) result of that queryset and you have the date of the last usage of your 'RAM'-part.

Django ORM: count a subset of related items

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.