Left outer join with Django - django

I have a person, which has a foreign key to User:
p = Personne.objects.get(user=self.request.user)
This person has "friends" through the model PersonneRelation where there's a src person and a dst person, so I'm retrieving all the person's friends like this:
friends = [a.dst.pk for a in PersonneRelation.objects.filter(src=p)]
I have a model Travel which embeds a description of a travel. I have also a model "activity" which is an activity the friend's current user have done (Activite). So here's the way I'm retrieving all the activities related to the current user:
context['activites'] = Activite.objects.filter(
Q(travel__personne__pk__in=friends) | Q(relation__src__pk__in=friends),
)
Everything works fine. I have also a model PersonneLiked where you precise if you liked an Activite. Thus this model has a foreign key to Activite.
class PersonneLiked(models.Model):
src = models.ForeignKey('Personne', related_name='liked_src')
dst = models.ForeignKey('Personne', related_name='liked_dst')
activite = models.ForeignKey('Activite', related_name='liked_activite')
# liked = thumb added *OR* removed :
liked = models.BooleanField(default=True)
What is the code I should do to retrieve all the PersonneLiked of context['activites']? It's like making an OUTER JOIN in SQL, but I dont know how I could do this with Django.

Since context['activities'] is a queryset of the appropriate Activite objects then you can do:
PersonneLiked.objects.filter(activite__in=context['activities'])
(Note that if this were a M2M relationship rather than a ForeignKey, this method would create duplicate entries in your final queryset.)

Related

Additional Foreign Key Query in Django

I am a newbie to django and was reading about select_related. I understand that whenever a foreign key is accessed django executes an additional query. But when I checked with DEBUG log in my code, it seems django executes two queries no matter if the foreign key is accessed or not. Can someone explain this behaviour ?
class Person(models.Model):
# ...
name = models.CharField(max_length=250)
class Book(models.Model):
# ...
author = models.ForeignKey(Person, on_delete=models.CASCADE)
As per doc
# Without select_related()...
b = Book.objects.get(id=4) # Executes a query.
p = b.author #Executes a query.
But with the get() it executes two queries
b = Book.objects.get(id=4) # Executes two queries (one for books one for author).
First of all, you need to call select select_related:
ids = [1,2,3,4]
query = Book.objects.filter(id__in=ids).select_related('author')
notice that I did that using the filter method and not the get method.
the reason is that select/prefetch related doesn't work with the get method.
if you still want only one object with select related you should do:
book = Book.objects.filter(id=4).select_related('author')[0]
author = book.author
or do:
book = Book.objects.select_related('author').get(id=4)
author = book.author
if you want to do it for multiple objects and get all the authors:
ids = [1,2,3,4]
query = Book.objects.filter(id__in=ids).select_related('author')
authors_in_query = [book.author for book in query]

Django Model field : Ordered List of Foreign Keys

I have a Route model which should store an ordered list of stops along that route. How should I go about modeling this relation?
class Stop(models.Model):
name = ..
latitude = ..
longitude = ..
class Route(models.Model):
stops_list = # Ordered list of stops on the route
Since there are many Stops along a Route, and stops could belong to multiple routes, I would use a ManyToMany to store this relationship. You may specify a through model to store data about the relationship, such as what time the route is expected to arrive at this stop. There are many options to add order information. One naive way would be to have an Integer order field as below, or you could store order implicity via arrival_time. If these routes do not change often, IntegerField is not a terrible implementation. However, if they do change often then you would need to update the fields.... not ideal.
class Stop(models.Model):
name = ..
latitude = ..
longitude = ..
class Route(models.Model):
stops_list = models.ManytoManyField(Stop, through='StopInfo') # Ordered list of stops on the route
class StopInfo(models.Model):
""" Model for storing data about the Stop/Route relationship """
stop = models.ForeignKey(Stop)
route = models.ForeignKey(Route)
arrival_time = models.DateTimeField(auto_now_add=True)
order = models.PositiveIntegerField()

I do not understand this error involving two objects with a many-to-many relation with one another

I am implementing a web interface for email lists. When a list administrator logs in, the site will visually display which lists they are an owner of and corresponding information about the lists. For this I have decided to have two tables:
1) An owner table which contains entries for information about list administrators. Each of these entries contains a 'ManyToManyField' which holds the information about which lists the owner in any given entry is an administrator for.
2) A list table which contains entries with information about each email list. Each entry contains the name of the list a 'ManyToManyField' holding information about which owners are administrators the list.
Here is the code in models.py:
from django.db import models
class ListEntry(models.Model):
name = models.CharField(max_length=64)
owners = models.ManyToManyField('OwnerEntry')
date = models.DateTimeField('date created')
class Meta:
ordering = ('name',)
class OwnerEntry(models.Model):
name = models.CharField(max_length=32)
lists = models.ManyToManyField('ListEntry')
class Meta:
ordering = ('name',)
I have already set up a simple local database to create a basic working website with. I have populated it with test entries using this code:
from list_app.models import *
from datetime import *
le1 = ListEntry(
name = "Physics 211 email list",
date = datetime.now(),
)
le1.save()
le2 = ListEntry(
name = "Physics 265 email list",
date = datetime(2014,1,1),
)
le2.save()
oe1 = OwnerEntry(
name = 'wasingej',
)
oe1.save()
oe1.lists.add(le1,le2)
le1.owners.add(oe1)
le2.owners.add(oe1)
oe2 = OwnerEntry(
name = 'doej',
)
oe2.save()
oe2.lists.add(le1)
le1.owners.add(oe2)
Here is where my error occurs: When the user has logged in via CAS, I have them redirected to this page in views.py:
def login_success(request):
u = OwnerEntry(name=request.user)
print(u.name)
print(u.lists)
return HttpResponse("login success!")
At the line 'print(u.lists)', I get the error "" needs to have a value for field "ownerentry" before this many-to-many relationship can be used.
What am I doing wrong here?
Your model structure is broken, for a start. You don't need ManyToManyFields on both sides of the relationship, only one - Django will provide the accessor for the reverse relationship.
Your issue is happening because you are not querying an existing instance from the database, you are instantiating an unsaved one. To query, you use model.objects.get():
u = OwnerEntry.objects.get(name=request.user.username)
You need to provide the actual class to the ManyToManyField constructor, not a string.
https://docs.djangoproject.com/en/dev/topics/db/examples/many_to_many/

Fetching ManyToMany objects from multiple objects through intermediate tables

Is there an easy way to fetch the ManyToMany objects from a query that returns more than one object? The way I am doing it now doesn't feel as sexy as I would like it to. Here is how I am doing it now in my view:
contacts = Contact.objects.all()
# Use Custom Manager Method to Fetch Each Contacts Phone Numbers
contacts = PhoneNumber.objects.inject(contacts)
My Models:
class PhoneNumber(models.Model):
number = models.CharField()
type = models.CharField()
# My Custom Manager
objects = PhoneNumberManager()
class Contact(models.Model):
name = models.CharField()
numbers = models.ManyToManyField(PhoneNumber, through='ContactPhoneNumbers')
class ContactPhoneNumbers(models.Model):
number = models.ForeignKey(PhoneNumber)
contact = models.ForeignKey(Contact)
ext = models.CharField()
My Custom Manager:
class PhoneNumberManager(models.Manager):
def inject(self, contacts):
contact_ids = ','.join([str(item.id) for item in contacts])
cursor = connection.cursor()
cursor.execute("""
SELECT l.contact_id, l.ext, p.number, p.type
FROM svcontact_contactphonenumbers l, svcontact_phonenumber p
WHERE p.id = l.number_id AND l.contact_id IN(%s)
""" % contact_ids)
result = {}
for row in cursor.fetchall():
id = str(row[0])
if not id in result:
result[id] = []
result[id].append({
'ext': row[1],
'number': row[2],
'type': row[3]
})
for contact in contacts:
id = str(contact.id)
if id in result:
contact.phonenumbers = result[id]
return contacts
There are a couple things you can do to find sexiness here :-)
Django does not have any OOTB way to inject the properties of the through table into your Contact instance. A M2M table with extra data is a SQL concept, so Django wouldn't try to fight the relations, nor guess what should happen in the event of namespace collision, etc... . In fact, I'd go so far as to say that you probably do not want to inject arbitrary model properties onto your Contact object... if you find yourself needing to do that, then it's probably a sign you should revise your model definition.
Instead, Django provides convenient ways to access the relation seamlessly, both in queries and for data retrieval, all the while preserving the integrity of the entities. In this case, you'll find that your Contact object offers a contactphonenumbers_set property that you can use to access the through data:
>>> c = Contact.objects.get(id=1)
>>> c.contactphonenumbers_set.all()
# Would produce a list of ContactPhoneNumbers objects for that contact
This means, in your case, to iterate of all contact phone numbers (for example) you would:
for contact in Contact.objects.all():
for phone in contact.contactphonenumbers_set.all():
print phone.number.number, phone.number.type, phone.ext
If you really, really, really want to do the injection for some reason, you'll see you can do that using the 3-line code sample immediately above: just change the print statements into assignment statements.
On a separate note, just for future reference, you could have written your inject function without SQL statements. In Django, the through table is itself a model, so you can query it directly:
def inject(self, contacts):
contact_phone_numbers = ContactPhoneNumbers.objects.\
filter(contact__in=contacts)
# And then do the result construction...
# - use contact_phone_number.number.phone to get the phone and ext
# - use contact_phone_number.contact to get the contact instance

django inner join query

I am working with django and having a hard time grasping how to do complex queries
Here is my model
class TankJournal(models.Model):
user = models.ForeignKey(User)
tank = models.ForeignKey(TankProfile)
ts = models.DateTimeField(auto_now=True)
title = models.CharField(max_length=50)
body = models.TextField()
class Meta:
ordering = ('-ts',)
get_latest_by = 'ts'
I need to pull the username given the tank object.
The user object is the one built into django.. thanks!
EDIT:
I have tried this
print User.objects.filter(tankjournal__tank__exact=id)
It seems to not pull out just the id.. and pull out everything in tankjournal and match it to the tank object
If you already have your tank object you should be able to do:
tank.user.username
To reduce the database queries you might want to consider the use of select_related(), e.g.
tanks = TankJournal.objects.all().select_related()
for tank in tanks:
username = tank.user.username
if you have a specific tank id then:
tank = TankJournal.objects.select_related().get(id=123456)
username = tank.user.username
I may be misunderstanding your question, but a request on User.objects.filter() will return a list of User objects, not User ids. What you've written looks technically correct.
Remember, though, that the model you have sets up a one-to-many between the TankProfile object and the TankJournal. In other words, a single TankProfile can be associated with more than one TankJournal, and therefore to more than one user. Given this, you're query is doing the right thing, returning more than one User.