forked from CGM_Public/pretix_original
Compare commits
9 Commits
scan-debug
...
v2023.10.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0eb5534c4d | ||
|
|
1ece080fa0 | ||
|
|
03a302415a | ||
|
|
0e73611f7c | ||
|
|
5fe5b82f1a | ||
|
|
9aa2976bda | ||
|
|
eec60f6242 | ||
|
|
c190fc315c | ||
|
|
eebd499359 |
@@ -59,7 +59,7 @@ dependencies = [
|
|||||||
"dnspython==2.3.*",
|
"dnspython==2.3.*",
|
||||||
"drf_ujson2==1.7.*",
|
"drf_ujson2==1.7.*",
|
||||||
"geoip2==4.*",
|
"geoip2==4.*",
|
||||||
"importlib_metadata==6.*", # Polyfill, we can probably drop this once we require Python 3.10+
|
"importlib_metadata==7.*", # Polyfill, we can probably drop this once we require Python 3.10+
|
||||||
"isoweek",
|
"isoweek",
|
||||||
"jsonschema",
|
"jsonschema",
|
||||||
"kombu==5.3.*",
|
"kombu==5.3.*",
|
||||||
|
|||||||
@@ -19,4 +19,4 @@
|
|||||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
# 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/>.
|
# <https://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
__version__ = "2023.11.0.dev0"
|
__version__ = "2023.10.2"
|
||||||
|
|||||||
@@ -152,11 +152,6 @@ class CheckinListViewSet(viewsets.ModelViewSet):
|
|||||||
@action(detail=True, methods=['POST'], url_name='failed_checkins')
|
@action(detail=True, methods=['POST'], url_name='failed_checkins')
|
||||||
@transaction.atomic()
|
@transaction.atomic()
|
||||||
def failed_checkins(self, *args, **kwargs):
|
def failed_checkins(self, *args, **kwargs):
|
||||||
additional_log_data = {}
|
|
||||||
if 'debug_data' in self.request.data:
|
|
||||||
# Intentionally undocumented, might be removed again
|
|
||||||
additional_log_data['debug_data'] = self.request.data.pop('debug_data')
|
|
||||||
|
|
||||||
serializer = FailedCheckinSerializer(
|
serializer = FailedCheckinSerializer(
|
||||||
data=self.request.data,
|
data=self.request.data,
|
||||||
context={'event': self.request.event}
|
context={'event': self.request.event}
|
||||||
@@ -199,16 +194,14 @@ class CheckinListViewSet(viewsets.ModelViewSet):
|
|||||||
'reason_explanation': c.error_explanation,
|
'reason_explanation': c.error_explanation,
|
||||||
'datetime': c.datetime,
|
'datetime': c.datetime,
|
||||||
'type': c.type,
|
'type': c.type,
|
||||||
'list': c.list.pk,
|
'list': c.list.pk
|
||||||
**additional_log_data,
|
|
||||||
}, user=self.request.user, auth=self.request.auth)
|
}, user=self.request.user, auth=self.request.auth)
|
||||||
else:
|
else:
|
||||||
self.request.event.log_action('pretix.event.checkin.unknown', data={
|
self.request.event.log_action('pretix.event.checkin.unknown', data={
|
||||||
'datetime': c.datetime,
|
'datetime': c.datetime,
|
||||||
'type': c.type,
|
'type': c.type,
|
||||||
'list': c.list.pk,
|
'list': c.list.pk,
|
||||||
'barcode': c.raw_barcode,
|
'barcode': c.raw_barcode
|
||||||
**additional_log_data,
|
|
||||||
}, user=self.request.user, auth=self.request.auth)
|
}, user=self.request.user, auth=self.request.auth)
|
||||||
|
|
||||||
return Response(serializer.data, status=201)
|
return Response(serializer.data, status=201)
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ class Command(BaseCommand):
|
|||||||
with language(locale), override(timezone):
|
with language(locale), override(timezone):
|
||||||
for receiver, response in signal_result:
|
for receiver, response in signal_result:
|
||||||
if not response:
|
if not response:
|
||||||
continue
|
return None
|
||||||
ex = response(e, o, report_status)
|
ex = response(e, o, report_status)
|
||||||
if ex.identifier == options['export_provider']:
|
if ex.identifier == options['export_provider']:
|
||||||
params = json.loads(options.get('parameters') or '{}')
|
params = json.loads(options.get('parameters') or '{}')
|
||||||
|
|||||||
@@ -424,10 +424,5 @@ class Discount(LoggedModel):
|
|||||||
break
|
break
|
||||||
|
|
||||||
for g in candidate_groups:
|
for g in candidate_groups:
|
||||||
self._apply_min_count(
|
self._apply_min_count(positions, g, g, result)
|
||||||
positions,
|
|
||||||
[idx for idx in g if idx in condition_candidates],
|
|
||||||
[idx for idx in g if idx in benefit_candidates],
|
|
||||||
result
|
|
||||||
)
|
|
||||||
return result
|
return result
|
||||||
|
|||||||
@@ -2620,7 +2620,7 @@ class OrderChangeManager:
|
|||||||
i = self.order.invoices.filter(is_cancellation=False).last()
|
i = self.order.invoices.filter(is_cancellation=False).last()
|
||||||
if self.reissue_invoice and self._invoice_dirty:
|
if self.reissue_invoice and self._invoice_dirty:
|
||||||
order_now_qualified = invoice_qualified(self.order)
|
order_now_qualified = invoice_qualified(self.order)
|
||||||
invoice_should_be_generated_now = (
|
invoice_should_be_generated = (
|
||||||
self.event.settings.invoice_generate == "True" or (
|
self.event.settings.invoice_generate == "True" or (
|
||||||
self.event.settings.invoice_generate == "paid" and
|
self.event.settings.invoice_generate == "paid" and
|
||||||
self.open_payment is not None and
|
self.open_payment is not None and
|
||||||
@@ -2635,16 +2635,13 @@ class OrderChangeManager:
|
|||||||
not i.canceled
|
not i.canceled
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
invoice_should_be_generated_later = not invoice_should_be_generated_now and (
|
|
||||||
self.event.settings.invoice_generate in ("True", "paid")
|
|
||||||
)
|
|
||||||
|
|
||||||
if order_now_qualified:
|
if order_now_qualified:
|
||||||
if invoice_should_be_generated_now:
|
if invoice_should_be_generated:
|
||||||
if i and not i.canceled:
|
if i and not i.canceled:
|
||||||
self._invoices.append(generate_cancellation(i))
|
self._invoices.append(generate_cancellation(i))
|
||||||
self._invoices.append(generate_invoice(self.order))
|
self._invoices.append(generate_invoice(self.order))
|
||||||
elif invoice_should_be_generated_later:
|
else:
|
||||||
self.order.invoice_dirty = True
|
self.order.invoice_dirty = True
|
||||||
self.order.save(update_fields=["invoice_dirty"])
|
self.order.save(update_fields=["invoice_dirty"])
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -219,15 +219,17 @@ class ExtValidationMixin:
|
|||||||
|
|
||||||
def clean(self, *args, **kwargs):
|
def clean(self, *args, **kwargs):
|
||||||
data = super().clean(*args, **kwargs)
|
data = super().clean(*args, **kwargs)
|
||||||
if isinstance(data, UploadedFile):
|
|
||||||
filename = data.name
|
from ...base.models import CachedFile
|
||||||
|
if isinstance(data, (UploadedFile, CachedFile)):
|
||||||
|
filename = data.name if isinstance(data, UploadedFile) else data.filename
|
||||||
ext = os.path.splitext(filename)[1]
|
ext = os.path.splitext(filename)[1]
|
||||||
ext = ext.lower()
|
ext = ext.lower()
|
||||||
if ext not in self.ext_whitelist:
|
if ext not in self.ext_whitelist:
|
||||||
raise forms.ValidationError(_("Filetype not allowed!"))
|
raise forms.ValidationError(_("Filetype not allowed!"))
|
||||||
|
|
||||||
if ext in IMAGE_EXTS:
|
if ext in IMAGE_EXTS:
|
||||||
validate_uploaded_file_for_valid_image(data)
|
validate_uploaded_file_for_valid_image(data if isinstance(data, UploadedFile) else data.file)
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
@@ -257,6 +259,12 @@ class CachedFileField(ExtFileField):
|
|||||||
if isinstance(data, File):
|
if isinstance(data, File):
|
||||||
if hasattr(data, '_uploaded_to'):
|
if hasattr(data, '_uploaded_to'):
|
||||||
return data._uploaded_to
|
return data._uploaded_to
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.clean(data)
|
||||||
|
except ValidationError:
|
||||||
|
return None
|
||||||
|
|
||||||
cf = CachedFile.objects.create(
|
cf = CachedFile.objects.create(
|
||||||
expires=now() + datetime.timedelta(days=1),
|
expires=now() + datetime.timedelta(days=1),
|
||||||
date=now(),
|
date=now(),
|
||||||
@@ -268,6 +276,9 @@ class CachedFileField(ExtFileField):
|
|||||||
cf.save()
|
cf.save()
|
||||||
data._uploaded_to = cf
|
data._uploaded_to = cf
|
||||||
return cf
|
return cf
|
||||||
|
if isinstance(data, CachedFile):
|
||||||
|
return data
|
||||||
|
|
||||||
return super().bound_data(data, initial)
|
return super().bound_data(data, initial)
|
||||||
|
|
||||||
def clean(self, *args, **kwargs):
|
def clean(self, *args, **kwargs):
|
||||||
|
|||||||
@@ -201,8 +201,6 @@ class VoucherForm(I18nModelForm):
|
|||||||
cnt = len(data['codes']) * data.get('max_usages', 0)
|
cnt = len(data['codes']) * data.get('max_usages', 0)
|
||||||
else:
|
else:
|
||||||
cnt = data.get('max_usages', 0)
|
cnt = data.get('max_usages', 0)
|
||||||
if self.instance and self.instance.pk:
|
|
||||||
cnt -= self.instance.redeemed # these do not need quota any more
|
|
||||||
|
|
||||||
Voucher.clean_item_properties(
|
Voucher.clean_item_properties(
|
||||||
data, self.instance.event,
|
data, self.instance.event,
|
||||||
|
|||||||
@@ -267,7 +267,7 @@ class WaitingListView(EventPermissionRequiredMixin, WaitingListQuerySetMixin, Pa
|
|||||||
free_seats = num_free_seats_for_product - num_valid_vouchers_for_product
|
free_seats = num_free_seats_for_product - num_valid_vouchers_for_product
|
||||||
wle.availability = (
|
wle.availability = (
|
||||||
Quota.AVAILABILITY_GONE if free_seats == 0 else wle.availability[0],
|
Quota.AVAILABILITY_GONE if free_seats == 0 else wle.availability[0],
|
||||||
min(free_seats, wle.availability[1]) if wle.availability[1] is not None else free_seats,
|
min(free_seats, wle.availability[1])
|
||||||
)
|
)
|
||||||
|
|
||||||
itemvar_cache[(wle.item, wle.variation, wle.subevent)] = wle.availability
|
itemvar_cache[(wle.item, wle.variation, wle.subevent)] = wle.availability
|
||||||
|
|||||||
@@ -44,11 +44,12 @@ def validate_uploaded_file_for_valid_image(f):
|
|||||||
# have to read the data into memory.
|
# have to read the data into memory.
|
||||||
if hasattr(f, 'temporary_file_path'):
|
if hasattr(f, 'temporary_file_path'):
|
||||||
file = f.temporary_file_path()
|
file = f.temporary_file_path()
|
||||||
|
elif hasattr(f, 'read'):
|
||||||
|
if hasattr(f, 'seek') and callable(f.seek):
|
||||||
|
f.seek(0)
|
||||||
|
file = BytesIO(f.read())
|
||||||
else:
|
else:
|
||||||
if hasattr(f, 'read'):
|
file = BytesIO(f['content'])
|
||||||
file = BytesIO(f.read())
|
|
||||||
else:
|
|
||||||
file = BytesIO(f['content'])
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -924,6 +924,11 @@ class StripePaymentIntentMethod(StripeMethod):
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if self.method == "card":
|
||||||
|
params['statement_descriptor_suffix'] = self.statement_descriptor(payment)
|
||||||
|
else:
|
||||||
|
params['statement_descriptor'] = self.statement_descriptor(payment)
|
||||||
|
|
||||||
intent = stripe.PaymentIntent.create(
|
intent = stripe.PaymentIntent.create(
|
||||||
amount=self._get_amount(payment),
|
amount=self._get_amount(payment),
|
||||||
currency=self.event.currency.lower(),
|
currency=self.event.currency.lower(),
|
||||||
@@ -935,7 +940,6 @@ class StripePaymentIntentMethod(StripeMethod):
|
|||||||
event=self.event.slug.upper(),
|
event=self.event.slug.upper(),
|
||||||
code=payment.order.code
|
code=payment.order.code
|
||||||
),
|
),
|
||||||
statement_descriptor=self.statement_descriptor(payment),
|
|
||||||
metadata={
|
metadata={
|
||||||
'order': str(payment.order.id),
|
'order': str(payment.order.id),
|
||||||
'event': self.event.id,
|
'event': self.event.id,
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ from tests.const import SAMPLE_PNG
|
|||||||
|
|
||||||
from pretix.api.serializers.item import QuestionSerializer
|
from pretix.api.serializers.item import QuestionSerializer
|
||||||
from pretix.base.models import (
|
from pretix.base.models import (
|
||||||
Checkin, CheckinList, InvoiceAddress, Order, OrderPosition, LogEntry,
|
Checkin, CheckinList, InvoiceAddress, Order, OrderPosition,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -1128,17 +1128,11 @@ def test_store_failed(token_client, organizer, clist, event, order):
|
|||||||
), {
|
), {
|
||||||
'raw_barcode': '123456',
|
'raw_barcode': '123456',
|
||||||
'nonce': '4321',
|
'nonce': '4321',
|
||||||
'error_reason': 'invalid',
|
'error_reason': 'invalid'
|
||||||
'debug_data': {'foo': 'bar'},
|
|
||||||
}, format='json')
|
}, format='json')
|
||||||
assert resp.status_code == 201
|
assert resp.status_code == 201
|
||||||
with scopes_disabled():
|
with scopes_disabled():
|
||||||
assert Checkin.all.filter(successful=False).exists()
|
assert Checkin.all.filter(successful=False).exists()
|
||||||
for le in LogEntry.objects.filter():
|
|
||||||
print(le.parsed_data)
|
|
||||||
assert LogEntry.objects.filter(action_type='pretix.event.checkin.unknown').first().parsed_data['debug_data'] == {
|
|
||||||
'foo': 'bar'
|
|
||||||
}
|
|
||||||
|
|
||||||
resp = token_client.post('/api/v1/organizers/{}/events/{}/checkinlists/{}/failed_checkins/'.format(
|
resp = token_client.post('/api/v1/organizers/{}/events/{}/checkinlists/{}/failed_checkins/'.format(
|
||||||
organizer.slug, event.slug, clist.pk,
|
organizer.slug, event.slug, clist.pk,
|
||||||
@@ -1168,7 +1162,7 @@ def test_store_failed(token_client, organizer, clist, event, order):
|
|||||||
'raw_barcode': '123456',
|
'raw_barcode': '123456',
|
||||||
'nonce': '1234',
|
'nonce': '1234',
|
||||||
'position': p.pk,
|
'position': p.pk,
|
||||||
'error_reason': 'unpaid',
|
'error_reason': 'unpaid'
|
||||||
}, format='json')
|
}, format='json')
|
||||||
assert resp.status_code == 201
|
assert resp.status_code == 201
|
||||||
with scopes_disabled():
|
with scopes_disabled():
|
||||||
|
|||||||
@@ -2006,20 +2006,6 @@ class OrderChangeManagerTests(TestCase):
|
|||||||
).confirm()
|
).confirm()
|
||||||
assert self.order.invoices.count() == 3
|
assert self.order.invoices.count() == 3
|
||||||
|
|
||||||
@classscope(attr='o')
|
|
||||||
def test_reissue_invoice_paid_only_after_payment_only_if_enabled(self):
|
|
||||||
self.event.settings.invoice_generate = "False"
|
|
||||||
assert self.order.invoices.count() == 0
|
|
||||||
self.ocm.add_position(self.ticket, None, Decimal('2.00'))
|
|
||||||
self.ocm.commit()
|
|
||||||
assert self.order.invoices.count() == 0
|
|
||||||
self.order.refresh_from_db()
|
|
||||||
assert not self.order.invoice_dirty
|
|
||||||
self.order.payments.create(
|
|
||||||
provider='manual', amount=self.order.total
|
|
||||||
).confirm()
|
|
||||||
assert self.order.invoices.count() == 0
|
|
||||||
|
|
||||||
@classscope(attr='o')
|
@classscope(attr='o')
|
||||||
def test_reissue_invoice_paid_stays_paid(self):
|
def test_reissue_invoice_paid_stays_paid(self):
|
||||||
self.event.settings.invoice_generate = "paid"
|
self.event.settings.invoice_generate = "paid"
|
||||||
|
|||||||
@@ -965,31 +965,6 @@ def test_limit_products(event, item, item2):
|
|||||||
assert sorted(new_prices) == sorted(expected)
|
assert sorted(new_prices) == sorted(expected)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
|
||||||
@scopes_disabled()
|
|
||||||
def test_limit_products_subevents_distinct(event, item, item2):
|
|
||||||
d1 = Discount(event=event, condition_min_count=2, benefit_discount_matching_percent=20, condition_all_products=False,
|
|
||||||
subevent_mode=Discount.SUBEVENT_MODE_DISTINCT)
|
|
||||||
d1.save()
|
|
||||||
d1.condition_limit_products.add(item)
|
|
||||||
|
|
||||||
positions = (
|
|
||||||
(item.pk, 1, Decimal('100.00'), False, False, Decimal('0.00')),
|
|
||||||
(item.pk, 2, Decimal('100.00'), False, False, Decimal('0.00')),
|
|
||||||
(item.pk, 3, Decimal('100.00'), False, False, Decimal('0.00')),
|
|
||||||
(item2.pk, 4, Decimal('50.00'), False, False, Decimal('0.00')),
|
|
||||||
)
|
|
||||||
expected = (
|
|
||||||
Decimal('80.00'),
|
|
||||||
Decimal('80.00'),
|
|
||||||
Decimal('80.00'),
|
|
||||||
Decimal('50.00'),
|
|
||||||
)
|
|
||||||
|
|
||||||
new_prices = [p for p, d in apply_discounts(event, 'web', positions)]
|
|
||||||
assert sorted(new_prices) == sorted(expected)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
@scopes_disabled()
|
@scopes_disabled()
|
||||||
def test_sales_channels(event, item):
|
def test_sales_channels(event, item):
|
||||||
|
|||||||
@@ -365,19 +365,6 @@ class VoucherFormTest(SoupTestMixin, TransactionTestCase):
|
|||||||
v.refresh_from_db()
|
v.refresh_from_db()
|
||||||
assert v.valid_until < now()
|
assert v.valid_until < now()
|
||||||
|
|
||||||
def test_change_voucher_validity_to_valid_quota_full_already_redeemed(self):
|
|
||||||
self.quota_tickets.size = 1
|
|
||||||
self.quota_tickets.save()
|
|
||||||
with scopes_disabled():
|
|
||||||
v = self.event.vouchers.create(item=self.ticket, valid_until=now() - datetime.timedelta(days=3),
|
|
||||||
block_quota=True, redeemed=1, max_usages=2)
|
|
||||||
self._change_voucher(v, {
|
|
||||||
'valid_until_0': (now() + datetime.timedelta(days=3)).strftime('%Y-%m-%d'),
|
|
||||||
'valid_until_1': (now() + datetime.timedelta(days=3)).strftime('%H:%M:%S')
|
|
||||||
})
|
|
||||||
v.refresh_from_db()
|
|
||||||
assert v.valid_until > now()
|
|
||||||
|
|
||||||
def test_change_voucher_validity_to_valid_quota_free(self):
|
def test_change_voucher_validity_to_valid_quota_free(self):
|
||||||
with scopes_disabled():
|
with scopes_disabled():
|
||||||
v = self.event.vouchers.create(item=self.ticket, valid_until=now() - datetime.timedelta(days=3),
|
v = self.event.vouchers.create(item=self.ticket, valid_until=now() - datetime.timedelta(days=3),
|
||||||
|
|||||||
Reference in New Issue
Block a user