Vorbereitung Release 0.2.0 #83
@@ -1,2 +1,4 @@
|
||||
.env
|
||||
.coverage
|
||||
app.log
|
||||
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
import logging
|
||||
import logging.config
|
||||
from typing import Any
|
||||
|
||||
LOGGING_CONFIG: dict[str, Any] = {
|
||||
"version": 1,
|
||||
"disable_existing_loggers": False,
|
||||
"formatters": {
|
||||
"default": {
|
||||
"()": "uvicorn.logging.DefaultFormatter",
|
||||
"fmt": "%(asctime)s - %(name)s - %(levelprefix)s %(message)s",
|
||||
"use_colors": None,
|
||||
},
|
||||
"access": {
|
||||
"()": "uvicorn.logging.AccessFormatter",
|
||||
"fmt": '%(asctime)s - %(name)s - %(levelprefix)s %(client_addr)s - "%(request_line)s" %(status_code)s', # noqa: E501
|
||||
},
|
||||
"access_file": {
|
||||
"()": "uvicorn.logging.AccessFormatter",
|
||||
"fmt": '%(asctime)s - %(name)s - %(levelprefix)s %(client_addr)s - "%(request_line)s" %(status_code)s', # noqa: E501
|
||||
"use_colors": False,
|
||||
},
|
||||
},
|
||||
"handlers": {
|
||||
"file_handler": {
|
||||
"formatter": "access_file",
|
||||
"class": "logging.handlers.RotatingFileHandler",
|
||||
"filename" : "./app.log",
|
||||
"mode" : "a+",
|
||||
"maxBytes" : 10*1024*1024,
|
||||
"backupCount": 0,
|
||||
},
|
||||
|
||||
"default": {
|
||||
"formatter": "default",
|
||||
"class": "logging.StreamHandler",
|
||||
"stream": "ext://sys.stderr",
|
||||
},
|
||||
"access": {
|
||||
"formatter": "access",
|
||||
"class": "logging.StreamHandler",
|
||||
"stream": "ext://sys.stdout",
|
||||
},
|
||||
},
|
||||
"loggers": {
|
||||
"uvicorn": {"handlers": ["default"], "level": "INFO", "propagate": False},
|
||||
"uvicorn.error": {"level": "INFO"},
|
||||
"uvicorn.access": {"handlers": ["access", "file_handler"], "level": "INFO", "propagate": False},
|
||||
},
|
||||
}
|
||||
|
||||
logging.config.dictConfig(LOGGING_CONFIG)
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -10,7 +10,7 @@ class Base(DeclarativeBase):
|
||||
|
||||
|
||||
class BaseMixin:
|
||||
#id = Column(String(255), primary_key=True, default=uuid.uuid4)
|
||||
#id = Column(String, primary_key=True, default=uuid.uuid4)
|
||||
id: Mapped[str] = mapped_column(primary_key=True, default=uuid.uuid4)
|
||||
# created_date = Column(DateTime)
|
||||
created_date: Mapped[datetime] = mapped_column(default=func.now())
|
||||
|
||||
@@ -6,28 +6,28 @@ from src.db.models.base import Base, BaseMixin
|
||||
|
||||
class Article(Base, BaseMixin):
|
||||
__tablename__ = 'article'
|
||||
title = Column(String(length=255), unique=True)
|
||||
title = Column(String, unique=True)
|
||||
article_authors = relationship("ArticleAuthor")
|
||||
|
||||
|
||||
class Author(Base, BaseMixin):
|
||||
__tablename__ = 'author'
|
||||
first_name = Column(String(255))
|
||||
last_name = Column(String(255))
|
||||
first_name = Column(String)
|
||||
last_name = Column(String)
|
||||
article_authors = relationship("ArticleAuthor")
|
||||
book_authors = relationship("BookAuthor")
|
||||
|
||||
|
||||
class BookshelfPublisher(Base, BaseMixin):
|
||||
__tablename__ = 'bookshelf_publisher'
|
||||
name = Column(String(length=255), unique=True)
|
||||
name = Column(String, unique=True)
|
||||
books = relationship("Book")
|
||||
|
||||
|
||||
class Book(Base, BaseMixin):
|
||||
__tablename__ = 'book'
|
||||
isbn = Column(String(255), unique=True)
|
||||
title = Column(String(255))
|
||||
isbn = Column(String, unique=True)
|
||||
title = Column(String)
|
||||
year = Column(Integer, nullable=False)
|
||||
publisher_id = Column(String, ForeignKey('bookshelf_publisher.id'), nullable=False)
|
||||
publisher = relationship('BookshelfPublisher', back_populates="books")
|
||||
|
||||
@@ -1,14 +1,23 @@
|
||||
from typing import Dict, List
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from typing import Dict, List, Optional
|
||||
from natsort import natsorted
|
||||
from sqlalchemy import Column, ForeignKey, Integer, String, Boolean
|
||||
from sqlalchemy.orm import relationship
|
||||
from sqlalchemy import Column, ForeignKey, Integer, String, Boolean, func
|
||||
from sqlalchemy.orm import relationship, Mapped, mapped_column
|
||||
|
||||
from src.db.models.base import Base, BaseMixin
|
||||
|
||||
|
||||
class Publisher(Base, BaseMixin):
|
||||
class Publisher(Base):
|
||||
__tablename__ = "publisher"
|
||||
id: Mapped[str] = mapped_column(primary_key=True, default=uuid.uuid4)
|
||||
created_date: Mapped[datetime] = mapped_column(default=func.now())
|
||||
last_modified_date: Mapped[datetime] = mapped_column(default=func.now())
|
||||
version: Mapped[int] = mapped_column(default=0)
|
||||
name = Column(String, unique=True)
|
||||
parent_publisher_id: Mapped[Optional[str]] = mapped_column(ForeignKey('publisher.id'))
|
||||
parent_publisher: Mapped[Optional['Publisher']] = relationship("Publisher", back_populates="imprints", remote_side=[id])
|
||||
imprints: Mapped[List['Publisher']] = relationship('Publisher', back_populates="parent_publisher")
|
||||
comics = relationship("Comic")
|
||||
|
||||
def __repr__(self):
|
||||
@@ -25,6 +34,7 @@ class Comic(Base, BaseMixin):
|
||||
publisher = relationship("Publisher", back_populates="comics")
|
||||
current_order = Column(Boolean)
|
||||
completed = Column(Boolean)
|
||||
weblink = Column(String, nullable=True)
|
||||
issues = relationship("Issue", order_by="Issue.issue_number")
|
||||
story_arcs = relationship("StoryArc")
|
||||
trade_paperbacks = relationship("TradePaperback")
|
||||
@@ -91,6 +101,7 @@ class Issue(Base, BaseMixin):
|
||||
class Artist(Base, BaseMixin):
|
||||
__tablename__ = "artist"
|
||||
name = Column(String, nullable=False)
|
||||
weblink = Column(String, nullable=True)
|
||||
comic_works = relationship("ComicWork")
|
||||
|
||||
def get_comics(self) -> Dict[str, List[str]]:
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from typing import List, Type
|
||||
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.webapps.comic.forms import AddWorktypeForm
|
||||
from src.schema.comics.worktype import AddWorkType
|
||||
|
||||
|
||||
def get_artist_details(artist: Artist) -> ArtistDetailResponse:
|
||||
@@ -42,7 +43,7 @@ def get_issue_details(issue: Issue) -> IssueDetailsResponse:
|
||||
)
|
||||
return response
|
||||
|
||||
def create_new_worktype(work: AddWorktypeForm, db: Session) -> WorkType:
|
||||
def create_new_worktype(work: AddWorkType, db: Session) -> WorkType:
|
||||
worktype = WorkType()
|
||||
worktype.id = str(uuid.uuid4())
|
||||
worktype.created_date = datetime.now()
|
||||
@@ -51,5 +52,15 @@ def create_new_worktype(work: AddWorktypeForm, db: Session) -> WorkType:
|
||||
db.add(worktype)
|
||||
db.commit()
|
||||
db.refresh(worktype)
|
||||
print(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
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
import logging
|
||||
import logging.config
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
|
||||
from src.apis.base import api_router
|
||||
from src.core.log_conf import LOGGING_CONFIG, logger
|
||||
from src.db.session import engine
|
||||
from src.db.utils import check_db_connected, check_db_disconnected
|
||||
from src.webapps.base import api_router as web_app_router
|
||||
from src.core.config import settings
|
||||
from src.db.models.base import Base
|
||||
|
||||
logging.basicConfig(level=logging.INFO,
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
handlers=[logging.StreamHandler()]) # Logs to console
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
@@ -31,13 +30,12 @@ def configure_static(app: FastAPI):
|
||||
def create_tables():
|
||||
Base.metadata.create_all(bind=engine)
|
||||
|
||||
def start_application():
|
||||
logging.info(f"using database: {settings.DATABASE_URL}")
|
||||
def start_application(log):
|
||||
log.info(f"using database: {settings.DATABASE_URL}")
|
||||
app = FastAPI(title=settings.PROJECT_NAME, version=settings.PROJECT_VERSION, lifespan=lifespan)
|
||||
include_router(app)
|
||||
configure_static(app)
|
||||
create_tables()
|
||||
return app
|
||||
|
||||
|
||||
kontor = start_application()
|
||||
kontor = start_application(logger)
|
||||
|
||||
@@ -15,4 +15,5 @@ class ArtistResponse(BaseModel):
|
||||
class ArtistDetailResponse(BaseModel):
|
||||
id: str
|
||||
name: str
|
||||
weblink: str
|
||||
works: Dict[str, List[str]]
|
||||
|
||||
@@ -16,6 +16,7 @@ class ComicDetailsResponse(BaseModel):
|
||||
title: str
|
||||
completed : bool
|
||||
current_order : bool
|
||||
weblink: str
|
||||
publisher: str
|
||||
volumes: List[str]
|
||||
works: Dict[str, List[str]]
|
||||
@@ -47,9 +48,9 @@ def get_comic_details(comic: Comic) -> ComicDetailsResponse | None:
|
||||
title=comic.title,
|
||||
completed=comic.completed,
|
||||
current_order=comic.current_order,
|
||||
weblink=comic.weblink,
|
||||
publisher=comic.publisher.name,
|
||||
volumes=volumes,
|
||||
works=works
|
||||
)
|
||||
return response
|
||||
|
||||
|
||||
@@ -20,6 +20,10 @@
|
||||
<th scope="row">Artist Name</th>
|
||||
<td colspan="2">{{artist.name}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Link</th>
|
||||
<td colspan="2">{{artist.weblink}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Works</th>
|
||||
<td colspan="2">
|
||||
@@ -46,5 +50,9 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div><a href="/comic/artists" class="btn btn-outline-primary btn-sm active" role="button" aria-pressed="true">Back to list</a></div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
@@ -27,6 +27,10 @@
|
||||
{% endwith %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Link</th>
|
||||
<td colspan="2">{{comic.weblink}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Works</th>
|
||||
<td colspan="2">
|
||||
|
||||
@@ -20,6 +20,24 @@
|
||||
<th scope="row">Publisher Name</th>
|
||||
<td colspan="2">{{publisher.name}}</td>
|
||||
</tr>
|
||||
{% if publisher.parent_publisher_id %}
|
||||
<tr>
|
||||
<th scope="row">Parent Company</th>
|
||||
<td colspan="2"><a href="/comic/publishers/{{publisher.parent_publisher_id}}">{{publisher.parent_publisher.name}}</a></td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if publisher.imprints|length > 0 %}
|
||||
<tr>
|
||||
<th scope="row">Imprints</th>
|
||||
<td colspan="2">
|
||||
<ul>
|
||||
{% for imprint in publisher.imprints %}
|
||||
<li><a href="/comic/publishers/{{imprint.id}}">{{imprint.name}}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
<tr>
|
||||
<th scope="row">Comics</th>
|
||||
<td colspan="2">
|
||||
|
||||
@@ -30,6 +30,10 @@
|
||||
{% endfor %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">ID</th>
|
||||
<td colspan="2">{{worktype.id}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Data Created</th>
|
||||
<td colspan="2">{{worktype.created_date}}</td>
|
||||
@@ -45,5 +49,23 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div>
|
||||
<a href="/comic/worktypes" class="btn btn-outline-primary btn-sm active" role="button" aria-pressed="true">Back to list</a>
|
||||
<a href="/comic/worktype/edit/{{worktype.id}}" class="btn btn-outline-primary btn-sm active" role="button" aria-pressed="true">Edit</a>
|
||||
<a href="/comic/worktype/delete/{{worktype.id}}" class="btn btn-outline-danger btn-sm active" role="button" aria-pressed="true">Delete</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script type="text/javascript">
|
||||
function delete_job(id){
|
||||
fetch('/comics/worktypes/'+id+'/delete/'+id,{
|
||||
method:'DELETE',})
|
||||
.then(response => response.json())
|
||||
.then(document.getElementById('result').innerHTML = "Refreshing...")
|
||||
.then(data => document.getElementById('result').innerHTML = data.detail);
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -16,11 +16,13 @@
|
||||
<tbody>
|
||||
{% for worktype in worktypes %}
|
||||
<tr>
|
||||
<th scope="row"><a href="/comic/worktypes/{{worktype.id}}">{{worktype.name}}</a></th>
|
||||
<th scope="row"><a href="/comic/worktypes/{{worktype.id}}">{{worktype.name}}</a></th>
|
||||
<td><a href="/comic/worktype/edit/{{worktype.id}}" class="btn btn-outline-primary btn-sm active" role="button" aria-pressed="true">Edit</a></td>
|
||||
<td><a href="/comic/worktype/delete/{{worktype.id}}" class="btn btn-outline-danger btn-sm active" role="button" aria-pressed="true">Delete</a></td>
|
||||
</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>
|
||||
<a href="/comic/worktype/add" class="btn btn-outline-primary btn-sm active" role="button" aria-pressed="true">Add WorkType</a>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<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>
|
||||
<p class="card-text">Link : {{obj.weblink}}</p>
|
||||
<a href="/comic/artists/{{obj.id}}" class="btn btn-primary">Read more</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -16,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="/comic/worktypes/">WorkTypes</a></li>
|
||||
<li><a class="dropdown-item" href="/comic/worktypes">WorkTypes</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="nav-item dropdown">
|
||||
|
||||
@@ -16,13 +16,13 @@
|
||||
<th scope="col">Cloudlink</th>
|
||||
</tr></thead>
|
||||
<tbody>
|
||||
{% for mediavideo in mediavideos %}
|
||||
<tr>
|
||||
{% for mediavideo in mediavideos %}
|
||||
<tr>
|
||||
<th scope="row"><a href="/media/videos/{{mediavideo.id}}">{{mediavideo.title}}</a></th>
|
||||
<td>{{mediavideo.url}}</td>
|
||||
<td>{{mediavideo.cloud_link}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
from fastapi import APIRouter, Request, responses, 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 typing import AnyStr
|
||||
|
||||
from src.db.repository.comic import create_new_worktype
|
||||
from src.db.repository.comic 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
|
||||
|
||||
@@ -59,26 +62,54 @@ def get_worktypes(db: SessionDep, request: Request, msg: str = None):
|
||||
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):
|
||||
def worktype_detail(db: SessionDep, request: Request, worktype_id: AnyStr):
|
||||
worktype = db.get(WorkType, worktype_id)
|
||||
return templates.TemplateResponse("comic/worktype_detail.html", {"request": request, "worktype": worktype})
|
||||
|
||||
@router.get("/add-worktype")
|
||||
@router.get("/worktype/add")
|
||||
def add_worktype(request: Request, db: SessionDep):
|
||||
return templates.TemplateResponse("comic/add_worktype.html", {"request": request})
|
||||
return templates.TemplateResponse("comic/worktype_edit.html", {"request": request})
|
||||
|
||||
@router.post("/add-worktype")
|
||||
async def post_worktype(request: Request, db: SessionDep):
|
||||
@router.post("/worktype/add")
|
||||
async def add_worktype(db: SessionDep, request: Request):
|
||||
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)
|
||||
logger.info(f"add_worktype: redirect to /comic/worktypes/{worktype.id}")
|
||||
return RedirectResponse(f"/comic/worktypes/{worktype.id}", status_code=status.HTTP_303_SEE_OTHER)
|
||||
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__)
|
||||
return templates.TemplateResponse("comic/worktype_edit.html", form.__dict__)
|
||||
print("form is not valid")
|
||||
return templates.TemplateResponse("comic/worktype_edit.html", form.__dict__)
|
||||
|
||||
@router.get("/worktype/edit/{worktype_id}")
|
||||
def edit_worktype(db: SessionDep, request: Request, worktype_id: str):
|
||||
worktype = db.get(WorkType, worktype_id)
|
||||
return templates.TemplateResponse("comic/worktype_edit.html", {"request": request, "worktype": worktype.name})
|
||||
|
||||
@router.post("/worktype/edit/{worktype_id}")
|
||||
async def edit_worktype(request: Request, db: SessionDep, worktype_id: str):
|
||||
form = AddWorktypeForm(request)
|
||||
await form.load_data()
|
||||
if form.is_valid():
|
||||
try:
|
||||
work = AddWorkType(**form.__dict__)
|
||||
worktype = update_worktype(work=work, worktype_id=worktype_id, db=db)
|
||||
return RedirectResponse(f"/comic/worktypes/{worktype.id}", status_code=status.HTTP_303_SEE_OTHER)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
form.__dict__.get("errors").append("worktype already added")
|
||||
return templates.TemplateResponse("comic/worktype_edit.html", form.__dict__)
|
||||
return templates.TemplateResponse("comic/worktype_edit.html", form.__dict__)
|
||||
|
||||
@router.get("/worktype/delete/{worktype_id}")
|
||||
async def delete_worktype(db: SessionDep, request: Request, worktype_id: str):
|
||||
worktype = db.get(WorkType, worktype_id)
|
||||
db.delete(worktype)
|
||||
db.commit()
|
||||
return RedirectResponse("/comic/worktypes", status_code=status.HTTP_303_SEE_OTHER)
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
from db.models.admin import (
|
||||
Assignment,
|
||||
Token,
|
||||
Profile,
|
||||
Permission,
|
||||
MailAccount,
|
||||
ModuleData,
|
||||
Mail,
|
||||
)
|
||||
from db.models.bookshelf import (
|
||||
ArticleAuthor,
|
||||
BookAuthor,
|
||||
BookshelfPublisher,
|
||||
Article,
|
||||
Book,
|
||||
Author,
|
||||
)
|
||||
from db.models.comic import (
|
||||
Issue,
|
||||
StoryArc,
|
||||
TradePaperback,
|
||||
Volume,
|
||||
ComicWork,
|
||||
Artist,
|
||||
Comic,
|
||||
Publisher,
|
||||
WorkType,
|
||||
)
|
||||
from db.models.media import (
|
||||
MediaFile,
|
||||
MediaActor,
|
||||
MediaActorFile,
|
||||
MediaArticle,
|
||||
MediaVideo,
|
||||
)
|
||||
from db.models.metadata import MetaDataColumn, MetaDataTable
|
||||
from db.models.tysc import (
|
||||
Card,
|
||||
CardSet,
|
||||
Rooster,
|
||||
Team,
|
||||
FieldPosition,
|
||||
Player,
|
||||
Vendor,
|
||||
Sport,
|
||||
)
|
||||
|
||||
registry = {
|
||||
Card.__tablename__: Card,
|
||||
CardSet.__tablename__: CardSet,
|
||||
Rooster.__tablename__: Rooster,
|
||||
Team.__tablename__: Team,
|
||||
FieldPosition.__tablename__: FieldPosition,
|
||||
Player.__tablename__: Player,
|
||||
Vendor.__tablename__: Vendor,
|
||||
Sport.__tablename__: Sport,
|
||||
Issue.__tablename__: Issue,
|
||||
TradePaperback.__tablename__: TradePaperback,
|
||||
StoryArc.__tablename__: StoryArc,
|
||||
Volume.__tablename__: Volume,
|
||||
ComicWork.__tablename__: ComicWork,
|
||||
Artist.__tablename__: Artist,
|
||||
Comic.__tablename__: Comic,
|
||||
Publisher.__tablename__: Publisher,
|
||||
WorkType.__tablename__: WorkType,
|
||||
ArticleAuthor.__tablename__: ArticleAuthor,
|
||||
BookAuthor.__tablename__: BookAuthor,
|
||||
BookshelfPublisher.__tablename__: BookshelfPublisher,
|
||||
Article.__tablename__: Article,
|
||||
Book.__tablename__: Book,
|
||||
Author.__tablename__: Author,
|
||||
MediaFile.__tablename__: MediaFile,
|
||||
MediaActor.__tablename__: MediaActor,
|
||||
MediaActorFile.__tablename__: MediaActorFile,
|
||||
MediaArticle.__tablename__: MediaArticle,
|
||||
MediaVideo.__tablename__: MediaVideo,
|
||||
MetaDataColumn.__tablename__: MetaDataColumn,
|
||||
MetaDataTable.__tablename__: MetaDataTable,
|
||||
Assignment.__tablename__: Assignment,
|
||||
Token.__tablename__: Token,
|
||||
Profile.__tablename__: Profile,
|
||||
Permission.__tablename__: Permission,
|
||||
ModuleData.__tablename__: ModuleData,
|
||||
MailAccount.__tablename__: MailAccount,
|
||||
Mail.__tablename__: Mail
|
||||
}
|
||||
@@ -3,16 +3,16 @@ from datetime import datetime
|
||||
from sqlalchemy import Column, ForeignKey, Integer, String, Boolean
|
||||
from sqlalchemy.orm import relationship, mapped_column, Mapped
|
||||
|
||||
from .base import Base, BaseMixin
|
||||
from db.models.base import Base, BaseMixin
|
||||
|
||||
|
||||
class Profile(Base, BaseMixin):
|
||||
__tablename__ = 'profile'
|
||||
first_name = Column(String(255))
|
||||
last_name = Column(String(255))
|
||||
user_name = Column(String(255), nullable=False)
|
||||
email = Column(String(255))
|
||||
password = Column(String(255))
|
||||
first_name = Column(String)
|
||||
last_name = Column(String)
|
||||
user_name = Column(String, nullable=False)
|
||||
email = Column(String)
|
||||
password = Column(String)
|
||||
enabled = Column(Boolean)
|
||||
assignments = relationship("Assignment")
|
||||
tokens = relationship("Token")
|
||||
@@ -30,11 +30,11 @@ class Profile(Base, BaseMixin):
|
||||
|
||||
class Token(Base, BaseMixin):
|
||||
__tablename__ = "token"
|
||||
token = Column(String(255), nullable=False, unique=True)
|
||||
name = Column(String(255))
|
||||
token = Column(String, nullable=False, unique=True)
|
||||
name = Column(String)
|
||||
last_used_date: Mapped[datetime] = mapped_column()
|
||||
enabled = Column(Boolean)
|
||||
profile_id = Column(String(255), ForeignKey("profile.id"), nullable=False)
|
||||
profile_id = Column(String, ForeignKey("profile.id"), nullable=False)
|
||||
profile = relationship("Profile", back_populates="tokens")
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from sqlalchemy import Column, ForeignKey, Integer, String
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from .base import Base, BaseMixin
|
||||
from db.models.base import Base, BaseMixin
|
||||
|
||||
|
||||
class Article(Base, BaseMixin):
|
||||
@@ -1,12 +1,22 @@
|
||||
from sqlalchemy import Column, ForeignKey, Integer, String, Boolean
|
||||
from sqlalchemy.orm import relationship
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from typing import List, Optional
|
||||
from sqlalchemy import Column, ForeignKey, Integer, String, Boolean, func
|
||||
from sqlalchemy.orm import relationship, Mapped, mapped_column
|
||||
|
||||
from .base import Base, BaseMixin
|
||||
from db.models.base import Base, BaseMixin
|
||||
|
||||
|
||||
class Publisher(Base, BaseMixin):
|
||||
class Publisher(Base):
|
||||
__tablename__ = "publisher"
|
||||
id: Mapped[str] = mapped_column(primary_key=True, default=uuid.uuid4)
|
||||
created_date: Mapped[datetime] = mapped_column(default=func.now())
|
||||
last_modified_date: Mapped[datetime] = mapped_column(default=func.now())
|
||||
version: Mapped[int] = mapped_column(default=0)
|
||||
name = Column(String, unique=True)
|
||||
parent_publisher_id: Mapped[Optional[str]] = mapped_column(ForeignKey('publisher.id'))
|
||||
parent_publisher: Mapped[Optional['Publisher']] = relationship("Publisher", back_populates="imprints", remote_side=[id])
|
||||
imprints: Mapped[List['Publisher']] = relationship('Publisher', back_populates="parent_publisher")
|
||||
comics = relationship("Comic")
|
||||
|
||||
def __repr__(self):
|
||||
@@ -23,6 +33,7 @@ class Comic(Base, BaseMixin):
|
||||
publisher = relationship("Publisher", back_populates="comics")
|
||||
current_order = Column(Boolean)
|
||||
completed = Column(Boolean)
|
||||
weblink = Column(String, nullable=True)
|
||||
issues = relationship("Issue")
|
||||
story_arcs = relationship("StoryArc")
|
||||
trade_paperbacks = relationship("TradePaperback")
|
||||
@@ -74,6 +85,7 @@ class Issue(Base, BaseMixin):
|
||||
class Artist(Base, BaseMixin):
|
||||
__tablename__ = "artist"
|
||||
name = Column(String, nullable=False)
|
||||
weblink = Column(String, nullable=True)
|
||||
comic_works = relationship("ComicWork")
|
||||
|
||||
|
||||
@@ -97,3 +109,4 @@ class ComicWork(Base, BaseMixin):
|
||||
artist = relationship("Artist", back_populates="comic_works")
|
||||
work_type_id = Column(String, ForeignKey("worktype.id"), nullable=False)
|
||||
work_type = relationship("WorkType", back_populates="comic_works")
|
||||
|
||||
@@ -6,16 +6,17 @@ from logging import Logger
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from sqlalchemy import UUID, select
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
|
||||
from .tysc import Card, CardSet, Rooster, Team, FieldPosition, Player, Vendor, Sport
|
||||
from .comic import Issue, TradePaperback, StoryArc, Volume, ComicWork, Artist, Comic, Publisher, WorkType
|
||||
from .bookshelf import ArticleAuthor, BookAuthor, BookshelfPublisher, Article, Book, Author
|
||||
from .admin import Mail, MailAccount, ModuleData, Permission, Profile, Token, Assignment
|
||||
from .metadata import MetaDataTable, MetaDataColumn
|
||||
from .media import MediaVideo, MediaArticle, MediaFile, MediaActor, MediaActorFile
|
||||
from db.models import registry
|
||||
from db.models.tysc import Card, CardSet, Rooster, Team, FieldPosition, Player, Vendor, Sport
|
||||
from db.models.comic import Issue, TradePaperback, StoryArc, Volume, ComicWork, Artist, Comic, Publisher, WorkType
|
||||
from db.models.bookshelf import ArticleAuthor, BookAuthor, BookshelfPublisher, Article, Book, Author
|
||||
from db.models.admin import Mail, MailAccount, ModuleData, Permission, Profile, Token, Assignment
|
||||
from db.models.metadata import MetaDataTable, MetaDataColumn
|
||||
from db.models.media import MediaVideo, MediaArticle, MediaFile, MediaActor, MediaActorFile
|
||||
|
||||
|
||||
class ColumnEntry(Enum):
|
||||
@@ -46,57 +47,8 @@ class KontorDB:
|
||||
|
||||
def __init__(self, db_engine: Any, log: Logger):
|
||||
self.engine = db_engine
|
||||
self.registry = {}
|
||||
self.init_registry()
|
||||
self.log = log
|
||||
|
||||
def init_registry(self):
|
||||
self.registry[Card.__tablename__] = Card
|
||||
self.registry[CardSet.__tablename__] = CardSet
|
||||
self.registry[Rooster.__tablename__] = Rooster
|
||||
self.registry[Team.__tablename__] = Team
|
||||
self.registry[FieldPosition.__tablename__] = FieldPosition
|
||||
self.registry[Player.__tablename__] = Player
|
||||
self.registry[Vendor.__tablename__] = Vendor
|
||||
self.registry[Sport.__tablename__] = Sport
|
||||
self.registry[Issue.__tablename__] = Issue
|
||||
self.registry[TradePaperback.__tablename__] = TradePaperback
|
||||
self.registry[StoryArc.__tablename__] = StoryArc
|
||||
self.registry[Volume.__tablename__] = Volume
|
||||
self.registry[ComicWork.__tablename__] = ComicWork
|
||||
self.registry[Artist.__tablename__] = Artist
|
||||
self.registry[Comic.__tablename__] = Comic
|
||||
self.registry[Publisher.__tablename__] = Publisher
|
||||
self.registry[WorkType.__tablename__] = WorkType
|
||||
self.registry[ArticleAuthor.__tablename__] = ArticleAuthor
|
||||
self.registry[BookAuthor.__tablename__] = BookAuthor
|
||||
self.registry[BookshelfPublisher.__tablename__] = BookshelfPublisher
|
||||
self.registry[Article.__tablename__] = Article
|
||||
self.registry[Book.__tablename__] = Book
|
||||
self.registry[Author.__tablename__] = Author
|
||||
self.registry[MediaFile.__tablename__] = MediaFile
|
||||
self.registry[MediaActor.__tablename__] = MediaActor
|
||||
self.registry[MediaActorFile.__tablename__] = MediaActorFile
|
||||
self.registry[MediaArticle.__tablename__] = MediaArticle
|
||||
self.registry[MediaVideo.__tablename__] = MediaVideo
|
||||
self.registry[MetaDataColumn.__tablename__] = MetaDataColumn
|
||||
self.registry[MetaDataTable.__tablename__] = MetaDataTable
|
||||
self.registry[Assignment.__tablename__] = Assignment
|
||||
self.registry[Token.__tablename__] = Token
|
||||
self.registry[Profile.__tablename__] = Profile
|
||||
self.registry[Permission.__tablename__] = Permission
|
||||
self.registry[ModuleData.__tablename__] = ModuleData
|
||||
self.registry[MailAccount.__tablename__] = MailAccount
|
||||
self.registry[Mail.__tablename__] = Mail
|
||||
|
||||
def get_table_names(self) -> list:
|
||||
result = []
|
||||
__session__ = sessionmaker(self.engine)
|
||||
with __session__() as session:
|
||||
tables = session.scalars(select(MetaDataTable)).all()
|
||||
result = [table.table_name for table in tables]
|
||||
return result
|
||||
|
||||
def get_table_by_name(self, table_name: str) -> dict:
|
||||
result = {}
|
||||
__session__ = sessionmaker(self.engine)
|
||||
@@ -130,19 +82,6 @@ class KontorDB:
|
||||
order += 1
|
||||
return meta_data
|
||||
|
||||
def get_columns(self, table_name: str) -> dict:
|
||||
columns = {}
|
||||
__session__ = sessionmaker(self.engine)
|
||||
table_info = self.get_table_by_name(table_name)
|
||||
_filters = {'table_id': table_info['id']}
|
||||
with __session__() as session:
|
||||
for column in session.query(MetaDataColumn).filter_by(**_filters).all():
|
||||
columns[column.column_name] = {
|
||||
ColumnEntry.COLUMN_ORDER: column.column_order,
|
||||
ColumnEntry.COLUMN_TYPE: column.column_type
|
||||
}
|
||||
return columns
|
||||
|
||||
def get_filters(self, table_name: str) -> dict:
|
||||
_filter_map = {}
|
||||
__session__ = sessionmaker(self.engine)
|
||||
@@ -159,7 +98,7 @@ class KontorDB:
|
||||
def data(self, table_name: str, columns: dict, filters: dict) -> list:
|
||||
data = []
|
||||
__session__ = sessionmaker(self.engine)
|
||||
table = self.registry[table_name]
|
||||
table = registry[table_name]
|
||||
with __session__() as session:
|
||||
entries = []
|
||||
if len(filters) == 0:
|
||||
@@ -363,7 +302,7 @@ class KontorDB:
|
||||
update_list[link.id] = link.title
|
||||
return update_list
|
||||
|
||||
def get_download_list(self) -> list[UUID]:
|
||||
def get_download_list(self) -> list[str]:
|
||||
download_list = []
|
||||
__session__ = sessionmaker(self.engine)
|
||||
_filter = {'should_download': True}
|
||||
@@ -8,7 +8,7 @@ from bs4 import BeautifulSoup
|
||||
from sqlalchemy import Boolean, Column, False_, String, ForeignKey
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from .base import Base, BaseMixin, BaseVideoMixin
|
||||
from db.models.base import Base, BaseMixin, BaseVideoMixin
|
||||
|
||||
|
||||
class MediaFile(Base, BaseMixin, BaseVideoMixin):
|
||||
@@ -1,7 +1,7 @@
|
||||
from sqlalchemy import Column, String, ForeignKey, Integer, Boolean
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from .base import Base, BaseMixin
|
||||
from db.models.base import Base, BaseMixin
|
||||
|
||||
|
||||
class MetaDataTable(Base, BaseMixin):
|
||||
@@ -1,7 +1,7 @@
|
||||
from sqlalchemy import Column, Integer, String, ForeignKey, UniqueConstraint, Boolean
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from .base import Base, BaseMixin
|
||||
from db.models.base import Base, BaseMixin
|
||||
|
||||
|
||||
class Sport(Base, BaseMixin):
|
||||
@@ -0,0 +1,28 @@
|
||||
|
||||
from typing import List
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from db.models.metadata import MetaDataColumn, MetaDataTable
|
||||
from db.schemas.metadata import MetaDataTableResponse, MetaDataColumnResponse
|
||||
|
||||
|
||||
def get_tables(db: Session) -> List[MetaDataTableResponse]:
|
||||
tables = db.query(MetaDataTable).all()
|
||||
results: List[MetaDataTableResponse] = [MetaDataTableResponse(id=table.id, name=table.table_name) for table in tables]
|
||||
return results
|
||||
|
||||
|
||||
def get_columns_for_table(db: Session, table: MetaDataTableResponse)-> List[MetaDataColumnResponse]:
|
||||
columns = db.query(MetaDataColumn).filter_by(table_id = table.id).all()
|
||||
results: List[MetaDataColumnResponse] = []
|
||||
for column in columns:
|
||||
result: MetaDataColumnResponse = MetaDataColumnResponse(
|
||||
id=str(column.id),
|
||||
name=column.column_name,
|
||||
label=column.column_label,
|
||||
order=column.column_order,
|
||||
ref_column=column.ref_column,
|
||||
column_type=column.column_type)
|
||||
results.append(result)
|
||||
return results
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
from pydantic import BaseModel, PositiveInt
|
||||
|
||||
|
||||
class MetaDataTableResponse(BaseModel):
|
||||
id: str
|
||||
name: str
|
||||
|
||||
|
||||
class MetaDataColumnResponse(BaseModel):
|
||||
id: str
|
||||
name: str
|
||||
label: str
|
||||
order: PositiveInt
|
||||
ref_column: str | None
|
||||
column_type: str
|
||||
@@ -80,7 +80,7 @@ def is_file_downloaded(media_file: dict, dir: Path) -> FileStatus:
|
||||
|
||||
|
||||
def update_status(item_id: UUID, file_info: dict):
|
||||
update = requests.put(f"http://127.0.0.1:8800/media/files/{item_id}", json=file_info)
|
||||
update = requests.put(f"http://127.0.0.1:8800/api/media/files/{item_id}", json=file_info)
|
||||
log.info(f"update status: {update.status_code}")
|
||||
log.info(f"update result: {update.json()}")
|
||||
|
||||
@@ -97,7 +97,7 @@ def rename_file(file_info: dict):
|
||||
if __name__ == '__main__':
|
||||
log = get_logger(args.verbose, args.config)
|
||||
log.info('kontor.download started')
|
||||
response = requests.get("http://127.0.0.1:8800/media/files?download=true")
|
||||
response = requests.get("http://127.0.0.1:8800/api/media/files?download=true")
|
||||
log.info(f"Status: {response.status_code}")
|
||||
data = response.json()
|
||||
log.info(f"data: {len(data)}")
|
||||
|
||||
@@ -2,17 +2,17 @@
|
||||
import data from json file to MariaDB
|
||||
"""
|
||||
from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter
|
||||
|
||||
from datetime import datetime
|
||||
import json
|
||||
import yaml
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from platformdirs import PlatformDirs
|
||||
from pathlib import Path
|
||||
|
||||
from schema.base import Base
|
||||
from schema.database import KontorDB
|
||||
from db.models import registry
|
||||
from db.models.base import Base
|
||||
from config import get_logger
|
||||
from schema.database import ExportType
|
||||
from db.repository.metadata import get_tables, get_columns_for_table
|
||||
|
||||
parser = ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter)
|
||||
parser.add_argument('--verbose', '-v', action='count', default=0)
|
||||
@@ -38,6 +38,34 @@ if __name__ == '__main__':
|
||||
engine = create_engine(connect_string)
|
||||
Base.metadata.create_all(bind=engine, checkfirst=True)
|
||||
__session__ = sessionmaker(bind=engine)
|
||||
kontor_db = KontorDB(engine, logger)
|
||||
kontor_db.export_db(ExportType.JSON, args.file)
|
||||
with __session__() as db:
|
||||
data = {}
|
||||
tables = get_tables(db)
|
||||
for table in tables:
|
||||
# logger.info(f"Table {table.name} with {table.id}")
|
||||
columns = get_columns_for_table(db, table)
|
||||
model = registry[table.name]
|
||||
rows = db.query(model).all()
|
||||
entries = []
|
||||
for row in rows:
|
||||
entry = {}
|
||||
for column in columns:
|
||||
# logger.info(f" Column {column.order} {column.name} with {column.id}")
|
||||
try:
|
||||
value = getattr(row, column.name)
|
||||
if isinstance(value, datetime):
|
||||
entry[column.name] = str(value)
|
||||
else:
|
||||
entry[column.name] = value
|
||||
except AttributeError as error:
|
||||
logger.info(f"{error}")
|
||||
entries.append(entry)
|
||||
data[table.name] = entries
|
||||
logger.info(f"{table.name}: {len(entries)} exported")
|
||||
json_dump = json.dumps(data, indent=4)
|
||||
with open(args.file, "w") as dump_file:
|
||||
dump_file.write(json_dump)
|
||||
logger.info(f"{len(data)} tables exported")
|
||||
#kontor_db = KontorDB(engine, logger)
|
||||
#kontor_db.export_db(ExportType.JSON, args.file)
|
||||
logger.info('kontor.export finished')
|
||||
|
||||
@@ -7,7 +7,6 @@ from typing import Dict, List
|
||||
|
||||
from config import get_logger, get_database_cursors
|
||||
import json
|
||||
import psycopg2
|
||||
from psycopg2.sql import SQL
|
||||
|
||||
parser = ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter)
|
||||
|
||||
@@ -37,7 +37,7 @@ def get_logger(level: int, config: str):
|
||||
if __name__ == '__main__':
|
||||
log = get_logger(args.verbose, args.config)
|
||||
log.info('kontor.update_titles started')
|
||||
response = requests.get("http://127.0.0.1:8800/media/files?review=true")
|
||||
response = requests.get("http://127.0.0.1:8800/api/media/files?review=true")
|
||||
log.info(f"Status: {response.status_code}")
|
||||
data = response.json()
|
||||
log.info(f"data: {len(data)}")
|
||||
@@ -49,11 +49,11 @@ if __name__ == '__main__':
|
||||
soup = BeautifulSoup(r.content, "html.parser")
|
||||
title = soup.title.string
|
||||
item['title'] = title
|
||||
item['review'] = 0
|
||||
item['review'] = False
|
||||
except:
|
||||
item['title'] = None
|
||||
item['review'] = 1
|
||||
update = requests.put(f"http://127.0.0.1:8800/media/files/{item['id']}", json=item)
|
||||
item['review'] = True
|
||||
update = requests.put(f"http://127.0.0.1:8800/api/media/files/{item['id']}", json=item)
|
||||
log.info(f"update status: {update.status_code}")
|
||||
log.info(f"update result: {update.json()}")
|
||||
log.info('kontor.update_titles finished')
|
||||
|
||||
@@ -84,6 +84,7 @@ public class ComicConstants {
|
||||
comics.addItem(new SideNavItem(TPB, TradePaperbackView.class));
|
||||
comics.addItem(new SideNavItem(STORYARC, StoryArcView.class));
|
||||
comics.addItem(new SideNavItem(VOLUME, VolumeView.class));
|
||||
comics.addItem(new SideNavItem(WORKTYPE, WorktypeView.class));
|
||||
return comics;
|
||||
}
|
||||
|
||||
|
||||
@@ -124,7 +124,6 @@ public class SetupModuleComics implements ApplicationListener<ContextRefreshedEv
|
||||
Comic brath = createComicIfNotFound(crossgen, "Brath", false, false);
|
||||
Comic catwomanrome = createComicIfNotFound(dc, "Catwoman When In Rome", false, false);
|
||||
Comic crimson = createComicIfNotFound(wildstorm, "Crimson", false, false);
|
||||
Comic crossgencomic = createComicIfNotFound(crossgen, "Crossgen", false, false);
|
||||
Comic dangergirl = createComicIfNotFound(cliffhanger, "Danger Girl", false, false);
|
||||
Comic dangergirlbackinblack = createComicIfNotFound(wildstorm, "Danger Girl Back in Black", false, false);
|
||||
Comic daringescapes = createComicIfNotFound(image, "Daring Escapes", false, true);
|
||||
@@ -275,23 +274,10 @@ public class SetupModuleComics implements ApplicationListener<ContextRefreshedEv
|
||||
createComicIfNotFound(darkhorse, "Star Wars: Knights of the Old Republic", false, false);
|
||||
createComicIfNotFound(darkhorse, "Star Wars: Legacy", false, false);
|
||||
createComicIfNotFound(darkhorse, "Star Wars: Dark Times", false, false);
|
||||
createComicWorkIfNotFound(crossgencomic, michaelturner, writer);
|
||||
createComicWorkIfNotFound(dangergirl, michaelturner, writer);
|
||||
createComicWorkIfNotFound(ultimatefantasticfour, michaelturner, writer);
|
||||
createComicWorkIfNotFound(ultimatespidermanannual, michaelturner, writer);
|
||||
createComicWorkIfNotFound(ultimatefantasticfour, brianbendis, writer);
|
||||
createComicWorkIfNotFound(ultimatespidermanannual, brianbendis, writer);
|
||||
createComicWorkIfNotFound(uncannyxmen, michaelturner, writer);
|
||||
createComicWorkIfNotFound(starwars, michaelturner, writer);
|
||||
createComicWorkIfNotFound(shehulk, michaelturner, writer);
|
||||
createComicWorkIfNotFound(shehulk2, michaelturner, writer);
|
||||
createComicWorkIfNotFound(scion, michaelturner, writer);
|
||||
createComicWorkIfNotFound(newavengers, michaelturner, writer);
|
||||
createComicWorkIfNotFound(newmutants, michaelturner, writer);
|
||||
createComicWorkIfNotFound(midnightnation, michaelturner, writer);
|
||||
createComicWorkIfNotFound(monsterwar, michaelturner, writer);
|
||||
createComicWorkIfNotFound(monsterwar2005, michaelturner, writer);
|
||||
createComicWorkIfNotFound(mystic, michaelturner, writer);
|
||||
createComicWorkIfNotFound(holidayspecial2004, michaelturner, writer);
|
||||
createComicWorkIfNotFound(hackslashgirlsgonedead, michaelturner, writer);
|
||||
createComicWorkIfNotFound(newavengers, brianbendis, writer);
|
||||
createComicWorkIfNotFound(uncannyxmen, brianbendis, writer);
|
||||
createStoryArcIfNotFound("Higher Learning", emmafrost);
|
||||
createStoryArcIfNotFound("Mind Games", emmafrost);
|
||||
|
||||
@@ -31,6 +31,8 @@ public class Artist extends AbstractEntity {
|
||||
@Column(unique = true)
|
||||
private String name;
|
||||
|
||||
private String weblink;
|
||||
|
||||
@OneToMany(fetch = FetchType.EAGER, mappedBy = "artist", cascade = CascadeType.REFRESH, orphanRemoval = true)
|
||||
@Nullable
|
||||
List<ComicWork> comicWorks = new LinkedList<>();
|
||||
|
||||
@@ -6,7 +6,7 @@ import java.util.List;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
|
||||
import de.thpeetz.kontor.common.data.AbstractEntity;
|
||||
import io.micrometer.common.lang.Nullable;
|
||||
import jakarta.annotation.Nullable;
|
||||
import jakarta.persistence.CascadeType;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
@@ -19,7 +19,6 @@ import jakarta.validation.constraints.NotNull;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
/**
|
||||
* Represents a comic entity.
|
||||
@@ -44,6 +43,9 @@ public class Comic extends AbstractEntity {
|
||||
|
||||
private Boolean completed = false;
|
||||
|
||||
@Nullable
|
||||
private String weblink;
|
||||
|
||||
@OneToMany(fetch = FetchType.EAGER, mappedBy = "comic", cascade = CascadeType.REFRESH, orphanRemoval = true)
|
||||
@Nullable
|
||||
List<ComicWork> comicWorks;
|
||||
|
||||
@@ -3,18 +3,21 @@ package de.thpeetz.kontor.comics.data;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonBackReference;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import de.thpeetz.kontor.common.data.AbstractEntity;
|
||||
import jakarta.annotation.Nullable;
|
||||
import jakarta.persistence.CascadeType;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.FetchType;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.OneToMany;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.validation.constraints.*;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@@ -26,10 +29,30 @@ public class Publisher extends AbstractEntity {
|
||||
@Column(unique = true)
|
||||
private String name;
|
||||
|
||||
private String weblink;
|
||||
|
||||
@JsonBackReference
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "parent_publisher_id")
|
||||
@Nullable
|
||||
@JsonIgnoreProperties({ "comics" })
|
||||
private Publisher parentCompany;
|
||||
|
||||
@OneToMany(fetch = FetchType.LAZY, mappedBy = "parentCompany", cascade = CascadeType.ALL, orphanRemoval = true)
|
||||
@Nullable
|
||||
private List<Publisher> imprints = new LinkedList<>();
|
||||
|
||||
@OneToMany(fetch = FetchType.EAGER, mappedBy = "publisher", cascade = CascadeType.ALL, orphanRemoval = true)
|
||||
@Nullable
|
||||
private List<Comic> comics = new LinkedList<>();
|
||||
|
||||
public String getParentCompanyName() {
|
||||
if (parentCompany != null) {
|
||||
return parentCompany.name;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuffer sb = new StringBuffer("Publisher{");
|
||||
|
||||
@@ -16,12 +16,14 @@ import com.vaadin.flow.data.binder.Binder;
|
||||
|
||||
import de.thpeetz.kontor.comics.data.Artist;
|
||||
import de.thpeetz.kontor.comics.data.ComicWork;
|
||||
import lombok.*;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class ArtistForm extends FormLayout {
|
||||
|
||||
TextField name = new TextField("Name");
|
||||
TextField weblink = new TextField("Link");
|
||||
Grid<ComicWork> comicWorks = new Grid<>(ComicWork.class);
|
||||
|
||||
Button save = new Button("Save");
|
||||
@@ -38,7 +40,7 @@ public class ArtistForm extends FormLayout {
|
||||
comicWorks.getColumnByKey("workType.name").setHeader("Work type");
|
||||
comicWorks.getColumnByKey("comic.title").setHeader("Comic");
|
||||
comicWorks.getColumns().forEach(col -> col.setAutoWidth(true));
|
||||
add(name, comicWorks, createButtonsLayout());
|
||||
add(name, weblink, comicWorks, createButtonsLayout());
|
||||
}
|
||||
|
||||
private HorizontalLayout createButtonsLayout() {
|
||||
@@ -72,17 +74,15 @@ public class ArtistForm extends FormLayout {
|
||||
this.comicWorks.setItems(works);
|
||||
}
|
||||
|
||||
@Getter
|
||||
public abstract static class ArtistFormEvent extends ComponentEvent<ArtistForm> {
|
||||
private Artist artist;
|
||||
private final Artist artist;
|
||||
|
||||
protected ArtistFormEvent(ArtistForm source, Artist artist) {
|
||||
super(source, false);
|
||||
this.artist = artist;
|
||||
}
|
||||
|
||||
public Artist getArtist() {
|
||||
return artist;
|
||||
}
|
||||
}
|
||||
|
||||
public static class SaveEvent extends ArtistFormEvent {
|
||||
|
||||
@@ -2,6 +2,7 @@ package de.thpeetz.kontor.comics.views;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import lombok.*;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -29,6 +30,7 @@ public class ComicForm extends FormLayout {
|
||||
|
||||
TextField title = new TextField("Title");
|
||||
ComboBox<Publisher> publisher = new ComboBox<>("Publisher");
|
||||
TextField weblink = new TextField("Link");
|
||||
Checkbox currentOrder = new Checkbox("Current order");
|
||||
Checkbox completed = new Checkbox("Completed");
|
||||
Grid<ComicWork> comicWorks = new Grid<>(ComicWork.class);
|
||||
@@ -51,7 +53,7 @@ public class ComicForm extends FormLayout {
|
||||
comicWorks.getColumnByKey("workType.name").setHeader("Work type");
|
||||
comicWorks.getColumnByKey("artist.name").setHeader("Artist");
|
||||
comicWorks.getColumns().forEach(col -> col.setAutoWidth(true));
|
||||
add(title, publisher, currentOrder, completed, comicWorks, createButtonsLayout());
|
||||
add(title, publisher, weblink, currentOrder, completed, comicWorks, createButtonsLayout());
|
||||
}
|
||||
|
||||
private HorizontalLayout createButtonsLayout() {
|
||||
@@ -85,17 +87,15 @@ public class ComicForm extends FormLayout {
|
||||
comicWorks.setItems(works);
|
||||
}
|
||||
|
||||
@Getter
|
||||
public abstract static class ComicFormEvent extends ComponentEvent<ComicForm> {
|
||||
private Comic comic;
|
||||
private final Comic comic;
|
||||
|
||||
protected ComicFormEvent(ComicForm source, Comic comic) {
|
||||
super(source, false);
|
||||
this.comic = comic;
|
||||
}
|
||||
|
||||
public Comic getComic() {
|
||||
return comic;
|
||||
}
|
||||
}
|
||||
|
||||
public static class SaveEvent extends ComicFormEvent {
|
||||
|
||||
@@ -50,6 +50,8 @@ public class ComicView extends VerticalLayout {
|
||||
.setHeader("Bestellung").setWidth("6rem").setSortable(true);
|
||||
Grid.Column<Comic> completedColumn = grid.addComponentColumn(comic -> StatusIcon.create(comic.getCompleted()))
|
||||
.setHeader("Abgeschlossen").setWidth("6rem").setSortable(true);
|
||||
Grid.Column<Comic> weblinkColumn = grid.addColumn(Comic::getWeblink)
|
||||
.setHeader("Link").setResizable(true).setSortable(true);
|
||||
TextField filterText = new TextField();
|
||||
@Getter
|
||||
ComicForm form;
|
||||
@@ -123,6 +125,7 @@ public class ComicView extends VerticalLayout {
|
||||
columnToggleContextMenu.addColumnToggleItem(publisherColumn);
|
||||
columnToggleContextMenu.addColumnToggleItem(currentOrderColumn);
|
||||
columnToggleContextMenu.addColumnToggleItem(completedColumn);
|
||||
columnToggleContextMenu.addColumnToggleItem(weblinkColumn);
|
||||
HorizontalLayout toolbar = new HorizontalLayout(filterText, addComicButton, menuButton);
|
||||
toolbar.addClassName("toolbar");
|
||||
return toolbar;
|
||||
|
||||
@@ -5,6 +5,7 @@ import com.vaadin.flow.component.ComponentEventListener;
|
||||
import com.vaadin.flow.component.Key;
|
||||
import com.vaadin.flow.component.button.Button;
|
||||
import com.vaadin.flow.component.button.ButtonVariant;
|
||||
import com.vaadin.flow.component.combobox.*;
|
||||
import com.vaadin.flow.component.formlayout.FormLayout;
|
||||
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
|
||||
import com.vaadin.flow.component.textfield.TextField;
|
||||
@@ -12,9 +13,14 @@ import com.vaadin.flow.data.binder.BeanValidationBinder;
|
||||
import com.vaadin.flow.data.binder.Binder;
|
||||
|
||||
import de.thpeetz.kontor.comics.data.Publisher;
|
||||
import lombok.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class PublisherForm extends FormLayout {
|
||||
public TextField name = new TextField("Name");
|
||||
public TextField weblink = new TextField("Link");
|
||||
ComboBox<Publisher> parentCompany = new ComboBox<>("Parent Company");
|
||||
|
||||
Button save = new Button("Save");
|
||||
Button delete = new Button("Delete");
|
||||
@@ -22,11 +28,13 @@ public class PublisherForm extends FormLayout {
|
||||
|
||||
Binder<Publisher> binder = new BeanValidationBinder<>(Publisher.class);
|
||||
|
||||
public PublisherForm() {
|
||||
public PublisherForm(List<Publisher> publishers) {
|
||||
addClassName("publisher-form");
|
||||
binder.bindInstanceFields(this);
|
||||
|
||||
add(name, createButtonsLayout());
|
||||
parentCompany.setItems(publishers);
|
||||
parentCompany.setItemLabelGenerator(Publisher::getName);
|
||||
add(name, weblink, parentCompany, createButtonsLayout());
|
||||
}
|
||||
|
||||
private HorizontalLayout createButtonsLayout() {
|
||||
@@ -55,17 +63,15 @@ public class PublisherForm extends FormLayout {
|
||||
binder.setBean(publisher);
|
||||
}
|
||||
|
||||
@Getter
|
||||
public abstract static class PublisherFormEvent extends ComponentEvent<PublisherForm> {
|
||||
private Publisher publisher;
|
||||
private final Publisher publisher;
|
||||
|
||||
protected PublisherFormEvent(PublisherForm source, Publisher publisher) {
|
||||
super(source, false);
|
||||
this.publisher = publisher;
|
||||
}
|
||||
|
||||
public Publisher getPublisher() {
|
||||
return publisher;
|
||||
}
|
||||
}
|
||||
|
||||
public static class SaveEvent extends PublisherFormEvent {
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package de.thpeetz.kontor.comics.views;
|
||||
|
||||
import com.vaadin.flow.component.button.*;
|
||||
import de.thpeetz.kontor.comics.data.*;
|
||||
import de.thpeetz.kontor.common.views.*;
|
||||
import lombok.*;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
|
||||
import com.vaadin.flow.component.Component;
|
||||
import com.vaadin.flow.component.button.Button;
|
||||
import com.vaadin.flow.component.grid.Grid;
|
||||
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
|
||||
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
||||
@@ -14,9 +17,7 @@ import com.vaadin.flow.router.Route;
|
||||
import com.vaadin.flow.spring.annotation.SpringComponent;
|
||||
|
||||
import de.thpeetz.kontor.comics.ComicConstants;
|
||||
import de.thpeetz.kontor.comics.data.Publisher;
|
||||
import de.thpeetz.kontor.comics.services.ComicService;
|
||||
import de.thpeetz.kontor.common.views.MainLayout;
|
||||
import jakarta.annotation.security.PermitAll;
|
||||
|
||||
@SpringComponent
|
||||
@@ -26,8 +27,24 @@ import jakarta.annotation.security.PermitAll;
|
||||
@PageTitle("Publisher | Comics | Kontor")
|
||||
public class PublisherView extends VerticalLayout {
|
||||
|
||||
Grid<Publisher> grid = new Grid<>(Publisher.class);
|
||||
@Getter
|
||||
Grid<Publisher> grid = new Grid<>(Publisher.class, false);
|
||||
Grid.Column<Publisher> idColumn = grid.addColumn(Publisher::getId)
|
||||
.setHeader("ID").setResizable(true).setSortable(true);
|
||||
Grid.Column<Publisher> createdColumn = grid.addColumn(Publisher::getCreatedDate)
|
||||
.setHeader("Erstellt").setResizable(true).setSortable(true);
|
||||
Grid.Column<Publisher> modifiedColumn = grid.addColumn(Publisher::getLastModifiedDate)
|
||||
.setHeader("Geändert").setResizable(true).setSortable(true);
|
||||
Grid.Column<Publisher> nameColumn = grid.addColumn(Publisher::getName)
|
||||
.setHeader("Titel").setResizable(true).setSortable(true);
|
||||
Grid.Column<Publisher> parentCompanyColumn = grid.addColumn(Publisher::getParentCompanyName)
|
||||
.setHeader("Parent Company").setResizable(true).setSortable(true);
|
||||
Grid.Column<Publisher> imprintColumn = grid.addComponentColumn(publisher -> StatusIcon.create(publisher.getParentCompany() != null))
|
||||
.setHeader("Imprint").setWidth("6rem").setSortable(true);
|
||||
Grid.Column<Publisher> weblinkColumn = grid.addColumn(Publisher::getWeblink)
|
||||
.setHeader("Link").setResizable(true).setSortable(true);
|
||||
TextField filterText = new TextField();
|
||||
@Getter
|
||||
PublisherForm form;
|
||||
|
||||
ComicService service;
|
||||
@@ -46,13 +63,12 @@ public class PublisherView extends VerticalLayout {
|
||||
private void configureGrid() {
|
||||
grid.addClassName("publisher-grid");
|
||||
grid.setSizeFull();
|
||||
grid.setColumns("name");
|
||||
grid.getColumns().forEach(col -> col.setAutoWidth(true));
|
||||
grid.asSingleSelect().addValueChangeListener(event -> editPublisher(event.getValue()));
|
||||
}
|
||||
|
||||
private void configureForm() {
|
||||
form = new PublisherForm();
|
||||
form = new PublisherForm(service.findAllPublishers(null));
|
||||
form.setWidth("25em");
|
||||
form.setVisible(false);
|
||||
form.addSaveListener(this::savePublisher);
|
||||
@@ -72,14 +88,6 @@ public class PublisherView extends VerticalLayout {
|
||||
closeEditor();
|
||||
}
|
||||
|
||||
public Grid<Publisher> getGrid() {
|
||||
return grid;
|
||||
}
|
||||
|
||||
public PublisherForm getForm() {
|
||||
return form;
|
||||
}
|
||||
|
||||
private Component getContent() {
|
||||
HorizontalLayout content = new HorizontalLayout(grid, form);
|
||||
content.setFlexGrow(2, grid);
|
||||
@@ -98,7 +106,17 @@ public class PublisherView extends VerticalLayout {
|
||||
Button addPublisherButton = new Button("Add publisher");
|
||||
addPublisherButton.addClickListener(click -> addPublisher());
|
||||
|
||||
HorizontalLayout toolbar = new HorizontalLayout(filterText, addPublisherButton);
|
||||
Button menuButton = new Button("Show/Hide Columns");
|
||||
menuButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
|
||||
ColumnToggleContextMenu<Publisher> columnToggleContextMenu = new ColumnToggleContextMenu<>(menuButton);
|
||||
columnToggleContextMenu.addColumnToggleItem(idColumn);
|
||||
columnToggleContextMenu.addColumnToggleItem(createdColumn);
|
||||
columnToggleContextMenu.addColumnToggleItem(modifiedColumn);
|
||||
columnToggleContextMenu.addColumnToggleItem(nameColumn);
|
||||
columnToggleContextMenu.addColumnToggleItem(parentCompanyColumn);
|
||||
columnToggleContextMenu.addColumnToggleItem(imprintColumn);
|
||||
columnToggleContextMenu.addColumnToggleItem(weblinkColumn);
|
||||
HorizontalLayout toolbar = new HorizontalLayout(filterText, addPublisherButton, menuButton);
|
||||
toolbar.addClassName("toolbar");
|
||||
return toolbar;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package de.thpeetz.kontor.comics.views;
|
||||
|
||||
import lombok.*;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
|
||||
import com.vaadin.flow.component.Component;
|
||||
@@ -26,8 +27,10 @@ import jakarta.annotation.security.PermitAll;
|
||||
@PageTitle("Worktype | Comics | Kontor")
|
||||
public class WorktypeView extends VerticalLayout {
|
||||
|
||||
@Getter
|
||||
Grid<Worktype> grid = new Grid<>(Worktype.class);
|
||||
TextField filterText = new TextField();
|
||||
@Getter
|
||||
WorktypeForm form;
|
||||
ComicService service;
|
||||
|
||||
@@ -42,10 +45,6 @@ public class WorktypeView extends VerticalLayout {
|
||||
updateList();
|
||||
}
|
||||
|
||||
public Grid<Worktype> getGrid() {
|
||||
return grid;
|
||||
}
|
||||
|
||||
private void configureGrid() {
|
||||
grid.addClassName("worktype-grid");
|
||||
grid.setSizeFull();
|
||||
@@ -54,13 +53,9 @@ public class WorktypeView extends VerticalLayout {
|
||||
grid.asSingleSelect().addValueChangeListener(event -> editWorktype(event.getValue()));
|
||||
}
|
||||
|
||||
public WorktypeForm getForm() {
|
||||
return form;
|
||||
}
|
||||
|
||||
private void configureForm() {
|
||||
form = new WorktypeForm();
|
||||
form.setWidth("25em");
|
||||
form.setWidth("45em");
|
||||
form.setVisible(false);
|
||||
form.addSaveListener(this::saveWorktype);
|
||||
form.addDeleteListener(this::deleteWorktype);
|
||||
|
||||
@@ -19,17 +19,19 @@ import de.thpeetz.kontor.bookshelf.BookshelfConstants;
|
||||
import de.thpeetz.kontor.comics.ComicConstants;
|
||||
import de.thpeetz.kontor.security.SecurityService;
|
||||
import de.thpeetz.kontor.tysc.TyscConstants;
|
||||
import lombok.*;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class KontorLayoutUtil {
|
||||
|
||||
private final AppLayout appLayout;
|
||||
@Setter
|
||||
private HorizontalLayout secondaryNavigation;
|
||||
|
||||
private AdminService adminService;
|
||||
private final AdminService adminService;
|
||||
|
||||
private SecurityService securityService;
|
||||
private final SecurityService securityService;
|
||||
|
||||
public KontorLayoutUtil(AppLayout layout, AdminService adminService, SecurityService securityService) {
|
||||
this.adminService = adminService;
|
||||
@@ -37,10 +39,6 @@ public class KontorLayoutUtil {
|
||||
this.appLayout = layout;
|
||||
}
|
||||
|
||||
public void setSecondaryNavigation(HorizontalLayout secondaryNavigation) {
|
||||
this.secondaryNavigation = secondaryNavigation;
|
||||
}
|
||||
|
||||
public void createHeader(String titleName) {
|
||||
appLayout.addToDrawer(createTitle(), getScroller());
|
||||
appLayout.addToNavbar(getHeader(titleName));
|
||||
|
||||
@@ -9,13 +9,7 @@ import de.thpeetz.kontor.security.SecurityService;
|
||||
|
||||
public class SeparateMainLayout extends AppLayout {
|
||||
|
||||
private final AdminService adminService;
|
||||
|
||||
private final SecurityService securityService;
|
||||
|
||||
public SeparateMainLayout(AdminService adminService, SecurityService securityService) {
|
||||
this.adminService = adminService;
|
||||
this.securityService = securityService;
|
||||
|
||||
KontorLayoutUtil layout = new KontorLayoutUtil(this, adminService, securityService);
|
||||
layout.setSecondaryNavigation(getSecondaryNavigation());
|
||||
|
||||
Reference in New Issue
Block a user