Vorbereitung Release 0.2.0 #83

Merged
tpeetz merged 178 commits from develop/0.2.0 into main 2026-01-29 22:50:42 +00:00
31 changed files with 397 additions and 233 deletions
Showing only changes of commit c77adb0e04 - Show all commits
+6 -7
View File
@@ -1,11 +1,10 @@
from uuid import UUID
from typing import List
from typing import List, AnyStr
from fastapi import APIRouter, HTTPException, status
from sqlalchemy import select
from src.apis.utils import SessionDep
from src.db.repository.comic import get_artist_details, list_comics
from src.schema.comics.comic import ComicResponse, ComicDetailsResponse, get_comic_details, get_short_info
from src.schema.comics.artist import ArtistCreation, ArtistDetailResponse, ArtistResponse, get_artist_details
from src.schema.comics.artist import ArtistCreation, ArtistDetailResponse, ArtistResponse
from src.db.models.comic import Comic, Artist
router = APIRouter(
@@ -18,14 +17,14 @@ router = APIRouter(
@router.get("/comics")
def get_all_comics(db: SessionDep) -> List[ComicResponse]:
results: List[ComicResponse] = []
comics = db.scalars(select(Comic)).all()
comics = list_comics(db)
for comic in comics:
response = get_short_info(comic)
results.append(response)
return results
@router.get("/comics/{comic_id}", response_model=ComicDetailsResponse)
def get_comic(comic_id: UUID, db: SessionDep) -> ComicDetailsResponse:
def get_comic(comic_id: AnyStr, db: SessionDep) -> ComicDetailsResponse:
comic = db.get(Comic, comic_id)
if comic is None:
raise HTTPException(status_code=404, detail="Comic could not be found")
@@ -41,7 +40,7 @@ def get_all_artists(db: SessionDep) -> List[ArtistResponse]:
return results
@router.get("/artists/{artist_id}", response_model=ArtistDetailResponse)
def get_artist(artist_id: UUID, db: SessionDep) -> ArtistDetailResponse:
def get_artist(artist_id: AnyStr, db: SessionDep) -> ArtistDetailResponse:
artist = db.get(Artist, artist_id)
if artist is None:
raise HTTPException(status_code=404, detail="Artist could not be found")
+3 -3
View File
@@ -1,4 +1,4 @@
from typing import List
from typing import List, AnyStr
from uuid import UUID
from fastapi import APIRouter, status, HTTPException
@@ -42,7 +42,7 @@ def get_all_files(db: SessionDep, review: bool = False, download: bool = False)
return results
@router.get("/files/{file_id}", response_model=MediaFileResponse)
def get_file(file_id: UUID, db: SessionDep) -> MediaFileResponse:
def get_file(file_id: AnyStr, db: SessionDep) -> MediaFileResponse:
mediafile = db.get(MediaFile, file_id)
if not mediafile:
raise HTTPException(status_code=404, detail="MediaFile could not be found")
@@ -50,7 +50,7 @@ def get_file(file_id: UUID, db: SessionDep) -> MediaFileResponse:
return response
@router.put("/files/{file_id}", response_model=MediaFileResponse)
def update_file(file_id: UUID, db: SessionDep, info: MediaFileResponse) -> MediaFileResponse:
def update_file(file_id: AnyStr, db: SessionDep, info: MediaFileResponse) -> MediaFileResponse:
mediaFile = db.get(MediaFile, file_id)
if not mediaFile:
raise HTTPException(status_code=404, detail="MediaFile could not be found")
+14 -14
View File
@@ -8,11 +8,11 @@ from src.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,17 +30,17 @@ 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")
class Permission(Base, BaseMixin):
__tablename__ = "permission"
name = Column(String(255), nullable=False)
name = Column(String, nullable=False)
assignments = relationship("Assignment")
@@ -54,17 +54,17 @@ class Assignment(Base, BaseMixin):
class ModuleData(Base, BaseMixin):
__tablename__ = "module_data"
module_name = Column(String(255), nullable=False)
module_name = Column(String, nullable=False)
import_data = Column(Boolean)
class MailAccount(Base, BaseMixin):
__tablename__ = "mail_account"
host = Column(String(255))
host = Column(String)
port = Column(Integer)
protocol = Column(String(255))
user_name = Column(String(255))
password = Column(String(255))
protocol = Column(String)
user_name = Column(String)
password = Column(String)
start_tls = Column(Boolean)
+7 -7
View File
@@ -10,8 +10,8 @@ class Base(DeclarativeBase):
class BaseMixin:
id = Column(String(255), primary_key=True, default=uuid.uuid4())
# id: Mapped[str] = mapped_column(primary_key=True, default=uuid.uuid4())
#id = Column(String(255), 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())
# last_modified_date = Column(DateTime)
@@ -21,10 +21,10 @@ class BaseMixin:
class BaseVideoMixin:
cloud_link = Column(String(255))
file_name = Column(String(255))
path = Column(String(255))
cloud_link = Column(String)
file_name = Column(String)
path = Column(String)
review = Column(Boolean)
title = Column(String(255))
url = Column(String(255), unique=True)
title = Column(String)
url = Column(String, unique=True)
should_download = Column(Boolean)
+8 -8
View File
@@ -8,7 +8,7 @@ from src.db.models.base import Base, BaseMixin
class Publisher(Base, BaseMixin):
__tablename__ = "publisher"
name = Column(String(length=255), unique=True)
name = Column(String, unique=True)
comics = relationship("Comic")
def __repr__(self):
@@ -20,7 +20,7 @@ class Publisher(Base, BaseMixin):
class Comic(Base, BaseMixin):
__tablename__ = 'comic'
title = Column(String(length=255), unique=True)
title = Column(String, unique=True)
publisher_id = Column(String, ForeignKey('publisher.id'), nullable=False)
publisher = relationship("Publisher", back_populates="comics")
current_order = Column(Boolean)
@@ -55,7 +55,7 @@ class Comic(Base, BaseMixin):
class Volume(Base, BaseMixin):
__tablename__ = "volume"
name = Column(String(length=255), nullable=False)
name = Column(String, nullable=False)
comic_id = Column(String, ForeignKey("comic.id"), nullable=False)
comic = relationship("Comic", back_populates="volumes")
issues = relationship("Issue")
@@ -63,7 +63,7 @@ class Volume(Base, BaseMixin):
class TradePaperback(Base, BaseMixin):
__tablename__ = "trade_paperback"
name = Column(String(length=255), nullable=False)
name = Column(String, nullable=False)
issue_start = Column(Integer)
issue_end = Column(Integer)
comic_id = Column(String, ForeignKey("comic.id"), nullable=False)
@@ -72,14 +72,14 @@ class TradePaperback(Base, BaseMixin):
class StoryArc(Base, BaseMixin):
__tablename__ = "story_arc"
name = Column(String(length=255), nullable=False)
name = Column(String, nullable=False)
comic_id = Column(String, ForeignKey("comic.id"), nullable=False)
comic = relationship("Comic", back_populates="story_arcs")
class Issue(Base, BaseMixin):
__tablename__ = "issue"
issue_number = Column(String(255))
issue_number = Column(String)
in_stock = Column(Boolean)
is_read = Column(Boolean)
comic_id = Column(String, ForeignKey("comic.id"), nullable=False)
@@ -90,7 +90,7 @@ class Issue(Base, BaseMixin):
class Artist(Base, BaseMixin):
__tablename__ = "artist"
name = Column(String(length=255), nullable=False)
name = Column(String, nullable=False)
comic_works = relationship("ComicWork")
def get_comics(self) -> Dict[str, List[str]]:
@@ -107,7 +107,7 @@ class Artist(Base, BaseMixin):
class WorkType(Base, BaseMixin):
__tablename__ = "worktype"
name = Column(String(length=255), nullable=False, unique=True)
name = Column(String, nullable=False, unique=True)
comic_works = relationship("ComicWork")
def __repr__(self):
+10 -10
View File
@@ -70,31 +70,31 @@ class MediaFile(Base, BaseMixin, BaseVideoMixin):
class MediaActor(Base, BaseMixin):
__tablename__ = 'media_actor'
name = Column(String(255))
name = Column(String)
media_actor_files = relationship("MediaActorFile")
class MediaActorFile(Base, BaseMixin):
__tablename__ = 'media_actor_file'
media_actor_id = Column(String(255), ForeignKey("media_actor.id"), nullable=False)
media_actor_id = Column(String, ForeignKey("media_actor.id"), nullable=False)
media_actor = relationship("MediaActor", back_populates="media_actor_files")
media_file_id = Column(String(255), ForeignKey("media_file.id"), nullable=True)
media_file_id = Column(String, ForeignKey("media_file.id"), nullable=True)
media_file = relationship("MediaFile", back_populates="media_actor_files")
class MediaArticle(Base, BaseMixin):
__tablename__ = 'media_article'
review = Column(Boolean)
title = Column(String(255))
url = Column(String(255), unique=True)
title = Column(String)
url = Column(String, unique=True)
class MediaVideo(Base, BaseMixin):
__tablename__ = 'media_video'
cloud_link = Column(String(255))
file_name = Column(String(255))
path = Column(String(255))
cloud_link = Column(String)
file_name = Column(String)
path = Column(String)
review = Column(Boolean)
title = Column(String(255))
url = Column(String(255), unique=True)
title = Column(String)
url = Column(String, unique=True)
should_download = Column(Boolean)
+7 -7
View File
@@ -6,7 +6,7 @@ from src.db.models.base import Base, BaseMixin
class MetaDataTable(Base, BaseMixin):
__tablename__ = 'meta_data_table'
table_name = Column(String(255), unique=True)
table_name = Column(String, unique=True)
table_columns = relationship("MetaDataColumn")
def __repr__(self):
@@ -18,15 +18,15 @@ class MetaDataTable(Base, BaseMixin):
class MetaDataColumn(Base, BaseMixin):
__tablename__ = 'meta_data_column'
column_name = Column(String(255), nullable=False)
column_sync_name = Column(String(255))
column_type = Column(String(255))
column_modifier = Column(String(255), nullable=True)
column_name = Column(String, nullable=False)
column_sync_name = Column(String)
column_type = Column(String)
column_modifier = Column(String, nullable=True)
column_order = Column(Integer)
table_id = Column(String, ForeignKey('meta_data_table.id'))
table = relationship("MetaDataTable", back_populates="table_columns")
column_label = Column(String(255))
filter_label = Column(String(255))
column_label = Column(String)
filter_label = Column(String)
is_shown = Column(Boolean)
show_filter = Column(Boolean)
ref_column = Column(String, nullable=True)
+9 -9
View File
@@ -9,15 +9,15 @@ class Sport(Base, BaseMixin):
__table_args__ = (
UniqueConstraint("name"),
)
name = Column(String(255), nullable=False, index=True, unique=True)
name = Column(String, nullable=False, index=True, unique=True)
teams = relationship("Team")
positions = relationship("FieldPosition")
class Team(Base, BaseMixin):
__tablename__ = "team"
name = Column(String(255), nullable=False, index=True, unique=True)
short_name = Column(String(255), nullable=False, )
name = Column(String, nullable=False, index=True, unique=True)
short_name = Column(String, nullable=False, )
sport_id = Column(String, ForeignKey("sport.id"), nullable=False)
sport = relationship("Sport", back_populates="teams")
roosters = relationship("Rooster")
@@ -29,8 +29,8 @@ class FieldPosition(Base, BaseMixin):
UniqueConstraint("name", "sport_id"),
UniqueConstraint("short_name", "sport_id"),
)
name = Column(String(255), nullable=False, index=True)
short_name = Column(String(255), nullable=False)
name = Column(String, nullable=False, index=True)
short_name = Column(String, nullable=False)
sport_id = Column(String, ForeignKey("sport.id"), nullable=False, index=True)
sport = relationship("Sport", back_populates="positions")
roosters = relationship("Rooster")
@@ -41,8 +41,8 @@ class Player(Base, BaseMixin):
__table_args__ = (
UniqueConstraint("first_name", "last_name"),
)
first_name = Column(String(255), nullable=False, index=True)
last_name = Column(String(255), nullable=False, index=True)
first_name = Column(String, nullable=False, index=True)
last_name = Column(String, nullable=False, index=True)
roosters = relationship("Rooster")
def get_full_name(self) -> str:
@@ -66,7 +66,7 @@ class Rooster(Base, BaseMixin):
class Vendor(Base, BaseMixin):
__tablename__ = "vendor"
name = Column(String(255), nullable=False, unique=True, index=True)
name = Column(String, nullable=False, unique=True, index=True)
card_sets = relationship("CardSet")
cards = relationship("Card")
@@ -76,7 +76,7 @@ class CardSet(Base, BaseMixin):
__table_args__ = (
UniqueConstraint("name", "vendor_id"),
)
name = Column(String(255), index=True)
name = Column(String, index=True)
parallel_set = Column(Boolean)
insert_set = Column(Boolean)
vendor_id = Column(String, ForeignKey("vendor.id"), nullable=False, index=True)
+27
View File
@@ -0,0 +1,27 @@
from typing import List, Type
from sqlalchemy.orm import Session
from src.db.models.comic import Artist, Comic
from src.schema.comics.artist import ArtistDetailResponse
def get_artist_details(artist: Artist) -> ArtistDetailResponse:
works = {}
for work in artist.comic_works:
work_type = work.work_type.name
comic_title = work.comic.title
if work_type in works:
works[work_type].append(comic_title)
else:
works[work_type] = [comic_title]
response = ArtistDetailResponse(
id=artist.id,
name=artist.name,
works=works
)
return response
def list_comics(db: Session) -> List[Type[Comic]]:
comics = db.query(Comic).all()
return comics
+2 -2
View File
@@ -1,7 +1,7 @@
from typing import Generator, Annotated
from typing import Generator
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, Session
from sqlalchemy.orm import sessionmaker
from src.core.config import settings
+7 -1
View File
@@ -1,3 +1,5 @@
import logging
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
@@ -7,6 +9,10 @@ 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
def include_router(app: FastAPI):
app.include_router(api_router)
app.include_router(web_app_router)
@@ -18,6 +24,7 @@ def create_tables():
Base.metadata.create_all(bind=engine)
def start_application():
logging.info(f"using database: {settings.DATABASE_URL}")
app = FastAPI(title=settings.PROJECT_NAME, version=settings.PROJECT_VERSION)
include_router(app)
configure_static(app)
@@ -26,4 +33,3 @@ def start_application():
kontor = start_application()
+3 -19
View File
@@ -7,30 +7,14 @@ from src.db.models.comic import Artist
class ArtistCreation(BaseModel):
id: str
name: str
class ArtistResponse(BaseModel):
id: UUID
id: str
name: str
class ArtistDetailResponse(BaseModel):
id: UUID
id: str
name: str
works: Dict[str, List[str]]
def get_artist_details(artist: Artist) -> ArtistDetailResponse:
works = {}
for work in artist.comic_works:
work_type = work.work_type.name
comic_title = work.comic.title
if work_type in works:
works[work_type].append(comic_title)
else:
works[work_type] = [comic_title]
response = ArtistDetailResponse(
id=artist.id,
name=artist.name,
works=works
)
return response
+2 -2
View File
@@ -7,12 +7,12 @@ from src.db.models.comic import Comic
class ComicResponse(BaseModel):
id: UUID
id: str
title: str
completed: bool
class ComicDetailsResponse(BaseModel):
id: UUID
id: str
created: str
title: str
completed : bool
+5 -7
View File
@@ -1,10 +1,8 @@
from uuid import UUID
from fastapi import APIRouter, Request
from fastapi.templating import Jinja2Templates
from src.apis.utils import SessionDep
from src.db.models.comic import Comic, Artist, Publisher
from typing import AnyStr
templates = Jinja2Templates(directory="src/templates")
router = APIRouter(include_in_schema=False, prefix="/comic")
@@ -15,7 +13,7 @@ def get_comics(db: SessionDep, request: Request, msg: str = None):
return templates.TemplateResponse("comic/comics.html", {"request": request, "msg": msg, "comics": comics})
@router.get("/comics/{comic_id}")
def comic_details(comic_id: UUID, request: Request, db: SessionDep):
def comic_details(comic_id: AnyStr, request: Request, db: SessionDep):
comic = db.get(Comic, comic_id)
return templates.TemplateResponse("comic/comic_detail.html", {"request": request, "comic":comic})
@@ -25,8 +23,8 @@ def get_artists(db: SessionDep, request: Request, msg: str = None):
return templates.TemplateResponse("comic/artists.html", {"request": request, "msg": msg, "artists": artists})
@router.get("/artists/{artist_id}")
def artist_detail(artist_id: UUID, request: Request, db: SessionDep):
artist = db.get(Artist, artist_id)
def artist_detail(artist_id: AnyStr, request: Request, db: SessionDep):
artist = db.get(Artist, str(artist_id))
return templates.TemplateResponse("comic/artist_detail.html", {"request": request, "artist": artist})
@router.get("/publishers")
@@ -35,7 +33,7 @@ def get_publishers(db: SessionDep, request: Request, msg: str = None):
return templates.TemplateResponse("comic/publishers.html", {"request": request, "publishers": publishers})
@router.get("/publishers/{publisher_id}")
def publisher_details(publisher_id: UUID, request: Request, db: SessionDep, msg: str = None):
def publisher_details(publisher_id: AnyStr, request: Request, db: SessionDep, msg: str = None):
publisher = db.get(Publisher, publisher_id)
if publisher is None:
msg = "Could not find Publisher"
+100 -6
View File
@@ -3,6 +3,8 @@ copy data from JSON to Postgres
"""
from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter
from pathlib import Path
from typing import Dict, List
from config import get_logger, get_database_cursors
import json
import psycopg2
@@ -29,7 +31,10 @@ def copy_data(postgres_conn, data_file: Path, log):
# result[table] = import_table(table, json_load[table])
truncate_statement = 'TRUNCATE {} CASCADE'.format(table)
#log.info(f"truncate: {truncate_statement}")
postgres_cursor.execute(truncate_statement)
try:
postgres_cursor.execute(truncate_statement)
except:
log.info(f"statement: {insert_statement} FAILED")
items = json_load[table]
for item in items:
#log.info(f"item: {item}")
@@ -39,21 +44,110 @@ def copy_data(postgres_conn, data_file: Path, log):
columns.append(key)
values.append(value)
row = tuple(values)
log.info(f"values: {row}")
#log.info(f"values: {row}")
insert_statement = 'INSERT INTO {}({}) VALUES({})'.format(table, ', '.join(columns), ', '.join(['%s']*len(columns)))
#log.info(f"statement: {insert_statement}")
postgres_cursor.execute(SQL(insert_statement), row)
try:
postgres_cursor.execute(SQL(insert_statement), row)
postgres_conn.commit()
except psycopg2.Error as error:
log.info('insert failed with %s', error)
except:
log.info(f'insert failed with {insert_statement}')
postgres_cursor.execute("SET session_replication_role='origin'")
def load_json(data_file, log) -> dict:
import_file = Path(data_file)
if not import_file.exists():
log.info(f"File {data_file} does not exist. Do nothing.")
return
log.info("read json file")
with open(data_file, 'r') as json_file:
json_load = json.load(json_file)
return json_load
def insert_data(postgres_conn, data: dict, log):
postgres_cursor = postgres_conn.cursor()
log.info("insert data")
table_list = []
#table_list = ['worktype', 'artist', 'publisher', 'volume', 'comic', 'issue', 'story_arc', 'trade_paperback', 'comic_work']
#table_list.extend(['sport', 'team', 'field_position', 'vendor', 'player', 'rooster', 'card_set', 'card'])
#table_list.extend(['card'])
#table_list.extend(['media_file', 'media_video', 'media_actor', 'media_actor_file', 'media_article'])
#table_list.extend(['media_actor_file'])
#table_list.extend(['profile', 'permission', 'token', 'assignment'])
#table_list.extend(['mail', 'mail_account', 'module_data', 'meta_data_table', 'meta_data_column'])
table_list.extend(['meta_data_column'])
#table_list.extend(['book', 'author', 'article', 'bookshelf_publisher', 'book_author', 'article_author'])
#if len(table_list) != 37:
# log.info(f"number of tables incorrect: {len(table_list)}")
# return
for table in table_list:
log.info(f"{table}: {len(data[table])}")
truncate_statement = 'DELETE FROM {}'.format(table)
log.info(f"truncate: {truncate_statement}")
try:
postgres_cursor.execute(truncate_statement)
postgres_conn.commit()
except:
log.info(f"statement: {truncate_statement} FAILED")
items = data[table]
for item in items:
# log.info(f"item: {item}")
values = []
columns = []
for (key, value) in item.items():
columns.append(key)
values.append(value)
row = tuple(values)
# log.info(f"values: {row}")
insert_statement = 'INSERT INTO {}({}) VALUES({})'.format(table, ', '.join(columns),
', '.join(['%s'] * len(columns)))
# log.info(f"statement: {insert_statement}")
try:
postgres_cursor.execute(SQL(insert_statement), row)
postgres_conn.commit()
except:
log.info(f'insert failed with {insert_statement}')
def parse_table_order(data: dict, log):
log.info("parse_table_order")
table_refs: Dict[str, List[str]] = {}
for table in data:
log.info(f"{table}: {len(data[table])}")
items = data[table]
table_refs[table] = []
if len(items) == 0:
continue
item = items[0]
for key, _ in item.items():
if key.endswith('_id'):
ref = key[0:-3]
log.info(f"table {table} has reference to {ref}")
if table in table_refs:
table_refs[table].append(ref)
else:
table_refs[table] = [ref]
log.info(f"parsed refs: {table_refs}")
table_order = []
for table in table_refs:
if len(table_refs[table]) == 0:
log.info(f"insert {table} at beginning")
table_order.insert(0, table)
else:
log.info(f"insert {table} at end")
table_order.append(table)
log.info(f"table_list: {len(table_order)}: {table_order}")
if __name__ == '__main__':
logger = get_logger(args.verbose, args.config)
logger.info('kontor.json_to_postgres started')
_, _, p_conn = get_database_cursors(logger, args.config)
copy_data(p_conn, args.file, logger)
data = load_json(args.file, logger)
#parse_table_order(data, logger)
insert_data(p_conn, data, logger)
#copy_data(p_conn, args.file, logger)
p_conn.close()
logger.info('kontor.json_to_postgres finished')
+6 -6
View File
@@ -40,7 +40,7 @@ class Token(Base, BaseMixin):
class Permission(Base, BaseMixin):
__tablename__ = "permission"
name = Column(String(255), nullable=False)
name = Column(String, nullable=False)
assignments = relationship("Assignment")
@@ -54,17 +54,17 @@ class Assignment(Base, BaseMixin):
class ModuleData(Base, BaseMixin):
__tablename__ = "module_data"
module_name = Column(String(255), nullable=False)
module_name = Column(String, nullable=False)
import_data = Column(Boolean)
class MailAccount(Base, BaseMixin):
__tablename__ = "mail_account"
host = Column(String(255))
host = Column(String)
port = Column(Integer)
protocol = Column(String(255))
user_name = Column(String(255))
password = Column(String(255))
protocol = Column(String)
user_name = Column(String)
password = Column(String)
start_tls = Column(Boolean)
+7 -7
View File
@@ -10,8 +10,8 @@ class Base(DeclarativeBase):
class BaseMixin:
id = Column(String(255), primary_key=True, default=uuid.uuid4())
# id: Mapped[str] = mapped_column(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())
# last_modified_date = Column(DateTime)
@@ -21,10 +21,10 @@ class BaseMixin:
class BaseVideoMixin:
cloud_link = Column(String(255))
file_name = Column(String(255))
path = Column(String(255))
cloud_link = Column(String)
file_name = Column(String)
path = Column(String)
review = Column(Boolean)
title = Column(String(255))
url = Column(String(255), unique=True)
title = Column(String)
url = Column(String, unique=True)
should_download = Column(Boolean)
+6 -6
View File
@@ -6,28 +6,28 @@ from .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")
+8 -8
View File
@@ -6,7 +6,7 @@ from .base import Base, BaseMixin
class Publisher(Base, BaseMixin):
__tablename__ = "publisher"
name = Column(String(length=255), unique=True)
name = Column(String, unique=True)
comics = relationship("Comic")
def __repr__(self):
@@ -18,7 +18,7 @@ class Publisher(Base, BaseMixin):
class Comic(Base, BaseMixin):
__tablename__ = 'comic'
title = Column(String(length=255), unique=True)
title = Column(String, unique=True)
publisher_id = Column(String, ForeignKey('publisher.id'), nullable=False)
publisher = relationship("Publisher", back_populates="comics")
current_order = Column(Boolean)
@@ -38,7 +38,7 @@ class Comic(Base, BaseMixin):
class Volume(Base, BaseMixin):
__tablename__ = "volume"
name = Column(String(length=255), nullable=False)
name = Column(String, nullable=False)
comic_id = Column(String, ForeignKey("comic.id"), nullable=False)
comic = relationship("Comic", back_populates="volumes")
issues = relationship("Issue")
@@ -46,7 +46,7 @@ class Volume(Base, BaseMixin):
class TradePaperback(Base, BaseMixin):
__tablename__ = "trade_paperback"
name = Column(String(length=255), nullable=False)
name = Column(String, nullable=False)
issue_start = Column(Integer)
issue_end = Column(Integer)
comic_id = Column(String, ForeignKey("comic.id"), nullable=False)
@@ -55,14 +55,14 @@ class TradePaperback(Base, BaseMixin):
class StoryArc(Base, BaseMixin):
__tablename__ = "story_arc"
name = Column(String(length=255), nullable=False)
name = Column(String, nullable=False)
comic_id = Column(String, ForeignKey("comic.id"), nullable=False)
comic = relationship("Comic", back_populates="story_arcs")
class Issue(Base, BaseMixin):
__tablename__ = "issue"
issue_number = Column(String(255))
issue_number = Column(String)
in_stock = Column(Boolean)
is_read = Column(Boolean)
comic_id = Column(String, ForeignKey("comic.id"), nullable=False)
@@ -73,13 +73,13 @@ class Issue(Base, BaseMixin):
class Artist(Base, BaseMixin):
__tablename__ = "artist"
name = Column(String(length=255), nullable=False)
name = Column(String, nullable=False)
comic_works = relationship("ComicWork")
class WorkType(Base, BaseMixin):
__tablename__ = "worktype"
name = Column(String(length=255), nullable=False, unique=True)
name = Column(String, nullable=False, unique=True)
comic_works = relationship("ComicWork")
def __repr__(self):
+10 -10
View File
@@ -69,31 +69,31 @@ class MediaFile(Base, BaseMixin, BaseVideoMixin):
class MediaActor(Base, BaseMixin):
__tablename__ = 'media_actor'
name = Column(String(255))
name = Column(String)
media_actor_files = relationship("MediaActorFile")
class MediaActorFile(Base, BaseMixin):
__tablename__ = 'media_actor_file'
media_actor_id = Column(String(255), ForeignKey("media_actor.id"), nullable=False)
media_actor_id = Column(String, ForeignKey("media_actor.id"), nullable=False)
media_actor = relationship("MediaActor", back_populates="media_actor_files")
media_file_id = Column(String(255), ForeignKey("media_file.id"), nullable=True)
media_file_id = Column(String, ForeignKey("media_file.id"), nullable=True)
media_file = relationship("MediaFile", back_populates="media_actor_files")
class MediaArticle(Base, BaseMixin):
__tablename__ = 'media_article'
review = Column(Boolean)
title = Column(String(255))
url = Column(String(255), unique=True)
title = Column(String)
url = Column(String, unique=True)
class MediaVideo(Base, BaseMixin):
__tablename__ = 'media_video'
cloud_link = Column(String(255))
file_name = Column(String(255))
path = Column(String(255))
cloud_link = Column(String)
file_name = Column(String)
path = Column(String)
review = Column(Boolean)
title = Column(String(255))
url = Column(String(255), unique=True)
title = Column(String)
url = Column(String, unique=True)
should_download = Column(Boolean)
+7 -7
View File
@@ -6,7 +6,7 @@ from .base import Base, BaseMixin
class MetaDataTable(Base, BaseMixin):
__tablename__ = 'meta_data_table'
table_name = Column(String(255), unique=True)
table_name = Column(String, unique=True)
table_columns = relationship("MetaDataColumn")
def __repr__(self):
@@ -18,15 +18,15 @@ class MetaDataTable(Base, BaseMixin):
class MetaDataColumn(Base, BaseMixin):
__tablename__ = 'meta_data_column'
column_name = Column(String(255), nullable=False)
column_sync_name = Column(String(255))
column_type = Column(String(255))
column_modifier = Column(String(255), nullable=True)
column_name = Column(String, nullable=False)
column_sync_name = Column(String)
column_type = Column(String)
column_modifier = Column(String, nullable=True)
column_order = Column(Integer)
table_id = Column(String, ForeignKey('meta_data_table.id'))
table = relationship("MetaDataTable", back_populates="table_columns")
column_label = Column(String(255))
filter_label = Column(String(255))
column_label = Column(String)
filter_label = Column(String)
is_shown = Column(Boolean)
show_filter = Column(Boolean)
ref_column = Column(String, nullable=True)
+9 -9
View File
@@ -9,15 +9,15 @@ class Sport(Base, BaseMixin):
__table_args__ = (
UniqueConstraint("name"),
)
name = Column(String(255), nullable=False, index=True, unique=True)
name = Column(String, nullable=False, index=True, unique=True)
teams = relationship("Team")
positions = relationship("FieldPosition")
class Team(Base, BaseMixin):
__tablename__ = "team"
name = Column(String(255), nullable=False, index=True, unique=True)
short_name = Column(String(255), nullable=False, )
name = Column(String, nullable=False, index=True, unique=True)
short_name = Column(String, nullable=False, )
sport_id = Column(String, ForeignKey("sport.id"), nullable=False)
sport = relationship("Sport", back_populates="teams")
roosters = relationship("Rooster")
@@ -29,8 +29,8 @@ class FieldPosition(Base, BaseMixin):
UniqueConstraint("name", "sport_id"),
UniqueConstraint("short_name", "sport_id"),
)
name = Column(String(255), nullable=False, index=True)
short_name = Column(String(255), nullable=False)
name = Column(String, nullable=False, index=True)
short_name = Column(String, nullable=False)
sport_id = Column(String, ForeignKey("sport.id"), nullable=False, index=True)
sport = relationship("Sport", back_populates="positions")
roosters = relationship("Rooster")
@@ -41,8 +41,8 @@ class Player(Base, BaseMixin):
__table_args__ = (
UniqueConstraint("first_name", "last_name"),
)
first_name = Column(String(255), nullable=False, index=True)
last_name = Column(String(255), nullable=False, index=True)
first_name = Column(String, nullable=False, index=True)
last_name = Column(String, nullable=False, index=True)
roosters = relationship("Rooster")
def get_full_name(self) -> str:
@@ -66,7 +66,7 @@ class Rooster(Base, BaseMixin):
class Vendor(Base, BaseMixin):
__tablename__ = "vendor"
name = Column(String(255), nullable=False, unique=True, index=True)
name = Column(String, nullable=False, unique=True, index=True)
card_sets = relationship("CardSet")
cards = relationship("Card")
@@ -76,7 +76,7 @@ class CardSet(Base, BaseMixin):
__table_args__ = (
UniqueConstraint("name", "vendor_id"),
)
name = Column(String(255), index=True)
name = Column(String, index=True)
parallel_set = Column(Boolean)
insert_set = Column(Boolean)
vendor_id = Column(String, ForeignKey("vendor.id"), nullable=False, index=True)
@@ -66,4 +66,9 @@ public class MetaDataColumn extends AbstractEntity {
}
return "no changes for " + this.getId() + "\n";
}
@Override
public String toString() {
return "MetaDataColumn{id=" + getId() + ", columnName=" + columnName + ", table=" + table.getTableName() +'}';
}
}
@@ -12,6 +12,8 @@ public interface MetaDataColumnRepository extends JpaRepository<MetaDataColumn,
List<MetaDataColumn> findByTable(MetaDataTable table);
List<MetaDataColumn> findByTableAndColumnName(MetaDataTable table, String column_name);
@Query("select m from MetaDataColumn m " +
"where lower(m.columnName) like lower(concat('%', :searchTerm, '%')) or lower(m.columnLabel) like lower(concat('%', :searchTerm, '%'))")
List<MetaDataColumn> search(@Param("searchTerm") String searchTerm);
@@ -67,61 +67,73 @@ public class MetaDataService {
this.getColumn(table, columnName, columnSyncName, columnType, columnModifier, columnOrder, isShown, columnLabel, showFilter, filterLabel, null);
}
public void checkColumnValues(MetaDataTable table, MetaDataColumn column, String columnName, String columnSyncName, String columnType, String columnModifier, Integer columnOrder, Boolean isShown, String columnLabel, Boolean showFilter, String filterLabel, String refColumn) {
log.debug("Column {} with name {} of table {} found, check Values", columnOrder, columnName, table.getTableName());
if (!column.getColumnName().equals(columnName)) {
log.debug("columnName has to be changed to {}", columnName);
column.setColumnName(columnName);
}
if (!column.getColumnSyncName().equals(columnSyncName)) {
log.debug("columnSyncName has to be changed to {}", columnSyncName);
column.setColumnSyncName(columnSyncName);
}
if (!column.getColumnType().equals(columnType)) {
log.debug("columnType has to be changed to {}", columnType);
column.setColumnType(columnType);
}
if (columnModifier != null && !columnModifier.equals(column.getColumnModifier())) {
log.debug("columnModifier has to be changed to {}", columnModifier);
column.setColumnModifier(columnModifier);
}
if (isShown != null && !isShown.equals(column.getIsShown())) {
log.debug("isShown has to be change to {}}", isShown);
column.setIsShown(isShown);
}
if (columnLabel != null && !columnLabel.equals(column.getColumnLabel())) {
log.debug("columnLabel has to be change to {}}", columnLabel);
column.setColumnLabel(columnLabel);
}
if (showFilter != null &&!showFilter.equals(column.getShowFilter())) {
log.debug("showFilter has to be change to {}}", showFilter);
column.setShowFilter(showFilter);
}
if (filterLabel != null && !filterLabel.equals(column.getFilterLabel())) {
log.debug("filterLabel has to be change to {}}", filterLabel);
column.setFilterLabel(filterLabel);
}
if (refColumn != null && !refColumn.equals(column.getRefColumn())) {
log.debug("refColumn has to be change to {}}", filterLabel);
column.setRefColumn(refColumn);
}
saveMetaDataColumn(column);
}
public void getColumn(MetaDataTable table, String columnName, String columnSyncName, String columnType, String columnModifier, Integer columnOrder, Boolean isShown, String columnLabel, Boolean showFilter, String filterLabel, String refColumn) {
if (table.getTableColumns().stream().anyMatch(column -> column.getColumnName().equals(columnName))) {
log.debug("Column {} with name {} of table {} found, check Values", columnOrder, columnName, table.getTableName());
MetaDataColumn column = table.getTableColumns().get(columnOrder.intValue()-1);
if (!column.getColumnName().equals(columnName)) {
log.debug("columnName has to be changed to {}", columnName);
column.setColumnName(columnName);
}
if (!column.getColumnSyncName().equals(columnSyncName)) {
log.debug("columnSyncName has to be changed to {}", columnSyncName);
column.setColumnSyncName(columnSyncName);
}
if (!column.getColumnType().equals(columnType)) {
log.debug("columnType has to be changed to {}", columnType);
column.setColumnType(columnType);
}
if (columnModifier != null && !columnModifier.equals(column.getColumnModifier())) {
log.debug("columnModifier has to be changed to {}", columnModifier);
column.setColumnModifier(columnModifier);
}
if (isShown != null && !isShown.equals(column.getIsShown())) {
log.debug("isShown has to be change to {}}", isShown);
column.setIsShown(isShown);
}
if (columnLabel != null && !columnLabel.equals(column.getColumnLabel())) {
log.debug("columnLabel has to be change to {}}", columnLabel);
column.setColumnLabel(columnLabel);
}
if (showFilter != null &&!showFilter.equals(column.getShowFilter())) {
log.debug("showFilter has to be change to {}}", showFilter);
column.setShowFilter(showFilter);
}
if (filterLabel != null && !filterLabel.equals(column.getFilterLabel())) {
log.debug("filterLabel has to be change to {}}", filterLabel);
column.setFilterLabel(filterLabel);
}
if (refColumn != null && !refColumn.equals(column.getRefColumn())) {
log.debug("refColumn has to be change to {}}", filterLabel);
column.setRefColumn(refColumn);
}
metaDataColumnRepository.save(column);
log.info("check if column {} of table {} exists", columnName, table.getTableName());
boolean columnNameExists = table.getTableColumns().stream().anyMatch(column -> column.getColumnName().equals(columnName));
boolean columnOrderExists = table.getTableColumns().get(columnOrder-1) != null;
List<MetaDataColumn> metaDataColumns = metaDataColumnRepository.findByTableAndColumnName(table, columnName);
log.debug("column found: name: {}, order: {}: table.columns: {}", columnNameExists, columnOrderExists, metaDataColumns);
if (columnOrderExists) {
MetaDataColumn column = table.getTableColumns().get(columnOrder-1);
checkColumnValues(table, column, columnName, columnSyncName, columnType, columnModifier, columnOrder, isShown, columnLabel, showFilter, filterLabel, refColumn);
} else {
log.info("Column {} of table {} not found, will create it", columnName, table.getTableName());
MetaDataColumn column = new MetaDataColumn();
column.setTable(table);
column.setColumnName(columnName);
column.setColumnSyncName(columnSyncName);
column.setColumnType(columnType);
column.setColumnModifier(columnModifier);
column.setColumnOrder(columnOrder);
column.setIsShown(Boolean.FALSE);
metaDataColumnRepository.save(column);
//addColumn(table,columnName,columnSyncName,columnType,columnModifier,columnOrder,isShown);
}
}
private void addColumn(MetaDataTable table, String columnName, String columnSyncName, String columnType, String columnModifier, Integer columnOrder, Boolean isShown) {
MetaDataColumn column = new MetaDataColumn();
column.setTable(table);
column.setColumnName(columnName);
column.setColumnSyncName(columnSyncName);
column.setColumnType(columnType);
column.setColumnModifier(columnModifier);
column.setColumnOrder(columnOrder);
column.setIsShown(isShown);
saveMetaDataColumn(column);
}
public List<MetaDataColumn> findAllMetaDataColumns(String stringFilter) {
if (stringFilter == null || stringFilter.isEmpty()) {
@@ -118,7 +118,7 @@ public class IssueView extends VerticalLayout {
menuButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
ColumnToggleContextMenu<Issue> columnToggleContextMenu = new ColumnToggleContextMenu<>(menuButton);
columnToggleContextMenu.addColumnToggleItem(idColumn);
columnToggleContextMenu.addColumnToggleItem(createdColumn);
columnToggleContextMenu.addColumnToggleItem(createdColumn);
columnToggleContextMenu.addColumnToggleItem(modifiedColumn);
columnToggleContextMenu.addColumnToggleItem(versionColumn);
columnToggleContextMenu.addColumnToggleItem(titleColumn);
@@ -38,4 +38,8 @@ public class CardSet extends AbstractEntity {
private boolean parallelSet = false;
private boolean insertSet = false;
public String getVendorName() {
return vendor.getName();
}
}
@@ -21,7 +21,6 @@ import lombok.ToString;
*/
@Getter
@Setter
@ToString
@EqualsAndHashCode(callSuper=false)
@Entity
@Table(indexes = {@Index(columnList = "name")},
@@ -35,4 +34,9 @@ public class Vendor extends AbstractEntity {
@OneToMany(fetch = FetchType.EAGER, mappedBy = "vendor")
@Nullable
private List<CardSet> cardSets;
@Override
public String toString() {
return "Vendor{id=" + this.getId() + ", name=" + name + '}';
}
}
@@ -1,9 +1,12 @@
package de.thpeetz.kontor.tysc.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;
@@ -13,12 +16,13 @@ import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.spring.annotation.SpringComponent;
import de.thpeetz.kontor.common.views.MainLayout;
import de.thpeetz.kontor.tysc.TyscConstants;
import de.thpeetz.kontor.tysc.data.CardSet;
import de.thpeetz.kontor.tysc.services.CardService;
import jakarta.annotation.security.PermitAll;
import java.util.*;
@SpringComponent
@Scope("prototype")
@PermitAll
@@ -26,8 +30,26 @@ import jakarta.annotation.security.PermitAll;
@PageTitle("CardSet | Tysc | Kontor")
public class CardSetView extends VerticalLayout {
Grid<CardSet> grid = new Grid<>(CardSet.class);
@Getter
Grid<CardSet> grid = new Grid<>(CardSet.class, false);
Grid.Column<CardSet> idColumn = grid.addColumn(CardSet::getId)
.setHeader("ID").setResizable(true).setSortable(true);
Grid.Column<CardSet> createdColumn = grid.addColumn(CardSet::getCreatedDate)
.setHeader("Erstellt").setResizable(true).setSortable(true);
Grid.Column<CardSet> modifiedColumn = grid.addColumn(CardSet::getLastModifiedDate)
.setHeader("Geändert").setResizable(true).setSortable(true);
Grid.Column<CardSet> versionColumn = grid.addColumn(CardSet::getVersion)
.setHeader("Version").setResizable(true).setSortable(true);
Grid.Column<CardSet> nameColumn = grid.addColumn(CardSet::getName)
.setHeader("Comic").setResizable(true).setSortable(true);
Grid.Column<CardSet> vendorNameColumn = grid.addColumn(CardSet::getVendorName)
.setHeader("Hersteller").setResizable(true).setSortable(true);
Grid.Column<CardSet> parallelSetColumn = grid.addComponentColumn(parallelSet -> StatusIcon.create(parallelSet.isParallelSet()))
.setHeader("Parallelset?").setWidth("6rem").setSortable(true);
Grid.Column<CardSet> insertSetColumn = grid.addComponentColumn(insertSet -> StatusIcon.create(insertSet.isInsertSet()))
.setHeader("Parallelset?").setWidth("6rem").setSortable(true);
TextField filterText = new TextField();
@Getter
CardSetForm form;
CardService service;
@@ -42,22 +64,18 @@ public class CardSetView extends VerticalLayout {
updateList();
}
public Grid<CardSet> getGrid() {
return grid;
}
private void configureGrid() {
grid.addClassName("cardSet-grid");
grid.setSizeFull();
grid.setColumns("name", "vendor.name", "parallelSet", "insertSet");
//grid.setColumns("name", "vendor.name", "parallelSet", "insertSet");
grid.getColumns().forEach(col -> col.setAutoWidth(true));
idColumn.setVisible(false);
createdColumn.setVisible(false);
modifiedColumn.setVisible(false);
versionColumn.setVisible(false);
grid.asSingleSelect().addValueChangeListener(event -> editCardSet(event.getValue()));
}
public CardSetForm getForm() {
return form;
}
private void configureForm() {
form = new CardSetForm();
form.setWidth("25em");
@@ -67,18 +85,6 @@ public class CardSetView extends VerticalLayout {
form.addCloseListener(e -> closeEditor());
}
private void saveCardSet(CardSetForm.SaveEvent event) {
service.saveCardSet(event.getCardSet());
updateList();
closeEditor();
}
private void deleteCardSet(CardSetForm.DeleteEvent event) {
service.deleteCardSet(event.getCardSet());
updateList();
closeEditor();
}
private Component getContent() {
HorizontalLayout content = new HorizontalLayout(grid, form);
content.setFlexGrow(2, grid);
@@ -97,11 +103,34 @@ public class CardSetView extends VerticalLayout {
Button addCardSetButton = new Button("Add cardSet");
addCardSetButton.addClickListener(click -> addCardSet());
HorizontalLayout toolbar = new HorizontalLayout(filterText, addCardSetButton);
Button menuButton = new Button("Show/Hide Columns");
menuButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
ColumnToggleContextMenu<CardSet> columnToggleContextMenu = new ColumnToggleContextMenu<>(menuButton);
columnToggleContextMenu.addColumnToggleItem(idColumn);
columnToggleContextMenu.addColumnToggleItem(createdColumn);
columnToggleContextMenu.addColumnToggleItem(modifiedColumn);
columnToggleContextMenu.addColumnToggleItem(versionColumn);
columnToggleContextMenu.addColumnToggleItem(nameColumn);
columnToggleContextMenu.addColumnToggleItem(vendorNameColumn);
columnToggleContextMenu.addColumnToggleItem(parallelSetColumn);
columnToggleContextMenu.addColumnToggleItem(insertSetColumn);
HorizontalLayout toolbar = new HorizontalLayout(filterText, addCardSetButton, menuButton);
toolbar.addClassName("toolbar");
return toolbar;
}
private void saveCardSet(CardSetForm.SaveEvent event) {
service.saveCardSet(event.getCardSet());
updateList();
closeEditor();
}
private void deleteCardSet(CardSetForm.DeleteEvent event) {
service.deleteCardSet(event.getCardSet());
updateList();
closeEditor();
}
public void editCardSet(CardSet cardSet) {
if (cardSet == null) {
closeEditor();
@@ -51,7 +51,6 @@ public class CardView extends VerticalLayout {
.setHeader("Inserts").setWidth("6rem").setSortable(true);
Grid.Column<Card> parallelSetColumn = grid.addComponentColumn(card -> StatusIcon.create(card.getCardSet().isParallelSet()))
.setHeader("Parallels").setWidth("6rem").setSortable(true);
Grid.Column<Card> playerColumn = grid.addColumn(Card::getPlayerName).setHeader("Spieler").setResizable(true).setSortable(true);
TextField filterText = new TextField();
@Getter
@@ -128,9 +127,10 @@ public class CardView extends VerticalLayout {
columnToggleContextMenu.addColumnToggleItem(yearColumn);
columnToggleContextMenu.addColumnToggleItem(vendorColumn);
columnToggleContextMenu.addColumnToggleItem(cardSetColumn);
columnToggleContextMenu.addColumnToggleItem(insertSetColumn);
columnToggleContextMenu.addColumnToggleItem(parallelSetColumn);
columnToggleContextMenu.addColumnToggleItem(playerColumn);
HorizontalLayout toolbar = new HorizontalLayout(filterText, addCardButton, menuButton);
toolbar.addClassName("toolbar");
return toolbar;
}