mirror of
https://github.com/pretix/pretix.git
synced 2025-12-06 21:42:49 +00:00
Compare commits
7 Commits
v2023.10.2
...
scan-debug
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8fa715ac4b | ||
|
|
6c479808d0 | ||
|
|
bd14be485a | ||
|
|
fbf362a91f | ||
|
|
82704b60c7 | ||
|
|
b92feb382b | ||
|
|
66f934bba7 |
@@ -59,7 +59,7 @@ dependencies = [
|
||||
"dnspython==2.3.*",
|
||||
"drf_ujson2==1.7.*",
|
||||
"geoip2==4.*",
|
||||
"importlib_metadata==7.*", # Polyfill, we can probably drop this once we require Python 3.10+
|
||||
"importlib_metadata==6.*", # Polyfill, we can probably drop this once we require Python 3.10+
|
||||
"isoweek",
|
||||
"jsonschema",
|
||||
"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
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
__version__ = "2023.10.2"
|
||||
__version__ = "2023.11.0.dev0"
|
||||
|
||||
@@ -152,6 +152,11 @@ class CheckinListViewSet(viewsets.ModelViewSet):
|
||||
@action(detail=True, methods=['POST'], url_name='failed_checkins')
|
||||
@transaction.atomic()
|
||||
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(
|
||||
data=self.request.data,
|
||||
context={'event': self.request.event}
|
||||
@@ -194,14 +199,16 @@ class CheckinListViewSet(viewsets.ModelViewSet):
|
||||
'reason_explanation': c.error_explanation,
|
||||
'datetime': c.datetime,
|
||||
'type': c.type,
|
||||
'list': c.list.pk
|
||||
'list': c.list.pk,
|
||||
**additional_log_data,
|
||||
}, user=self.request.user, auth=self.request.auth)
|
||||
else:
|
||||
self.request.event.log_action('pretix.event.checkin.unknown', data={
|
||||
'datetime': c.datetime,
|
||||
'type': c.type,
|
||||
'list': c.list.pk,
|
||||
'barcode': c.raw_barcode
|
||||
'barcode': c.raw_barcode,
|
||||
**additional_log_data,
|
||||
}, user=self.request.user, auth=self.request.auth)
|
||||
|
||||
return Response(serializer.data, status=201)
|
||||
|
||||
@@ -104,7 +104,7 @@ class Command(BaseCommand):
|
||||
with language(locale), override(timezone):
|
||||
for receiver, response in signal_result:
|
||||
if not response:
|
||||
return None
|
||||
continue
|
||||
ex = response(e, o, report_status)
|
||||
if ex.identifier == options['export_provider']:
|
||||
params = json.loads(options.get('parameters') or '{}')
|
||||
|
||||
@@ -424,5 +424,10 @@ class Discount(LoggedModel):
|
||||
break
|
||||
|
||||
for g in candidate_groups:
|
||||
self._apply_min_count(positions, g, g, result)
|
||||
self._apply_min_count(
|
||||
positions,
|
||||
[idx for idx in g if idx in condition_candidates],
|
||||
[idx for idx in g if idx in benefit_candidates],
|
||||
result
|
||||
)
|
||||
return result
|
||||
|
||||
@@ -2620,7 +2620,7 @@ class OrderChangeManager:
|
||||
i = self.order.invoices.filter(is_cancellation=False).last()
|
||||
if self.reissue_invoice and self._invoice_dirty:
|
||||
order_now_qualified = invoice_qualified(self.order)
|
||||
invoice_should_be_generated = (
|
||||
invoice_should_be_generated_now = (
|
||||
self.event.settings.invoice_generate == "True" or (
|
||||
self.event.settings.invoice_generate == "paid" and
|
||||
self.open_payment is not None and
|
||||
@@ -2635,13 +2635,16 @@ class OrderChangeManager:
|
||||
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 invoice_should_be_generated:
|
||||
if invoice_should_be_generated_now:
|
||||
if i and not i.canceled:
|
||||
self._invoices.append(generate_cancellation(i))
|
||||
self._invoices.append(generate_invoice(self.order))
|
||||
else:
|
||||
elif invoice_should_be_generated_later:
|
||||
self.order.invoice_dirty = True
|
||||
self.order.save(update_fields=["invoice_dirty"])
|
||||
else:
|
||||
|
||||
@@ -219,17 +219,15 @@ class ExtValidationMixin:
|
||||
|
||||
def clean(self, *args, **kwargs):
|
||||
data = super().clean(*args, **kwargs)
|
||||
|
||||
from ...base.models import CachedFile
|
||||
if isinstance(data, (UploadedFile, CachedFile)):
|
||||
filename = data.name if isinstance(data, UploadedFile) else data.filename
|
||||
if isinstance(data, UploadedFile):
|
||||
filename = data.name
|
||||
ext = os.path.splitext(filename)[1]
|
||||
ext = ext.lower()
|
||||
if ext not in self.ext_whitelist:
|
||||
raise forms.ValidationError(_("Filetype not allowed!"))
|
||||
|
||||
if ext in IMAGE_EXTS:
|
||||
validate_uploaded_file_for_valid_image(data if isinstance(data, UploadedFile) else data.file)
|
||||
validate_uploaded_file_for_valid_image(data)
|
||||
|
||||
return data
|
||||
|
||||
@@ -259,12 +257,6 @@ class CachedFileField(ExtFileField):
|
||||
if isinstance(data, File):
|
||||
if hasattr(data, '_uploaded_to'):
|
||||
return data._uploaded_to
|
||||
|
||||
try:
|
||||
self.clean(data)
|
||||
except ValidationError:
|
||||
return None
|
||||
|
||||
cf = CachedFile.objects.create(
|
||||
expires=now() + datetime.timedelta(days=1),
|
||||
date=now(),
|
||||
@@ -276,9 +268,6 @@ class CachedFileField(ExtFileField):
|
||||
cf.save()
|
||||
data._uploaded_to = cf
|
||||
return cf
|
||||
if isinstance(data, CachedFile):
|
||||
return data
|
||||
|
||||
return super().bound_data(data, initial)
|
||||
|
||||
def clean(self, *args, **kwargs):
|
||||
|
||||
@@ -201,6 +201,8 @@ class VoucherForm(I18nModelForm):
|
||||
cnt = len(data['codes']) * data.get('max_usages', 0)
|
||||
else:
|
||||
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(
|
||||
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
|
||||
wle.availability = (
|
||||
Quota.AVAILABILITY_GONE if free_seats == 0 else wle.availability[0],
|
||||
min(free_seats, wle.availability[1])
|
||||
min(free_seats, wle.availability[1]) if wle.availability[1] is not None else free_seats,
|
||||
)
|
||||
|
||||
itemvar_cache[(wle.item, wle.variation, wle.subevent)] = wle.availability
|
||||
|
||||
@@ -44,12 +44,11 @@ def validate_uploaded_file_for_valid_image(f):
|
||||
# have to read the data into memory.
|
||||
if hasattr(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:
|
||||
file = BytesIO(f['content'])
|
||||
if hasattr(f, 'read'):
|
||||
file = BytesIO(f.read())
|
||||
else:
|
||||
file = BytesIO(f['content'])
|
||||
|
||||
try:
|
||||
try:
|
||||
|
||||
@@ -924,11 +924,6 @@ 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(
|
||||
amount=self._get_amount(payment),
|
||||
currency=self.event.currency.lower(),
|
||||
@@ -940,6 +935,7 @@ class StripePaymentIntentMethod(StripeMethod):
|
||||
event=self.event.slug.upper(),
|
||||
code=payment.order.code
|
||||
),
|
||||
statement_descriptor=self.statement_descriptor(payment),
|
||||
metadata={
|
||||
'order': str(payment.order.id),
|
||||
'event': self.event.id,
|
||||
|
||||
@@ -35,7 +35,7 @@ from tests.const import SAMPLE_PNG
|
||||
|
||||
from pretix.api.serializers.item import QuestionSerializer
|
||||
from pretix.base.models import (
|
||||
Checkin, CheckinList, InvoiceAddress, Order, OrderPosition,
|
||||
Checkin, CheckinList, InvoiceAddress, Order, OrderPosition, LogEntry,
|
||||
)
|
||||
|
||||
|
||||
@@ -1128,11 +1128,17 @@ def test_store_failed(token_client, organizer, clist, event, order):
|
||||
), {
|
||||
'raw_barcode': '123456',
|
||||
'nonce': '4321',
|
||||
'error_reason': 'invalid'
|
||||
'error_reason': 'invalid',
|
||||
'debug_data': {'foo': 'bar'},
|
||||
}, format='json')
|
||||
assert resp.status_code == 201
|
||||
with scopes_disabled():
|
||||
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(
|
||||
organizer.slug, event.slug, clist.pk,
|
||||
@@ -1162,7 +1168,7 @@ def test_store_failed(token_client, organizer, clist, event, order):
|
||||
'raw_barcode': '123456',
|
||||
'nonce': '1234',
|
||||
'position': p.pk,
|
||||
'error_reason': 'unpaid'
|
||||
'error_reason': 'unpaid',
|
||||
}, format='json')
|
||||
assert resp.status_code == 201
|
||||
with scopes_disabled():
|
||||
|
||||
@@ -2006,6 +2006,20 @@ class OrderChangeManagerTests(TestCase):
|
||||
).confirm()
|
||||
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')
|
||||
def test_reissue_invoice_paid_stays_paid(self):
|
||||
self.event.settings.invoice_generate = "paid"
|
||||
|
||||
@@ -965,6 +965,31 @@ def test_limit_products(event, item, item2):
|
||||
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
|
||||
@scopes_disabled()
|
||||
def test_sales_channels(event, item):
|
||||
|
||||
@@ -365,6 +365,19 @@ class VoucherFormTest(SoupTestMixin, TransactionTestCase):
|
||||
v.refresh_from_db()
|
||||
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):
|
||||
with scopes_disabled():
|
||||
v = self.event.vouchers.create(item=self.ticket, valid_until=now() - datetime.timedelta(days=3),
|
||||
|
||||
Reference in New Issue
Block a user