Django: Unique ID's across tables - python

I was curious if there was a good solution to implement unique ID's between tables.
class Voice(models.Model):
id = .. <------|
slug = ... |
name = .... |-- No duplicate IDs
|
class Group(models.Model): |
id = .. <------|
slug = ...
name = ....
My hope is that when I get an ID in a view, selecting from one model will give me None but the other will always return the object (and vice versa). If there is a better approach feel free to share. Right now I am using the slug+id as query filters but would like to move away from that.

I'd worry less about the unique ids and consider the data model relationships. From what you're saying, it sounds like there's a commonality between the two and that model can have a voice, group or both associated with it.
class NewCommonModel(models.Model):
# common fields go here.
class Voice(models.Model):
new_common_model = models.OneToOneField(NewCommonModel, on_delete=models.CASCADE)
# voice specific fields
class Group(models.Model):
new_common_model = models.OneToOneField(NewCommonModel, on_delete=models.CASCADE)
# group specific fields

Define id as an IntegerField instead of auto. Voice always has even numbers as id and Group always odd. This way you will even know in advance in which model you should look for

I would recommend the use of a uuid as your primary key. Solves your unique problem, obfuscates your pk, is unique across the universe, and is built into django as well.
Since you mentioned slug, there are ways to have a unique slug per model where you'd never need to make a composite key with the pk. Or, you can include the slug in the url for cosmetic reasons and just filter in your view for only the pk, which should always be unique.
But ordinarily, having the same pk in two models shouldn't really ever be an issue, and without knowing more, I would be concerned you're doing something odd.

Related

Django annotate value based on another model field

I have these two models, Cases and Specialties, just like this:
class Case(models.Model):
...
judge = models.CharField()
....
class Specialty(models.Model):
name = models.CharField()
sys_num = models.IntegerField()
I know this sounds like a really weird structure but try to bare with me:
The field judge in the Case model refer to a Specialty instance sys_num value (judge is a charfield but it will always carries an integer) (each Specialty instance has a unique sys_num). So I can get the Specialty name related to a specific Case instance using something like this:
my_pk = #some number here...
my_case_judge = Case.objects.get(pk=my_pk).judge
my_specialty_name = Specialty.objects.get(sys_num=my_case_judge)
I know this sounds really weird but I can't change the underlying schemma of the tables, just work around it with sql and Django's orm.
My problem is: I want to annotate the Specialty names in a queryset of Cases that have already called values().
I only managed to get it working using Case and When but it's not dynamic. If I add more Specialty instances I'll have to manually alter the code.
cases.annotate(
specialty=Case(
When(judge=0, then=Value('name 0 goes here')),
When(judge=1, then=Value('name 1 goes here')),
When(judge=2, then=Value('name 2 goes here')),
When(judge=3, then=Value('name 3 goes here')),
...
Can this be done dynamically? I look trough django's query reference docs but couldn't produce a working solution with the tools specified there.
You can do this with a subquery expression:
from django.db.models import OuterRef, Subquery
Case.objects.annotate(
specialty=Subquery(
Specialty.objects.filter(sys_num=OuterRef('judge')).values('name')[:1]
)
)
For some databases, casting might even be necessary:
from django.db.models import IntegerField, OuterRef, Subquery
from django.db.models.functions import Cast
Case.objects.annotate(
specialty=Subquery(
Specialty.objects.filter(sys_num=Cast(
OuterRef('judge'),
output_field=IntegerField()
)).values('name')[:1]
)
)
But the modeling is very bad. Usually it is better to work with a ForeignKey, this will guarantee that the judge can only point to a valid case (so referential integrity), will create indexes on the fields, and it will also make the Django ORM more effective since it allows more advanced querying with (relativily) small queries.

Error saving django model with OneToOne field - Column specified twice

This question has been asked before, but the answers there do not solve my problem.
I am using a legacy database, nothing can be changed
Here are my django models, with all but the relevant fields stripped off, obviously class meta has Managed=False in my actual code:
class AppCosts(models.Model):
id = models.CharField(primary_key=True)
cost = models.DecimalField()
class AppDefs(models.Model):
id = models.CharField(primary_key=True)
data = models.TextField()
appcost = models.OneToOneField(AppCosts, db_column='id')
class JobHistory(models.Model):
job_name = models.CharField(primary_key=True)
job_application = models.CharField()
appcost = models.OneToOneField(AppCosts, to_field='id', db_column='job_application')
app = models.OneToOneField(AppDefs, to_field='id', db_column='job_application')
The OneToOne fields work fine for querying, and I get the correct result using select_related()
But when I create a new record for the JobHistory table, when I call save(), I get:
DatabaseError: (1110, "Column 'job_application' specified twice")
I am using django 1.4 and I do not quite get how this OneToOneField works. I can't find any example where primary keys are named differently and has this particular semantics
I need the django model that would let me do this SQL:
select job_history.job_name, job_history.job_application, app_costs.cost from job_history, app_costs where job_history.job_application = app_costs.id;
You have defined appcost and app to have the same underlying database column, job_application, which is also the name of another existing field: so three fields share the same column. That makes no sense at all.
OneToOneFields are just foreign keys constrained to a single value on both ends. If you have foreign keys from JobHistory to AppCost and AppDef, then presumably you have actual columns in your database that contain those foreign keys. Those are the values you should be using for db_field for those fields, not "job_application".
Edit I'm glad you said you didn't design this schema, because it is pretty horrible: you won't have any foreign key constraints, for example, which makes referential integrity impossible. But never mind, we can actually achieve what you want, more or less.
There are various issues with that you have, but the main one is that you don't need the separate "job_application" field at all. That is, as I said earlier, the foreign key, so let it be that. Also note it should be an actual foreign key field, not a one-to-one, since there are many histories to one app.
One constraint that we can't achieve easily in Django is to have the same field acting as FK for two tables. But that doesn't really matter, since we can get to AppCosts via AppDefs.
So the models could just look like this:
class AppCosts(models.Model):
app = models.OneToOneField('AppDefs', primary_key=True, db_field='id')
cost = models.DecimalField()
class AppDefs(models.Model):
id = models.CharField(primary_key=True)
data = models.TextField()
class JobHistory(models.Model):
job_name = models.CharField(primary_key=True)
app = models.ForeignKey(AppDefs, db_column='job_application')
Note that I've moved the one-to-one between Costs and Defs onto AppCosts, since it seems to make sense to have the canonical ID in Defs.
Now, given a JobHistory instance, you can do history.app to get the app instance, history.app.cost to get the app cost, and use the history.app_id to get the underlying app ID from the job_application column.
If you wanted to reproduce that SQL output more exactly, something like this would now work:
JobHistory.objects.values_list('job_name', 'app_id', 'app__appcosts__cost')

Django - one to one relation, mostly NULLs at one end, NOT NULL on the other

I’m writing simple application with Django and PostgreSQL for managing a home book library, where I can have many borrower profiles for people who borrowed books (Borrower model). And I have users, who can borrow a book themselves, so that the book becomes borrowed by the user’s borrower profile.
On the other hand admin can lend books to any borrower, even to one unregistered as a user.
So I have a few MyUsers (and that field, btw, references Django’s User) and many Borrowers, and I want to create a one-to-one relation between them, but every MyUser has to reference one unique Borrower, but many Borrowers will not reference any existing MyUsers (they can only reference one or none, or in another words, be referenced by only one or no user).
My question is: how to model that optimally? Using models.OneToOneField, models.ForeignKey and which model should reference which?
I will probably have many borrowers, who do not have user accounts.
Natural solution seems to be OneToOneField(Borrower, null=False) in a User model. But then when searching for users based on borrowers I will have to mostly deal with DoesNotExists exceptions and only once in a while I will get a proper result.
I can also make ForeignKey(Borrower, unique=True, null=False) – then I will have to check sets having single element or empty.
And I can make ForeignKeys both ways:
class Borrower(models.Model):
# ...
user = models.ForeignKey(MyUser, unique=True, null=True)
class MyUser(models.Model):
# ...
borrower = models.ForeignKey(Borrower, unique=True, null=False)
That implicitly defines the relations and I can easily make searches both ways, but it also makes one additional, redundant field in database tables.
I will probably just stick with OneToOneField for now, but I’d like to know which approach makes most sense in this case. What are the proc and cons? And is there an alternative, better solution?
I would stick with the OneToOneField; as you say, it is the most natural solution.
The only downside you mentioned is that borrower.user can raise a DoesNotExist exception. If you don't like that, you can always define your own method (or property) to return something else (like None) instead. Something like:
class MyUser(models.Model):
borrower = models.OneToOneField(Borrower, null=False)
class Borrower(models.Model):
#property
def user(self):
try:
return self.myuser
except DoesNotExist:
return None
#user.setter
def user(self, user):
self.myuser = user

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)

Django filter many-to-many with contains

I am trying to filter a bunch of objects through a many-to-many relation. Because the trigger_roles field may contain multiple entries I tried the contains filter. But as that is designed to be used with strings I'm pretty much helpless how i should filter this relation (you can ignore the values_list() atm.).
This function is attached to the user profile:
def getVisiblePackages(self):
visiblePackages = {}
for product in self.products.all():
moduleDict = {}
for module in product.module_set.all():
pkgList = []
involvedStatus = module.workflow_set.filter(trigger_roles__contains=self.role.id,allowed=True).values_list('current_state', flat=True)
My workflow model looks like this (simplified):
class Workflow(models.Model):
module = models.ForeignKey(Module)
current_state = models.ForeignKey(Status)
next_state = models.ForeignKey(Status)
allowed = models.BooleanField(default=False)
involved_roles = models.ManyToManyField(Role, blank=True, null=True)
trigger_roles = models.ManyToManyField(Role, blank=True, null=True)
Though the solution might be quiet simple, my brain won't tell me.
Thanks for your help.
Have you tried something like this:
module.workflow_set.filter(trigger_roles__in=[self.role], allowed=True)
or just if self.role.id is not a list of pks:
module.workflow_set.filter(trigger_roles__id__exact=self.role.id, allowed=True)
The simplest approach to achieve this would be checking for equalty over the whole instance (instead of the id) in the ManyToManyField. That looks if the instance is inside the many to many relationship. Example:
module.workflow_set.filter(trigger_roles=self.role, allowed=True)
I know this is an old question, but it looks like the OP never quite got the answer he was looking for. If you have two sets of ManyToManyFields you want to compare, the trick is to use the __in operator, not contains. So for example if you have an "Event" model with a ManyToMany to "Group" on field eventgroups, and your User model (obviously) attaches to Group, you can query like this:
Event.objects.filter(eventgroups__in=u.groups.all())
singularity is almost right with the first example. You just need to make sure it's a list. The second example, checking the trigger_roles__id__exact is a better solution though.
module.workflow_set.filter(trigger_roles__in=[self.role.id],allowed=True)

Categories

Resources