💻 Примеры кода: Agile Projects ч.2
⚡ Ключевые фрагменты
# ProjectDetailAPIView — GET/PUT(partial)/DELETE
class ProjectDetailAPIView(APIView):
def get_object(self, pk):
return get_object_or_404(Project, pk=pk)
def put(self, request, pk):
serializer = CreateProjectSerializer(
instance=self.get_object(pk), data=request.data, partial=True)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.validated_data)
# TasksListAPIView с пагинацией
class TasksListAPIView(APIView):
paginator = StandardResultsSetPagination
def get(self, request):
tasks = Task.objects.filter(project__name=request.query_params.get('project_name'))
paginator = self.paginator()
page = paginator.paginate_queryset(tasks, request, view=self)
if page is not None:
return paginator.get_paginated_response(AllTasksSerializer(page, many=True).data)
Пример 1: ProjectDetailSerializer и ProjectDetailAPIView
Сериализатор для отображения деталей проекта (задача 1):
# apps/projects/serializers/project_serializers.py
class ProjectDetailSerializer(serializers.ModelSerializer):
class Meta:
model = Project
fields = ('id', 'name', 'description', 'count_of_files')
View для GET/PUT/DELETE конкретного проекта:
# apps/projects/views/project_views.py
from rest_framework.views import APIView
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework import status
from rest_framework.generics import get_object_or_404
from apps.projects.models import Project
from apps.projects.serializers.project_serializers import (
ProjectDetailSerializer, CreateProjectSerializer
)
class ProjectDetailAPIView(APIView):
def get_object(self, pk: int):
return get_object_or_404(Project, pk=pk)
def get(self, request: Request, pk: int) -> Response:
project = self.get_object(pk=pk)
serializer = ProjectDetailSerializer(project)
return Response(serializer.data, status=status.HTTP_200_OK)
def put(self, request: Request, pk: int) -> Response:
project = self.get_object(pk=pk)
serializer = CreateProjectSerializer(
instance=project,
data=request.data,
partial=True # <-- частичное обновление
)
if serializer.is_valid(raise_exception=True):
serializer.save()
return Response(serializer.validated_data, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request: Request, pk: int) -> Response:
project = self.get_object(pk=pk)
project.delete()
return Response(
data={"message": "Project was deleted successfully"},
status=status.HTTP_200_OK
)
URL-регистрация:
# apps/projects/urls.py
urlpatterns = [
path('', ProjectsListAPIView.as_view()),
path('<int:pk>/', ProjectDetailAPIView.as_view()), # NEW
]
Пример 2: AllTasksSerializer и TasksListAPIView с пагинацией
# apps/tasks/serializers/task_serializers.py
from rest_framework import serializers
from apps.projects.models import Project
from apps.tasks.models import Task, Tag
class AllTasksSerializer(serializers.ModelSerializer):
project = serializers.SlugRelatedField(read_only=True, slug_field='name')
assignee = serializers.SlugRelatedField(read_only=True, slug_field='email')
class Meta:
model = Task
fields = ('id', 'name', 'status', 'priority', 'project', 'assignee', 'deadline')
# apps/tasks/views/task_views.py
from rest_framework.pagination import PageNumberPagination
from rest_framework.views import APIView
class StandardResultsSetPagination(PageNumberPagination):
page_size = 5
page_size_query_param = 'page_size'
max_page_size = 15
class TasksListAPIView(APIView):
paginator = StandardResultsSetPagination
def get_objects(self):
project_name = self.request.query_params.get('project_name')
assignee_email = self.request.query_params.get('assignee_email')
if project_name:
return Task.objects.filter(project__name=project_name)
elif assignee_email:
return Task.objects.filter(assignee__email=assignee_email)
return Task.objects.all()
def get(self, request, *args, **kwargs):
tasks = self.get_objects()
if not tasks.exists():
return Response(data=[], status=status.HTTP_204_NO_CONTENT)
paginator = self.paginator()
page = paginator.paginate_queryset(tasks, request, view=self)
if page is not None:
serializer = AllTasksSerializer(page, many=True)
return paginator.get_paginated_response(serializer.data)
serializer = AllTasksSerializer(tasks, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
def post(self, request, *args, **kwargs):
serializer = CreateTaskSerializer(data=request.data)
if serializer.is_valid(raise_exception=True):
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Пример 3: CreateProjectFileSerializer с chunked save
# apps/projects/serializers/project_file_serializers.py
from rest_framework import serializers
from apps.projects.models import ProjectFile
from apps.projects.utils.upload_file_helpers import (
check_extension, create_file_path, check_file_size, save_file
)
class CreateProjectFileSerializer(serializers.ModelSerializer):
class Meta:
model = ProjectFile
fields = ('file_name',)
def validate_file_name(self, value: str) -> str:
if not value.isascii():
raise serializers.ValidationError("Please, provide a valid file name.")
if not check_extension(value):
raise serializers.ValidationError(
"Valid file extensions: ['.csv', '.doc', '.pdf', '.xlsx']"
)
return value
def create(self, validated_data):
project = self.context.get('project')
raw_file = self.context.get('raw_file')
file_path = create_file_path(
project_name=project.name,
file_name=validated_data['file_name']
)
if check_file_size(file=raw_file):
save_file(file_path=file_path, file_content=raw_file)
validated_data['file_path'] = file_path
project_file = ProjectFile.objects.create(**validated_data)
project_file.project.add(project)
return project_file
else:
raise serializers.ValidationError("File size is too large (2 MB as maximum).")
Пример 4: Кастомная модель 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)
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}"
# settings.py
AUTH_USER_MODEL = 'users.User' # добавить до первой миграции!
Пример 5: ProjectFileDetailGenericView с удалением файла
# apps/projects/views/project_file_views.py
from rest_framework.generics import RetrieveDestroyAPIView, get_object_or_404
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, *args, **kwargs):
task = self.get_object()
serializer = self.get_serializer(task)
return Response(data=serializer.data, status=status.HTTP_200_OK)
def destroy(self, request, *args, **kwargs):
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)