class AnswerSerializer(HyperlinkedModelSerializer):
    creator = SlugRelatedField(read_only=True, slug_field="username")
    question = HyperlinkedRelatedField(
        view_name="api:reader-studies-question-detail",
        queryset=Question.objects.all(),
    )
    images = HyperlinkedRelatedField(many=True,
                                     queryset=Image.objects.all(),
                                     view_name="api:image-detail")
    answer_image = HyperlinkedRelatedField(read_only=True,
                                           view_name="api:image-detail")

    def validate(self, attrs):
        answer = attrs.get("answer")
        if self.instance:
            if (not self.instance.question.reader_study.
                    allow_answer_modification):
                raise ValidationError(
                    "This reader study does not allow answer modification.")
            if list(attrs.keys()) != ["answer"]:
                raise ValidationError("Only the answer field can be modified.")
            question = self.instance.question
            images = self.instance.images.all()
            creator = self.instance.creator
        else:
            question = attrs.get("question")
            images = attrs.get("images")
            creator = self.context.get("request").user
        Answer.validate(
            creator=creator,
            question=question,
            answer=answer,
            images=images,
            instance=self.instance,
        )

        if self.instance:
            on_commit(lambda: add_scores.apply_async(
                kwargs={
                    "instance_pk": str(self.instance.pk),
                    "pk_set": list(
                        map(str, images.values_list("pk", flat=True))),
                }))
        return attrs if not self.instance else {"answer": answer}

    class Meta:
        model = Answer
        fields = (
            "answer",
            "api_url",
            "created",
            "creator",
            "images",
            "pk",
            "question",
            "modified",
            "answer_image",
        )
        swagger_schema_fields = {
            "properties": {
                "answer": {
                    "title": "Answer",
                    **ANSWER_TYPE_SCHEMA
                }
            }
        }
Beispiel #2
0
class SubEventSerializer(I18nAwareModelSerializer):
    item_price_overrides = SubEventItemSerializer(source='subeventitem_set',
                                                  many=True,
                                                  required=False)
    variation_price_overrides = SubEventItemVariationSerializer(
        source='subeventitemvariation_set', many=True, required=False)
    seat_category_mapping = SeatCategoryMappingField(source='*',
                                                     required=False)
    event = SlugRelatedField(slug_field='slug', read_only=True)
    meta_data = MetaDataField(source='*')

    class Meta:
        model = SubEvent
        fields = ('id', 'name', 'date_from', 'date_to', 'active',
                  'date_admission', 'presale_start', 'presale_end', 'location',
                  'geo_lat', 'geo_lon', 'event', 'is_public', 'frontpage_text',
                  'seating_plan', 'item_price_overrides',
                  'variation_price_overrides', 'meta_data',
                  'seat_category_mapping', 'last_modified')

    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))

    def validate_seating_plan(self, value):
        if value and value.organizer != self.context['request'].organizer:
            raise ValidationError('Invalid seating plan.')
        if self.instance and self.instance.pk:
            try:
                validate_plan_change(self.context['request'].event,
                                     self.instance, value)
            except SeatProtected as e:
                raise ValidationError(str(e))
        return value

    def validate_seat_category_mapping(self, value):
        item_cache = {
            i.pk: i
            for i in self.context['request'].event.items.all()
        }
        result = {}
        for k, item in value['seat_category_mapping'].items():
            if item not in item_cache:
                raise ValidationError(
                    'Item \'{id}\' does not exist.'.format(id=item))
            result[k] = item_cache[item]
        return {'seat_category_mapping': result}

    @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, v in value['meta_data'].items():
            if key not in self.meta_properties:
                raise ValidationError(
                    _('Meta data property \'{name}\' does not exist.').format(
                        name=key))
            if self.meta_properties[key].allowed_values:
                if v not in [
                        _v.strip() for _v in
                        self.meta_properties[key].allowed_values.splitlines()
                ]:
                    raise ValidationError(
                        _('Meta data property \'{name}\' does not allow value \'{value}\'.'
                          ).format(name=key, value=v))
        return value

    @cached_property
    def ignored_meta_properties(self):
        perm_holder = (self.context['request'].auth if isinstance(
            self.context['request'].auth,
            (Device, TeamAPIToken)) else self.context['request'].user)
        if perm_holder.has_organizer_permission(
                'can_change_organizer_settings',
                request=self.context['request']):
            return []
        return [k for k, p in self.meta_properties.items() if p.protected]

    @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)
        seat_category_mapping = validated_data.pop('seat_category_mapping',
                                                   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():
                if key not in self.ignored_meta_properties:
                    subevent.meta_values.create(
                        property=self.meta_properties.get(key), value=value)

        # Seats
        if subevent.seating_plan:
            if seat_category_mapping is not None:
                for key, value in seat_category_mapping.items():
                    self.context[
                        'request'].event.seat_category_mappings.create(
                            product=value,
                            layout_category=key,
                            subevent=subevent)
            generate_seats(
                self.context['request'].event, subevent, subevent.seating_plan,
                {
                    m.layout_category: m.product
                    for m in self.context['request'].event.
                    seat_category_mappings.select_related('product').filter(
                        subevent=subevent)
                })

        return subevent

    @transaction.atomic
    def update(self, instance, validated_data):
        item_price_overrides_data = validated_data.pop('subeventitem_set',
                                                       None)
        variation_price_overrides_data = validated_data.pop(
            'subeventitemvariation_set', None)
        meta_data = validated_data.pop('meta_data', None)
        seat_category_mapping = validated_data.pop('seat_category_mapping',
                                                   None)
        subevent = super().update(instance, validated_data)

        if item_price_overrides_data is not None:
            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()

        if variation_price_overrides_data is not None:
            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():
                if key not in self.ignored_meta_properties:
                    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 self.ignored_meta_properties:
                    if prop.name not in meta_data:
                        current_object.delete()

        # Seats
        if seat_category_mapping is not None or (
                'seating_plan' in validated_data
                and validated_data['seating_plan'] is None):
            current_mappings = {
                m.layout_category: m
                for m in self.context['request'].event.seat_category_mappings.
                filter(subevent=subevent)
            }
            if not subevent.seating_plan:
                seat_category_mapping = {}
            for key, value in seat_category_mapping.items():
                if key in current_mappings:
                    m = current_mappings.pop(key)
                    m.product = value
                    m.save()
                else:
                    self.context[
                        'request'].event.seat_category_mappings.create(
                            product=value,
                            layout_category=key,
                            subevent=subevent)
            for m in current_mappings.values():
                m.delete()
        if 'seating_plan' in validated_data or seat_category_mapping is not None:
            generate_seats(
                self.context['request'].event, subevent, subevent.seating_plan,
                {
                    m.layout_category: m.product
                    for m in self.context['request'].event.
                    seat_category_mappings.select_related('product').filter(
                        subevent=subevent)
                })

        return subevent