Refs #654 -- Add writable API for subevents (#1217)

- [x] Write operations for subevents
- [x] Tests
- [x] Documentation
This commit is contained in:
Martin Gross
2019-03-21 21:40:59 +01:00
committed by Raphael Michel
parent 516fab52da
commit ca7d55082b
6 changed files with 817 additions and 8 deletions

View File

@@ -191,8 +191,8 @@ class SubEventItemVariationSerializer(I18nAwareModelSerializer):
class SubEventSerializer(I18nAwareModelSerializer):
item_price_overrides = SubEventItemSerializer(source='subeventitem_set', many=True)
variation_price_overrides = SubEventItemVariationSerializer(source='subeventitemvariation_set', many=True)
item_price_overrides = SubEventItemSerializer(source='subeventitem_set', many=True, required=False)
variation_price_overrides = SubEventItemVariationSerializer(source='subeventitemvariation_set', many=True, required=False)
event = SlugRelatedField(slug_field='slug', read_only=True)
meta_data = MetaDataField(source='*')
@@ -202,6 +202,103 @@ class SubEventSerializer(I18nAwareModelSerializer):
'presale_start', 'presale_end', 'location', 'event',
'item_price_overrides', 'variation_price_overrides', 'meta_data')
def validate(self, data):
data = super().validate(data)
event = self.context['request'].event
full_data = self.to_internal_value(self.to_representation(self.instance)) if self.instance else {}
full_data.update(data)
Event.clean_dates(data.get('date_from'), data.get('date_to'))
Event.clean_presale(data.get('presale_start'), data.get('presale_end'))
SubEvent.clean_items(event, [item['item'] for item in full_data.get('subeventitem_set')])
SubEvent.clean_variations(event, [item['variation'] for item in full_data.get('subeventitemvariation_set')])
return data
def validate_item_price_overrides(self, data):
return list(filter(lambda i: 'item' in i, data))
def validate_variation_price_overrides(self, data):
return list(filter(lambda i: 'variation' in i, data))
@cached_property
def meta_properties(self):
return {
p.name: p for p in self.context['request'].organizer.meta_properties.all()
}
def validate_meta_data(self, value):
for key in value['meta_data'].keys():
if key not in self.meta_properties:
raise ValidationError(_('Meta data property \'{name}\' does not exist.').format(name=key))
return value
@transaction.atomic
def create(self, validated_data):
item_price_overrides_data = validated_data.pop('subeventitem_set') if 'subeventitem_set' in validated_data else {}
variation_price_overrides_data = validated_data.pop('subeventitemvariation_set') if 'subeventitemvariation_set' in validated_data else {}
meta_data = validated_data.pop('meta_data', None)
subevent = super().create(validated_data)
for item_price_override_data in item_price_overrides_data:
SubEventItem.objects.create(subevent=subevent, **item_price_override_data)
for variation_price_override_data in variation_price_overrides_data:
SubEventItemVariation.objects.create(subevent=subevent, **variation_price_override_data)
# Meta data
if meta_data is not None:
for key, value in meta_data.items():
subevent.meta_values.create(
property=self.meta_properties.get(key),
value=value
)
return subevent
@transaction.atomic
def update(self, instance, validated_data):
item_price_overrides_data = validated_data.pop('subeventitem_set') if 'subeventitem_set' in validated_data else {}
variation_price_overrides_data = validated_data.pop('subeventitemvariation_set') if 'subeventitemvariation_set' in validated_data else {}
meta_data = validated_data.pop('meta_data', None)
subevent = super().update(instance, validated_data)
existing_item_overrides = {item.item: item.id for item in SubEventItem.objects.filter(subevent=subevent)}
for item_price_override_data in item_price_overrides_data:
id = existing_item_overrides.pop(item_price_override_data['item'], None)
SubEventItem(id=id, subevent=subevent, **item_price_override_data).save()
SubEventItem.objects.filter(id__in=existing_item_overrides.values()).delete()
existing_variation_overrides = {item.variation: item.id for item in SubEventItemVariation.objects.filter(subevent=subevent)}
for variation_price_override_data in variation_price_overrides_data:
id = existing_variation_overrides.pop(variation_price_override_data['variation'], None)
SubEventItemVariation(id=id, subevent=subevent, **variation_price_override_data).save()
SubEventItemVariation.objects.filter(id__in=existing_variation_overrides.values()).delete()
# Meta data
if meta_data is not None:
current = {mv.property: mv for mv in subevent.meta_values.select_related('property')}
for key, value in meta_data.items():
prop = self.meta_properties.get(key)
if prop in current:
current[prop].value = value
current[prop].save()
else:
subevent.meta_values.create(
property=self.meta_properties.get(key),
value=value
)
for prop, current_object in current.items():
if prop.name not in meta_data:
current_object.delete()
return subevent
class TaxRuleSerializer(CountryFieldMixin, I18nAwareModelSerializer):
class Meta:

View File

@@ -217,9 +217,10 @@ class SubEventFilter(FilterSet):
return queryset.exclude(expr)
class SubEventViewSet(ConditionalListView, viewsets.ReadOnlyModelViewSet):
class SubEventViewSet(ConditionalListView, viewsets.ModelViewSet):
serializer_class = SubEventSerializer
queryset = ItemCategory.objects.none()
write_permission = 'can_change_event_settings'
filter_backends = (DjangoFilterBackend, filters.OrderingFilter)
filterset_class = SubEventFilter
@@ -240,6 +241,42 @@ class SubEventViewSet(ConditionalListView, viewsets.ReadOnlyModelViewSet):
'subeventitem_set', 'subeventitemvariation_set'
)
def perform_update(self, serializer):
super().perform_update(serializer)
serializer.instance.log_action(
'pretix.subevent.changed',
user=self.request.user,
auth=self.request.auth,
data=self.request.data
)
def perform_create(self, serializer):
serializer.save(event=self.request.event)
serializer.instance.log_action(
'pretix.subevent.added',
user=self.request.user,
auth=self.request.auth,
data=self.request.data
)
def perform_destroy(self, instance):
if not instance.allow_delete():
raise PermissionDenied('The sub-event can not be deleted as it has already been used in orders. Please set'
' \'active\' to false instead to hide it from users.')
try:
with transaction.atomic():
instance.log_action(
'pretix.subevent.deleted',
user=self.request.user,
auth=self.request.auth,
data=self.request.data
)
super().perform_destroy(instance)
except ProtectedError:
raise PermissionDenied('The sub-event could not be deleted as some constraints (e.g. data created by '
'plug-ins) do not allow it.')
class TaxRuleViewSet(ConditionalListView, viewsets.ModelViewSet):
serializer_class = TaxRuleSerializer

View File

@@ -936,6 +936,18 @@ class SubEvent(EventMixin, LoggedModel):
if self.event:
self.event.cache.clear()
@staticmethod
def clean_items(event, items):
for item in items:
if event != item.event:
raise ValidationError(_('One or more items do not belong to this event.'))
@staticmethod
def clean_variations(event, variations):
for variation in variations:
if event != variation.item.event:
raise ValidationError(_('One or more variations do not belong to this event.'))
def generate_invite_token():
return get_random_string(length=32, allowed_chars=string.ascii_lowercase + string.digits)