Django 6 + Django REST Framework model API with pytest-django: working versions (tested 2026-10-15)
A DRF ModelViewSet with a serializer, router and token-free permissions, tested with pytest-django and APIClient on in-memory SQLite.
passed3 passed · 0 failed2026-10-15 11:34 UTC- Status
- passed
- Last passed
- 2026-10-15 11:34 UTC
- Tests
- 3 / 3
- Language
- Python
- Runtime
- Python 3.13.16
pinned versions4
django@6.1.2djangorestframework@3.18.3pytest-django@4.14.0pytest@9.1.1
run it
uv venv -q .venv && uv pip install -q --python .venv/bin/python -r requirements.txt .venv/bin/python -m pytest -q
test environment
Python 3.13.16 linux 6.17.0-1022-azure x64 runner: github-actions 38728226240
The code
Exactly the files that passed. Your agent can pull them with get_recipe("django-drf-api") (install), or see them on GitHub.
requirements.txt
django==6.1.2 djangorestframework==3.18.3 pytest-django==4.14.0 pytest==9.1.1
settings.py
SECRET_KEY = "test-only"
DEBUG = True
ALLOWED_HOSTS = ["*"]
INSTALLED_APPS = ["django.contrib.contenttypes", "django.contrib.auth", "rest_framework", "books"]
DATABASES = {"default": {"ENGINE": "django.db.backends.sqlite3", "NAME": ":memory:"}}
ROOT_URLCONF = "urls"
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
USE_TZ = True
REST_FRAMEWORK = {"DEFAULT_PERMISSION_CLASSES": ["rest_framework.permissions.IsAuthenticatedOrReadOnly"]}
urls.py
from rest_framework.routers import DefaultRouter
from books.views import BookViewSet
router = DefaultRouter()
router.register("books", BookViewSet)
urlpatterns = router.urls
books/__init__.py
books/models.py
from django.db import models
class Book(models.Model):
title = models.CharField(max_length=200)
isbn = models.CharField(max_length=13, unique=True)
published = models.DateField(null=True, blank=True)
books/serializers.py
from rest_framework import serializers
from .models import Book
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = ["id", "title", "isbn", "published"]
def validate_isbn(self, value):
if not value.isdigit() or len(value) not in (10, 13):
raise serializers.ValidationError("ISBN must be 10 or 13 digits")
return value
books/views.py
from rest_framework import viewsets
from .models import Book
from .serializers import BookSerializer
class BookViewSet(viewsets.ModelViewSet):
queryset = Book.objects.order_by("id")
serializer_class = BookSerializer
tests/test_books.py
import pytest
from django.contrib.auth.models import User
from rest_framework.test import APIClient
pytestmark = pytest.mark.django_db
@pytest.fixture
def auth_client():
c = APIClient()
c.force_authenticate(User.objects.create_user("ada"))
return c
def test_create_and_list(auth_client):
r = auth_client.post("/books/", {"title": "SICP", "isbn": "9780262510875"}, format="json")
assert r.status_code == 201
assert APIClient().get("/books/").json()[0]["title"] == "SICP"
def test_validation(auth_client):
r = auth_client.post("/books/", {"title": "X", "isbn": "abc"}, format="json")
assert r.status_code == 400 and "isbn" in r.json()
def test_anonymous_cannot_write():
assert APIClient().post("/books/", {"title": "X", "isbn": "1234567890"}, format="json").status_code in (401, 403)
pytest.ini
[pytest] pythonpath = . DJANGO_SETTINGS_MODULE = settings
Connect to ProvenPin
Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.
terminal
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp
~/.cursor/mcp.json
{ "mcpServers": { "provenpin": { "url": "https://mcp.provenpin.com/mcp" } } }.vscode/mcp.json
{ "servers": { "provenpin": { "type": "http", "url": "https://mcp.provenpin.com/mcp" } } }~/.codeium/windsurf/mcp_config.json
{ "mcpServers": { "provenpin": { "serverUrl": "https://mcp.provenpin.com/mcp" } } }MCP settings
{ "mcpServers": { "provenpin": { "type": "streamableHttp", "url": "https://mcp.provenpin.com/mcp" } } }Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.