Добавление выдач

This commit is contained in:
2025-12-20 11:06:13 +03:00
parent 3473c31f73
commit 09b7cb17a5
16 changed files with 153 additions and 15 deletions
+1 -1
View File
@@ -3,7 +3,7 @@ from typing import Optional
# Конфигурация # Конфигурация
USERNAME = "admin" USERNAME = "admin"
PASSWORD = "n_ElBL9LTfTTgZSqHShqOg" PASSWORD = "7WaVlcj8EWzEbbdab9kqRw"
BASE_URL = "http://localhost:8000" BASE_URL = "http://localhost:8000"
+1
View File
@@ -1,3 +1,4 @@
"""Модуль моделей""" """Модуль моделей"""
from .db import * from .db import *
from .dto import * from .dto import *
from .enums import *
+3
View File
@@ -5,6 +5,7 @@ from sqlmodel import Field, Relationship
from library_service.models.dto.book import BookBase from library_service.models.dto.book import BookBase
from library_service.models.db.links import AuthorBookLink, GenreBookLink from library_service.models.db.links import AuthorBookLink, GenreBookLink
from library_service.models.enums import BookStatus
if TYPE_CHECKING: if TYPE_CHECKING:
from .author import Author from .author import Author
@@ -14,9 +15,11 @@ if TYPE_CHECKING:
class Book(BookBase, table=True): class Book(BookBase, table=True):
"""Модель книги в базе данных""" """Модель книги в базе данных"""
id: int | None = Field(default=None, primary_key=True, index=True) id: int | None = Field(default=None, primary_key=True, index=True)
status: BookStatus = Field(default=BookStatus.ACTIVE)
authors: List["Author"] = Relationship( authors: List["Author"] = Relationship(
back_populates="books", link_model=AuthorBookLink back_populates="books", link_model=AuthorBookLink
) )
genres: List["Genre"] = Relationship( genres: List["Genre"] = Relationship(
back_populates="books", link_model=GenreBookLink back_populates="books", link_model=GenreBookLink
) )
loans: List["BookUserLink"] = Relationship(sa_relationship_kwargs={"cascade": "all, delete"})
+18
View File
@@ -1,4 +1,5 @@
"""Модуль связей между сущностями в БД""" """Модуль связей между сущностями в БД"""
from datetime import datetime
from sqlmodel import SQLModel, Field 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) 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) 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)
+1
View File
@@ -26,3 +26,4 @@ class User(UserBase, table=True):
# Связи # Связи
roles: List["Role"] = Relationship(back_populates="users", link_model=UserRoleLink) roles: List["Role"] = Relationship(back_populates="users", link_model=UserRoleLink)
loans: List["BookUserLink"] = Relationship(sa_relationship_kwargs={"cascade": "all, delete"})
+15 -7
View File
@@ -4,9 +4,10 @@ from .genre import GenreBase, GenreCreate, GenreList, GenreRead, GenreUpdate
from .book import BookBase, BookCreate, BookList, BookRead, BookUpdate from .book import BookBase, BookCreate, BookList, BookRead, BookUpdate
from .role import RoleBase, RoleCreate, RoleList, RoleRead, RoleUpdate from .role import RoleBase, RoleCreate, RoleList, RoleRead, RoleUpdate
from .user import UserBase, UserCreate, UserList, UserRead, UserUpdate, UserLogin from .user import UserBase, UserCreate, UserList, UserRead, UserUpdate, UserLogin
from .loan import LoanBase, LoanCreate, LoanList, LoanRead, LoanUpdate
from .token import Token, TokenData from .token import Token, TokenData
from .combined import (AuthorWithBooks, GenreWithBooks, BookWithAuthors, BookWithGenres, from .combined import (AuthorWithBooks, GenreWithBooks, BookWithAuthors, BookWithGenres,
BookWithAuthorsAndGenres, BookFilteredList) BookWithAuthorsAndGenres, BookFilteredList, BookStatusUpdate, LoanWithBook)
__all__ = [ __all__ = [
"AuthorBase", "AuthorBase",
@@ -20,11 +21,24 @@ __all__ = [
"BookRead", "BookRead",
"BookList", "BookList",
"BookFilteredList", "BookFilteredList",
"BookStatusUpdate",
"GenreBase", "GenreBase",
"GenreCreate", "GenreCreate",
"GenreUpdate", "GenreUpdate",
"GenreRead", "GenreRead",
"GenreList", "GenreList",
"LoanBase",
"LoanCreate",
"LoanUpdate",
"LoanRead",
"LoanList",
"LoanWithBook",
"UserBase",
"UserCreate",
"UserUpdate",
"UserRead",
"UserList",
"UserLogin",
"RoleBase", "RoleBase",
"RoleCreate", "RoleCreate",
"RoleUpdate", "RoleUpdate",
@@ -32,10 +46,4 @@ __all__ = [
"RoleList", "RoleList",
"Token", "Token",
"TokenData", "TokenData",
"UserBase",
"UserCreate",
"UserRead",
"UserUpdate",
"UserList",
"UserLogin",
] ]
+4 -3
View File
@@ -1,11 +1,10 @@
"""Модуль DTO-моделей книг""" """Модуль DTO-моделей книг"""
from typing import List, TYPE_CHECKING from typing import List
from pydantic import ConfigDict from pydantic import ConfigDict
from sqlmodel import SQLModel from sqlmodel import SQLModel
if TYPE_CHECKING: from library_service.models.enums import BookStatus
from .combined import BookWithAuthorsAndGenres
class BookBase(SQLModel): class BookBase(SQLModel):
@@ -29,11 +28,13 @@ class BookUpdate(SQLModel):
"""Модель книги для обновления""" """Модель книги для обновления"""
title: str | None = None title: str | None = None
description: str | None = None description: str | None = None
status: BookStatus | None = None
class BookRead(BookBase): class BookRead(BookBase):
"""Модель книги для чтения""" """Модель книги для чтения"""
id: int id: int
status: BookStatus
class BookList(SQLModel): class BookList(SQLModel):
+9
View File
@@ -5,6 +5,7 @@ from sqlmodel import SQLModel, Field
from .author import AuthorRead from .author import AuthorRead
from .genre import GenreRead from .genre import GenreRead
from .book import BookRead from .book import BookRead
from .loan import LoanRead
class AuthorWithBooks(SQLModel): class AuthorWithBooks(SQLModel):
@@ -50,3 +51,11 @@ class BookFilteredList(SQLModel):
"""Список книг с фильтрацией""" """Список книг с фильтрацией"""
books: List[BookWithAuthorsAndGenres] books: List[BookWithAuthorsAndGenres]
total: int total: int
class LoanWithBook(LoanRead):
"""Модель выдачи, включающая данные о книге"""
book: BookRead
class BookStatusUpdate(SQLModel):
"""Модель для ручного изменения статуса библиотекарем"""
status: str
+35
View File
@@ -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
+10
View File
@@ -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"
+1 -1
View File
@@ -250,4 +250,4 @@ def get_roles(
return RoleList( return RoleList(
roles=[RoleRead(**role.model_dump()) for role in roles], roles=[RoleRead(**role.model_dump()) for role in roles],
total=len(roles), total=len(roles),
) )
+1
View File
@@ -20,6 +20,7 @@ if config.config_file_name is not None:
# add your model's MetaData object here # add your model's MetaData object here
# for 'autogenerate' support # for 'autogenerate' support
from library_service.models.enums import *
from library_service.models.db import * from library_service.models.db import *
target_metadata = SQLModel.metadata target_metadata = SQLModel.metadata
+51
View File
@@ -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 ###
+1 -1
View File
@@ -1,4 +1,4 @@
"""genres """Genres
Revision ID: 9d7a43ac5dfc Revision ID: 9d7a43ac5dfc
Revises: d266fdc61e99 Revises: d266fdc61e99
+1 -1
View File
@@ -1,4 +1,4 @@
"""auth """Auth
Revision ID: b838606ad8d1 Revision ID: b838606ad8d1
Revises: 9d7a43ac5dfc Revises: 9d7a43ac5dfc
+1 -1
View File
@@ -1,4 +1,4 @@
"""init """Init
Revision ID: d266fdc61e99 Revision ID: d266fdc61e99
Revises: Revises: