From 09b7cb17a5ec0918c4a38f7821eecac8282c9bc9 Mon Sep 17 00:00:00 2001 From: wowlikon Date: Sat, 20 Dec 2025 11:06:13 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D0=B2=D1=8B=D0=B4=D0=B0=D1=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- data.py | 2 +- library_service/models/__init__.py | 1 + library_service/models/db/book.py | 3 ++ library_service/models/db/links.py | 18 ++++++++ library_service/models/db/user.py | 1 + library_service/models/dto/__init__.py | 22 +++++++--- library_service/models/dto/book.py | 7 +-- library_service/models/dto/combined.py | 9 ++++ library_service/models/dto/loan.py | 35 +++++++++++++++ library_service/models/enums.py | 10 +++++ library_service/routers/auth.py | 2 +- migrations/env.py | 1 + migrations/versions/02ed6e775351_loans.py | 51 ++++++++++++++++++++++ migrations/versions/9d7a43ac5dfc_genres.py | 2 +- migrations/versions/b838606ad8d1_auth.py | 2 +- migrations/versions/d266fdc61e99_init.py | 2 +- 16 files changed, 153 insertions(+), 15 deletions(-) create mode 100644 library_service/models/dto/loan.py create mode 100644 library_service/models/enums.py create mode 100644 migrations/versions/02ed6e775351_loans.py diff --git a/data.py b/data.py index 98ff870..d0842da 100644 --- a/data.py +++ b/data.py @@ -3,7 +3,7 @@ from typing import Optional # Конфигурация USERNAME = "admin" -PASSWORD = "n_ElBL9LTfTTgZSqHShqOg" +PASSWORD = "7WaVlcj8EWzEbbdab9kqRw" BASE_URL = "http://localhost:8000" diff --git a/library_service/models/__init__.py b/library_service/models/__init__.py index 28ca7ae..a9b0e8b 100644 --- a/library_service/models/__init__.py +++ b/library_service/models/__init__.py @@ -1,3 +1,4 @@ """Модуль моделей""" from .db import * from .dto import * +from .enums import * \ No newline at end of file diff --git a/library_service/models/db/book.py b/library_service/models/db/book.py index 55218f3..0fecbfd 100644 --- a/library_service/models/db/book.py +++ b/library_service/models/db/book.py @@ -5,6 +5,7 @@ from sqlmodel import Field, Relationship from library_service.models.dto.book import BookBase from library_service.models.db.links import AuthorBookLink, GenreBookLink +from library_service.models.enums import BookStatus if TYPE_CHECKING: from .author import Author @@ -14,9 +15,11 @@ if TYPE_CHECKING: class Book(BookBase, table=True): """Модель книги в базе данных""" id: int | None = Field(default=None, primary_key=True, index=True) + status: BookStatus = Field(default=BookStatus.ACTIVE) authors: List["Author"] = Relationship( back_populates="books", link_model=AuthorBookLink ) genres: List["Genre"] = Relationship( back_populates="books", link_model=GenreBookLink ) + loans: List["BookUserLink"] = Relationship(sa_relationship_kwargs={"cascade": "all, delete"}) diff --git a/library_service/models/db/links.py b/library_service/models/db/links.py index 1a9bc2c..a706035 100644 --- a/library_service/models/db/links.py +++ b/library_service/models/db/links.py @@ -1,4 +1,5 @@ """Модуль связей между сущностями в БД""" +from datetime import datetime from sqlmodel import SQLModel, Field @@ -22,3 +23,20 @@ class UserRoleLink(SQLModel, table=True): user_id: int | None = Field(default=None, foreign_key="users.id", primary_key=True) role_id: int | None = Field(default=None, foreign_key="roles.id", primary_key=True) + + +class BookUserLink(SQLModel, table=True): + """ + Модель истории выдачи книг (Loan). + Связывает книгу и пользователя с фиксацией времени. + """ + __tablename__ = "book_loans" + + id: int | None = Field(default=None, primary_key=True, index=True) + + book_id: int = Field(foreign_key="book.id") + user_id: int = Field(foreign_key="users.id") + + borrowed_at: datetime = Field(default_factory=datetime.utcnow) + due_date: datetime + returned_at: datetime | None = Field(default=None) \ No newline at end of file diff --git a/library_service/models/db/user.py b/library_service/models/db/user.py index ff1e3f9..cb14b00 100644 --- a/library_service/models/db/user.py +++ b/library_service/models/db/user.py @@ -26,3 +26,4 @@ class User(UserBase, table=True): # Связи roles: List["Role"] = Relationship(back_populates="users", link_model=UserRoleLink) + loans: List["BookUserLink"] = Relationship(sa_relationship_kwargs={"cascade": "all, delete"}) diff --git a/library_service/models/dto/__init__.py b/library_service/models/dto/__init__.py index 22cf1f1..ded0399 100644 --- a/library_service/models/dto/__init__.py +++ b/library_service/models/dto/__init__.py @@ -4,9 +4,10 @@ from .genre import GenreBase, GenreCreate, GenreList, GenreRead, GenreUpdate from .book import BookBase, BookCreate, BookList, BookRead, BookUpdate from .role import RoleBase, RoleCreate, RoleList, RoleRead, RoleUpdate from .user import UserBase, UserCreate, UserList, UserRead, UserUpdate, UserLogin +from .loan import LoanBase, LoanCreate, LoanList, LoanRead, LoanUpdate from .token import Token, TokenData from .combined import (AuthorWithBooks, GenreWithBooks, BookWithAuthors, BookWithGenres, - BookWithAuthorsAndGenres, BookFilteredList) + BookWithAuthorsAndGenres, BookFilteredList, BookStatusUpdate, LoanWithBook) __all__ = [ "AuthorBase", @@ -20,11 +21,24 @@ __all__ = [ "BookRead", "BookList", "BookFilteredList", + "BookStatusUpdate", "GenreBase", "GenreCreate", "GenreUpdate", "GenreRead", "GenreList", + "LoanBase", + "LoanCreate", + "LoanUpdate", + "LoanRead", + "LoanList", + "LoanWithBook", + "UserBase", + "UserCreate", + "UserUpdate", + "UserRead", + "UserList", + "UserLogin", "RoleBase", "RoleCreate", "RoleUpdate", @@ -32,10 +46,4 @@ __all__ = [ "RoleList", "Token", "TokenData", - "UserBase", - "UserCreate", - "UserRead", - "UserUpdate", - "UserList", - "UserLogin", ] diff --git a/library_service/models/dto/book.py b/library_service/models/dto/book.py index 546cfea..514ad4f 100644 --- a/library_service/models/dto/book.py +++ b/library_service/models/dto/book.py @@ -1,11 +1,10 @@ """Модуль DTO-моделей книг""" -from typing import List, TYPE_CHECKING +from typing import List from pydantic import ConfigDict from sqlmodel import SQLModel -if TYPE_CHECKING: - from .combined import BookWithAuthorsAndGenres +from library_service.models.enums import BookStatus class BookBase(SQLModel): @@ -29,11 +28,13 @@ class BookUpdate(SQLModel): """Модель книги для обновления""" title: str | None = None description: str | None = None + status: BookStatus | None = None class BookRead(BookBase): """Модель книги для чтения""" id: int + status: BookStatus class BookList(SQLModel): diff --git a/library_service/models/dto/combined.py b/library_service/models/dto/combined.py index 8e0f46c..2e328ae 100644 --- a/library_service/models/dto/combined.py +++ b/library_service/models/dto/combined.py @@ -5,6 +5,7 @@ from sqlmodel import SQLModel, Field from .author import AuthorRead from .genre import GenreRead from .book import BookRead +from .loan import LoanRead class AuthorWithBooks(SQLModel): @@ -50,3 +51,11 @@ class BookFilteredList(SQLModel): """Список книг с фильтрацией""" books: List[BookWithAuthorsAndGenres] total: int + +class LoanWithBook(LoanRead): + """Модель выдачи, включающая данные о книге""" + book: BookRead + +class BookStatusUpdate(SQLModel): + """Модель для ручного изменения статуса библиотекарем""" + status: str \ No newline at end of file diff --git a/library_service/models/dto/loan.py b/library_service/models/dto/loan.py new file mode 100644 index 0000000..36a2e90 --- /dev/null +++ b/library_service/models/dto/loan.py @@ -0,0 +1,35 @@ +"""Модуль DTO-моделей для выдачи книг""" +from typing import List + +from datetime import datetime +from sqlmodel import SQLModel + + +class LoanBase(SQLModel): + """Базовая модель выдачи""" + book_id: int + user_id: int + due_date: datetime + + +class LoanCreate(LoanBase): + """Модель для создания записи о выдаче""" + pass + + +class LoanUpdate(SQLModel): + """Модель для обновления записи о выдаче""" + returned_at: datetime | None = None + + +class LoanRead(LoanBase): + """Модель чтения записи о выдаче""" + id: int + borrowed_at: datetime + returned_at: datetime | None = None + + +class LoanList(SQLModel): + """Список выдач""" + loans: List[LoanRead] + total: int diff --git a/library_service/models/enums.py b/library_service/models/enums.py new file mode 100644 index 0000000..75597c1 --- /dev/null +++ b/library_service/models/enums.py @@ -0,0 +1,10 @@ +"""Модуль перечислений (Enums)""" +from enum import Enum + +class BookStatus(str, Enum): + """Статусы книги""" + ACTIVE = "active" + BORROWED = "borrowed" + RESERVED = "reserved" + RESTORATION = "restoration" + WRITTEN_OFF = "written_off" \ No newline at end of file diff --git a/library_service/routers/auth.py b/library_service/routers/auth.py index ab34fa6..63f06e6 100644 --- a/library_service/routers/auth.py +++ b/library_service/routers/auth.py @@ -250,4 +250,4 @@ def get_roles( return RoleList( roles=[RoleRead(**role.model_dump()) for role in roles], total=len(roles), - ) \ No newline at end of file + ) diff --git a/migrations/env.py b/migrations/env.py index af5c8d9..3e72b52 100644 --- a/migrations/env.py +++ b/migrations/env.py @@ -20,6 +20,7 @@ if config.config_file_name is not None: # add your model's MetaData object here # for 'autogenerate' support +from library_service.models.enums import * from library_service.models.db import * target_metadata = SQLModel.metadata diff --git a/migrations/versions/02ed6e775351_loans.py b/migrations/versions/02ed6e775351_loans.py new file mode 100644 index 0000000..0dee15c --- /dev/null +++ b/migrations/versions/02ed6e775351_loans.py @@ -0,0 +1,51 @@ +"""Loans + +Revision ID: 02ed6e775351 +Revises: b838606ad8d1 +Create Date: 2025-12-20 10:36:30.853896 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +import sqlmodel + + +# revision identifiers, used by Alembic. +revision: str = '02ed6e775351' +down_revision: Union[str, None] = 'b838606ad8d1' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + book_status_enum = sa.Enum('active', 'borrowed', 'reserved', 'restoration', 'written_off', name='bookstatus') + book_status_enum.create(op.get_bind()) + op.create_table('book_loans', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('book_id', sa.Integer(), nullable=False), + sa.Column('user_id', sa.Integer(), nullable=False), + sa.Column('borrowed_at', sa.DateTime(), nullable=False), + sa.Column('due_date', sa.DateTime(), nullable=False), + sa.Column('returned_at', sa.DateTime(), nullable=True), + sa.ForeignKeyConstraint(['book_id'], ['book.id'], ), + sa.ForeignKeyConstraint(['user_id'], ['users.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_index(op.f('ix_book_loans_id'), 'book_loans', ['id'], unique=False) + op.add_column('book', sa.Column('status', book_status_enum, nullable=False, server_default='active')) + op.drop_index(op.f('ix_roles_name'), table_name='roles') + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.create_index(op.f('ix_roles_name'), 'roles', ['name'], unique=True) + op.drop_column('book', 'status') + op.drop_index(op.f('ix_book_loans_id'), table_name='book_loans') + op.drop_table('book_loans') + book_status_enum = sa.Enum('active', 'borrowed', 'reserved', 'restoration', 'written_off', name='bookstatus') + book_status_enum.drop(op.get_bind()) + # ### end Alembic commands ### diff --git a/migrations/versions/9d7a43ac5dfc_genres.py b/migrations/versions/9d7a43ac5dfc_genres.py index 21d4227..5bda391 100644 --- a/migrations/versions/9d7a43ac5dfc_genres.py +++ b/migrations/versions/9d7a43ac5dfc_genres.py @@ -1,4 +1,4 @@ -"""genres +"""Genres Revision ID: 9d7a43ac5dfc Revises: d266fdc61e99 diff --git a/migrations/versions/b838606ad8d1_auth.py b/migrations/versions/b838606ad8d1_auth.py index 5995ff3..2bb6bd3 100644 --- a/migrations/versions/b838606ad8d1_auth.py +++ b/migrations/versions/b838606ad8d1_auth.py @@ -1,4 +1,4 @@ -"""auth +"""Auth Revision ID: b838606ad8d1 Revises: 9d7a43ac5dfc diff --git a/migrations/versions/d266fdc61e99_init.py b/migrations/versions/d266fdc61e99_init.py index 012e677..3b9342e 100644 --- a/migrations/versions/d266fdc61e99_init.py +++ b/migrations/versions/d266fdc61e99_init.py @@ -1,4 +1,4 @@ -"""init +"""Init Revision ID: d266fdc61e99 Revises: