add CRUD for WorkType

This commit is contained in:
Thomas Peetz
2025-05-13 00:42:41 +02:00
parent 06a48a03ac
commit 3537642df9
49 changed files with 514 additions and 215 deletions
+2
View File
@@ -1,2 +1,4 @@
.env
.coverage
app.log
+53
View File
@@ -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__)
+1 -1
View File
@@ -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 -6
View File
@@ -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")
+15 -4
View File
@@ -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]]:
+15 -4
View File
@@ -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
+5 -7
View File
@@ -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)
+1
View File
@@ -15,4 +15,5 @@ class ArtistResponse(BaseModel):
class ArtistDetailResponse(BaseModel):
id: str
name: str
weblink: str
works: Dict[str, List[str]]
+2 -1
View File
@@ -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">
+4 -4
View File
@@ -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>
+40 -9
View File
@@ -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)
+86
View File
@@ -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):
+28
View File
@@ -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
+15
View File
@@ -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
+2 -2
View File
@@ -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)}")
+35 -7
View File
@@ -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')
-1
View File
@@ -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)
+4 -4
View File
@@ -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());