💻 Примеры кода проекта Agile Projects
⚡ Ключевые фрагменты
# Модель Tag с MinLengthValidator
from django.core.validators import MinLengthValidator
class Tag(models.Model):
name = models.CharField(max_length=20, validators=[MinLengthValidator(4)])
def __str__(self): return self.name
# Deadline = последний день текущего месяца
def calculate_end_of_month():
now = timezone.now()
last_day = calendar.monthrange(now.year, now.month)[1]
return datetime(now.year, now.month, last_day).astimezone()
# APIView: get с фильтрацией
def get(self, request):
date_from = request.query_params.get('date_from')
date_to = request.query_params.get('date_to')
projects = self.get_objects(date_from, date_to)
...
1. Модель Tag (apps/tasks/models/tag.py)
from django.db import models
from django.core.validators import MinLengthValidator
class Tag(models.Model):
name = models.CharField(
max_length=20,
validators=[MinLengthValidator(4)]
)
def __str__(self):
return self.name
Минимальная длина задаётся через MinLengthValidator — валидатор уровня Django, не базы данных. CHECK CONSTRAINT в БД не создаётся.
2. Enum классы (apps/tasks/choices/)
# statuses.py
from enum import Enum
class Statuses(Enum):
NEW = "NEW"
IN_PROGRESS = "IN_PROGRESS"
PENDING = "PENDING"
BLOCKED = "BLOCKED"
TESTING = "TESTING"
CLOSED = "CLOSED"
@classmethod
def choices(cls):
return [(attr.name, attr.value) for attr in cls]
# priority.py
class Priority(Enum):
VERY_LOW = (1, 'Very Low')
LOW = (2, 'Low')
MEDIUM = (3, 'Medium')
HIGH = (4, 'High')
CRITICAL = (5, 'Critical')
@classmethod
def choices(cls):
return [(key.value[0], key.value[1]) for key in cls]
def __getitem__(self, item):
return self.value[item]
3. Утилита расчёта дедлайна (apps/tasks/utils/set_end_of_month.py)
from datetime import datetime
import calendar
from django.utils import timezone
def calculate_end_of_month() -> datetime:
current_date = timezone.now()
# monthrange(год, месяц) → (день_недели_первого, кол-во_дней)
amount_of_days = calendar.monthrange(
current_date.year,
current_date.month
)[1]
date = datetime(
year=current_date.year,
month=current_date.month,
day=amount_of_days,
)
return date.astimezone() # конвертировать в локальный часовой пояс
4. Сериализатор с кастомной валидацией (project_serializers.py)
class CreateProjectSerializer(serializers.ModelSerializer):
created_at = serializers.DateTimeField(read_only=True)
class Meta:
model = Project
fields = ('name', 'description', 'created_at')
def validate_description(self, value: str) -> str:
if len(value) < 30:
raise serializers.ValidationError(
"Description must be at least 30 characters long"
)
return value
Метод validate_<field_name> вызывается автоматически при is_valid().
5. Фильтрация по диапазону дат (project_views.py)
from django.utils import timezone
from datetime import datetime
class ProjectsListAPIView(APIView):
def get_objects(self, date_from=None, date_to=None):
if date_from and date_to:
# Строки → datetime → aware datetime (с учётом timezone)
date_from = timezone.make_aware(
datetime.strptime(date_from, '%Y-%m-%d')
)
date_to = timezone.make_aware(
datetime.strptime(date_to, '%Y-%m-%d')
)
return Project.objects.filter(
created_at__range=[date_from, date_to]
)
return Project.objects.all()
def get(self, request: Request) -> Response:
date_from = request.query_params.get('date_from')
date_to = request.query_params.get('date_to')
projects = self.get_objects(date_from, date_to)
if not projects.exists():
return Response(data=[], status=status.HTTP_204_NO_CONTENT)
serializer = AllProjectsSerializer(projects, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
Пример запроса с фильтром: GET /api/v1/projects/?date_from=2024-01-01&date_to=2024-12-31
6. Загрузка файла с chunked-записью
# apps/projects/views/project_file_views.py
def post(self, request: Request) -> Response:
file_content = request.FILES["file"]
project_id = request.data["project_id"]
project = get_object_or_404(Project, pk=project_id)
serializer = CreateProjectFileSerializer(
data=request.data,
context={"raw_file": file_content}
)
if serializer.is_valid(raise_exception=True):
project_file = serializer.save()
project_file.project.set([project]) # M2M связь
return Response(
{"message": "File upload successfully"},
status=status.HTTP_200_OK
)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Обратите внимание: файл передаётся в сериализатор через context, а не через data, так как поле file не является полем модели ProjectFile напрямую.