💻 Примеры: полные модели практикума

Финальный вид models.py и admin.py после всех 18 задач

⚡ Структура моделей практикума

Tag(name)
ProjectFile(name, file, created_at)
Project(name, description, created_at, files→ProjectFile)
  └─ Meta: ordering, verbose_name, unique_together
  └─ property: count_of_files
Task(title, description, status, priority, project→Project,
     created_at, updated_at, deleted_at, due_date,
     tags→Tag, assignee→User)
  └─ Meta: ordering, verbose_name, unique_together

Пример 1: Полный models.py

Финальный вид файла после всех задач практикума (Django 5.x).

from django.db import models
from django.contrib.auth.models import User
from django.core.validators import MinLengthValidator


# --- Choices ---

STATUSES_CHOICES = [
    ('New', 'New'),
    ('In_progress', 'In_progress'),
    ('Completed', 'Completed'),
    ('Closed', 'Closed'),
    ('Pending', 'Pending'),
    ('Blocked', 'Blocked'),
]

PRIORITY_CHOICES = [
    ('Low', 'Low'),
    ('Medium', 'Medium'),
    ('High', 'High'),
    ('Very High', 'Very High'),
]


# --- Модели ---

class Tag(models.Model):
    """Тег для задачи."""
    name = models.CharField(max_length=20, unique=True)

    def __str__(self):
        return self.name


class ProjectFile(models.Model):
    """Файл, прикреплённый к проекту."""
    name = models.CharField(max_length=120)
    file = models.FileField(upload_to='projects/')
    created_at = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return self.name

    class Meta:
        verbose_name = 'Project File'
        verbose_name_plural = 'Project Files'
        ordering = ['-created_at']


class Project(models.Model):
    """Проект — контейнер задач."""
    name = models.CharField(max_length=100, unique=True)
    description = models.TextField()
    created_at = models.DateTimeField(auto_now_add=True)
    files = models.ManyToManyField(ProjectFile, related_name='projects', blank=True)

    def __str__(self):
        return self.name

    @property
    def count_of_files(self):
        return self.files.count()

    class Meta:
        ordering = ['-name']
        verbose_name = 'Project'
        verbose_name_plural = 'Projects'
        unique_together = (('name', 'created_at'),)


class Task(models.Model):
    """Задача в рамках проекта."""
    title = models.CharField(
        max_length=255, unique=True,
        validators=[MinLengthValidator(10)]
    )
    description = models.TextField(null=True, blank=True)
    status = models.CharField(
        max_length=15, choices=STATUSES_CHOICES, default='New'
    )
    priority = models.CharField(max_length=15, choices=PRIORITY_CHOICES)
    project = models.ForeignKey(Project, on_delete=models.CASCADE)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    deleted_at = models.DateTimeField(null=True, blank=True)
    due_date = models.DateTimeField(null=True, blank=True)
    tags = models.ManyToManyField('Tag', blank=True, related_name='tasks')
    assignee = models.ForeignKey(
        User, null=True, blank=True, on_delete=models.SET_NULL
    )

    def __str__(self):
        return self.title

    class Meta:
        ordering = ['-due_date', 'assignee']
        verbose_name = 'Task'
        verbose_name_plural = 'Tasks'
        unique_together = (('title', 'project'),)
Отличие от исходного решения: для assignee использован on_delete=models.SET_NULL вместо CASCADE — при удалении пользователя задача сохраняется с assignee=None. Это более безопасное поведение в реальных проектах.

Пример 2: Полный admin.py

from django.contrib import admin
from .models import Project, Task, Tag, ProjectFile


@admin.register(Tag)
class TagAdmin(admin.ModelAdmin):
    list_display = ['name']
    search_fields = ['name']


@admin.register(ProjectFile)
class ProjectFileAdmin(admin.ModelAdmin):
    list_display = ['name', 'file', 'created_at']
    search_fields = ['name']
    list_filter = ['created_at']


@admin.register(Project)
class ProjectAdmin(admin.ModelAdmin):
    list_display = ['name', 'display_count_of_files', 'created_at']
    search_fields = ['name']
    actions = ['replace_spaces_with_underscores']

    def display_count_of_files(self, obj):
        return obj.count_of_files
    display_count_of_files.short_description = 'Count of Files'

    def replace_spaces_with_underscores(self, request, queryset):
        for obj in queryset:
            obj.name = obj.name.replace(' ', '_')
            obj.save()
    replace_spaces_with_underscores.short_description = 'Replace spaces with underscores'


@admin.register(Task)
class TaskAdmin(admin.ModelAdmin):
    list_display = [
        'title', 'project', 'status', 'priority',
        'created_at', 'due_date', 'assignee'
    ]
    search_fields = ['title']
    list_filter = ['status', 'priority', 'project', 'created_at', 'due_date', 'assignee']
    actions = [
        'change_status',
        'change_priority_to_low',
        'change_priority_to_medium',
        'change_priority_to_high',
        'change_priority_to_very_high',
    ]

    def change_status(self, request, queryset):
        for obj in queryset:
            obj.status = 'Closed'
            obj.save()
    change_status.short_description = 'Mark as Closed'

    def change_priority_to_low(self, request, queryset):
        for obj in queryset:
            obj.priority = 'Low'
            obj.save()
    change_priority_to_low.short_description = 'Mark as Low priority'

    def change_priority_to_medium(self, request, queryset):
        for obj in queryset:
            obj.priority = 'Medium'
            obj.save()
    change_priority_to_medium.short_description = 'Mark as Medium priority'

    def change_priority_to_high(self, request, queryset):
        for obj in queryset:
            obj.priority = 'High'
            obj.save()
    change_priority_to_high.short_description = 'Mark as High priority'

    def change_priority_to_very_high(self, request, queryset):
        for obj in queryset:
            obj.priority = 'Very High'
            obj.save()
    change_priority_to_very_high.short_description = 'Mark as Very High priority'

Пример 3: Создание и применение fixtures

# Шаг 1: Создать директорию для fixtures
mkdir practicum_2/fixtures

# Шаг 2: Сохранить группы разрешений
python manage.py dumpdata auth.Group --natural-foreign --indent=4 \
    > practicum_2/fixtures/groups_fixture.json

# Шаг 3: Сохранить пользователей
python manage.py dumpdata auth.User --natural-foreign --indent=4 \
    > practicum_2/fixtures/users_fixture.json

# Шаг 4: Удалить базу данных (db.sqlite3)
# Шаг 5: Пересоздать миграции
python manage.py makemigrations
python manage.py migrate

# Шаг 6: Восстановить данные
python manage.py loaddata practicum_2/fixtures/groups_fixture.json
python manage.py loaddata practicum_2/fixtures/users_fixture.json