💻 Примеры кода
⚡ Ключевые паттерны
# get_serializer_class
def get_serializer_class(self):
return AllProjectFilesSerializer if self.request.method == 'GET' \
else CreateProjectFileSerializer
# 204 при пустом queryset
if not qs.exists():
return Response(data=[], status=status.HTTP_204_NO_CONTENT)
# FileResponse
response = FileResponse(file_handle, content_type='application/octet-stream')
response['Content-Disposition'] = f'attachment; filename="{name}"'
# set_password
user.set_password(password)
user.save()
Пример 1. ProjectFileListGenericView — динамический сериализатор
Один класс, два сериализатора: для GET — показываем больше полей, для POST — принимаем минимум.
# apps/projects/views/project_file_views.py
from rest_framework.generics import ListCreateAPIView
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework import status
from apps.projects.models import ProjectFile
from apps.projects.serializers.project_file_serializers import (
AllProjectFilesSerializer,
CreateProjectFileSerializer,
)
class ProjectFileListGenericView(ListCreateAPIView):
def get_serializer_class(self):
if self.request.method == 'GET':
return AllProjectFilesSerializer
return CreateProjectFileSerializer
def get_queryset(self):
project_name = self.request.query_params.get('project')
if project_name:
return ProjectFile.objects.filter(
project__name=project_name
)
return ProjectFile.objects.all()
def list(self, request: Request, *args, **kwargs) -> Response:
project_files = self.get_queryset()
if not project_files.exists():
return Response(
data=[],
status=status.HTTP_204_NO_CONTENT
)
serializer = self.get_serializer(project_files, many=True)
return Response(
serializer.data,
status=status.HTTP_200_OK
)
Пример 2. ProjectFileDetailGenericView — детали + физическое удаление
# apps/projects/views/project_file_views.py
from rest_framework.generics import RetrieveDestroyAPIView, get_object_or_404
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework import status
from apps.projects.models import ProjectFile
from apps.projects.serializers.project_file_serializers import ProjectFileDetailSerializer
from apps.projects.utils.upload_file_helpers import delete_file
class ProjectFileDetailGenericView(RetrieveDestroyAPIView):
serializer_class = ProjectFileDetailSerializer
def get_object(self):
return get_object_or_404(ProjectFile, pk=self.kwargs['pk'])
def retrieve(self, request: Request, *args, **kwargs) -> Response:
task = self.get_object()
serializer = self.get_serializer(task)
return Response(
data=serializer.data,
status=status.HTTP_200_OK
)
def destroy(self, request: Request, *args, **kwargs) -> Response:
task = self.get_object()
try:
delete_file(file_path=task.file_path.path)
except Exception as e:
return Response(
data={"message": str(e)},
status=status.HTTP_400_BAD_REQUEST
)
else:
task.delete()
return Response(
data={"message": "File deleted successfully"},
status=status.HTTP_200_OK
)
Пример 3. Кастомная модель User
# apps/users/models.py
from django.contrib.auth.base_user import AbstractBaseUser
from django.contrib.auth.models import PermissionsMixin, UserManager
from django.core.validators import MinLengthValidator
from django.db import models
from django.utils.translation import gettext_lazy as _
from apps.projects.models import Project
from apps.users.choices.positions import UserPositions
class User(AbstractBaseUser, PermissionsMixin):
username = models.CharField(
_("username"), max_length=50, unique=True,
error_messages={"unique": _("A user with that username already exists.")}
)
first_name = models.CharField(
_("first name"), max_length=40, validators=[MinLengthValidator(2)]
)
last_name = models.CharField(
_("last name"), max_length=40, validators=[MinLengthValidator(2)]
)
email = models.EmailField(_("email address"), max_length=150, unique=True)
phone = models.CharField(max_length=75, null=True, blank=True)
is_staff = models.BooleanField(default=False)
is_active = models.BooleanField(default=True)
date_joined = models.DateTimeField(name="registered", auto_now_add=True)
last_login = models.DateTimeField(null=True, blank=True)
updated_at = models.DateTimeField(auto_now=True)
deleted_at = models.DateTimeField(null=True, blank=True)
deleted = models.BooleanField(default=False)
position = models.CharField(
max_length=15,
choices=UserPositions.choices,
default=UserPositions.PROGRAMMER
)
project = models.ForeignKey(
Project,
on_delete=models.CASCADE,
related_name="users",
null=True,
blank=True,
)
USERNAME_FIELD = "email"
REQUIRED_FIELDS = ["username", "first_name", "last_name", "position"]
objects = UserManager()
def __str__(self):
return f"{self.last_name} {self.first_name}"
Пример 4. DownloadProjectFileView — FileResponse
# apps/projects/views/project_file_views.py
from django.http import FileResponse
from rest_framework.views import APIView
from rest_framework.request import Request
from rest_framework.generics import get_object_or_404
from apps.projects.models import ProjectFile
class DownloadProjectFileView(APIView):
def get_object(self):
return get_object_or_404(ProjectFile, pk=self.kwargs['pk'])
def get(self, request: Request, *args, **kwargs) -> FileResponse:
project_file = self.get_object()
file_handle = project_file.file_path.open()
response = FileResponse(
file_handle,
content_type='application/octet-stream'
)
response['Content-Disposition'] = (
f'attachment; filename="{project_file.file_name}"'
)
return response
Эндпоинт: GET /api/v1/projects/files/download/<pk>/
Пример 5. URL-конфигурация — правильный порядок
# apps/projects/urls.py
from django.urls import path
from apps.projects.views.project_views import (
ProjectListAPIView, ProjectDetailAPIView
)
from apps.projects.views.project_file_views import (
ProjectFileListGenericView,
ProjectFileDetailGenericView,
DownloadProjectFileView,
)
urlpatterns = [
path('', ProjectListAPIView.as_view()),
path('<int:pk>/', ProjectDetailAPIView.as_view()),
path('files/', ProjectFileListGenericView.as_view()),
# ВАЖНО: download/ стоит ДО <int:pk>/, иначе Django попытается
# найти ProjectFile с pk="download"
path('files/download/<int:pk>/', DownloadProjectFileView.as_view()),
path('files/<int:pk>/', ProjectFileDetailGenericView.as_view()),
]
Пример 6. UserListGenericView — фильтрация по проекту
# apps/users/views.py
from rest_framework.generics import ListAPIView
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework import status
from apps.users.models import User
from apps.users.serializers import UserListSerializer
class UserListGenericView(ListAPIView):
serializer_class = UserListSerializer
def get_queryset(self):
project_name = self.request.query_params.get('project_name')
if project_name:
return User.objects.filter(project__name=project_name)
return User.objects.all()
def list(self, request: Request, *args, **kwargs) -> Response:
users = self.get_queryset()
if not users.exists():
return Response(data=[], status=status.HTTP_204_NO_CONTENT)
serializer = self.get_serializer(users, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)