diff --git a/doc/api/resources/items.rst b/doc/api/resources/items.rst index 1ee80386b3..d5a478250b 100644 --- a/doc/api/resources/items.rst +++ b/doc/api/resources/items.rst @@ -36,8 +36,8 @@ admission boolean ``true`` for it (such as primary tickets) and ``false`` for others (such as add-ons or merchandise). position integer An integer, used for sorting -picture string A product picture to be displayed in the shop - (read-only, can be ``null``). +picture file A product picture to be displayed in the shop + (can be ``null``). sales_channels list of strings Sales channels this product is available on, such as ``"web"`` or ``"resellers"``. Defaults to ``["web"]``. available_from datetime The first date time at which this item can be bought diff --git a/src/pretix/api/serializers/fields.py b/src/pretix/api/serializers/fields.py index 3bd949d681..f23a7ad82d 100644 --- a/src/pretix/api/serializers/fields.py +++ b/src/pretix/api/serializers/fields.py @@ -27,3 +27,50 @@ class ListMultipleChoiceField(serializers.MultipleChoiceField): ] return remove_duplicates_from_list(representation_data) + + +class UploadedFileField(serializers.Field): + default_error_messages = { + 'required': 'No file was submitted.', + 'not_found': 'The submitted file ID was not found.', + 'invalid_type': 'The submitted file has a file type that is not allowed in this field.', + 'size': 'The submitted file is too large to be used in this field.', + } + + def __init__(self, *args, **kwargs): + self.allowed_types = kwargs.pop('allowed_types', None) + self.max_size = kwargs.pop('max_size', None) + super().__init__(*args, **kwargs) + + def to_internal_value(self, data): + from pretix.base.models import CachedFile + + request = self.context.get('request', None) + try: + cf = CachedFile.objects.get( + session_key=f'api-upload-{str(type(request.user or request.auth))}-{(request.user or request.auth).pk}', + file__isnull=False, + pk=data[len("file:"):], + ) + except CachedFile.DoesNotExist: + self.fail('not_found') + + if self.allowed_types and cf.type not in self.allowed_types: + self.fail('invalid_type') + if self.max_size and cf.file.size > self.max_size: + self.fail('size') + + return cf.file + + def to_representation(self, value): + if not value: + return None + + try: + url = value.url + except AttributeError: + return None + request = self.context.get('request', None) + if request is not None: + return request.build_absolute_uri(url) + return url diff --git a/src/pretix/api/serializers/item.py b/src/pretix/api/serializers/item.py index 403601aee8..36018d9d67 100644 --- a/src/pretix/api/serializers/item.py +++ b/src/pretix/api/serializers/item.py @@ -7,6 +7,7 @@ from django.utils.translation import gettext_lazy as _ from rest_framework import serializers from pretix.api.serializers.event import MetaDataField +from pretix.api.serializers.fields import UploadedFileField from pretix.api.serializers.i18n import I18nAwareModelSerializer from pretix.base.models import ( Item, ItemAddOn, ItemBundle, ItemCategory, ItemMetaValue, ItemVariation, @@ -113,6 +114,9 @@ class ItemSerializer(I18nAwareModelSerializer): variations = InlineItemVariationSerializer(many=True, required=False) tax_rate = ItemTaxRateField(source='*', read_only=True) meta_data = MetaDataField(required=False, source='*') + picture = UploadedFileField(required=False, allow_null=True, allowed_types=( + 'image/png', 'image/jpeg', 'image/gif' + ), max_size=10 * 1024 * 1024) class Meta: model = Item @@ -123,7 +127,7 @@ class ItemSerializer(I18nAwareModelSerializer): 'min_per_order', 'max_per_order', 'checkin_attention', 'has_variations', 'variations', 'addons', 'bundles', 'original_price', 'require_approval', 'generate_tickets', 'show_quota_left', 'hidden_if_available', 'allow_waitinglist', 'issue_giftcard', 'meta_data') - read_only_fields = ('has_variations', 'picture') + read_only_fields = ('has_variations',) def validate(self, data): data = super().validate(data)