From d9136e45f6d2c255700659ee8995b16f1fbfe6fb Mon Sep 17 00:00:00 2001 From: Thomas Peetz Date: Mon, 9 Jun 2025 23:38:55 +0200 Subject: [PATCH] WIP: add HTML form for editing comics --- kontor-api/src/apis/version1/comic.py | 3 +- kontor-api/src/core/log_conf.py | 1 + kontor-api/src/db/repository/comic.py | 66 ------------------- .../src/db/repository/comics/__init__.py | 0 kontor-api/src/db/repository/comics/artist.py | 19 ++++++ kontor-api/src/db/repository/comics/comic.py | 29 ++++++++ .../src/db/repository/comics/worktype.py | 32 +++++++++ kontor-api/src/schema/comics/comic.py | 12 +++- .../src/templates/comic/comic_edit.html | 44 +++++++++++++ kontor-api/src/webapps/base.py | 1 - .../src/webapps/comic/forms/__init__.py | 0 kontor-api/src/webapps/comic/forms/comic.py | 18 +++++ .../comic/{forms.py => forms/worktype.py} | 0 kontor-api/src/webapps/comic/route_artists.py | 2 +- kontor-api/src/webapps/comic/route_comics.py | 32 ++++++++- .../src/webapps/comic/route_worktype.py | 8 +-- 16 files changed, 190 insertions(+), 77 deletions(-) delete mode 100644 kontor-api/src/db/repository/comic.py create mode 100644 kontor-api/src/db/repository/comics/__init__.py create mode 100644 kontor-api/src/db/repository/comics/artist.py create mode 100644 kontor-api/src/db/repository/comics/comic.py create mode 100644 kontor-api/src/db/repository/comics/worktype.py create mode 100644 kontor-api/src/templates/comic/comic_edit.html create mode 100644 kontor-api/src/webapps/comic/forms/__init__.py create mode 100644 kontor-api/src/webapps/comic/forms/comic.py rename kontor-api/src/webapps/comic/{forms.py => forms/worktype.py} (100%) diff --git a/kontor-api/src/apis/version1/comic.py b/kontor-api/src/apis/version1/comic.py index 8feef55..7bd0c59 100644 --- a/kontor-api/src/apis/version1/comic.py +++ b/kontor-api/src/apis/version1/comic.py @@ -2,7 +2,8 @@ from typing import List, AnyStr from fastapi import APIRouter, HTTPException, status from src.apis.utils import SessionDep -from src.db.repository.comic import get_artist_details, list_comics, get_issue_details +from src.db.repository.comics.artist import get_artist_details +from src.db.repository.comics.comic import list_comics, get_issue_details from src.schema.comics.comic import ComicResponse, ComicDetailsResponse, get_comic_details, get_short_info from src.schema.comics.artist import ArtistCreation, ArtistDetailResponse, ArtistResponse from src.db.models.comic import Comic, Artist, Issue diff --git a/kontor-api/src/core/log_conf.py b/kontor-api/src/core/log_conf.py index 68e2bbe..9a2335e 100644 --- a/kontor-api/src/core/log_conf.py +++ b/kontor-api/src/core/log_conf.py @@ -33,6 +33,7 @@ LOGGING_CONFIG: dict[str, Any] = { }, }, "loggers": { + "root": {"handlers": ["default"], "level": "INFO", "propagate": False}, "uvicorn": {"handlers": ["default"], "level": "INFO", "propagate": False}, "uvicorn.error": {"level": "INFO"}, "uvicorn.access": {"handlers": ["default"], "level": "INFO", "propagate": False}, diff --git a/kontor-api/src/db/repository/comic.py b/kontor-api/src/db/repository/comic.py deleted file mode 100644 index a20fccc..0000000 --- a/kontor-api/src/db/repository/comic.py +++ /dev/null @@ -1,66 +0,0 @@ -import uuid -from datetime import datetime -from typing import List, Type, AnyStr - -from sqlalchemy.orm import Session - -from src.core.log_conf import logger -from src.db.models.comic import Artist, Comic, Issue, WorkType -from src.schema.comics.artist import ArtistDetailResponse -from src.schema.comics.issue import IssueDetailsResponse -from src.schema.comics.worktype import AddWorkType - - -def get_artist_details(artist: Artist) -> ArtistDetailResponse: - works = {} - for work in artist.comic_works: - work_type = work.work_type.name - comic_title = work.comic.title - if work_type in works: - works[work_type].append(comic_title) - else: - works[work_type] = [comic_title] - response = ArtistDetailResponse( - id=artist.id, - name=artist.name, - works=works - ) - return response - -def list_comics(db: Session) -> List[Type[Comic]]: - comics = db.query(Comic).all() - return comics - - -def get_issue_details(issue: Issue) -> IssueDetailsResponse: - response = IssueDetailsResponse( - id=issue.id, - issue_number=issue.issue_number, - in_stock=issue.in_stock, - is_read=issue.is_read, - comic_id=issue.comic_id, - volume_id=issue.volume_id - ) - return response - -def create_new_worktype(work: AddWorkType, db: Session) -> WorkType: - worktype = WorkType() - worktype.id = str(uuid.uuid4()) - worktype.created_date = datetime.now() - worktype.last_modified_date = datetime.now() - worktype.name = work.worktype - db.add(worktype) - db.commit() - db.refresh(worktype) - logger.info(f"create_new_worktype: {worktype}") - return worktype - - -def update_worktype(work: AddWorkType, worktype_id: AnyStr, db: Session) -> WorkType: - logger.info("update worktype") - worktype = db.get(WorkType, worktype_id) - worktype.name = work.worktype - db.add(worktype) - db.commit() - db.refresh(worktype) - return worktype diff --git a/kontor-api/src/db/repository/comics/__init__.py b/kontor-api/src/db/repository/comics/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/kontor-api/src/db/repository/comics/artist.py b/kontor-api/src/db/repository/comics/artist.py new file mode 100644 index 0000000..8dda467 --- /dev/null +++ b/kontor-api/src/db/repository/comics/artist.py @@ -0,0 +1,19 @@ +from src.db.models.comic import Artist +from src.schema.comics.artist import ArtistDetailResponse + + +def get_artist_details(artist: Artist) -> ArtistDetailResponse: + works = {} + for work in artist.comic_works: + work_type = work.work_type.name + comic_title = work.comic.title + if work_type in works: + works[work_type].append(comic_title) + else: + works[work_type] = [comic_title] + response = ArtistDetailResponse( + id=artist.id, + name=artist.name, + works=works + ) + return response diff --git a/kontor-api/src/db/repository/comics/comic.py b/kontor-api/src/db/repository/comics/comic.py new file mode 100644 index 0000000..8f20431 --- /dev/null +++ b/kontor-api/src/db/repository/comics/comic.py @@ -0,0 +1,29 @@ +from typing import List, Type, AnyStr + +from sqlalchemy.orm import Session + +from src.core.log_conf import logger +from src.db.models.comic import Comic, Issue +from src.schema.comics.comic import ComicSchema +from src.schema.comics.issue import IssueDetailsResponse + + +def list_comics(db: Session) -> List[Type[Comic]]: + comics = db.query(Comic).all() + return comics + + +def get_issue_details(issue: Issue) -> IssueDetailsResponse: + response = IssueDetailsResponse( + id=issue.id, + issue_number=issue.issue_number, + in_stock=issue.in_stock, + is_read=issue.is_read, + comic_id=issue.comic_id, + volume_id=issue.volume_id + ) + return response + + +def update_comic(comic: ComicSchema, comic_id: AnyStr, db: Session): + logger.info(f"update_comic: {comic} with {comic_id}") diff --git a/kontor-api/src/db/repository/comics/worktype.py b/kontor-api/src/db/repository/comics/worktype.py new file mode 100644 index 0000000..cb545ad --- /dev/null +++ b/kontor-api/src/db/repository/comics/worktype.py @@ -0,0 +1,32 @@ +import uuid +from datetime import datetime +from typing import AnyStr + +from sqlalchemy.orm import Session + +from src.core.log_conf import logger +from src.db.models.comic import WorkType +from src.schema.comics.worktype import AddWorkType + + +def create_new_worktype(work: AddWorkType, db: Session) -> WorkType: + worktype = WorkType() + worktype.id = str(uuid.uuid4()) + worktype.created_date = datetime.now() + worktype.last_modified_date = datetime.now() + worktype.name = work.worktype + db.add(worktype) + db.commit() + db.refresh(worktype) + logger.info(f"create_new_worktype: {worktype}") + return worktype + + +def update_worktype(work: AddWorkType, worktype_id: AnyStr, db: Session) -> WorkType: + logger.info("update worktype") + worktype = db.get(WorkType, worktype_id) + worktype.name = work.worktype + db.add(worktype) + db.commit() + db.refresh(worktype) + return worktype diff --git a/kontor-api/src/schema/comics/comic.py b/kontor-api/src/schema/comics/comic.py index 168402a..1583417 100644 --- a/kontor-api/src/schema/comics/comic.py +++ b/kontor-api/src/schema/comics/comic.py @@ -1,6 +1,6 @@ -from typing import List, Dict +from typing import List, Dict, Optional -from pydantic import BaseModel +from pydantic import BaseModel, AnyUrl from src.db.models.comic import Comic @@ -21,6 +21,14 @@ class ComicDetailsResponse(BaseModel): volumes: List[str] works: Dict[str, List[str]] + +class ComicSchema(BaseModel): + title: str + weblink: Optional[AnyUrl] + completed: Optional[bool] + current_order: Optional[bool] + + def get_short_info(comic: Comic) -> ComicResponse: response = ComicResponse( id=comic.id, diff --git a/kontor-api/src/templates/comic/comic_edit.html b/kontor-api/src/templates/comic/comic_edit.html new file mode 100644 index 0000000..fb8edab --- /dev/null +++ b/kontor-api/src/templates/comic/comic_edit.html @@ -0,0 +1,44 @@ +{% extends "shared/base.html" %} + + +{% block title %} + Edit Comic +{% endblock %} + +{% block content %} +
+
+
+ {% for error in errors %} +
  • {{error}}
  • + {% endfor %} +
    +
    + +
    +

    Edit an Comic entry

    +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    +
    +
    +{% endblock %} diff --git a/kontor-api/src/webapps/base.py b/kontor-api/src/webapps/base.py index acb8015..384fe1a 100644 --- a/kontor-api/src/webapps/base.py +++ b/kontor-api/src/webapps/base.py @@ -1,7 +1,6 @@ from fastapi import APIRouter, Request from fastapi.templating import Jinja2Templates -from src.core.config import settings from src.webapps.admin import route_admin from src.webapps.auth import route_login from src.webapps.comic import route_comics, route_worktype, route_artists diff --git a/kontor-api/src/webapps/comic/forms/__init__.py b/kontor-api/src/webapps/comic/forms/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/kontor-api/src/webapps/comic/forms/comic.py b/kontor-api/src/webapps/comic/forms/comic.py new file mode 100644 index 0000000..54dd4a1 --- /dev/null +++ b/kontor-api/src/webapps/comic/forms/comic.py @@ -0,0 +1,18 @@ +from fastapi import Request +from typing import List, Optional + + +class ValidateComicForm: + def __init__(self, request: Request): + self.request = request + self.errors: List = [] + self.title: Optional[str] = None + + async def load_data(self): + form = await self.request.form() + self.title = form.get("title") + + def is_valid(self): + if not self.errors: + return True + return False diff --git a/kontor-api/src/webapps/comic/forms.py b/kontor-api/src/webapps/comic/forms/worktype.py similarity index 100% rename from kontor-api/src/webapps/comic/forms.py rename to kontor-api/src/webapps/comic/forms/worktype.py diff --git a/kontor-api/src/webapps/comic/route_artists.py b/kontor-api/src/webapps/comic/route_artists.py index 00cf0cd..cbe2bcc 100644 --- a/kontor-api/src/webapps/comic/route_artists.py +++ b/kontor-api/src/webapps/comic/route_artists.py @@ -1,4 +1,4 @@ -from fastapi import APIRouter, Request, responses, status +from fastapi import APIRouter, Request, status from fastapi.templating import Jinja2Templates from fastapi.responses import RedirectResponse diff --git a/kontor-api/src/webapps/comic/route_comics.py b/kontor-api/src/webapps/comic/route_comics.py index ceccc78..79c284b 100644 --- a/kontor-api/src/webapps/comic/route_comics.py +++ b/kontor-api/src/webapps/comic/route_comics.py @@ -1,9 +1,14 @@ -from fastapi import APIRouter, Request +from fastapi import APIRouter, Request, status from fastapi.templating import Jinja2Templates +from fastapi.responses import RedirectResponse from src.apis.utils import SessionDep -from src.db.models.comic import Comic, Artist, Publisher, Issue +from src.db.models.comic import Comic, Publisher, Issue from typing import AnyStr +from src.core.log_conf import logger +from src.db.repository.comics.comic import update_comic +from src.schema.comics.comic import ComicSchema +from src.webapps.comic.forms.comic import ValidateComicForm templates = Jinja2Templates(directory="src/templates") router = APIRouter(include_in_schema=False, prefix="/comic") @@ -35,6 +40,29 @@ def comic_details(comic_id: AnyStr, request: Request, db: SessionDep): comic = db.get(Comic, comic_id) return templates.TemplateResponse("comic/comic_detail.html", {"request": request, "comic":comic}) +@router.get("/comic/edit/{comic_id}") +def edit_comic(db: SessionDep, request: Request, comic_id: str): + comic = db.get(Comic, comic_id) + return templates.TemplateResponse("comic/comic_edit.html", {"request": request, "comic_title": comic.title, "comic_weblink": comic.weblink}) + + + +@router.post("/comic/edit/{comic_id}") +async def validate_comic(request: Request, db: SessionDep, comic_id: str): + form = ValidateComicForm(request) + await form.load_data() + logger.info(f"form: {form}") + if form.is_valid(): + try: + comic = ComicSchema(**form.__dict__) + comic = update_comic(comic=comic, comic_id=comic_id, db=db) + return RedirectResponse(f"/comic/comics/{comic.id}", status_code=status.HTTP_303_SEE_OTHER) + except Exception as e: + print(e) + form.__dict__.get("errors").append("comic already added") + return templates.TemplateResponse("comic/comic_edit.html", form.__dict__) + return templates.TemplateResponse("comic/comic_edit.html", form.__dict__) + @router.get("/publishers") def get_publishers(db: SessionDep, request: Request, msg: str | None = None): publishers = db.query(Publisher).all() diff --git a/kontor-api/src/webapps/comic/route_worktype.py b/kontor-api/src/webapps/comic/route_worktype.py index 59ddb4d..8e55f20 100644 --- a/kontor-api/src/webapps/comic/route_worktype.py +++ b/kontor-api/src/webapps/comic/route_worktype.py @@ -1,15 +1,15 @@ -from fastapi import APIRouter, Request, responses, status +from fastapi import APIRouter, Request, status from fastapi.templating import Jinja2Templates from fastapi.responses import RedirectResponse from src.apis.utils import SessionDep -from src.db.models.comic import Comic, Artist, Publisher, Issue, WorkType +from src.db.models.comic import WorkType from typing import AnyStr -from src.db.repository.comic import create_new_worktype, update_worktype +from src.db.repository.comics.worktype import create_new_worktype, update_worktype from src.main import logger from src.schema.comics.worktype import AddWorkType -from src.webapps.comic.forms import AddWorktypeForm +from src.webapps.comic.forms.worktype import AddWorktypeForm templates = Jinja2Templates(directory="src/templates") router = APIRouter(include_in_schema=False, prefix="/comic")