Constrain Django model by field value - django

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.

Related

How to set 2 attributes to primary key together in 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.

Django - What are the advantages and disadvantages of using unique_togueter vs. using a queryset in the view?

Suppose we have the next model:
class Publications(models.Model):
author = ..........
post = ..........
and we don't want duplicate records to be stored in the database.
This could be done with unique togheter on the model:
Meta:
unique_together = (author, post)
or it could be done in the view with something like:
register_exist = Publications.objects.filter(...).exists()
if register_exist == False:
#Code to save the info
What are the advantages or disadvantages of using these methods?
Meta:
unique_together = (author, post)
Constrain at database level. This make the data always consistent no matter what views input the data.
But the other one:
register_exist = Publications.objects.filter(...).exists()
if register_exist == False:
#Code to save the info
Constrain at application level. There might be a cost to query and check if the record is existing or not. And the data might not be consistent among the application when somebody might add new record without this step (by incident or accident), that make the data no longer consistent anymore.
In a nutshell, the unique_together attribute create a UNIQUE constraint whereas the .filter(..) is used to filter the QuerySet wrt the given conditions.
In other words, If you applied unique_together functionality in your model, you can't break that constraint (technically possible, but) even if you try to do so.

Django can not makemigrations: can't create form field for followers yet. Because its related model 'self' has not been loaded yet

This is the model.
class CustomUser(AbstractUser):
followers = ArrayField(ArrayField(models.ForeignKey('self',
related_name = 'following_set',
on_delete = models.CASCADE ), size = 1))
followings = ArrayField(ArrayField(models.ForeignKey('self',
related_name = 'follower_set',
on_delete = models.CASCADE ), size = 1))
As the documentation on an ArrayField [Django-doc] says, you can not use a ForeignKey [Django-doc]:
base_field
This is a required argument.
Specifies the underlying data type and behavior for the array. It
should be an instance of a subclass of Field. For example, it could
be an IntegerField or a CharField. Most field types are permitted,
with the exception of those handling relational data
(ForeignKey, OneToOneField and ManyToManyField).
Furthermore ArrayFields are usually not good practice anyway. A lot of database backends do not support these fields, and furthermore they easily result in complex queries, and frequently there are no efficient indexing structures on these anyway: if you want to find out what the common followers are of two users, that will result in some complicated logic. If you have to query inside the array, then it definitely does not follow the conditions of the first normal form (1NF) [wiki] of databases. Although of course one can debate if 1NF always improves the overall quality of a database, I think it is defintely an extra argument to say that ArrayFields should usually be used as a "last resort", or if the array is an "atomical" object.
Finally by using two arrays, it will be a technical challenge to keep the two in sync: if a is no longer following b, then a should be removed from the followers of b, but b should also be removed from the followings of a. This may look easy, but eventually the number of use cases will grow, and eventually it will result in some bugs.
You can construct a ManyToMany model [Django-doc], like:
class CustomUser(AbstractUser):
followers = models.ManyToManyField(
'self',
symmetrical=False,
related_name='following'
)
This will create a hidden table in between. By specifying symmetrical=False [Django-doc] this means that if a user u1 is following a user u2, then u2 is not per se following u1.
If you want to add extra data in a "following" relation, like the timestamp when a user started following another user, you can make a model like Follow, and specify this as the through= model of your followers.

How to add Foreign Keys to Django Field History?

I'm trying to track Foreign Keys using django-field-history, but when I add it, it does additional queries on every page using the Model
For example
from field_history.tracker import FieldHistoryTracker
class Author(models.Model):
user = models.ForeignKey('auth.user)
field_history = FieldHistoryTracker(['user'])
will always give more queries on pages using Author, like so
SELECT ••• FROM "auth_user" WHERE "auth_user"."id" = '2'
1239 similar queries. Duplicated 1235 times.
I've tried using user_id instead of user in Field History Tracker, but it will always return None. Using user.id or anything like it just returns an error.
I really need to keep that history data, but not at the cost of thousands of additional queries.
Also, would really enjoy keeping django-field-history as my whole DB is using it, but I'm aware I might have to switch package, and if so, which one would you advise ?
As far as my understanding goes, you are trying to log which user has updated, for this you should use _field_history_user as described in the documentation.
For example:
class Pizza(models.Model):
name = models.CharField(max_length=255)
updated_by = models.ForeignKey('auth.User')
field_history = FieldHistoryTracker(['name'])
#property
def _field_history_user(self):
return self.updated_by
It would always update which user has updated the row for this table.

How to filter the options for a ForeignKey based on an other ForeignKey

I need to filter the options for a ForeignKey based on an other ForeignKey.
class LabValue(models.Model):
measurement = models.ForeignKey(
'LabMeasurement', on_delete=models.CASCADE)
unit = models.ForeignKey(
LabMeasurementUnit,
on_delete=models.CASCADE,
limit_choices_to={'parameter__id': self.measurement.parameter.id},
)
How can I retrieve self.measurement.parameter.id? If I manually enter an ID instead of self.measurement.parameter.id like for example "1" the query works.
def __str__(self):
return str(self.measurement.parameter.id)
also works as desired and returns e. g. 1 as result
I don't believe this is possible, not due to Django's ORM, but due to the way the database itself works. measurement is a ForeignKey, so it's a relationship between two tables. But relationships between tables don't deal with filtering.
Thus, you will have to implement this at the level of the forms which update the model, or, alternatively, override the model's save function and throw an error if the desired expectation is not satisfied.
For a very clear answer: You can't do that.
Explanation:
It's a django model when you create model object/save then only your model object is created and on that time valid foreign key of specified instance is to be passed, so giving a limit to specific choices to model directly is not possible you need to handle it from view.
So if you have handle the choices of option in view but as of alternatively you can override clean() method to raise errors.
in model you can use clean method as below:
def clean(self):
if self.measurement.parameter.id == 1:
# .. do whatever you want to write if this condition happens
pass
elif self.measurement.parameter.id in [1,2,3,4,55,6]: # or just pass set of ids, statically or with queryset
# .. code
else:
raise Exception("Unexpected selection of choice!!!")