💻 Примеры: полная модель Library Project

Финальная схема всех моделей с комментариями

⚡ Схема Library Project

  • Author — first_name, last_name, birth_date, profile, deleted, rating
  • AuthorDetail — OneToOne → Author: biography, birth_city, gender
  • Category — name (unique)
  • Library — name, location, site
  • Member — email (unique), role, active; ManyToMany → Library
  • Book — title, author (FK), category (FK), libraries (M2M), publisher_id → Member
  • Borrow — member, book, library, borrow_date, return_date; метод is_overdue()
  • Review — book, reviewer, rating; Book.rating property
  • Posts — title (unique_for_date), body, author, library, created_at, updated_at (auto_now)
  • Event — library (FK), books (M2M); EventParticipant — event, member (M2M)

Полный models.py — Library Project

Финальная версия после прохождения всех 15 задач практикума.

Импорты и константы

from django.db import models
from django.core.validators import MinValueValidator, MaxValueValidator
from django.utils import timezone


# --- Choices ---

GENDER_CHOICES = [
    ('Male', 'Мужской'),
    ('Female', 'Женский'),
    ('Other', 'Другой'),
]

ROLE_CHOICES = [
    ('Admin', 'Администратор'),
    ('Staff', 'Сотрудник'),
    ('Reader', 'Читатель'),
]

GENRE_CHOICES = [
    ('Fiction', 'Художественная'),
    ('Non-Fiction', 'Документальная'),
    ('Science Fiction', 'Научная фантастика'),
    ('Fantasy', 'Фэнтези'),
    ('Mystery', 'Детектив'),
    ('Biography', 'Биография'),
]

Модель Author

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="Рейтинг автора"
    )

    def __str__(self):
        # Задача 14: имя и инициал фамилии
        return f"{self.first_name} {self.last_name[0]}."

    class Meta:
        verbose_name = "Автор"
        verbose_name_plural = "Авторы"

Модель AuthorDetail (OneToOne)

class AuthorDetail(models.Model):
    # OneToOne: у каждого автора — одна запись с деталями
    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)

    def __str__(self):
        return f"Детали: {self.author}"

    class Meta:
        verbose_name = "Детали автора"
        verbose_name_plural = "Детали авторов"

Модель Category и Library

class Category(models.Model):
    name = models.CharField(max_length=30, unique=True)

    def __str__(self):
        return self.name

    class Meta:
        verbose_name = "Категория"
        verbose_name_plural = "Категории"


class Library(models.Model):
    name = models.CharField(max_length=100)
    location = models.CharField(max_length=200)
    site = models.URLField(null=True, blank=True)

    def __str__(self):
        return self.name

    class Meta:
        verbose_name = "Библиотека"
        verbose_name_plural = "Библиотеки"

Модель Member

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)
    # ManyToMany: участник может состоять в нескольких библиотеках
    libraries = models.ManyToManyField('Library', related_name='members')

    def __str__(self):
        return f"{self.first_name} {self.last_name} ({self.role})"

    class Meta:
        verbose_name = "Участник"
        verbose_name_plural = "Участники"

Модель Book (центральная)

class Book(models.Model):
    title = models.CharField(max_length=100)
    # ForeignKey на Author: при удалении автора книга остаётся (SET_NULL)
    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)]
    )
    # ForeignKey на Category (один ко многим)
    category = models.ForeignKey(
        Category, null=True, on_delete=models.SET_NULL, related_name='books'
    )
    # ManyToMany с Library: книга может быть в нескольких библиотеках
    libraries = models.ManyToManyField(Library, related_name='books')
    # ForeignKey на Member (заменил Publisher в задаче 9)
    publisher_id = models.ForeignKey(Member, null=True, on_delete=models.CASCADE)

    @property
    def rating(self):
        """Средний рейтинг по всем отзывам (задача 12)."""
        reviews = self.reviews.all()
        total_reviews = reviews.count()
        if total_reviews == 0:
            return 0
        total_rating = sum(review.rating for review in reviews)
        return round(total_rating / total_reviews, 2)

    def __str__(self):
        return self.title

    class Meta:
        verbose_name = "Книга"
        verbose_name_plural = "Книги"

Модели Borrow и Review

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()

    def __str__(self):
        return f"{self.member} → {self.book}"

    class Meta:
        verbose_name = "Выдача книги"
        verbose_name_plural = "Выдачи книг"


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()

    def __str__(self):
        return f"Отзыв {self.reviewer} на {self.book}: {self.rating}"

    class Meta:
        verbose_name = "Отзыв"
        verbose_name_plural = "Отзывы"

Модели Posts, Event, EventParticipant

class Posts(models.Model):
    # unique_for_date: заголовок уникален в рамках одного дня
    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)  # обновляется автоматически

    def __str__(self):
        return self.title

    class Meta:
        verbose_name = "Пост"
        verbose_name_plural = "Посты"


class Event(models.Model):
    title = models.CharField(max_length=255)
    description = models.TextField()
    date = models.DateField()
    library = models.ForeignKey(Library, on_delete=models.CASCADE, related_name='events')
    books = models.ManyToManyField(Book, related_name='events')

    def __str__(self):
        return self.title

    class Meta:
        verbose_name = "Событие"
        verbose_name_plural = "События"


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)

    def __str__(self):
        return f"Участник события {self.event}"

    class Meta:
        verbose_name = "Участник события"
        verbose_name_plural = "Участники событий"

Работа в shell

# Запуск shell
python manage.py shell

# Создать автора
from library.models import Author
a = Author.objects.create(
    first_name="Leo", last_name="Tolstoy", birth_date="1828-09-09"
)

# Создать библиотеку
from library.models import Library
lib = Library.objects.create(name="Центральная", location="Москва, ул. Ленина 1")

# Создать книгу и добавить в библиотеку
from library.models import Book
book = Book.objects.create(title="Война и мир", author_id=a, publishing_date="1869-01-01")
book.libraries.add(lib)

# Получить все книги библиотеки
lib.books.all()

# Получить все библиотеки книги
book.libraries.all()