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

Haider Alaamery
3 min readDec 18, 2024

--

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?

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

Haider Alaamery
Haider Alaamery

Responses (12)

Write a response