Specify relationship verbs in Django at runtime? - python

How do you identify model relationship verbs in Django?
So, lets say I have a model Movie, and want fields for Director and Writer. I don't want to have two distinct tables for these roles, like this:
...because it violates database best practices (normal forms, etc.), and doesn't keep things DRY. Instead I want a single Person model, via a ManyToMany relationship with Movie, like this:
How do I allow these verbs, marked in the diamonds, to be specified at runtime?
For example, in the Django Admin, Movie should have a Person field, with some place to enter a Role, e.g. written by or directed by. I don't want to have fields in Movie for each possible role: I don't want a director or writer or animal_trainer field, for example. There might be hundreds... some of them duplicates.
In Rails, I'd just make a join table with an extra field Role, but I assume Django has a better way.
I'm sure this is documented somewhere in the Django docs, or maybe just in general SQL docs somewhere, but I guess I don't know the correct search terms to Google. A link to relevant docs would be sufficient, if they exist. Thanks!

I think your best option in (for this context) is simply having 2 models, Movie and person, and having 2 foreign keys in the Movie table, one for writer, one for director, since every movie will have both of those fields, e.g.
def Person(models.Model):
...
def Movie(models.Model):
writer = models.ForeignKey(Person, related_name='movies_written')
director = models.ForeignKey(Person, related_name='movies_directed')
...
EDIT
With your change in question, I believe the best answer to this is from https://docs.djangoproject.com/en/dev/topics/db/models/#extra-fields-on-many-to-many-relationships
def Person(models.Model):
...
def Movie(models.Model):
...
workers = models.ManyToManyField('Person', through='WorkedOn')
# model in case you want to specify anything specific for roles for every person
def Role(models.Model):
...
# can have extra fields, e.g. dates worked, pay, etc.
def WorkedOn(models.Model):
person = models.ForeignKey('Person')
movie = models.ForeignKey('Movie')
role = models.ForeignKey('Role')
...

Related

How to access data across M2M tables in Django?

What is the 'best practice' way of accessing data across a 1 (or more) many-to-many tables?
This is incredibly difficult for me as I am not sure what I shuld be googling/looking up.
I have attached a diagram of my data model. I am able to query data for 'C' related ot a user, by utilizing serializers.
there has to be a simpler way of doing this (I'm hoping).
Doing it with serializers seems incredibly limiting. I'd like to access a user's 'B' and 'C' and transform the object to only have a custom structure and possible unique values.
Any direction is much appreciated. Pretty new to Django, so I apologize for this newb type of question.
Here is an example of M2M relation using Django:
class User(models.Model):
name = models.CharField(...)
class Song(models.Model)
title = models.CharField(...)
users_that_like_me = models.ManyToManyField('User', ..., related_name='songs_that_i_like')
So a User can like many Songs and a Song can be liked by many Users.
To see all the songs a user liked, we can do:
user = User.objects.get(id='<the-user-id>')
liked_songs = user.songs_that_i_like.all()
And to see all the users who like a particular song we can similarly do:
song = Song.objects.get(id='<the-song-id>')
users_that_like_this_song = song.users_that_like_me.all()
Both liked_songs and users_that_like_this_song are actually querysets, meaning we can do some Django magic on them.
For example, to find all users named Jon that liked this song we can do:
users_that_like_this_song.filter(name='Jon')
We can also add some property shortcuts to our Models to help with some common tasks, for example:
class User(models.Model):
...
#property
def number_of_liked_songs(self):
return self.songs_that_i_like.count()
Then we can do:
user = User.objects.get(id='<the-user-id>')
number_of_songs_i_like = user.number_of_liked_songs
There's much more we can do with Django - if you're looking for something specific let us know.

How to have extra django model fields depending on the value of a field?

I have a model in my Django project called Job. Each Job has a category. An example of a category could be tutoring. This can be represented as what my model looks like right now:
from __future__ import unicode_literals
from django.db import models
class Job(models.Model):
# Abbreviations for possible categories to be stored in the database.
TUTORING = "TU"
PETSITTING = "PS"
BABYSITTING = "BS"
INTERIOR_DESIGN = "IND"
SHOPPING = "SH"
SOFTWARE_DEVELOPMENT = "SD"
DESIGN = "DE"
ART = "AR"
HOUSEKEEPING = "HK"
OTHER = "OT"
JOB_CATEGORY_CHOICES = (
(TUTORING, 'Tutoring'),
(PETSITTING, "Petsitting"),
(BABYSITTING, "Babysitting"),
(INTERIOR_DESIGN, "Interior Design"),
(SHOPPING, "Shopping"),
(SOFTWARE_DEVELOPMENT, "Software Development"),
(DESIGN), "Design"),
(ART, "Art"),
(HOUSEKEEPING, "Housekeeping"),
(OTHER, "Other"),
)
created_at = models.DateTimeField(auto_now_add=True)
title = models.CharField(max_length=255)
description = models.TextField()
category = models.CharField(max_length=3, choices=JOB_CATEGORY_CHOICES, default=OTHER,)
def __str__(self):
return self.title
Depending on the category of the Job, different fields are required. For example, if I take tutoring as the category again, then extra fields like address, subject, level of study and others are needed. If the category of the Job is software development however, extra fields like project_size and required_qualifications are needed.
Should I create a separate model for each type of Job or is there some kind of model inheritance I can use where job types inherit from the main Job model which holds all the common fields that all Jobs need.
Essentially, what is the best way to have extra fields depending on the Job category?
You have some options:
1. OneToOneField on various category models:
Pro:
allows other models to have FK to Job model. E.g. you could retrieve all of a person jobs via person.jobs.all() no matter which category.
Con:
Allows instances of different categories to relate to the same Job instance: Extra work is needed to maintain data integrity
More tables, more joins, slower queries
Adding a category always entails a migration!
2. Multi-Table inheritance:
Uses OneToOneField under the hood.
Pro:
as above + but each instance of a category will autocreate its own Job instance, so no collisions between categories.
Con:
More tables, more joins, slower queries. Obscures some of the db stuff that's going on.
Adding a category always entails a migration!
3. Job as an abstract base model
Pro: Single table for each category, faster queries
Con: separate relations need to be maintained for each category, no grouping possible at the db level.
Adding a category always entails a migration!
4. Put all the category specific fields in Job (and make them nullable)
Pro: One Table, easy relations, Queries for special categories via filter on category field still possible.
You can use specific model managers to handle categories: Job.tutoring.all()
Possibly many categories share various subsets of fields
No overengineering, easy maintainability.
Adding a new category will only require a migration if it requires a field that is not there yet. You could have a generic CharField used by multiple categories for different semantic purposes and access it via propertys with meaningful names. These cannot, however, be used in filters or qs-updates.
À la:
class Job(models.Model):
# ...
attribute = models.CharField(...)
def _get_attribute(self):
return self.attribute
def _set_attribute(self, value):
self.attribute = value
# for shopping
shop_name = property(_get_attribute, _set_attribute)
# for babysitting
family_name = property(_get_attribute, _set_attribute)
# then you can use
babysitting_job.family_name = 'Miller'
Con: Some fields are null for each job
While options 1-3 may better model the real world and make you feel good about the sophisticated model structure you have cooked up, I would not discard option 4 too quickly.
If the category fields are few and commonly shared between categories, this would be my way to go.
The optimal thing to do would be to use a OneToOneField. Before further explanation, I'll just use this example:
from django.db import models
class Menu(models.Model):
name = models.CharField(max_length=30)
class Item(models.Model):
menu = models.OneToOneField(Menu)
name = models.CharField(max_length=30)
description = models.CharField(max_length=100)
Menu here could compare to your Job model. Once an item in the menu is chosen, the Menu model basically extends the chosen Item's fields. Item here can be compared to your Job category.
You can read more on this stuff here.

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')

Understanding Querysets, creating the perfect database relationship

I'm am currently trying to figure out the best way to structure my database schema based on a few models. I'll try and explain this the best I can so I can work out the best way to tackle the problem.
Firstly, I have 3 models that are "related"
User which is extended to contain the field api_key, Campaign and finally Beacon.
User's can have many Campaign's but a Campaign can only relate to one User my first choice here was to have Campaign have a foreign key to User, correct me if I'm wrong, but I feel that is the best choice there. Likewise, Campaign can have many Beacon's but a Beacon can only relate to one Campaign at a time. Again, I'm presuming that a foreign key here would work the best.
The issue arises when I try and query the Beacon's that relate to any given Campaign. I wish to return all Beacon's that relate to the User whilst also getting the data for Campaign.
I wish to return a JSON string like the following:
{
XXXX-YYYYY: {
message: "Hello World",
destination: "http://example.com"
}
XXXX-YYYYY: {
message: "Hello World",
destination: "http://example.com"
}
}
XXXX-YYYYY being the Beacon.factory_id and message/destination being Campaign.message and Campaign.destination
I'm thinking Queryset's here, but I've never worked with them before and it just confused me.
According to your question, you have something like this:
class User(models.Model):
pass
class Campaign(models.Model):
user = models.ForeignKey(User, verbose_name="Attached to")
message = models.CharField()
destination = models.CharField()
class Beacon(models.Model):
factory_id = models.CharField()
campaign = models.ForeignKey(Campaign, verbose_name="Campaign")
You can follow ForeignKey "backward", by using campaign_set generated attribute:
If a model has a ForeignKey, instances of the foreign-key model will have access to a Manager that returns all instances of the first model. By default, this Manager is named FOO_set, where FOO is the source model name, lowercased.
So you can query your Beacon model like this:
beacon = Beacon.objects.get(factory_id="XXXX-YYYYY")
# Get every campaigns related and only relevant fields (in a list of dict)
campaigns = beacon.campaign_set.all().values('message', 'destination')
for campaign in campaigns:
print(campaign['message'])
print(campaign['destination'])
For your dictionary, it is impossible to make it exactly like this. You can't have a duplicate key.
I wish to return all Beacons that relate to the User whilst also getting the data for Campaign
beacons = Beacon.objects.filter(campaign__user=user).select_related('campaign')
You can then easily process this into your desired data structure.
I'm thinking Querysets here, but I've never worked with them before and it just confused me
A QuerySet is simply how the Django ORM represents a query to your database that results in a set of items. So the above is a QuerySet, as is something as simple as User.objects.all(). You can read some introductory material about QuerySets in the documentation.

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