Перейти к основному контенту
Tech Path Finder
КурсыИнтервьюКод-ревьюБлог
Tech Path Finder

Персонализированный путеводитель в IT. Квизы, мок-интервью, код ревью и аналитика прогресса.

@potapov_me

Платформа

  • Курсы
  • Прогресс
  • Мок-интервью
  • Код ревью
  • Живое ревью с ИИ
  • Тренажёр переговоров
  • Закладки

Контент

  • Блог
  • Главная
  • Обратная связь

Компания

  • О проекте
  • Тарифы
  • Условия использования
  • Конфиденциальность
  • Согласие на обработку данных
  • Cookie
  • Реквизиты

Аккаунт

  • Войти
  • Зарегистрироваться
  • Профиль

© 2026 Tech Path Finder. Все права защищены.

·ИП Потапов К.С.·Политика конфиденциальности·
Сделано с ❤️ в России
  1. Best Practices
best_practices

Best Practices

Структура проекта, обработка ошибок, логирование, безопасность

Best Practices в Django REST Framework

Проверенные подходы и паттерны для создания надёжных, безопасных и поддерживаемых API на DRF.

#Структура проекта

#Рекомендуемая структура

myproject/
├── manage.py
├── config/                 # Настройки проекта
│   ├── __init__.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
├── apps/                   # Приложения
│   ├── articles/
│   │   ├── __init__.py
│   │   ├── models.py
│   │   ├── serializers.py
│   │   ├── views.py
│   │   ├── urls.py
│   │   ├── permissions.py
│   │   ├── filters.py
│   │   └── services.py
│   └── users/
├── tests/                  # Тесты
│   ├── __init__.py
│   ├── conftest.py
│   ├── test_articles.py
│   └── test_users.py
├── requirements/
│   ├── base.txt
│   ├── dev.txt
│   └── prod.txt
└── scripts/
    └── deploy.sh

#Разделение на приложения

  • По доменам: articles, users, orders, payments
  • Общие компоненты: apps/core/, apps/common/
  • API версии: apps/api/v1/, apps/api/v2/

#Сериализаторы

#Разные сериализаторы для разных операций

# serializers.py class ArticleListSerializer(serializers.ModelSerializer): """Для списка — минимум полей""" author_name = serializers.CharField(source='author.username', read_only=True) class Meta: model = Article fields = ['id', 'title', 'author_name', 'created_at'] class ArticleDetailSerializer(serializers.ModelSerializer): """Для детального просмотра — все поля""" author = AuthorSerializer(read_only=True) tags = TagSerializer(many=True, read_only=True) comment_count = serializers.IntegerField(read_only=True) class Meta: model = Article fields = [ 'id', 'title', 'content', 'author', 'tags', 'comment_count', 'created_at', 'updated_at' ] class ArticleCreateSerializer(serializers.ModelSerializer): """Для создания — только writable поля""" class Meta: model = Article fields = ['title', 'content', 'tag_ids'] def create(self, validated_data): tag_ids = validated_data.pop('tag_ids', []) article = Article.objects.create(**validated_data) article.tags.set(tag_ids) return article

#Валидация на уровне модели

# models.py class Article(models.Model): title = models.CharField(max_length=200) content = models.TextField() price = models.DecimalField(max_digits=10, decimal_places=2) class Meta: constraints = [ models.CheckConstraint(check=models.Q(price__gte=0), name='price_positive'), models.UniqueConstraint(fields=['title', 'author'], name='unique_title_per_author'), ] # serializers.py class ArticleSerializer(serializers.ModelSerializer): class Meta: model = Article fields = ['id', 'title', 'content', 'price'] # Валидация constraints будет выполнена автоматически

#Представления

#Используйте ViewSet для CRUD

# views.py from rest_framework import viewsets from rest_framework.permissions import IsAuthenticatedOrReadOnly class ArticleViewSet(viewsets.ModelViewSet): """ ViewSet для статей. list: GET /articles/ create: POST /articles/ retrieve: GET /articles/{id}/ update: PUT /articles/{id}/ destroy: DELETE /articles/{id}/ """ permission_classes = [IsAuthenticatedOrReadOnly] def get_queryset(self): return Article.objects.select_related('author').prefetch_related('tags') def get_serializer_class(self): if self.action == 'list': return ArticleListSerializer elif self.action == 'retrieve': return ArticleDetailSerializer return ArticleCreateSerializer def perform_create(self, serializer): serializer.save(author=self.request.user)

#Кастомные действия с @action

from rest_framework.decorators import action from rest_framework.response import Response from rest_framework import status class ArticleViewSet(viewsets.ModelViewSet): @action(detail=True, methods=['post'], permission_classes=[IsAdminUser]) def publish(self, request, pk=None): """Публикация статьи (только админ)""" article = self.get_object() article.is_published = True article.save() return Response({'status': 'published'}) @action(detail=True, methods=['post']) def like(self, request, pk=None): """Лайк статьи""" article = self.get_object() article.likes.add(request.user) return Response({'status': 'liked'}) @action(detail=False, methods=['get']) def my_articles(self, request): """Мои статьи""" queryset = Article.objects.filter(author=request.user) serializer = self.get_serializer(queryset, many=True) return Response(serializer.data)

#Обработка ошибок

#Кастомный exception handler

# utils/exceptions.py from rest_framework.views import exception_handler from rest_framework.response import Response def custom_exception_handler(exc, context): response = exception_handler(exc, context) if response is not None: # Единый формат ошибок error_data = { 'success': False, 'error': { 'code': response.status_code, 'message': response.data.get('detail', 'An error occurred'), 'fields': {} } } # Собираем ошибки по полям for key, value in response.data.items(): if key != 'detail': if isinstance(value, list): error_data['error']['fields'][key] = value[0] else: error_data['error']['message'] = value response.data = error_data return response # settings.py REST_FRAMEWORK = { 'EXCEPTION_HANDLER': 'myproject.utils.exceptions.custom_exception_handler', }

#Логирование ошибок

import logging logger = logging.getLogger(__name__) class ArticleViewSet(viewsets.ModelViewSet): def create(self, request, *args, **kwargs): try: return super().create(request, *args, **kwargs) except Exception as e: logger.exception(f'Error creating article: {e}') raise

#Безопасность

#HTTPS в production

# settings.py SECURE_SSL_REDIRECT = True SESSION_COOKIE_SECURE = True CSRF_COOKIE_SECURE = True SECURE_HSTS_SECONDS = 31536000 # 1 год SECURE_HSTS_INCLUDE_SUBDOMAINS = True SECURE_HSTS_PRELOAD = True

#CORS настройки

# settings.py INSTALLED_APPS = ['corsheaders'] MIDDLEWARE = ['corsheaders.middleware.CorsMiddleware'] + MIDDLEWARE CORS_ALLOWED_ORIGINS = [ 'https://example.com', 'https://www.example.com', ] # Или для development CORS_ALLOW_ALL_ORIGINS = True # Только для dev!

#Rate limiting для аутентификации

# settings.py REST_FRAMEWORK = { 'DEFAULT_THROTTLE_RATES': { 'auth': '5/hour', # Защита от брутфорса 'anon': '100/day', 'user': '1000/day', } }

#Валидация входных данных

class UserRegistrationSerializer(serializers.Serializer): email = serializers.EmailField(required=True) password = serializers.CharField( min_length=8, write_only=True, required=True ) password_confirm = serializers.CharField(write_only=True, required=True) def validate(self, data): if data['password'] != data['password_confirm']: raise serializers.ValidationError('Passwords do not match') # Проверка сложности пароля if not any(c.isupper() for c in data['password']): raise serializers.ValidationError('Password must contain uppercase letter') return data

#Тестирование

#Фикстуры для тестов

# tests/conftest.py import pytest from django.contrib.auth.models import User @pytest.fixture def user(): return User.objects.create_user(username='testuser', password='testpass') @pytest.fixture def authenticated_client(client, user): client.force_authenticate(user=user) return client @pytest.fixture def article(user): return Article.objects.create( title='Test Article', content='Test content', author=user )

#Тесты для ViewSet

# tests/test_articles.py import pytest from rest_framework import status @pytest.mark.django_db class TestArticleViewSet: def test_list_articles(self, client, article): response = client.get('/api/articles/') assert response.status_code == status.HTTP_200_OK assert len(response.data) == 1 def test_create_article_unauthenticated(self, client): response = client.post('/api/articles/', {}) assert response.status_code == status.HTTP_401_UNAUTHORIZED def test_create_article_authenticated(self, authenticated_client): data = {'title': 'New Article', 'content': 'Content'} response = authenticated_client.post('/api/articles/', data) assert response.status_code == status.HTTP_201_CREATED assert Article.objects.count() == 2 def test_delete_not_own_article(self, authenticated_client, article): # Пользователь не может удалить чужую статью response = authenticated_client.delete(f'/api/articles/{article.id}/') assert response.status_code == status.HTTP_403_FORBIDDEN

#Документирование

#Docstrings для view

class ArticleViewSet(viewsets.ModelViewSet): """ ViewSet для управления статьями. list: Возвращает список всех статей. Поддерживает фильтрацию, поиск и пагинацию. create: Создаёт новую статью. Требуется аутентификация. retrieve: Возвращает статью по ID. Включает информацию об авторе и тегах. update: Полное обновление статьи. Только владелец или админ. partial_update: Частичное обновление статьи. Только владелец или админ. destroy: Удаляет статью. Только владелец или админ. """ pass

#Мониторинг и логирование

#Структурированное логирование

# settings.py LOGGING = { 'version': 1, 'formatters': { 'json': { '()': 'pythonjsonlogger.jsonlogger.JsonFormatter', }, }, 'handlers': { 'console': { 'class': 'logging.StreamHandler', 'formatter': 'json', }, }, 'loggers': { 'django': { 'handlers': ['console'], 'level': 'INFO', }, 'myproject': { 'handlers': ['console'], 'level': 'DEBUG', 'propagate': False, }, }, } # В коде import logging logger = logging.getLogger(__name__) logger.info('Article created', extra={ 'article_id': article.id, 'author_id': article.author_id, })

#Чеклист перед деплоем

  • Отключён DEBUG = True
  • Отключён BrowsableAPIRenderer
  • Настроен HTTPS
  • Настроен CORS
  • Включён троттлинг
  • Настроено логирование
  • Запущены миграции
  • Созданы индексы в БД
  • Настроено кэширование (Redis/Memcached)
  • Запущены тесты
  • Сгенерирована документация API
  • Настроен мониторинг (Sentry, etc.)