Tests and docs

This commit is contained in:
Raphael Michel
2024-10-14 15:37:27 +02:00
parent bbefb59036
commit 3416366763
4 changed files with 191 additions and 6 deletions
+72 -2
View File
@@ -29,8 +29,8 @@ item_assignments list of objects Products this l
===================================== ========================== =======================================================
Endpoints
---------
Layout endpoints
----------------
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/ticketlayouts/
@@ -268,5 +268,75 @@ Endpoints
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to delete this resource.
API ticket rendering
--------------------
.. http:post:: /api/v1/organizers/(organizer)/events/(event)/ticketpdfrenderer/render_batch/
With this API call, you can instruct the system to render a set of tickets into one combined PDF file. To specify
which tickets to render, you need to submit a list of "parts". For every part, the following fields are supported:
* ``orderposition`` (``integer``, required): The ID of the order position to render.
* ``override_channel`` (``string``, optional): The sales channel ID to be used for layout selection instead of the
original channel of the order.
* ``override_layout`` (``integer``, optional): The ticket layout ID to be used instead of the auto-selected one.
If your input parameters validate correctly, a ``202 Accepted`` status code is returned.
The body points you to the download URL of the result. Running a ``GET`` request on that result URL will
yield one of the following status codes:
* ``200 OK`` The export succeeded. The body will be your resulting file. Might be large!
* ``409 Conflict`` Your export is still running. The body will be JSON with the structure ``{"status": "running", "percentage": 40}``. ``percentage`` can be ``null`` if it is not known and ``status`` can be ``waiting`` before the task is actually being processed. Please retry, but wait at least one second before you do.
* ``410 Gone`` Running the export has failed permanently. The body will be JSON with the structure ``{"status": "failed", "message": "Error message"}``
* ``404 Not Found`` The export does not exist / is expired.
.. warning:: This endpoint is considered **experimental**. It might change at any time without prior notice.
.. note:: To avoid performance issues, a maximum number of 1000 parts is currently allowed.
**Example request**:
.. sourcecode:: http
POST /api/v1/organizers/bigevents/events/sampleconf/ticketpdfrenderer/render_batch/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
Content-Type: application/json
{
"parts": [
{
"orderposition": 55412
},
{
"orderposition": 55412,
"override_channel": "web"
},
{
"orderposition": 55412,
"override_layout": 56
}
]
}
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
{
"download": "https://pretix.eu/api/v1/organizers/bigevents/events/sampleconf/ticketpdfrenderer/download/29891ede-196f-4942-9e26-d055a36e98b8/3f279f13-c198-4137-b49b-9b360ce9fcce/"
}
:param organizer: The ``slug`` field of the organizer to fetch
:param event: The ``slug`` field of the event to fetch
:statuscode 202: no error
:statuscode 400: Invalid input options
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to view this resource.
.. _here: https://github.com/pretix/pretix/blob/master/src/pretix/static/schema/pdf-layout.schema.json
+1 -1
View File
@@ -201,7 +201,7 @@ class RenderJobSerializer(serializers.Serializer):
def validate(self, attrs):
if len(attrs["parts"]) > 1000:
raise ValidationError("Please do not submit more than 1000 parts.")
raise ValidationError({"parts": ["Please do not submit more than 1000 parts."]})
return super().validate(attrs)
@@ -140,6 +140,7 @@ def bulk_render(event: Event, fileid: int, parts: list) -> int:
merger.close()
outbuffer.seek(0)
file.type = "application/pdf"
file.file.save(cachedfile_name(file, file.filename), ContentFile(outbuffer.getvalue()))
file.save()
return file.pk
+117 -3
View File
@@ -21,6 +21,8 @@
#
import copy
import json
from datetime import timedelta
from decimal import Decimal
import pytest
from django.core.files.base import ContentFile
@@ -28,7 +30,9 @@ from django.utils.timezone import now
from django_scopes import scopes_disabled
from rest_framework.test import APIClient
from pretix.base.models import Event, Item, Organizer, Team
from pretix.base.models import (
Event, Item, Order, OrderPosition, Organizer, Team,
)
from pretix.plugins.ticketoutputpdf.models import TicketLayoutItem
@@ -39,14 +43,35 @@ def env():
organizer=o, name='Dummy', slug='dummy',
date_from=now(), plugins='pretix.plugins.banktransfer'
)
t = Team.objects.create(organizer=event.organizer)
t = Team.objects.create(organizer=event.organizer, can_view_orders=True)
t.limit_events.add(event)
item1 = Item.objects.create(event=event, name="Ticket", default_price=23)
tl = event.ticket_layouts.create(name="Foo", default=True, layout='[{"a": 2}]')
tl = event.ticket_layouts.create(
name="Foo",
default=True,
layout='[{"type": "poweredby", "left": "0", "bottom": "0", "size": "1.00", "content": "dark"}]',
)
TicketLayoutItem.objects.create(layout=tl, item=item1, sales_channel=o.sales_channels.get(identifier="web"))
return event, tl, item1
@pytest.fixture
def position(env):
item = env[0].items.create(name="Ticket", default_price=3, admission=True)
order = Order.objects.create(
code='FOO', event=env[0], email='dummy@dummy.test',
status=Order.STATUS_PAID, locale='en',
datetime=now() - timedelta(days=4),
expires=now() - timedelta(hours=4) + timedelta(days=10),
total=Decimal('23.00'),
sales_channel=env[0].organizer.sales_channels.get(identifier="web"),
)
return OrderPosition.objects.create(
order=order, item=item, variation=None,
price=Decimal("23.00"), attendee_name_parts={"full_name": "Peter"}, positionid=1
)
@pytest.fixture
def client():
return APIClient()
@@ -213,3 +238,92 @@ def test_api_delete(env, token_client):
)
assert resp.status_code == 204
assert not env[0].ticket_layouts.exists()
@pytest.mark.django_db
def test_renderer_batch_valid(env, token_client, position):
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/ticketpdfrenderer/render_batch/'.format(env[0].slug, env[0].slug),
{
"parts": [
{
"orderposition": position.pk,
},
{
"orderposition": position.pk,
"override_channel": "web",
},
{
"orderposition": position.pk,
"override_layout": env[1].pk,
},
]
},
format='json',
)
assert resp.status_code == 202
assert "download" in resp.data
resp = token_client.get("/" + resp.data["download"].split("/", 3)[3])
assert resp.status_code == 200
assert resp["Content-Type"] == "application/pdf"
@pytest.mark.django_db
def test_renderer_batch_invalid(env, token_client, position):
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/ticketpdfrenderer/render_batch/'.format(env[0].slug, env[0].slug),
{
"parts": [
{
"orderposition": -2,
},
]
},
format='json',
)
assert resp.status_code == 400
assert resp.data == {"parts": [{"orderposition": ["Invalid pk \"-2\" - object does not exist."]}]}
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/ticketpdfrenderer/render_batch/'.format(env[0].slug, env[0].slug),
{
"parts": [
{
"orderposition": position.pk,
"override_layout": -2,
},
]
},
format='json',
)
assert resp.status_code == 400
assert resp.data == {"parts": [{"override_layout": ["Invalid pk \"-2\" - object does not exist."]}]}
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/ticketpdfrenderer/render_batch/'.format(env[0].slug, env[0].slug),
{
"parts": [
{
"orderposition": position.pk,
"override_channel": "magic",
},
]
},
format='json',
)
assert resp.status_code == 400
assert resp.data == {"parts": [{"override_channel": ["Object with identifier=magic does not exist."]}]}
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/ticketpdfrenderer/render_batch/'.format(env[0].slug, env[0].slug),
{
"parts": [
{
"orderposition": position.pk,
}
] * 1002
},
format='json',
)
assert resp.status_code == 400
assert resp.data == {"parts": ["Please do not submit more than 1000 parts."]}