Table join using the Django ORM - python

I'm trying to figure out the Django ORM and I'm getting a little confused on the equivalent of a table join.
Assume I have the three models below, abbreviated for readability. "User" is the out-of-the-box Django user model, "AppUser" is a corresponding model for storing additional profile info like zip code, and "Group" is a collection of users (note that this group has nothing to do with authentication groups). Each AppUser has a one-to-one relationship with each User. Similarly, each AppUser also points to the Group of which that user is member (which could be None).
My task: given a group_id, generate a list of all the member emails.
I know that I can "traverse backward one-level" from Group to AppUser using .appuser_set.all() but I don't know how to go further to fetch the related User and its email without iterating through and doing it in a very DB heavy way like this:
appUser = AppUser.objects.get(user_id=request.user.id)
group = appUser.group
appUsers = group.appuser_set.all()
emails = []
for x in appUsers:
emails.append(x.user.email)
Alternatively I could write a raw SQL join to do it but suspect the Django ORM is capable.
What's the proper and most efficient way to do this?
Models:
class User(Model):
id = IntegerField()
username = CharField()
email = CharField()
class Meta:
db_table = "auth_user"
class AppUser(Model):
user = OneToOneField(User, primary_key=True)
group = ForeignKey('Group', null=True, on_delete=SET_NULL)
zip = CharField()
class Meta:
db_table = "app_users"
class Group(Model):
creator = ForeignKey(AppUser, related_name='+')
created = DateTimeField()
name = CharField()
class Meta:
db_table = "groups"

The trick is to always start from the model you actually want to fetch. In this case, you want to fetch emails, which is a field on the User model. So, start with that model, and use the double-underscore syntax to follow the relationships to Group:
users = User.objects.filter(appuser__group_id=group_id)
In this case, you actually only need a single JOIN, because group_id is a field on AppUser itself (it's the underlying db field for the group ForeignKey). If you had the group name, you would need two joins:
users = User.objects.filter(appuser__group__name=group_name)

Related

Best approach to use Many to Many in Django + SQL

I am making a web app and i wish to use many to many fields in models.I haven't given much thought before and have simply used many to many field in models but now i am thinking how actually these many to many are stored in database and what would be the best approach when using Many to Many Fields in Models.
Approach 1:
class Company(models.Model):
name = models.CharField(max_length = 190,blank = False,null = False)
about = models.CharField(max_length = 260,blank = False,null = True)
website = models.URLField(blank = False)
address = models.OneToOneField(Address,related_name="org_address",on_delete=models.SET_NULL,null=True)
admin = models.ManyToManyField(User,related_name="org_admin",blank = False)
created_on = models.DateTimeField(default = timezone.now)
updated_on = models.DateTimeField(default = timezone.now)
OR
Approach 2:
class Company(models.Model):
name = models.CharField(max_length = 190,blank = False,null = False)
about = models.CharField(max_length = 260,blank = False,null = True)
website = models.URLField(blank = False)
address = models.OneToOneField(Address,related_name="org_address",on_delete=models.SET_NULL,null=True)
created_on = models.DateTimeField(default = timezone.now)
updated_on = models.DateTimeField(default = timezone.now)
class CompanyAdmin(models.Model):
admin = models.ManyToManyField(User,related_name="org_admin",blank = False)
company = models.ForeignKey(Company,related_name="company",on_delete=models.CASCADE)
Do both methods handle many to many fields in the same way ?
I am using MySQL as Database
Update
"To make them Equivalent, Second Approach should have field as ForiegnKey and not many to many"
also to avoid multiple company admin entries Field company should be one to one.
Solution
Do not create join tables as Django ORM Handles this itself.
but now I am thinking how actually these many to many are stored in database.
Django will construct a model that has two ForeignKeys, one to the Company and one to the User model. This model thus corresponds to the junction table [wiki] between the Company and User model.
A ManyToManyField thus does not map on a column. The ManyToManyField is more a concept that enables one to write simpler queries, and provides a more convenient interface.
Do both methods handle many to many fields in the same way ?
No. With the latter you will make two extra tables, one for the CompanyAdmin model, and one for the ManyToManyField between CompanyAdmin and User. It would also result in more JOINs to fetch the companies for which a User is an admin for example, and it would here be possible to construct multiple CompanyAdmins for the same Company, and thus linking the same User multiple times.
You can define however custom attributes in the junction table, by specifying the junction model explicitly with the through=… parameter [Django-doc]:
from django.conf import settings
class Company(models.Model):
admin = models.ManyToManyField(
settings.AUTH_USER_MODEL,
related_name='org_admin',
through='CompanyAdmin'
)
created_on = models.DateTimeField(auto_now_add=True)
updated_on = models.DateTimeField(auto_now=True)
class CompanyAdmin(models.Model):
admin = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE
)
company = models.ForeignKey(
Company,
on_delete=models.CASCADE
)
Note: It is normally better to make use of the settings.AUTH_USER_MODEL [Django-doc] to refer to the user model, than to use the User model [Django-doc] directly. For more information you can see the referencing the User model section of the documentation.
Note: Django's DateTimeField [Django-doc]
has a auto_now_add=… parameter [Django-doc]
to work with timestamps. This will automatically assign the current datetime
when creating the object, and mark it as non-editable (editable=False), such
that it does not appear in ModelForms by default.

Filtering django queryset by a value of the through table of a m2m relationship

I'm trying to do this:
queryset.filter(m2m_related_lookup__through_table_field=value)
These are the models, simplified:
class User(models.Model):
name = models.CharField("nom", max_length=100)
surname = models.CharField("cognoms", max_length=100)
class Activity(models.Model):
name = models.CharField("Títol", max_length=200)
date_start = models.DateField("Dia inici")
date_end = models.DateField("Dia finalització")
enrolled = models.ManyToManyField(User, related_name='enrolled_activities', through='ActivityEnrolled')
class ActivityEnrolled(models.Model):
class Meta:
db_table = 'main_activity_personetes_enrolled'
activity = models.ForeignKey(Activity, on_delete=models.CASCADE)
user = models.ForeignKey(Personeta, on_delete=models.CASCADE)
date_enrolled = models.DateTimeField("data d'inscripció")
confirmed = models.BooleanField("confirmada", default=False)
I guess is quite simple, just a many 2 many with a custom through table, so I can store the enrollment date and some other things there.
This relationship is set at Activity, with a related_name of 'enrolled_activities'.
So, how can I query "all the users where the ActivityEnrolled.enrollment_date is in 2019" using Django's ORM?
This is for a custom filter (with admin.SimpleListFilter) for a change_list view in the Django Admin, which is listing the User items. In other words, is like I'm doing User.objects.filter(blabla).
Trying: queryset.filter(enrolled_activities__date_enrolled__year=2019) obviously throws the error Related Field got invalid lookup: date_enrolled, because enrolled_activities does not refer to the through table but to the related table (this is: Activity), and this field does not exist there.
Is the only solution to query the through table instead of Users?
Like: ActivityEnrolled.objects.filter(date_enrolled__year=2019) + grouping the results so it only returns one row per each User. I know I can do that but it's quite nasty, I've been trying to find a cleaner way to avoid it but with no success.
Thank you very much!!
So, how can I query "all the users where the ActivityEnrolled.enrollment_date is in 2019" using Django's ORM?
A many-to-many relation is in fact just a combination of two one-to-many tables. We thus can filter on the one-to-many relation with:
User.objects.filter(activityenrolled__enrollment_date__year=2019).distinct()
The .distinct() will prevent yielding the same user, if th user has multiple activities for which he/she was enrolled in 2019.

Add relationship to three tables with data and strange column names

I have a Postgres database with 3 tables with data and their models in Django. I do not have control over how these tables are filled. But I need to add relationships to them.
It would not be a problem for me in MsSQL, Oracle or MySql. But Im confused here.
class Keywords(models.Model):
id = models.AutoField(primary_key=True)
keyword = models.CharField(unique=True, max_length=250)
class Mapping(models.Model):
id = models.AutoField(primary_key=True)
keyword = models.CharField(max_length=250)
videoid = models.CharField(max_length=50)
class Video(models.Model):
videoid = models.CharField(primary_key=True, max_length=50)
In your model Mapping, which is used to relate with the models Keywords and Video, you can make changes like:
class Mapping(models.Model):
id = models.AutoField(primary_key=True)
keyword = models.ForeignKey(Keywords, on_delete=models.CASCADE)
videoid = models.ForeignKey(Video, on_delete=models.CASCADE)
You also don't need to define id for the model as Django itself creates a id field which is auto generated and primary key.
Use inspectdb in order to generate your models from your db tables.
$ ./manage.py inspectdb table1 table2 table3 >> models.py
Relation
class Video(models.Model):
#...
keywords = models.ManyToManyField(Keywords)
Then you can remove the Mapping model, the table for this relation is generated by Django.
If you want to keep the data of the already related instances, use the through key parameter for the ManyToManyField with the Mapping model.
Finally, I found a solution:
class Mapping(models.Model):
id = models.AutoField(primary_key=True)
video = models.ForeignKey(Videos, to_field='videoid', db_column='videoid', on_delete=models.DO_NOTHING,blank=False,null=True,)
keyword = models.ForeignKey(Keywords, to_field='keyword', db_column='keyword', on_delete=models.DO_NOTHING, blank=False, null=True,)
To add relations to existing tables with weird column names and type it is the best to use to_field and db_column. In this case, Django will not try to create standard id columns for relations.

Django model filtering with distinct on a ForeignKey

I may have a simple Django filtering questing. I need a way to select only unique on a ForeignKey, like a distinct.
my models
class Post(models.Model):
user = models.ForeignKey(User)
....
class Share(models.Model):
post = models.ForeignKey(Post)
user = models.ForeignKey(User, blank=True, null=True)
....
I need a way to get all shares with only unique post.user's and need only to get the latest post, to make a inbox.
I ended up with something like ..
Share.objects.filter(user=request.user).distinct("post__user").order_by("post__created")
But i gets an error::
DISTINCT ON fields is not supported by this database backend
Is there a way to make this work?, and what is the best solution?

Relational Mapping using Peewee

I'm new to relational databases and I'm quite confused on how should I create the models. What I need need to get done is to filter the posts content through the language choice and to do that I need to create a relational database. My doubt comes when deciding how many models(tables) should I have to accomplish this. Here is an example:
models.py
class Post(Model):
timestamp = DateTimeField(default=datetime.datetime.now)
user = ForeignKeyField(
rel_model=User,
related_name='posts'
)
language = TextField()
content = ForeignKeyField(THIS NEEDS TO POINT TO THE LANGUAGE)
class Meta:
database = DATABASE
Is it possible to accomplish something like this? Should I create more than one Post model?
Thank you in advanced.
Have you run through the quickstart guide? Doing so might give you a feel for how to create models and set up relationships:
http://docs.peewee-orm.com/en/latest/peewee/quickstart.html
To answer your immediate question, you can create another table for language, i.e.
class Language(Model):
name = CharField()
class Meta:
database = DATABASE
So all together you'd have (cleaned up a bit):
DATABASE = SqliteDatabase('mydb.db') # or PostgresqlDatabase or MySQLDatabase
class BaseModel(Model):
class Meta:
database = DATABASE
class User(BaseModel):
email = CharField()
# whatever other user fields
class Language(BaseModel):
name = CharField()
# other fields?
class Post(BaseModel):
timestamp = DateTimeField(default=datetime.datetime.now)
user = ForeignKeyField(User, related_name='posts')
language = TextField()
content = ForeignKeyField(Language, related_name='posts')

Categories

Resources