Compare commits

..

9 Commits

Author SHA1 Message Date
Raphael Michel
0eb5534c4d Bump version to 2023.10.2 2024-02-21 15:39:22 +01:00
Raphael Michel
1ece080fa0 CachedFileField: Do not store file that does not pass validation 2024-02-21 15:39:09 +01:00
Mira Weller
03a302415a forms: fix image file upload in CachedFileField 2024-02-21 15:39:09 +01:00
Mira Weller
0e73611f7c forms: fix bound data retrieval of CachedFile
when re-submitting a form a second time, the cached file got lost
2024-02-21 15:39:09 +01:00
Mira Weller
5fe5b82f1a forms: fix file type validation on CachedFileInput 2024-02-21 15:39:09 +01:00
Raphael Michel
9aa2976bda Bump to 2023.10.1.post1 2024-01-17 22:36:47 +01:00
Raphael Michel
eec60f6242 Bump importlib_metadata to 7 2024-01-17 22:36:44 +01:00
Raphael Michel
c190fc315c Bump to 2023.10.1 2024-01-17 22:32:06 +01:00
Raphael Michel
eebd499359 Fix #3810 -- Stripe: Move to statement_descriptor_suffix 2024-01-17 22:31:18 +01:00
15 changed files with 37 additions and 96 deletions

View File

@@ -59,7 +59,7 @@ dependencies = [
"dnspython==2.3.*",
"drf_ujson2==1.7.*",
"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",
"jsonschema",
"kombu==5.3.*",

View File

@@ -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.11.0.dev0"
__version__ = "2023.10.2"

View File

@@ -152,11 +152,6 @@ 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}
@@ -199,16 +194,14 @@ class CheckinListViewSet(viewsets.ModelViewSet):
'reason_explanation': c.error_explanation,
'datetime': c.datetime,
'type': c.type,
'list': c.list.pk,
**additional_log_data,
'list': c.list.pk
}, 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,
**additional_log_data,
'barcode': c.raw_barcode
}, user=self.request.user, auth=self.request.auth)
return Response(serializer.data, status=201)

View File

@@ -104,7 +104,7 @@ class Command(BaseCommand):
with language(locale), override(timezone):
for receiver, response in signal_result:
if not response:
continue
return None
ex = response(e, o, report_status)
if ex.identifier == options['export_provider']:
params = json.loads(options.get('parameters') or '{}')

View File

@@ -424,10 +424,5 @@ class Discount(LoggedModel):
break
for g in candidate_groups:
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
)
self._apply_min_count(positions, g, g, result)
return result

View File

@@ -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_now = (
invoice_should_be_generated = (
self.event.settings.invoice_generate == "True" or (
self.event.settings.invoice_generate == "paid" and
self.open_payment is not None and
@@ -2635,16 +2635,13 @@ 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_now:
if invoice_should_be_generated:
if i and not i.canceled:
self._invoices.append(generate_cancellation(i))
self._invoices.append(generate_invoice(self.order))
elif invoice_should_be_generated_later:
else:
self.order.invoice_dirty = True
self.order.save(update_fields=["invoice_dirty"])
else:

View File

@@ -219,15 +219,17 @@ class ExtValidationMixin:
def clean(self, *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 = 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)
validate_uploaded_file_for_valid_image(data if isinstance(data, UploadedFile) else data.file)
return data
@@ -257,6 +259,12 @@ 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(),
@@ -268,6 +276,9 @@ 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):

View File

@@ -201,8 +201,6 @@ 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,

View File

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

View File

@@ -44,11 +44,12 @@ 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:
if hasattr(f, 'read'):
file = BytesIO(f.read())
else:
file = BytesIO(f['content'])
file = BytesIO(f['content'])
try:
try:

View File

@@ -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(
amount=self._get_amount(payment),
currency=self.event.currency.lower(),
@@ -935,7 +940,6 @@ 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,

View File

@@ -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, LogEntry,
Checkin, CheckinList, InvoiceAddress, Order, OrderPosition,
)
@@ -1128,17 +1128,11 @@ def test_store_failed(token_client, organizer, clist, event, order):
), {
'raw_barcode': '123456',
'nonce': '4321',
'error_reason': 'invalid',
'debug_data': {'foo': 'bar'},
'error_reason': 'invalid'
}, 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,
@@ -1168,7 +1162,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():

View File

@@ -2006,20 +2006,6 @@ 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"

View File

@@ -965,31 +965,6 @@ 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):

View File

@@ -365,19 +365,6 @@ 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),