How to set 2 attributes to primary key together in Django? - django

I have a model in Django:
class Subject(models.Model):
level = models.CharField(max_length=50)
subject_name = models.CharField(max_length=50)
teacher_name = models.ForeignKey(Teacher, on_delete=models.CASCADE)
total_seats = models.IntegerField()
subject_details = models.CharField(max_length=50)
For the Subject table I want the level and the subject_name together to be primary keys. In fact, I dont want any other objects to have the same name and level. I know I can use unique_together but where do I mention the primary_key = True?

You don't. Django does not work with composite primary keys. This is specified in the documentation:
Each model requires exactly one field to have primary_key=True (either explicitly declared or automatically added).
In the FAQ section it also continues with:
Do Django models support multiple-column primary keys?
No. Only single-column primary keys are supported.
But this isn’t an issue in practice, because there’s nothing stopping
you from adding other constraints (using the unique_together model
option or creating the constraint directly in your database), and
enforcing the uniqueness at that level. Single-column primary keys are
needed for things such as the admin interface to work; e.g., you need
a single value to specify an object to edit or delete.
It is a feature that is often requested (see for example this Django ticket), but it was not implemented. It will probably be quite cumbersome, first of all a lot of existing Django tooling will need to be updated (for example JOINs should be done with the two keys, FOREIGN KEYs should then result in two or more fields constructed, etc.). But another, and probably even more severe problem might be the large number of packages built on top of Django that make the assumption that the primary key is not a composite. It would thus break a lot of packages in the Django "ecosystem".
There are some packages like django-compositekey [GitHub] that aim to implement this. But the last update is made in october 2014.
It is not per se a problem not to make it a primary key. In fact Django's GenericForeignKey [Django-doc] only works if the primary keys are all of the same type. So using unique_together should be sufficient. Normally this will also make a UNIQUE INDEX at the databaes side.

I think you want this 2 fields indexed by database because the main cause of primary key is to make field unique and indexed by the DBMS, so you can make your fields unique_together in Meta class and set db_index=True in field args.

Related

bulk create in django with foreign key

Models:
class Author(Base):
name = models.CharField(max_length=100, unique=True)
class Book(Base):
name = models.CharField(max_length=100, unique=True)
class AuthorBookAssn(Base):
author = models.ForeignKey(Author, on_delete=models.PROTECT)
book = models.ForeignKey(Book, on_delete=models.CASCADE)
I have an api to create a book, and along with the book data we would also get a list of author ids.
Now for each book we need to create one/more records (depending on the author ids provided) in the AuthorBookAssn table.
What is the best way to do this and can the create be done in bulk.
Currently the approach is to get the author objects for each of the ids in the list and then call
AuthorBookAssn.objects.create(book=book_instance,author=author_instance)
You've created a many-to-many relationship so your current method is the only possible way based on your current structure. If you were to use Django's in-built m2m field then you would essentially do the same except you would do something like author.books.add(book), but again, you would have to do this separately to your book/author creation. An alternative would be to use a many-to-one relation (i.e. ForeignKey field) which would allow you to connect the two when an object is created. Many-to-One might not be how you want to structure things if books can have multiple authors and vice-versa.
(supplementary to OsVoid's answer)
There might be some degree of optimization by working with the object ids (primary key values) rather than fetching the entire objects. Premature optimization is a bad idea, and you'd have to benchmark this idea to see if any improvement is measurable (assuming you have any need to optimize at all).
Given book_pk and author_pk you can use the "magic" _id suffix:
AuthorBookAssn.objects.create(book_id=book_pk,author_id=author_pk)
And instead of fetching whole objects, you might fetch just their pk values using a .values_list('pk') in a queryset. (with flat=True if only the one value is being requested). Since this is just a number, it also might be possible to attach it to some other objects that you really do need to obtain, using annotation.
Also, you can cause your own model to be used for the association in a Django ManyToMany relation, using "through". This is valuable if you want to store extra information about the association, such as when it was created, who by, for what purpose, etc.

Constrain Django model by field value

Consider the following Django model:
class Account(models.Model):
ACCOUNT_CHOICES = [
('m', 'Main',),
('s','Secondary'),
('o', 'Other')
]
user = models.ForeignKey(User)
level = models.CharField(max_length=1, choices=ACCOUNT_CHOICES)
How can I enforce a database constraint of a maximum of one 'Main' account per user, while still allowing users any number of 'Secondary' or 'Other' accounts? In a sense, I want unique_together for user and level, but only when the value of level is m.
I know that I can manually check on saving, but I would prefer the database to check automatically and raise an IntegrityError when appropriate.
I don't think you can do that with your current model, but if those are the only two choices for the level field, consider changing it to a nullable BooleanField, for example
is_main = models.BooleanField(null=True)
and set it to None for secondary accounts. Then a unique_together will work because every null value is unique as far as SQL is concerned (see this answer).
Since there are more choices for the level field as you later clarified, you may add a third field and possibly override the .save() method to have it automatically set to None if level is not "m" for extra convenience.
Edit: If you are not concerned about portability, #Trent has suggested that PostgreSQL supports partial unique indexes, for example:
create unique index u_i on accounts(user_id, level_id) WHERE level_id = 'm';
Here is an SQL Fiddle.
Edit 2: Actually it looks like it is finally possible to create partial indexes in Django ORM starting from Django 2.2. See this question for details.

Django ORM "." vs "_" when access the id of foreign key

class Author(models.Model):
name = models.CharField(max_length=120)
class Book(models.Model):
name = models.CharField(max_length=20)
author= models.ForeignKey(Author)
Consider those two models. When I access this foreign key, what's the difference between those two ways:
id= Book.objects.get(pk=1).author.id
id= Book.objects.get(pk=1).author_id
Semantically there is no difference. But the version with author_id will be more efficient than author.id.
If you define a foreign key, then you actually defined two Django fields at once: the fieldname, which is a reference to a model object to the model to which you refer, and a field fieldname_id, that contains the value of the primary key to the object to which you refer. Only the latter is stored in the database (since the former can usually not be stored in a database).
Note that if you want to access the .author, usually this means that you have to perform an extra query, since those relations, unless explicitly loaded with .select_related(..), are not loaded immediately, but lazily: it requires an extra database to obtain the relevant Author object. Of course one extra query does not matter that much, but if you would for example do this in a for loop, then this results in the n+1-problem: you will need 1 query to fetch the books, and n queries to fetch the author of every book.
Note that there are - as mentioned before - ways to reduce the amount of querying for related objects with prefetch_related, and select_related. But still it will result in transferring more data. If you are only interested in the primary key of the Author, then you can use author_id, which does not require such extra fetch.

How to create a primary key consists of two fields in Django?

I develop a certain application, which I found with the specified database and model schema. I am using Django version 1.8.2. Below is presented a problem. Unnecessary fields have been omitted, model names are invented for the purposes of an example, because I can not disclose. Consider the following models A and B.
class B (models.Model):
name = models.CharField(max_length=100)
class A (models.Model):
name = models.CharField(max_length=100, primary_key=True)
related_name = models.ForeignKey(B, null=True, blank=True)
After a long time a project the possibility that there may be several of the same name A, but with different foreign key B. In this particular case, I would like to model the primary key "A" consisted of two fields: name and related name. How to create such a key consists of two fields in django?
You want to use a composite key. Django does not support this See here. There is some support but you can't have relationships so it's pretty limited as far as practical usage.
Currently Django models only support a single column in this set,
denying many designs where the natural primary key of a table is
multiple columns. Django currently can't work with these schemas; they
must instead introduce a redundant single-column key (a “surrogate”
key), forcing applications to make arbitrary and otherwise-unnecessary
choices about which key to use for the table in any given instance.
Django does not support composite keys. But you could use unique-together
unique_together = ("name", "related_name")

unique_together foreign key object properties

I've got two models: Common and ARecord. ARecord has a ForeignKey relationship to Common. I want to ensure that ARecord is unique with a combination of items from ARecord and Common.
class Common(models.Model):
NAIC_number = models.CharField(max_length=5)
file_location_state = models.CharField(max_length=2)
file_location_code = models.CharField(max_length=2)
class ARecord(models.Model):
common = models.ForeignKey(Common)
coverage_code = models.CharField(max_length=6)
record_type = models.CharField(max_length=1)
class Meta:
unique_together = ('coverage_code', 'common__NAIC_number')
However, when I attempt to access the foreign key object property via the usual double underscore, I get a model validation error.
`arecord.arecord: "unique_together" refers to common__NAIC_number, a field that doesn't exist. Check your syntax.`
This seems like it should be possible and, a slightly different question was asked that indicates it is , but perhaps I'm missing something obvious?
As Manoj implies, you can't do this with unique_together, because that is a database constraint and the sort of thing you want can't be done with database constraints.
Instead, you want do this programmatically, probably via model validation, which will ensure that no instances are created that violate your constraint.
This doesn't make sense to me. The documentation defines unique_together thus:
This is a list of lists of fields that must be unique when considered together. It's used in the Django admin and is enforced at the database level (i.e., the appropriate UNIQUE statements are included in the CREATE TABLE statement).
(Emphasis added)
I don't know how an UNIQUE statement can be added at the database level for such a case (using one column in the current table and another in a different table accessed through a foreign key). I hope those who know better about databases will correct me if I am wrong.