chaining queries together in Django - python

I have a query that gets me 32 avatar images from my avatar application:
newUserAv = Avatar.objects.filter(valid=True)[:32]
I'd like to combine this with a query to django's Auth user model, so I can get the last the last 32 people, who have avatar images, sorted by the date joined.
What is the best way to chain these two together?
The avatar application was a reusable app, and its model is:
image = models.ImageField(upload_to="avatars/%Y/%b/%d", storage=storage)
user = models.ForeignKey(User)
date = models.DateTimeField(auto_now_add=True)
valid = models.BooleanField()
Note that the date field, is the date the avatar is updated, so not suitable for my purporse

Either you put a field in your own User class (you might have to subclass User or compose with django.contrib.auth.models.User) that indicates that the User has an avatar. Than you can make your query easily.
Or do something like that:
from django.utils.itercompat import groupby
avatars = Avatar.objects.select_related("user").filter(valid=True).order_by("-user__date_joined")[:32]
grouped_users = groupby(avatars, lambda x: x.user)
user_list = []
for user, avatar_list in grouped_users:
user.avatar = list(avatar_list)[0]
user_list.append(user)
# user_list is now what you asked for in the first_place:
# a list of users with their avatars
This assumes that one user has one and only one avatar. Your model allows for more than one avatar per user so you have to watch out not to store more than one.
Explanation of Code Snippet:
The avatars of the most 32 recent joined users are requested together with the related user, so there doesn't have to be a database query for any of them in the upcoming code.
The list of avatars is then grouped with the user as a key. The list gets all items from the generator avatar_list and the first item (there should only be one) is assigned to user.avatar
Note that this is not necessary, you could always do something like:
for avatar in avatars:
user = avatar.user
But it might feel more naturally to access the avatars by user.avatar.

It's not possible to combine queries on two different base models. Django won't let you do this (it'll throw an error telling you exactly that).
However, if you have a foreignkey from one model to the other, then adding select_related() to your query will fetch the related objects into memory in a single DB query so that you can access them without going back to the DB.

Related

Django .order_by() related field returns too many items

I'm trying to return a list of users that have recently made a post, but the order_by method makes it return too many items.
there is only 2 accounts total, but when I call
test = Account.objects.all().order_by('-posts__timestamp')
[print(i) for i in test]
it will return the author of every post instance, and its duplicates. Not just the two account instances.
test#test.example
test#test.example
test#test.example
test#test.example
foo#bar.example
Any help?
class Account(AbstractBaseUser):
...
class Posts(models.Model):
author = models.ForeignKey('accounts.Account',on_delete=models.RESTRICT, related_name="posts")
timestamp = models.DateTimeField(auto_now_add=True)
title = ...
content = ...
This is totally normal. You should understand how is the SQL query generated.
Yours should look something like that:
select *
from accounts
left join post on post.account_id = account.id
order by post.timestamp
You are effectively selecting every post with its related users. It is normal that you have some duplicated users.
What you could do is ensure that your are selecting distinct users: Account.objects.order_by('-posts__timestamp').distinct('pk')
What I would do is cache this information in the account (directly on the acount model or in another model that has a 1-to-1 relashionship with your users.
Adding a last_post_date to your Account model would allow you to have a less heavy request.
Updating the Account.last_post_date every time a Post is created can be a little tedious, but you can abstract this by using django models signals.

Django filtering: from a list of IDs

I'm using Django Rest Framework.
The model class is
class MyModel(models.Model):
id = models.CharField(max_length=200)
name = models.CharField(max_length=200)
genre = models.CharField(max_length=200)
And what I have set up so far is that, when the user does a POST request, the backend will take the request data and run a python script (which takes some parameters from the request data) which will in turn return a list of IDs corresponding to the "id" in MyModel. But the problem is, let's say I want to return only the ids that point to the model instances with genre "forensic", how do I do that?
I don't really have a clue how to do that, apart from doing a query on each id returned by the python script and filtering out the ones I want based on the genre returned from the query?
Maybe you can try like this:
MyModel.objects.filter(id__in=IDS, genre='forensic').values_list('id', flat=True) # assuming IDS come from the script

Django 1.11 - nested OuterRef usage

I recently updated Django to the bleeding-edge version 1.11rc1 because of the Subquery feature that was introduced there.
Now, let's say this is my use case: I have following models - Users, Groups and Permissions. So, I have some Users whom I can group (e.g. Administrators group) and Permissions - which are lists of users that can do some things (e.g. I have User A, User B and Administrators who can create new users). What I want to do now is display all of the Permissions with a number of users inside them efficiently. So in other words, I want to make a QuerySet which would return all the information about the Permissions and calculate the number of the users for each Permission. The first, obvious way to work-around this would be to create a get_user_count method for the Permission model which would return all users from my ManyToMany relationships, but that would require at least 1 additional query per Permission, which is unacceptable for me, as I'm planning to have a lot of Permissions. This is where I want to use Subquery.
So, to clarify things up - this is models.py:
class User(models.Model):
name = models.CharField(max_length=20)
class Group(models.Model):
users = models.ManyToManyField(User)
class Permission(models.Model):
users = models.ManyToManyField(User)
groups = models.ManyToManyField(Group)
And I want to create queryset that will return all Permissions with a number of users inside. For the sake of example, let's say I only want to include Users that belong to my groups - so I'd have something like this:
groups = Group.objects.filter(permission=OuterRef('pk'))
users = User.objects.filter(group__in=groups)
queryset = Permission.objects.annotate(
user_no=Subquery(users.annotate(c=Count('*')).values('c'))
)
The problem here is that my OuterRef cannot be resolved as used in "subquery's filter's filter":
This queryset contains a reference to an outer query and may only be used in a subquery.
Although, when I use another subquery to fetch the groups:
groups = Group.objects.filter(permission=OuterRef(OuterRef('pk')))
users = User.objects.filter(group__in=Subquery(groups))
queryset = Permission.objects.annotate(
user_no=Subquery(users.annotate(c=Count('*')).values('c'))
)
I get an error right in the first line:
int() argument must be a string, a bytes-like object or a number, not 'OuterRef'
The rest of the lines do not matter and have no influence on the error. The weird thing is, the exact same syntax appears in the documentation: https://docs.djangoproject.com/en/dev/ref/models/expressions/#django.db.models.OuterRef
The question is: what do I do incorrectly? Or how to achieve what I want in other way (although efficiently)?
Well, it's a bug in Django: https://github.com/django/django/pull/9529
I fixed it by excluding double-depth (OuterRef(OuterRef('pk'))) using annotations:
return self.annotate(category=Subquery(
# this is the "inner" subquery which is now just an annotated variable `category`
Category.objects.filter(offer=OuterRef('pk'))[:1].values('pk')
)).annotate(fs=Subquery(
# this is the "outer" subquery; instead of using subquery, I just use annotated `category` variable
Category.objects.filter(pk=OuterRef('category')).values('slug')
))
Hope it helps :)

Add models to specific user (Django)

Good evening,
I am working on some little website for fun and want users to be able to add items to their accounts. What I am struggling with is coming up with a proper solution how to implement this properly.
I thought about adding the User Object itself to the item's model via ForeignKey but wouldn't it be necessary to filter through all entries in the end to find the elements attached to x user? While this would work, it seems quite inefficient, especially when the database has grown to some point. What would be a better solution?
From what I understand of your use case, a User can have many items and and an Item can belong to multiple users. It this s the case, using ManyToManyField seems the way to go :
class Item(models.Model):
users = models.ManyToManyField('auth.User', related_name='items')
You can then query items from a specific user like this:
# adding an item to a user
user.items.add(my_item)
# query user items
user.items.all()
user.items.filter(name__startswith='Hello')
If you want to store additional information about the relationship, such as the date were the item was linked to the user, you have to specifiy an explicit intermediate model:
class Item(models.Model):
users = models.ManyToManyField('auth.User', through='ItemUser', related_name='users')
class ItemUser(models.Model):
"""Explicit intermediary model"""
user = models.ForeignKey('auth.User')
item = models.ForeignKey(Item)
date_added = models.DateTimeField(auto_now_add=True)
To create the binding beetween a User and an Item, just instanciate the intermediate model:
binding = ItemUser(user=user, item=item)
binding.save()
assert user in item.users.all()
You could create a model UserItems for each user with a ForeignKey pointing to the user and an item ID pointing to items. The UserItems model should store the unique item IDs of the items that belong to a user. This should scale better if items can be attached to multiple users or if items can exist that aren't attached to any user yet.

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