✅ Решения практикума 3

Разбор всех 26 задач с пояснением логики

⚡ Ключевые решения

  • Задача 1: Tag(name='Backend'); tag.save() или списком в цикле
  • Задача 7: Tag.objects.all(), .first(), .last(), .count()
  • Задача 11: Q(date__gt=...) & Q(name__icontains='TI')
  • Задача 15: (Q(status='new') & Q(priority='Urgent')) | ~Q(tags__name='Q&A')
  • Задача 17: Task.objects.update(due_date=F('due_date') + timedelta(weeks=1))
  • Задача 21: цикл → изменить поле → bulk_update(tasks, ['status'])
  • Задача 23: .annotate(file_count=Count('project_files')).filter(Q(...) & Q(file_count__gte=3))

Блок 1: Создание данных

Задача 1: Создание тэгов

Логика: создаём объекты модели Tag в памяти, затем сохраняем каждый вызовом save(). Можно сначала сформировать список, затем обойти его в цикле.

python manage.py shell

from management_app.models.tag import Tag

tags_list = [
    Tag(name='Backend'),
    Tag(name='Frontend'),
    Tag(name='Q&A'),
    Tag(name='Design'),
    Tag(name='DevOPS')
]
for tag in tags_list:
    tag.save()

Результат: 5 тэгов сохранены в БД. Переменные back_tag, front_tag и т.д. потребуются в задаче 6 — удобно получить их через Tag.objects.get(name='Backend').

Задача 2: Создание проектов

Логика: показаны оба способа — save() и objects.create(). Они эквивалентны, create() делает то же что конструктор + save() за один вызов.

from management_app.models.project import Project

tiger_project = Project(name='TIGER', description='THIS IS A FIRST PROJECT')
tiger_project.save()

saphyr_project = Project.objects.create(
    name='SapHYR INC',
    description='THE BEST PROJECT EVER'
)

Задача 3: Добавление файлов к проектам

Логика: создаём файлы через objects.create(), затем добавляем их к проектам через related manager project_files.add() — это M2M-связь.

from management_app.models.project import Project, ProjectFile

# Файлы проекта TIGER
tiger_proj_file_1 = ProjectFile.objects.create(
    file_name='THE FIRST FILE',
    file_path='projects/tiger/THE_first_file.doc'
)
tiger_proj_file_2 = ProjectFile.objects.create(
    file_name='IMPORTANT DOCUMENT',
    file_path='projects/tiger/important_doc_9s9fh7g4hd4hgf6.pdf'
)
tiger_proj_file_3 = ProjectFile.objects.create(
    file_name='README',
    file_path='projects/tiger/README.md'
)
tiger_project.project_files.add(tiger_proj_file_1)
tiger_project.project_files.add(tiger_proj_file_2)
tiger_project.project_files.add(tiger_proj_file_3)

# Файлы проекта SapHYR
saphyr_file_1 = ProjectFile.objects.create(
    file_name='README',
    file_path='projects/saphyr/README.md'
)
saphyr_file_2 = ProjectFile.objects.create(
    file_name='DB_DIAGRAMM',
    file_path='projects/saphyr/db_schema_h3g45f67d.drawio'
)
saphyr_file_3 = ProjectFile.objects.create(
    file_name='budget',
    file_path='projects/saphyr/proj_budget.xlsx'
)
saphyr_project.project_files.add(saphyr_file_1)
saphyr_project.project_files.add(saphyr_file_2)
saphyr_project.project_files.add(saphyr_file_3)

Задача 4: Создание пользователей

Логика: используем стандартную модель Django User. В лекции применяется objects.create(), но для production-кода правильнее create_user() — он хэширует пароль.

from django.contrib.auth.models import User

# Из лекции (пароль НЕ хэшируется — только для учебных целей)
backend_dev = User.objects.create(
    username='backend_dev',
    password='sd7f6g5fsfd',
    email='backend.dev@gmail.com'
)
frontend_dev = User.objects.create(
    username='frontend_dev',
    password='sd7f6g5fsfd',
    email='frontend.dev@gmail.com'
)
designer_dev = User.objects.create(
    username='designer',
    password='sd7f6g5fsfd',
    email='omg.designer@icloud.com'
)
devops_dev = User.objects.create(
    username='devops',
    password='sd7f6g5fsfd',
    email='devops.3000@icloud.com'
)
qa_dev = User.objects.create(
    username='qa_dev',
    password='sd7f6g5fsfd',
    email='qa.doesntmetter@gmail.com'
)
В production-коде используйте User.objects.create_user(username, email, password) — он корректно хэширует пароль через set_password().

Задача 5: Создание задач для проектов

Логика: при создании задачи через objects.create() передаём объекты FK-полей напрямую (а не id). Django сам определяет pk.

from management_app.models.task import Task

# Задачи для TIGER
tiger_task_1 = Task.objects.create(
    name="Create new endpoint to get all project's tasks",
    priority='Critical',
    project=tiger_project,
    assignee=backend_dev
)
tiger_task_2 = Task.objects.create(
    name="Update schema",
    priority='Critical',
    project=tiger_project,
    assignee=backend_dev
)
tiger_task_3 = Task.objects.create(
    name="Connect new microservice",
    priority='Normal',
    project=tiger_project,
    assignee=devops_dev
)
tiger_task_4 = Task.objects.create(
    name="Update Stage build",
    priority='Major',
    project=tiger_project,
    assignee=devops_dev
)
tiger_task_5 = Task.objects.create(
    name="Update UX to mobile app",
    priority='Urgent',
    project=tiger_project,
    assignee=designer_dev
)
tiger_task_6 = Task.objects.create(
    name="Update 404 page",
    priority='Critical',
    project=tiger_project,
    assignee=frontend_dev
)
tiger_task_7 = Task.objects.create(
    name="Test new functionality",
    priority='Urgent',
    project=tiger_project,
    assignee=qa_dev
)
tiger_task_8 = Task.objects.create(
    name="test register form",
    priority='Normal',
    project=tiger_project,
    assignee=qa_dev
)

# Задачи для SapHYR INC
saphyr_task_1 = Task.objects.create(
    name="Update new endpoint to delete panel",
    priority='Critical',
    project=saphyr_project,
    assignee=backend_dev
)
saphyr_task_2 = Task.objects.create(
    name="Update DB schema",
    priority='Critical',
    project=saphyr_project,
    assignee=backend_dev
)
saphyr_task_3 = Task.objects.create(
    name="Connect new Azure storage",
    priority='Normal',
    project=saphyr_project,
    assignee=devops_dev
)
saphyr_task_4 = Task.objects.create(
    name="Update Build pipelines",
    priority='Major',
    project=saphyr_project,
    assignee=devops_dev
)
saphyr_task_5 = Task.objects.create(
    name="Update UI to desktop app",
    priority='Urgent',
    project=saphyr_project,
    assignee=designer_dev
)
saphyr_task_6 = Task.objects.create(
    name="Update redirect page",
    priority='Critical',
    project=saphyr_project,
    assignee=frontend_dev
)
saphyr_task_7 = Task.objects.create(
    name="Test new mobile functionality",
    priority='Urgent',
    project=saphyr_project,
    assignee=qa_dev
)
saphyr_task_8 = Task.objects.create(
    name="test update account form",
    priority='Normal',
    project=saphyr_project,
    assignee=qa_dev
)

Задача 6: Добавление тэгов к задачам

Логика: ManyToMany-связь. После создания задач добавляем тэги через related manager .tags.add(). Выбор тэга по смыслу задачи (backend задача → backend тэг).

# Сначала получаем тэги из БД (если переменные из задачи 1 не сохранились)
back_tag = Tag.objects.get(name='Backend')
front_tag = Tag.objects.get(name='Frontend')
qa_tag = Tag.objects.get(name='Q&A')
design_tag = Tag.objects.get(name='Design')
devops_tag = Tag.objects.get(name='DevOPS')
designer_tag = design_tag  # в источнике designer_tag — алиас Design

# TIGER задачи
tiger_task_1.tags.add(back_tag)
tiger_task_2.tags.add(back_tag)
tiger_task_3.tags.add(devops_tag)
tiger_task_4.tags.add(devops_tag)
tiger_task_5.tags.add(designer_tag)
tiger_task_5.tags.add(design_tag)
tiger_task_6.tags.add(front_tag)
tiger_task_7.tags.add(qa_tag)
tiger_task_8.tags.add(qa_tag)

# SapHYR задачи
saphyr_task_1.tags.add(back_tag)
saphyr_task_2.tags.add(back_tag)
saphyr_task_3.tags.add(front_tag)
saphyr_task_4.tags.add(front_tag)
saphyr_task_5.tags.add(design_tag)
saphyr_task_6.tags.add(design_tag)
saphyr_task_7.tags.add(qa_tag)
saphyr_task_8.tags.add(qa_tag)

Блок 2: Базовые запросы

Задача 7: Получение тегов

Логика: базовые методы QuerySet Manager — all(), first(), last(), count().

from management_app.models.tag import Tag

all_tags = Tag.objects.all()
for tag in all_tags:
    print(tag.name)
# Backend
# Frontend
# Q&A
# Design
# DevOPS

last_tag = Tag.objects.last()
last_tag.name
# 'Design'

first_tag = Tag.objects.first()
first_tag.name
# 'Backend'

count_of_tags = Tag.objects.count()
count_of_tags
# 5

Задача 8: Проверка существования тега

Логика: .exists() возвращает True/False — эффективно, не загружает объекты в память.

tag_exists = Tag.objects.filter(name='SUPERTAG').exists()
tag_exists
# False

Задача 9: Фильтрация тегов по строке

Логика: lookup __icontains — «contains без учёта регистра». В примере ищем все тэги, содержащие букву «e».

tags_by_str_match = Tag.objects.filter(name__icontains='e')
tags_by_str_match
# <QuerySet [<Tag: Backend>, <Tag: Design>, <Tag: DevOPS>, <Tag: Frontend>]>

for tag in tags_by_str_match:
    print(tag.name)
# Backend
# Design
# DevOPS
# Frontend

Задача 10: Фильтрация проектов по дате

Логика: __gt — «больше» (strictly greater than). Используем django.utils.timezone для timezone-aware дат.

from django.utils import timezone
from management_app.models.project import Project

required_date = timezone.datetime(2023, 1, 1).astimezone()
required_projects = Project.objects.filter(date_of_creation__gt=required_date)
required_projects
# <QuerySet [<Project: TIGER>, <Project: SapHYR INC>]>

Задача 11: Q-класс для комбинированных условий

Логика: два условия через AND (&). Q-класс нужен когда простой dict-синтаксис filter() не позволяет выразить логику.

from django.db.models import Q

specifying_projects = Project.objects.filter(
    Q(date_of_creation__gt=required_date) & Q(name__icontains='TI')
)
specifying_projects
# <QuerySet [<Project: TIGER>]>

specifying_projects[0].name
# 'TIGER'
specifying_projects[0].description
# 'THIS IS A FIRST PROJECT'

Задача 12: Файлы конкретного проекта

Логика: доступ к связанным объектам через double underscore lookup. project__name__contains — JOIN ProjectFile → Project по FK, затем фильтр по имени проекта.

required_files = ProjectFile.objects.filter(project__name__contains='TIGER')
required_files
# <QuerySet [<ProjectFile: README>, <ProjectFile: IMPORTANT DOCUMENT>, <ProjectFile: THE FIRST FILE>]>

for f in required_files:
    print(f.file_path)

Блок 3: Продвинутая фильтрация

Задача 13: Фильтрация по статусу и приоритету

Логика: несколько kwargs в filter() — все условия применяются как AND. Доступ к связанной модели через точку: obj.assignee.email.

required_tasks = Task.objects.filter(status='new', priority='Urgent')
for obj in required_tasks:
    print("=" * 50)
    print(obj.name)
    print(obj.status)
    print(obj.priority)
    print(obj.due_date)
    print(obj.assignee.email)
    print("=" * 50)

Задача 14: Обновление статуса задачи

Логика: filter().update() — один SQL UPDATE. Важно: переменная называется task_to_update (не task_to_updated — опечатка в оригинале).

task_to_update = Task.objects.filter(name='Update schema')
task_to_update.update(status='pending')
В оригинале лекции опечатка: task_to_updated.update() — это NameError. Правильно: task_to_update.update().

Задача 15: Комбинация условий с NOT

Логика: Q-класс с тремя операторами: & (AND), | (OR), ~ (NOT). Скобки задают приоритет.

specific_tasks = Task.objects.filter(
    (Q(status='new') & Q(priority='Urgent')) | ~Q(tags__name='Q&A')
)
for task in specific_tasks:
    print("=" * 50)
    print(task.name)
    print(task.project.name)
    print(task.assignee.email)
    print("=" * 50)

Задача 16: F-класс — обновление по значению поля

Логика: F-класс ссылается на значение поля в базе данных. Фильтруем задачи, где месяц due_date равен следующему месяцу после created_date.

from django.db.models import F

Task.objects.filter(
    due_date__month=F('created_date__month') + 1
).update(priority="Critical")
⚠️ Проверить по документации: синтаксис F('field__month') в filter-аргументе может вести себя неожиданно. Для надёжности используйте ExtractMonth из django.db.models.functions.

Задача 17: Сдвиг due_date на неделю

Логика: .update() с F-классом и timedelta — один SQL UPDATE для всех задач. Эффективнее, чем обходить в цикле Python.

from datetime import timedelta
from django.db.models import F

Task.objects.update(due_date=F('due_date') + timedelta(weeks=1))

Задача 18: Задачи без исполнителя

Логика: lookup __isnull=True — аналог SQL WHERE assignee_id IS NULL. Работает с FK-полями, ссылающимися на NULL.

tasks_without_assignee = Task.objects.filter(assignee__isnull=True)
for task in tasks_without_assignee:
    print("=" * 50)
    print(task.name)
    print(task.project.name)
    print("=" * 50)

Задача 19: Задачи через тэг (M2M lookup)

Логика: доступ к M2M-связанной модели через double underscore: tags__name__icontains — Django делает JOIN через промежуточную таблицу.

tasks_with_qa_tags = Task.objects.filter(tags__name__icontains="Q&A")
for task in tasks_with_qa_tags:
    print("=" * 50)
    print(task.name)
    print(task.status)
    print(task.priority)
    print(task.project.name)
    print("=" * 50)

Задача 20: Проекты с файлами за последнюю неделю

Логика: двухшаговый запрос: сначала получаем нужные файлы, затем фильтруем проекты через project_files__in. distinct() убирает дублирующиеся проекты (у одного проекта может быть несколько файлов).

import datetime
from django.utils import timezone
from management_app.models.project import Project, ProjectFile

last_week = timezone.now() - datetime.timedelta(days=7)

# Шаг 1: файлы за последнюю неделю
recent_files = ProjectFile.objects.filter(created_at__gte=last_week)

# Шаг 2: проекты с этими файлами
projects_with_recent_files = Project.objects.filter(
    project_files__in=recent_files
).distinct()

for proj in projects_with_recent_files:
    print(proj.name, proj.date_of_creation)

Блок 4: Массовые операции и агрегация

Задача 21: bulk_update статуса

Логика: bulk_update() — один SQL UPDATE для списка объектов. Менее затратно чем вызывать .save() для каждого. Второй аргумент — список обновляемых полей.

tasks_to_update = Task.objects.filter(status="new")
for task in tasks_to_update:
    task.status = "in_progress"
Task.objects.bulk_update(tasks_to_update, ['status'])

# Проверка
for task in Task.objects.all():
    print(task.status)

Задача 22: bulk_update due_date на 3 дня

Логика: комбинация F-класса и timedelta внутри bulk_update. Каждый объект получает своё значение due_date + 3 дня.

from datetime import timedelta
from django.db.models import F

tasks_to_update = Task.objects.filter(status="in_progress")
for task in tasks_to_update:
    task.due_date = F('due_date') + timedelta(days=3)
Task.objects.bulk_update(tasks_to_update, ['due_date'])

Задача 23: annotate(Count) + filter

Логика: annotate() добавляет вычисляемое поле к каждой строке QuerySet. После аннотации можно фильтровать по вычисленному значению.

from django.db.models import Count, Q
from django.utils import timezone
from management_app.models.project import Project

req_date = timezone.datetime(2023, 7, 7).astimezone()

projects_filtered = Project.objects.annotate(
    file_count=Count('project_files')
).filter(
    Q(date_of_creation__gt=req_date) & Q(file_count__gte=1)
)
В источнике условие file_count__gte=1, хотя задача говорит «больше трёх» — замените на Q(file_count__gte=3) по условию задачи.

Задача 24: Комбинированная фильтрация с концом месяца

Логика: вспомогательная функция вычисляет последний день текущего месяца через calendar.monthrange(). Затем фильтруем задачи с Q-условием по приоритету и due_date.

from django.utils import timezone
import calendar
import datetime
from django.db.models import Q

def calculate_end_of_month():
    current_date = timezone.now()
    amount_of_days = calendar.monthrange(
        current_date.year,
        current_date.month
    )[1]
    date = datetime.datetime(
        year=current_date.year,
        month=current_date.month,
        day=amount_of_days,
    )
    return timezone.make_aware(date)

end_of_month = calculate_end_of_month()

tasks_filtered = Task.objects.filter(
    Q(priority="High") | Q(priority="Critical"),
    due_date__lte=end_of_month
)
В оригинале лекции импорт from django.db.models.functions.datetime import datetime — нестандартный. Здесь используется стандартный import datetime и timezone.make_aware().

Задача 25: exclude() с Q

Логика: exclude() — противоположность filter(). Исключаем задачи с определёнными статусами. Можно было написать .filter(~Q(status='pending') & ~Q(status='closed')), но exclude(Q|Q) нагляднее.

from django.db.models import Q

tasks_excluded = Task.objects.exclude(
    Q(status="pending") | Q(status="closed")
)

Задача 26: Обновление приоритета старых задач

Логика: фильтрация по FK (project__name='TIGER') и по дате (created_date__lt=one_month_ago) в одном вызове. Затем .update() меняет приоритет одним SQL-запросом.

import datetime
from django.utils import timezone

one_month_ago = timezone.now() - datetime.timedelta(days=30)

Task.objects.filter(
    project__name='TIGER',
    created_date__lt=one_month_ago
).update(priority="Urgent")

← К оглавлению урока