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

Implement API for getting and updating print jobs with all connected changes

parent ac51a9df
No related branches found
No related tags found
1 merge request!3Setup printer stuff
Pipeline #61913 passed
from django.contrib import admin from django.contrib import admin
from django.shortcuts import get_object_or_404
from django.utils import timezone from django.utils import timezone
from oauth2_provider.contrib.rest_framework import TokenHasScope from oauth2_provider.contrib.rest_framework import TokenHasScope
from rest_framework import generics, permissions, serializers from rest_framework import generics, permissions, serializers
from rest_framework.permissions import BasePermission 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() admin.autodiscover()
...@@ -20,6 +23,16 @@ class CorrectPrinterPermission(BasePermission): ...@@ -20,6 +23,16 @@ class CorrectPrinterPermission(BasePermission):
return False 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 CardPrinterSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = CardPrinter model = CardPrinter
...@@ -35,6 +48,7 @@ class CardPrinterSerializer(serializers.ModelSerializer): ...@@ -35,6 +48,7 @@ class CardPrinterSerializer(serializers.ModelSerializer):
"status_text", "status_text",
"last_seen_at", "last_seen_at",
"cups_printer", "cups_printer",
"generate_number_on_server",
) )
...@@ -44,6 +58,26 @@ class CardPrinterStatusSerializer(serializers.ModelSerializer): ...@@ -44,6 +58,26 @@ class CardPrinterStatusSerializer(serializers.ModelSerializer):
fields = ("status", "status_text") 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): class CardPrinterDetails(generics.RetrieveAPIView):
"""Show details about the card printer.""" """Show details about the card printer."""
...@@ -71,3 +105,32 @@ class CardPrinterUpdateStatus(generics.UpdateAPIView): ...@@ -71,3 +105,32 @@ class CardPrinterUpdateStatus(generics.UpdateAPIView):
instance.last_seen_at = timezone.now() instance.last_seen_at = timezone.now()
instance.save() instance.save()
return r 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()
...@@ -26,12 +26,12 @@ class CardForm(forms.ModelForm): ...@@ -26,12 +26,12 @@ class CardForm(forms.ModelForm):
class CardPrinterForm(forms.ModelForm): class CardPrinterForm(forms.ModelForm):
layout = Layout( layout = Layout(
Fieldset(_("Generic attributes"), "name", "location", "description"), Fieldset(_("Generic attributes"), "name", "location", "description"),
Fieldset(_("Printer settings"), "cups_printer"), Fieldset(_("Printer settings"), "cups_printer", "generate_number_on_server"),
) )
class Meta: class Meta:
model = CardPrinter model = CardPrinter
fields = ["name", "location", "description", "cups_printer"] fields = ["name", "location", "description", "cups_printer", "generate_number_on_server"]
class PrinterSelectForm(forms.Form): class PrinterSelectForm(forms.Form):
......
# 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'),
),
]
# 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'),
),
]
import uuid
from datetime import timedelta from datetime import timedelta
from typing import Any, Union from typing import Any, Optional, Union
from django.conf import settings from django.conf import settings
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
...@@ -10,6 +11,7 @@ from django.utils import timezone ...@@ -10,6 +11,7 @@ from django.utils import timezone
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from celery.result import AsyncResult from celery.result import AsyncResult
from model_utils.models import TimeStampedModel
from aleksis.core.mixins import ExtensibleModel from aleksis.core.mixins import ExtensibleModel
from aleksis.core.models import OAuthApplication, Person from aleksis.core.models import OAuthApplication, Person
...@@ -80,6 +82,9 @@ class CardPrinter(ExtensibleModel): ...@@ -80,6 +82,9 @@ class CardPrinter(ExtensibleModel):
# Settings # Settings
cups_printer = models.CharField(max_length=255, verbose_name=_("CUPS printer"), blank=True) 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): def save(self, *args, **kwargs):
if not self.oauth2_application: if not self.oauth2_application:
...@@ -141,6 +146,14 @@ class CardPrinter(ExtensibleModel): ...@@ -141,6 +146,14 @@ class CardPrinter(ExtensibleModel):
for printer in cls.objects.all(): for printer in cls.objects.all():
printer.check_online_status() 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: class Meta:
verbose_name = _("Card printer") verbose_name = _("Card printer")
verbose_name_plural = _("Card printers") verbose_name_plural = _("Card printers")
...@@ -210,10 +223,16 @@ class Card(ExtensibleModel): ...@@ -210,10 +223,16 @@ class Card(ExtensibleModel):
def print_card(self, printer: CardPrinter): def print_card(self, printer: CardPrinter):
job = CardPrintJob(card=self, printer=printer) job = CardPrintJob(card=self, printer=printer)
job.save() 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: if self.chip_number:
self.generate_pdf() self.generate_pdf()
return job return job
def generate_number(self) -> int:
return uuid.uuid1().int >> 32
def __str__(self): def __str__(self):
if self.chip_number: if self.chip_number:
return f"{self.person} ({self.chip_number})" return f"{self.person} ({self.chip_number})"
...@@ -224,7 +243,7 @@ class Card(ExtensibleModel): ...@@ -224,7 +243,7 @@ class Card(ExtensibleModel):
verbose_name_plural = _("Cards") verbose_name_plural = _("Cards")
class CardPrintJob(ExtensibleModel): class CardPrintJob(TimeStampedModel, ExtensibleModel):
printer = models.ForeignKey( printer = models.ForeignKey(
CardPrinter, on_delete=models.CASCADE, verbose_name=_("Printer"), related_name="jobs" CardPrinter, on_delete=models.CASCADE, verbose_name=_("Printer"), related_name="jobs"
) )
......
...@@ -83,6 +83,9 @@ ...@@ -83,6 +83,9 @@
<th> <th>
{% trans "Card" %} {% trans "Card" %}
</th> </th>
<th>
{% trans "Created at" %}
</th>
<th> <th>
{% trans "Status" %} {% trans "Status" %}
</th> </th>
...@@ -95,7 +98,10 @@ ...@@ -95,7 +98,10 @@
</a> </a>
</td> </td>
<td> <td>
{{ job.status }} {{ job.created }}
</td>
<td>
{{ job.status }} {% if job.status_text %}({{ job.status_text }}){% endif %}
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}
......
...@@ -35,4 +35,14 @@ urlpatterns = [ ...@@ -35,4 +35,14 @@ urlpatterns = [
api.CardPrinterUpdateStatus.as_view(), api.CardPrinterUpdateStatus.as_view(),
name="api_card_printer_status", 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",
),
] ]
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