Compare commits

..

3 Commits

Author SHA1 Message Date
Richard Schreiber
ed1a2dd57f add safe fallback for attendee_name_parts being None 2022-05-10 11:18:57 +02:00
Richard Schreiber
6da65ab13e improve order of fields 2022-05-10 09:32:47 +02:00
Richard Schreiber
d1d6afe721 add name_for_salutation to editor/pdf 2022-05-10 09:27:29 +02:00
21 changed files with 330 additions and 930 deletions

View File

@@ -172,6 +172,8 @@ Cart position endpoints
* does not check or calculate prices but believes any prices you send
* does not support the redemption of vouchers
* does not prevent you from buying items that can only be bought with a voucher
* does not support file upload questions
@@ -189,7 +191,6 @@ Cart position endpoints
* ``expires`` (optional)
* ``includes_tax`` (optional, **deprecated**, do not use, will be removed)
* ``sales_channel`` (optional)
* ``voucher`` (optional, expect a voucher code)
* ``answers``
* ``question``

View File

@@ -23,7 +23,6 @@ import os
from datetime import timedelta
from django.core.files import File
from django.db.models import Q
from django.utils.crypto import get_random_string
from django.utils.timezone import now
from django.utils.translation import gettext_lazy
@@ -34,7 +33,7 @@ from pretix.api.serializers.i18n import I18nAwareModelSerializer
from pretix.api.serializers.order import (
AnswerCreateSerializer, AnswerSerializer, InlineSeatSerializer,
)
from pretix.base.models import Quota, Seat, Voucher
from pretix.base.models import Quota, Seat
from pretix.base.models.orders import CartPosition
@@ -62,12 +61,11 @@ class CartPositionCreateSerializer(I18nAwareModelSerializer):
seat = serializers.CharField(required=False, allow_null=True)
sales_channel = serializers.CharField(required=False, default='sales_channel')
includes_tax = serializers.BooleanField(required=False, allow_null=True)
voucher = serializers.CharField(required=False, allow_null=True)
class Meta:
model = CartPosition
fields = ('cart_id', 'item', 'variation', 'price', 'attendee_name', 'attendee_name_parts', 'attendee_email',
'subevent', 'expires', 'includes_tax', 'answers', 'seat', 'sales_channel', 'voucher')
'subevent', 'expires', 'includes_tax', 'answers', 'seat', 'sales_channel')
def create(self, validated_data):
answers_data = validated_data.pop('answers')
@@ -127,46 +125,14 @@ class CartPositionCreateSerializer(I18nAwareModelSerializer):
raise ValidationError('The specified seat ID is not unique.')
else:
validated_data['seat'] = seat
if not seat.is_available(
sales_channel=validated_data.get('sales_channel', 'web'),
distance_ignore_cart_id=validated_data['cart_id'],
):
raise ValidationError(gettext_lazy('The selected seat "{seat}" is not available.').format(seat=seat.name))
elif seated:
raise ValidationError('The specified product requires to choose a seat.')
if validated_data.get('voucher'):
try:
voucher = self.context['event'].vouchers.get(code__iexact=validated_data.get('voucher'))
except Voucher.DoesNotExist:
raise ValidationError('The specified voucher does not exist.')
if voucher and not voucher.applies_to(validated_data.get('item'), validated_data.get('variation')):
raise ValidationError('The specified voucher is not valid for the given item and variation.')
if voucher and voucher.seat and voucher.seat != validated_data.get('seat'):
raise ValidationError('The specified voucher is not valid for this seat.')
if voucher and voucher.subevent_id and voucher.subevent_id != validated_data.get('subevent'):
raise ValidationError('The specified voucher is not valid for this subevent.')
if voucher.valid_until is not None and voucher.valid_until < now():
raise ValidationError('The specified voucher is expired.')
redeemed_in_carts = CartPosition.objects.filter(
Q(voucher=voucher) & Q(event=self.context['event']) & Q(expires__gte=now())
)
cart_count = redeemed_in_carts.count()
v_avail = voucher.max_usages - voucher.redeemed - cart_count
if v_avail < 1:
raise ValidationError('The specified voucher has already been used the maximum number of times.')
validated_data['voucher'] = voucher
if validated_data.get('seat'):
if not validated_data['seat'].is_available(
sales_channel=validated_data.get('sales_channel', 'web'),
distance_ignore_cart_id=validated_data['cart_id'],
ignore_voucher_id=validated_data['voucher'].pk if validated_data.get('voucher') else None,
):
raise ValidationError(
gettext_lazy('The selected seat "{seat}" is not available.').format(seat=validated_data['seat'].name))
validated_data.pop('sales_channel')
# todo: does this make sense?
validated_data['custom_price_input'] = validated_data['price']

View File

@@ -25,7 +25,7 @@ from django.db import transaction
from django.db.models import F, Q
from django.utils.timezone import now
from django_filters.rest_framework import (
BooleanFilter, CharFilter, DjangoFilterBackend, FilterSet,
BooleanFilter, DjangoFilterBackend, FilterSet,
)
from django_scopes import scopes_disabled
from rest_framework import status, viewsets
@@ -40,7 +40,6 @@ from pretix.base.models import Voucher
with scopes_disabled():
class VoucherFilter(FilterSet):
active = BooleanFilter(method='filter_active')
code = CharFilter(lookup_expr='iexact')
class Meta:
model = Voucher

View File

@@ -4,6 +4,7 @@
from django.core.exceptions import ImproperlyConfigured
from django.db import migrations, models
from django_mysql.checks import mysql_connections
from django_mysql.utils import connection_is_mariadb
def set_attendee_name_parts(apps, schema_editor):
@@ -30,7 +31,7 @@ def check_mysqlversion(apps, schema_editor):
conns = list(mysql_connections())
found = 'Unknown version'
for alias, conn in conns:
if hasattr(conn, 'mysql_is_mariadb') and conn.mysql_is_mariadb and hasattr(conn, 'mysql_version'):
if connection_is_mariadb(conn) and hasattr(conn, 'mysql_version'):
if conn.mysql_version >= (10, 2, 7):
any_conn_works = True
else:

View File

@@ -1,22 +0,0 @@
# Generated by Django 3.2.12 on 2022-05-12 15:34
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('pretixbase', '0214_customer_notes_ext_id'),
]
operations = [
migrations.AlterField(
model_name='customer',
name='identifier',
field=models.CharField(db_index=True, max_length=190),
),
migrations.AlterUniqueTogether(
name='customer',
unique_together={('organizer', 'email'), ('organizer', 'identifier')},
),
]

View File

@@ -24,7 +24,6 @@ from django.conf import settings
from django.contrib.auth.hashers import (
check_password, is_password_usable, make_password,
)
from django.core.validators import RegexValidator
from django.db import models
from django.db.models import F, Q
from django.utils.crypto import get_random_string, salted_hmac
@@ -45,18 +44,7 @@ class Customer(LoggedModel):
"""
id = models.BigAutoField(primary_key=True)
organizer = models.ForeignKey(Organizer, related_name='customers', on_delete=models.CASCADE)
identifier = models.CharField(
max_length=190,
db_index=True,
help_text=_('You can enter any value here to make it easier to match the data with other sources. If you do '
'not input one, we will generate one automatically.'),
validators=[
RegexValidator(
regex=r"^[a-zA-Z0-9]([a-zA-Z0-9.\-_]*[a-zA-Z0-9])?$",
message=_("The identifier may only contain letters, numbers, dots, dashes, and underscores. It must start and end with a letter or number."),
),
],
)
identifier = models.CharField(max_length=190, db_index=True, unique=True)
email = models.EmailField(db_index=True, null=True, blank=False, verbose_name=_('E-mail'), max_length=190)
phone = PhoneNumberField(null=True, blank=True, verbose_name=_('Phone number'))
password = models.CharField(verbose_name=_('Password'), max_length=128)
@@ -77,7 +65,7 @@ class Customer(LoggedModel):
objects = ScopedManager(organizer='organizer')
class Meta:
unique_together = [['organizer', 'email'], ['organizer', 'identifier']]
unique_together = [['organizer', 'email']]
ordering = ('email',)
def get_email_field_name(self):

View File

@@ -1243,13 +1243,7 @@ class Question(LoggedModel):
max_length=190,
verbose_name=_("Internal identifier"),
help_text=_('You can enter any value here to make it easier to match the data with other sources. If you do '
'not input one, we will generate one automatically.'),
validators=[
RegexValidator(
regex=r"^[a-zA-Z0-9.\-_]+$",
message=_("The identifier may only contain letters, numbers, dots, dashes, and underscores."),
),
],
'not input one, we will generate one automatically.')
)
help_text = I18nTextField(
verbose_name=_("Help text"),
@@ -1467,17 +1461,7 @@ class Question(LoggedModel):
class QuestionOption(models.Model):
question = models.ForeignKey('Question', related_name='options', on_delete=models.CASCADE)
identifier = models.CharField(
max_length=190,
help_text=_('You can enter any value here to make it easier to match the data with other sources. If you do '
'not input one, we will generate one automatically.'),
validators=[
RegexValidator(
regex=r"^[a-zA-Z0-9.\-_]+$",
message=_("The identifier may only contain letters, numbers, dots, dashes, and underscores."),
),
],
)
identifier = models.CharField(max_length=190)
answer = I18nCharField(verbose_name=_('Answer'))
position = models.IntegerField(default=0)

View File

@@ -530,10 +530,7 @@ def _get_attendee_name_part(key, op, order, ev):
def _get_ia_name_part(key, op, order, ev):
value = order.invoice_address.name_parts.get(key, '') if getattr(order, 'invoice_address', None) else ''
if key == 'salutation' and value:
return pgettext('person_name_salutation', value)
return value
return order.invoice_address.name_parts.get(key, '') if getattr(order, 'invoice_address', None) else ''
def get_images(event):

View File

@@ -447,10 +447,7 @@ class CartManager:
try:
voucher = self.event.vouchers.get(code__iexact=voucher_code.strip())
except Voucher.DoesNotExist:
if self.event.organizer.accepted_gift_cards.filter(secret__iexact=voucher_code).exists():
raise CartError(error_messages['gift_card'])
else:
raise CartError(error_messages['voucher_invalid'])
raise CartError(error_messages['voucher_invalid'])
voucher_use_diff = Counter()
ops = []

View File

@@ -1,36 +0,0 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.
#
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
# this file, see <https://pretix.eu/about/en/license>.
#
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>.
#
from django.template.loaders.app_directories import Loader
from django.template.utils import get_app_template_dirs
class AppLoader(Loader):
def get_dirs(self):
ds = get_app_template_dirs('templates')
ignore_patterns = {
# Ignore templates of plugins we don't actually use as they cause trouble during
# static file compression
'/django_filters/',
'/django_otp/',
}
return [d for d in ds if not any(p in str(d) for p in ignore_patterns)]

File diff suppressed because it is too large Load Diff

View File

@@ -8,7 +8,7 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-04-28 16:44+0000\n"
"PO-Revision-Date: 2022-05-10 02:00+0000\n"
"PO-Revision-Date: 2022-05-05 06:00+0000\n"
"Last-Translator: Iryna N <in380@nyu.edu>\n"
"Language-Team: Ukrainian <https://translate.pretix.eu/projects/pretix/"
"pretix-js/uk/>\n"
@@ -88,7 +88,7 @@ msgstr "Так"
#: pretix/plugins/paypal/static/pretixplugins/paypal/pretix-paypal.js:47
msgid "MyBank"
msgstr "MyBank"
msgstr ""
#: pretix/plugins/paypal/static/pretixplugins/paypal/pretix-paypal.js:48
msgid "Przelewy24"

View File

@@ -26,7 +26,7 @@ from urllib.parse import urljoin, urlsplit
import django_libsass
import sass
from compressor.filters.cssmin import CSSMinFilter
from compressor.filters.cssmin import CSSCompressorFilter
from django.conf import settings
from django.core.cache import cache
from django.core.files.base import ContentFile, File
@@ -115,7 +115,7 @@ def compile_scss(object, file="main.scss", fonts=True):
include_paths=[sassdir], output_style='nested',
custom_functions=cf
)
cssf = CSSMinFilter(css)
cssf = CSSCompressorFilter(css)
css = cssf.output()
cache.set('sass_compile_{}_{}'.format(cp, srcchecksum), css, 600)

View File

@@ -26,9 +26,9 @@
{% csrf_token %}
<input type="hidden" name="subevent" value="{{ subevent.id|default_if_none:"" }}"/>
{% if event.has_subevents %}
{% eventsignal event "pretix.presale.signals.render_seating_plan" request=request subevent=subevent voucher=voucher %}
{% eventsignal event "pretix.presale.signals.render_seating_plan" request=request subevent=subevent %}
{% else %}
{% eventsignal event "pretix.presale.signals.render_seating_plan" request=request voucher=voucher %}
{% eventsignal event "pretix.presale.signals.render_seating_plan" request=request %}
{% endif %}
</form>
{% include "pretixpresale/fragment_modals.html" %}

View File

@@ -700,22 +700,6 @@ class SeatingPlanView(EventViewMixin, TemplateView):
kwargs={'cart_namespace': kwargs.get('cart_namespace') or ''})
if context['cart_redirect'].startswith('https:'):
context['cart_redirect'] = '/' + context['cart_redirect'].split('/', 3)[3]
v = self.request.GET.get('voucher')
if v:
v = v.strip()
try:
voucher = self.request.event.vouchers.get(code__iexact=v)
if voucher.redeemed >= voucher.max_usages or voucher.valid_until is not None \
and voucher.valid_until < now() or voucher.item is not None \
and voucher.item.is_available() is False:
voucher = None
except Voucher.DoesNotExist:
voucher = None
else:
voucher = None
context['voucher'] = voucher
return context

View File

@@ -593,7 +593,7 @@ CSRF_FAILURE_VIEW = 'pretix.base.views.errors.csrf_failure'
template_loaders = (
'django.template.loaders.filesystem.Loader',
'pretix.helpers.template_loaders.AppLoader',
'django.template.loaders.app_directories.Loader',
)
if not DEBUG:
template_loaders = (
@@ -648,10 +648,6 @@ COMPRESS_PRECOMPILERS = (
('text/vue', 'pretix.helpers.compressor.VueCompiler'),
)
COMPRESS_OFFLINE_CONTEXT = {
'basetpl': 'empty.html',
}
COMPRESS_ENABLED = COMPRESS_OFFLINE = not debug_fallback
COMPRESS_FILTERS = {
@@ -659,10 +655,7 @@ COMPRESS_FILTERS = {
# CssAbsoluteFilter is incredibly slow, especially when dealing with our _flags.scss
# However, we don't need it if we consequently use the static() function in Sass
# 'compressor.filters.css_default.CssAbsoluteFilter',
'compressor.filters.cssmin.rCSSMinFilter',
),
'js': (
'compressor.filters.jsmin.JSMinFilter',
'compressor.filters.cssmin.CSSCompressorFilter',
)
}

View File

@@ -286,7 +286,7 @@ var editor = {
} else if (key.startsWith('meta:')) {
return key.substr(5);
}
return $('#toolbox-content option[value="'+key+'"], #toolbox-content option[data-old-value="'+key+'"]').attr('data-sample') || '???';
return $('#toolbox-content option[value='+key+'], #toolbox-content option[data-old-value='+key+']').attr('data-sample') || '';
},
_load_page: function (page_number, dump) {
@@ -835,32 +835,32 @@ var editor = {
editor._delete();
break;
case 65: /* A */
if (e.ctrlKey || e.metaKey) {
if (e.ctrlKey) {
editor._selectAll();
}
break;
case 89: /* Y */
if (e.ctrlKey || e.metaKey) {
if (e.ctrlKey) {
editor._redo();
}
break;
case 90: /* Z */
if (e.ctrlKey || e.metaKey) {
if (e.ctrlKey) {
editor._undo();
}
break;
case 88: /* X */
if (e.ctrlKey || e.metaKey) {
if (e.ctrlKey) {
editor._cut();
}
break;
case 86: /* V */
if (e.ctrlKey || e.metaKey) {
if (e.ctrlKey) {
editor._paste();
}
break;
case 67: /* C */
if (e.ctrlKey || e.metaKey) {
if (e.ctrlKey) {
editor._copy();
}
break;

View File

@@ -165,12 +165,13 @@ setup(
'celery==4.4.*',
'chardet==4.0.*',
'cryptography>=3.4.2',
'csscompressor',
'css-inline==0.7.*',
'defusedcsv>=1.1.0',
'dj-static',
'Django==3.2.*',
'django-bootstrap3==15.0.*',
'django-compressor==3.1.*',
'django-compressor==2.4.*',
'django-countries==7.2.*',
'django-filter==21.1',
'django-formset-js-improved==0.5.0.2',

View File

@@ -888,180 +888,3 @@ def test_cartpos_create_bulk_partial_seat_failure(token_client, organizer, event
assert CartPosition.objects.count() == 1
cp1 = CartPosition.objects.get(pk=resp.data['results'][0]['data']['id'])
assert cp1.price == Decimal('23.00')
@pytest.mark.django_db
def test_cartpos_create_with_voucher_by_code(token_client, organizer, event, item, quota, seat):
with scopes_disabled():
voucher = event.vouchers.create(code="FOOBAR", seat=seat)
res = copy.deepcopy(CARTPOS_CREATE_PAYLOAD)
res['item'] = item.pk
res['voucher'] = voucher.code
res['seat'] = seat.seat_guid
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/cartpositions/'.format(
organizer.slug, event.slug
), format='json', data=res
)
assert resp.status_code == 201
with scopes_disabled():
cp1 = CartPosition.objects.get(pk=resp.data['id'])
assert cp1.voucher == voucher
assert cp1.seat == seat
@pytest.mark.django_db
def test_cartpos_create_with_voucher_unknown(token_client, organizer, event, item, quota):
res = copy.deepcopy(CARTPOS_CREATE_PAYLOAD)
res['item'] = item.pk
res['voucher'] = 'TEST'
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/cartpositions/'.format(
organizer.slug, event.slug
), format='json', data=res
)
assert resp.status_code == 400
assert resp.data == ['The specified voucher does not exist.']
@pytest.mark.django_db
def test_cartpos_create_with_voucher_invalid_item(token_client, organizer, event, item, quota):
with scopes_disabled():
item2 = event.items.create(name="item2")
voucher = event.vouchers.create(code="FOOBAR", item=item2)
res = copy.deepcopy(CARTPOS_CREATE_PAYLOAD)
res['item'] = item.pk
res['voucher'] = voucher.code
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/cartpositions/'.format(
organizer.slug, event.slug
), format='json', data=res
)
assert resp.status_code == 400
assert resp.data == ['The specified voucher is not valid for the given item and variation.']
@pytest.mark.django_db
def test_cartpos_create_with_voucher_invalid_seat(token_client, organizer, event, item, quota, seat):
with scopes_disabled():
seat2 = event.seats.create(seat_number="A2", product=item, seat_guid="A2")
voucher = event.vouchers.create(code="FOOBAR", item=item, seat=seat2)
res = copy.deepcopy(CARTPOS_CREATE_PAYLOAD)
res['item'] = item.pk
res['voucher'] = voucher.code
res['seat'] = seat.seat_guid
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/cartpositions/'.format(
organizer.slug, event.slug
), format='json', data=res
)
assert resp.status_code == 400
assert resp.data == ['The specified voucher is not valid for this seat.']
@pytest.mark.django_db
def test_cartpos_create_with_voucher_invalid_subevent(token_client, organizer, event, item, quota, subevent):
with scopes_disabled():
voucher = event.vouchers.create(code="FOOBAR", item=item, subevent=subevent)
quota.subevent = subevent
quota.save()
res = copy.deepcopy(CARTPOS_CREATE_PAYLOAD)
res['item'] = item.pk
res['voucher'] = voucher.code
res['subevent'] = subevent.pk
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/cartpositions/'.format(
organizer.slug, event.slug
), format='json', data=res
)
assert resp.status_code == 400
assert resp.data == ['The specified voucher is not valid for this subevent.']
@pytest.mark.django_db
def test_cartpos_create_with_voucher_expired(token_client, organizer, event, item, quota):
with scopes_disabled():
voucher = event.vouchers.create(code="FOOBAR", item=item, valid_until=now() - datetime.timedelta(days=1))
res = copy.deepcopy(CARTPOS_CREATE_PAYLOAD)
res['item'] = item.pk
res['voucher'] = voucher.code
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/cartpositions/'.format(
organizer.slug, event.slug
), format='json', data=res
)
assert resp.status_code == 400
assert resp.data == ['The specified voucher is expired.']
@pytest.mark.django_db
def test_cartpos_create_with_voucher_redeemed(token_client, organizer, event, item, quota):
with scopes_disabled():
voucher = event.vouchers.create(code="FOOBAR", item=item, redeemed=1)
res = copy.deepcopy(CARTPOS_CREATE_PAYLOAD)
res['item'] = item.pk
res['voucher'] = voucher.code
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/cartpositions/'.format(
organizer.slug, event.slug
), format='json', data=res
)
assert resp.status_code == 400
assert resp.data == ['The specified voucher has already been used the maximum number of times.']
@pytest.mark.django_db
def test_cartpos_create_bulk_with_voucher(token_client, organizer, event, item, quota):
with scopes_disabled():
voucher = event.vouchers.create(code="FOOBAR", item=item, max_usages=3, redeemed=1)
res = copy.deepcopy(CARTPOS_CREATE_PAYLOAD)
res['item'] = item.pk
res['expires'] = (now() + datetime.timedelta(days=1)).isoformat()
res['voucher'] = voucher.code
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/cartpositions/bulk_create/'.format(
organizer.slug, event.slug
), format='json', data=[
res,
res
]
)
assert resp.status_code == 200
assert len(resp.data['results']) == 2
assert resp.data['results'][0]['success']
assert resp.data['results'][1]['success']
with scopes_disabled():
assert CartPosition.objects.count() == 2
cp1 = CartPosition.objects.get(pk=resp.data['results'][0]['data']['id'])
cp2 = CartPosition.objects.get(pk=resp.data['results'][1]['data']['id'])
assert cp1.voucher == voucher
assert cp2.voucher == voucher
@pytest.mark.django_db
def test_cartpos_create_bulk_with_voucher_redeemed(token_client, organizer, event, item, quota):
with scopes_disabled():
voucher = event.vouchers.create(code="FOOBAR", item=item, max_usages=3, redeemed=2)
res = copy.deepcopy(CARTPOS_CREATE_PAYLOAD)
res['item'] = item.pk
res['expires'] = (now() + datetime.timedelta(days=1)).isoformat()
res['voucher'] = voucher.code
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/cartpositions/bulk_create/'.format(
organizer.slug, event.slug
), format='json', data=[
res,
res
]
)
assert resp.status_code == 200
assert len(resp.data['results']) == 2
assert resp.data['results'][0]['success']
assert not resp.data['results'][1]['success']
assert resp.data['results'][1]['errors'] == {'non_field_errors': ['The specified voucher has already been used the maximum number of times.']}
with scopes_disabled():
assert CartPosition.objects.count() == 1
cp1 = CartPosition.objects.get(pk=resp.data['results'][0]['data']['id'])
assert cp1.voucher == voucher

View File

@@ -2026,31 +2026,6 @@ def test_order_create_with_seat_consumed_from_cart(token_client, organizer, even
assert p.seat == seat
@pytest.mark.django_db
def test_order_create_with_voucher_consumed_from_cart(token_client, organizer, event, item, quota, question):
with scopes_disabled():
voucher = event.vouchers.create(code="FOOBAR", item=item, max_usages=3, redeemed=2)
CartPosition.objects.create(
event=event, cart_id='aaa', item=item, voucher=voucher,
price=21.5, expires=now() + datetime.timedelta(minutes=10),
)
res = copy.deepcopy(ORDER_CREATE_PAYLOAD)
res['positions'][0]['item'] = item.pk
res['positions'][0]['voucher'] = voucher.code
res['positions'][0]['answers'][0]['question'] = question.pk
res['consume_carts'] = ['aaa']
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/orders/'.format(
organizer.slug, event.slug
), format='json', data=res
)
assert resp.status_code == 201
with scopes_disabled():
o = Order.objects.get(code=resp.data['code'])
p = o.positions.first()
assert p.voucher == voucher
@pytest.mark.django_db
def test_order_create_send_no_emails(token_client, organizer, event, item, quota, question):
res = copy.deepcopy(ORDER_CREATE_PAYLOAD)