diff --git a/kontor-api/.gitignore b/kontor-api/.gitignore
index 3373a70..f1f28bd 100644
--- a/kontor-api/.gitignore
+++ b/kontor-api/.gitignore
@@ -1,2 +1,4 @@
.env
.coverage
+app.log
+
diff --git a/kontor-api/src/core/log_conf.py b/kontor-api/src/core/log_conf.py
new file mode 100644
index 0000000..99c17ad
--- /dev/null
+++ b/kontor-api/src/core/log_conf.py
@@ -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__)
diff --git a/kontor-api/src/db/models/base.py b/kontor-api/src/db/models/base.py
index d090abf..84d00ef 100644
--- a/kontor-api/src/db/models/base.py
+++ b/kontor-api/src/db/models/base.py
@@ -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())
diff --git a/kontor-api/src/db/models/bookshelf.py b/kontor-api/src/db/models/bookshelf.py
index d01fc6b..c2b4e4b 100644
--- a/kontor-api/src/db/models/bookshelf.py
+++ b/kontor-api/src/db/models/bookshelf.py
@@ -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")
diff --git a/kontor-api/src/db/models/comic.py b/kontor-api/src/db/models/comic.py
index e9432fe..460421b 100644
--- a/kontor-api/src/db/models/comic.py
+++ b/kontor-api/src/db/models/comic.py
@@ -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]]:
diff --git a/kontor-api/src/db/repository/comic.py b/kontor-api/src/db/repository/comic.py
index 53f3a12..a20fccc 100644
--- a/kontor-api/src/db/repository/comic.py
+++ b/kontor-api/src/db/repository/comic.py
@@ -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
diff --git a/kontor-api/src/main.py b/kontor-api/src/main.py
index df4abe9..7650618 100644
--- a/kontor-api/src/main.py
+++ b/kontor-api/src/main.py
@@ -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)
diff --git a/kontor-api/src/schema/comics/artist.py b/kontor-api/src/schema/comics/artist.py
index c97282c..48d6210 100644
--- a/kontor-api/src/schema/comics/artist.py
+++ b/kontor-api/src/schema/comics/artist.py
@@ -15,4 +15,5 @@ class ArtistResponse(BaseModel):
class ArtistDetailResponse(BaseModel):
id: str
name: str
+ weblink: str
works: Dict[str, List[str]]
diff --git a/kontor-api/src/schema/comics/comic.py b/kontor-api/src/schema/comics/comic.py
index 4d7051b..168402a 100644
--- a/kontor-api/src/schema/comics/comic.py
+++ b/kontor-api/src/schema/comics/comic.py
@@ -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
-
diff --git a/kontor-api/src/templates/comic/artist_detail.html b/kontor-api/src/templates/comic/artist_detail.html
index 58ee61b..aed3bd1 100644
--- a/kontor-api/src/templates/comic/artist_detail.html
+++ b/kontor-api/src/templates/comic/artist_detail.html
@@ -20,6 +20,10 @@
Artist Name |
{{artist.name}} |
+
+ | Link |
+ {{artist.weblink}} |
+
| Works |
@@ -46,5 +50,9 @@
+
{% endblock %}
+
diff --git a/kontor-api/src/templates/comic/comic_detail.html b/kontor-api/src/templates/comic/comic_detail.html
index 76a5b79..c30c31c 100644
--- a/kontor-api/src/templates/comic/comic_detail.html
+++ b/kontor-api/src/templates/comic/comic_detail.html
@@ -27,6 +27,10 @@
{% endwith %}
|
+
+ | Link |
+ {{comic.weblink}} |
+
| Works |
diff --git a/kontor-api/src/templates/comic/publisher_detail.html b/kontor-api/src/templates/comic/publisher_detail.html
index 6429d58..d9fc233 100644
--- a/kontor-api/src/templates/comic/publisher_detail.html
+++ b/kontor-api/src/templates/comic/publisher_detail.html
@@ -20,6 +20,24 @@
| Publisher Name |
{{publisher.name}} |
+ {% if publisher.parent_publisher_id %}
+
+ | Parent Company |
+ {{publisher.parent_publisher.name}} |
+
+ {% endif %}
+ {% if publisher.imprints|length > 0 %}
+
+ | Imprints |
+
+
+ |
+
+ {% endif %}
| Comics |
diff --git a/kontor-api/src/templates/comic/worktype_detail.html b/kontor-api/src/templates/comic/worktype_detail.html
index ac734a3..1c17823 100644
--- a/kontor-api/src/templates/comic/worktype_detail.html
+++ b/kontor-api/src/templates/comic/worktype_detail.html
@@ -30,6 +30,10 @@
{% endfor %}
|
+
+ | ID |
+ {{worktype.id}} |
+
| Data Created |
{{worktype.created_date}} |
@@ -45,5 +49,23 @@
+
{% endblock %}
+
+{% block scripts %}
+
+{% endblock %}
diff --git a/kontor-api/src/templates/comic/add_worktype.html b/kontor-api/src/templates/comic/worktype_edit.html
similarity index 100%
rename from kontor-api/src/templates/comic/add_worktype.html
rename to kontor-api/src/templates/comic/worktype_edit.html
diff --git a/kontor-api/src/templates/comic/worktypes.html b/kontor-api/src/templates/comic/worktypes.html
index 19a43fb..ee5ea6a 100644
--- a/kontor-api/src/templates/comic/worktypes.html
+++ b/kontor-api/src/templates/comic/worktypes.html
@@ -16,11 +16,13 @@
{% for worktype in worktypes %}
- | {{worktype.name}} |
+ {{worktype.name}} |
+ Edit |
+ Delete |
{% endfor %}
- Add WorkType
+ Add WorkType
{% endblock %}
diff --git a/kontor-api/src/templates/components/artist_cards.html b/kontor-api/src/templates/components/artist_cards.html
index 53192af..f0f5584 100644
--- a/kontor-api/src/templates/components/artist_cards.html
+++ b/kontor-api/src/templates/components/artist_cards.html
@@ -1,6 +1,7 @@
{{obj.name}}
+
Link : {{obj.weblink}}
Read more
diff --git a/kontor-api/src/templates/components/navbar.html b/kontor-api/src/templates/components/navbar.html
index f4cb733..ad9cc99 100644
--- a/kontor-api/src/templates/components/navbar.html
+++ b/kontor-api/src/templates/components/navbar.html
@@ -16,7 +16,7 @@
Artists
Publishers
- WorkTypes
+ WorkTypes
diff --git a/kontor-api/src/templates/media/videos.html b/kontor-api/src/templates/media/videos.html
index 1075bac..c8a029a 100644
--- a/kontor-api/src/templates/media/videos.html
+++ b/kontor-api/src/templates/media/videos.html
@@ -16,13 +16,13 @@
Cloudlink |
- {% for mediavideo in mediavideos %}
-
+ {% for mediavideo in mediavideos %}
+
| {{mediavideo.title}} |
{{mediavideo.url}} |
{{mediavideo.cloud_link}} |
-
- {% endfor %}
+
+ {% endfor %}
diff --git a/kontor-api/src/webapps/comic/route_comics.py b/kontor-api/src/webapps/comic/route_comics.py
index 83a533e..94f8f2a 100644
--- a/kontor-api/src/webapps/comic/route_comics.py
+++ b/kontor-api/src/webapps/comic/route_comics.py
@@ -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)
diff --git a/kontor-scripts/schema/__init__.py b/kontor-scripts/db/__init__.py
similarity index 100%
rename from kontor-scripts/schema/__init__.py
rename to kontor-scripts/db/__init__.py
diff --git a/kontor-scripts/db/models/__init__.py b/kontor-scripts/db/models/__init__.py
new file mode 100644
index 0000000..d0fe076
--- /dev/null
+++ b/kontor-scripts/db/models/__init__.py
@@ -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
+}
diff --git a/kontor-scripts/schema/admin.py b/kontor-scripts/db/models/admin.py
similarity index 83%
rename from kontor-scripts/schema/admin.py
rename to kontor-scripts/db/models/admin.py
index 583b772..b7e886a 100644
--- a/kontor-scripts/schema/admin.py
+++ b/kontor-scripts/db/models/admin.py
@@ -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")
diff --git a/kontor-scripts/schema/base.py b/kontor-scripts/db/models/base.py
similarity index 100%
rename from kontor-scripts/schema/base.py
rename to kontor-scripts/db/models/base.py
diff --git a/kontor-scripts/schema/bookshelf.py b/kontor-scripts/db/models/bookshelf.py
similarity index 97%
rename from kontor-scripts/schema/bookshelf.py
rename to kontor-scripts/db/models/bookshelf.py
index 2830ee2..08fa13d 100644
--- a/kontor-scripts/schema/bookshelf.py
+++ b/kontor-scripts/db/models/bookshelf.py
@@ -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):
diff --git a/kontor-scripts/schema/comic.py b/kontor-scripts/db/models/comic.py
similarity index 78%
rename from kontor-scripts/schema/comic.py
rename to kontor-scripts/db/models/comic.py
index 9108605..c0678ba 100644
--- a/kontor-scripts/schema/comic.py
+++ b/kontor-scripts/db/models/comic.py
@@ -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")
+
diff --git a/kontor-scripts/schema/database.py b/kontor-scripts/db/models/database.py
similarity index 77%
rename from kontor-scripts/schema/database.py
rename to kontor-scripts/db/models/database.py
index 3e7db0f..ba8b6c0 100644
--- a/kontor-scripts/schema/database.py
+++ b/kontor-scripts/db/models/database.py
@@ -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}
diff --git a/kontor-scripts/schema/media.py b/kontor-scripts/db/models/media.py
similarity index 98%
rename from kontor-scripts/schema/media.py
rename to kontor-scripts/db/models/media.py
index 979e8e1..734d443 100644
--- a/kontor-scripts/schema/media.py
+++ b/kontor-scripts/db/models/media.py
@@ -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):
diff --git a/kontor-scripts/schema/metadata.py b/kontor-scripts/db/models/metadata.py
similarity index 96%
rename from kontor-scripts/schema/metadata.py
rename to kontor-scripts/db/models/metadata.py
index 42dd88b..915c4a9 100644
--- a/kontor-scripts/schema/metadata.py
+++ b/kontor-scripts/db/models/metadata.py
@@ -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):
diff --git a/kontor-scripts/schema/tysc.py b/kontor-scripts/db/models/tysc.py
similarity index 98%
rename from kontor-scripts/schema/tysc.py
rename to kontor-scripts/db/models/tysc.py
index 1bc500e..2f85b94 100644
--- a/kontor-scripts/schema/tysc.py
+++ b/kontor-scripts/db/models/tysc.py
@@ -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):
diff --git a/kontor-scripts/db/repository/__init__.py b/kontor-scripts/db/repository/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/kontor-scripts/db/repository/metadata.py b/kontor-scripts/db/repository/metadata.py
new file mode 100644
index 0000000..0e0558b
--- /dev/null
+++ b/kontor-scripts/db/repository/metadata.py
@@ -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
+
diff --git a/kontor-scripts/db/schemas/metadata.py b/kontor-scripts/db/schemas/metadata.py
new file mode 100644
index 0000000..8986fa9
--- /dev/null
+++ b/kontor-scripts/db/schemas/metadata.py
@@ -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
diff --git a/kontor-scripts/download.py b/kontor-scripts/download.py
index 2faa422..cafe201 100644
--- a/kontor-scripts/download.py
+++ b/kontor-scripts/download.py
@@ -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)}")
diff --git a/kontor-scripts/export.py b/kontor-scripts/export.py
index e81a4e4..e5a0f09 100644
--- a/kontor-scripts/export.py
+++ b/kontor-scripts/export.py
@@ -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')
diff --git a/kontor-scripts/json_to_postgres.py b/kontor-scripts/json_to_postgres.py
index 3aa0738..2616313 100644
--- a/kontor-scripts/json_to_postgres.py
+++ b/kontor-scripts/json_to_postgres.py
@@ -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)
diff --git a/kontor-scripts/update_title.py b/kontor-scripts/update_title.py
index a410eac..7e42bdb 100644
--- a/kontor-scripts/update_title.py
+++ b/kontor-scripts/update_title.py
@@ -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')
diff --git a/kontor-spring/src/main/java/de/thpeetz/kontor/comics/ComicConstants.java b/kontor-spring/src/main/java/de/thpeetz/kontor/comics/ComicConstants.java
index c89f7b1..86dc359 100644
--- a/kontor-spring/src/main/java/de/thpeetz/kontor/comics/ComicConstants.java
+++ b/kontor-spring/src/main/java/de/thpeetz/kontor/comics/ComicConstants.java
@@ -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;
}
diff --git a/kontor-spring/src/main/java/de/thpeetz/kontor/comics/SetupModuleComics.java b/kontor-spring/src/main/java/de/thpeetz/kontor/comics/SetupModuleComics.java
index 52674fd..75ce372 100644
--- a/kontor-spring/src/main/java/de/thpeetz/kontor/comics/SetupModuleComics.java
+++ b/kontor-spring/src/main/java/de/thpeetz/kontor/comics/SetupModuleComics.java
@@ -124,7 +124,6 @@ public class SetupModuleComics implements ApplicationListener comicWorks = new LinkedList<>();
diff --git a/kontor-spring/src/main/java/de/thpeetz/kontor/comics/data/Comic.java b/kontor-spring/src/main/java/de/thpeetz/kontor/comics/data/Comic.java
index 6b04649..d20ea37 100644
--- a/kontor-spring/src/main/java/de/thpeetz/kontor/comics/data/Comic.java
+++ b/kontor-spring/src/main/java/de/thpeetz/kontor/comics/data/Comic.java
@@ -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 comicWorks;
diff --git a/kontor-spring/src/main/java/de/thpeetz/kontor/comics/data/Publisher.java b/kontor-spring/src/main/java/de/thpeetz/kontor/comics/data/Publisher.java
index 233d405..cd27a43 100644
--- a/kontor-spring/src/main/java/de/thpeetz/kontor/comics/data/Publisher.java
+++ b/kontor-spring/src/main/java/de/thpeetz/kontor/comics/data/Publisher.java
@@ -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 imprints = new LinkedList<>();
+
@OneToMany(fetch = FetchType.EAGER, mappedBy = "publisher", cascade = CascadeType.ALL, orphanRemoval = true)
@Nullable
private List comics = new LinkedList<>();
+ public String getParentCompanyName() {
+ if (parentCompany != null) {
+ return parentCompany.name;
+ }
+ return null;
+ }
+
@Override
public String toString() {
final StringBuffer sb = new StringBuffer("Publisher{");
diff --git a/kontor-spring/src/main/java/de/thpeetz/kontor/comics/views/ArtistForm.java b/kontor-spring/src/main/java/de/thpeetz/kontor/comics/views/ArtistForm.java
index 43a93d4..5eb1e0d 100644
--- a/kontor-spring/src/main/java/de/thpeetz/kontor/comics/views/ArtistForm.java
+++ b/kontor-spring/src/main/java/de/thpeetz/kontor/comics/views/ArtistForm.java
@@ -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 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 {
- 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 {
diff --git a/kontor-spring/src/main/java/de/thpeetz/kontor/comics/views/ComicForm.java b/kontor-spring/src/main/java/de/thpeetz/kontor/comics/views/ComicForm.java
index 55fc557..56aa1b2 100644
--- a/kontor-spring/src/main/java/de/thpeetz/kontor/comics/views/ComicForm.java
+++ b/kontor-spring/src/main/java/de/thpeetz/kontor/comics/views/ComicForm.java
@@ -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 = new ComboBox<>("Publisher");
+ TextField weblink = new TextField("Link");
Checkbox currentOrder = new Checkbox("Current order");
Checkbox completed = new Checkbox("Completed");
Grid 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 {
- 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 {
diff --git a/kontor-spring/src/main/java/de/thpeetz/kontor/comics/views/ComicView.java b/kontor-spring/src/main/java/de/thpeetz/kontor/comics/views/ComicView.java
index b89cfde..7732b72 100644
--- a/kontor-spring/src/main/java/de/thpeetz/kontor/comics/views/ComicView.java
+++ b/kontor-spring/src/main/java/de/thpeetz/kontor/comics/views/ComicView.java
@@ -50,6 +50,8 @@ public class ComicView extends VerticalLayout {
.setHeader("Bestellung").setWidth("6rem").setSortable(true);
Grid.Column completedColumn = grid.addComponentColumn(comic -> StatusIcon.create(comic.getCompleted()))
.setHeader("Abgeschlossen").setWidth("6rem").setSortable(true);
+ Grid.Column 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;
diff --git a/kontor-spring/src/main/java/de/thpeetz/kontor/comics/views/PublisherForm.java b/kontor-spring/src/main/java/de/thpeetz/kontor/comics/views/PublisherForm.java
index dbd2f62..6b1c866 100644
--- a/kontor-spring/src/main/java/de/thpeetz/kontor/comics/views/PublisherForm.java
+++ b/kontor-spring/src/main/java/de/thpeetz/kontor/comics/views/PublisherForm.java
@@ -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 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 binder = new BeanValidationBinder<>(Publisher.class);
- public PublisherForm() {
+ public PublisherForm(List 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 {
- 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 {
diff --git a/kontor-spring/src/main/java/de/thpeetz/kontor/comics/views/PublisherView.java b/kontor-spring/src/main/java/de/thpeetz/kontor/comics/views/PublisherView.java
index b716889..cf1eae8 100644
--- a/kontor-spring/src/main/java/de/thpeetz/kontor/comics/views/PublisherView.java
+++ b/kontor-spring/src/main/java/de/thpeetz/kontor/comics/views/PublisherView.java
@@ -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 grid = new Grid<>(Publisher.class);
+ @Getter
+ Grid grid = new Grid<>(Publisher.class, false);
+ Grid.Column idColumn = grid.addColumn(Publisher::getId)
+ .setHeader("ID").setResizable(true).setSortable(true);
+ Grid.Column createdColumn = grid.addColumn(Publisher::getCreatedDate)
+ .setHeader("Erstellt").setResizable(true).setSortable(true);
+ Grid.Column modifiedColumn = grid.addColumn(Publisher::getLastModifiedDate)
+ .setHeader("GeƤndert").setResizable(true).setSortable(true);
+ Grid.Column nameColumn = grid.addColumn(Publisher::getName)
+ .setHeader("Titel").setResizable(true).setSortable(true);
+ Grid.Column parentCompanyColumn = grid.addColumn(Publisher::getParentCompanyName)
+ .setHeader("Parent Company").setResizable(true).setSortable(true);
+ Grid.Column imprintColumn = grid.addComponentColumn(publisher -> StatusIcon.create(publisher.getParentCompany() != null))
+ .setHeader("Imprint").setWidth("6rem").setSortable(true);
+ Grid.Column 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 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 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;
}
diff --git a/kontor-spring/src/main/java/de/thpeetz/kontor/comics/views/WorktypeView.java b/kontor-spring/src/main/java/de/thpeetz/kontor/comics/views/WorktypeView.java
index 98f3adb..b744e09 100644
--- a/kontor-spring/src/main/java/de/thpeetz/kontor/comics/views/WorktypeView.java
+++ b/kontor-spring/src/main/java/de/thpeetz/kontor/comics/views/WorktypeView.java
@@ -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 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 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);
diff --git a/kontor-spring/src/main/java/de/thpeetz/kontor/common/views/KontorLayoutUtil.java b/kontor-spring/src/main/java/de/thpeetz/kontor/common/views/KontorLayoutUtil.java
index 64c60d2..3a07cbb 100644
--- a/kontor-spring/src/main/java/de/thpeetz/kontor/common/views/KontorLayoutUtil.java
+++ b/kontor-spring/src/main/java/de/thpeetz/kontor/common/views/KontorLayoutUtil.java
@@ -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));
diff --git a/kontor-spring/src/main/java/de/thpeetz/kontor/common/views/SeparateMainLayout.java b/kontor-spring/src/main/java/de/thpeetz/kontor/common/views/SeparateMainLayout.java
index 474d4a7..3ea163f 100644
--- a/kontor-spring/src/main/java/de/thpeetz/kontor/common/views/SeparateMainLayout.java
+++ b/kontor-spring/src/main/java/de/thpeetz/kontor/common/views/SeparateMainLayout.java
@@ -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());