From bb4aee45c19154951eb13292ae50e5602874158d Mon Sep 17 00:00:00 2001 From: Jonathan Weth <git@jonathanweth.de> Date: Sun, 27 Mar 2022 16:54:51 +0200 Subject: [PATCH] Implement API for getting and updating print jobs with all connected changes --- aleksis/apps/kort/api.py | 65 ++++++++++++++++++- aleksis/apps/kort/forms.py | 4 +- ...9_cardprinter_generate_number_on_server.py | 18 +++++ .../migrations/0010_auto_20220326_2123.py | 25 +++++++ aleksis/apps/kort/models.py | 23 ++++++- .../kort/printer/detail_content.html | 8 ++- aleksis/apps/kort/urls.py | 10 +++ 7 files changed, 147 insertions(+), 6 deletions(-) create mode 100644 aleksis/apps/kort/migrations/0009_cardprinter_generate_number_on_server.py create mode 100644 aleksis/apps/kort/migrations/0010_auto_20220326_2123.py diff --git a/aleksis/apps/kort/api.py b/aleksis/apps/kort/api.py index cce086a..ded44c5 100644 --- a/aleksis/apps/kort/api.py +++ b/aleksis/apps/kort/api.py @@ -1,11 +1,14 @@ from django.contrib import admin +from django.shortcuts import get_object_or_404 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 rest_framework.response import Response +from rest_framework.views import APIView -from aleksis.apps.kort.models import CardPrinter +from aleksis.apps.kort.models import Card, CardPrinter, CardPrintJob admin.autodiscover() @@ -20,6 +23,16 @@ class CorrectPrinterPermission(BasePermission): return False +class CorrectJobPrinterPermission(BasePermission): + """Check whether the OAuth2 application belongs to the job's printer.""" + + def has_object_permission(self, request, view, obj) -> bool: + token = request.auth + if token.application == obj.printer.oauth2_application: + return True + return False + + class CardPrinterSerializer(serializers.ModelSerializer): class Meta: model = CardPrinter @@ -35,6 +48,7 @@ class CardPrinterSerializer(serializers.ModelSerializer): "status_text", "last_seen_at", "cups_printer", + "generate_number_on_server", ) @@ -44,6 +58,26 @@ class CardPrinterStatusSerializer(serializers.ModelSerializer): fields = ("status", "status_text") +class CardSerializer(serializers.ModelSerializer): + class Meta: + model = Card + fields = ("id", "chip_number", "valid_until", "deactivated", "person", "pdf_file") + + +class CardPrintJobSerializer(serializers.ModelSerializer): + card = CardSerializer() + + class Meta: + model = CardPrintJob + fields = ("id", "printer", "card", "status", "status_text") + + +class CardPrintJobStatusSerializer(serializers.ModelSerializer): + class Meta: + model = CardPrintJob + fields = ("id", "status", "status_text") + + class CardPrinterDetails(generics.RetrieveAPIView): """Show details about the card printer.""" @@ -71,3 +105,32 @@ class CardPrinterUpdateStatus(generics.UpdateAPIView): instance.last_seen_at = timezone.now() instance.save() return r + + +class GetNextPrintJob(APIView): + """Get the next print job.""" + + permission_classes = [permissions.IsAuthenticated, TokenHasScope, CorrectPrinterPermission] + required_scopes = ["card_printer"] + serializer_class = CardPrinterSerializer + queryset = CardPrinter.objects.all() + + def get_object(self, pk): + return get_object_or_404(CardPrinter, pk=pk) + + def get(self, request, pk, *args, **kwargs): + printer = self.get_object(pk) + job = printer.get_next_print_job() + if not job: + return Response({"status": "no_job"}) + serializer = CardPrintJobSerializer(job) + return Response(serializer.data) + + +class CardPrintJobUpdateStatusView(generics.UpdateAPIView): + """Update the status of the card printer.""" + + permission_classes = [permissions.IsAuthenticated, TokenHasScope, CorrectJobPrinterPermission] + required_scopes = ["card_printer"] + serializer_class = CardPrintJobStatusSerializer + queryset = CardPrintJob.objects.all() diff --git a/aleksis/apps/kort/forms.py b/aleksis/apps/kort/forms.py index 1bb2c98..e0ba41c 100644 --- a/aleksis/apps/kort/forms.py +++ b/aleksis/apps/kort/forms.py @@ -26,12 +26,12 @@ class CardForm(forms.ModelForm): class CardPrinterForm(forms.ModelForm): layout = Layout( Fieldset(_("Generic attributes"), "name", "location", "description"), - Fieldset(_("Printer settings"), "cups_printer"), + Fieldset(_("Printer settings"), "cups_printer", "generate_number_on_server"), ) class Meta: model = CardPrinter - fields = ["name", "location", "description", "cups_printer"] + fields = ["name", "location", "description", "cups_printer", "generate_number_on_server"] class PrinterSelectForm(forms.Form): diff --git a/aleksis/apps/kort/migrations/0009_cardprinter_generate_number_on_server.py b/aleksis/apps/kort/migrations/0009_cardprinter_generate_number_on_server.py new file mode 100644 index 0000000..e099be6 --- /dev/null +++ b/aleksis/apps/kort/migrations/0009_cardprinter_generate_number_on_server.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.12 on 2022-03-26 16:59 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('kort', '0008_auto_20220319_2018'), + ] + + operations = [ + migrations.AddField( + model_name='cardprinter', + name='generate_number_on_server', + field=models.BooleanField(default=True, verbose_name='Generate card number on server'), + ), + ] diff --git a/aleksis/apps/kort/migrations/0010_auto_20220326_2123.py b/aleksis/apps/kort/migrations/0010_auto_20220326_2123.py new file mode 100644 index 0000000..028d25c --- /dev/null +++ b/aleksis/apps/kort/migrations/0010_auto_20220326_2123.py @@ -0,0 +1,25 @@ +# Generated by Django 3.2.12 on 2022-03-26 20:23 + +from django.db import migrations +import django.utils.timezone +import model_utils.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('kort', '0009_cardprinter_generate_number_on_server'), + ] + + operations = [ + migrations.AddField( + model_name='cardprintjob', + name='created', + field=model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created'), + ), + migrations.AddField( + model_name='cardprintjob', + name='modified', + field=model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified'), + ), + ] diff --git a/aleksis/apps/kort/models.py b/aleksis/apps/kort/models.py index c69670e..a6281f3 100644 --- a/aleksis/apps/kort/models.py +++ b/aleksis/apps/kort/models.py @@ -1,5 +1,6 @@ +import uuid from datetime import timedelta -from typing import Any, Union +from typing import Any, Optional, Union from django.conf import settings from django.core.exceptions import ValidationError @@ -10,6 +11,7 @@ from django.utils import timezone from django.utils.translation import gettext as _ from celery.result import AsyncResult +from model_utils.models import TimeStampedModel from aleksis.core.mixins import ExtensibleModel from aleksis.core.models import OAuthApplication, Person @@ -80,6 +82,9 @@ class CardPrinter(ExtensibleModel): # Settings cups_printer = models.CharField(max_length=255, verbose_name=_("CUPS printer"), blank=True) + generate_number_on_server = models.BooleanField( + default=True, verbose_name=_("Generate card number on server") + ) def save(self, *args, **kwargs): if not self.oauth2_application: @@ -141,6 +146,14 @@ class CardPrinter(ExtensibleModel): for printer in cls.objects.all(): printer.check_online_status() + def get_next_print_job(self) -> Optional["CardPrintJob"]: + jobs = self.jobs.order_by("created").filter(status=PrintStatus.REGISTERED) + if self.generate_number_on_server: + jobs = jobs.filter(card__pdf_file__isnull=False) + if jobs.exists(): + return jobs.first() + return None + class Meta: verbose_name = _("Card printer") verbose_name_plural = _("Card printers") @@ -210,10 +223,16 @@ class Card(ExtensibleModel): def print_card(self, printer: CardPrinter): job = CardPrintJob(card=self, printer=printer) job.save() + if not self.chip_number and printer.generate_number_on_server: + self.chip_number = str(self.generate_number()) + self.save() if self.chip_number: self.generate_pdf() return job + def generate_number(self) -> int: + return uuid.uuid1().int >> 32 + def __str__(self): if self.chip_number: return f"{self.person} ({self.chip_number})" @@ -224,7 +243,7 @@ class Card(ExtensibleModel): verbose_name_plural = _("Cards") -class CardPrintJob(ExtensibleModel): +class CardPrintJob(TimeStampedModel, ExtensibleModel): printer = models.ForeignKey( CardPrinter, on_delete=models.CASCADE, verbose_name=_("Printer"), related_name="jobs" ) diff --git a/aleksis/apps/kort/templates/kort/printer/detail_content.html b/aleksis/apps/kort/templates/kort/printer/detail_content.html index eaefc0d..9618a4b 100644 --- a/aleksis/apps/kort/templates/kort/printer/detail_content.html +++ b/aleksis/apps/kort/templates/kort/printer/detail_content.html @@ -83,6 +83,9 @@ <th> {% trans "Card" %} </th> + <th> + {% trans "Created at" %} + </th> <th> {% trans "Status" %} </th> @@ -95,7 +98,10 @@ </a> </td> <td> - {{ job.status }} + {{ job.created }} + </td> + <td> + {{ job.status }} {% if job.status_text %}({{ job.status_text }}){% endif %} </td> </tr> {% endfor %} diff --git a/aleksis/apps/kort/urls.py b/aleksis/apps/kort/urls.py index 9aa6a22..a0c9f5d 100644 --- a/aleksis/apps/kort/urls.py +++ b/aleksis/apps/kort/urls.py @@ -35,4 +35,14 @@ urlpatterns = [ api.CardPrinterUpdateStatus.as_view(), name="api_card_printer_status", ), + path( + "api/v1/printers/<int:pk>/jobs/next/", + api.GetNextPrintJob.as_view(), + name="api_get_next_print_job", + ), + path( + "api/v1/jobs/<int:pk>/status/", + api.CardPrintJobUpdateStatusView.as_view(), + name="api_update_job_status", + ), ] -- GitLab