DRF ModelViewSet with django-filter range, exact and ordering filters: working versions (tested 2026-10-15)
A ModelViewSet with a django-filter FilterSet for price ranges, exact name and ordering, plus page-number pagination, configured in code and tested offline with APIClient on in-memory SQLite.
passed5 passed · 0 failed2026-10-15 13:20 UTC- Status
- passed
- Last passed
- 2026-10-15 13:20 UTC
- Tests
- 5 / 5
- Language
- Python
- Runtime
- Python 3.13.16
uv venv -q .venv && uv pip install -q --python .venv/bin/python -r requirements.txt .venv/bin/python -m pytest -q
Python 3.13.16 linux 6.17.0-1022-azure x64 runner: github-actions 38741369695
The code
Exactly the files that passed. Your agent can pull them with get_recipe("drf-django-filter-viewset") (install), or see them on GitHub.
django==6.1.2 djangorestframework==3.18.3 django-filter==26.2.0 pytest-django==4.14.0 pytest==9.1.1
[pytest] pythonpath = .
import django
from django.conf import settings
# Settings live in code, so the suite needs no settings module on disk.
# django.setup() must run after configure() so app models can be imported.
if not settings.configured:
settings.configure(
DEBUG=False,
SECRET_KEY="test-only",
ROOT_URLCONF="shop.api",
INSTALLED_APPS=[
"django.contrib.contenttypes",
"django.contrib.auth",
"rest_framework",
"django_filters",
"shop",
],
DATABASES={
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": ":memory:",
}
},
REST_FRAMEWORK={
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
"PAGE_SIZE": 2,
},
USE_TZ=True,
DEFAULT_AUTO_FIELD="django.db.models.BigAutoField",
)
django.setup()
from django.db import models
class Product(models.Model):
name = models.CharField(max_length=100)
price = models.DecimalField(max_digits=8, decimal_places=2)
import django_filters
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import serializers, viewsets
from rest_framework.routers import DefaultRouter
from shop.models import Product
class ProductSerializer(serializers.ModelSerializer):
class Meta:
model = Product
fields = ["id", "name", "price"]
class ProductFilter(django_filters.FilterSet):
min_price = django_filters.NumberFilter(field_name="price", lookup_expr="gte")
max_price = django_filters.NumberFilter(field_name="price", lookup_expr="lte")
ordering = django_filters.OrderingFilter(fields=("price", "name"))
class Meta:
model = Product
fields = ["name"] # exact match
class ProductViewSet(viewsets.ModelViewSet):
# An explicit order keeps pagination stable across requests.
queryset = Product.objects.order_by("id")
serializer_class = ProductSerializer
filter_backends = [DjangoFilterBackend]
filterset_class = ProductFilter
router = DefaultRouter()
router.register("products", ProductViewSet)
urlpatterns = router.urls
from decimal import Decimal
import pytest
from rest_framework.test import APIClient
from shop.models import Product
@pytest.fixture
def api(db):
Product.objects.bulk_create([
Product(name="Pen", price=Decimal("3.50")),
Product(name="Notebook", price=Decimal("12.00")),
Product(name="Desk", price=Decimal("150.00")),
Product(name="Lamp", price=Decimal("25.00")),
Product(name="Mug", price=Decimal("8.00")),
])
return APIClient()
def test_range_and_exact_filters(api):
r = api.get("/products/", {"min_price": "5", "max_price": "20"})
assert r.status_code == 200
assert r.data["count"] == 2
assert {p["name"] for p in r.data["results"]} == {"Notebook", "Mug"}
r = api.get("/products/", {"name": "Desk"})
assert [p["name"] for p in r.data["results"]] == ["Desk"]
def test_ordering_descending_by_price(api):
r = api.get("/products/", {"ordering": "-price"})
assert [p["name"] for p in r.data["results"]] == ["Desk", "Lamp"]
def test_pagination_counts_filtered_queryset(api):
# count and pages reflect the filter, not the whole table
page1 = api.get("/products/", {"min_price": "5", "page": 1})
page2 = api.get("/products/", {"min_price": "5", "page": 2})
assert page1.data["count"] == 4
assert page2.data["count"] == 4
names = [p["name"] for p in page1.data["results"] + page2.data["results"]]
assert len(names) == len(set(names)) == 4
def test_invalid_decimal_filter_returns_400(api):
r = api.get("/products/", {"min_price": "cheap"})
assert r.status_code == 400
def test_create_roundtrips_decimal_as_string(api):
r = api.post("/products/", {"name": "Chair", "price": "42.5"}, format="json")
assert r.status_code == 201
# DRF renders DecimalField as a string padded to decimal_places
assert r.data["price"] == "42.50"
Connect to ProvenPin
Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp
{ "mcpServers": { "provenpin": { "url": "https://mcp.provenpin.com/mcp" } } }{ "servers": { "provenpin": { "type": "http", "url": "https://mcp.provenpin.com/mcp" } } }{ "mcpServers": { "provenpin": { "serverUrl": "https://mcp.provenpin.com/mcp" } } }{ "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.
Related stacks
Django 6 + Django REST Framework model API with pytest-django
A DRF ModelViewSet with a serializer, router and token-free permissions, tested with pytest-django and APIClient on in-memory SQLite.
Django REST Framework JWT auth with simplejwt, tested offline
Obtain and refresh JWT pairs with simplejwt, add custom claims, protect a DRF view, and test expired-token rejection on sqlite in-memory with pytest-django.
Django models with factory_boy and pytest-django db fixtures
Django models built with factory_boy DjangoModelFactory (SubFactory, get_or_create, traits) and tested offline on in-memory SQLite with pytest-django database access rules and on_commit capture.
Django Ninja API with Schema models and pagination on sqlite
A typed Django Ninja CRUD API with Schema input and output models, page-number pagination and sqlite, tested offline with the ninja TestClient and pytest-django.
All Django recipesAll Django REST Framework recipesAll django-filter recipesAll pytest-django recipes