The hidden pitfalls of unique_for_date

Can you spot the maintainability problem with this code?

from django.db import modelsclass MyModel(models.Model):
date = models.DateField()
text = models.CharField(unique_for_date='date')

is the culprit. It’s meant to make sure will be unique for . However, has a pitfalls:

  • It’s checked only if is called, so if is called without first calling then you’re not going to have a great time.
  • It won’t be checked even if is called when using a ModelForm if the form that does no include a field involved in the check.
  • Only the date portion of the field will be considered, even when validating a .
  • The constraint is not enforced by the database.

That’s a lot of caveats to keep in mind when building a mental model of the code we’re working on. Lots of room there for the unexpected to creep in:

  • If a developer does in the shell and forgets to first call . Yes we should not SSH into the production shell and create records ad hoc, but most people have done it at least once.
  • If a view or serializer does or without calling . Code review can catch this but if code humans could catch 100% of mistakes 100% of time with 100% consistency then we wouldn’t need code review in the first place because such Übermensch would not create bugs in the first place.

So has many pitfalls to be triggered by human error. When implementing the fields the developer may conclude that these problems don’t apply for the specific problem they’re solving, and they trust themselves, their current and future team mates not to make mistakes. However, over time requirements changes. Over time things tend to get more different, not more similar. Code entropy is real. As the situation on the ground changes can we be sure that one of those problems won’t be hit? What’s your risk appetite?

Avoiding the problem

Instead of nice and small but brittle:

from django.db import modelsclass MyModel(models.Model):
date = models.DateField()
text = models.CharField(unique_for_date=’date’)

We can do a more verbose, less DRY, but simultaneously more explicit and more future proof:

class ExampleModel(models.Model):
date = models.DateField()
text= models.CharField()
def save(self, *args, **kwargs):
# change specific filter depending on need.
if self.objects.filter(date=self.date, text=self.text).exists():
raise ValidationError({‘name’: ‘Nein!’})
return super().save(*args, **kwargs)

This validation will be called whenever is called, but unfortunately not when is called, but there’s no silver bullet here.

Does your codebase use `unique_for`?

It’s easy for tech debt to slip in. I can check that for you at django.doctor. I’m a GitHub bot that suggest Django improvements to your code:

If you would prefer code smells not make it into your codebase, I also review pull requests:

See the GitHub PR bot and reduce dev effort of improving your code.

I’m a GitHub bot that automatically improves your Django. https://django.doctor

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store