kontor-api: Add details pages for Comics, Artists and MediaFiles
This commit is contained in:
@@ -21,4 +21,5 @@ dependencies = [
|
||||
"python-dotenv>=1.1.0",
|
||||
"python-jose>=3.4.0",
|
||||
"python-multipart>=0.0.20",
|
||||
"natsort>=8.4.0",
|
||||
]
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from typing import Dict, List
|
||||
from natsort import natsorted
|
||||
from sqlalchemy import Column, ForeignKey, Integer, String
|
||||
from sqlalchemy.dialects.mysql import BIT
|
||||
from sqlalchemy.orm import relationship
|
||||
@@ -24,7 +26,7 @@ class Comic(Base, BaseMixin):
|
||||
publisher = relationship("Publisher", back_populates="comics")
|
||||
current_order = Column(BIT(1))
|
||||
completed = Column(BIT(1))
|
||||
issues = relationship("Issue")
|
||||
issues = relationship("Issue", order_by="Issue.issue_number")
|
||||
story_arcs = relationship("StoryArc")
|
||||
trade_paperbacks = relationship("TradePaperback")
|
||||
volumes = relationship("Volume")
|
||||
@@ -36,6 +38,21 @@ class Comic(Base, BaseMixin):
|
||||
def __str__(self):
|
||||
return f'{self.title}({self.id})'
|
||||
|
||||
def get_artists(self) -> Dict[str, List[str]]:
|
||||
works: Dict[str, List[str]] = {}
|
||||
for work in self.comic_works:
|
||||
work_type = work.work_type.name
|
||||
artist = work.artist
|
||||
if work_type in works:
|
||||
works[work_type].append(artist)
|
||||
else:
|
||||
works[work_type] = [artist]
|
||||
return works
|
||||
|
||||
def sorted_issues(self):
|
||||
sorted_issues = natsorted(self.issues, key=lambda x: getattr(x, 'issue_number'))
|
||||
return sorted_issues
|
||||
|
||||
|
||||
class Volume(Base, BaseMixin):
|
||||
__tablename__ = "volume"
|
||||
@@ -77,6 +94,17 @@ class Artist(Base, BaseMixin):
|
||||
name = Column(String(length=255), nullable=False)
|
||||
comic_works = relationship("ComicWork")
|
||||
|
||||
def get_comics(self) -> Dict[str, List[str]]:
|
||||
works: Dict[str, List[str]] = {}
|
||||
for work in self.comic_works:
|
||||
work_type = work.work_type.name
|
||||
comic = work.comic
|
||||
if work_type in works:
|
||||
works[work_type].append(comic)
|
||||
else:
|
||||
works[work_type] = [comic]
|
||||
return works
|
||||
|
||||
|
||||
class WorkType(Base, BaseMixin):
|
||||
__tablename__ = "worktype"
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 544 B |
Binary file not shown.
|
After Width: | Height: | Size: 634 B |
@@ -2,14 +2,14 @@
|
||||
|
||||
|
||||
{% block title %}
|
||||
<title>Comic Detail</title>
|
||||
<title>Artist Detail</title>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<h1 class="display-5">Comic Detail</h1>
|
||||
<h1 class="display-5">Artist Detail</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -23,9 +23,14 @@
|
||||
<tr>
|
||||
<th scope="row">Works</th>
|
||||
<td colspan="2">
|
||||
{% for work in artist.comic_works %}
|
||||
{% for work in artist.get_comics() %}
|
||||
<p>
|
||||
{{work.work_type.name}}: <a href="/comic/comics/{{work.comic.id}}">{{work.comic.title}}</a>
|
||||
{{work}}:
|
||||
<ul>
|
||||
{% for comic in artist.get_comics()[work] %}
|
||||
<li><a href="/comic/comics/{{comic.id}}">{{comic.title}}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</p>
|
||||
{% endfor %}
|
||||
</td>
|
||||
|
||||
@@ -6,12 +6,7 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<h1 class="display-5">Comic Detail</h1>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container">
|
||||
|
||||
<div class="row">
|
||||
<table class="table table-striped table-hover">
|
||||
@@ -26,17 +21,26 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Completed</th>
|
||||
<td colspan="2">{{comic.completed}}</td>
|
||||
<td colspan="2">
|
||||
{% with check=comic.completed %}
|
||||
{% include "components/check.html" %}
|
||||
{% endwith %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Works</th>
|
||||
<td colspan="2">
|
||||
{% for work in comic.comic_works %}
|
||||
{% for work in comic.get_artists() %}
|
||||
<p>
|
||||
{{work.work_type.name}}: <a href="/comic/artists/{{work.artist.id}}">{{work.artist.name}}</a>
|
||||
{{work}}:
|
||||
<ul>
|
||||
{% for artist in comic.get_artists()[work] %}
|
||||
<li><a href="/comic/artists/{{artist.id}}">{{artist.name}}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</p>
|
||||
{% endfor %}
|
||||
</td>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Data Created</th>
|
||||
@@ -46,6 +50,20 @@
|
||||
<th scope="row">Data Modified</th>
|
||||
<td colspan="2">{{comic.last_modified_date}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Data Version</th>
|
||||
<td colspan="2">{{comic.version}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Issues</th>
|
||||
<td colspan="2">
|
||||
<ul>
|
||||
{% for issue in comic.sorted_issues() %}
|
||||
<li><a href="comic/issues/{{issue.id}}">{{issue.issue_number}}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@@ -19,8 +19,7 @@
|
||||
<div class="col-lg-4 col-md-3 col-sm-10 mr-auto">
|
||||
{% with obj=comic %}
|
||||
{% include "components/comic_cards.html" %}
|
||||
{% endwith %}
|
||||
|
||||
{% endwith %}
|
||||
{% if loop.index %3 %}
|
||||
</div>
|
||||
{% else %}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
{% extends "shared/base.html" %}
|
||||
|
||||
|
||||
{% block title %}
|
||||
<title>Publisher Detail</title>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<h1 class="display-5">Publisher Detail</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<table class="table table-striped table-hover">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row">Publisher Name</th>
|
||||
<td colspan="2">{{publisher.name}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Comics</th>
|
||||
<td colspan="2">
|
||||
<ul>
|
||||
{% for comic in publisher.comics %}
|
||||
<li><a href="/comic/comics/{{comic.id}}">{{comic.title}}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Data Created</th>
|
||||
<td colspan="2">{{publisher.created_date}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Data Modified</th>
|
||||
<td colspan="2">{{publisher.last_modified_date}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,31 @@
|
||||
{% extends "shared/base.html" %}
|
||||
|
||||
{% block title %}
|
||||
<title>Publisher List</title>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% with msg=msg %}
|
||||
{% include "components/alerts.html" %}
|
||||
{% endwith %}
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<h1 class="display-5">Find Publishers..</h1>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
{% for publisher in publishers %}
|
||||
<div class="col-lg-4 col-md-3 col-sm-10 mr-auto">
|
||||
{% with obj=publisher %}
|
||||
{% include "components/publisher_cards.html" %}
|
||||
{% endwith %}
|
||||
{% if loop.index %3 %}
|
||||
</div>
|
||||
{% else %}
|
||||
</div></div><br><div class="row">
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,6 @@
|
||||
<div class="card shadow p-3 mb-2 bg-body rounded" style="width: 18rem;">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">{{obj.name}}</h5>
|
||||
<a href="/media/actors/{{obj.id}}" class="btn btn-primary">Read more</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,5 @@
|
||||
{% if check == 1 %}
|
||||
<img src="{{ url_for('static', path='images/tick.png') }}" alt="" width="24" height="24">
|
||||
{% else %}
|
||||
<img src="{{ url_for('static', path='images/cross.png') }}" alt="" width="24" height="24">
|
||||
{% endif %}
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">{{obj.title}}</h5>
|
||||
<p class="card-text">Publisher : {{obj.publisher.name}}</p>
|
||||
<p class="card-text">Completed : {{obj.completed}}</p>
|
||||
<p class="card-text">Completed : {% with check=obj.completed %}{% include "components/check.html" %}{% endwith %}</p>
|
||||
<a href="/comic/comics/{{obj.id}}" class="btn btn-primary">Read more</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
<ul class="dropdown-menu" aria-labelledby="navbarDropdown">
|
||||
<li><a class="dropdown-item" href="/comic/comics/">Comics</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><hr class="dropdown-divider"></li>
|
||||
<li><a class="dropdown-item" href="#">Something else here</a></li>
|
||||
</ul>
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
<div class="card shadow p-3 mb-2 bg-body rounded" style="width: 18rem;">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">{{obj.name}}</h5>
|
||||
<a href="/comic/publishers/{{obj.id}}" class="btn btn-primary">Read more</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,45 @@
|
||||
{% extends "shared/base.html" %}
|
||||
|
||||
|
||||
{% block title %}
|
||||
<title>Actor Detail</title>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<h1 class="display-5">Actor Detail</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<table class="table table-striped table-hover">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row">Actor Name</th>
|
||||
<td colspan="2">{{actor.name}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Works</th>
|
||||
<td colspan="2">
|
||||
<ul>
|
||||
{% for media_actor_files in actor.media_actor_files %}
|
||||
<li><a href="/media/files/{{media_actor_files.media_file.id}}">{{media_actor_files.media_file.title}}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Data Created</th>
|
||||
<td colspan="2">{{actor.created_date}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Data Modified</th>
|
||||
<td colspan="2">{{actor.last_modified_date}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,32 @@
|
||||
{% extends "shared/base.html" %}
|
||||
|
||||
{% block title %}
|
||||
<title>MediaFile Actors</title>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% with msg=msg %}
|
||||
{% include "components/alerts.html" %}
|
||||
{% endwith %}
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<h1 class="display-5">Actors..</h1>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
{% for actor in actors %}
|
||||
<div class="col-lg-4 col-md-3 col-sm-10 mr-auto">
|
||||
{% with obj=actor %}
|
||||
{% include "components/actor_cards.html" %}
|
||||
{% endwith %}
|
||||
|
||||
{% if loop.index %3 %}
|
||||
</div>
|
||||
{% else %}
|
||||
</div></div><br><div class="row">
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,65 @@
|
||||
{% extends "shared/base.html" %}
|
||||
|
||||
|
||||
{% block title %}
|
||||
<title>MediaFile Detail</title>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<h1 class="display-5">MediaFile Detail</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<table class="table table-striped table-hover">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row">MediaFile Title</th>
|
||||
<td colspan="2">{{mediafile.title}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">MediaFile URL</th>
|
||||
<td colspan="2">{{mediafile.url}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">MediaFile Cloudlink</th>
|
||||
<td colspan="2">{{mediafile.cloud_link}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">MediaFile Download?</th>
|
||||
<td colspan="2">{{mediafile.should_download}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">MediaFile Review?</th>
|
||||
<td colspan="2">{{mediafile.review}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Actors</th>
|
||||
<td colspan="2">
|
||||
<ul>
|
||||
{% for media_actor_files in mediafile.media_actor_files %}
|
||||
<li><a href="/media/actors/{{media_actor_files.media_actor.id}}">{{media_actor_files.media_actor.name}}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Data Created</th>
|
||||
<td colspan="2">{{mediafile.created_date}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Data Modified</th>
|
||||
<td colspan="2">{{mediafile.last_modified_date}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Data Version</th>
|
||||
<td colspan="2">{{mediafile.version}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -4,8 +4,7 @@ from fastapi import APIRouter, Request
|
||||
from fastapi.templating import Jinja2Templates
|
||||
|
||||
from src.apis.utils import SessionDep
|
||||
from src.db.models.comic import Comic, Artist
|
||||
from src.schema.comics.comic import get_comic_details
|
||||
from src.db.models.comic import Comic, Artist, Publisher
|
||||
|
||||
templates = Jinja2Templates(directory="src/templates")
|
||||
router = APIRouter(include_in_schema=False, prefix="/comic")
|
||||
@@ -29,3 +28,15 @@ def get_artists(db: SessionDep, request: Request, msg: str = None):
|
||||
def artist_detail(artist_id: UUID, request: Request, db: SessionDep):
|
||||
artist = db.get(Artist, artist_id)
|
||||
return templates.TemplateResponse("comic/artist_detail.html", {"request": request, "artist": artist})
|
||||
|
||||
@router.get("/publishers")
|
||||
def get_publishers(db: SessionDep, request: Request, msg: str = None):
|
||||
publishers = db.query(Publisher).all()
|
||||
return templates.TemplateResponse("comic/publishers.html", {"request": request, "publishers": publishers})
|
||||
|
||||
@router.get("/publishers/{publisher_id}")
|
||||
def publisher_details(publisher_id: UUID, request: Request, db: SessionDep, msg: str = None):
|
||||
publisher = db.get(Publisher, publisher_id)
|
||||
if publisher is None:
|
||||
msg = "Could not find Publisher"
|
||||
return templates.TemplateResponse("comic/publisher_detail.html", {"request": request, "msg": msg, "publisher": publisher})
|
||||
|
||||
@@ -27,6 +27,6 @@ def get_actors(db: SessionDep, request: Request, msg: str = None):
|
||||
|
||||
@router.get("/actors/{actor_id}")
|
||||
def artist_detail(actor_id: UUID, request: Request, db: SessionDep):
|
||||
mediaactor = db.get(MediaActor, actor_id)
|
||||
return templates.TemplateResponse("media/artist_detail.html", {"request": request, "mediaactor": mediaactor})
|
||||
actor = db.get(MediaActor, actor_id)
|
||||
return templates.TemplateResponse("media/actor_detail.html", {"request": request, "actor": actor})
|
||||
|
||||
|
||||
Generated
+11
@@ -284,6 +284,7 @@ dependencies = [
|
||||
{ name = "fastapi", extra = ["standard"] },
|
||||
{ name = "httpx" },
|
||||
{ name = "mariadb" },
|
||||
{ name = "natsort" },
|
||||
{ name = "pathlib" },
|
||||
{ name = "platformdirs" },
|
||||
{ name = "pytest" },
|
||||
@@ -302,6 +303,7 @@ requires-dist = [
|
||||
{ name = "fastapi", extras = ["standard"], specifier = ">=0.115.12" },
|
||||
{ name = "httpx", specifier = "==0.24.1" },
|
||||
{ name = "mariadb", specifier = ">=1.1.12" },
|
||||
{ name = "natsort", specifier = ">=8.4.0" },
|
||||
{ name = "pathlib", specifier = ">=1.0.1" },
|
||||
{ name = "platformdirs", specifier = ">=4.3.7" },
|
||||
{ name = "pytest", specifier = "==7.4.0" },
|
||||
@@ -376,6 +378,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload_time = "2022-08-14T12:40:09.779Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "natsort"
|
||||
version = "8.4.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e2/a9/a0c57aee75f77794adaf35322f8b6404cbd0f89ad45c87197a937764b7d0/natsort-8.4.0.tar.gz", hash = "sha256:45312c4a0e5507593da193dedd04abb1469253b601ecaf63445ad80f0a1ea581", size = 76575, upload_time = "2023-06-20T04:17:19.925Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ef/82/7a9d0550484a62c6da82858ee9419f3dd1ccc9aa1c26a1e43da3ecd20b0d/natsort-8.4.0-py3-none-any.whl", hash = "sha256:4732914fb471f56b5cce04d7bae6f164a592c7712e1c85f9ef585e197299521c", size = 38268, upload_time = "2023-06-20T04:17:17.522Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "packaging"
|
||||
version = "25.0"
|
||||
|
||||
Reference in New Issue
Block a user