Django Foreign Key QuerySet (join) - python

So I'm learning Django and trying to get values throught foreign key by using Django ORM functions only. I've got 2 tables: User Table (default Django auth_user system table) and Student Table containing student_id, user_id(FK) and nr_indeksu (it's a number containing additional info for student specific information).
class Student(models.Model):
user = models.ForeignKey(User)
nr_indeksu = models.BigIntegerField()
def __unicode__(self):
return unicode(self.user
I want to fetch nr_indeksu through User model. In other words execute this query (using QuerySet):
SELECT nr_indeksu FROM auth_user
INNER JOIN courses_student on auth_user.id = courses_student.user_id;
I have tried using select_related() function:
u = User.objects.select_related().get(pk=2)
but when i try to access nr_indeksu:
u.nr_indeksu
i got error that User object has no attribute (it makes sense because nr_indeksu is in Student model but shouldn't i be able to fetch it from User?)

Remember a ForeignKey is a one-to-many relation: there are many Users for each Student. From a user object, you can access the related Students by doing user_obj.student_set.all().

Adding to Daniel's answer, you can also use related_names , right now when you have to do a reverse foreign-key lookup, you need to write :-
user_obj.student_set.all()
However, with related_name attribute, the model would look like :-
class Student(models.Model):
user = models.ForeignKey(User, related_name='studentuser')
nr_indeksu = models.BigIntegerField()
def __unicode__(self):
return unicode(self.user
then for a reverse lookup, you need to write :-
user_obj.studentuser.all()
Basically, all I mean to say is, you can supply own names for reverse lookup by passing related_name attribute, if in case you don't want to use the default name used by django which is generally of the form <foreign_key_name>_set eg. in your case it is student_set.

s = Student.objects.get(user__id=2)
s.nr_indeksu

Related

How to see all possible query sets of a model in django

I have two models like below in django
class User(AbstractBaseUser, PermissionsMixin):
first_name = models.CharField(....)
last_name = models.CharField(_(....)
email = models.EmailField(...)
class VcsToken(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
as you can see VcsToken is related to User, and a user can have many VcsToken,
So how do I get all the VcsTokens of a User.
Similarly I have many one to many relationships from user to other models, so How do I know their reference name? (I know its a set but how do I know the set name? ) Is there any way to list the query set names for a model.
What you are looking for is the feature for walking backward on the foreign key relationships. This is covered here in the docs: https://docs.djangoproject.com/en/3.0/topics/db/queries/#following-relationships-backward
In your example you should be able to access VcsToken from User like this:
user = User.objects.get(pk=1) # let's get an example user
user.vcstoken_set.all() # returns all related VcsToken objects.
Optionally, when the ForeignKey is defined, you can specify a related_name argument that would be used by django for this purpose. For example:
class User(AbstractBaseUser, PermissionsMixin):
...
email = models.EmailField(...)
class VcsToken(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='tokens')
If such an argument is specified, then that is the reverse lookup name used by django and you would need to do user.tokens.all() to access them. When no such argument is specified a default name is used by django that ends with "_set".
Hope this helps, let me know if anything needs to be clarified.
For a given User object myuser, you can access this with:
myuser.vcstoken_set.all()
so How do I know their reference name?
This is the value for the related_name=… parameter [Django-doc] if there is no such parameter in the ForeignKey construction, it will default to modelname_set, with the modelname in lowercase.
Is there any way to list the query set names for a model.
You can access all ManyToOneFields with:
django.db.models.fields.reverse_related.ManyToOneRel
[f.get_accessor_name() for f in User._meta.get_fields() if isinstance(f, ManyToOneRel)]
This will construct a list of the names of ForeignKeys in reverse.

Django ORM - queryset order by filtered foreign key relationship

Question: Can a filtered left join or a join on a subquery (very simple in postgres) be accomplished with the Django ORM/queryset functions?
Models:
class User(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=256)
class DashboardItem(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=256)
class DashboardItemUserData(models.Model):
id = models.AutoField(primary_key=True)
user = models.ForeignKey(User)
dashboard_item = models.ForeignKey(DashboardItem)
is_favorite = BooleanField()
The DashboardItemUserData stores per-user information as to whether the user has "favorited" the dashboard item. I need to be able to sort DashboardItem's based on the current requesting user. However, I need the join to be filtered since the ordering should respect the current user regardless of if other users have "favorited" the item or not.
Query I would think would work (or similar):
current_user = request.user
DashboardItem.objects.annotate(is_favorite=DashboardItemUserData.objects.filter(user=current_user).values('is_favorite')).order_by('is_favorite','name')
Here's what I would do in SQL:
postgres query using where on join
postgres query using subquery
DashboardItemUserData.objects.filter(user_id=1).\
annotate(name=dashboard_item__name).\
order_by('is_favorite', 'name')
Subquery would make sense if this was a LEFT join. Or if it contained TOP/LIMIT.
Hate to answer my own first question...but will leave this here in case anyone else comes across it. I've found two approaches that will work here:
If we can guarantee a database record for each user in the DashboardItemUserData table, we can simply use "filter()" to create an inner join. Since a user will have a record for all DashboardItem's, an inner join won't exclude any.
current_user = request.user
DashboardItem.objects.filter(dashboarditemuserdata__user=current_user).order_by('is_favorite', 'name')
If we cannot guarantee a record for each user in DashboardItemUserData, we can use the Django ORM's FilteredRelation feature. This will create a left outer join where we first filter the DashboardItemUserData table to only the user in question (so we get the right user data but don't blow up the left join).
current_user = request.user
DashboardItem.objects.annotate(user_item_data=FilteredRelation('dashboarditemuserdata', condition=Q(dashboarditemuserdata__user=current_user))).order_by('user_item_data__is_favorite', 'name')

Django Two Model Fields, same DB Column

We are trying to work with legacy DB Tables that were generated outside of Django and are not structured in an ideal way. We also can not modify the existing tables.
The DB uses the same user ID (pk) across all the tables, wether or not there is a record for that user ID. It also uses that ID as a PK on the other tables, rather than rely on them to auto increment their own IDs.
So imagine something like this below:
class Items(models.Model):
user_id = models.ForeignKey('User', db_column='UserID')
class User(models.Model):
user_id = models.IntegerField(primary_key=True)
class UserTypeA(models.Model):
user_id = models.IntegerField(primary_key=True) # Same Value as User
class UserTypeB(models.Model):
user_id = models.IntegerField(primary_key=True) # Same Value as User
What we thought of creating a relationship between Items and UserTypeA (as well as UserTypeB) is to create another field entry that uses the same column as the user_id.
class Items(models.Model):
user_id = models.ForeignKey('User', db_column='UserID')
user_type_a = models.ForeignKey('UserTypeA', db_column='UserID')
user_type_b = models.ForeignKey('UserTypeB', db_column='UserID')
This unfortunately returns a "db_column is already used" type error.
Any thoughts on how to better approach the way what we're trying to do?
A detail to note is that we're only ever reading from this databases (no updates to), so a read-only solution is fine.
Thanks,
-RB
I've solved a similar problem with this (this code should be put before the definition of your Model):
from django.db.models.signals import class_prepared
def remove_field(sender, **kwargs):
if sender.__name__ == "MyModel":
sender._meta.local_fields.remove(sender.myFKField.field)
class_prepared.connect(remove_field)
(Tested in Django 1.5.11)
Django uses local_fields to make the CREATE TABLE query.
So, I've just attached the signal class_prepared and check if sender equals the class I was expecting. If so, I've removed the field from that list.
After doing that, the CREATE TABLE query didn't include the field with same db_column and the error did not ocurr.
However the Model still working properly (with manager methods properly populating the removed field from local_fields), I can't tell the real impact of that.

Two way table and django ORM

Consider two django models 'User' and 'BoardGame', the latter has a ManyToMany field 'vote' defined with a custom through table:
class Vote(models.Model):
user = models.ForeignKey(User)
boardgame = models.ForeignKey(BoardGame)
vote = models.IntegerField()
I need to print a two way table having users names on the top, boardgames names on the left column and votes in the middle.
Is there a way to obtain this using django? (Remember that a user might not have voted every single boardgame.)
UPDATE: MORE DETAILS
1) Clearly this can be work out using some lines of python (which probably would result in many queries to the database), but I'm more interested in discovering if there is something directly implemented in django that could do the work. After all a ManyToMany field is nothing but a two way table (in this case with some data associated).
2) A possible 'solution' would be a FULL OUTER JOIN using a raw query, but, again, I am looking for something built-in inside django.
3) More specifically I'm using Class Based View and I was wondering if there exists an appropriate query to associate to queryset parameter of ListView.
Assuming:
class User(models.Model):
...
class BoardGame(models.Model):
users = models.ManyToManyField(User, through='Vote')
...
class Vote(models.Model):
user = models.ForeignKey(User)
boardgame = models.ForeignKey(BoardGame)
vote = models.IntegerField()
would work like this:
from django.db import connections, reset_queries
reset_queries()
users = User.objects.all().prefetch_related('vote_set')
table = []
table.append([''] + list(users))
for board_game in BoardGame.objects.all().prefetch_related('vote_set'):
row = [board_game]
for user in users:
for vote in user.vote_set.all():
if vote in board_game.vote_set.all():
row.append(vote)
break
else:
row.append('')
table.append(row)
len(connection.queries) # Returns always 4
This is not the solution you wanted, but it shows a way to get the table from the database with only 4 queries no matter how many objects you have.
I don't think there is anything in the Django core or generic Class Based Views that will render tables for you, but try django-tables2

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