Розробка бекенду сайту на Python (Django)
Django — "все з коробки" в буквальному сенсі. ORM, міграції, адмін-панель, аутентифікація, форми, кешування, i18n — все це є з коробки, без вибору бібліотек та інтеграції компонентів. Це робить Django найшвидшим способом запустити повноцінний бекенд, якщо вам не потрібні екзотичні можливості.
Типова область застосування: корпоративні сайти, CMS, портали, API для SPA та мобільних додатків, системи з багатою бізнес-логікою та рольовою моделлю.
Структура проекту
Django-проект складається з проекту та додатків. Гарна практика — робити додатки невеликими та функціонально ізольованими:
myproject/
manage.py
config/
settings/
base.py
development.py
production.py
urls.py
wsgi.py
asgi.py
apps/
users/
models.py
views.py
serializers.py # для DRF
urls.py
admin.py
services.py # бізнес-логіка окремо від views
tests/
test_models.py
test_views.py
products/
orders/
requirements/
base.txt
development.txt
production.txt
Налаштування ділимо по оточенням та ніколи не комітимо секрети в репозиторій:
# config/settings/base.py
from pathlib import Path
import environ
env = environ.Env()
BASE_DIR = Path(__file__).resolve().parent.parent.parent
SECRET_KEY = env('DJANGO_SECRET_KEY')
DATABASES = {
'default': env.db('DATABASE_URL', default='postgres://localhost/mydb')
}
CACHES = {
'default': {
'BACKEND': 'django_redis.cache.RedisCache',
'LOCATION': env('REDIS_URL', default='redis://localhost:6379/0'),
'OPTIONS': {'CLIENT_CLASS': 'django_redis.client.DefaultClient'}
}
}
Моделі та ORM
Сильна сторона Django — виразна ORM з підтримкою складних запитів:
from django.db import models
from django.contrib.postgres.fields import ArrayField
from django.contrib.postgres.indexes import GinIndex
class Product(models.Model):
name = models.CharField(max_length=255)
slug = models.SlugField(unique=True)
price = models.DecimalField(max_digits=10, decimal_places=2)
category = models.ForeignKey('Category', on_delete=models.SET_NULL, null=True)
attributes = models.JSONField(default=dict)
tags = ArrayField(models.CharField(max_length=50), default=list)
is_active = models.BooleanField(default=True)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
indexes = [
models.Index(fields=['slug']),
models.Index(fields=['category', 'is_active']),
GinIndex(fields=['attributes']), # для пошуку по JSONB
]
ordering = ['-created_at']
def __str__(self):
return self.name
Складні запити через annotate, aggregate, prefetch_related:
from django.db.models import Count, Avg, Q, Prefetch
# Отримати категорії з кількістю активних продуктів та середньою ціною
categories = Category.objects.annotate(
products_count=Count('product', filter=Q(product__is_active=True)),
avg_price=Avg('product__price', filter=Q(product__is_active=True))
).filter(products_count__gt=0).order_by('-products_count')
# N+1 проблема вирішується через prefetch_related
orders = Order.objects.prefetch_related(
Prefetch('items', queryset=OrderItem.objects.select_related('product'))
).select_related('user').filter(status='processing')
Django REST Framework
DRF — стандарт для API на Django. Серіалізатори, ViewSets, пагінація:
from rest_framework import serializers, viewsets, permissions, filters
from rest_framework.decorators import action
from rest_framework.response import Response
from django_filters.rest_framework import DjangoFilterBackend
class ProductSerializer(serializers.ModelSerializer):
category_name = serializers.CharField(source='category.name', read_only=True)
class Meta:
model = Product
fields = ['id', 'name', 'slug', 'price', 'category', 'category_name', 'attributes']
read_only_fields = ['slug']
def validate_price(self, value):
if value <= 0:
raise serializers.ValidationError('Ціна повинна бути додатною')
return value
class ProductViewSet(viewsets.ModelViewSet):
queryset = Product.objects.select_related('category').filter(is_active=True)
serializer_class = ProductSerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
filterset_fields = ['category', 'is_active']
search_fields = ['name', 'description']
ordering_fields = ['price', 'created_at']
ordering = ['-created_at']
@action(detail=True, methods=['post'], permission_classes=[permissions.IsAuthenticated])
def favorite(self, request, pk=None):
product = self.get_object()
request.user.favorites.add(product)
return Response({'status': 'added'})
Аутентифікація
JWT через djangorestframework-simplejwt:
# config/urls.py
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView
urlpatterns = [
path('api/auth/login/', TokenObtainPairView.as_view()),
path('api/auth/refresh/', TokenRefreshView.as_view()),
]
Користувальницький JWT payload:
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
class CustomTokenSerializer(TokenObtainPairSerializer):
@classmethod
def get_token(cls, user):
token = super().get_token(user)
token['email'] = user.email
token['role'] = user.role
return token
Celery для фонових задач
# tasks.py
from celery import shared_task
from django.core.mail import send_mail
@shared_task(bind=True, max_retries=3, default_retry_delay=60)
def send_order_confirmation(self, order_id: int) -> None:
try:
order = Order.objects.select_related('user').get(id=order_id)
send_mail(
subject=f'Замовлення #{order.id} підтверджено',
message=render_email_template('order_confirmation', order),
from_email=settings.DEFAULT_FROM_EMAIL,
recipient_list=[order.user.email]
)
except Order.DoesNotExist:
# не повторювати — об'єкт не знайдено
return
except Exception as exc:
raise self.retry(exc=exc)
Кешування
from django.core.cache import cache
from django.views.decorators.cache import cache_page
from functools import wraps
# Низькорівневе кешування
def get_popular_products():
cache_key = 'popular_products'
result = cache.get(cache_key)
if result is None:
result = list(Product.objects.filter(is_active=True).order_by('-views')[:10])
cache.set(cache_key, result, timeout=300)
return result
# Інвалідація по тегах через django-cache-memoize
from cache_memoize import cache_memoize
@cache_memoize(300, extra_verbose_cache_key=True)
def get_category_products(category_id: int, page: int = 1):
# ...
Адмін-панель
Вбудована адмін-панель Django економить тижні роботи:
@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
list_display = ['name', 'price', 'category', 'is_active', 'created_at']
list_filter = ['category', 'is_active', 'created_at']
search_fields = ['name', 'slug']
list_editable = ['is_active', 'price']
prepopulated_fields = {'slug': ('name',)}
readonly_fields = ['created_at', 'updated_at']
def get_queryset(self, request):
return super().get_queryset(request).select_related('category')
Терміни розробки
- Налаштування проекту, БД, auth — 3–5 днів
- Моделі + міграції + адмін — 1 тиждень
- API на DRF — 1–3 тижні залежно від складності
- Celery + Redis + очереди — 3–5 днів
- Інтеграції (платежі, email, сторонні API) — 1–2 тижні
- Тести (pytest-django) — 1 тиждень
Повноцінний бекенд для корпоративного сайту чи портального сайту: 5–10 тижнів. Django окуповується вбудованою адмін-панеллю — часто клієнт управляє контентом через неї без окремої CMS.







