diff --git a/aleksis/apps/kort/api.py b/aleksis/apps/kort/api.py new file mode 100644 index 0000000000000000000000000000000000000000..08f403e7db51585966dc0689a0fcb404bf544696 --- /dev/null +++ b/aleksis/apps/kort/api.py @@ -0,0 +1,72 @@ +from django.contrib import admin +from django.utils import timezone + +from oauth2_provider.contrib.rest_framework import TokenHasScope +from rest_framework import generics, permissions, serializers +from rest_framework.permissions import BasePermission + +from aleksis.apps.kort.models import CardPrinter + +admin.autodiscover() + + +class CorrectPrinterPermission(BasePermission): + """Check whether the OAuth2 application belongs to the printer.""" + + def has_object_permission(self, request, view, obj) -> bool: + token = request.auth + if token.application == obj.oauth2_application: + return True + return False + + +class CardPrinterSerializer(serializers.ModelSerializer): + class Meta: + model = CardPrinter + fields = ( + "id", + "name", + "description", + "location", + "status", + "status_label", + "status_color", + "status_icon", + "status_text", + "last_seen_at", + ) + + +class CardPrinterStatusSerializer(serializers.ModelSerializer): + class Meta: + model = CardPrinter + fields = ("status", "status_text") + + +class CardPrinterDetails(generics.RetrieveAPIView): + """Show details about the card printer.""" + + permission_classes = [permissions.IsAuthenticated, TokenHasScope, CorrectPrinterPermission] + required_scopes = ["card_printer"] + serializer_class = CardPrinterSerializer + queryset = CardPrinter.objects.all() + + def get_object(self): + token = self.request.auth + return token.application.card_printers.all().first() + + +class CardPrinterUpdateStatus(generics.UpdateAPIView): + """Update the status of the card printer.""" + + permission_classes = [permissions.IsAuthenticated, TokenHasScope, CorrectPrinterPermission] + required_scopes = ["card_printer"] + serializer_class = CardPrinterStatusSerializer + queryset = CardPrinter.objects.all() + + def update(self, request, *args, **kwargs): + r = super().update(request, *args, **kwargs) + instance = self.get_object() + instance.last_seen_at = timezone.now() + instance.save() + return r diff --git a/aleksis/apps/kort/apps.py b/aleksis/apps/kort/apps.py index 797ede1bc9d1a9db4456910879452dccfc6bdf85..437a5bb8e682058629a89254713ccd3fba0a1468 100644 --- a/aleksis/apps/kort/apps.py +++ b/aleksis/apps/kort/apps.py @@ -1,6 +1,3 @@ -from django.apps import apps -from django.db import models -from django.db.models import functions from django.utils.translation import gettext as _ from aleksis.core.util.apps import AppConfig @@ -25,18 +22,4 @@ class DefaultConfig(AppConfig): @classmethod def get_all_scopes(cls) -> dict[str, str]: """Return all OAuth scopes and their descriptions for this app.""" - Card = apps.get_model("kort", "Card") - label_prefix = _("Access and manage printer status and print jobs") - scopes = dict( - Card.objects.annotate( - scope=functions.Concat( - models.Value(f"{Card.SCOPE_PREFIX}_"), - models.F("pk"), - output_field=models.CharField(), - ), - label=functions.Concat(models.Value(f"{label_prefix}: "), models.F("name")), - ) - .values_list("scope", "label") - .distinct() - ) - return scopes + return {"card_printer": _("Access and manage printer status and print jobs")} diff --git a/aleksis/apps/kort/forms.py b/aleksis/apps/kort/forms.py index 6036332b97911d095c50df5b7fec2078f5f5435f..fdf0c520613b565cc3a46e6499b96dff38008c8b 100644 --- a/aleksis/apps/kort/forms.py +++ b/aleksis/apps/kort/forms.py @@ -2,7 +2,7 @@ from django import forms from django_select2.forms import ModelSelect2Widget -from aleksis.apps.kort.models import Card +from aleksis.apps.kort.models import Card, CardPrinter class CardForm(forms.ModelForm): @@ -19,3 +19,9 @@ class CardForm(forms.ModelForm): attrs={"data-minimum-input-length": 0, "class": "browser-default"}, ), } + + +class CardPrinterForm(forms.ModelForm): + class Meta: + model = CardPrinter + fields = ["name", "location", "description"] diff --git a/aleksis/apps/kort/menus.py b/aleksis/apps/kort/menus.py index 812fb3894999177feb8f04c7b2644c635756e5e1..2116a83244bdcf51f4870a6b21c3c193d4cd8fb6 100644 --- a/aleksis/apps/kort/menus.py +++ b/aleksis/apps/kort/menus.py @@ -23,6 +23,17 @@ MENUS = { ) ], }, + { + "name": _("Card Printers"), + "url": "card_printers", + "svg_icon": "mdi:printer-outline", + "validators": [ + ( + "aleksis.core.util.predicates.permission_validator", + "core.view_cardprinters_rule", + ) + ], + }, ], } ] diff --git a/aleksis/apps/kort/models.py b/aleksis/apps/kort/models.py index 11e2e03d521202339c1c5f155b6a05d3b97ee60d..0dcbf56d9f3d94d220e86d78ca41e8a6e397bbbe 100644 --- a/aleksis/apps/kort/models.py +++ b/aleksis/apps/kort/models.py @@ -1,5 +1,6 @@ -from typing import Union +from typing import Any, Union +from django.conf import settings from django.core.exceptions import ValidationError from django.core.validators import FileExtensionValidator from django.db import models @@ -19,6 +20,31 @@ class CardPrinterStatus(models.TextChoices): WITH_ERRORS = "with_errors", _("With errors") NOT_REGISTERED = "not_registered", _("Not registered") + @classmethod + def get_color(cls, value): + _colors = { + CardPrinterStatus.ONLINE.value: "green", + CardPrinterStatus.OFFLINE.value: "red", + CardPrinterStatus.WITH_ERRORS.value: "orange", + CardPrinterStatus.NOT_REGISTERED.value: "grey", + } + return _colors.get(value) + + @classmethod + def get_icon(cls, value): + _icons = { + CardPrinterStatus.ONLINE.value: "mdi:printer-check", + CardPrinterStatus.OFFLINE.value: "mdi:printer-off", + CardPrinterStatus.WITH_ERRORS.value: "mdi:printer-alert", + CardPrinterStatus.NOT_REGISTERED.value: "mdi:printer-search", + } + return _icons.get(value) + + @classmethod + def get_label(cls, value): + _labels = {x: y for x, y in cls.choices} + return _labels.get(value) + class PrintStatus(models.TextChoices): REGISTERED = "registered", _("Registered") @@ -47,6 +73,7 @@ class CardPrinter(ExtensibleModel): verbose_name=_("OAuth2 application"), blank=True, null=True, + related_name="card_printers", ) def save(self, *args, **kwargs): @@ -55,6 +82,7 @@ class CardPrinter(ExtensibleModel): client_type=OAuthApplication.CLIENT_CONFIDENTIAL, authorization_grant_type=OAuthApplication.GRANT_AUTHORIZATION_CODE, name=f"Card printer: {self.name}", + redirect_uris="urn:ietf:wg:oauth:2.0:oob", ) application.save() self.oauth2_application = application @@ -64,6 +92,35 @@ class CardPrinter(ExtensibleModel): def __str__(self): return self.name + @property + def status_label(self) -> str: + """Return the verbose name of the status.""" + return CardPrinterStatus.get_label(self.status) + + @property + def status_color(self) -> str: + """Return a color for the status.""" + return CardPrinterStatus.get_color(self.status) + + @property + def status_icon(self) -> str: + """Return an iconify icon for the status.""" + return CardPrinterStatus.get_icon(self.status) + + def generate_config(self) -> dict[str, Any]: + """Generate the configuration for the printer client.""" + config = { + "base_url": settings.BASE_URL, + "client_id": self.oauth2_application.client_id, + "client_secret": self.oauth2_application.client_secret, + } + return config + + @property + def config_filename(self) -> str: + """Return the filename for the printer client configuration.""" + return f"card-printer-config-{self.pk}.json" + class Meta: verbose_name = _("Card printer") verbose_name_plural = _("Card printers") diff --git a/aleksis/apps/kort/tables.py b/aleksis/apps/kort/tables.py index 5b53edde95c511f73605f25b3adc094ee3b674e6..fabd05a3d38136e4ce634f5a0d8f3084d2a29025 100644 --- a/aleksis/apps/kort/tables.py +++ b/aleksis/apps/kort/tables.py @@ -1,7 +1,15 @@ from django.template.loader import render_to_string from django.utils.translation import gettext as _ -from django_tables2 import A, BooleanColumn, Column, LinkColumn, RelatedLinkColumn, Table +from django_tables2 import ( + A, + BooleanColumn, + Column, + DateTimeColumn, + LinkColumn, + RelatedLinkColumn, + Table, +) class CardTable(Table): @@ -27,3 +35,28 @@ class CardTable(Table): def render_actions(self, value, record): return render_to_string("kort/card/actions.html", dict(pk=value, card=record)) + + +class CardPrinterTable(Table): + """Table to list card printers.""" + + class Meta: + attrs = {"class": "highlight"} + + name = LinkColumn("card_printer", verbose_name=_("Printer name"), args=[A("pk")]) + location = Column(verbose_name=_("Printer location")) + + current_status = Column(verbose_name=_("Current status"), accessor=A("pk")) + last_seen_at = DateTimeColumn(verbose_name=_("Last seen at")) + actions = Column(verbose_name=_("Actions"), accessor=A("pk")) + + def render_current_status(self, value, record): + return render_to_string( + "kort/printer/status.html", + dict( + printer=record, + ), + ) + + def render_actions(self, value, record): + return render_to_string("kort/printer/actions.html", dict(pk=value, printer=record)) diff --git a/aleksis/apps/kort/templates/kort/printer/actions.html b/aleksis/apps/kort/templates/kort/printer/actions.html new file mode 100644 index 0000000000000000000000000000000000000000..74f1bccbaafe048280e28a20e29195c346b1a54f --- /dev/null +++ b/aleksis/apps/kort/templates/kort/printer/actions.html @@ -0,0 +1,26 @@ +{% load i18n %} + +<div id="detail-modal-{{ printer.pk }}" class="modal"> + <div class="modal-content"> + <h4>{{ printer.name }}</h4> + {% include "kort/printer/detail_content.html" %} + </div> + <div class="modal-footer"> + <a href="#!" class="modal-close waves-effect waves-green btn-flat"> + <i class="material-icons left iconify" data-icon="mdi:close"></i> + {% trans "Close" %} + </a> + </div> +</div> +<a class="btn-flat waves-effect waves-green green-text modal-trigger" href="#detail-modal-{{ printer.pk }}"> + <i class="material-icons left iconify" data-icon="mdi:play-box-outline"></i> + {% trans "Show" %} +</a> +<a class="btn-flat waves-effect waves-orange orange-text modal-trigger" href="{% url "edit_card_printer" printer.pk %}"> + <i class="material-icons left iconify" data-icon="mdi:pencil-outline"></i> + {% trans "Edit" %} +</a> +<a class="btn-flat waves-effect waves-red red-text modal-trigger" href="{% url "delete_card_printer" printer.pk %}"> + <i class="material-icons left iconify" data-icon="mdi:delete-outline"></i> + {% trans "Delete" %} +</a> \ No newline at end of file diff --git a/aleksis/apps/kort/templates/kort/printer/create.html b/aleksis/apps/kort/templates/kort/printer/create.html new file mode 100644 index 0000000000000000000000000000000000000000..086400b8641fa9bf9c1d6118c8a161f266456254 --- /dev/null +++ b/aleksis/apps/kort/templates/kort/printer/create.html @@ -0,0 +1,24 @@ +{# -*- 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 %}Create card{% endblocktrans %}{% endblock %} +{% block page_title %}{% blocktrans %}Create card{% endblocktrans %}{% endblock %} + + +{% block content %} + <form method="post" enctype="multipart/form-data"> + {% csrf_token %} + {% form form=form %}{% endform %} + {% include "core/partials/save_button.html" %} + </form> + {% include_js "select2-materialize" %} + {{ form.media.js }} +{% endblock %} diff --git a/aleksis/apps/kort/templates/kort/printer/delete.html b/aleksis/apps/kort/templates/kort/printer/delete.html new file mode 100644 index 0000000000000000000000000000000000000000..9fc32ae6baaede5d13e20316484705ffec3f800f --- /dev/null +++ b/aleksis/apps/kort/templates/kort/printer/delete.html @@ -0,0 +1,32 @@ +{# -*- engine:django -*- #} + +{% extends "core/base.html" %} + +{% load i18n rules material_form %} +{% load render_table from django_tables2 %} + +{% block browser_title %}{% blocktrans %}Delete Card Printer{% endblocktrans %}{% endblock %} +{% block no_page_title %}{% endblock %} + +{% block content %} + <p class="flow-text">{% trans "Do you really want to delete the following card printer?" %}</p> + {% include "kort/printer/short.html" with printer=object %} + <figure class="alert warning"> + <i class="material-icons left iconify" data-icon="mdi:alert-octagon-outline"></i> + {% blocktrans %} + Please pay attention that this also will deactivate the access for the print client and you would have to + reconfigure the client if you want to use it further. + {% endblocktrans %} + </figure> + <form method="post" action=""> + {% csrf_token %} + <a href="{% url "card_printers" %}" class="modal-close waves-effect waves-green btn"> + <i class="material-icons left iconify" data-icon="mdi:arrow-left"></i> + {% trans "Go back" %} + </a> + <button type="submit" name="delete" class="modal-close waves-effect waves-light red btn"> + <i class="material-icons left iconify" data-icon="mdi:delete-outline"></i> + {% trans "Delete" %} + </button> + </form> +{% endblock %} \ No newline at end of file diff --git a/aleksis/apps/kort/templates/kort/printer/detail.html b/aleksis/apps/kort/templates/kort/printer/detail.html new file mode 100644 index 0000000000000000000000000000000000000000..ff50526b0146274e045d1a57cd283ce1367feae7 --- /dev/null +++ b/aleksis/apps/kort/templates/kort/printer/detail.html @@ -0,0 +1,18 @@ +{# -*- engine:django -*- #} + +{% extends "core/base.html" %} + +{% load material_form i18n any_js %} + + +{% block browser_title %}{% blocktrans %}Card Printer{% endblocktrans %}{% endblock %} +{% block page_title %}{{ object.name }}{% endblock %} + + +{% block content %} + <a class="btn waves-effect waves-light secondary margin-bottom" href="{% url "card_printers" %}"> + <i class="material-icons left iconify" data-icon="mdi:arrow-left"></i> + {% trans "Back to all printers" %} + </a> + {% include "kort/printer/detail_content.html" with printer=object %} +{% endblock %} diff --git a/aleksis/apps/kort/templates/kort/printer/detail_content.html b/aleksis/apps/kort/templates/kort/printer/detail_content.html new file mode 100644 index 0000000000000000000000000000000000000000..84e8ab655c4380073dcf21c44c74dc4c39cf326a --- /dev/null +++ b/aleksis/apps/kort/templates/kort/printer/detail_content.html @@ -0,0 +1,79 @@ +{% load i18n %} +<div class="row no-margin"> + <div class="col s12 m12 l6 no-padding"> + <div class="card"> + <div class="card-content"> + <div class="card-title">{% trans "Printer details" %}</div> + <table> + <tr> + <th> + <i class="material-icons left iconify" data-icon="mdi:printer-outline"></i> + {% trans "Name" %}</th> + <td>{{ printer.name }}</td> + </tr> + <tr> + <th> + <i class="material-icons left iconify" data-icon="mdi:map-marker-outline"></i> + {% trans "Location" %} + </th> + <td>{{ printer.location|default:"–" }}</td> + </tr> + <tr> + <th> + <i class="material-icons left iconify" data-icon="mdi:card-text-outline"></i> + {% trans "Description" %} + </th> + <td>{{ printer.description|default:"–" }}</td> + </tr> + <tr> + <th> + <i class="material-icons left iconify" data-icon="mdi:clock-check-outline"></i> + {% trans "Last seen at" %} + </th> + <td>{{ printer.last_seen_at|default:_("never seen yet") }}</td> + </tr> + <tr> + <th {% if printer.status_text %}rowspan="2"{% endif %}> + <i class="material-icons left iconify" data-icon="mdi:checkbox-blank-badge-outline"></i> + {% trans "Status" %} + </th> + <td> + {% include "kort/printer/status.html" %} + </td> + </tr> + {% if printer.status_text %} + <tr> + <td> + {{ printer.status_text }} + </td> + </tr> + {% endif %} + </table> + </div> + </div> + </div> + <div class="col s12 m12 l6"> + <!-- Jobs here --> + {% if printer.status == "not_registered" %} + <div class="card"> + <div class="card-content"> + <div class="card-title">{% trans "Setup printer client" %}</div> + <p> + {% blocktrans %} + To enable printing, you have to register the print client on the device + which the printer is connected to. + {% endblocktrans %} + </p> + <h6>{% trans "1. Download print client" %}</h6> + <h6>{% trans "2. Download configuration file" %}</h6> + <a class="btn waves-effect waves-light" href="{% url "card_printer_config" printer.pk %}"> + <i class="material-icons left iconify" data-icon="mdi:download-outline"></i> + {% trans "Download configuration" %} + </a> + <h6>{% trans "3. Setup client" %}</h6> + <code>kort-client setup {{ printer.config_filename }}</code> + </div> + </div> + {% endif %} + </div> +</div> \ No newline at end of file diff --git a/aleksis/apps/kort/templates/kort/printer/edit.html b/aleksis/apps/kort/templates/kort/printer/edit.html new file mode 100644 index 0000000000000000000000000000000000000000..450a2d022f44dcc3581c681f6279f87500f14185 --- /dev/null +++ b/aleksis/apps/kort/templates/kort/printer/edit.html @@ -0,0 +1,24 @@ +{# -*- 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 printer{% endblocktrans %}{% endblock %} +{% block page_title %}{% blocktrans %}Edit card printer{% endblocktrans %}{% endblock %} + + +{% block content %} + <form method="post" enctype="multipart/form-data"> + {% csrf_token %} + {% form form=form %}{% endform %} + {% include "core/partials/save_button.html" %} + </form> + {% include_js "select2-materialize" %} + {{ form.media.js }} +{% endblock %} diff --git a/aleksis/apps/kort/templates/kort/printer/list.html b/aleksis/apps/kort/templates/kort/printer/list.html new file mode 100644 index 0000000000000000000000000000000000000000..014df87d08e4f8fdadcc590bca0ebb6fe1b87d77 --- /dev/null +++ b/aleksis/apps/kort/templates/kort/printer/list.html @@ -0,0 +1,22 @@ +{# -*- engine:django -*- #} + +{% extends "core/base.html" %} + +{% load i18n rules material_form any_js %} +{% load render_table from django_tables2 %} + +{% block browser_title %}{% blocktrans %}Card printers{% endblocktrans %}{% endblock %} +{% block page_title %}{% blocktrans %}Card printers{% endblocktrans %}{% endblock %} + +{% block content %} + {% has_perm 'core.create_cardprinter_rule' user person as can_create_person %} + + {% if can_create_person %} + <a class="btn green waves-effect waves-light" href="{% url 'create_card_printer' %}"> + <i class="material-icons left iconify" data-icon="mdi:plus"></i> + {% trans "Register new card printer" %} + </a> + {% endif %} + + {% render_table table %} +{% endblock %} diff --git a/aleksis/apps/kort/templates/kort/printer/short.html b/aleksis/apps/kort/templates/kort/printer/short.html new file mode 100644 index 0000000000000000000000000000000000000000..706a5e14840f8f5cbaea409a0b76b9f77c50235d --- /dev/null +++ b/aleksis/apps/kort/templates/kort/printer/short.html @@ -0,0 +1,15 @@ +{% load i18n %} +<table> + <tr> + <th>{% trans "Name" %}</th> + <td>{{ printer.name }}</td> + </tr> + <tr> + <th>{% trans "Location" %}</th> + <td>{{ printer.location|default:"–" }}</td> + </tr> + <tr> + <th>{% trans "Status" %}</th> + <td>{% include "kort/printer/status.html" %}</td> + </tr> +</table> \ No newline at end of file diff --git a/aleksis/apps/kort/templates/kort/printer/status.html b/aleksis/apps/kort/templates/kort/printer/status.html new file mode 100644 index 0000000000000000000000000000000000000000..22172068b5f49ea24b47c360286a7764dd0e0a8d --- /dev/null +++ b/aleksis/apps/kort/templates/kort/printer/status.html @@ -0,0 +1,5 @@ +{% load i18n %} +<span class="chip white-text {{ printer.status_color }}"> + <i class="material-icons left iconify" data-icon="{{ printer.status_icon }}"></i> + {{ printer.status_label }} +</span> \ No newline at end of file diff --git a/aleksis/apps/kort/urls.py b/aleksis/apps/kort/urls.py index 983140bc22fee78ee81eddfa486a3ca36faffb92..df8dd267e5dc9df819f8d0bd259f59f18d89d16e 100644 --- a/aleksis/apps/kort/urls.py +++ b/aleksis/apps/kort/urls.py @@ -1,6 +1,6 @@ from django.urls import path -from . import views +from . import api, views urlpatterns = [ path("test", views.TestPDFView.as_view(), name="test_pdf"), @@ -14,4 +14,24 @@ urlpatterns = [ ), path("cards/<int:pk>/deactivate/", views.CardDeactivateView.as_view(), name="deactivate_card"), path("cards/<int:pk>/delete/", views.CardDeleteView.as_view(), name="delete_card"), + path("printers/", views.CardPrinterListView.as_view(), name="card_printers"), + path("printers/create/", views.CardPrinterCreateView.as_view(), name="create_card_printer"), + path("printers/<int:pk>/", views.CardPrinterDetailView.as_view(), name="card_printer"), + path("printers/<int:pk>/edit/", views.CardPrinterEditView.as_view(), name="edit_card_printer"), + path( + "printers/<int:pk>/delete/", + views.CardPrinterDeleteView.as_view(), + name="delete_card_printer", + ), + path( + "printers/<int:pk>/config/", + views.CardPrinterConfigView.as_view(), + name="card_printer_config", + ), + path("api/v1/printers/", api.CardPrinterDetails.as_view(), name="api_card_printer"), + path( + "api/v1/printers/<int:pk>/status/", + api.CardPrinterUpdateStatus.as_view(), + name="api_card_printer_status", + ), ] diff --git a/aleksis/apps/kort/views.py b/aleksis/apps/kort/views.py index 1741e6e3bac558d9373d0b35eed4c600b409011c..445887200f4e027f5cee89b6758729416357ab5d 100644 --- a/aleksis/apps/kort/views.py +++ b/aleksis/apps/kort/views.py @@ -1,3 +1,5 @@ +import json + from django.contrib import messages from django.http import HttpRequest, HttpResponse from django.shortcuts import redirect, render @@ -10,10 +12,10 @@ from django_tables2 import SingleTableView from reversion.views import RevisionMixin from rules.contrib.views import PermissionRequiredMixin -from aleksis.apps.kort.forms import CardForm -from aleksis.apps.kort.models import Card -from aleksis.apps.kort.tables import CardTable -from aleksis.core.mixins import AdvancedCreateView, AdvancedDeleteView +from aleksis.apps.kort.forms import CardForm, CardPrinterForm +from aleksis.apps.kort.models import Card, CardPrinter +from aleksis.apps.kort.tables import CardPrinterTable, CardTable +from aleksis.core.mixins import AdvancedCreateView, AdvancedDeleteView, AdvancedEditView from aleksis.core.util.celery_progress import render_progress_page from aleksis.core.views import RenderPDFView @@ -110,3 +112,67 @@ class CardGeneratePDFView(PermissionRequiredMixin, RevisionMixin, SingleObjectMi button_url=redirect_url, button_icon="credit_card", ) + + +class CardPrinterListView(PermissionRequiredMixin, RevisionMixin, SingleTableView): + """List view for all card printers.""" + + permission_required = "core.view_cardprinters_rule" + template_name = "kort/printer/list.html" + model = CardPrinter + table_class = CardPrinterTable + + +class CardPrinterCreateView(PermissionRequiredMixin, RevisionMixin, AdvancedCreateView): + """View used to create a card printer.""" + + permission_required = "core.create_cardprinter_rule" + template_name = "kort/printer/create.html" + form_class = CardPrinterForm + model = CardPrinter + success_message = _("The card printer has been created successfully.") + + def get_success_url(self): + return reverse("card_printer", args=[self.object.pk]) + + +class CardPrinterEditView(PermissionRequiredMixin, RevisionMixin, AdvancedEditView): + """View used to edit a card printer.""" + + permission_required = "core.edit_cardprinter_rule" + template_name = "kort/printer/edit.html" + form_class = CardPrinterForm + model = CardPrinter + success_message = _("The card printer has been changed successfully.") + + def get_success_url(self): + return reverse("card_printer", args=[self.object.pk]) + + +class CardPrinterDeleteView(PermissionRequiredMixin, RevisionMixin, AdvancedDeleteView): + """View used to delete a card printer.""" + + permission_required = "core.delete_cardprinter_rule" + success_url = reverse_lazy("card_printers") + template_name = "kort/printer/delete.html" + model = CardPrinter + success_message = _("The card printer has been deleted successfully.") + + +class CardPrinterDetailView(PermissionRequiredMixin, RevisionMixin, DetailView): + permission_required = "core.view_cardprinter_rule" + model = CardPrinter + template_name = "kort/printer/detail.html" + + +class CardPrinterConfigView(PermissionRequiredMixin, RevisionMixin, SingleObjectMixin, View): + permission_required = "core.view_cardprinter_rule" + model = CardPrinter + + def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: + self.object = self.get_object() + response = HttpResponse( + json.dumps(self.object.generate_config()), content_type="application/json" + ) + response["Content-Disposition"] = f'attachment; filename="{self.object.config_filename}"' + return response