10 Django Model Field Tricks You Probably Didn’t Know About

1. Customizing Field Validation with clean()
Did you know you can add custom validation logic to specific fields using the clean()
method?
from django.db import models
from django.core.exceptions import ValidationError
class Product(models.Model):
name = models.CharField(max_length=100)
price = models.DecimalField(max_digits=10, decimal_places=2)
def clean(self):
if self.price <= 0:
raise ValidationError("Price must be greater than zero.")
Use
clean()
in your save logic or forms to ensure that invalid data never sneaks into your database.
2. Conditional Default Values
Sometimes, you need default values to change dynamically based on some condition.
from django.utils.timezone import now
class Event(models.Model):
name = models.CharField(max_length=100)
start_date = models.DateTimeField(default=now)
end_date = models.DateTimeField(default=lambda: now() + timedelta(days=7))
This is great for timestamps or automatically generating default future dates.
3. Unique Constraints for Multi-Field Uniqueness
Replace the deprecated unique_together with UniqueConstraint for better control.
from django.db import models
class Membership(models.Model):
user = models.ForeignKey('auth.User', on_delete=models.CASCADE)
group = models.ForeignKey('Group', on_delete=models.CASCADE)
class Meta:
constraints = [
models.UniqueConstraint(fields=['user', 'group'], name='unique_membership')
]
This ensures no user can be added to the same group twice.
4. Using choices
with Enums
Django’s choices
make fields cleaner, especially with Enums.
from django.db import models
class Status(models.TextChoices):
PENDING = 'P', 'Pending'
APPROVED = 'A', 'Approved'
REJECTED = 'R', 'Rejected'
class Request(models.Model):
status = models.CharField(max_length=1, choices=Status.choices, default=Status.PENDING)
Access your choices like
Status.PENDING
instead of raw strings.
5. through
for Many-to-Many Customization
Need extra data in your Many-to-Many relationships? Use through
.
class Author(models.Model):
name = models.CharField(max_length=100)
class Book(models.Model):
title = models.CharField(max_length=100)
authors = models.ManyToManyField(Author, through='Authorship')
class Authorship(models.Model):
author = models.ForeignKey(Author, on_delete=models.CASCADE)
book = models.ForeignKey(Book, on_delete=models.CASCADE)
role = models.CharField(max_length=50) # e.g., "Co-Author", "Editor"
This allows you to track additional details about the relationship.
6. Field-Level Permissions with editable=False
Prevent direct editing in admin while still allowing updates programmatically.
class Order(models.Model):
total_price = models.DecimalField(max_digits=10, decimal_places=2, editable=False)
You can still update
total_price
in your code but not in the admin.
7. Custom File Upload Paths
Organize uploaded files dynamically with upload_to
.
def upload_to(instance, filename):
return f"uploads/{instance.user.id}/{filename}"
class Profile(models.Model):
user = models.OneToOneField('auth.User', on_delete=models.CASCADE)
avatar = models.ImageField(upload_to=upload_to)
Uploaded files are neatly stored based on the user’s ID.
8. Using Property
Fields in Models
Django models can include computed properties.
class Employee(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
@property
def full_name(self):
return f"{self.first_name} {self.last_name}"
Access it like a field:
employee.full_name
.
9. Auto-Setting Fields with save()
Customize field behavior by overriding save()
.
class Article(models.Model):
title = models.CharField(max_length=100)
slug = models.SlugField(unique=True, blank=True)
def save(self, *args, **kwargs):
if not self.slug:
self.slug = self.title.lower().replace(' ', '-')
super().save(*args, **kwargs)
Automatically generate slugs from titles if not provided.
10. Soft Deletes with is_archived
Instead of deleting records, archive them.
class BaseModel(models.Model):
is_archived = models.BooleanField(default=False)
def delete(self, *args, **kwargs):
self.is_archived = True
self.save()
class Meta:
abstract = True
Your data stays safe while appearing “deleted” to the user.
Conclusion:
Django’s model system is powerful, and these tricks can help you write cleaner, more efficient code. Which of these tricks are you already using? What’s your favorite?