mirror of
https://github.com/wowlikon/LiB.git
synced 2026-02-04 04:31:09 +00:00
Добавление количества страниц книгам
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
# Postgres
|
# Postgres
|
||||||
POSTGRES_HOST="db"
|
POSTGRES_HOST="localhost"
|
||||||
POSTGRES_PORT="5432"
|
POSTGRES_PORT="5432"
|
||||||
POSTGRES_USER="postgres"
|
POSTGRES_USER="postgres"
|
||||||
POSTGRES_PASSWORD="postgres"
|
POSTGRES_PASSWORD="postgres"
|
||||||
|
|||||||
@@ -1,43 +1,55 @@
|
|||||||
"""Модуль DTO-моделей книг"""
|
"""Модуль DTO-моделей книг"""
|
||||||
|
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
from pydantic import ConfigDict
|
from pydantic import ConfigDict
|
||||||
from sqlmodel import SQLModel
|
from sqlmodel import SQLModel, Field
|
||||||
|
|
||||||
from library_service.models.enums import BookStatus
|
from library_service.models.enums import BookStatus
|
||||||
|
|
||||||
|
|
||||||
class BookBase(SQLModel):
|
class BookBase(SQLModel):
|
||||||
"""Базовая модель книги"""
|
"""Базовая модель книги"""
|
||||||
|
|
||||||
title: str
|
title: str
|
||||||
description: str
|
description: str
|
||||||
|
page_count: int = Field(gt=0)
|
||||||
|
|
||||||
model_config = ConfigDict( # pyright: ignore
|
model_config = ConfigDict( # pyright: ignore
|
||||||
json_schema_extra={
|
json_schema_extra={
|
||||||
"example": {"title": "book_title", "description": "book_description"}
|
"example": {
|
||||||
|
"title": "book_title",
|
||||||
|
"description": "book_description",
|
||||||
|
"page_count": 1,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class BookCreate(BookBase):
|
class BookCreate(BookBase):
|
||||||
"""Модель книги для создания"""
|
"""Модель книги для создания"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class BookUpdate(SQLModel):
|
class BookUpdate(SQLModel):
|
||||||
"""Модель книги для обновления"""
|
"""Модель книги для обновления"""
|
||||||
|
|
||||||
title: str | None = None
|
title: str | None = None
|
||||||
description: str | None = None
|
description: str | None = None
|
||||||
|
page_count: int | None = None
|
||||||
status: BookStatus | None = None
|
status: BookStatus | None = None
|
||||||
|
|
||||||
|
|
||||||
class BookRead(BookBase):
|
class BookRead(BookBase):
|
||||||
"""Модель книги для чтения"""
|
"""Модель книги для чтения"""
|
||||||
|
|
||||||
id: int
|
id: int
|
||||||
status: BookStatus
|
status: BookStatus
|
||||||
|
|
||||||
|
|
||||||
class BookList(SQLModel):
|
class BookList(SQLModel):
|
||||||
"""Список книг"""
|
"""Список книг"""
|
||||||
|
|
||||||
books: List[BookRead]
|
books: List[BookRead]
|
||||||
total: int
|
total: int
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ class BookWithAuthors(SQLModel):
|
|||||||
id: int
|
id: int
|
||||||
title: str
|
title: str
|
||||||
description: str
|
description: str
|
||||||
|
page_count: int
|
||||||
authors: List[AuthorRead] = Field(default_factory=list)
|
authors: List[AuthorRead] = Field(default_factory=list)
|
||||||
|
|
||||||
|
|
||||||
@@ -46,6 +47,7 @@ class BookWithGenres(SQLModel):
|
|||||||
id: int
|
id: int
|
||||||
title: str
|
title: str
|
||||||
description: str
|
description: str
|
||||||
|
page_count: int
|
||||||
status: BookStatus | None = None
|
status: BookStatus | None = None
|
||||||
genres: List[GenreRead] = Field(default_factory=list)
|
genres: List[GenreRead] = Field(default_factory=list)
|
||||||
|
|
||||||
@@ -56,6 +58,7 @@ class BookWithAuthorsAndGenres(SQLModel):
|
|||||||
id: int
|
id: int
|
||||||
title: str
|
title: str
|
||||||
description: str
|
description: str
|
||||||
|
page_count: int
|
||||||
status: BookStatus | None = None
|
status: BookStatus | None = None
|
||||||
authors: List[AuthorRead] = Field(default_factory=list)
|
authors: List[AuthorRead] = Field(default_factory=list)
|
||||||
genres: List[GenreRead] = Field(default_factory=list)
|
genres: List[GenreRead] = Field(default_factory=list)
|
||||||
|
|||||||
@@ -234,6 +234,12 @@ $(document).ready(() => {
|
|||||||
function renderBook(book) {
|
function renderBook(book) {
|
||||||
$("#book-title").text(book.title);
|
$("#book-title").text(book.title);
|
||||||
$("#book-id").text(`ID: ${book.id}`);
|
$("#book-id").text(`ID: ${book.id}`);
|
||||||
|
if (book.page_count && book.page_count > 0) {
|
||||||
|
$("#book-page-count-value").text(book.page_count);
|
||||||
|
$("#book-page-count-text").removeClass("hidden");
|
||||||
|
} else {
|
||||||
|
$("#book-page-count-text").addClass("hidden");
|
||||||
|
}
|
||||||
$("#book-authors-text").text(
|
$("#book-authors-text").text(
|
||||||
book.authors.map((a) => a.name).join(", ") || "Автор неизвестен",
|
book.authors.map((a) => a.name).join(", ") || "Автор неизвестен",
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -180,6 +180,11 @@ $(document).ready(() => {
|
|||||||
clone.querySelector(".book-title").textContent = book.title;
|
clone.querySelector(".book-title").textContent = book.title;
|
||||||
clone.querySelector(".book-authors").textContent =
|
clone.querySelector(".book-authors").textContent =
|
||||||
book.authors.map((a) => a.name).join(", ") || "Автор неизвестен";
|
book.authors.map((a) => a.name).join(", ") || "Автор неизвестен";
|
||||||
|
if (book.page_count && book.page_count > 0) {
|
||||||
|
const pageEl = clone.querySelector(".book-page-count");
|
||||||
|
pageEl.querySelector(".page-count-value").textContent = book.page_count;
|
||||||
|
pageEl.classList.remove("hidden");
|
||||||
|
}
|
||||||
clone.querySelector(".book-desc").textContent = book.description || "";
|
clone.querySelector(".book-desc").textContent = book.description || "";
|
||||||
|
|
||||||
const statusConfig = getStatusConfig(book.status);
|
const statusConfig = getStatusConfig(book.status);
|
||||||
|
|||||||
@@ -235,18 +235,25 @@ $(document).ready(() => {
|
|||||||
|
|
||||||
const title = $("#book-title").val().trim();
|
const title = $("#book-title").val().trim();
|
||||||
const description = $("#book-description").val().trim();
|
const description = $("#book-description").val().trim();
|
||||||
|
const pageCount = parseInt($("#book-page-count").val()) || null;
|
||||||
|
|
||||||
if (!title) {
|
if (!title) {
|
||||||
Utils.showToast("Введите название книги", "error");
|
Utils.showToast("Введите название книги", "error");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!parseInt(pageCount)) {
|
||||||
|
Utils.showToast("Введите количество страниц", "error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const bookPayload = {
|
const bookPayload = {
|
||||||
title: title,
|
title: title,
|
||||||
description: description || null,
|
description: description || null,
|
||||||
|
page_count: pageCount ? parseInt(pageCount) : null,
|
||||||
};
|
};
|
||||||
|
|
||||||
const createdBook = await Api.post("/api/books/", bookPayload);
|
const createdBook = await Api.post("/api/books/", bookPayload);
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ $(document).ready(() => {
|
|||||||
const $titleInput = $("#book-title");
|
const $titleInput = $("#book-title");
|
||||||
const $descInput = $("#book-description");
|
const $descInput = $("#book-description");
|
||||||
const $statusSelect = $("#book-status");
|
const $statusSelect = $("#book-status");
|
||||||
|
const $pagesInput = $("#book-page-count");
|
||||||
const $submitBtn = $("#submit-btn");
|
const $submitBtn = $("#submit-btn");
|
||||||
const $submitText = $("#submit-text");
|
const $submitText = $("#submit-text");
|
||||||
const $loadingSpinner = $("#loading-spinner");
|
const $loadingSpinner = $("#loading-spinner");
|
||||||
@@ -69,6 +70,7 @@ $(document).ready(() => {
|
|||||||
function populateForm(book) {
|
function populateForm(book) {
|
||||||
$titleInput.val(book.title);
|
$titleInput.val(book.title);
|
||||||
$descInput.val(book.description || "");
|
$descInput.val(book.description || "");
|
||||||
|
$pagesInput.val(book.page_count);
|
||||||
$statusSelect.val(book.status);
|
$statusSelect.val(book.status);
|
||||||
updateCounters();
|
updateCounters();
|
||||||
}
|
}
|
||||||
@@ -329,6 +331,7 @@ $(document).ready(() => {
|
|||||||
|
|
||||||
const title = $titleInput.val().trim();
|
const title = $titleInput.val().trim();
|
||||||
const description = $descInput.val().trim();
|
const description = $descInput.val().trim();
|
||||||
|
const pages = $pagesInput.val();
|
||||||
const status = $statusSelect.val();
|
const status = $statusSelect.val();
|
||||||
|
|
||||||
if (!title) {
|
if (!title) {
|
||||||
@@ -340,6 +343,7 @@ $(document).ready(() => {
|
|||||||
if (title !== originalBook.title) payload.title = title;
|
if (title !== originalBook.title) payload.title = title;
|
||||||
if (description !== (originalBook.description || ""))
|
if (description !== (originalBook.description || ""))
|
||||||
payload.description = description || null;
|
payload.description = description || null;
|
||||||
|
if (pageCount !== originalBook.page_count) payload.page_count = pages;
|
||||||
if (status !== originalBook.status) payload.status = status;
|
if (status !== originalBook.status) payload.status = status;
|
||||||
|
|
||||||
if (Object.keys(payload).length === 0) {
|
if (Object.keys(payload).length === 0) {
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ const StorageHelper = {
|
|||||||
|
|
||||||
const Utils = {
|
const Utils = {
|
||||||
escapeHtml: (text) => {
|
escapeHtml: (text) => {
|
||||||
if (!text) return "";
|
if (text === null || text === undefined) return "";
|
||||||
return text.replace(
|
return String(text).replace(
|
||||||
/[&<>"']/g,
|
/[&<>"']/g,
|
||||||
(m) =>
|
(m) =>
|
||||||
({
|
({
|
||||||
|
|||||||
@@ -107,6 +107,10 @@
|
|||||||
id="book-authors-text"
|
id="book-authors-text"
|
||||||
class="text-lg text-gray-600 font-medium mb-6"
|
class="text-lg text-gray-600 font-medium mb-6"
|
||||||
></p>
|
></p>
|
||||||
|
<p id="book-page-count-text" class="text-sm text-gray-500 mb-6 hidden">
|
||||||
|
<span class="font-medium">Количество страниц:</span>
|
||||||
|
<span id="book-page-count-value"></span>
|
||||||
|
</p>
|
||||||
<div class="prose prose-gray max-w-none mb-8">
|
<div class="prose prose-gray max-w-none mb-8">
|
||||||
<h3
|
<h3
|
||||||
class="text-sm font-bold text-gray-400 uppercase tracking-wider mb-2"
|
class="text-sm font-bold text-gray-400 uppercase tracking-wider mb-2"
|
||||||
|
|||||||
@@ -152,6 +152,10 @@
|
|||||||
<span class="font-medium">Авторы:</span>
|
<span class="font-medium">Авторы:</span>
|
||||||
<span class="book-authors"></span>
|
<span class="book-authors"></span>
|
||||||
</p>
|
</p>
|
||||||
|
<p class="book-page-count text-sm text-gray-600 mb-2 hidden">
|
||||||
|
<span class="font-medium">Страниц:</span>
|
||||||
|
<span class="page-count-value"></span>
|
||||||
|
</p>
|
||||||
<p
|
<p
|
||||||
class="book-desc text-gray-700 text-sm mb-2 line-clamp-3"
|
class="book-desc text-gray-700 text-sm mb-2 line-clamp-3"
|
||||||
></p>
|
></p>
|
||||||
|
|||||||
@@ -69,6 +69,22 @@
|
|||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<label
|
||||||
|
for="book-page-count"
|
||||||
|
class="block text-sm font-semibold text-gray-700 mb-2"
|
||||||
|
>
|
||||||
|
Количество страниц
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
id="book-page-count"
|
||||||
|
name="page_count"
|
||||||
|
min="1"
|
||||||
|
class="w-full border border-gray-300 rounded-lg px-4 py-3 focus:outline-none focus:ring-2 focus:ring-gray-400 transition"
|
||||||
|
placeholder="Укажите количество страниц"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
<div class="bg-white p-4 rounded-lg border border-gray-200">
|
<div class="bg-white p-4 rounded-lg border border-gray-200">
|
||||||
<h2 class="text-sm font-semibold text-gray-700 mb-3">
|
<h2 class="text-sm font-semibold text-gray-700 mb-3">
|
||||||
|
|||||||
@@ -78,6 +78,23 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label
|
||||||
|
for="book-page-count"
|
||||||
|
class="block text-sm font-semibold text-gray-700 mb-2"
|
||||||
|
>
|
||||||
|
Количество страниц
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
id="book-page-count"
|
||||||
|
name="page_count"
|
||||||
|
min="1"
|
||||||
|
class="w-full border border-gray-300 rounded-lg px-4 py-3 focus:outline-none focus:ring-2 focus:ring-gray-400 transition"
|
||||||
|
placeholder="Укажите количество страниц"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label
|
<label
|
||||||
for="book-status"
|
for="book-status"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
"""recovery codes and totp
|
"""Recovery codes and totp
|
||||||
|
|
||||||
Revision ID: a585fd97b88c
|
Revision ID: a585fd97b88c
|
||||||
Revises: a8e40ab24138
|
Revises: a8e40ab24138
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
"""role payroll
|
"""Role payroll
|
||||||
|
|
||||||
Revision ID: a8e40ab24138
|
Revision ID: a8e40ab24138
|
||||||
Revises: 02ed6e775351
|
Revises: 02ed6e775351
|
||||||
Create Date: 2025-12-20 13:44:13.807704
|
Create Date: 2025-12-20 13:44:13.807704
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import Sequence, Union
|
from typing import Sequence, Union
|
||||||
|
|
||||||
from alembic import op
|
from alembic import op
|
||||||
@@ -13,29 +14,49 @@ import sqlmodel
|
|||||||
from sqlalchemy.dialects import postgresql
|
from sqlalchemy.dialects import postgresql
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
# revision identifiers, used by Alembic.
|
||||||
revision: str = 'a8e40ab24138'
|
revision: str = "a8e40ab24138"
|
||||||
down_revision: Union[str, None] = '02ed6e775351'
|
down_revision: Union[str, None] = "02ed6e775351"
|
||||||
branch_labels: Union[str, Sequence[str], None] = None
|
branch_labels: Union[str, Sequence[str], None] = None
|
||||||
depends_on: Union[str, Sequence[str], None] = None
|
depends_on: Union[str, Sequence[str], None] = None
|
||||||
|
|
||||||
|
|
||||||
def upgrade() -> None:
|
def upgrade() -> None:
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
op.alter_column('book', 'status',
|
op.alter_column(
|
||||||
existing_type=postgresql.ENUM('active', 'borrowed', 'reserved', 'restoration', 'written_off', name='bookstatus'),
|
"book",
|
||||||
|
"status",
|
||||||
|
existing_type=postgresql.ENUM(
|
||||||
|
"active",
|
||||||
|
"borrowed",
|
||||||
|
"reserved",
|
||||||
|
"restoration",
|
||||||
|
"written_off",
|
||||||
|
name="bookstatus",
|
||||||
|
),
|
||||||
type_=sa.String(),
|
type_=sa.String(),
|
||||||
existing_nullable=False,
|
existing_nullable=False,
|
||||||
existing_server_default=sa.text("'active'::bookstatus"))
|
existing_server_default=sa.text("'active'::bookstatus"),
|
||||||
op.add_column('roles', sa.Column('payroll', sa.Integer(), nullable=False))
|
)
|
||||||
|
op.add_column("roles", sa.Column("payroll", sa.Integer(), nullable=False))
|
||||||
# ### end Alembic commands ###
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
def downgrade() -> None:
|
def downgrade() -> None:
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
op.drop_column('roles', 'payroll')
|
op.drop_column("roles", "payroll")
|
||||||
op.alter_column('book', 'status',
|
op.alter_column(
|
||||||
|
"book",
|
||||||
|
"status",
|
||||||
existing_type=sa.String(),
|
existing_type=sa.String(),
|
||||||
type_=postgresql.ENUM('active', 'borrowed', 'reserved', 'restoration', 'written_off', name='bookstatus'),
|
type_=postgresql.ENUM(
|
||||||
|
"active",
|
||||||
|
"borrowed",
|
||||||
|
"reserved",
|
||||||
|
"restoration",
|
||||||
|
"written_off",
|
||||||
|
name="bookstatus",
|
||||||
|
),
|
||||||
existing_nullable=False,
|
existing_nullable=False,
|
||||||
existing_server_default=sa.text("'active'::bookstatus"))
|
existing_server_default=sa.text("'active'::bookstatus"),
|
||||||
|
)
|
||||||
# ### end Alembic commands ###
|
# ### end Alembic commands ###
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
"""Book page_count
|
||||||
|
|
||||||
|
Revision ID: c5dfc16bdc66
|
||||||
|
Revises: a585fd97b88c
|
||||||
|
Create Date: 2026-01-23 00:09:14.192263
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
import sqlmodel
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision: str = "c5dfc16bdc66"
|
||||||
|
down_revision: Union[str, None] = "a585fd97b88c"
|
||||||
|
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! ###
|
||||||
|
op.add_column("book", sa.Column("page_count", sa.Integer(), nullable=False))
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_column("book", "page_count")
|
||||||
|
# ### end Alembic commands ###
|
||||||
Reference in New Issue
Block a user