← Назад к вопросам
Как создавать и использовать эндпоинты (endpoints) в Django ORM?
2.0 Middle🔥 231 комментариев
#Django#REST API и HTTP
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Создание и использование endpoints в Django ORM
Эндпоинты — это URL маршруты которые обрабатывают HTTP запросы и возвращают данные. В Django это реализуется через views и URLs. Разберём полный цикл создания.
1. Базовая архитектура Django endpoints
# СТРУКТУРА ПРОЕКТА
#
myproject/
├── myproject/
│ ├── settings.py
│ ├── urls.py # Главный роутер
│ └── wsgi.py
├── myapp/
│ ├── models.py # Модели БД
│ ├── views.py # Бизнес-логика (view handlers)
│ ├── urls.py # Маршруты приложения
│ ├── serializers.py # Преобразование данных JSON
│ └── tests.py # Тесты
└── manage.py
# FLOW запроса:
# HTTP Request → myproject.urls → myapp.urls → views.py → models.py → Response
2. Создание моделей (начало)
# myapp/models.py
from django.db import models
from django.utils import timezone
class User(models.Model):
"""Модель пользователя"""
name = models.CharField(max_length=100)
email = models.EmailField(unique=True)
phone = models.CharField(max_length=20, null=True, blank=True)
created_at = models.DateTimeField(default=timezone.now)
is_active = models.BooleanField(default=True)
class Meta:
db_table = "users"
ordering = ["-created_at"]
def __str__(self):
return self.name
def __repr__(self):
return f"<User(id={self.id}, email={self.email})>"
class Post(models.Model):
"""Модель поста блога"""
author = models.ForeignKey(User, on_delete=models.CASCADE, related_name="posts")
title = models.CharField(max_length=200)
content = models.TextField()
published_at = models.DateTimeField(default=timezone.now)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
db_table = "posts"
ordering = ["-published_at"]
def __str__(self):
return self.title
3. Миграции БД
# Создать миграцию
python manage.py makemigrations
# Применить миграцию
python manage.py migrate
# Показать статус миграций
python manage.py showmigrations
4. Создание views (обработчики)
Function-Based Views (FBV)
# myapp/views.py
from django.http import JsonResponse
from django.views.decorators.http import require_http_methods
from django.views.decorators.csrf import csrf_exempt
from django.core.paginator import Paginator
import json
from .models import User, Post
@require_http_methods(["GET"])
def list_users(request):
"""GET /users — список пользователей"""
# Фильтрация
users = User.objects.filter(is_active=True)
# Пагинация
page = request.GET.get("page", 1)
paginator = Paginator(users, per_page=10)
page_obj = paginator.get_page(page)
# Преобразование в JSON
data = {
"count": paginator.count,
"next": page_obj.next_page_number() if page_obj.has_next() else None,
"previous": page_obj.previous_page_number() if page_obj.has_previous() else None,
"results": [
{
"id": user.id,
"name": user.name,
"email": user.email,
"created_at": user.created_at.isoformat(),
}
for user in page_obj
]
}
return JsonResponse(data)
@require_http_methods(["GET"])
def get_user(request, user_id):
"""GET /users/{id} — получить пользователя"""
try:
user = User.objects.get(id=user_id)
except User.DoesNotExist:
return JsonResponse({"error": "User not found"}, status=404)
data = {
"id": user.id,
"name": user.name,
"email": user.email,
"phone": user.phone,
"created_at": user.created_at.isoformat(),
"is_active": user.is_active,
"posts_count": user.posts.count(),
}
return JsonResponse(data)
@csrf_exempt
@require_http_methods(["POST"])
def create_user(request):
"""POST /users — создать пользователя"""
try:
data = json.loads(request.body)
except json.JSONDecodeError:
return JsonResponse({"error": "Invalid JSON"}, status=400)
# Валидация
if not data.get("name") or not data.get("email"):
return JsonResponse(
{"error": "name and email are required"},
status=400
)
# Проверка на дубликат
if User.objects.filter(email=data["email"]).exists():
return JsonResponse({"error": "Email already exists"}, status=400)
# Создание
user = User.objects.create(
name=data["name"],
email=data["email"],
phone=data.get("phone"),
)
return JsonResponse(
{
"id": user.id,
"name": user.name,
"email": user.email,
},
status=201
)
@csrf_exempt
@require_http_methods(["PUT"])
def update_user(request, user_id):
"""PUT /users/{id} — обновить пользователя"""
try:
user = User.objects.get(id=user_id)
except User.DoesNotExist:
return JsonResponse({"error": "User not found"}, status=404)
try:
data = json.loads(request.body)
except json.JSONDecodeError:
return JsonResponse({"error": "Invalid JSON"}, status=400)
# Обновление
user.name = data.get("name", user.name)
user.email = data.get("email", user.email)
user.phone = data.get("phone", user.phone)
user.save()
return JsonResponse({"id": user.id, "name": user.name})
@csrf_exempt
@require_http_methods(["DELETE"])
def delete_user(request, user_id):
"""DELETE /users/{id} — удалить пользователя"""
try:
user = User.objects.get(id=user_id)
except User.DoesNotExist:
return JsonResponse({"error": "User not found"}, status=404)
user.delete()
return JsonResponse({"message": "User deleted"}, status=204)
Class-Based Views (CBV)
# myapp/views.py (альтернатива)
from django.views import View
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from django.utils.decorators import method_decorator
import json
class UserListView(View):
"""Обработчик для /users"""
def get(self, request):
"""GET /users"""
users = User.objects.filter(is_active=True)[:10]
data = [
{"id": u.id, "name": u.name, "email": u.email}
for u in users
]
return JsonResponse({"results": data})
@method_decorator(csrf_exempt)
def post(self, request):
"""POST /users"""
data = json.loads(request.body)
user = User.objects.create(name=data["name"], email=data["email"])
return JsonResponse({"id": user.id}, status=201)
class UserDetailView(View):
"""Обработчик для /users/{id}"""
def get_object(self, user_id):
try:
return User.objects.get(id=user_id)
except User.DoesNotExist:
return None
def get(self, request, user_id):
"""GET /users/{id}"""
user = self.get_object(user_id)
if not user:
return JsonResponse({"error": "Not found"}, status=404)
return JsonResponse({"id": user.id, "name": user.name})
@method_decorator(csrf_exempt)
def put(self, request, user_id):
"""PUT /users/{id}"""
user = self.get_object(user_id)
if not user:
return JsonResponse({"error": "Not found"}, status=404)
data = json.loads(request.body)
user.name = data.get("name", user.name)
user.save()
return JsonResponse({"id": user.id, "name": user.name})
@method_decorator(csrf_exempt)
def delete(self, request, user_id):
"""DELETE /users/{id}"""
user = self.get_object(user_id)
if not user:
return JsonResponse({"error": "Not found"}, status=404)
user.delete()
return JsonResponse({}, status=204)
5. URL маршруты
# myapp/urls.py
from django.urls import path
from . import views
urlpatterns = [
# Function-based views
path("users", views.list_users, name="user-list"),
path("users/<int:user_id>", views.get_user, name="user-detail"),
path("users/create", views.create_user, name="user-create"),
path("users/<int:user_id>/update", views.update_user, name="user-update"),
path("users/<int:user_id>/delete", views.delete_user, name="user-delete"),
# Class-based views (альтернатива)
# path("users", views.UserListView.as_view(), name="user-list"),
# path("users/<int:user_id>", views.UserDetailView.as_view(), name="user-detail"),
]
# myproject/urls.py (главный)
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path("admin", admin.site.urls),
path("api/v1/", include("myapp.urls")), # /api/v1/users
]
6. REST Framework (рекомендуется для API)
# Установка: pip install djangorestframework
# myapp/serializers.py
from rest_framework import serializers
from .models import User, Post
class UserSerializer(serializers.ModelSerializer):
posts_count = serializers.SerializerMethodField()
class Meta:
model = User
fields = ["id", "name", "email", "phone", "created_at", "is_active", "posts_count"]
read_only_fields = ["id", "created_at", "posts_count"]
def get_posts_count(self, obj):
return obj.posts.count()
class PostSerializer(serializers.ModelSerializer):
author = UserSerializer(read_only=True)
class Meta:
model = Post
fields = ["id", "author", "title", "content", "published_at", "updated_at"]
read_only_fields = ["id", "published_at", "updated_at"]
# myapp/views.py (с REST Framework)
from rest_framework import viewsets, status
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated
from .models import User, Post
from .serializers import UserSerializer, PostSerializer
class UserViewSet(viewsets.ModelViewSet):
"""REST endpoint для users"""
queryset = User.objects.filter(is_active=True)
serializer_class = UserSerializer
permission_classes = [IsAuthenticated]
def get_queryset(self):
# Фильтрация по параметрам
qs = super().get_queryset()
email = self.request.query_params.get("email")
if email:
qs = qs.filter(email__icontains=email)
return qs
@action(detail=True, methods=["get"])
def posts(self, request, pk=None):
"""GET /users/{id}/posts — посты пользователя"""
user = self.get_object()
posts = user.posts.all()
serializer = PostSerializer(posts, many=True)
return Response(serializer.data)
@action(detail=False, methods=["post"])
def bulk_create(self, request):
"""POST /users/bulk_create — создать много пользователей"""
users_data = request.data
users = [User(**data) for data in users_data]
User.objects.bulk_create(users)
return Response({"message": "Created"}, status=status.HTTP_201_CREATED)
# myapp/urls.py
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from . import views
router = DefaultRouter()
router.register(r"users", views.UserViewSet)
urlpatterns = [
path("", include(router.urls)),
]
7. Query Optimization
# ПРОБЛЕМА: N+1 queries
def get_users_with_posts(request):
"""ПЛОХО — для каждого user по 1 запросу к posts"""
users = User.objects.all()
result = []
for user in users:
result.append({
"id": user.id,
"name": user.name,
"posts_count": user.posts.count() # N+1! Дополнительный запрос
})
return JsonResponse({"users": result})
# РЕШЕНИЕ 1: select_related (для ForeignKey)
users = User.objects.select_related("profile").all()
# РЕШЕНИЕ 2: prefetch_related (для ManyToMany и обратные FK)
from django.db.models import Prefetch, Count
users = User.objects.prefetch_related("posts").all()
# РЕШЕНИЕ 3: annotate (для aggregation)
users = User.objects.annotate(
posts_count=Count("posts")
).all()
def get_users_optimized(request):
"""ХОРОШО — оптимизированный запрос"""
users = User.objects.annotate(
posts_count=Count("posts")
).filter(is_active=True)
result = [
{
"id": user.id,
"name": user.name,
"posts_count": user.posts_count, # Из annotation
}
for user in users
]
return JsonResponse({"users": result})
8. Фильтрация и поиск
# myapp/views.py
from django.db.models import Q
def search_users(request):
"""GET /users/search?q=john — поиск пользователей"""
q = request.GET.get("q", "")
users = User.objects.filter(
Q(name__icontains=q) | Q(email__icontains=q)
).distinct()
return JsonResponse({
"results": [
{"id": u.id, "name": u.name, "email": u.email}
for u in users
]
})
# REST Framework с фильтрацией
from django_filters import FilterSet, CharFilter
from django_filters.rest_framework import DjangoFilterBackend
class UserFilter(FilterSet):
name = CharFilter(field_name="name", lookup_expr="icontains")
email = CharFilter(field_name="email", lookup_expr="icontains")
class Meta:
model = User
fields = ["name", "email", "is_active"]
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
filter_backends = [DjangoFilterBackend]
filterset_class = UserFilter
search_fields = ["name", "email"]
ordering_fields = ["created_at", "name"]
9. Тестирование endpoints
# myapp/tests.py
from django.test import TestCase, Client
from django.urls import reverse
from .models import User
import json
class UserAPITestCase(TestCase):
def setUp(self):
self.client = Client()
self.user = User.objects.create(
name="John",
email="john@example.com"
)
def test_list_users(self):
"""Test GET /users"""
response = self.client.get("/api/v1/users")
self.assertEqual(response.status_code, 200)
data = json.loads(response.content)
self.assertGreaterEqual(len(data["results"]), 1)
def test_get_user(self):
"""Test GET /users/{id}"""
response = self.client.get(f"/api/v1/users/{self.user.id}")
self.assertEqual(response.status_code, 200)
data = json.loads(response.content)
self.assertEqual(data["email"], "john@example.com")
def test_create_user(self):
"""Test POST /users"""
response = self.client.post(
"/api/v1/users",
data=json.dumps({"name": "Jane", "email": "jane@example.com"}),
content_type="application/json"
)
self.assertEqual(response.status_code, 201)
self.assertTrue(User.objects.filter(email="jane@example.com").exists())
def test_delete_user(self):
"""Test DELETE /users/{id}"""
user_id = self.user.id
response = self.client.delete(f"/api/v1/users/{user_id}")
self.assertEqual(response.status_code, 204)
self.assertFalse(User.objects.filter(id=user_id).exists())
Итоговая структура endpoints
GET /api/v1/users — список (paginated)
GET /api/v1/users/{id} — деталь
POST /api/v1/users — создание
PUT /api/v1/users/{id} — полное обновление
PATCH /api/v1/users/{id} — частичное обновление
DELETE /api/v1/users/{id} — удаление
GET /api/v1/users/{id}/posts — related resource
GET /api/v1/users/search — поиск
Рекомендация: Используй Django REST Framework для API — намного удобнее чем вручную писать JSON responses!