home / skills / personamanagmentlayer / pcl / django-expert

django-expert skill

/stdlib/frameworks/django-expert

This skill provides expert Django guidance to build robust web apps with ORM, admin, authentication, and REST capabilities.

npx playbooks add skill personamanagmentlayer/pcl --skill django-expert

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
10.9 KB
---
name: django-expert
version: 1.0.0
description: Expert-level Django development for robust Python web applications with ORM, admin, and authentication
category: frameworks
tags: [django, python, web, orm, mvc, rest-framework]
allowed-tools:
  - Read
  - Write
  - Edit
  - Bash(python:*, django-admin:*, manage.py:*)
---

# Django Expert

Expert guidance for Django - high-level Python web framework for building secure, scalable web applications with batteries included.

## Core Concepts

### Django Architecture
- MVT (Model-View-Template) pattern
- ORM (Object-Relational Mapping)
- Admin interface
- Authentication system
- URL routing
- Template engine
- Forms and validation

### Key Components
- Models (database tables)
- Views (business logic)
- Templates (presentation)
- URLs (routing)
- Forms (user input)
- Middleware (request/response processing)

## Project Setup

```bash
# Install Django
pip install django

# Create project
django-admin startproject myproject
cd myproject

# Create app
python manage.py startapp myapp

# Run migrations
python manage.py migrate

# Create superuser
python manage.py createsuperuser

# Run server
python manage.py runserver
```

## Models

```python
# myapp/models.py
from django.db import models
from django.contrib.auth.models import User
from django.utils import timezone

class Post(models.Model):
    STATUS_CHOICES = [
        ('draft', 'Draft'),
        ('published', 'Published'),
    ]

    title = models.CharField(max_length=200)
    slug = models.SlugField(max_length=200, unique=True)
    author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='posts')
    content = models.TextField()
    status = models.CharField(max_length=10, choices=STATUS_CHOICES, default='draft')
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    published_at = models.DateTimeField(null=True, blank=True)

    class Meta:
        ordering = ['-created_at']
        indexes = [
            models.Index(fields=['-created_at']),
            models.Index(fields=['slug']),
        ]

    def __str__(self):
        return self.title

    def save(self, *args, **kwargs):
        if self.status == 'published' and not self.published_at:
            self.published_at = timezone.now()
        super().save(*args, **kwargs)

class Comment(models.Model):
    post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='comments')
    author = models.ForeignKey(User, on_delete=models.CASCADE)
    content = models.TextField()
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ['created_at']

    def __str__(self):
        return f'Comment by {self.author} on {self.post}'
```

## Views

```python
# myapp/views.py
from django.shortcuts import render, get_object_or_404, redirect
from django.contrib.auth.decorators import login_required
from django.views.generic import ListView, DetailView, CreateView
from django.contrib import messages
from .models import Post, Comment
from .forms import PostForm, CommentForm

# Function-based view
def post_list(request):
    posts = Post.objects.filter(status='published').select_related('author')
    return render(request, 'blog/post_list.html', {'posts': posts})

def post_detail(request, slug):
    post = get_object_or_404(Post, slug=slug, status='published')
    comments = post.comments.select_related('author')

    if request.method == 'POST':
        form = CommentForm(request.POST)
        if form.is_valid():
            comment = form.save(commit=False)
            comment.post = post
            comment.author = request.user
            comment.save()
            messages.success(request, 'Comment added successfully')
            return redirect('post_detail', slug=slug)
    else:
        form = CommentForm()

    return render(request, 'blog/post_detail.html', {
        'post': post,
        'comments': comments,
        'form': form
    })

# Class-based views
class PostListView(ListView):
    model = Post
    template_name = 'blog/post_list.html'
    context_object_name = 'posts'
    paginate_by = 10

    def get_queryset(self):
        return Post.objects.filter(status='published').select_related('author')

class PostDetailView(DetailView):
    model = Post
    template_name = 'blog/post_detail.html'
    context_object_name = 'post'

    def get_queryset(self):
        return Post.objects.filter(status='published')

class PostCreateView(CreateView):
    model = Post
    form_class = PostForm
    template_name = 'blog/post_form.html'

    def form_valid(self, form):
        form.instance.author = self.request.user
        return super().form_valid(form)
```

## URLs

```python
# myproject/urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('myapp.urls')),
]

# myapp/urls.py
from django.urls import path
from . import views

app_name = 'blog'

urlpatterns = [
    path('', views.PostListView.as_view(), name='post_list'),
    path('post/<slug:slug>/', views.post_detail, name='post_detail'),
    path('post/create/', views.PostCreateView.as_view(), name='post_create'),
]
```

## Forms

```python
# myapp/forms.py
from django import forms
from .models import Post, Comment

class PostForm(forms.ModelForm):
    class Meta:
        model = Post
        fields = ['title', 'slug', 'content', 'status']
        widgets = {
            'content': forms.Textarea(attrs={'rows': 10}),
        }

    def clean_slug(self):
        slug = self.cleaned_data['slug']
        if Post.objects.filter(slug=slug).exists():
            raise forms.ValidationError('Slug already exists')
        return slug

class CommentForm(forms.ModelForm):
    class Meta:
        model = Comment
        fields = ['content']
        widgets = {
            'content': forms.Textarea(attrs={'rows': 3}),
        }
```

## Django REST Framework

```python
# Install: pip install djangorestframework

# settings.py
INSTALLED_APPS = [
    ...
    'rest_framework',
]

# serializers.py
from rest_framework import serializers
from .models import Post, Comment

class CommentSerializer(serializers.ModelSerializer):
    author = serializers.StringRelatedField()

    class Meta:
        model = Comment
        fields = ['id', 'author', 'content', 'created_at']

class PostSerializer(serializers.ModelSerializer):
    author = serializers.StringRelatedField()
    comments = CommentSerializer(many=True, read_only=True)

    class Meta:
        model = Post
        fields = ['id', 'title', 'slug', 'author', 'content',
                  'status', 'created_at', 'comments']

# views.py (API)
from rest_framework import viewsets, permissions
from rest_framework.decorators import action
from rest_framework.response import Response

class PostViewSet(viewsets.ModelViewSet):
    queryset = Post.objects.filter(status='published')
    serializer_class = PostSerializer
    permission_classes = [permissions.IsAuthenticatedOrReadOnly]

    def perform_create(self, serializer):
        serializer.save(author=self.request.user)

    @action(detail=True, methods=['post'])
    def publish(self, request, pk=None):
        post = self.get_object()
        post.status = 'published'
        post.save()
        return Response({'status': 'published'})

# urls.py (API)
from rest_framework.routers import DefaultRouter

router = DefaultRouter()
router.register(r'posts', PostViewSet)

urlpatterns = [
    path('api/', include(router.urls)),
]
```

## Admin Interface

```python
# myapp/admin.py
from django.contrib import admin
from .models import Post, Comment

@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
    list_display = ['title', 'author', 'status', 'created_at']
    list_filter = ['status', 'created_at']
    search_fields = ['title', 'content']
    prepopulated_fields = {'slug': ('title',)}
    date_hierarchy = 'created_at'

    actions = ['make_published']

    def make_published(self, request, queryset):
        queryset.update(status='published')
    make_published.short_description = "Mark selected as published"

@admin.register(Comment)
class CommentAdmin(admin.ModelAdmin):
    list_display = ['post', 'author', 'created_at']
    list_filter = ['created_at']
    search_fields = ['content']
```

## Authentication

```python
# views.py
from django.contrib.auth import login, logout
from django.contrib.auth.forms import UserCreationForm

def signup(request):
    if request.method == 'POST':
        form = UserCreationForm(request.POST)
        if form.is_valid():
            user = form.save()
            login(request, user)
            return redirect('home')
    else:
        form = UserCreationForm()
    return render(request, 'registration/signup.html', {'form': form})

# URLs
from django.contrib.auth import views as auth_views

urlpatterns = [
    path('login/', auth_views.LoginView.as_view(), name='login'),
    path('logout/', auth_views.LogoutView.as_view(), name='logout'),
    path('signup/', signup, name='signup'),
]
```

## Testing

```python
# myapp/tests.py
from django.test import TestCase, Client
from django.contrib.auth.models import User
from .models import Post

class PostModelTest(TestCase):
    def setUp(self):
        self.user = User.objects.create_user(username='test', password='test')
        self.post = Post.objects.create(
            title='Test Post',
            slug='test-post',
            author=self.user,
            content='Test content',
            status='published'
        )

    def test_post_creation(self):
        self.assertEqual(self.post.title, 'Test Post')
        self.assertEqual(str(self.post), 'Test Post')

    def test_post_slug_unique(self):
        with self.assertRaises(Exception):
            Post.objects.create(
                title='Another Post',
                slug='test-post',  # Duplicate slug
                author=self.user,
                content='Content'
            )

class PostViewTest(TestCase):
    def setUp(self):
        self.client = Client()
        self.user = User.objects.create_user(username='test', password='test')

    def test_post_list_view(self):
        response = self.client.get('/')
        self.assertEqual(response.status_code, 200)

    def test_post_create_requires_auth(self):
        response = self.client.get('/post/create/')
        self.assertEqual(response.status_code, 302)  # Redirect to login

        self.client.login(username='test', password='test')
        response = self.client.get('/post/create/')
        self.assertEqual(response.status_code, 200)
```

## Best Practices

- Use select_related/prefetch_related to avoid N+1 queries
- Implement proper validation in forms
- Use class-based views for common patterns
- Protect against CSRF attacks (enabled by default)
- Use Django's built-in authentication
- Implement proper permission checks
- Add database indexes for frequently queried fields
- Use Django migrations for schema changes

## Resources

- Django Docs: https://docs.djangoproject.com/
- Django REST Framework: https://www.django-rest-framework.org/
- Django Packages: https://djangopackages.org/

Overview

This skill provides expert-level Django development guidance for building robust Python web applications with ORM, admin, and authentication. It condenses practical patterns, code snippets, and architecture recommendations to accelerate secure, maintainable Django projects. Use it to design models, views, APIs, admin workflows, forms, and tests with production-ready considerations.

How this skill works

The skill inspects common Django concerns and offers concrete implementations and patterns: model design with indexes and signals, view choices (function- and class-based), form validation, admin customization, and REST API wiring with Django REST Framework. It highlights performance tactics like select_related/prefetch_related, security defaults (CSRF, authentication), and testing strategies to validate behavior and permissions.

When to use it

  • Starting a new Django project and choosing sensible defaults for models, URLs, and settings
  • Designing relational models and optimizing queries to avoid N+1 problems
  • Creating admin interfaces and bulk actions for content management
  • Exposing models via REST APIs with serializers, viewsets, and permission rules
  • Implementing authentication flows (signup, login, logout) and access control
  • Writing unit and integration tests for models and views

Best practices

  • Define clear model fields, Meta ordering, and database indexes for frequent queries
  • Use select_related and prefetch_related to prevent N+1 query issues
  • Prefer class-based views for common patterns and keep business logic in views/services
  • Validate user input in ModelForms and enforce unique constraints at the DB level
  • Use Django REST Framework serializers and permissions for API consistency
  • Add tests for models, views, and authentication flows; run migrations via version control

Example use cases

  • A blogging platform with Post and Comment models, publish workflow, and admin bulk actions
  • An API-backed frontend using DRF viewsets and nested serializers for comments
  • A CMS where editors create content via admin and publish using a custom admin action
  • A site requiring signup, login, and protected create/update views for authenticated users
  • Performance tuning an app by adding indexes and converting slow querysets to use select_related

FAQ

Should I use function-based or class-based views?

Use class-based views for standard CRUD patterns to reduce boilerplate; use function-based views for simple or highly customized request handling.

How do I avoid N+1 queries?

Use select_related for single-valued foreign keys and prefetch_related for many-to-many or reverse relations to fetch related objects in fewer queries.