Skip to content
Snippets Groups Projects
Commit 6af1376e authored by Thomas Schneider's avatar Thomas Schneider
Browse files

views/schild: Form design rework

Rather large refactoring:
- A bunch of CSS so that it doesn’t look like ass;
- Roll our own (WT)Forms, as wtforms-sqlalchemy is too limited.  Drop that one
  completely;
- Deduplicate code from /schild/<ident> and /create;
- As SchildForm.{image, template} are now RadioFields, iterate directly via
  their .choices property instead of passing templates/images separately to the
  template;
- … and some minor shenanigans as usual.
parent 6da157fb
No related branches found
No related tags found
No related merge requests found
...@@ -128,7 +128,7 @@ main { ...@@ -128,7 +128,7 @@ main {
} }
.preview { .preview {
width: 95vw; width: min(95vw, 100rem);
/* A4 aspect ratio: √2:1 */ /* A4 aspect ratio: √2:1 */
aspect-ratio: 1.4142135623730951; aspect-ratio: 1.4142135623730951;
...@@ -143,3 +143,68 @@ main { ...@@ -143,3 +143,68 @@ main {
.preview-label { .preview-label {
place-self: center; place-self: center;
} }
form {
display: inline-block;
}
@label-padding: 90px;
label.for-text {
display: inline-block;
min-width: @label-padding;
text-align: right;
}
input[type="text"], textarea {
width: 300px;
}
textarea {
height: 5em;
vertical-align: top;
}
fieldset {
display: flex;
flex-wrap: wrap;
}
fieldset > div {
display: flex;
gap: 0.5rem;
flex-direction: column;
}
.imageselect > div {
width: 10rem;
display: flex;
gap: 0.5rem;
}
fieldset label {
flex-grow: 1;
}
.imageselect img {
width: 100%;
background-color: #fff;
}
fieldset input {
display: none;
}
fieldset img,
fieldset iframe,
.preview-small {
border: 3px solid lightgray;
border-radius: 10px;
}
input[type="radio"]:checked {
& + label > img,
& + iframe {
border:3px solid red;
}
}
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
groups = ["default", "dev"] groups = ["default", "dev"]
strategy = ["inherit_metadata"] strategy = ["inherit_metadata"]
lock_version = "4.5.0" lock_version = "4.5.0"
content_hash = "sha256:4254d9a7fba04b352e6ddf4d85e4ce62abbf60c4d39127493dd18cb4b4dbc110" content_hash = "sha256:618addcae7a6ab39fb74f18ca29c1a64f762aea504459d0db220bc7e886efe59"
[[metadata.targets]] [[metadata.targets]]
requires_python = ">=3.12" requires_python = ">=3.12"
...@@ -1304,21 +1304,6 @@ files = [ ...@@ -1304,21 +1304,6 @@ files = [
{file = "wtforms-3.1.2.tar.gz", hash = "sha256:f8d76180d7239c94c6322f7990ae1216dae3659b7aa1cee94b6318bdffb474b9"}, {file = "wtforms-3.1.2.tar.gz", hash = "sha256:f8d76180d7239c94c6322f7990ae1216dae3659b7aa1cee94b6318bdffb474b9"},
] ]
[[package]]
name = "wtforms-sqlalchemy"
version = "0.4.1"
requires_python = ">=3.8"
summary = "SQLAlchemy tools for WTForms"
groups = ["default"]
dependencies = [
"SQLAlchemy>=1.4",
"WTForms>=3.1",
]
files = [
{file = "WTForms-SQLAlchemy-0.4.1.tar.gz", hash = "sha256:370f52b738527cf6d8ab78d3488afb9342666144da2637e0cf8b5ae522e8a4db"},
{file = "WTForms_SQLAlchemy-0.4.1-py3-none-any.whl", hash = "sha256:df3965015b60de172f4b35e691a47d9913d56bb0abb62c620edd04c1376979e3"},
]
[[package]] [[package]]
name = "zopfli" name = "zopfli"
version = "0.2.3" version = "0.2.3"
......
...@@ -15,7 +15,6 @@ dependencies = [ ...@@ -15,7 +15,6 @@ dependencies = [
"flask", "flask",
"flask-sqlalchemy", "flask-sqlalchemy",
"flask-wtf", "flask-wtf",
"wtforms-sqlalchemy",
"uuid7", "uuid7",
"psycopg", "psycopg",
"flask-weasyprint", "flask-weasyprint",
......
...@@ -2,6 +2,8 @@ from . import db ...@@ -2,6 +2,8 @@ from . import db
from .models import Schild from .models import Schild
from .helpers import Blueprint, get_template_attribute from .helpers import Blueprint, get_template_attribute
from collections import namedtuple
from pathlib import Path
from flask import url_for from flask import url_for
...@@ -19,7 +21,6 @@ bp = Blueprint( ...@@ -19,7 +21,6 @@ bp = Blueprint(
@bp.route("/schild/<ident>.html") @bp.route("/schild/<ident>.html")
def schild_html(ident): def schild_html(ident):
schild = db.get_or_404(Schild, ident) schild = db.get_or_404(Schild, ident)
# raise Exception
return bp.render_template(schild.template, schild=schild) return bp.render_template(schild.template, schild=schild)
...@@ -38,13 +39,16 @@ def sample_pdf(template): ...@@ -38,13 +39,16 @@ def sample_pdf(template):
return render_pdf(url_for(".sample_html", template=template)) return render_pdf(url_for(".sample_html", template=template))
Template = namedtuple("Template", "name description")
def list_templates(): def list_templates():
schild = Schild() schild = Schild()
loader = bp.jinja_loader loader = bp.jinja_loader
for t in loader.list_templates(): for t in loader.list_templates():
if t.startswith("_"): if t.startswith("_"):
continue continue
yield dict( yield Template(
name=t, name=t,
description=get_template_attribute( description=get_template_attribute(
bp.real_template_name(t), bp.real_template_name(t),
...@@ -53,3 +57,9 @@ def list_templates(): ...@@ -53,3 +57,9 @@ def list_templates():
default=None, default=None,
), ),
) )
def list_images():
base = Path(bp.static_folder) / "img"
for f in base.iterdir():
yield f.name
...@@ -5,6 +5,10 @@ from uuid_extensions import uuid7 ...@@ -5,6 +5,10 @@ from uuid_extensions import uuid7
from sqlalchemy import String, Text, Uuid from sqlalchemy import String, Text, Uuid
from sqlalchemy.orm import Mapped, mapped_column from sqlalchemy.orm import Mapped, mapped_column
from flask_wtf import FlaskForm
from wtforms import fields, validators
from . import db from . import db
...@@ -14,3 +18,25 @@ class Schild(db.Model): ...@@ -14,3 +18,25 @@ class Schild(db.Model):
text: Mapped[str] = mapped_column(Text) text: Mapped[str] = mapped_column(Text)
image: Mapped[str] = mapped_column(String(255), nullable=True) image: Mapped[str] = mapped_column(String(255), nullable=True)
template: Mapped[str] = mapped_column(String(255)) template: Mapped[str] = mapped_column(String(255))
class SchildForm(FlaskForm):
title = fields.StringField(
"Titel",
validators=[
validators.InputRequired(),
validators.Length(max=Schild.title.type.length),
],
)
text = fields.TextAreaField(
"Text",
validators=[validators.InputRequired()],
)
image = fields.RadioField(
"Bild",
validators=[validators.Optional()],
)
template = fields.RadioField(
"Vorlage",
validators=[validators.DataRequired()],
)
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
{{ field }} {{ field }}
{%- else -%} {%- else -%}
<div class="box"> <div class="box">
{{ field.label }} {{ field.label(class="for-text") }}
{{ field(class="input-dispatch") }} {{ field(class="input-dispatch") }}
</div> </div>
{%- endif -%} {%- endif -%}
...@@ -21,16 +21,57 @@ ...@@ -21,16 +21,57 @@
{% block main -%} {% block main -%}
<form method="post" action=""> <form method="post" action="">
{%- for field in form -%} {{ form.csrf_token }}
{{ render_field(field) }}
{%- endfor -%} {{ render_field(form.title) }}
<select name="template"> {{ render_field(form.text) }}
{%- for t in templates -%}
<option value="{{ t.name }}"> <fieldset class="templateselect">
<legend>Vorlage</legend>
{%- for t in form.template.choices -%}
<div>
<input
type="radio"
name="template"
id="template:{{ t.name }}"
value="{{ t.name }}"
{% if t.name == (schild | default(None)).template -%}
checked
{%- endif -%}
/>
<iframe
class="preview preview-small"
src="{{ url_for('instance.sample_html', template=t.name) }}"
id="template-preview:{{ t.name }}"
>
</iframe>
<label for="template:{{ t.name }}" class="preview-label">
{{ t.description or t.name }} {{ t.description or t.name }}
</option> </label>
</div>
{% endfor %} {% endfor %}
</select> </fieldset>
<fieldset class="imageselect">
<legend>Bild</legend>
{%- for img in form.image.choices -%}
<div>
<input
type="radio"
name="image"
id="img:{{ img }}"
value="{{ img }}"
{% if img == (schild | default(None)).image -%}
checked
{%- endif -%}
/>
<label for="img:{{ img }}">
<img src="{{ url_for('instance.static', filename='img/'+img) }}" title="{{ img }}" />
</label>
</div>
{%- endfor -%}
</fieldset>
<div class="box"> <div class="box">
{%- if schild -%} {%- if schild -%}
<input type="submit" value="Speichern" /> <input type="submit" value="Speichern" />
...@@ -44,6 +85,7 @@ ...@@ -44,6 +85,7 @@
{%- endif -%} {%- endif -%}
</div> </div>
</form> </form>
{%- if schild %} {%- if schild %}
<div> <div>
<a href="{{ url_for('instance.schild_pdf', ident=schild.ident) }}"> <a href="{{ url_for('instance.schild_pdf', ident=schild.ident) }}">
...@@ -56,7 +98,8 @@ ...@@ -56,7 +98,8 @@
<iframe <iframe
class="preview" class="preview"
id="preview" id="preview"
src="{{ url_for('instance.schild_html', ident=schild.ident) }}" /> src="{{ url_for('instance.schild_html', ident=schild.ident) }}">
</iframe>
</div> </div>
</div> </div>
{% endif %} {% endif %}
......
from . import db from . import db
from .instance import list_templates from .instance import list_templates, list_images
from .models import Schild from .models import Schild, SchildForm
from flask import Blueprint, render_template, request, redirect, url_for from flask import Blueprint, render_template, request, redirect, url_for
from flask_wtf import FlaskForm
from wtforms_sqlalchemy.orm import model_form
bp = Blueprint("views", __name__) bp = Blueprint("views", __name__)
SchildForm = model_form(Schild, base_class=FlaskForm)
@bp.route("/") @bp.route("/")
def index(): def index():
schilder = db.session.execute(db.select(Schild)).scalars() schilder = db.session.execute(db.select(Schild)).scalars()
return render_template("index.html.j2", schilder=schilder) return render_template("index.html.j2", schilder=schilder)
@bp.route("/schild/<ident>", methods=["GET", "POST"]) def _do_schild_form(schild):
def schild(ident):
schild = db.get_or_404(Schild, ident)
if request.method == "POST":
form = SchildForm(request.form, obj=schild) form = SchildForm(request.form, obj=schild)
if form.validate(): form.image.choices = list(list_images())
form.template.choices = list(list_templates())
if request.method == "POST" and form.validate():
form.populate_obj(schild) form.populate_obj(schild)
db.session.add(schild) db.session.add(schild)
db.session.commit() db.session.commit()
else: return form
form = SchildForm(obj=schild)
return render_template("schild.html.j2", schild=schild, form=form, templates=list_templates())
@bp.route("/schild/<ident>", methods=["GET", "POST"])
def schild(ident):
schild = db.get_or_404(Schild, ident)
form = _do_schild_form(schild)
return render_template(
"schild.html.j2",
schild=schild,
form=form,
)
@bp.route("/create", methods=["GET", "POST"]) @bp.route("/create", methods=["GET", "POST"])
def create(): def create():
schild = Schild() schild = Schild()
form = _do_schild_form(schild)
if request.method == "POST": if request.method == "POST":
form = SchildForm(request.form, obj=schild)
if form.validate():
form.populate_obj(schild)
db.session.add(schild)
db.session.commit()
return redirect(url_for(".schild", ident=schild.ident)) return redirect(url_for(".schild", ident=schild.ident))
else: else:
form = SchildForm(obj=schild) return render_template(
return render_template("schild.html.j2", form=form) "schild.html.j2",
form=form,
)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment