Allow to create devices through the API (#1785)

This commit is contained in:
Raphael Michel
2020-09-25 18:16:18 +02:00
committed by GitHub
parent 4cb18218b2
commit c78e88a1ba
6 changed files with 299 additions and 9 deletions

View File

@@ -0,0 +1,220 @@
.. spelling:: fullname
.. _`rest-devices`:
Devices
=======
See also :ref:`rest-deviceauth`.
Device resource
----------------
The device resource contains the following public fields:
.. rst-class:: rest-resource-table
===================================== ========================== =======================================================
Field Type Description
===================================== ========================== =======================================================
device_id integer Internal ID of the device within this organizer
unique_serial string Unique identifier of this device
name string Device name
all_events boolean Whether this device has access to all events
limit_events list List of event slugs this device has access to
hardware_brand string Device hardware manufacturer (read-only)
hardware_model string Device hardware model (read-only)
software_brand string Device software product (read-only)
software_version string Device software version (read-only)
created datetime Creation time
initialized datetime Time of initialization (or ``null``)
initialization_token string Token for initialization
revoked boolean Whether this device no longer has access
===================================== ========================== =======================================================
Device endpoints
----------------
.. http:get:: /api/v1/organizers/(organizer)/devices/
Returns a list of all devices within a given organizer.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/devices/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"device_id": 1,
"unique_serial": "UOS3GNZ27O39V3QS",
"initialization_token": "frkso3m2w58zuw70",
"all_events": false,
"limit_events": [
"museum"
],
"revoked": false,
"name": "Scanner",
"created": "2020-09-18T14:17:40.971519Z",
"initialized": "2020-09-18T14:17:44.190021Z",
"hardware_brand": "Zebra",
"hardware_model": "TC25",
"software_brand": "pretixSCAN",
"software_version": "1.5.1"
}
]
}
:query integer page: The page number in case of a multi-page result set, default is 1
:param organizer: The ``slug`` field of the organizer to fetch
:statuscode 200: no error
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer does not exist **or** you have no permission to view this resource.
.. http:get:: /api/v1/organizers/(organizer)/devices/(device_id)/
Returns information on one device, identified by its ID.
**Example request**:
.. sourcecode:: http
GET /api/v1/organizers/bigevents/devices/1/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
{
"device_id": 1,
"unique_serial": "UOS3GNZ27O39V3QS",
"initialization_token": "frkso3m2w58zuw70",
"all_events": false,
"limit_events": [
"museum"
],
"revoked": false,
"name": "Scanner",
"created": "2020-09-18T14:17:40.971519Z",
"initialized": "2020-09-18T14:17:44.190021Z",
"hardware_brand": "Zebra",
"hardware_model": "TC25",
"software_brand": "pretixSCAN",
"software_version": "1.5.1"
}
:param organizer: The ``slug`` field of the organizer to fetch
:param device_id: The ``device_id`` field of the device to fetch
:statuscode 200: no error
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer does not exist **or** you have no permission to view this resource.
.. http:post:: /api/v1/organizers/(organizer)/devices/
Creates a new device
**Example request**:
.. sourcecode:: http
POST /api/v1/organizers/bigevents/devices/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
Content-Type: application/json
{
"name": "Scanner",
"all_events": true,
"limit_events": [],
}
**Example response**:
.. sourcecode:: http
HTTP/1.1 201 Created
Vary: Accept
Content-Type: application/json
{
"device_id": 1,
"unique_serial": "UOS3GNZ27O39V3QS",
"initialization_token": "frkso3m2w58zuw70",
"all_events": true,
"limit_events": [],
"revoked": false,
"name": "Scanner",
"created": "2020-09-18T14:17:40.971519Z",
"initialized": null
"hardware_brand": null,
"hardware_model": null,
"software_brand": null,
"software_version": null
}
:param organizer: The ``slug`` field of the organizer to create a device for
:statuscode 201: no error
:statuscode 400: The device could not be created due to invalid submitted data.
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer does not exist **or** you have no permission to create this resource.
.. http:patch:: /api/v1/organizers/(organizer)/devices/(device_id)/
Update a device.
**Example request**:
.. sourcecode:: http
PATCH /api/v1/organizers/bigevents/devices/1/ HTTP/1.1
Host: pretix.eu
Accept: application/json, text/javascript
Content-Type: application/json
Content-Length: 94
{
"name": "Foo"
}
**Example response**:
.. sourcecode:: http
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json
{
"id": 1,
"name": "Foo",
...
}
:param organizer: The ``slug`` field of the organizer to modify
:param device_id: The ``device_id`` field of the deviec to modify
:statuscode 200: no error
:statuscode 400: The device could not be modified due to invalid submitted data
:statuscode 401: Authentication failure
:statuscode 403: The requested organizer does not exist **or** you have no permission to change this resource.

View File

@@ -24,6 +24,7 @@ Resources and endpoints
giftcards giftcards
carts carts
teams teams
devices
webhooks webhooks
seatingplans seatingplans
billing_invoices billing_invoices

View File

@@ -9,7 +9,8 @@ from pretix.api.serializers.i18n import I18nAwareModelSerializer
from pretix.api.serializers.order import CompatibleJSONField from pretix.api.serializers.order import CompatibleJSONField
from pretix.base.auth import get_auth_backends from pretix.base.auth import get_auth_backends
from pretix.base.models import ( from pretix.base.models import (
GiftCard, Organizer, SeatingPlan, Team, TeamAPIToken, TeamInvite, User, Device, GiftCard, Organizer, SeatingPlan, Team, TeamAPIToken, TeamInvite,
User,
) )
from pretix.base.models.seating import SeatingPlanLayoutValidator from pretix.base.models.seating import SeatingPlanLayoutValidator
from pretix.base.services.mail import SendMailException, mail from pretix.base.services.mail import SendMailException, mail
@@ -66,9 +67,6 @@ class EventSlugField(serializers.SlugRelatedField):
class TeamSerializer(serializers.ModelSerializer): class TeamSerializer(serializers.ModelSerializer):
limit_events = EventSlugField(slug_field='slug', many=True) limit_events = EventSlugField(slug_field='slug', many=True)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
class Meta: class Meta:
model = Team model = Team
fields = ( fields = (
@@ -86,6 +84,28 @@ class TeamSerializer(serializers.ModelSerializer):
return data return data
class DeviceSerializer(serializers.ModelSerializer):
limit_events = EventSlugField(slug_field='slug', many=True)
device_id = serializers.IntegerField(read_only=True)
unique_serial = serializers.CharField(read_only=True)
hardware_brand = serializers.CharField(read_only=True)
hardware_model = serializers.CharField(read_only=True)
software_brand = serializers.CharField(read_only=True)
software_version = serializers.CharField(read_only=True)
created = serializers.DateTimeField(read_only=True)
revoked = serializers.BooleanField(read_only=True)
initialized = serializers.DateTimeField(read_only=True)
initialization_token = serializers.DateTimeField(read_only=True)
class Meta:
model = Device
fields = (
'device_id', 'unique_serial', 'initialization_token', 'all_events', 'limit_events',
'revoked', 'name', 'created', 'initialized', 'hardware_brand', 'hardware_model',
'software_brand', 'software_version'
)
class TeamInviteSerializer(serializers.ModelSerializer): class TeamInviteSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = TeamInvite model = TeamInvite

View File

@@ -21,6 +21,7 @@ orga_router.register(r'webhooks', webhooks.WebHookViewSet)
orga_router.register(r'seatingplans', organizer.SeatingPlanViewSet) orga_router.register(r'seatingplans', organizer.SeatingPlanViewSet)
orga_router.register(r'giftcards', organizer.GiftCardViewSet) orga_router.register(r'giftcards', organizer.GiftCardViewSet)
orga_router.register(r'teams', organizer.TeamViewSet) orga_router.register(r'teams', organizer.TeamViewSet)
orga_router.register(r'devices', organizer.DeviceViewSet)
team_router = routers.DefaultRouter() team_router = routers.DefaultRouter()
team_router.register(r'members', organizer.TeamMemberViewSet) team_router.register(r'members', organizer.TeamMemberViewSet)

View File

@@ -6,20 +6,22 @@ from django.shortcuts import get_object_or_404
from django.utils.functional import cached_property from django.utils.functional import cached_property
from django_filters.rest_framework import DjangoFilterBackend, FilterSet from django_filters.rest_framework import DjangoFilterBackend, FilterSet
from django_scopes import scopes_disabled from django_scopes import scopes_disabled
from rest_framework import filters, serializers, status, viewsets from rest_framework import filters, mixins, serializers, status, viewsets
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.exceptions import MethodNotAllowed, PermissionDenied from rest_framework.exceptions import MethodNotAllowed, PermissionDenied
from rest_framework.mixins import CreateModelMixin, DestroyModelMixin from rest_framework.mixins import CreateModelMixin, DestroyModelMixin
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.viewsets import GenericViewSet
from pretix.api.models import OAuthAccessToken from pretix.api.models import OAuthAccessToken
from pretix.api.serializers.organizer import ( from pretix.api.serializers.organizer import (
GiftCardSerializer, OrganizerSerializer, SeatingPlanSerializer, DeviceSerializer, GiftCardSerializer, OrganizerSerializer,
TeamAPITokenSerializer, TeamInviteSerializer, TeamMemberSerializer, SeatingPlanSerializer, TeamAPITokenSerializer, TeamInviteSerializer,
TeamSerializer, TeamMemberSerializer, TeamSerializer,
) )
from pretix.base.models import ( from pretix.base.models import (
GiftCard, Organizer, SeatingPlan, Team, TeamAPIToken, TeamInvite, User, Device, GiftCard, Organizer, SeatingPlan, Team, TeamAPIToken, TeamInvite,
User,
) )
from pretix.helpers.dicts import merge_dicts from pretix.helpers.dicts import merge_dicts
@@ -353,3 +355,44 @@ class TeamAPITokenViewSet(CreateModelMixin, DestroyModelMixin, viewsets.ReadOnly
serializer = self.get_serializer_class()(instance) serializer = self.get_serializer_class()(instance)
headers = self.get_success_headers(serializer.data) headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_200_OK, headers=headers) return Response(serializer.data, status=status.HTTP_200_OK, headers=headers)
class DeviceViewSet(mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.ListModelMixin,
GenericViewSet):
serializer_class = DeviceSerializer
queryset = Device.objects.none()
permission = 'can_change_organizer_settings'
write_permission = 'can_change_organizer_settings'
lookup_field = 'device_id'
def get_queryset(self):
return self.request.organizer.devices.order_by('pk')
def get_serializer_context(self):
ctx = super().get_serializer_context()
ctx['organizer'] = self.request.organizer
return ctx
@transaction.atomic()
def perform_create(self, serializer):
inst = serializer.save(organizer=self.request.organizer)
inst.log_action(
'pretix.device.created',
user=self.request.user,
auth=self.request.auth,
data=merge_dicts(self.request.data, {'id': inst.pk})
)
@transaction.atomic()
def perform_update(self, serializer):
inst = serializer.save()
inst.log_action(
'pretix.device.changed',
user=self.request.user,
auth=self.request.auth,
data=self.request.data
)
return inst

View File

@@ -144,6 +144,11 @@ org_permission_sub_urls = [
('get', 'can_manage_gift_cards', 'giftcards/1/', 404), ('get', 'can_manage_gift_cards', 'giftcards/1/', 404),
('put', 'can_manage_gift_cards', 'giftcards/1/', 404), ('put', 'can_manage_gift_cards', 'giftcards/1/', 404),
('patch', 'can_manage_gift_cards', 'giftcards/1/', 404), ('patch', 'can_manage_gift_cards', 'giftcards/1/', 404),
('get', 'can_change_organizer_settings', 'devices/', 200),
('post', 'can_change_organizer_settings', 'devices/', 400),
('get', 'can_change_organizer_settings', 'devices/1/', 404),
('put', 'can_change_organizer_settings', 'devices/1/', 404),
('patch', 'can_change_organizer_settings', 'devices/1/', 404),
('get', 'can_change_teams', 'teams/', 200), ('get', 'can_change_teams', 'teams/', 200),
('post', 'can_change_teams', 'teams/', 400), ('post', 'can_change_teams', 'teams/', 400),
('get', 'can_change_teams', 'teams/{team_id}/', 200), ('get', 'can_change_teams', 'teams/{team_id}/', 200),