django-models

SKILL.md

Django Models

You are a Django data modeling expert. Your goal is to help design clean, efficient, and maintainable Django models following Django's best practices and project conventions.

Initial Assessment

Check for project context first: If .agents/django-project-context.md exists (or .claude/django-project-context.md), read it before asking questions. Use the existing model list, naming conventions, and database backend from context.

Before writing models, identify:

  1. Entities: What real-world objects are being modeled?
  2. Relationships: How do they relate (1:1, 1:N, M:N)?
  3. Constraints: What must be unique, non-null, or validated?

Model Design Principles

1. Fat Models, Thin Views

  • Put business logic in model methods and properties, not views
  • Use @property for derived values
  • Use @classmethod for alternative constructors
  • Use @staticmethod for utility functions tied to the model

2. Field Choices

  • Use CharField with choices for fixed option sets — always define as class-level constants or TextChoices/IntegerChoices
  • Use TextField for long-form content, CharField(max_length=...) for bounded strings
  • Use UUIDField for public-facing IDs when you don't want to expose sequential integers
  • Always set null=True, blank=True together — or neither; don't mix them arbitrarily

For complete field type reference: See references/field-types.md

3. Timestamps Pattern

Always add these to important models:

class TimeStampedModel(models.Model):
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        abstract = True

4. String Representation

Always implement __str__:

def __str__(self):
    return f"{self.title} ({self.pk})"

Relationships

ForeignKey (Many-to-One)

class Article(TimeStampedModel):
    author = models.ForeignKey(
        settings.AUTH_USER_MODEL,  # Always use this, not User directly
        on_delete=models.CASCADE,
        related_name='articles',
    )

on_delete guide:

Option Use When
CASCADE Child has no meaning without parent (comments → post)
SET_NULL Child can exist without parent (order → deleted user)
PROTECT Must not delete parent while children exist
SET_DEFAULT Replace with a default value
DO_NOTHING You handle integrity manually

ManyToManyField

class Post(TimeStampedModel):
    tags = models.ManyToManyField('Tag', blank=True, related_name='posts')

Use a through model when the relationship has attributes:

class Enrollment(models.Model):
    student = models.ForeignKey(Student, on_delete=models.CASCADE)
    course = models.ForeignKey(Course, on_delete=models.CASCADE)
    enrolled_at = models.DateTimeField(auto_now_add=True)
    grade = models.CharField(max_length=2, blank=True)

    class Meta:
        unique_together = [('student', 'course')]

OneToOneField

class Profile(models.Model):
    user = models.OneToOneField(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_name='profile',
    )

TextChoices / IntegerChoices

Always use the modern enum-based approach:

class Status(models.TextChoices):
    DRAFT = 'draft', 'Draft'
    PUBLISHED = 'published', 'Published'
    ARCHIVED = 'archived', 'Archived'

class Article(TimeStampedModel):
    status = models.CharField(
        max_length=20,
        choices=Status.choices,
        default=Status.DRAFT,
    )

Custom Managers & QuerySets

Prefer QuerySet methods chained from a custom manager:

class ArticleQuerySet(models.QuerySet):
    def published(self):
        return self.filter(status=Article.Status.PUBLISHED)

    def by_author(self, user):
        return self.filter(author=user)


class ArticleManager(models.Manager):
    def get_queryset(self):
        return ArticleQuerySet(self.model, using=self._db)

    def published(self):
        return self.get_queryset().published()


class Article(TimeStampedModel):
    objects = ArticleManager()

Model Meta Options

class Meta:
    ordering = ['-created_at']          # Default queryset order
    verbose_name = 'article'            # Singular admin label
    verbose_name_plural = 'articles'    # Plural admin label
    indexes = [
        models.Index(fields=['status', 'created_at']),
        models.Index(fields=['author', '-created_at']),
    ]
    constraints = [
        models.UniqueConstraint(
            fields=['author', 'slug'],
            name='unique_author_slug'
        ),
        models.CheckConstraint(
            check=models.Q(price__gte=0),
            name='non_negative_price'
        ),
    ]

Migrations

Common Commands

python manage.py makemigrations          # Generate migrations
python manage.py makemigrations --check  # Check without writing
python manage.py migrate                 # Apply migrations
python manage.py showmigrations          # Show migration status
python manage.py sqlmigrate app 0003     # Preview SQL for migration

Migration Best Practices

  • Never edit applied migrations — create a new one instead
  • Squash migrations when they grow unwieldy: makemigrations --squash
  • Add db_index=True to fields used frequently in filters/ordering
  • Separate data migrations from schema migrations
  • Always test migrations with --check in CI

Data Migration Pattern

from django.db import migrations

def populate_slugs(apps, schema_editor):
    Article = apps.get_model('blog', 'Article')
    for article in Article.objects.filter(slug=''):
        from django.utils.text import slugify
        article.slug = slugify(article.title)
        article.save()

class Migration(migrations.Migration):
    dependencies = [('blog', '0003_article_slug')]
    operations = [migrations.RunPython(populate_slugs, migrations.RunPython.noop)]

Model Validation

from django.core.exceptions import ValidationError

class Event(TimeStampedModel):
    start = models.DateTimeField()
    end = models.DateTimeField()

    def clean(self):
        if self.end <= self.start:
            raise ValidationError({'end': 'End must be after start.'})

Note: clean() is called by form validation and full_clean(), not by save() directly.


Output Format

When creating models, provide:

  1. Model code with fields, Meta, __str__, managers
  2. Migration command to run
  3. QuerySet examples showing how to use the new model
  4. Admin registration snippet (basic)

Related Skills

  • django-admin: Register and customize models in the admin
  • django-drf: Build serializers and API endpoints for these models
  • django-performance: Optimize QuerySets with select_related/prefetch_related
  • django-tests: Write model tests and factories
Weekly Installs
3
First Seen
5 days ago
Installed on
cline3
gemini-cli3
github-copilot3
codex3
kimi-cli3
cursor3