add display for WorkType

This commit is contained in:
Thomas Peetz
2025-05-09 01:17:35 +02:00
parent d60e606663
commit 06a48a03ac
14 changed files with 230 additions and 13 deletions
+11
View File
@@ -110,6 +110,17 @@ class WorkType(Base, BaseMixin):
name = Column(String, nullable=False, unique=True) name = Column(String, nullable=False, unique=True)
comic_works = relationship("ComicWork") comic_works = relationship("ComicWork")
def get_artists(self) -> Dict[str, List[str]]:
works: Dict[str, List[str]] = {}
for work in self.comic_works:
comic = work.comic.title
artist = work.artist
if comic in works:
works[comic].append(artist)
else:
works[comic] = [artist]
return works
def __repr__(self): def __repr__(self):
return f'Worktype({self.id} {self.version} {self.name} {len(self.comic_works)})' return f'Worktype({self.id} {self.version} {self.name} {len(self.comic_works)})'
+11
View File
@@ -98,3 +98,14 @@ class MediaVideo(Base, BaseMixin):
title = Column(String) title = Column(String)
url = Column(String, unique=True) url = Column(String, unique=True)
should_download = Column(Boolean) should_download = Column(Boolean)
def __repr__(self):
return f'MediaFile({self.id} {self.title} {self.url})'
def __str__(self):
if self.title is None:
return f'{self.url}({self.id})'
else:
return f'{self.title}({self.id})'
+16 -1
View File
@@ -1,10 +1,13 @@
import uuid
from datetime import datetime
from typing import List, Type from typing import List, Type
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from src.db.models.comic import Artist, Comic, Issue from src.db.models.comic import Artist, Comic, Issue, WorkType
from src.schema.comics.artist import ArtistDetailResponse from src.schema.comics.artist import ArtistDetailResponse
from src.schema.comics.issue import IssueDetailsResponse from src.schema.comics.issue import IssueDetailsResponse
from src.webapps.comic.forms import AddWorktypeForm
def get_artist_details(artist: Artist) -> ArtistDetailResponse: def get_artist_details(artist: Artist) -> ArtistDetailResponse:
@@ -38,3 +41,15 @@ def get_issue_details(issue: Issue) -> IssueDetailsResponse:
volume_id=issue.volume_id volume_id=issue.volume_id
) )
return response return response
def create_new_worktype(work: AddWorktypeForm, 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)
print(worktype)
return worktype
+14 -2
View File
@@ -1,9 +1,21 @@
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
import uuid
from datetime import datetime
from src.db.models.media import MediaVideo from src.db.models.media import MediaVideo
from src.webapps.media.forms import AddLinkForm from src.webapps.media.forms import AddLinkForm
def create_new_video(video: AddLinkForm, db: Session) -> MediaVideo: def create_new_video(video: AddLinkForm, db: Session) -> MediaVideo:
print(video.url) print(video.url)
return MediaVideo() media_video = MediaVideo()
media_video.id = str(uuid.uuid4())
media_video.url = video.url
media_video.created_date = datetime.now()
media_video.last_modified_date = datetime.now()
media_video.review = True
media_video.should_download = True
db.add(media_video)
db.commit()
db.refresh(media_video)
print(media_video)
return media_video
+4
View File
@@ -0,0 +1,4 @@
from pydantic import BaseModel
class AddWorkType(BaseModel):
worktype: str
@@ -0,0 +1,28 @@
{% extends "shared/base.html" %}
{% block title %}
<title>Add a Video Link</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">Add a WorkType</h3>
<form method="POST">
<div class="mb-3">
<input type="text" required class="form-control" name="worktype" value="{{worktype}}" placeholder="WorkType here">
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</div>
</div>
{% endblock %}
@@ -0,0 +1,49 @@
{% extends "shared/base.html" %}
{% block title %}
<title>WorkType Detail</title>
{% endblock %}
{% block content %}
<div class="container">
<div class="row">
<table class="table table-striped table-hover">
<tbody>
<tr>
<th scope="row">WorkType Name</th>
<td colspan="2">{{worktype.name}}</td>
</tr>
<tr>
<th scope="row">Works</th>
<td colspan="2">
{% for comic in worktype.get_artists() %}
<p>
{{comic}}:
<ul>
{% for artist in worktype.get_artists()[comic] %}
<li><a href="/comic/artists/{{artist.id}}">{{artist.name}}</a></li>
{% endfor %}
</ul>
</p>
{% endfor %}
</td>
</tr>
<tr>
<th scope="row">Data Created</th>
<td colspan="2">{{worktype.created_date}}</td>
</tr>
<tr>
<th scope="row">Data Modified</th>
<td colspan="2">{{worktype.last_modified_date}}</td>
</tr>
<tr>
<th scope="row">Data Version</th>
<td colspan="2">{{worktype.version}}</td>
</tr>
</tbody>
</table>
</div>
</div>
{% endblock %}
@@ -0,0 +1,26 @@
{% extends "shared/base.html" %}
{% block title %}
<title>WorkTypes</title>
{% endblock %}
{% block content %}
{% with msg=msg %}
{% include "components/alerts.html" %}
{% endwith %}
<div class="container">
<table class="table table-hover">
<thead><tr>
<th scope="col">Name</th>
</tr></thead>
<tbody>
{% for worktype in worktypes %}
<tr>
<th scope="row"><a href="/comic/worktypes/{{worktype.id}}">{{worktype.name}}</a></th>
</tr>
{% endfor %}
</tbody>
</table>
<a href="/comic/add-worktype" class="btn btn-outline-primary btn-sm active" role="button" aria-pressed="true">Add WorkType</a>
</div>
{% endblock %}
@@ -1,8 +1,6 @@
<nav class="navbar navbar-expand-lg navbar-light bg-light px-5"> <nav class="navbar navbar-expand-lg navbar-light bg-light px-5">
<div class="container-fluid"> <div class="container-fluid">
<a class="navbar-brand" href="#"> <a class="navbar-brand" href="#">Kontor</a>
<img src="{{ url_for('static', path='images/logo.png') }}" alt="" width="30" height="24">
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"> <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span> <span class="navbar-toggler-icon"></span>
</button> </button>
@@ -18,7 +16,7 @@
<li><a class="dropdown-item" href="/comic/artists/">Artists</a></li> <li><a class="dropdown-item" href="/comic/artists/">Artists</a></li>
<li><a class="dropdown-item" href="/comic/publishers/">Publishers</a></li> <li><a class="dropdown-item" href="/comic/publishers/">Publishers</a></li>
<li><hr class="dropdown-divider"></li> <li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#">Something else here</a></li> <li><a class="dropdown-item" href="/comic/worktypes/">WorkTypes</a></li>
</ul> </ul>
</li> </li>
<li class="nav-item dropdown"> <li class="nav-item dropdown">
+1 -2
View File
@@ -1,5 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en-us"> <html lang="de-DE">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
@@ -21,6 +21,5 @@
{% block scripts %} {% block scripts %}
{% endblock %} {% endblock %}
</body> </body>
</html> </html>
+1
View File
@@ -1,6 +1,7 @@
from fastapi import APIRouter, Request from fastapi import APIRouter, Request
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
from src.core.config import settings
from src.webapps.admin import route_admin from src.webapps.admin import route_admin
from src.webapps.auth import route_login from src.webapps.auth import route_login
from src.webapps.comic import route_comics from src.webapps.comic import route_comics
+20
View File
@@ -0,0 +1,20 @@
from fastapi import Request
from typing import List, Optional
class AddWorktypeForm:
def __init__(self, request: Request):
self.request = request
self.errors: List = []
self.worktype: Optional[str] = None
async def load_data(self):
form = await self.request.form()
self.worktype = form.get("worktype")
def is_valid(self):
if not self.worktype or (len(self.worktype) == 0):
self.errors.append("WorkType cannot be empty")
if not self.errors:
return True
return False
+36 -2
View File
@@ -1,9 +1,13 @@
from fastapi import APIRouter, Request from fastapi import APIRouter, Request, responses, status
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
from src.apis.utils import SessionDep from src.apis.utils import SessionDep
from src.db.models.comic import Comic, Artist, Publisher, Issue from src.db.models.comic import Comic, Artist, Publisher, Issue, WorkType
from typing import AnyStr from typing import AnyStr
from src.db.repository.comic import create_new_worktype
from src.schema.comics.worktype import AddWorkType
from src.webapps.comic.forms import AddWorktypeForm
templates = Jinja2Templates(directory="src/templates") templates = Jinja2Templates(directory="src/templates")
router = APIRouter(include_in_schema=False, prefix="/comic") router = APIRouter(include_in_schema=False, prefix="/comic")
@@ -48,3 +52,33 @@ def get_issues(db: SessionDep, request: Request, msg: str = None):
def issue_details(issue_id: AnyStr, request: Request, db: SessionDep): def issue_details(issue_id: AnyStr, request: Request, db: SessionDep):
issue = db.get(Issue, issue_id) issue = db.get(Issue, issue_id)
return templates.TemplateResponse("comic/issue_detail.html", {"request": request, "issue": issue}) return templates.TemplateResponse("comic/issue_detail.html", {"request": request, "issue": issue})
@router.get("/worktypes")
def get_worktypes(db: SessionDep, request: Request, msg: str = None):
worktypes = db.query(WorkType).all()
return templates.TemplateResponse("comic/worktypes.html", {"request": request, "msg": msg, "worktypes": worktypes})
@router.get("/worktypes/{worktype_id}")
def worktype_detail(worktype_id: AnyStr, request: Request, db: SessionDep):
worktype = db.get(WorkType, worktype_id)
return templates.TemplateResponse("comic/worktype_detail.html", {"request": request, "worktype": worktype})
@router.get("/add-worktype")
def add_worktype(request: Request, db: SessionDep):
return templates.TemplateResponse("comic/add_worktype.html", {"request": request})
@router.post("/add-worktype")
async def post_worktype(request: Request, db: SessionDep):
form = AddWorktypeForm(request)
await form.load_data()
if form.is_valid():
try:
work = AddWorkType(**form.__dict__)
worktype = create_new_worktype(work=work, db=db)
return responses.RedirectResponse(f"/comic/worktypes/{worktype.id}", status_code=status.HTTP_302_FOUND)
except Exception as e:
print(e)
form.__dict__.get("errors").append("worktype already added")
return templates.TemplateResponse("comic/add_worktype.html", form.__dict__)
return templates.TemplateResponse("comic/add_worktype.html", form.__dict__)
+11 -2
View File
@@ -1,11 +1,14 @@
from typing import AnyStr from typing import AnyStr
from fastapi import APIRouter, Request, status, responses from fastapi import APIRouter, Request, status, responses
from fastapi.security.utils import get_authorization_scheme_param
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
from src.apis.utils import SessionDep from src.apis.utils import SessionDep
from src.db.models.media import MediaVideo from src.db.models.media import MediaVideo
from src.db.repository.media import create_new_video from src.db.repository.media import create_new_video
from src.apis.version1.admin import get_current_user_from_token
from src.db.models.admin import Profile
from src.schema.media.video import AddLink from src.schema.media.video import AddLink
from src.webapps.media.forms import AddLinkForm from src.webapps.media.forms import AddLinkForm
@@ -15,7 +18,14 @@ router = APIRouter(include_in_schema=False, prefix="/media")
@router.get("/videos") @router.get("/videos")
def get_mediavideos(db: SessionDep, request: Request, msg: str = None): def get_mediavideos(db: SessionDep, request: Request, msg: str = None):
mediavideos = db.query(MediaVideo).all() mediavideos = db.query(MediaVideo).all()
return templates.TemplateResponse("media/videos.html", {"request": request, "msg": msg, "mediavideos": mediavideos}) try:
token = request.cookies.get("access_token")
_, param = get_authorization_scheme_param(token) # scheme will hold "Bearer" and param will hold actual token value
current_user: Profile = get_current_user_from_token(token=param, db=db)
return templates.TemplateResponse("media/videos.html", {"request": request, "msg": msg, "user": current_user, "mediavideos": mediavideos})
except Exception as e:
print(e)
return templates.TemplateResponse("media/videos.html", {"request": request, "msg": msg, "user": None, "mediavideos": mediavideos})
@router.get("/videos/{video_id}") @router.get("/videos/{video_id}")
def video_details(video_id: AnyStr, request: Request, db: SessionDep): def video_details(video_id: AnyStr, request: Request, db: SessionDep):
@@ -26,7 +36,6 @@ def video_details(video_id: AnyStr, request: Request, db: SessionDep):
def add_video_link(request: Request, db: SessionDep): def add_video_link(request: Request, db: SessionDep):
return templates.TemplateResponse("media/add_video_link.html", {"request": request}) return templates.TemplateResponse("media/add_video_link.html", {"request": request})
@router.post("/add-link") @router.post("/add-link")
async def post_video_link(request: Request, db: SessionDep): async def post_video_link(request: Request, db: SessionDep):
form = AddLinkForm(request) form = AddLinkForm(request)