Ejemplo n.º 1
0
 def save(self):
     failed = False
     for form in self.forms:
         # Every form represents a CartPosition or OrderPosition with questions attached
         if not form.is_valid():
             failed = True
         else:
             # This form was correctly filled, so we store the data as
             # answers to the questions / in the CartPosition object
             for k, v in form.cleaned_data.items():
                 if k == 'attendee_name':
                     form.pos.attendee_name = v if v != '' else None
                     form.pos.save()
                 elif k.startswith('question_') and v is not None:
                     field = form.fields[k]
                     if hasattr(field, 'answer'):
                         # We already have a cached answer object, so we don't
                         # have to create a new one
                         if v == '':
                             field.answer.delete()
                         else:
                             self._save_to_answer(field, field.answer, v)
                             field.answer.save()
                     elif v != '':
                         answer = QuestionAnswer(
                             cartposition=(form.pos if isinstance(form.pos, CartPosition) else None),
                             orderposition=(form.pos if isinstance(form.pos, OrderPosition) else None),
                             question=field.question,
                         )
                         self._save_to_answer(field, answer, v)
                         answer.save()
     return not failed
Ejemplo n.º 2
0
    def save(self):
        failed = False
        for form in self.forms:
            meta_info = form.pos.meta_info_data
            # Every form represents a CartPosition or OrderPosition with questions attached
            if not form.is_valid():
                failed = True
            else:
                # This form was correctly filled, so we store the data as
                # answers to the questions / in the CartPosition object
                for k, v in form.cleaned_data.items():
                    if k == 'attendee_name_parts':
                        form.pos.attendee_name_parts = v if v else None
                    elif k == 'attendee_email':
                        form.pos.attendee_email = v if v != '' else None
                    elif k == 'company':
                        form.pos.company = v if v != '' else None
                    elif k == 'street':
                        form.pos.street = v if v != '' else None
                    elif k == 'zipcode':
                        form.pos.zipcode = v if v != '' else None
                    elif k == 'city':
                        form.pos.city = v if v != '' else None
                    elif k == 'country':
                        form.pos.country = v if v != '' else None
                    elif k == 'state':
                        form.pos.state = v if v != '' else None
                    elif k.startswith('question_'):
                        field = form.fields[k]
                        if hasattr(field, 'answer'):
                            # We already have a cached answer object, so we don't
                            # have to create a new one
                            if v == '' or v is None or (isinstance(field, forms.FileField) and v is False) \
                                    or (isinstance(v, QuerySet) and not v.exists()):
                                if field.answer.file:
                                    field.answer.file.delete()
                                field.answer.delete()
                            else:
                                self._save_to_answer(field, field.answer, v)
                                field.answer.save()
                        elif v != '' and v is not None:
                            answer = QuestionAnswer(
                                cartposition=(form.pos if isinstance(form.pos, CartPosition) else None),
                                orderposition=(form.pos if isinstance(form.pos, OrderPosition) else None),
                                question=field.question,
                            )
                            self._save_to_answer(field, answer, v)
                            answer.save()
                    else:
                        meta_info.setdefault('question_form_data', {})
                        if v is None:
                            if k in meta_info['question_form_data']:
                                del meta_info['question_form_data'][k]
                        else:
                            meta_info['question_form_data'][k] = v

            form.pos.meta_info = json.dumps(meta_info)
            form.pos.save()
        return not failed
Ejemplo n.º 3
0
    def save(self):
        failed = False
        for form in self.forms:
            meta_info = form.pos.meta_info_data
            # Every form represents a CartPosition or OrderPosition with questions attached
            if not form.is_valid():
                failed = True
            else:
                # This form was correctly filled, so we store the data as
                # answers to the questions / in the CartPosition object
                for k, v in form.cleaned_data.items():
                    if k == 'attendee_name_parts':
                        form.pos.attendee_name_parts = v if v else None
                        form.pos.save()
                    elif k == 'attendee_email':
                        form.pos.attendee_email = v if v != '' else None
                        form.pos.save()
                    elif k.startswith('question_'):
                        field = form.fields[k]
                        if hasattr(field, 'answer'):
                            # We already have a cached answer object, so we don't
                            # have to create a new one
                            if v == '' or v is None or (isinstance(field, forms.FileField) and v is False) \
                                    or (isinstance(v, QuerySet) and not v.exists()):
                                if field.answer.file:
                                    field.answer.file.delete()
                                field.answer.delete()
                            else:
                                self._save_to_answer(field, field.answer, v)
                                field.answer.save()
                        elif v != '' and v is not None:
                            answer = QuestionAnswer(
                                cartposition=(form.pos if isinstance(form.pos, CartPosition) else None),
                                orderposition=(form.pos if isinstance(form.pos, OrderPosition) else None),
                                question=field.question,
                            )
                            self._save_to_answer(field, answer, v)
                            answer.save()
                    else:
                        meta_info.setdefault('question_form_data', {})
                        if v is None:
                            if k in meta_info['question_form_data']:
                                del meta_info['question_form_data'][k]
                        else:
                            meta_info['question_form_data'][k] = v

            form.pos.meta_info = json.dumps(meta_info)
            form.pos.save(update_fields=['meta_info'])
        return not failed
Ejemplo n.º 4
0
 def assign(self, value, order, position, invoice_address, **kwargs):
     if value:
         if not hasattr(order, '_answers'):
             order._answers = []
         if isinstance(value, QuestionOption):
             a = QuestionAnswer(orderposition=position, question=self.q, answer=str(value))
             a._options = [value]
             order._answers.append(a)
         elif isinstance(value, list):
             a = QuestionAnswer(orderposition=position, question=self.q, answer=', '.join(str(v) for v in value))
             a._options = value
             order._answers.append(a)
         else:
             order._answers.append(QuestionAnswer(question=self.q, answer=str(value), orderposition=position))
Ejemplo n.º 5
0
    def create(self, validated_data):
        fees_data = validated_data.pop('fees') if 'fees' in validated_data else []
        positions_data = validated_data.pop('positions') if 'positions' in validated_data else []
        payment_provider = validated_data.pop('payment_provider', None)
        payment_info = validated_data.pop('payment_info', '{}')
        payment_date = validated_data.pop('payment_date', now())
        force = validated_data.pop('force', False)
        simulate = validated_data.pop('simulate', False)
        self._send_mail = validated_data.pop('send_mail', False)

        if 'invoice_address' in validated_data:
            iadata = validated_data.pop('invoice_address')
            name = iadata.pop('name', '')
            if name and not iadata.get('name_parts'):
                iadata['name_parts'] = {
                    '_legacy': name
                }
            ia = InvoiceAddress(**iadata)
        else:
            ia = None

        lockfn = self.context['event'].lock
        if simulate:
            lockfn = NoLockManager
        with lockfn() as now_dt:
            free_seats = set()
            seats_seen = set()
            consume_carts = validated_data.pop('consume_carts', [])
            delete_cps = []
            quota_avail_cache = {}
            v_budget = {}
            voucher_usage = Counter()
            if consume_carts:
                for cp in CartPosition.objects.filter(
                    event=self.context['event'], cart_id__in=consume_carts, expires__gt=now()
                ):
                    quotas = (cp.variation.quotas.filter(subevent=cp.subevent)
                              if cp.variation else cp.item.quotas.filter(subevent=cp.subevent))
                    for quota in quotas:
                        if quota not in quota_avail_cache:
                            quota_avail_cache[quota] = list(quota.availability())
                        if quota_avail_cache[quota][1] is not None:
                            quota_avail_cache[quota][1] += 1
                    if cp.voucher:
                        voucher_usage[cp.voucher] -= 1
                    if cp.expires > now_dt:
                        if cp.seat:
                            free_seats.add(cp.seat)
                    delete_cps.append(cp)

            errs = [{} for p in positions_data]

            for i, pos_data in enumerate(positions_data):

                if pos_data.get('voucher'):
                    v = pos_data['voucher']

                    if pos_data.get('addon_to'):
                        errs[i]['voucher'] = ['Vouchers are currently not supported for add-on products.']
                        continue

                    if not v.applies_to(pos_data['item'], pos_data.get('variation')):
                        errs[i]['voucher'] = [error_messages['voucher_invalid_item']]
                        continue

                    if v.subevent_id and pos_data.get('subevent').pk != v.subevent_id:
                        errs[i]['voucher'] = [error_messages['voucher_invalid_subevent']]
                        continue

                    if v.valid_until is not None and v.valid_until < now_dt:
                        errs[i]['voucher'] = [error_messages['voucher_expired']]
                        continue

                    voucher_usage[v] += 1
                    if voucher_usage[v] > 0:
                        redeemed_in_carts = CartPosition.objects.filter(
                            Q(voucher=pos_data['voucher']) & Q(event=self.context['event']) & Q(expires__gte=now_dt)
                        ).exclude(pk__in=[cp.pk for cp in delete_cps])
                        v_avail = v.max_usages - v.redeemed - redeemed_in_carts.count()
                        if v_avail < voucher_usage[v]:
                            errs[i]['voucher'] = [
                                'The voucher has already been used the maximum number of times.'
                            ]

                    if v.budget is not None:
                        price = pos_data.get('price')
                        if price is None:
                            price = get_price(
                                item=pos_data.get('item'),
                                variation=pos_data.get('variation'),
                                voucher=v,
                                custom_price=None,
                                subevent=pos_data.get('subevent'),
                                addon_to=pos_data.get('addon_to'),
                                invoice_address=ia,
                            ).gross
                        pbv = get_price(
                            item=pos_data['item'],
                            variation=pos_data.get('variation'),
                            voucher=None,
                            custom_price=None,
                            subevent=pos_data.get('subevent'),
                            addon_to=pos_data.get('addon_to'),
                            invoice_address=ia,
                        )

                        if v not in v_budget:
                            v_budget[v] = v.budget - v.budget_used()
                        disc = pbv.gross - price
                        if disc > v_budget[v]:
                            new_disc = v_budget[v]
                            v_budget[v] -= new_disc
                            if new_disc == Decimal('0.00') or pos_data.get('price') is not None:
                                errs[i]['voucher'] = [
                                    'The voucher has a remaining budget of {}, therefore a discount of {} can not be '
                                    'given.'.format(v_budget[v] + new_disc, disc)
                                ]
                                continue
                            pos_data['price'] = price + (disc - new_disc)
                        else:
                            v_budget[v] -= disc

                seated = pos_data.get('item').seat_category_mappings.filter(subevent=pos_data.get('subevent')).exists()
                if pos_data.get('seat'):
                    if not seated:
                        errs[i]['seat'] = ['The specified product does not allow to choose a seat.']
                    try:
                        seat = self.context['event'].seats.get(seat_guid=pos_data['seat'], subevent=pos_data.get('subevent'))
                    except Seat.DoesNotExist:
                        errs[i]['seat'] = ['The specified seat does not exist.']
                    else:
                        pos_data['seat'] = seat
                        if (seat not in free_seats and not seat.is_available(sales_channel=validated_data.get('sales_channel', 'web'))) or seat in seats_seen:
                            errs[i]['seat'] = [gettext_lazy('The selected seat "{seat}" is not available.').format(seat=seat.name)]
                        seats_seen.add(seat)
                elif seated:
                    errs[i]['seat'] = ['The specified product requires to choose a seat.']

            if not force:
                for i, pos_data in enumerate(positions_data):
                    if pos_data.get('voucher'):
                        if pos_data['voucher'].allow_ignore_quota or pos_data['voucher'].block_quota:
                            continue

                    new_quotas = (pos_data.get('variation').quotas.filter(subevent=pos_data.get('subevent'))
                                  if pos_data.get('variation')
                                  else pos_data.get('item').quotas.filter(subevent=pos_data.get('subevent')))
                    if len(new_quotas) == 0:
                        errs[i]['item'] = [gettext_lazy('The product "{}" is not assigned to a quota.').format(
                            str(pos_data.get('item'))
                        )]
                    else:
                        for quota in new_quotas:
                            if quota not in quota_avail_cache:
                                quota_avail_cache[quota] = list(quota.availability())

                            if quota_avail_cache[quota][1] is not None:
                                quota_avail_cache[quota][1] -= 1
                                if quota_avail_cache[quota][1] < 0:
                                    errs[i]['item'] = [
                                        gettext_lazy('There is not enough quota available on quota "{}" to perform the operation.').format(
                                            quota.name
                                        )
                                    ]

            if any(errs):
                raise ValidationError({'positions': errs})

            if validated_data.get('locale', None) is None:
                validated_data['locale'] = self.context['event'].settings.locale
            order = Order(event=self.context['event'], **validated_data)
            order.set_expires(subevents=[p.get('subevent') for p in positions_data])
            order.meta_info = "{}"
            order.total = Decimal('0.00')
            if simulate:
                order = WrappedModel(order)
                order.last_modified = now()
                order.code = 'PREVIEW'
            else:
                order.save()

            if ia:
                if not simulate:
                    ia.order = order
                    ia.save()
                else:
                    order.invoice_address = ia
                    ia.last_modified = now()

            pos_map = {}
            for pos_data in positions_data:
                answers_data = pos_data.pop('answers', [])
                addon_to = pos_data.pop('addon_to', None)
                attendee_name = pos_data.pop('attendee_name', '')
                if attendee_name and not pos_data.get('attendee_name_parts'):
                    pos_data['attendee_name_parts'] = {
                        '_legacy': attendee_name
                    }
                pos = OrderPosition(**pos_data)
                if simulate:
                    pos.order = order._wrapped
                else:
                    pos.order = order
                if addon_to:
                    pos.addon_to = pos_map[addon_to]

                if pos.price is None:
                    price = get_price(
                        item=pos.item,
                        variation=pos.variation,
                        voucher=pos.voucher,
                        custom_price=None,
                        subevent=pos.subevent,
                        addon_to=pos.addon_to,
                        invoice_address=ia,
                    )
                    pos.price = price.gross
                    pos.tax_rate = price.rate
                    pos.tax_value = price.tax
                    pos.tax_rule = pos.item.tax_rule
                else:
                    pos._calculate_tax()

                pos.price_before_voucher = get_price(
                    item=pos.item,
                    variation=pos.variation,
                    voucher=None,
                    custom_price=None,
                    subevent=pos.subevent,
                    addon_to=pos.addon_to,
                    invoice_address=ia,
                ).gross

                if simulate:
                    pos = WrappedModel(pos)
                    pos.id = 0
                    answers = []
                    for answ_data in answers_data:
                        options = answ_data.pop('options', [])
                        answ = WrappedModel(QuestionAnswer(**answ_data))
                        answ.options = WrappedList(options)
                        answers.append(answ)
                    pos.answers = answers
                    pos.pseudonymization_id = "PREVIEW"
                else:
                    if pos.voucher:
                        Voucher.objects.filter(pk=pos.voucher.pk).update(redeemed=F('redeemed') + 1)
                    pos.save()
                    for answ_data in answers_data:
                        options = answ_data.pop('options', [])
                        answ = pos.answers.create(**answ_data)
                        answ.options.add(*options)
                pos_map[pos.positionid] = pos

            if not simulate:
                for cp in delete_cps:
                    cp.delete()

        order.total = sum([p.price for p in pos_map.values()])
        fees = []
        for fee_data in fees_data:
            is_percentage = fee_data.pop('_treat_value_as_percentage', False)
            if is_percentage:
                fee_data['value'] = round_decimal(order.total * (fee_data['value'] / Decimal('100.00')),
                                                  self.context['event'].currency)
            is_split_taxes = fee_data.pop('_split_taxes_like_products', False)

            if is_split_taxes:
                d = defaultdict(lambda: Decimal('0.00'))
                trz = TaxRule.zero()
                for p in pos_map.values():
                    tr = p.tax_rule
                    d[tr] += p.price - p.tax_value

                base_values = sorted([tuple(t) for t in d.items()], key=lambda t: (t[0] or trz).rate)
                sum_base = sum(t[1] for t in base_values)
                fee_values = [(t[0], round_decimal(fee_data['value'] * t[1] / sum_base, self.context['event'].currency))
                              for t in base_values]
                sum_fee = sum(t[1] for t in fee_values)

                # If there are rounding differences, we fix them up, but always leaning to the benefit of the tax
                # authorities
                if sum_fee > fee_data['value']:
                    fee_values[0] = (fee_values[0][0], fee_values[0][1] + (fee_data['value'] - sum_fee))
                elif sum_fee < fee_data['value']:
                    fee_values[-1] = (fee_values[-1][0], fee_values[-1][1] + (fee_data['value'] - sum_fee))

                for tr, val in fee_values:
                    fee_data['tax_rule'] = tr
                    fee_data['value'] = val
                    f = OrderFee(**fee_data)
                    f.order = order._wrapped if simulate else order
                    f._calculate_tax()
                    fees.append(f)
                    if not simulate:
                        f.save()
            else:
                f = OrderFee(**fee_data)
                f.order = order._wrapped if simulate else order
                f._calculate_tax()
                fees.append(f)
                if not simulate:
                    f.save()

        order.total += sum([f.value for f in fees])
        if simulate:
            order.fees = fees
            order.positions = pos_map.values()
            return order  # ignore payments
        else:
            order.save(update_fields=['total'])

        if order.total == Decimal('0.00') and validated_data.get('status') == Order.STATUS_PAID and not payment_provider:
            payment_provider = 'free'

        if order.total == Decimal('0.00') and validated_data.get('status') != Order.STATUS_PAID:
            order.status = Order.STATUS_PAID
            order.save()
            order.payments.create(
                amount=order.total, provider='free', state=OrderPayment.PAYMENT_STATE_CONFIRMED,
                payment_date=now()
            )
        elif payment_provider == "free" and order.total != Decimal('0.00'):
            raise ValidationError('You cannot use the "free" payment provider for non-free orders.')
        elif validated_data.get('status') == Order.STATUS_PAID:
            if not payment_provider:
                raise ValidationError('You cannot create a paid order without a payment provider.')
            order.payments.create(
                amount=order.total,
                provider=payment_provider,
                info=payment_info,
                payment_date=payment_date,
                state=OrderPayment.PAYMENT_STATE_CONFIRMED
            )
        elif payment_provider:
            order.payments.create(
                amount=order.total,
                provider=payment_provider,
                info=payment_info,
                state=OrderPayment.PAYMENT_STATE_CREATED
            )

        return order
Ejemplo n.º 6
0
    def save(self):
        failed = False
        for form in self.forms:
            meta_info = form.pos.meta_info_data
            # Every form represents a CartPosition or OrderPosition with questions attached
            if not form.is_valid():
                failed = True
            else:
                # This form was correctly filled, so we store the data as
                # answers to the questions / in the CartPosition object
                for k, v in form.cleaned_data.items():
                    if k == 'attendee_name_parts':
                        form.pos.attendee_name_parts = v if v else None
                    elif k == 'attendee_email':
                        form.pos.attendee_email = v if v != '' else None
                    elif k == 'company':
                        form.pos.company = v if v != '' else None
                    elif k == 'street':
                        form.pos.street = v if v != '' else None
                    elif k == 'zipcode':
                        form.pos.zipcode = v if v != '' else None
                    elif k == 'city':
                        form.pos.city = v if v != '' else None
                    elif k == 'country':
                        form.pos.country = v if v != '' else None
                    elif k == 'state':
                        form.pos.state = v if v != '' else None
                    elif k.startswith('question_'):
                        field = form.fields[k]
                        if hasattr(field, 'answer'):
                            # We already have a cached answer object, so we don't
                            # have to create a new one
                            if v == '' or v is None or (isinstance(field, forms.FileField) and v is False) \
                                    or (isinstance(v, QuerySet) and not v.exists()):
                                if field.answer.file:
                                    field.answer.file.delete()
                                field.answer.delete()
                            else:
                                self._save_to_answer(field, field.answer, v)
                                field.answer.save()
                        elif v != '' and v is not None:
                            answer = QuestionAnswer(
                                cartposition=(form.pos if isinstance(
                                    form.pos, CartPosition) else None),
                                orderposition=(form.pos if isinstance(
                                    form.pos, OrderPosition) else None),
                                question=field.question,
                            )
                            try:
                                self._save_to_answer(field, answer, v)
                                answer.save()
                            except IntegrityError:
                                # Since we prefill ``field.answer`` at form creation time, there's a possible race condition
                                # here if the users submits their save request a second time while the first one is still running,
                                # thus leading to duplicate QuestionAnswer objects. Since Django doesn't support UPSERT, the "proper"
                                # fix would be a transaction with select_for_update(), or at least fetching using get_or_create here
                                # again. However, both of these approaches have a significant performance overhead for *all* requests,
                                # while the issue happens very very rarely. So we opt for just catching the error and retrying properly.
                                answer = QuestionAnswer.objects.get(
                                    cartposition=(form.pos if isinstance(
                                        form.pos, CartPosition) else None),
                                    orderposition=(form.pos if isinstance(
                                        form.pos, OrderPosition) else None),
                                    question=field.question,
                                )
                                self._save_to_answer(field, answer, v)
                                answer.save()

                    else:
                        meta_info.setdefault('question_form_data', {})
                        if v is None:
                            if k in meta_info['question_form_data']:
                                del meta_info['question_form_data'][k]
                        else:
                            meta_info['question_form_data'][k] = v

            form.pos.meta_info = json.dumps(meta_info)
            form.pos.save()
        return not failed
Ejemplo n.º 7
0
    def save(self):
        failed = False
        for form in self.forms:
            meta_info = form.pos.meta_info_data
            # Every form represents a CartPosition or OrderPosition with questions attached
            if not form.is_valid():
                failed = True
            else:
                if form.cleaned_data.get('saved_id'):
                    prof = AttendeeProfile.objects.filter(
                        customer=self.cart_customer,
                        pk=form.cleaned_data.get(
                            'saved_id')).first() or AttendeeProfile(
                                customer=getattr(self, 'cart_customer', None))
                    answers_key_to_index = {
                        a.get('field_name'): i
                        for i, a in enumerate(prof.answers)
                    }
                else:
                    prof = AttendeeProfile(
                        customer=getattr(self, 'cart_customer', None))
                    answers_key_to_index = {}

                # This form was correctly filled, so we store the data as
                # answers to the questions / in the CartPosition object
                for k, v in form.cleaned_data.items():
                    if k in ('save', 'saved_id'):
                        continue
                    elif k == 'attendee_name_parts':
                        form.pos.attendee_name_parts = v if v else None
                        prof.attendee_name_parts = form.pos.attendee_name_parts
                        prof.attendee_name_cached = form.pos.attendee_name
                    elif k in ('attendee_email', 'company', 'street',
                               'zipcode', 'city', 'country', 'state'):
                        v = v if v != '' else None
                        setattr(form.pos, k, v)
                        setattr(prof, k, v)
                    elif k.startswith('question_'):
                        field = form.fields[k]
                        if hasattr(field, 'answer'):
                            # We already have a cached answer object, so we don't
                            # have to create a new one
                            if v == '' or v is None or (isinstance(field, forms.FileField) and v is False) \
                                    or (isinstance(v, QuerySet) and not v.exists()):
                                if field.answer.file:
                                    field.answer.file.delete()
                                field.answer.delete()
                            else:
                                self._save_to_answer(field, field.answer, v)
                                field.answer.save()
                                if isinstance(
                                        field, forms.ModelMultipleChoiceField
                                ) or isinstance(field, forms.ModelChoiceField):
                                    answer_value = {
                                        o.identifier: str(o)
                                        for o in field.answer.options.all()
                                    }
                                elif isinstance(field, forms.BooleanField):
                                    answer_value = bool(field.answer.answer)
                                else:
                                    answer_value = str(field.answer.answer)
                                answer_dict = {
                                    'field_name':
                                    k,
                                    'field_label':
                                    str(field.label),
                                    'value':
                                    answer_value,
                                    'question_type':
                                    field.question.type,
                                    'question_identifier':
                                    field.question.identifier,
                                }
                                if k in answers_key_to_index:
                                    prof.answers[
                                        answers_key_to_index[k]] = answer_dict
                                else:
                                    prof.answers.append(answer_dict)
                        elif v != '' and v is not None:
                            answer = QuestionAnswer(
                                cartposition=(form.pos if isinstance(
                                    form.pos, CartPosition) else None),
                                orderposition=(form.pos if isinstance(
                                    form.pos, OrderPosition) else None),
                                question=field.question,
                            )
                            try:
                                self._save_to_answer(field, answer, v)
                                answer.save()
                            except IntegrityError:
                                # Since we prefill ``field.answer`` at form creation time, there's a possible race condition
                                # here if the users submits their save request a second time while the first one is still running,
                                # thus leading to duplicate QuestionAnswer objects. Since Django doesn't support UPSERT, the "proper"
                                # fix would be a transaction with select_for_update(), or at least fetching using get_or_create here
                                # again. However, both of these approaches have a significant performance overhead for *all* requests,
                                # while the issue happens very very rarely. So we opt for just catching the error and retrying properly.
                                answer = QuestionAnswer.objects.get(
                                    cartposition=(form.pos if isinstance(
                                        form.pos, CartPosition) else None),
                                    orderposition=(form.pos if isinstance(
                                        form.pos, OrderPosition) else None),
                                    question=field.question,
                                )
                                self._save_to_answer(field, answer, v)
                                answer.save()

                            if isinstance(field, forms.ModelMultipleChoiceField
                                          ) or isinstance(
                                              field, forms.ModelChoiceField):
                                answer_value = {
                                    o.identifier: str(o)
                                    for o in answer.options.all()
                                }
                            elif isinstance(field, forms.BooleanField):
                                answer_value = bool(answer.answer)
                            else:
                                answer_value = str(answer.answer)
                            answer_dict = {
                                'field_name': k,
                                'field_label': str(field.label),
                                'value': answer_value,
                                'question_type': field.question.type,
                                'question_identifier':
                                field.question.identifier,
                            }
                            if k in answers_key_to_index:
                                prof.answers[
                                    answers_key_to_index[k]] = answer_dict
                            else:
                                prof.answers.append(answer_dict)

                    else:
                        field = form.fields[k]

                        meta_info.setdefault('question_form_data', {})
                        if v is None:
                            if k in meta_info['question_form_data']:
                                del meta_info['question_form_data'][k]
                        else:
                            meta_info['question_form_data'][k] = v

                        answer_dict = {
                            'field_name': k,
                            'field_label': str(field.label),
                            'value': str(v),
                            'question_type': None,
                            'question_identifier': None,
                        }
                        if k in answers_key_to_index:
                            prof.answers[answers_key_to_index[k]] = answer_dict
                        else:
                            prof.answers.append(answer_dict)

            form.pos.meta_info = json.dumps(meta_info)
            form.pos.save()

            if form.cleaned_data.get('save') and not failed:
                prof.save()
                self.cart_session[
                    f'saved_attendee_profile_{form.pos.pk}'] = prof.pk

        return not failed