Задача 1: Создание проекта и модели Author
Логика: Создаём Django-проект, регистрируем приложение в INSTALLED_APPS, определяем базовую модель, применяем миграции.
# Команды создания проекта
django-admin startproject config .
python manage.py startapp library
# config/settings.py — добавить в INSTALLED_APPS:
INSTALLED_APPS = [
...
'library',
]
# library/models.py
from django.db import models
class Author(models.Model):
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)
birth_date = models.DateField()
# library/admin.py
from django.contrib import admin
from .models import Author
admin.site.register(Author)
# Применение миграций
python manage.py makemigrations
python manage.py migrate
python manage.py createsuperuser # для входа в Admin
migrate запустите сервер (runserver) и откройте http://127.0.0.1:8000/admin/ — в разделе Library вы увидите «Authors».Задача 2: Дополнительные поля Author
Логика: URLField для ссылки, BooleanField для флага удаления, IntegerField с валидаторами для рейтинга.
from django.core.validators import MinValueValidator, MaxValueValidator
class Author(models.Model):
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)
birth_date = models.DateField()
profile = models.URLField(null=True, blank=True)
deleted = models.BooleanField(default=False)
rating = models.IntegerField(
default=1,
validators=[MinValueValidator(1), MaxValueValidator(10)]
)
После изменений: python manage.py makemigrations && python manage.py migrate
Задача 3: verbose_name и help_text
Логика: verbose_name меняет отображение поля в Admin-интерфейсе, не затрагивая имена колонок в БД. help_text добавляет подсказку под полем в форме Admin.
class Author(models.Model):
first_name = models.CharField(max_length=100, verbose_name="Имя")
last_name = models.CharField(max_length=100, verbose_name="Фамилия")
birth_date = models.DateField(verbose_name="Дата рождения")
profile = models.URLField(null=True, blank=True, verbose_name="Ссылка на профиль")
deleted = models.BooleanField(
default=False,
verbose_name="Удалён ли автор",
help_text="Если False - автор активен. Если True - автора больше нет в списке доступных"
)
rating = models.IntegerField(
default=1,
validators=[MinValueValidator(1), MaxValueValidator(10)],
verbose_name="Рейтинг автора"
)
verbose_name и help_text хранятся только в Python-коде, не в схеме БД. Django может создать пустую миграцию, но применение её не изменит таблицу.Задача 4: Модель Book с ForeignKey
Логика: Связь Author→Book — «один ко многим». on_delete=models.SET_NULL означает: при удалении автора поле author_id у книги становится NULL (книга не удаляется).
class Book(models.Model):
title = models.CharField(max_length=100)
author_id = models.ForeignKey(Author, null=True, on_delete=models.SET_NULL)
publishing_date = models.DateField()
on_delete обязателен в Django 2.0+. Без него получите ошибку TypeError.Задача 5: Дополнение Book — choices и validators
Логика: choices ограничивает допустимые значения поля. null=True, blank=True делает поле необязательным и в БД, и в формах.
GENRE_CHOICES = [
('Fiction', 'Fiction'),
('Non-Fiction', 'Non-Fiction'),
('Science Fiction', 'Science Fiction'),
('Fantasy', 'Fantasy'),
('Mystery', 'Mystery'),
('Biography', 'Biography'),
]
class Book(models.Model):
title = models.CharField(max_length=100)
author_id = models.ForeignKey(Author, null=True, on_delete=models.SET_NULL)
publishing_date = models.DateField()
summary = models.TextField(null=True, blank=True)
genre = models.CharField(max_length=50, null=True, choices=GENRE_CHOICES)
page_count = models.IntegerField(
null=True, blank=True,
validators=[MaxValueValidator(10000)]
)
Задача 6: Модель Publisher + Book.publisher_id
Логика: Создаём отдельную таблицу Publisher и ссылаемся на неё из Book через ForeignKey.
class Publisher(models.Model):
name = models.CharField(max_length=100)
address = models.CharField(max_length=255, null=True)
city = models.CharField(max_length=100, null=True)
country = models.CharField(max_length=100)
class Book(models.Model):
...
publisher_id = models.ForeignKey(Publisher, null=True, on_delete=models.CASCADE)
Задача 7: Модель Category + related_name
Логика: Связь Category→Book — «один ко многим» (у одной категории много книг, у книги одна категория). related_name='books' позволяет обратный доступ: category.books.all().
class Category(models.Model):
name = models.CharField(max_length=30, unique=True)
class Book(models.Model):
...
category = models.ForeignKey(
Category, null=True, on_delete=models.SET_NULL, related_name='books'
)
Задача 8: Модель Library + ManyToMany
Логика: Библиотека↔Книга — «многие ко многим». Одна книга может быть в нескольких библиотеках, одна библиотека содержит много книг. Django сам создаёт промежуточную таблицу.
class Library(models.Model):
name = models.CharField(max_length=100)
location = models.CharField(max_length=200)
site = models.URLField(null=True, blank=True)
class Book(models.Model):
...
libraries = models.ManyToManyField(Library, related_name='books')
# Использование в shell:
book.libraries.all() # все библиотеки книги
library.books.all() # все книги библиотеки
book.libraries.add(lib) # добавить книгу в библиотеку
Задача 9: Модель Member, удалить Publisher
Логика: Member заменяет Publisher. Удаляем класс Publisher из models.py, меняем ForeignKey в Book. null=True на publisher_id гарантирует, что книги с пустым полем не вызовут ошибку.
GENDER_CHOICES = [
('Male', 'Мужской'),
('Female', 'Женский'),
('Other', 'Другой'),
]
ROLE_CHOICES = [
('Admin', 'Администратор'),
('Staff', 'Сотрудник'),
('Reader', 'Читатель'),
]
class Member(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
email = models.EmailField(unique=True)
gender = models.CharField(max_length=50, choices=GENDER_CHOICES)
birth_date = models.DateField()
age = models.IntegerField(validators=[MinValueValidator(6), MaxValueValidator(120)])
role = models.CharField(max_length=20, choices=ROLE_CHOICES)
active = models.BooleanField(default=True)
libraries = models.ManyToManyField('Library', related_name='members')
class Book(models.Model):
...
publisher_id = models.ForeignKey(Member, null=True, on_delete=models.CASCADE)
Задача 10: Модель Posts с unique_for_date
Логика: unique_for_date='created_at' означает: комбинация (title, дата created_at) должна быть уникальной. auto_now=True на updated_at — дата обновляется автоматически при каждом .save().
class Posts(models.Model):
title = models.CharField(max_length=255, unique_for_date='created_at')
body = models.TextField()
author = models.ForeignKey(Member, on_delete=models.CASCADE, related_name='posts')
moderated = models.BooleanField(default=False)
library = models.ForeignKey(Library, on_delete=models.CASCADE, related_name='posts')
created_at = models.DateField()
updated_at = models.DateField(auto_now=True)
Задача 11: Модель Borrow + метод is_overdue
Логика: Три ForeignKey — по одному на каждую связанную сущность. Метод is_overdue() сравнивает return_date с текущей датой через timezone.now().date() (timezone-aware, учитывает часовой пояс Django).
from django.utils import timezone
class Borrow(models.Model):
member = models.ForeignKey(Member, on_delete=models.CASCADE, related_name='borrows')
book = models.ForeignKey(Book, on_delete=models.CASCADE, related_name='borrows')
library = models.ForeignKey(Library, on_delete=models.CASCADE, related_name='borrows')
borrow_date = models.DateField()
return_date = models.DateField()
returned = models.BooleanField(default=False)
def is_overdue(self):
if self.returned:
return False # книга возвращена — просрочки нет
return self.return_date < timezone.now().date()
Задача 12: Модель Review + property на Book
Логика: @property не создаёт колонку в БД — это вычисляемое значение. При обращении к book.rating Django делает запрос в таблицу Review и считает среднее.
class Review(models.Model):
book = models.ForeignKey(Book, on_delete=models.CASCADE, related_name='reviews')
reviewer = models.ForeignKey(Member, on_delete=models.CASCADE, related_name='reviews')
rating = models.FloatField()
description = models.TextField()
# Добавить в класс Book:
class Book(models.Model):
...
@property
def rating(self):
reviews = self.reviews.all()
total_reviews = reviews.count()
if total_reviews == 0:
return 0
total_rating = sum(review.rating for review in reviews)
average_rating = total_rating / total_reviews
return round(average_rating, 2)
rating в задаче 2 и @property rating в задаче 12 конфликтуют — они определены в одном классе Author / Book. В данном практикуме rating IntegerField у Author — рейтинг популярности, а @property rating у Book — средняя оценка отзывов. Это разные модели, конфликта нет.Задача 13: Модель AuthorDetail с OneToOneField
Логика: OneToOne гарантирует, что у каждого автора может быть ровно одна запись AuthorDetail. related_name='details' позволяет author.details.biography.
class AuthorDetail(models.Model):
author = models.OneToOneField(Author, on_delete=models.CASCADE, related_name='details')
biography = models.TextField()
birth_city = models.CharField(max_length=50)
gender = models.CharField(max_length=50, choices=GENDER_CHOICES)
# Использование:
author.details.biography # получить биографию
AuthorDetail.objects.create(author=author, biography="...", birth_city="Тула", gender="Male")
Задача 14: __str__ для всех моделей
Логика: Метод __str__ определяет, как объект отображается в Django Admin, shell, и repr(). Под каждую модель — свой формат.
class Author(models.Model):
def __str__(self):
return f"{self.first_name} {self.last_name[0]}." # "Leo T."
class Book(models.Model):
def __str__(self):
return self.title
class Library(models.Model):
def __str__(self):
return self.name
class Member(models.Model):
def __str__(self):
return f"{self.first_name} {self.last_name} ({self.role})"
class Category(models.Model):
def __str__(self):
return self.name
class Borrow(models.Model):
def __str__(self):
return f"{self.member} → {self.book}"
class Review(models.Model):
def __str__(self):
return f"Отзыв {self.reviewer} на {self.book}: {self.rating}"
class AuthorDetail(models.Model):
def __str__(self):
return f"Детали: {self.author}"
class Posts(models.Model):
def __str__(self):
return self.title
class Event(models.Model):
def __str__(self):
return self.title
Задача 15: Система событий Event + EventParticipant
Логика: Event связан с Library (ForeignKey) и книгами (ManyToMany). EventParticipant связывает событие и участника. default=timezone.now — текущая дата по умолчанию (передаём функцию, не её результат).
class Event(models.Model):
title = models.CharField(max_length=255)
description = models.TextField()
date = models.DateField() # в лекции DateField; для дат+времени — DateTimeField
library = models.ForeignKey(Library, on_delete=models.CASCADE, related_name='events')
books = models.ManyToManyField(Book, related_name='events')
class EventParticipant(models.Model):
event = models.ForeignKey(Event, on_delete=models.CASCADE, related_name='participants')
member = models.ManyToManyField(Member, related_name='event_participations')
registration_date = models.DateField(default=timezone.now)
В задаче сказано «и дата, и время» для поля события, однако в решении лекции использован
DateField. Если нужно хранить время начала события — используйте DateTimeField.