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
11 changed files with 160 additions and 28 deletions
Showing only changes of commit b4a0c2d7a5 - Show all commits
+19 -7
View File
@@ -1,6 +1,6 @@
import uuid
from datetime import datetime
from typing import Dict, List, Optional
from typing import Dict, List, Optional, Any
from natsort import natsorted
from sqlalchemy import Column, ForeignKey, Integer, String, Boolean, func
from sqlalchemy.orm import relationship, Mapped, mapped_column
@@ -48,10 +48,10 @@ class Comic(Base, BaseMixin):
def __str__(self):
return f'{self.title}({self.id})'
def get_artists(self) -> Dict[str, List[str]]:
works: Dict[str, List[str]] = {}
def get_artists(self) -> Dict[Any, List[Any]]:
works: Dict[Any, List[Any]] = {}
for work in self.comic_works:
work_type = work.work_type.name
work_type = work.work_type
artist = work.artist
if work_type in works:
works[work_type].append(artist)
@@ -107,6 +107,18 @@ class Issue(Base, BaseMixin):
story_arc = relationship("StoryArc", back_populates="issues")
issue_works = relationship("IssueWork")
def get_artists(self) -> Dict[Any, List[Any]]:
works: Dict[Any, List[Any]] = {}
for work in self.issue_works:
work_type = work.work_type
artist = work.artist
if work_type in works:
works[work_type].append(artist)
else:
works[work_type] = [artist]
return works
class Artist(Base, BaseMixin):
__tablename__ = "artist"
name = Column(String, nullable=False)
@@ -114,10 +126,10 @@ class Artist(Base, BaseMixin):
comic_works = relationship("ComicWork")
issue_works = relationship("IssueWork")
def get_comics(self) -> Dict[str, List[str]]:
works: Dict[str, List[str]] = {}
def get_comics(self) -> Dict[Any, List[Comic]]:
works: Dict[Any, List[Comic]] = {}
for work in self.comic_works:
work_type = work.work_type.name
work_type = work.work_type
comic = work.comic
if work_type in works:
works[work_type].append(comic)
@@ -29,7 +29,7 @@
<td colspan="2">
{% for work in artist.get_comics() %}
<p>
{{work}}:
<a href="/comic/worktypes/{{work.id}}">{{work.name}}</a>
<ul>
{% for comic in artist.get_comics()[work] %}
<li><a href="/comic/comics/{{comic.id}}">{{comic.title}}</a></li>
@@ -47,6 +47,10 @@
<th scope="row">Data Modified</th>
<td colspan="2">{{artist.last_modified_date}}</td>
</tr>
<tr>
<th scope="row">Data Version</th>
<td colspan="2">{{artist.version}}</td>
</tr>
</tbody>
</table>
</div>
@@ -36,7 +36,7 @@
<td colspan="2">
{% for work in comic.get_artists() %}
<p>
{{work}}:
<a href="/comic/worktypes/{{work.id}}">{{work.name}}</a>
<ul>
{% for artist in comic.get_artists()[work] %}
<li><a href="/comic/artists/{{artist.id}}">{{artist.name}}</a></li>
+8 -2
View File
@@ -10,9 +10,15 @@
{% endwith %}
<div class="container">
<div class="row">
<div class="col">
<form class="d-flex" action="/comic/comics/">
<input class="form-control me-2" name="query" id="autocomplete" type="search" placeholder="Search" aria-label="Search">
Completed<input type="checkbox" name="completed" {% if request.query_params.get("completed")=="on" %}checked{% endif %} aria-label="Completed">
Order<input type="checkbox" name="order" {% if request.query_params.get("order")=="on" %}checked{% endif %} aria-label="Order">
<button class="btn btn-outline-success" type="submit">Search</button>
</form>
</div>
<div class="row">
<h1 class="display-5">Comics..</h1>
</div>
</div>
<div class="row">
<table class="table table-hover">
@@ -50,12 +50,29 @@
<a href="/comic/comics/{{issue.comic_id}}">{{issue.comic.title}}</a>
</td>
</tr>
{% if issue.volume %}
<tr>
<th scope="row">Volume</th>
<td colspan="2">
<a href="/comic/comics/{{issue.volume_id}}">{{issue.volume.name}}</a>
</td>
</tr>
{% endif %}
<tr>
<th scope="row">Works</th>
<td colspan="2">
{% for work in issue.get_artists() %}
<p>
<a href="/comic/worktypes/{{work.id}}">{{work.name}}</a>
<ul>
{% for artist in issue.get_artists()[work] %}
<li><a href="/comic/artists/{{artist.id}}">{{artist.name}}</a></li>
{% endfor %}
</ul>
</p>
{% endfor %}
</td>
</tr>
<tr>
<th scope="row">Data Created</th>
<td colspan="2">{{issue.created_date}}</td>
+18 -1
View File
@@ -10,7 +10,24 @@ router = APIRouter(include_in_schema=False, prefix="/comic")
@router.get("/comics")
def get_comics(db: SessionDep, request: Request, msg: str | None = None):
comics = db.query(Comic).all()
params = request.query_params
query = params.get("query")
filter = {}
completed = params.get('completed') == "on"
if completed:
filter['completed'] = True
order = params.get("order") == "on"
if order:
filter['current_order'] = True
if query is not None and len(query) > 0:
filter['title'] = query
if len(filter) > 0:
if "title" in filter:
comics = db.query(Comic).filter(Comic.title.ilike(f'%{query}%'))
else:
comics = db.query(Comic).filter_by(**filter).all()
else:
comics = db.query(Comic).all()
return templates.TemplateResponse("comic/comics.html", {"request": request, "msg": msg, "comics": comics})
@router.get("/comics/{comic_id}")
@@ -70,6 +70,18 @@ public class Issue extends AbstractEntity {
return null;
}
public String getFullTitle() {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(this.getComic().getTitle());
stringBuilder.append(" #");
stringBuilder.append(this.getIssueNumber());
if (this.title !=null && !this.title.isEmpty()) {
stringBuilder.append(": ");
stringBuilder.append(this.title);
}
return stringBuilder.toString();
}
@Override
public String toString() {
StringBuilder result = new StringBuilder();
@@ -16,7 +16,10 @@ import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.binder.BeanValidationBinder;
import com.vaadin.flow.data.binder.Binder;
import de.thpeetz.kontor.comics.data.*;
import de.thpeetz.kontor.comics.data.Comic;
import de.thpeetz.kontor.comics.data.Issue;
import de.thpeetz.kontor.comics.data.IssueWork;
import de.thpeetz.kontor.comics.data.Volume;
import de.thpeetz.kontor.common.views.YearMonthField;
import lombok.extern.slf4j.Slf4j;
@@ -14,12 +14,13 @@ import com.vaadin.flow.data.binder.BeanValidationBinder;
import com.vaadin.flow.data.binder.Binder;
import de.thpeetz.kontor.comics.data.Artist;
import de.thpeetz.kontor.comics.data.Issue;
import de.thpeetz.kontor.comics.data.Comic;
import de.thpeetz.kontor.comics.data.IssueWork;
import de.thpeetz.kontor.comics.data.Worktype;
import de.thpeetz.kontor.common.views.ComicIssueField;
public class IssueWorkForm extends FormLayout {
ComboBox<Issue> issue = new ComboBox<>("Issue");
ComicIssueField issue = new ComicIssueField("Issue");
ComboBox<Artist> artist = new ComboBox<>("Artist");
ComboBox<Worktype> workType = new ComboBox<>("Worktype");
@@ -29,12 +30,13 @@ public class IssueWorkForm extends FormLayout {
Binder<IssueWork> binder = new BeanValidationBinder<>(IssueWork.class);
public IssueWorkForm(List<Issue> issues, List<Artist> artists, List<Worktype> workTypes) {
public IssueWorkForm(List<Comic> comics, List<Artist> artists, List<Worktype> workTypes) {
addClassName("issuework-form");
binder.bindInstanceFields(this);
issue.setItems(issues);
issue.setItemLabelGenerator(Issue::getIssueNumber);
issue.setComics(comics);
//issue.setItems(issues);
//issue.setItemLabelGenerator(Issue::getIssueNumber);
artist.setItems(artists);
artist.setItemLabelGenerator(Artist::getName);
workType.setItems(workTypes);
@@ -1,6 +1,8 @@
package de.thpeetz.kontor.comics.views;
import de.thpeetz.kontor.comics.data.Issue;
import de.thpeetz.kontor.comics.data.IssueWork;
import lombok.Getter;
import org.springframework.context.annotation.Scope;
import com.vaadin.flow.component.Component;
@@ -24,7 +26,9 @@ import jakarta.annotation.security.PermitAll;
@PageTitle("IssueWork | Comics | Kontor")
public class IssueWorkView extends VerticalLayout {
@Getter
Grid<IssueWork> grid = new Grid<>(IssueWork.class);
@Getter
IssueWorkForm form;
ComicService service;
@@ -39,24 +43,16 @@ public class IssueWorkView extends VerticalLayout {
updateList();
}
public Grid<IssueWork> getGrid() {
return grid;
}
private void configureGrid() {
grid.addClassName("issue-grid");
grid.setSizeFull();
grid.setColumns("issue.title", "artist.name", "workType.name");
grid.setColumns("issue.fullTitle", "artist.name", "workType.name");
grid.getColumns().forEach(col -> col.setAutoWidth(true));
grid.asSingleSelect().addValueChangeListener(event -> editIssueWork(event.getValue()));
}
public IssueWorkForm getForm() {
return form;
}
private void configureForm() {
form = new IssueWorkForm(service.findAllIssues(), service.findAllArtists(null),
form = new IssueWorkForm(service.findAllComics(null), service.findAllArtists(null),
service.findAllWorktypes(null));
form.setWidth("25em");
form.setVisible(false);
@@ -0,0 +1,63 @@
package de.thpeetz.kontor.common.views;
import com.vaadin.flow.component.Unit;
import com.vaadin.flow.component.customfield.CustomField;
import com.vaadin.flow.component.select.Select;
import lombok.extern.slf4j.Slf4j;
import de.thpeetz.kontor.comics.data.Comic;
import de.thpeetz.kontor.comics.data.Issue;
import java.util.List;
@Slf4j
public class ComicIssueField extends CustomField<Issue> {
public final Select<Comic> comic = new Select<>();
public final Select<Issue> issue = new Select<>();
public ComicIssueField(String caption) {
comic.setEnabled(false);
comic.setWidth(9, Unit.EM);
comic.setItemLabelGenerator(Comic::getTitle);
comic.addValueChangeListener(e -> {
updateIssues();
});
issue.setEnabled(false);
issue.setWidth(9, Unit.EM);
issue.setItemLabelGenerator(Issue::getIssueNumber);
add(comic, issue);
}
@Override
protected Issue generateModelValue() {
log.info("ComicIssueField.generateModelValue: {}", issue.getValue());
return issue.getValue();
}
@Override
protected void setPresentationValue(Issue newPresentationValue) {
log.info("ComicIssueField.setPresentationValue({})", newPresentationValue);
if (newPresentationValue == null) return;
comic.setValue(newPresentationValue.getComic());
log.info("setPresentationValue: set Comic select = {}", comic.getValue());
issue.setValue(newPresentationValue);
}
public void setComics(List<Comic> comics) {
log.info("ComicIssueField.setComics");
comic.setItems(comics);
comic.setEnabled(true);
}
private void updateIssues() {
log.info("ComicIssueField.updateIssues");
if (comic.getValue() == null) {
issue.setValue(null);
issue.setEnabled(false);
return;
}
issue.setItems(comic.getValue().getIssues());
issue.setEnabled(true);
}
}