Using PostgreSQL Arrayfield as ManyToMany -like lookup? - python

I have two models:
class Authors(models.Model):
name = models.CharField()
class Books(models.Model):
title = models.CharField()
author_ids = ArrayField(Integerfield())
I would like to be able to look about the authors of the books by doing books.authors. Is there some way to do this? I can't seem to find a way to do this. This database is already prepopulated with the author_ids so reworking it to something else, like using an intermediary table, would be difficult.

You can use a subquery combined with id__in:
author_ids = Books.objects.first().values('author_ids')
authors = Authors.objects.filter(id__in=author_ids)
If you only evaluate authors, this will only hit the DB once. If you need to evaluate both the books and authors, then there is probably a better solution using a union.

Related

Look up objects that aren't the target of any foreign keys using Django ORM

I have two models, Author and Book. I need to get all Authors who have no Books with Django's ORM. How can I do that?
Here are my models:
class Author(models.Model):
name = models.CharField(max_length=30)
age = models.IntegerField()
class Book(models.Model):
name = models.CharField(max_length=500)
author = models.ForeignKey(Author, related_name='books')
I am currently doing it with for loop, but I would prefer to do it with the ORM:
for person in Author.objects.all():
if person.books.count() == 0:
"something code"
But it need to make update for objects.
I tried this but I just get an empty QuerySet:
Author.objects.select_related('books').values('id').annotate(books_count=Count('id')).filter(books_count=0)
.annotate(books_count=Count('id'))
Django will count the Author's ids here. Try
.annotate(books_count=Count('books__id'))
instead. The double underscore indicates a query that spans relationships:
Django offers a powerful and intuitive way to "follow" relationships in lookups, taking care of the SQL JOINs for you automatically, behind the scenes. To span a relationship, just use the field name of related fields across models, separated by double underscores, until you get to the field you want.
This example retrieves all Entry objects with a Blog whose name is 'Beatles Blog':
>>> Entry.objects.filter(blog__name='Beatles Blog')

How to combine django "prefetch_related" and "values" methods?

How can prefetch_related and values method be applied in combination?
Previously, I had the following code. Limiting fields in this query is required for performance optimization.
Organizations.objects.values('id','name').order_by('name')
Now, I need to prefetch its association and append it in the serializer using "prefetch_related" method.
Organizations.objects.prefetch_related('locations').order_by('name')
Here, I cannot seem to find a way to limit the fields after using "prefetch_related".
I have tried the following, but on doing so serializer does not see the associated "locations".
Organizations.objects.prefetch_related('locations').values("id", "name").order_by('name')
Model Skeleton:
class Organizations(models.Model):
name = models.CharField(max_length=40)
class Location(models.Model):
name = models.CharField(max_length=50)
organization = models.ForeignKey(Organizations, to_field="name", db_column="organization_name", related_name='locations')
class Meta:
db_table = u'locations'
Use only() to limit number of fields retrieved if you're concerned about your app performances. See reference.
In the example above, this would be:
Organizations.objects.prefetch_related('locations').only('id', 'name').order_by('name')
which would result in two queries:
SELECT id, name FROM organizations;
SELECT * from locations WHERE organization_name = <whatever is seen before>;

In Django, how do I join a table with a composite primary key to another table?

Here's what I have in the way of models:
class Lead(models.Model):
user = models.ForeignKey(User, related_name='leads')
site = models.ForeignKey(Site)
...
class UserDemographic(models.Model):
user = models.ForeignKey(User, related_name='user_demographic')
site = models.ForeignKey(Site)
...
class Meta:
unique_together = 'user', 'site'
In the first model, we record leads on a per-site, per-user basis. There can be multiple leads from the same user on a given site. In the second model, we store each user's demographic data. For each site, each use has only one record of demographic data.
What I would like to be able to do is tack this demographic data onto our leads query. Each lead has both user and site, and I want to grab the data in the demographic table and pair it to the corresponding lead. So basically what I need here is a left join that will unite these two. This is simple enough to do when there is only one foreign key, but I have no clue how to make it work when there are two foreign keys on which to join the tables.
Any ideas on this? Is there even a way to do this in Django, or will I have to resort to a raw query? Thanks!
Django's ORM doesn't let you do this natively, but you can minimise your raw sql by using the extra method. Something like this should work:
Lead.objects.extra(tables=['appname_userdemographic'],
where=['appname_userdemographic.user_id=appname_lead.user_id',
'appname_userdemographic.site_id=appname_lead.site_id'],
select={'country': 'appname_userdemographic.country'})
Alternately, you could refactor your models so you don't need the composite key - for example, create a UserSite model and link your lead and demographic models to that.
class UserSite(models.Model):
user = models.ForeignKey(User)
site = models.ForeignKey(Site)
class Lead(models.Model):
user_site = models.OneToOneField(UserSite)
...
class UserDemographic(models.Model):
user_site = models.OneToOneField(UserSite)
...
Then you can use select_related, like so:
Lead.objects.select_related('usersite__userdemographic')

Django: Referencing a manytomany field that has been defined in another model

I'm trying to work out how to calculate the number of books written by an author. The book model sets up the manytomany field with the author model. I have no idea how even google something like this which I thought would be very simple to do but I don't even know how to start.
Here's the stripped down code:
class Author(models.Model):
first_name = models.CharField()
last_name = models.CharField()
def books(self):
"""Return a list of comma separated list of books by the author"""
pass
class Book(models.Model):
title = models.CharField()
authors = models.ManyToManyField(Author)
Maybe I could do a filter query to get books by a particular author but it doesn't seem like the django way to do it? I'm also not sure how to pass the filter function a python object rather than text.
Thanks
when you define a foreignkey or manytomany field, django sets up a reverse relation for you, which in your case is book_set. so something like:
def books(self):
books = self.book_set.all()
return ', '.join([book.title for book in books])
see related objects in the docs

How to perform queries in Django following double-join relationships (or: How to get around Django's restrictions on ManyToMany "through" models?)

There must be a way to do this query through the ORM, but I'm not seeing it.
The Setup
Here's what I'm modelling: one Tenant can occupy multiple rooms and one User can own multiple rooms. So Rooms have an FK to Tenant and an FK to User. Rooms are also maintained by a (possibly distinct) User.
That is, I have these (simplified) models:
class Tenant(models.Model):
name = models.CharField(max_length=100)
class Room(models.Model):
owner = models.ForeignKey(User)
maintainer = models.ForeignKey(User)
tenant = models.ForeignKey(Tenant)
The Problem
Given a Tenant, I want the Users owning a room which they occupy.
The relevant SQL query would be:
SELECT auth_user.id, ...
FROM tenants_tenant, tenants_room, auth_user
WHERE tenants_tenant.id = tenants_room.tenant_id
AND tenants_room.owner_id = auth_user.id;
Getting any individual value off the related User objects can be done with, for example, my_tenant.rooms.values_list('owner__email', flat=True), but getting a full queryset of Users is tripping me up.
Normally one way to solve it would be to set up a ManyToMany field on my Tenant model pointing at User with TenantRoom as the 'through' model. That won't work in this case, though, because the TenantRoom model has a second (unrelated) ForeignKey to User(see "restictions"). Plus it seems like needless clutter on the Tenant model.
Doing my_tenant.rooms.values_list('user', flat=True) gets me close, but returns a ValuesListQuerySet of user IDs rather than a queryset of the actual User objects.
The Question
So: is there a way to get a queryset of the actual model instances, through the ORM, using just one query?
Edit
If there is, in fact, no way to do this directly in one query through the ORM, what is the best (some combination of most performant, most idiomatic, most readable, etc.) way to accomplish what I'm looking for? Here are the options I see:
Subselect
users = User.objects.filter(id__in=my_tenant.rooms.values_list('user'))
Subselect through Python (see Performance considerations for reasoning behind this)
user_ids = id__in=my_tenant.rooms.values_list('user')
users = User.objects.filter(id__in=list(user_ids))
Raw SQL:
User.objects.all("""SELECT auth_user.*
FROM tenants_tenant, tenants_room, auth_user
WHERE tenants_tenant.id = tenants_room.tenant_id
AND tenants_room.owner_id = auth_user.id""")
Others...?
The proper way to do this is with related_name:
class Tenant(models.Model):
name = models.CharField(max_length=100)
class Room(models.Model):
owner = models.ForeignKey(User, related_name='owns')
maintainer = models.ForeignKey(User, related_name='maintains')
tenant = models.ForeignKey(Tenant)
Then you can do this:
jrb = User.objects.create(username='jrb')
bill = User.objects.create(username='bill')
bob = models.Tenant.objects.create(name="Bob")
models.Room.objects.create(owner=jrb, maintainer=bill, tenant=bob)
User.objects.filter(owns__tenant=bob)

Categories

Resources