Inner join on Django with where clause? - django

I'm trying to filter out a ModelForm to only display dropdown values tied to a specific user.
I've got three tabled tied together:
User, Project, ProjectUser.
One user can have many projects, and one projects can have many users, and the table ProjectUser is just a joined table between User and Project, e.g
id | project_id | user_id
1 5 1
2 6 2
3 6 1
How would I write the following inner join query in Django ORM?
SELECT name
FROM projectuser
INNER JOIN project
ON projectuser.project_id = project.id
WHERE user_id = <request.user here>

When Django ORM applies a Filter to a field specified as a foreign key, Django ORM understands that it is a related table and joins it.
Project.objects.filter(projectuser__user=user)
You can join multiple tables, or even apply a filter to the reverse of a foreign key! You can use the related_name of the ForeignKey field appropriately.

You original SQL
SELECT name
FROM projectuser
INNER JOIN project
ON projectuser.project_id = project.id
WHERE user_id = <request.user here>
So as i see your SQL, you want to get list of name from projectuser for specific user. If so, here is the answer
ProjectUser.objects.filter(user_id = user).values_list('name', flat = True)
I see you accept answer with Project.objects.filter(projectuser__user=user)
For this answer your SQL should look like this
SELECT name
FROM project
INNER JOIN projectuser
ON projectuser.project_id = project.id
WHERE projectuser.user_id = <request.user here>

Related

Django and Postgres inserting into many-to-many table not working

I'm using python and psycopg2 to scrape some data and insert them into my database.
I've already created the movies and actors tables inside my Django models.py and inside my movies table, there is a many to many relationship between movies and actors table.
below is my models.py:
class Movie(models.Model):
name = models.CharField(max_length=55)
summary = models.TextField(max_length=1024)
actor = models.ManyToManyField(Actor, blank=True)
when I create a movie from Django-admin I select which actors are included in the movie and everything works fine and all the related actors for that movie will show up on my website.
But the problem is that when I try to insert scraped data into my database outside of my Django project folder, the related actors won't be shown on my website because obviously, I have not set the many to many relationship between them.
I have tried creating a junction table using SQL commands which gets the movie id and the actor's id and links them together but I don't know how I should tell Django to use that table and show the related actors for each movie.
This is the SQL code I use to insert into my db:
INSERT INTO movies(name, summary)
VALUES ('movie name', 'sth')
and the code to insert to actors table:
INSERT INTO actors(name, bio)
VALUES ('actorname', 'sth')
Both actors and movies table have auto generated id and I insert them insto the junction table using the code below:
INSERT INTO movie_actors (actor_id, movie_id)
VALUES (
(SELECT actor_id from actors where name='actor name'),
(SELECT movie_id from movie where name='movie name')
)
Am I doing it right?
I would really appreciate it if someone could help me with this.
Django automatically creates a table for many2many relationships. From docs:
ManyToManyField.through
Django will automatically generate a table to manage many-to-many relationships. However, if you want to manually specify the intermediary table, you can use the through option to specify the Django model that represents the intermediate table that you want to use.
The most common use for this option is when you want to associate extra data with a many-to-many relationship.
So you must find the name of the table that django had already created.
Secondly, I suggest that you use django's ORM instead of raw queries so you don't have these kind of problems anymore.
Django automatically creates a through table for M2M relations, if you need you can specify custom through table. In your case I think there is no need of custom through table.
I using Django ORM instead of writing raw query.
INSERT INTO movies(name, summary) VALUES ('movie name', 'sth')
instead of tis raw query you can use the following ORM query:
movie = Movie.objects.create(name="movie name", summary="movie sammuary")
This will create a movie entry in the Movie table.
Next to create user entry you can use the following query:
actor = Actor.objects.create(name="actor name", bio="actor bio")
Now you created the entries in both the table, next you can establish the realtion, for that you have to use the following query:
movie.actor.add(actor)
Incase if you want to add multiple actors at the same time, you create multiple actors object and use following query:
movie.actor.add(actor1, actor2, actor2)
For more details you can check django's offical documentation

Django ORM inner join with table sharing the same foreign key

I have three tables:
Table 1 : order
Fields: id, client_reference, price and status
(status is a foreignkey linked to order_status.id)
Table 2 : order_status
Fields: id, lastupdate
Table 3 : order_status_i18n
Fields: id, order_status_id, language and label
(order_status_id is also a foreignkey linked to order_status.id)
what I'd like to do is to get the order_status_i18n label instead of order_status_id based on the user language, so the SQL request would be like:
SELECT o.client_reference as reference, o_s_i18n.label
FROM
order AS o
INNER JOIN order_status_i18n AS o_s_i18n
ON o.status=o_s_i18n.order_status_id WHERE o_s_i18n.language='the language';
and the Model for order contains a foreignkey attribute linked to the order_status model but not order_status_i18n, so I tried to use
Order.objects.filter(some_filters).prefetch_related('status')
.filter(status__in=OrderStatusI18N.objects.filter(language='the_language'))
Which gives me a queryset conflict telling me that I should use the queryset for OrderStatus rather than OrderStatusI18N and I totally agree to this.
But anyway, what would be the good way to manage such a request using django ORM?
I think this should do the thing.
Order.objects.filter(some_filters).prefetch_related('status').annotate(i18_label=F('status__order_status_i18n__language')).filter(status__order_status_i18n__language='en')

Django 2 multi-table joins?

Python 3.6 / Django 2
I have a Profile model that defines and "extended" User model. I have a Group model that defines some internal, app related groups; and, a ProfileGroupLink model that defines the possible group(s) a profile might belong to.
Let's say the tables look like:
Profile
----------
id int
name_last varchar(32)
name_first varchar(32)
account_number uuid
Group
----------
id uuid
name varchar(32)
ProfileGroupLink
----------
id int
groupLnk uuid foreign key(Group.id)int
profileLnk uuid foreign key(Profile.account_number)
I have the active user "profile" from:
my_profile = Profile.objects.get(user=request.user)
I would normally write an SQL query something like:
select Group.name
from Group as Group
inner join Profile as Profile
on ? = Profile.id
inner join ProfileGroupLink as ProfileGroupLink
on Profile.id = ProfileGroupLink.profileLnk
and Group.id = ProfileGroupLink.groupLnk
order by Group.name
and pass the profile id in as a parameter. I am looking at the page: https://docs.djangoproject.com/en/2.0/topics/db/queries/ but I am not getting the double underscore syntax for joins.
My current attempt is:
groups = Group.objects.all() \
.filter(GroupList__profile=my_profile.account_number) \
.filter(GroupList__group=Group__group_id)
but PyCharm flags "Group__group_id" as an unresolved reference. I am importing the models. How do I do this in Django?
I'm going to assume that what you want to get the query is to obtain the groups that belong to a profile
groups = Group.objects.filte(profilegrouplink__profilelnk=my_profile)
note that I put everything in lowercase, do not need to put all the joins and do not use the models to make the filters, the attributes are used

Django how to get specific field with complex join in one-to-many relationship

I have a database consists of 3 tables
Project table
Section table
Task table
And the their relationships
Project table have one-to-many relation ship with Section table.
Section table have one-to-many relation ship with task table.
I have already queried a target task record by this code
task = tasks.objects.filter(section_id=sectionID)
Assume that I have sectionID and then I would like to get project.id from a task object, what should I do?
Thank you.
I guess you models look like that:
class Project(models.Model):
#...
class Section(models.Model):
#...
project = models.ForeignKey(Project)
class Task(models.Model):
#...
section = models.ForeignKey(Section)
So in order to get a task project you just have to follow the relation.
#when task is a single task object
project = task.section.project
Have in mind that this will make 2 queries (one to get the section and one to get the project) you can optimize this with select_related
tasks = Task.objects.filter(section_id=sectionID).select_related('section__project')
for task in tasks:
project = task.section.project
print project.id
ending with a single query.
I guess you can also want to know how to get a queryset of projects contained by some sectionID, in this case you can use the reverse relations like so:
projects = Project.objects.filter(section__id=sectionID)
You can reach it using task.section.project.id.

Finding multiple instances of a tag with Django "through" field

I run a lab annotation website where users can annotate samples with tags relating to disease, tissue type, etc. Here is a simple example from models.py:
from django.contrib.auth.models import User
from django.db import models
class Sample(models.Model):
name = models.CharField(max_length = 255)
tags=models.ManyToManyField('Tag', through = 'Annot')
class Tag(models.Model):
name = models.CharField(max_length = 255)
class Annot(models.Model):
tag = models.ForeignKey('Tag')
sample = models.ForeignKey('Sample')
user = models.ForeignKey(User, null = True)
date = models.DateField(auto_now_add = True)
I'm looking for a query in django's ORM which will return the tags in which two users agree on the annotation of same tag. It would be helpful if I could supply a list of users to limit my query (if someone only believes User1 and User2 and wants to find the sample/tag pairs that only they agree on.)
I think I understood what you need. This one made me think, thanks! :-)
I believe the equivalent SQL query would be something like:
select t.name, s.name, count(user_id) count_of_users
from yourapp_annot a, yourapp_tag t, yourapp_sample s
where a.tag_id = t.id
and s.id = a.sample_id
group by t.name, s.name
having count_of_users > 1
While I try hard not to think in SQL when I'm coming up with django model navigation (it tends to get in the way); when it comes to aggregation queries it always helps me to visualize what the SQL would be.
In django we now have aggregations.
Here is what I came up with:
models.Annot.objects.select_related().values(
'tag__name','sample__name').annotate(
count_of_users=Count('user__id')).filter(count_of_users__gt=1)
The result set will contain the tag, the sample, and the count of users that tagged said sample with said tag.
Breaking it apart for the folks that are not used to django aggregation:
models.Annot.objects.select_related()
select_related() is forcing all tables related to Annot to be retrieved in the same query
This is what will allow me to specify tag__name and sample__name in the values() call
values('tag__name','sample__name')
values() is limiting the fields to retrieve to tag.name and sample.name
This makes sure that my aggregation on count of clients will group by just these fields
annotate(count_of_users=Count('user__id'))
annotate() adds an aggregation as an extra field to a query
filter(count_of_users__gt=1)
And finally I filter on the aggregate count.
If you want to add an additional filter on what users should be taken into account, you need to do this:
models.Annot.objects.filter(user=[... list of users...]).select_related().values(
'tag__name','sample__name').annotate(
count_of_users=Count('user__id')).filter(count_of_users__gt=1)
I think that is it.
One thing... Notice that I used tag__name and sample__name in the query above. But your models do not specify that tag names and sample names are unique.
Should they be unique? Add a unique=True to the field definitions in the models.
Shouldn't they be unique? You need to replace tag__name and sample__name with tag__id and sample__id in the query above.