django-models

When the user wants to design Django models, define fields, write migrations, or build QuerySets and managers. Use when the user says "create a model," "add a field," "write a migration," "database schema," "ORM query," "QuerySet," "related fields," "ForeignKey," "ManyToMany," "model mixin," "abstract model," "model manager," "custom manager," or "database index." For REST API serializers based on models, see django-drf. For admin display of models, see django-admin.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "django-models" with this command: npx skills add ristemingov/django-claude-setup/ristemingov-django-claude-setup-django-models

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:

OptionUse When
CASCADEChild has no meaning without parent (comments → post)
SET_NULLChild can exist without parent (order → deleted user)
PROTECTMust not delete parent while children exist
SET_DEFAULTReplace with a default value
DO_NOTHINGYou 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

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

django-admin

No summary provided by upstream source.

Repository SourceNeeds Review
General

django-views

No summary provided by upstream source.

Repository SourceNeeds Review
General

django-auth

No summary provided by upstream source.

Repository SourceNeeds Review