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

Save

parent 06e5ce33
No related branches found
No related tags found
No related merge requests found
Showing
with 567 additions and 237 deletions
...@@ -4,7 +4,7 @@ MENUS = { ...@@ -4,7 +4,7 @@ MENUS = {
"NAV_MENU_CORE": [ "NAV_MENU_CORE": [
{ {
"name": _("Kort"), "name": _("Kort"),
"url": "empty", "url": "test_pdf",
"root": True, "root": True,
"validators": [ "validators": [
"menu_generator.validators.is_authenticated", "menu_generator.validators.is_authenticated",
......
...@@ -3,7 +3,8 @@ from django.utils.translation import gettext as _ ...@@ -3,7 +3,8 @@ from django.utils.translation import gettext as _
from aleksis.core.mixins import ExtensibleModel from aleksis.core.mixins import ExtensibleModel
from aleksis.core.models import Person from aleksis.core.models import Person
from django.template import Template, Context
from django.core.exceptions import ValidationError
class CardPrinterStatus(models.TextChoices): class CardPrinterStatus(models.TextChoices):
ONLINE = "online", _("Online") ONLINE = "online", _("Online")
...@@ -36,6 +37,36 @@ class CardPrinter(ExtensibleModel): ...@@ -36,6 +37,36 @@ class CardPrinter(ExtensibleModel):
verbose_name_plural = _("Card printers") verbose_name_plural = _("Card printers")
class CardLayout(ExtensibleModel):
name = models.CharField(max_length=255, verbose_name=_("Name"))
template = models.TextField(verbose_name=_("Template"))
css = models.TextField(verbose_name=_("Custom CSS"), blank=True)
def get_template(self) -> Template:
return Template(self.template)
def render(self, card: "Card"):
t = self.get_template()
context = card.get_context()
return t.render(Context(context))
def validate_template(self):
try:
t = Template(self.template)
t.render(Context())
except Exception as e:
raise ValidationError(_("Template is invalid: {}").format(e))
class Card(ExtensibleModel): class Card(ExtensibleModel):
person = models.ForeignKey( person = models.ForeignKey(
Person, models.CASCADE, verbose_name=_("Person"), related_name="cards" Person, models.CASCADE, verbose_name=_("Person"), related_name="cards"
...@@ -64,6 +95,13 @@ class Card(ExtensibleModel): ...@@ -64,6 +95,13 @@ class Card(ExtensibleModel):
else: else:
return PrintStatus.REGISTERED return PrintStatus.REGISTERED
def get_context(self):
return {
"person": self.person,
"chip_number": self.chip_number,
"valid_until": self.valid_until,
}
class Meta: class Meta:
verbose_name = _("Card") verbose_name = _("Card")
verbose_name_plural = _("Cards") verbose_name_plural = _("Cards")
aleksis/apps/kort/static/kort/background.png

32.7 KiB

Image diff could not be displayed: it is too large. Options to address this: view the blob.
aleksis/apps/kort/static/kort/logo-background.png

133 KiB

aleksis/apps/kort/static/kort/logo.jpg

41.9 KiB

aleksis/apps/kort/static/kort/logo.png

123 KiB

aleksis/apps/kort/static/kort/signature.png

12.1 KiB

aleksis/apps/kort/static/kort/stamp.png

62.9 KiB

aleksis/apps/kort/static/kort/test.png

32.7 KiB

{% extends 'core/base.html' %}
{% load i18n %}
{% block content %}
<p class="flow-text">
{% blocktrans %}Kort (manage student IDs){% endblocktrans %}
</p>
{% endblock %}
{# -*- 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">
Mustermann
</div>
</div>
<div class="info-item">
<div class="info-title">
Vornamen/Given names/Prénoms
</div>
<div class="info-value">
Max
</div>
</div>
<div class="info-item">
<div class="info-title">
Wohnort/Residence/Résidence
</div>
<div class="info-value">
Musterstraße 1, 12345 Musterstadt
</div>
</div>
<div class="info-item">
<div class="info-title">
Geburtsdatum/Date of birth/Date de naissance
</div>
<div class="info-value">
01.01.2007
</div>
</div>
</div>
</div>
<div class="front-footer">
{% generate_barcode "55846268859" %}
<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 %}
from django import template
from io import BytesIO
import barcode
from django.utils.safestring import mark_safe
register = template.Library()
# Adapted by https://stackoverflow.com/questions/62244670/print-barcode-in-pdf-with-django
@register.simple_tag
def generate_barcode(uid):
rv = BytesIO()
writer = barcode.writer.SVGWriter()
code = barcode.get('code128', uid, writer=writer)
code.write(rv, options={"module_height": 5, "module_width": 0.3, "text_distance": 2, "font_size": 6})
rv.seek(0)
# get rid of the first bit of boilerplate
rv.readline()
rv.readline()
rv.readline()
rv.readline()
# read the svg tag into a string
svg = rv.read()
return mark_safe(svg.decode("utf-8"))
...@@ -3,5 +3,5 @@ from django.urls import path ...@@ -3,5 +3,5 @@ from django.urls import path
from . import views from . import views
urlpatterns = [ urlpatterns = [
path("empty", views.empty, name="empty"), path("test", views.TestPDFView.as_view(), name="test_pdf"),
] ]
from aleksis.core.views import RenderPDFView
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.http import HttpRequest, HttpResponse from django.http import HttpRequest, HttpResponse
from django.shortcuts import render from django.shortcuts import render
...@@ -8,3 +9,16 @@ def empty(request: HttpRequest) -> HttpResponse: ...@@ -8,3 +9,16 @@ def empty(request: HttpRequest) -> HttpResponse:
context = {} context = {}
return render(request, "kort/empty.html", context) return render(request, "kort/empty.html", context)
class TestPDFView(RenderPDFView):
template_name = "kort/pdf.html"
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
context = self.get_context_data(**kwargs)
return render(request, self.template_name, context)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["title"] = "Test PDF"
return context
\ No newline at end of file
This diff is collapsed.
...@@ -26,6 +26,7 @@ secondary = true ...@@ -26,6 +26,7 @@ secondary = true
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = "^3.9" python = "^3.9"
aleksis-core = "^2.0" aleksis-core = "^2.0"
python-barcode = "^0.13.1"
[tool.poetry.dev-dependencies] [tool.poetry.dev-dependencies]
aleksis-builddeps = "^5" aleksis-builddeps = "^5"
......
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