Compare commits
4 Commits
b4b931633b
...
2d336cd1da
| Author | SHA1 | Date | |
|---|---|---|---|
| 2d336cd1da | |||
| 18d320422a | |||
| 2e62ab4fb8 | |||
| da41b4be01 |
@@ -182,7 +182,11 @@ def books_import() -> Response:
|
||||
isbn=isbn,
|
||||
authors=book_data.authors,
|
||||
publisher=book_data.publisher,
|
||||
first_published=book_data.publishedDate.year,
|
||||
first_published=(
|
||||
book_data.publishedDate.year
|
||||
if isinstance(book_data.publishedDate, date)
|
||||
else book_data.publishedDate
|
||||
),
|
||||
genres=book_data.categories,
|
||||
)
|
||||
db.session.add(book)
|
||||
@@ -393,7 +397,7 @@ class BookRequestSchema(BaseModel):
|
||||
genres: list[str] = []
|
||||
publisher: str = ""
|
||||
owner_id: Optional[int] = None
|
||||
bought: datetime = Field(default_factory=datetime.now)
|
||||
bought: date = Field(default_factory=datetime.today)
|
||||
location: str = "billy salon"
|
||||
loaned_to: str = ""
|
||||
loaned_from: str = ""
|
||||
@@ -475,8 +479,8 @@ def readings_new(id: int) -> str:
|
||||
|
||||
|
||||
class ReadingRequestSchema(BaseModel):
|
||||
start_date: datetime = Field(default_factory=datetime.now)
|
||||
end_date: Optional[datetime] = None
|
||||
start_date: date = Field(default_factory=datetime.today)
|
||||
end_date: Optional[date] = None
|
||||
finished: bool = False
|
||||
dropped: bool = False
|
||||
rating: Optional[int] = None
|
||||
@@ -521,7 +525,7 @@ def reading(book_id: int, reading_id: int) -> str:
|
||||
if reading_req.end_date is None and (
|
||||
reading_req.finished or reading_req.dropped
|
||||
):
|
||||
reading_req.end_date = datetime.now()
|
||||
reading_req.end_date = datetime.today()
|
||||
for key, value in reading_req.model_dump().items():
|
||||
setattr(reading, key, value)
|
||||
db.session.commit()
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
from datetime import date
|
||||
from datetime import date, datetime
|
||||
from typing import Any, Optional
|
||||
|
||||
import requests
|
||||
from pydantic import BaseModel
|
||||
from pydantic import BaseModel, field_validator
|
||||
|
||||
# {
|
||||
# "title": "Concilio de Sombras (Sombras de Magia 2)",
|
||||
@@ -50,23 +51,35 @@ from pydantic import BaseModel
|
||||
|
||||
class GoogleBook(BaseModel):
|
||||
title: str
|
||||
authors: list[str]
|
||||
publisher: str
|
||||
publishedDate: date
|
||||
description: str
|
||||
industryIdentifiers: list[dict[str, str]]
|
||||
pageCount: int
|
||||
printType: str
|
||||
categories: list[str]
|
||||
maturityRating: str
|
||||
allowAnonLogging: bool
|
||||
contentVersion: str
|
||||
panelizationSummary: dict[str, bool]
|
||||
imageLinks: dict[str, str]
|
||||
language: str
|
||||
previewLink: str
|
||||
infoLink: str
|
||||
canonicalVolumeLink: str
|
||||
authors: list[str] = []
|
||||
publisher: str = ""
|
||||
publishedDate: Optional[date | int] = None
|
||||
description: str = ""
|
||||
industryIdentifiers: list[dict[str, str]] = []
|
||||
pageCount: int = 0
|
||||
printType: str = ""
|
||||
categories: list[str] = []
|
||||
maturityRating: str = ""
|
||||
allowAnonLogging: bool = False
|
||||
contentVersion: str = ""
|
||||
panelizationSummary: dict[str, bool] = {}
|
||||
imageLinks: dict[str, str] = {}
|
||||
language: str = ""
|
||||
previewLink: str = ""
|
||||
infoLink: str = ""
|
||||
canonicalVolumeLink: str = ""
|
||||
|
||||
# Validate publishedDate when given in YYYY-MM format and convert to date
|
||||
@field_validator("publishedDate", mode="before")
|
||||
@classmethod
|
||||
def validate_published_date(cls, v: Any) -> Any:
|
||||
if isinstance(v, str):
|
||||
try:
|
||||
return datetime.strptime(v, "%Y-%m").date()
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
return v
|
||||
|
||||
|
||||
def fetch_google_book_data(isbn: str) -> GoogleBook:
|
||||
|
||||
@@ -29,7 +29,7 @@ class Book(db.Model): # type: ignore[name-defined]
|
||||
genres: Mapped[list[str]] = mapped_column(JSON, default=list)
|
||||
publisher: Mapped[str] = mapped_column(default="")
|
||||
owner_id: Mapped[Optional[int]] = mapped_column(ForeignKey("user.id"))
|
||||
bought: Mapped[datetime] = mapped_column(default=datetime.now)
|
||||
bought: Mapped[date] = mapped_column(default=datetime.today)
|
||||
location: Mapped[str] = mapped_column(default="billy salon")
|
||||
loaned_to: Mapped[str] = mapped_column(default="")
|
||||
loaned_from: Mapped[str] = mapped_column(default="")
|
||||
@@ -44,7 +44,7 @@ class Book(db.Model): # type: ignore[name-defined]
|
||||
|
||||
class Reading(db.Model): # type: ignore[name-defined]
|
||||
id: Mapped[int] = mapped_column(primary_key=True)
|
||||
start_date: Mapped[date] = mapped_column(default=datetime.now)
|
||||
start_date: Mapped[date] = mapped_column(default=datetime.today)
|
||||
end_date: Mapped[Optional[date]] = mapped_column(default=None)
|
||||
finished: Mapped[bool] = mapped_column(default=False)
|
||||
dropped: Mapped[bool] = mapped_column(default=False)
|
||||
@@ -58,7 +58,7 @@ class Reading(db.Model): # type: ignore[name-defined]
|
||||
|
||||
class Wishlist(db.Model): # type: ignore[name-defined]
|
||||
id: Mapped[int] = mapped_column(primary_key=True)
|
||||
wishlisted: Mapped[date] = mapped_column(default=datetime.now)
|
||||
wishlisted: Mapped[date] = mapped_column(default=datetime.today)
|
||||
user_id: Mapped[int] = mapped_column(ForeignKey("user.id"))
|
||||
book_id: Mapped[int] = mapped_column(ForeignKey("book.id"))
|
||||
user: Mapped["User"] = relationship(back_populates="wishes")
|
||||
|
||||
@@ -14,6 +14,29 @@
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="{{ url_for('static',
|
||||
filename='favicon-16x16.png') }}">
|
||||
<link rel="manifest" href="{{ url_for('static', filename='site.webmanifest') }}">
|
||||
|
||||
<script src="{{ url_for('static', filename='htmx.min.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='alpine.min.js') }}" defer></script>
|
||||
<script src="{{ url_for('static', filename='tom-select.complete.min.js') }}"></script>
|
||||
|
||||
<script>
|
||||
htmx.on('htmx:beforeHistorySave', function () {
|
||||
// find all TomSelect elements
|
||||
document.querySelectorAll('.tomselect')
|
||||
.forEach(elt => elt.tomselect ? elt.tomselect.destroy() : null) // and call destroy() on them
|
||||
});
|
||||
|
||||
document.addEventListener('htmx:beforeSwap', function (evt) {
|
||||
// alert on errors
|
||||
if (evt.detail.xhr.status >= 400) {
|
||||
error_dialog = document.querySelector('#error-alert');
|
||||
error_dialog.querySelector('.modal-title').textContent = 'Error ' + evt.detail.xhr.status;
|
||||
error_dialog.querySelector('.modal-body').innerHTML = evt.detail.xhr.response;
|
||||
error_dialog.showModal();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
</head>
|
||||
|
||||
<body hx-boost="true" hx-push-url="true" hx-target="body">
|
||||
@@ -58,31 +81,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
</main>
|
||||
|
||||
<script src="{{ url_for('static', filename='htmx.min.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='alpine.min.js') }}" defer></script>
|
||||
<script src="{{ url_for('static', filename='tom-select.complete.min.js') }}"></script>
|
||||
|
||||
<script>
|
||||
htmx.on('htmx:beforeHistorySave', function () {
|
||||
// find all TomSelect elements
|
||||
document.querySelectorAll('.tomselect')
|
||||
.forEach(elt => elt.tomselect ? elt.tomselect.destroy() : null) // and call destroy() on them
|
||||
});
|
||||
|
||||
document.addEventListener('htmx:beforeSwap', function (evt) {
|
||||
// alert on errors
|
||||
if (evt.detail.xhr.status >= 400) {
|
||||
error_dialog = document.querySelector('#error-alert');
|
||||
error_dialog.querySelector('.modal-title').textContent = 'Error ' + evt.detail.xhr.status;
|
||||
error_dialog.querySelector('.modal-body').innerHTML = evt.detail.xhr.response;
|
||||
error_dialog.showModal();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -8,8 +8,8 @@
|
||||
|
||||
<div>
|
||||
{% block form %}
|
||||
<form class="row row-cols-1 row-cols-xxl-2" hx-put="/books/{{ book.id }}" hx-trigger="change" hx-swap="none"
|
||||
method="post">
|
||||
<form class="row row-cols-1 row-cols-xxl-2" hx-put="/books/{{ book.id }}" hx-trigger="change" hx-push-url="false"
|
||||
hx-swap="none" method="post">
|
||||
{% from "error_feedback.html.j2" import validation_error %}
|
||||
{% macro simple_field(field, name, value, type) %}
|
||||
<label class="form-label" for="{{ field }}">{{ name }}:</label>
|
||||
@@ -94,9 +94,9 @@
|
||||
|
||||
<div class="pt-2">
|
||||
<input class="btn btn-primary" type="submit" hx-post="/books/{{ book.id }}" hx-target="body"
|
||||
hx-swap="innerHTML" value="Submit">
|
||||
hx-swap="innerHTML" hx-push-url="true" value="Submit">
|
||||
<button class="btn btn-danger" hx-delete="/books/{{ book.id }}" hx-target="body" hx-swap="innerHTML"
|
||||
hx-confirm="Are you sure?">Delete</button>
|
||||
hx-confirm="Are you sure?" hx-push-url="true">Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -35,8 +35,7 @@
|
||||
<div x-data="{ofilters: false, ocols: false}">
|
||||
<div class="btn-toolbar pb-2 justify-content-between">
|
||||
<div class="btn-group pe-2">
|
||||
<button class="btn btn-primary border" type="button" hx-get="/books/new"
|
||||
hx-target="#search-results">New</button>
|
||||
<button class="btn btn-primary border" type="button" hx-get="/books/new">New</button>
|
||||
<button class="btn btn-primary border" type="button"
|
||||
onclick="document.querySelector('#isbn-prompt').showModal()">Import</button>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user