Composite primary key in django - python

I have a legacy db table which has composite primary key. I don't think I will be able to change the structure to include a surrogate key, as there is some code written that uses that table. And in django, I cannot use that table, as it doesn't have a primary key(non-composite).
Do django models support composite primary keys? If not, is there any workaround without changing the structure of the table?
P.S. I am using postgresql.

Try similar below code:
class MyTable(models.Model):
class Meta:
unique_together = (('key1', 'key2'),)
key1 = models.IntegerField(primary_key=True)
key2 = models.IntegerField()
or if you want only unique mixed fields:
class MyTable(models.Model):
class Meta:
unique_together = (('key1', 'key2'),)
key1 = models.IntegerField()
key2 = models.IntegerField()
EDIT: I would like to note that there is a problem with this approach if there are 3 columns. Update queries don't work because it tries to update (puts pk fields right after "SET") the fields that are unique together and obviously fails.

The accepted answer is fine. However, it's a little old. unique_together may be deprecated in favor of UniqueConstraint. So, the better way of doing this would be;
UniqueConstraint(fields = ['key1', 'key2'], name = 'constraint_name')

I solved this with virtual field inherited from django AutoField, that combines a values from several fields into single JSON dict.
That makes such models, compatible with django admin and genetic views.
$ pip install django-viewflow --pre
from viewflow.fields import CompositeKey
class Seat(models.Model):
id = CompositeKey(columns=['aircraft_code', 'seat_no'])
aircraft_code = models.ForeignKey(
Aircraft, models.DO_NOTHING,
db_column='aircraft_code'
)
seat_no = models.CharField(max_length=4)
This makes possible to access as to legacy databases, as to PostgreSQL TimeScaleDB tables

Another option is to set managed=False in the model's Meta, then manually create the table.
class MyTable(models.Model):
foo = models.IntegerField(primary_key=True)
bar = models.IntegerField()
baz = models.IntegerField()
class Meta:
managed = False
db_table = 'myapp_mytable'
def __repr__(self):
return f'<MyTable: MyTable object ({self.foo}, {self.bar}, {self.baz)>'
In a postgres shell:
CREATE TABLE myapp_mytable (
foo INTEGER NOT NULL,
bar INTEGER NOT NULL,
baz INTEGER NOT NULL,
PRIMARY KEY(foo, bar, baz)
);
It appears to behave correctly:
>>> MyTable.objects.create(foo=1, bar=1, baz=1)
<MyTable: MyTable object (1, 1, 1)>
>>> MyTable.objects.create(foo=1, bar=1, baz=2)
<MyTable: MyTable object (1, 1, 2)>
>>> MyTable.objects.create(foo=1, bar=1, baz=2)
django.db.utils.IntegrityError: duplicate key value violates unique constraint "myapp_mytable_pkey"
DETAIL: Key (foo, bar, baz)=(1, 1, 2) already exists.
Note that this is only tested in Django 3.x, so I'm not sure if it works in older versions.

Related

How to use a field on a foreign key (another model) as part of my model primary key value

I'm new to Django and I feel sometimes it is not clear in which .py of myApp I should write solutions and examples I see.
In my models.py I have a model called Project and a model called Order. In admin section (http://127.0.0.1:8000/admin/myApp/), I would like the user to type a project number when creating a new project. Every project can have multiple Orders. I would like the Order primary key to be composed of the Project number it belongs to, plus a consecutive number. The user can only change the consecutive number part of the Oder primary key but not alter the Project number the order belongs to.
For instance for Project with project_number(primary key) = 951, Orders primary keys can be 951-1, 951-2, etc
Another project with project_number(primary key) = 1015 can also have orders 1,2, etc but they won't conflict with orders of project 951 because they will be labelled 1015-1, 1015-2, etc.
Is it possible to achieve this in models.py?
How would I have to modify order_number field below?
Notice I need the order_number field to fetch its project_number from order_project field and I won't know the order_project exact value until the user is creating the order and associating it with a project.
If what I'm asking can't be done please suggest a way to solve this and clearly explain in which .py of myApp I should write the code.
class Project(models.Model):
project_number = models.IntegerField(unique=True,primary_key=True)
project_name = models.CharField(max_length=400)
class Order(models.Model):
order_project = models.ForeignKey("Project", on_delete=models.CASCADE,verbose_name = "Project to which this order is associated",related_name= "orders_for_project")
order_number = models.CharField(unique=True,primary_key=True,max_length = 10)
UPDATE:
Following suggestions from the community my code now looks like this:
class Project(models.Model):
project_number = models.IntegerField(unique=True,primary_key=True)
project_name = models.CharField(max_length=400)
class Order(models.Model):
order_project = models.ForeignKey("Project", on_delete=models.CASCADE,verbose_name = "Project to which this order is associated",related_name= "orders_for_project")
order_number = models.IntegerField(default=1,validators=[MinValueValidator(1)])
class Meta:
#TODO capture error and present it in human readable form
constraints = [ models.UniqueConstraint(fields= ['order_project','order_number'], name = 'unique_order_id'),]
def __str__(self):
return str(self.order_project.project_number) + ("-") + str(self.order_number) + (" : ") + str(self.order_description)
I do not fully understand why my Order primary key could not be formed considering the value of the primary key of Project but this is a workaround solution
You can keep Order's primary key independent from Product's PK, just set uniqueness constraint on order_project_id + order_number combination:
class Order(models.Model):
order_project = models.ForeignKey("Project", on_delete=models.CASCADE,verbose_name = "Project to which this order is associated",related_name= "orders_for_project")
order_number = models.IntegerField()
class Meta:
unique_together = ('order_project', 'order_number')
Then if you want to display order number in {order_project_id}-{order_number} format you can just generate this value using those 2 fields in runtime.

Can unique_together avoid nulls in Django

I am using django and Sql server as a database.
I have a table with a multiple primary key using the unique_together
class Attribute_tmp(models.Model):
useridtmp = models.ForeignKey(User_tmp, on_delete=models.CASCADE, blank =True, null = True)
userid = models.ForeignKey(User, on_delete=models.CASCADE, blank =True, null = True)
fk_str = models.ForeignKey(Stream, on_delete=models.CASCADE)
class Meta:
unique_together = (('userid', 'fk_str'),('useridtmp', 'fk_str'))
So when i add objects of this table when the useridtmp is null, it doesn't work because i will have a duplicated key.
My Question is how can i avoid the null values.
Thank you
Did you tried this?
class Meta:
constraints = [
models.UniqueConstraint(fields=['userid', 'fk_str'], name='name of constraint'),
models.UniqueConstraint(fields=['useridtmp', 'fk_str'], name='another name of constraint')
]
Since you have null=True and blank=True in your ForeignKey field, it is reasonable for db to store null values.
If in any case, the two ForeignKeys shouldn't be null, you can mark null and blank as false. Simply removing them from field settings will do the trick as they are set to false by default. You probably need to recreate the database to make the new settings working.
Then, the unique_together will not have an issue with null values.

Having NULL as a primary key value

I would like to have an entity as follows:
class EntitySharedLinkPermission(models.Model):
OFF = None
COMPANY_VIEW = "Company View"
COMPANY_EDIT = "Company Edit"
PUBLIC_VIEW = "Public View"
PUBLIC_EDIT = "Public Edit"
name = models.CharField(max_length=12, primary_key=True)
class Meta: db_table = 'entity_shared_link_permission'
However, I cannot have NULL as a primary key value here. What should I do here instead? One idea was to just remove the primary key on this table and have a unique key instead (no PK in the table) to get around this, but surely there must be a better solution.
Simply put, you can't have null as primary key column value. You should always supply non null value to the primary key. Also, don't go for unique, it just isn't the solution though it masquerades as being one. If you can't always supply non null value, introduce a new identity column to your table instead.
If you don't expect the list of items to change often, and the set is small, then it looks to me like you're trying to set up a "choices" field, for which Django already has nice support. Here's the example that the Django docs use, which you could easily adapt to your situation:
from django.db import models
class Student(models.Model):
FRESHMAN = 'FR'
SOPHOMORE = 'SO'
JUNIOR = 'JR'
SENIOR = 'SR'
YEAR_IN_SCHOOL_CHOICES = (
(FRESHMAN, 'Freshman'),
(SOPHOMORE, 'Sophomore'),
(JUNIOR, 'Junior'),
(SENIOR, 'Senior'),
)
year_in_school = models.CharField(
max_length=2,
choices=YEAR_IN_SCHOOL_CHOICES,
default=FRESHMAN,
)
def is_upperclass(self):
return self.year_in_school in (self.JUNIOR, self.SENIOR)
However, if you expect the list of permissions to be fluid and change often, you should consider making the permissions a Model of their own, and simply use a ForeignKey (or ManyToMany) relationship,

Can I relate one-to-one to a key-value model?

Consider this Django schema:
class Foo(models.Model):
# ...
class FooOption(models.Model):
foo = models.ForeignKey(foo, related_name='options')
key = models.CharField(max_length=64)
value = models.TextField()
class Meta:
unique_together = [('foo', 'key')]
Essentially, FooOptions works a key-value set for each Foo.
<edit>
There's a known set of keys that the system uses,
Every Foo has a number of (key, value) pairs, where values are arbitrary,
Or equivalently, Every Foo can have one value for every single key.
</edit>
As there's limited number of keys in FooOption, I'd like to rephrase this relation a bit using Django's ORM. The current design pictures the relation as a 1-n between a Foo and FooOptions; I'd like the code to picture it as 1-1 between a Foo and each specific FooOption key.
This would allow me to access each options like this:
class Foo(models.Model):
# ...
opt1 = OptionField('colour')
opt2 = OptionField('size')
foo = Foo()
foo.opt1 = 'something'
foo.save()
Especially, I'd like to be able to select_related specific FooOptions when querying for many Foos, to obtain an ORM-ed equivalent of:
SELECT foo.*, opt_colour.value, opt_size.value
FROM foo
LEFT JOIN foo_option opt_colour
ON foo.id = foo_option.foo_id AND foo_option.key = 'id'
LEFT JOIN foo_option opt_size
ON foo.id = foo_option.foo_id AND foo_option.key = 'size';
Is it possible to code such custom OptionField? How?
I may be misinterpreting your design, but it looks like you should actually be going with a Many-to-many relationship. You seem to want to have multiple options for each Foo instance (i.e. colour, size, etc.) and I imagine you would want a specific colour or size to be available to describe various Foo's.
class Foo(models.Model):
options = models.ManyToManyField('FooOption')
# Other attributes.
class FooOption(models.Model):
key = models.CharField(max_length=64)
value = models.TextField()
class Meta:
unique_together = [('key', 'value')]

How can I do INNER JOIN in Django in legacy database?

Sorry for probably simple question but I'm a newby in Django and really confused.
I have an ugly legacy tables that I can not change.
It has 2 tables:
class Salespersons(models.Model):
id = models.IntegerField(unique=True, primary_key=True)
xsin = models.IntegerField()
name = models.CharField(max_length=200)
surname = models.CharField(max_length=200)
class Store(models.Model):
id = models.IntegerField(unique=True, primary_key=True)
xsin = models.IntegerField()
brand = models.CharField(max_length=200)
So I suppose I can not add Foreign keys in class definitions because they change the tables.
I need to execute such sql request:
SELECT * FROM Salespersons, Store INNER JOIN Store ON (Salespersons.xsin = Store.xsin);
How can I achieve it using Django ORM?
Or I'm allowed to get Salespersons and Store separately i.e.
stores = Store.objects.filter(xsin = 1000)
salespersons = Salespersons.objects.filter(xsin = 1000)
Given your example query, are your tables actually named Salespersons/Store?
Anyway, something like this should work:
results = Salespersons.objects.extra(tables=["Store"],
where=["""Salespersons.xsin = Store.xsin"""])
However, given the names of the tables/models it doesn't seem to me that an inner join would be logically correct. Unless you always have just 1 salesperson per store with same xsin.
If you can make one of the xsin fields unique, you can use a ForeignKey with to_field to generate the inner join like this:
class Salespersons(models.Model):
xsin = models.IntegerField(unique=True)
class Store(models.Model):
xsin = models.ForeignKey(Salespersons, db_column='xsin', to_field='xsin')
>>> Store.objects.selected_related('xsin')
I don't see why you can't use the models.ForeignKey fields even if the database lacks the constraints -- if you don't explicitly execute the SQL to change the database then the tables won't change. If you use a ForeignKey then you can use Salespersons.objects.select_related('xsin') to request that the related objects are fetched at the same time.

Categories