Skip to content
Snippets Groups Projects
Commit b12e2c6c authored by Jonathan Weth's avatar Jonathan Weth :keyboard:
Browse files

Merge branch '11-allow-editing-layout' into 'main'

Resolve "Allow editing layout"

Closes #11

See merge request !5
parents 29da3623 8e3f8149
No related branches found
No related tags found
1 merge request!5Resolve "Allow editing layout"
Pipeline #81637 failed
Showing with 316 additions and 248 deletions
{# -*- engine:django -*- #}
{% extends "core/base.html" %}
{% load material_form i18n any_js %}
{% block browser_title %}{% blocktrans %}Card Layout{% endblocktrans %}{% endblock %}
{% block page_title %}{{ object.name }}{% endblock %}
{% block content %}
<a class="btn waves-effect waves-light secondary margin-bottom" href="{% url "card_layouts" %}">
<i class="material-icons left iconify" data-icon="mdi:arrow-left"></i>
{% trans "Back to all layouts" %}
</a>
<a class="btn waves-effect waves-light margin-bottom orange modal-trigger"
href="{% url "edit_card_layout" object.pk %}">
<i class="material-icons left iconify" data-icon="mdi:pencil-outline"></i>
{% trans "Edit" %}
</a>
<a class="btn waves-effect waves-light margin-bottom red modal-trigger" href="{% url "delete_card_layout" object.pk %}">
<i class="material-icons left iconify" data-icon="mdi:delete-outline"></i>
{% trans "Delete" %}
</a>
{% include "kort/card_layout/detail_content.html" with card_layout=object %}
{% endblock %}
{% load i18n %}
<div class="row no-margin">
<div class="col s12 m12 l12 xl6 no-padding">
<div class="card">
<div class="card-content">
<div class="card-title">{% trans "Layout details" %}</div>
<table>
<tr>
<th>
<i class="material-icons left iconify" data-icon="mdi:label-outline"></i>
{% trans "Name" %}</th>
<td>{{ card_layout.name }}</td>
</tr>
<tr>
<th>
<i class="material-icons left iconify" data-icon="mdi:image-size-select-large"></i>
{% trans "Size (w × h)" %}
</th>
<td>{{ card_layout.width }} mm × {{ card_layout.height }} mm</td>
</tr>
<tr>
<th>
<i class="material-icons left iconify" data-icon="mdi:folder-multiple-image"></i>
{% trans "Media files" %}
</th>
<td>
<ul class="collection">
{% for file in card_layout.media_files.all %}
<a class="collection-item" href="{{ file.media_file.url }}">{{ file.media_file.name }}</a>
{% empty %}
<li class="collection-item">{% trans "No media files uploaded." %}</li>
{% endfor %}
</ul>
</td>
</tr>
</table>
</div>
</div>
</div>
<div class="col s12 m12 l12 xl6">
<div class="card">
<div class="card-content">
<div class="card-title">{% trans "Template" %}</div>
<pre>{{ card_layout.template|linebreaksbr }}</pre>
</div>
</div>
<div class="card">
<div class="card-content">
<div class="card-title">{% trans "Custom CSS" %}</div>
<pre>{{ card_layout.css|linebreaksbr }}</pre>
</div>
</div>
</div>
</div>
\ No newline at end of file
{# -*- engine:django -*- #}
{% extends "core/base.html" %}
{% load material_form i18n any_js %}
{% block extra_head %}
{{ form.media.css }}
{% include_css "select2-materialize" %}
{% endblock %}
{% block browser_title %}{% blocktrans %}Edit card layout{% endblocktrans %}{% endblock %}
{% block page_title %}{% blocktrans %}Edit card layout{% endblocktrans %}{% endblock %}
{% block content %}
{% include "kort/card_layout/instructions.html" %}
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
{% form form=form %}{% endform %}
<h5>{% trans "Media Files" %}</h5>
{{ formset.management_form }}
{% for inline_form in formset.forms %}
{% form form=inline_form %}{% endform %}
{% endfor %}
{% include "core/partials/save_button.html" %}
</form>
{% include_js "select2-materialize" %}
{{ form.media.js }}
{% endblock %}
{% load i18n %}
<div class="card">
<div class="card-content">
<div class="card-title">{% trans "Instructions on templating the card" %}</div>
{% blocktrans %}
You will be able to use the following data in your individual template. The template has to been written in the
Django template language.
{% endblocktrans %}
<div class="collection">
<div class="collection-item">
<code>{% verbatim %}{{ chip_number }}{% endverbatim %}</code> - {% trans "The chip number as string" %}
</div>
<div class="collection-item">
<code>{% verbatim %}{{ person.x }}{% endverbatim %}</code>
- {% trans "This will give you access to any attributes of the person object like name, personal data or contact data." %}
</div>
<div class="collection-item">
<code>{% verbatim %}{{ valid_until }}{% endverbatim %}</code>
- {% trans "The date until when the card is valid" %}
</div>
<div class="collection-item">
<code>{% verbatim %}
{{ media_files.0.media_file.url }}{% endverbatim %}</code> -
{% trans "This will give you access to the uploaded media files. Replace 0 by the number of your media file (starting with 0)." %}
</div>
</div>
</div>
</div>
{# -*- engine:django -*- #}
{% extends "core/base.html" %}
{% load i18n rules material_form any_js %}
{% load render_table from django_tables2 %}
{% block browser_title %}{% blocktrans %}Card layouts{% endblocktrans %}{% endblock %}
{% block page_title %}{% blocktrans %}Card layouts{% endblocktrans %}{% endblock %}
{% block content %}
{% has_perm 'core.create_cardlayout_rule' user person as can_create %}
{% if can_create %}
<a class="btn green waves-effect waves-light" href="{% url 'create_card_layout' %}">
<i class="material-icons left iconify" data-icon="mdi:plus"></i>
{% trans "Create new card layout" %}
</a>
{% endif %}
{% render_table table %}
{% endblock %}
{% load i18n %}
<table>
<tr>
<th>{% trans "Name" %}</th>
<td>{{ card_layout.name }}</td>
</tr>
</table>
\ No newline at end of file
{# -*- engine:django -*- #}
{% extends "core/base_simple_print.html" %}
{% load i18n static barcode %}
{% block size %}
{% with width=86 height=55 %}
{{ block.super }}
{% endwith %}
{% endblock %}
{% block browser_title %}{% blocktrans %}Test PDF generation{% endblocktrans %}{% endblock %}
{% block page_title %}{% blocktrans %}Test PDF generation{% endblocktrans %}{% endblock %}
{% block extra_head %}
<style>
@import url('https://fonts.googleapis.com/css2?family=Cabin:ital,wght@0,400;0,500;0,600;0,700;1,400;1,500;1,600;1,700&display=swap');
body {
font-family: 'Cabin', sans-serif;
}
.front-header {
margin-left: 1mm;
margin-right: 2mm;
margin-top: 1mm;
}
.front-header {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: flex-start;
}
.front-logo {
width: 36mm;
}
.front-header .title .top-title {
font-weight: 700;
font-size: 13.5pt;
text-align: right;
line-height: 1.4;
}
.front-header .title .bottom-title {
font-weight: 500;
font-size: 8.1pt;
text-align: right;
line-height: 1;
}
.front-picture {
width: 23mm;
margin-left: 3mm;
margin-right: 3mm;
}
.front-main {
margin-top: 1mm;
display: flex;
flex-direction: row;
justify-content: stretch;
align-items: flex-start;
}
.front-left {
display: flex;
flex-direction: column;
align-items: center;
}
svg {
float: left;
}
.info-item {
margin-bottom: 1mm;
}
.info-title {
font-size: 6.5pt;
line-height: 1;
}
.info-value {
font-size: 9pt;
}
.front-footer {
display: flex;
flex-direction: row;
justify-content: flex-start;
}
.stamp img {
height: 10mm;
width: 10mm;
}
.signature {
display: flex;
flex-direction: column;
align-items: center;
margin-top: -1mm;
}
.signature img {
height: 6mm;
width: auto;
}
.stamp {
position: absolute;
right: 2mm;
bottom: 2mm;
}
.front-footer .signature {
font-size: 6pt;
}
.front {
background-color: white;
background-image: url('{% static 'kort/logo-background.png' %}');
background-repeat: no-repeat;
background-size: contain;
background-position-y: center;
background-position-x: right;
}
.back {
background-image: url('{% static 'kort/building.png' %}');
background-repeat: no-repeat;
background-size: cover;
background-position-y: center;
background-position-x: center;
}
.back .back-logo {
position: absolute;
top: 2mm;
left: 2mm;
width: 30mm;
}
.back .back-text {
position: absolute;
top: 34mm;
left: 2mm;
right: 2mm;
color: white;
text-align: center;
line-height: 1.2;
font-size: 9pt;
font-weight: 500;
}
</style>
{% endblock %}
{% block content %}
<section class="sheet front">
<div class="front-header">
<img src="{% static "kort/logo.png" %}" class="front-logo">
<div class="title">
<div class="top-title">Schüler:innenausweis</div>
<div class="bottom-title">Student ID Card / Carte de Collégien</div>
</div>
</div>
<div class="front-main">
<div class="front-left">
<img src="https://picsum.photos/id/1074/200/270" class="front-picture">
</div>
<div class="front-right">
<div class="info-item">
<div class="info-title">
Name/Surname/Nom
</div>
<div class="info-value">
{{ person.last_name }}
</div>
</div>
<div class="info-item">
<div class="info-title">
Vornamen/Given names/Prénoms
</div>
<div class="info-value">
{{ person.first_name }} {{ person.additional_name }}
</div>
</div>
<div class="info-item">
<div class="info-title">
Wohnort/Residence/Résidence
</div>
<div class="info-value">
{{ person.street }} {{ person.housenumber }}, {{ person.postal_code }} {{ person.place }}
</div>
</div>
<div class="info-item">
<div class="info-title">
Geburtsdatum/Date of birth/Date de naissance
</div>
<div class="info-value">
{{ person.date_of_birth|date:"SHORT_DATE_FORMAT" }}
</div>
</div>
</div>
</div>
<div class="front-footer">
{% generate_barcode chip_number %}
<div class="signature">
Lübeck, den {% now "SHORT_DATE_FORMAT" %}
<img src="{% static "kort/signature.png" %}">
</div>
<div class="stamp">
<img src="{% static "kort/stamp.png" %}">
</div>
</div>
</section>
<section class="sheet back">
<img src="{% static "kort/logo.png" %}" class="back-logo">
<div class="back-text">
Katharineum zu Lübeck <br>
Städtisches Gymnasium mit altsprachlichen Zweig<br>
<br>
Königstraße 27-31 <br>
23552 Lübeck
</div>
</section>
{% endblock %}
{% load material_form material_form_internal %}
{% part bound_field.field %}<{{ field.widget.component|default:'dmc-textarea' }}>
<div class="row">
<div{% attrs bound_field 'group' %}
id="id_{{ bound_field.html_name }}_container"
class="input-field col s12{% if field.required %} required{% endif %}{% if bound_field.errors %} has-error{% endif %}"
{% endattrs %}>
{% part field label %}
<label{% attrs bound_field 'label' %}
for="{{ bound_field.id_for_label }}"
{% if bound_field.value %}class="active"{% endif %}
{% endattrs %}>{{ bound_field.label }}</label>
{% endpart %}
<div class="margin-top-35">
{% part field prefix %}{% endpart %}{% part field control %}
{{ bound_field }}
{% endpart %}
</div>
{% part field help_text %}{% if field.help_text %}
<div class="help-block">{{ bound_field.help_text|safe }}</div>
{% endif %}
{% endpart %}{% part field errors %}
{% if bound_field.errors %}
{% include 'material/field_errors.html' %}
{% endif %}
{% endpart %}{{ hidden_initial }}
</div>
</div></{{ field.widget.component|default:'dmc-textarea' }}>{% endpart %}
...@@ -29,6 +29,15 @@ urlpatterns = [ ...@@ -29,6 +29,15 @@ urlpatterns = [
views.CardPrinterConfigView.as_view(), views.CardPrinterConfigView.as_view(),
name="card_printer_config", name="card_printer_config",
), ),
path("layouts/", views.CardLayoutListView.as_view(), name="card_layouts"),
path("layouts/create/", views.CardLayoutCreateView.as_view(), name="create_card_layout"),
path("layouts/<int:pk>/", views.CardLayoutDetailView.as_view(), name="card_layout"),
path("layouts/<int:pk>/edit/", views.CardLayoutEditView.as_view(), name="edit_card_layout"),
path(
"layouts/<int:pk>/delete/",
views.CardLayoutDeleteView.as_view(),
name="delete_card_layout",
),
path("api/v1/printers/", api.CardPrinterDetails.as_view(), name="api_card_printer"), path("api/v1/printers/", api.CardPrinterDetails.as_view(), name="api_card_printer"),
path( path(
"api/v1/printers/<int:pk>/status/", "api/v1/printers/<int:pk>/status/",
......
...@@ -13,9 +13,15 @@ from django_tables2 import SingleTableView ...@@ -13,9 +13,15 @@ from django_tables2 import SingleTableView
from reversion.views import RevisionMixin from reversion.views import RevisionMixin
from rules.contrib.views import PermissionRequiredMixin from rules.contrib.views import PermissionRequiredMixin
from aleksis.apps.kort.forms import CardForm, CardPrinterForm, PrinterSelectForm from aleksis.apps.kort.forms import (
from aleksis.apps.kort.models import Card, CardPrinter, PrintStatus CardForm,
from aleksis.apps.kort.tables import CardPrinterTable, CardTable CardLayoutForm,
CardLayoutMediaFileFormSet,
CardPrinterForm,
PrinterSelectForm,
)
from aleksis.apps.kort.models import Card, CardLayout, CardPrinter, PrintStatus
from aleksis.apps.kort.tables import CardLayoutTable, CardPrinterTable, CardTable
from aleksis.core.mixins import AdvancedCreateView, AdvancedDeleteView, AdvancedEditView from aleksis.core.mixins import AdvancedCreateView, AdvancedDeleteView, AdvancedEditView
from aleksis.core.util.celery_progress import render_progress_page from aleksis.core.util.celery_progress import render_progress_page
from aleksis.core.views import RenderPDFView from aleksis.core.views import RenderPDFView
...@@ -99,13 +105,20 @@ class CardPrintView(PermissionRequiredMixin, RevisionMixin, SingleObjectMixin, V ...@@ -99,13 +105,20 @@ class CardPrintView(PermissionRequiredMixin, RevisionMixin, SingleObjectMixin, V
printer = self.request.GET.get("printer") printer = self.request.GET.get("printer")
printer = get_object_or_404(CardPrinter, pk=printer) printer = get_object_or_404(CardPrinter, pk=printer)
self.object.print_card(printer) try:
messages.success( job = self.object.print_card(printer)
request, messages.success(
_( request,
"The print job for the card {} on the printer {} has been created successfully." _(
).format(self.object.person, printer.name), "The print job #{} for the card {} on "
) "the printer {} has been created successfully."
).format(job.pk, self.object.person, printer.name),
)
except ValueError as e:
messages.error(
request,
_("The print job couldn't be started because of the following error: {}").format(e),
)
return redirect(self.success_url) return redirect(self.success_url)
...@@ -215,6 +228,89 @@ class CardPrinterDetailView(PermissionRequiredMixin, RevisionMixin, DetailView): ...@@ -215,6 +228,89 @@ class CardPrinterDetailView(PermissionRequiredMixin, RevisionMixin, DetailView):
return context return context
class CardLayoutFormMixin:
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
self.formset = CardLayoutMediaFileFormSet(
self.request.POST or None, self.request.FILES or None, instance=self.object
)
context["formset"] = self.formset
return context
def post(self, request, *args, **kwargs):
self.object = self.get_object()
context = self.get_context_data(**kwargs)
form = self.get_form()
if form.is_valid() and self.formset.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
def form_valid(self, form):
self.object = form.save()
self.formset.instance = self.object
self.formset.save()
return super().form_valid(form)
class CardLayoutListView(PermissionRequiredMixin, RevisionMixin, SingleTableView):
"""List view for all card layouts."""
permission_required = "core.view_cardlayouts_rule"
template_name = "kort/card_layout/list.html"
model = CardLayout
table_class = CardLayoutTable
class CardLayoutCreateView(
PermissionRequiredMixin, CardLayoutFormMixin, RevisionMixin, AdvancedCreateView
):
"""View used to create a card layout."""
permission_required = "core.create_cardlayout_rule"
template_name = "kort/card_layout/create.html"
form_class = CardLayoutForm
model = CardLayout
success_message = _("The card layout has been created successfully.")
def get_success_url(self):
return reverse("card_layout", args=[self.object.pk])
def get_object(self):
return None
class CardLayoutEditView(
PermissionRequiredMixin, CardLayoutFormMixin, RevisionMixin, AdvancedEditView
):
"""View used to edit a card layout."""
permission_required = "core.edit_cardlayout_rule"
template_name = "kort/card_layout/edit.html"
form_class = CardLayoutForm
model = CardLayout
success_message = _("The card layout has been changed successfully.")
def get_success_url(self):
return reverse("card_layout", args=[self.object.pk])
class CardLayoutDeleteView(PermissionRequiredMixin, RevisionMixin, AdvancedDeleteView):
"""View used to delete a card layout."""
permission_required = "core.delete_cardlayout_rule"
success_url = reverse_lazy("card_layouts")
template_name = "kort/card_layout/delete.html"
model = CardLayout
success_message = _("The card layout has been deleted successfully.")
class CardLayoutDetailView(PermissionRequiredMixin, RevisionMixin, DetailView):
permission_required = "core.view_cardlayout_rule"
model = CardLayout
template_name = "kort/card_layout/detail.html"
class CardPrinterConfigView(PermissionRequiredMixin, RevisionMixin, SingleObjectMixin, View): class CardPrinterConfigView(PermissionRequiredMixin, RevisionMixin, SingleObjectMixin, View):
permission_required = "core.view_cardprinter_rule" permission_required = "core.view_cardprinter_rule"
model = CardPrinter model = CardPrinter
......
...@@ -27,6 +27,7 @@ secondary = true ...@@ -27,6 +27,7 @@ secondary = true
python = "^3.9" python = "^3.9"
aleksis-core = "^2.8" aleksis-core = "^2.8"
python-barcode = "^0.14.0" python-barcode = "^0.14.0"
django-ace = "^1.0.12"
[tool.poetry.dev-dependencies] [tool.poetry.dev-dependencies]
aleksis-builddeps = "*" aleksis-builddeps = "*"
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment