1

As I was searching the ways of adding the model validation I found more than one way to achieve it. For eg if my model is as follows:

from django.db import models
from django.contrib.auth import get_user_model

User = get_user_model()


class ChatThread(models.Model):
    user1 = models.ForeignKey(User, on_delete=models.CASCADE)
    user2 = models.ForeignKey(User, on_delete=models.CASCADE)
    

I want to validate such that user1 & user2 are different. Then I could have following options:

  • override the save method
  • implement clean or full_clean method
  • use signals pre_save
  • ...

What would be the best approach for these kind of task ??

Abishek Bashyal
  • 307
  • 1
  • 6
  • 15
  • 2
    For this simple validation, I would use the `unique_together` attribute of the `Meta` class of your `ChatThread` model. – May.D Mar 17 '21 at 13:14
  • thanks @May.D, `unique_together` attribute would be suitable here – Abishek Bashyal Mar 17 '21 at 14:39
  • 1
    @May.D adding a unique constraint is probably a good idea, however it doesn't solve the validation requirement in question. For example, inserting `ChatThread(user1_id=1, user2_id=1)` would pass the unique constraint, but result in identical users in the same instance. – Lord Elrond Mar 17 '21 at 15:42
  • Indeed you are right, in fact I just tested it and `unique_together` doesnt work for foreign keys, as explained here : https://stackoverflow.com/a/38081616/8237838 (see also comments). My mistake. – May.D Mar 17 '21 at 16:30

1 Answers1

4

I would add a database level check constraint to your model, which can be done with the following:

from django.db.models import Q, F

class ChatThread(models.Model):
    user1 = models.ForeignKey(User, on_delete=models.CASCADE)
    user2 = models.ForeignKey(User, on_delete=models.CASCADE)

    class Meta:
        constraints = [
            models.CheckConstraint(check=(
                ~Q(user1=F('user2'))
            ), name='check_users_not_equal')
        ]

The line Q(user1=F('user2')) checks that the user1 field is equal to the user2 field, and the tilde ~, checks that the preceding Q object is not true.

Is a database level check constraint better/more effective than the conventional methods shown above?

The general argument in favor of application-level validation is the DRY Principle. For example, if you have a model with an email field, and a database-level constraint verifying that the input is a valid email, you would end up writing the validation logic twice; once for the database-constraint, and once in the client-side code. You could forgo the client-side validation, but that would result in your users receiving generic error messages anytime they enter an invalid email.

In your specific case, I would strongly recommend database-level validation over application-level validation. If a piece of logic violates this rule (ie. user1 != user2), then it is almost guaranteed to be the result of broken code, and not user-input, which means the end user will receive a generic error message in either case.

There are more pros and cons to each approach that I didn't mention. If you want to read more on this topic, I'd start here: Should you enforce constraints at the database level as well as the application level?

Lord Elrond
  • 13,430
  • 7
  • 40
  • 80