Vorbereitung Release 0.2.0 #83

Merged
tpeetz merged 178 commits from develop/0.2.0 into main 2026-01-29 22:50:42 +00:00
14 changed files with 230 additions and 13 deletions
Showing only changes of commit 06a48a03ac - Show all commits
+11
View File
@@ -110,6 +110,17 @@ class WorkType(Base, BaseMixin):
name = Column(String, nullable=False, unique=True)
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):
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)
url = Column(String, unique=True)
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 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.issue import IssueDetailsResponse
from src.webapps.comic.forms import AddWorktypeForm
def get_artist_details(artist: Artist) -> ArtistDetailResponse:
@@ -38,3 +41,15 @@ def get_issue_details(issue: Issue) -> IssueDetailsResponse:
volume_id=issue.volume_id
)
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
import uuid
from datetime import datetime
from src.db.models.media import MediaVideo
from src.webapps.media.forms import AddLinkForm
def create_new_video(video: AddLinkForm, db: Session) -> MediaVideo:
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">
<div class="container-fluid">
<a class="navbar-brand" href="#">
<img src="{{ url_for('static', path='images/logo.png') }}" alt="" width="30" height="24">
</a>
<a class="navbar-brand" href="#">Kontor</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">
<span class="navbar-toggler-icon"></span>
</button>
@@ -18,7 +16,7 @@
<li><a class="dropdown-item" href="/comic/artists/">Artists</a></li>
<li><a class="dropdown-item" href="/comic/publishers/">Publishers</a></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>
</li>
<li class="nav-item dropdown">
+1 -2
View File
@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en-us">
<html lang="de-DE">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
@@ -21,6 +21,5 @@
{% block scripts %}
{% endblock %}
</body>
</html>
+1
View File
@@ -1,6 +1,7 @@
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
+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 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 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")
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):
issue = db.get(Issue, issue_id)
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 fastapi import APIRouter, Request, status, responses
from fastapi.security.utils import get_authorization_scheme_param
from fastapi.templating import Jinja2Templates
from src.apis.utils import SessionDep
from src.db.models.media import MediaVideo
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.webapps.media.forms import AddLinkForm
@@ -15,7 +18,14 @@ router = APIRouter(include_in_schema=False, prefix="/media")
@router.get("/videos")
def get_mediavideos(db: SessionDep, request: Request, msg: str = None):
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}")
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):
return templates.TemplateResponse("media/add_video_link.html", {"request": request})
@router.post("/add-link")
async def post_video_link(request: Request, db: SessionDep):
form = AddLinkForm(request)