django-views

SKILL.md

Django Views

You are a Django views and URL routing expert. Your goal is to write clean, maintainable views using the right pattern for the task.

Initial Assessment

Check for project context first: If .agents/django-project-context.md exists (or .claude/django-project-context.md), read it to understand whether the project favors CBVs or FBVs, and what auth approach is used.


FBV vs CBV Decision Guide

Situation Recommendation
Simple, one-off logic FBV — less boilerplate
Standard CRUD (list/detail/create/update/delete) CBV — use generic views
Reusable behavior across views CBV with Mixins
Complex branching logic FBV — clearer flow
REST API endpoints Use DRF (see django-drf skill)

Function-Based Views

from django.shortcuts import render, get_object_or_404, redirect
from django.contrib.auth.decorators import login_required
from django.views.decorators.http import require_http_methods

@login_required
@require_http_methods(['GET', 'POST'])
def article_create(request):
    if request.method == 'POST':
        form = ArticleForm(request.POST)
        if form.is_valid():
            article = form.save(commit=False)
            article.author = request.user
            article.save()
            return redirect('articles:detail', pk=article.pk)
    else:
        form = ArticleForm()
    return render(request, 'articles/create.html', {'form': form})

Common Shortcuts

Function Use For
render(request, template, context) Render template with context
redirect(viewname_or_url, *args) HTTP redirect
get_object_or_404(Model, **kwargs) Get object or raise 404
get_list_or_404(Model, **kwargs) Get list or raise 404
HttpResponse(content) Raw HTTP response
JsonResponse(dict) JSON response

Class-Based Views

Generic Display Views

from django.views.generic import ListView, DetailView
from django.contrib.auth.mixins import LoginRequiredMixin

class ArticleListView(LoginRequiredMixin, ListView):
    model = Article
    template_name = 'articles/list.html'  # default: <app>/<model>_list.html
    context_object_name = 'articles'       # default: object_list
    paginate_by = 20
    ordering = ['-created_at']

    def get_queryset(self):
        # Scope to the current user
        return super().get_queryset().filter(author=self.request.user)

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['page_title'] = 'My Articles'
        return context


class ArticleDetailView(LoginRequiredMixin, DetailView):
    model = Article
    template_name = 'articles/detail.html'
    context_object_name = 'article'

Generic Edit Views

from django.views.generic.edit import CreateView, UpdateView, DeleteView
from django.urls import reverse_lazy

class ArticleCreateView(LoginRequiredMixin, CreateView):
    model = Article
    form_class = ArticleForm
    template_name = 'articles/form.html'
    success_url = reverse_lazy('articles:list')

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


class ArticleUpdateView(LoginRequiredMixin, UpdateView):
    model = Article
    form_class = ArticleForm
    template_name = 'articles/form.html'

    def get_success_url(self):
        return reverse_lazy('articles:detail', kwargs={'pk': self.object.pk})

    def get_queryset(self):
        # Only allow editing own articles
        return super().get_queryset().filter(author=self.request.user)


class ArticleDeleteView(LoginRequiredMixin, DeleteView):
    model = Article
    template_name = 'articles/confirm_delete.html'
    success_url = reverse_lazy('articles:list')

URL Configuration

App URLs

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

app_name = 'articles'  # Always use app namespaces

urlpatterns = [
    path('', views.ArticleListView.as_view(), name='list'),
    path('create/', views.ArticleCreateView.as_view(), name='create'),
    path('<int:pk>/', views.ArticleDetailView.as_view(), name='detail'),
    path('<int:pk>/edit/', views.ArticleUpdateView.as_view(), name='update'),
    path('<int:pk>/delete/', views.ArticleDeleteView.as_view(), name='delete'),
    path('<slug:slug>/', views.ArticleDetailView.as_view(), name='detail-by-slug'),
]

Root URLs

# project/urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('articles/', include('articles.urls', namespace='articles')),
    path('api/', include('api.urls', namespace='api')),
    path('accounts/', include('django.contrib.auth.urls')),
]

URL Patterns

Pattern Matches
<int:pk> Integer: 42
<str:name> Non-empty string without /
<slug:slug> Slug: my-article-title
<uuid:id> UUID: 550e8400-e29b-41d4-a716-446655440000
<path:subpath> Any string including /

Mixins

Authentication & Permission Mixins

from django.contrib.auth.mixins import (
    LoginRequiredMixin,      # Redirect to login if not authenticated
    PermissionRequiredMixin, # Require specific permission
    UserPassesTestMixin,     # Custom test function
)

class AdminOnlyView(PermissionRequiredMixin, ListView):
    permission_required = 'articles.view_article'
    # Or multiple: permission_required = ['app.perm1', 'app.perm2']


class OwnerOnlyView(UserPassesTestMixin, DetailView):
    def test_func(self):
        obj = self.get_object()
        return obj.author == self.request.user

Custom Mixins

class OwnerQuerySetMixin:
    """Filter queryset to objects owned by the current user."""
    owner_field = 'author'

    def get_queryset(self):
        qs = super().get_queryset()
        return qs.filter(**{self.owner_field: self.request.user})


class ArticleListView(LoginRequiredMixin, OwnerQuerySetMixin, ListView):
    model = Article

Form Handling in Views

Using Django Forms with CBVs

from django import forms

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

    def clean_title(self):
        title = self.cleaned_data['title']
        if len(title) < 5:
            raise forms.ValidationError('Title must be at least 5 characters.')
        return title

Reverse URL Resolution

# In templates:
{% url 'articles:detail' pk=article.pk %}
{% url 'articles:list' %}

# In Python:
from django.urls import reverse, reverse_lazy

reverse('articles:detail', kwargs={'pk': 1})        # Returns string
reverse_lazy('articles:list')                        # Lazy — use in class attributes

Related Skills

  • django-drf: For REST API views using Django REST Framework
  • django-auth: For authentication views and custom login flows
  • django-models: For the underlying models these views work with
  • django-tests: For testing views with the test client
Weekly Installs
3
First Seen
6 days ago
Installed on
cline3
gemini-cli3
github-copilot3
codex3
kimi-cli3
cursor3