Задача 1: Модель Project
Логика: unique=True на CharField создаёт уникальный индекс в БД. TextField() — текст без ограничения длины. auto_now_add=True заполняется один раз при создании объекта.
from django.db import models
class Project(models.Model):
name = models.CharField(max_length=100, unique=True)
description = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.name
Задача 2: Модель Task
Логика: MinLengthValidator(10) проверяет минимальную длину на уровне Django-валидации. Choices ограничивают допустимые значения. on_delete=models.CASCADE каскадно удаляет задачи при удалении проекта. auto_now=True обновляется при каждом сохранении.
from django.core.validators import MinLengthValidator
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 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)
def __str__(self):
return self.title
Задача 3: Tag + due_date + M2M
Логика: Модель Tag создаётся отдельно. Поле due_date — nullable DateTimeField. Для M2M достаточно blank=True, null=True для ManyToManyField игнорируется Django.
class Tag(models.Model):
name = models.CharField(max_length=20, unique=True)
def __str__(self):
return self.name
class Task(models.Model):
... # поля из задачи 2
due_date = models.DateTimeField(null=True, blank=True) # NEW
tags = models.ManyToManyField('Tag', blank=True, related_name='tasks') # NEW
Задача 4: Admin — базовая настройка
Логика: list_display — колонки в списке. search_fields — поля для полнотекстового поиска. list_filter — панель фильтров справа.
from django.contrib import admin
from .models import Project, Task, Tag
@admin.register(Tag)
class TagAdmin(admin.ModelAdmin):
list_display = ['name']
search_fields = ['name']
@admin.register(Project)
class ProjectAdmin(admin.ModelAdmin):
list_display = ['name', 'created_at']
search_fields = ['name']
@admin.register(Task)
class TaskAdmin(admin.ModelAdmin):
list_display = ['title', 'project', 'status', 'priority', 'created_at', 'due_date']
search_fields = ['title']
list_filter = ['status', 'priority', 'project', 'created_at', 'due_date']
Задача 5: Связь Task → User (assignee)
Логика: ForeignKey к встроенной модели User. null=True, blank=True — задача может быть без исполнителя. При удалении пользователя задача сохраняется с assignee=None (решение безопаснее, чем CASCADE из источника).
from django.contrib.auth.models import User
class Task(models.Model):
...
assignee = models.ForeignKey(
User, null=True, blank=True, on_delete=models.CASCADE
) # NEW — из источника практикума
Задача 6: Пользователь в Admin
Логика: Пользователи создаются напрямую в Admin → Authentication and Authorization → Users. Для отображения в TaskAdmin добавляем assignee в list_display и list_filter.
@admin.register(Task)
class TaskAdmin(admin.ModelAdmin):
list_display = [
'title', 'project', 'status', 'priority',
'created_at', 'due_date', 'assignee' # добавлено
]
list_filter = [
'status', 'priority', 'project',
'created_at', 'due_date', 'assignee' # добавлено
]
Создание пользователя решается напрямую в Административной панели. Пользователя можно создать в Django Admin в таблице User.
Задача 7: Meta для Project
Логика: ordering=['-name'] — убывание по имени. unique_together гарантирует уникальность комбинации полей на уровне БД.
class Project(models.Model):
...
class Meta:
ordering = ['-name']
verbose_name = 'Project'
verbose_name_plural = 'Projects'
unique_together = (('name', 'created_at'),)
Задача 8: Meta для Task
Логика: Порядок сортировки по due_date (убывание) и priority. Уникальность — пара (title, project): одна задача с таким именем в рамках одного проекта.
class Task(models.Model):
...
class Meta:
ordering = ['-due_date', '-priority']
verbose_name = 'Task'
verbose_name_plural = 'Tasks'
unique_together = (('title', 'project'),)
Задача 9: Модель ProjectFile + связь M2M с Project
Логика: FileField(upload_to='projects/') сохраняет файлы в папку MEDIA_ROOT/projects/. M2M позволяет одному файлу принадлежать нескольким проектам.
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 Project(models.Model):
...
files = models.ManyToManyField(ProjectFile, related_name='projects', blank=True) # NEW
Задача 10: Admin для ProjectFile
@admin.register(ProjectFile)
class ProjectFileAdmin(admin.ModelAdmin):
list_display = ['name', 'file', 'created_at']
search_fields = ['name']
list_filter = ['created_at']
Задача 11: Meta для ProjectFile
class ProjectFile(models.Model):
...
class Meta:
verbose_name = 'Project File'
verbose_name_plural = 'Project Files'
ordering = ['-created_at']
Задача 12: Property + Admin (count_of_files)
Логика: @property в модели возвращает вычисляемое значение. В Admin нельзя напрямую указать property в list_display — нужен оборачивающий метод в ModelAdmin с атрибутом short_description.
class Project(models.Model):
...
@property
def count_of_files(self):
return self.files.count()
@admin.register(Project)
class ProjectAdmin(admin.ModelAdmin):
list_display = ['name', 'display_count_of_files', 'created_at']
search_fields = ['name']
def display_count_of_files(self, obj):
return obj.count_of_files
display_count_of_files.short_description = 'Count of Files'
Задача 13: Admin-действие — замена пробелов
Логика: Метод принимает request и queryset (в источнике — objects, но это одно и то же). short_description — отображаемое название в выпадающем меню Actions.
@admin.register(Project)
class ProjectAdmin(admin.ModelAdmin):
actions = ['replace_spaces_with_underscores']
def replace_spaces_with_underscores(self, request, objects):
for obj in objects:
obj.name = obj.name.replace(' ', '_')
obj.save()
return objects
replace_spaces_with_underscores.short_description = 'Replace spaces with underscores'
Задача 14: Admin-действие — смена статуса
@admin.register(Task)
class TaskAdmin(admin.ModelAdmin):
actions = ['change_status']
def change_status(self, request, objects):
for obj in objects:
obj.status = 'Closed'
obj.save()
return objects
change_status.short_description = 'Mark as Closed'
Задача 15: Admin-действия — смена приоритета (4 шт.)
@admin.register(Task)
class TaskAdmin(admin.ModelAdmin):
actions = [
'change_status',
'change_priority_to_low',
'change_priority_to_medium',
'change_priority_to_high',
'change_priority_to_very_high',
]
def change_priority_to_low(self, request, objects):
for obj in objects:
obj.priority = 'Low'
obj.save()
change_priority_to_low.short_description = 'Mark as Low priority'
def change_priority_to_medium(self, request, objects):
for obj in objects:
obj.priority = 'Medium'
obj.save()
change_priority_to_medium.short_description = 'Mark as Medium priority'
def change_priority_to_high(self, request, objects):
for obj in objects:
obj.priority = 'High'
obj.save()
change_priority_to_high.short_description = 'Mark as High priority'
def change_priority_to_very_high(self, request, objects):
for obj in objects:
obj.priority = 'Very High'
obj.save()
change_priority_to_very_high.short_description = 'Mark as Very High priority'
Задача 16: Группы разрешений
Логика: Группы настраиваются в Admin → Authentication and Authorization → Groups. Нажмите "Add group", задайте имя и выберите нужные разрешения из списка.
Manager — разрешения:
Authentication and Authorization | group | Can view group
Authentication and Authorization | permission | Can view permission
Authentication and Authorization | user | Can add user
Authentication and Authorization | user | Can view user
Practicum_2 | Project | Can add/change/delete/view project
Practicum_2 | Project File | Can add/change/delete/view project file
Practicum_2 | Tag | Can add/change/view tag
Practicum_2 | Task | Can add/change/view task
Client — разрешения:
Authentication and Authorization | user | Can add/change/view user
Practicum_2 | Project | Can add/change/view project
Practicum_2 | Project File | Can add/view project file
Practicum_2 | Tag | Can add/change/delete/view tag
Practicum_2 | Task | Can add/change/delete/view task
Developer — разрешения:
Authentication and Authorization | user | Can add/change/delete/view user
Practicum_2 | Project | Can add/change/delete/view project
Practicum_2 | Project File | Can add/change/delete/view project file
Practicum_2 | Tag | Can add/change/delete/view tag
Practicum_2 | Task | Can add/change/delete/view task
Задача 17: Пользователь с группой Client
Логика: Django предоставляет базовую модель User. Нужно создать нового пользователя, отредактировать его, добавив группу разрешений и флаг is_staff.
- Admin → Users → Add User → заполнить username/password
- Отредактировать пользователя: поставить галочку
Staff status - В разделе "Groups" выбрать группу "Client"
- Сохранить и войти под этим пользователем в Admin
- Убедиться, что недоступные действия (удаление проекта, удаление пользователя) не отображаются
Задача 18: Fixtures
Логика: dumpdata сериализует данные в JSON. --natural-foreign заменяет числовые ID на "естественные" ключи (имена), что делает fixture переносимым. loaddata воссоздаёт объекты из JSON.
# 1. Сохранить группы разрешений
python manage.py dumpdata auth.Group --natural-foreign --indent=4 \
> practicum_2/fixtures/groups_fixture.json
# 2. Сохранить пользователей
python manage.py dumpdata auth.User --natural-foreign --indent=4 \
> practicum_2/fixtures/users_fixture.json
# 3. Удалить базу данных (удалить файл db.sqlite3)
# 4. Провести все миграции заново
python manage.py makemigrations
python manage.py migrate
# 5. Применить снимок групп разрешений
python manage.py loaddata practicum_2/fixtures/groups_fixture.json
# 6. Применить снимок пользователей
python manage.py loaddata practicum_2/fixtures/users_fixture.json
loaddata завершится с ошибкой IntegrityError.