WIP: add HTML form for editing comics

This commit is contained in:
Thomas Peetz
2025-06-09 23:38:55 +02:00
parent b610947403
commit d9136e45f6
16 changed files with 190 additions and 77 deletions
+2 -1
View File
@@ -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
+1
View File
@@ -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},
-66
View File
@@ -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
@@ -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
@@ -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}")
@@ -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
+10 -2
View File
@@ -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,
@@ -0,0 +1,44 @@
{% extends "shared/base.html" %}
{% block title %}
<title>Edit Comic</title>
{% endblock %}
{% block content %}
<div class="container">
<div class="row">
<div class="text-danger font-weight-bold">
{% for error in errors %}
<li>{{error}}</li>
{% endfor %}
</div>
</div>
<div class="row my-5">
<h3 class="text-center display-4">Edit an Comic entry</h3>
<form method="POST">
<div class="form-group">
<label for="title">Title</label>
<input type="text" class="form-control" id="title" name="title" value="{{comic_title}}" placeholder="Comic title here">
</div>
<div class="form-group">
<label for="weblink">Link</label>
<input type="text" class="form-control" id="weblink" name="weblink" value="{{comic_weblink}}" placeholder="Web link for comic here">
</div>
<div class="form-check">
<label class="form-check-label" for="completed">Completed</label>
<input type="checkbox" id="completed" class="form-check-input" name="completed" value="{{comic_completed}}" placeholder="Is comic series completed?">
</div>
<div class="form-check">
<label class="form-check-label" for="current_order">Completed</label>
<input type="checkbox" id="current_order" class="form-check-input" name="current_order" value="{{comic_completed}}" placeholder="Is comic series completed?">
</div>
<div>
<button type="submit" class="btn btn-primary">Submit</button>
<button type="cancel" class="btn btn-primary">Cancel</button>
</div>
</form>
</div>
</div>
{% endblock %}
-1
View File
@@ -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
@@ -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
@@ -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
+30 -2
View File
@@ -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()
@@ -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")