Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions app/eventyay/base/exporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -345,10 +345,11 @@ def _render_xlsx(self, form_data, output_file=None):
)

def render(self, form_data: dict, output_file=None) -> Tuple[str, str, bytes]:
if form_data.get('_format') == 'xlsx':
format_value = form_data.get('_format')
if format_value == 'xlsx':
return self._render_xlsx(form_data, output_file=output_file)
elif ':' in form_data.get('_format'):
sheet, f = form_data.get('_format').split(':')
elif format_value and ':' in format_value:
sheet, f = format_value.split(':')
if f == 'default':
return self._render_sheet_csv(
form_data,
Expand Down
2 changes: 1 addition & 1 deletion app/eventyay/base/exporters/dekodi.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class DekodiNREIExporter(BaseExporter):

def _encode_invoice(self, invoice: Invoice):
p_last = invoice.order.payments.filter(
state=[
state__in=[
OrderPayment.PAYMENT_STATE_CONFIRMED,
OrderPayment.PAYMENT_STATE_REFUNDED,
]
Expand Down
8 changes: 4 additions & 4 deletions app/eventyay/base/exporters/json.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def render(self, form_data):
for variation in item.variations.all()
],
}
for item in self.event.items.select_related('tax_rule').prefetch_related('variations')
for item in self.event.products.select_related('tax_rule').prefetch_related('variations')
],
'questions': [
{
Expand Down Expand Up @@ -83,7 +83,7 @@ def render(self, form_data):
'positions': [
{
'id': position.id,
'item': position.item_id,
'item': position.product_id,
'variation': position.variation_id,
'price': position.price,
'attendee_name': position.attendee_name,
Expand All @@ -107,10 +107,10 @@ def render(self, form_data):
{
'id': quota.id,
'size': quota.size,
'items': [item.id for item in quota.items.all()],
'items': [item.id for item in quota.products.all()],
'variations': [variation.id for variation in quota.variations.all()],
}
for quota in self.event.quotas.all().prefetch_related('items', 'variations')
for quota in self.event.quotas.all().prefetch_related('products', 'variations')
],
}
}
Expand Down
14 changes: 7 additions & 7 deletions app/eventyay/base/exporters/orderlist.py
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ def iterate_orders(self, form_data: dict):

qs = self._date_filter(qs, form_data, rel='')

if form_data['paid_only']:
if form_data.get('paid_only', True):
qs = qs.filter(status=Order.STATUS_PAID)
tax_rates = self._get_all_tax_rates(qs)

Expand Down Expand Up @@ -529,7 +529,7 @@ def iterate_fees(self, form_data: dict):
)
.select_related('order', 'order__invoice_address', 'tax_rule')
)
if form_data['paid_only']:
if form_data.get('paid_only', True):
qs = qs.filter(order__status=Order.STATUS_PAID)

qs = self._date_filter(qs, form_data, rel='order__')
Expand Down Expand Up @@ -645,14 +645,14 @@ def iterate_positions(self, form_data: dict):
.select_related(
'order',
'order__invoice_address',
'item',
'product',
'variation',
'voucher',
'tax_rule',
)
.prefetch_related('answers', 'answers__question', 'answers__options')
)
if form_data['paid_only']:
if form_data.get('paid_only', True):
qs = qs.filter(order__status=Order.STATUS_PAID)

qs = self._date_filter(qs, form_data, rel='order__')
Expand Down Expand Up @@ -709,7 +709,7 @@ def iterate_positions(self, form_data: dict):
for q in questions:
if q.type == Question.TYPE_CHOICE_MULTIPLE:
options[q.pk] = []
if form_data['group_multiple_choice']:
if form_data.get('group_multiple_choice', False):
for o in q.options.all():
options[q.pk].append(o)
headers.append(str(q.question))
Expand Down Expand Up @@ -781,7 +781,7 @@ def iterate_positions(self, form_data: dict):
row.append('')
row.append('')
row += [
str(op.item),
str(op.product),
str(op.variation) if op.variation else '',
op.price,
op.tax_rate,
Expand Down Expand Up @@ -828,7 +828,7 @@ def iterate_positions(self, form_data: dict):
acache[a.question_id] = str(a)
for q in questions:
if q.type == Question.TYPE_CHOICE_MULTIPLE:
if form_data['group_multiple_choice']:
if form_data.get('group_multiple_choice', False):
row.append(
', '.join(str(o.answer) for o in options[q.pk] if o.pk in acache.get(q.pk, set()))
)
Expand Down
22 changes: 11 additions & 11 deletions app/eventyay/base/exporters/waitinglist.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ def iterate_list(self, form_data):
WaitingListEntry.objects.filter(
event__in=self.events,
)
.select_related('item', 'variation', 'voucher', 'subevent')
.select_related('product', 'variation', 'voucher', 'subevent')
.order_by('created')
)

Expand Down Expand Up @@ -118,16 +118,16 @@ def iterate_list(self, form_data):
datetime_format = '%Y-%m-%d %H:%M:%S %Z'

row = [
entry.created.astimezone(tz).strftime(datetime_format), # alternative: .isoformat(),
entry.name,
entry.email,
entry.phone,
str(entry.item) if entry.item else '',
str(entry.variation) if entry.variation else '',
entry.event.slug,
entry.event.name,
entry.subevent.name if entry.subevent else '',
event_for_date_columns.date_from.astimezone(tz).strftime(datetime_format),
entry.created.astimezone(tz).strftime(datetime_format), # alternative: .isoformat(),
entry.name,
entry.email,
entry.phone,
str(entry.product) if entry.product else '',
str(entry.variation) if entry.variation else '',
entry.event.slug,
entry.event.name,
entry.subevent.name if entry.subevent else '',
event_for_date_columns.date_from.astimezone(tz).strftime(datetime_format),
Comment on lines +121 to +130
Copy link

Copilot AI Nov 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent indentation in list construction. Lines 121-130 should be indented to align with the opening bracket on line 120. All list items should have consistent indentation (either all at the same level as line 120, or all indented one level deeper).

Suggested change
entry.created.astimezone(tz).strftime(datetime_format), # alternative: .isoformat(),
entry.name,
entry.email,
entry.phone,
str(entry.product) if entry.product else '',
str(entry.variation) if entry.variation else '',
entry.event.slug,
entry.event.name,
entry.subevent.name if entry.subevent else '',
event_for_date_columns.date_from.astimezone(tz).strftime(datetime_format),
entry.created.astimezone(tz).strftime(datetime_format), # alternative: .isoformat(),
entry.name,
entry.email,
entry.phone,
str(entry.product) if entry.product else '',
str(entry.variation) if entry.variation else '',
entry.event.slug,
entry.event.name,
entry.subevent.name if entry.subevent else '',
event_for_date_columns.date_from.astimezone(tz).strftime(datetime_format),

Copilot uses AI. Check for mistakes.
event_for_date_columns.date_to.astimezone(tz).strftime(datetime_format)
if event_for_date_columns.date_to
else '',
Expand Down
24 changes: 23 additions & 1 deletion app/eventyay/base/services/export.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,14 @@ def set_progress(val):
'Your data table is too big for a PDF page. Please reduce the amount of data you are exporting.'
)
raise ExportError(msg) from e
except Exception as e:
logger.exception(f'Error during export with provider {provider}.')
# Provide specific error message based on exception type
error_msg = str(e) if str(e) else type(e).__name__
msg = gettext(
'An error occurred while generating your export: {error}. Please try again or contact support if the problem persists.'
).format(error=error_msg)
raise ExportError(msg) from e
if d is None:
raise ExportError(gettext('Your export did not contain any data.'))
file.filename, file.type, data = d
Expand Down Expand Up @@ -113,7 +121,21 @@ def set_progress(val):
continue
ex = response(events, set_progress)
if ex.identifier == provider:
d = ex.render(form_data)
try:
d = ex.render(form_data)
except LayoutError as e:
logger.exception('Error while making PDF.')
msg = gettext(
'Your data table is too big for a PDF page. Please reduce the amount of data you are exporting.'
)
raise ExportError(msg) from e
except Exception as e:
logger.exception(f'Error during multi-event export with provider {provider}.')
error_msg = str(e) if str(e) else type(e).__name__
msg = gettext(
'An error occurred while generating your export: {error}. Please try again or contact support if the problem persists.'
).format(error=error_msg)
raise ExportError(msg) from e
if d is None:
raise ExportError(gettext('Your export did not contain any data.'))
file.filename, file.type, data = d
Expand Down
3 changes: 3 additions & 0 deletions app/eventyay/config/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,9 @@ def instance_name(request):
}
}

# Feature flag for JSON field support (PostgreSQL only)
JSON_FIELD_AVAILABLE = DATABASES['default']['ENGINE'].split('.')[-1] == 'postgresql'


AUTHENTICATION_BACKENDS = (
'rules.permissions.ObjectPermissionBackend',
Expand Down
5 changes: 3 additions & 2 deletions app/eventyay/plugins/badges/exporters.py
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,9 @@ def render_page(positions):
if pagebuffer:
render_page(pagebuffer)

if not any:
raise OrderError(_('None of the selected products is configured to print badges.'))

Comment on lines +235 to +237
Copy link

Copilot AI Nov 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moving the empty badge check before writing metadata prevents writing corrupt PDFs, but the check should occur before attempting to write to outbuffer (line 244). If any is False, output_pdf_writer has no pages added, and calling write(outbuffer) on line 244 may still produce an invalid or empty PDF before the error is raised on line 236. The check should be moved to before line 238 to prevent any write operations.

Suggested change
if not any:
raise OrderError(_('None of the selected products is configured to print badges.'))
# Check for empty badge set before writing metadata or PDF
if not any:
raise OrderError(_('None of the selected products is configured to print badges.'))

Copilot uses AI. Check for mistakes.
output_pdf_writer.add_metadata(
{
'/Title': 'Badges',
Expand All @@ -240,8 +243,6 @@ def render_page(positions):
)
output_pdf_writer.write(outbuffer)
outbuffer.seek(0)
if not any:
raise OrderError(_('None of the selected products is configured to print badges.'))
return outbuffer


Expand Down
17 changes: 11 additions & 6 deletions app/eventyay/plugins/checkinlists/exporters.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ def _get_queryset(self, cl, form_data):
)
.select_related(
'order',
'item',
'product',
'variation',
'addon_to',
'order__invoice_address',
Expand All @@ -195,7 +195,7 @@ def _get_queryset(self, cl, form_data):
)

if not cl.all_products:
qs = qs.filter(item__in=cl.limit_products.values_list('id', flat=True))
qs = qs.filter(product__in=cl.limit_products.values_list('id', flat=True))

if cl.subevent:
qs = qs.filter(subevent=cl.subevent)
Expand Down Expand Up @@ -311,9 +311,14 @@ def pagesize(self):
return pagesizes.landscape(pagesizes.A4)

def get_story(self, doc, form_data):
if 'list' not in form_data or not form_data['list']:
# Return empty story instead of None
from reportlab.platypus import Paragraph
return [Paragraph("No check-in list selected.", self.get_style())]

cl = self.event.checkin_lists.get(pk=form_data['list'])

questions = tuple(Question.objects.filter(event=self.event, id__in=form_data['questions']))
questions = tuple(Question.objects.filter(event=self.event, id__in=form_data.get('questions', [])))

headlinestyle = self.get_style()
headlinestyle.fontSize = 15
Expand Down Expand Up @@ -698,12 +703,12 @@ def iterate_list(self, form_data):
if form_data.get('list'):
qs = qs.filter(list_id=form_data.get('list'))
if form_data.get('items'):
qs = qs.filter(position__item_id__in=form_data['items'])
qs = qs.filter(position__product_id__in=form_data['items'])

yield self.ProgressSetTotal(total=qs.count())

qs = qs.select_related(
'position__item',
'position__product',
'position__order',
'position__order__invoice_address',
'position',
Expand All @@ -724,7 +729,7 @@ def iterate_list(self, form_data):
ci.position.order.code,
ci.position.positionid,
ci.position.secret,
str(ci.position.item),
str(ci.position.product),
ci.position.attendee_name or ia.name,
str(ci.device),
_('Yes') if ci.forced else _('No'),
Expand Down
21 changes: 13 additions & 8 deletions app/eventyay/plugins/reports/exporters.py
Original file line number Diff line number Diff line change
Expand Up @@ -375,28 +375,33 @@ def _table_story(self, doc, form_data, net=False):
if tup[0]:
tdata.append([Paragraph(str(tup[0].name), tstyle_bold)])
for l, s in states:
tdata[-1].append(str(tup[0].num[l][0]))
tdata[-1].append(floatformat(tup[0].num[l][2 if net else 1], places))
num_data = tup[0].num.get(l, (0, 0, 0)) if hasattr(tup[0], 'num') and tup[0].num else (0, 0, 0)
tdata[-1].append(str(num_data[0] if len(num_data) > 0 else 0))
tdata[-1].append(floatformat(num_data[2 if net else 1] if len(num_data) > (2 if net else 1) else 0, places))
Comment on lines +378 to +380
Copy link

Copilot AI Nov 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The defensive check if len(num_data) > 0 on line 379 is redundant since num_data is guaranteed to be a tuple of length 3 from the get() default (0, 0, 0) on line 378. The same applies to the length check on line 380. These checks can be simplified to directly access num_data[0] and num_data[2 if net else 1].

Copilot uses AI. Check for mistakes.
for item in tup[1]:
tdata.append([str(item)])
for l, s in states:
tdata[-1].append(str(item.num[l][0]))
tdata[-1].append(floatformat(item.num[l][2 if net else 1], places))
num_data = item.num.get(l, (0, 0, 0)) if hasattr(item, 'num') and item.num else (0, 0, 0)
tdata[-1].append(str(num_data[0] if len(num_data) > 0 else 0))
tdata[-1].append(floatformat(num_data[2 if net else 1] if len(num_data) > (2 if net else 1) else 0, places))
Comment on lines +384 to +386
Copy link

Copilot AI Nov 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The defensive check if len(num_data) > 0 on line 385 is redundant since num_data is guaranteed to be a tuple of length 3 from the get() default (0, 0, 0) on line 384. The same applies to the length check on line 386. These checks can be simplified to directly access num_data[0] and num_data[2 if net else 1].

Copilot uses AI. Check for mistakes.
if item.has_variations:
for var in item.all_variations:
tdata.append([Paragraph(' ' + str(var), tstyle)])
for l, s in states:
tdata[-1].append(str(var.num[l][0]))
tdata[-1].append(floatformat(var.num[l][2 if net else 1], places))
num_data = var.num.get(l, (0, 0, 0)) if hasattr(var, 'num') and var.num else (0, 0, 0)
tdata[-1].append(str(num_data[0] if len(num_data) > 0 else 0))
tdata[-1].append(floatformat(num_data[2 if net else 1] if len(num_data) > (2 if net else 1) else 0, places))
Comment on lines +391 to +393
Copy link

Copilot AI Nov 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The defensive check if len(num_data) > 0 on line 392 is redundant since num_data is guaranteed to be a tuple of length 3 from the get() default (0, 0, 0) on line 391. The same applies to the length check on line 393. These checks can be simplified to directly access num_data[0] and num_data[2 if net else 1].

Copilot uses AI. Check for mistakes.

tdata.append(
[
_('Total'),
]
)
for l, s in states:
tdata[-1].append(str(total['num'][l][0]))
tdata[-1].append(floatformat(total['num'][l][2 if net else 1], places))
# Safeguard for empty data
num_data = total.get('num', {}).get(l, (0, 0, 0))
tdata[-1].append(str(num_data[0] if len(num_data) > 0 else 0))
tdata[-1].append(floatformat(num_data[2 if net else 1] if len(num_data) > (2 if net else 1) else 0, places))
Comment on lines +403 to +404
Copy link

Copilot AI Nov 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The defensive check if len(num_data) > 0 on line 403 is redundant since num_data is guaranteed to be a tuple of length 3 from the get() default (0, 0, 0) on line 402. The same applies to the length check on line 404. These checks can be simplified to directly access num_data[0] and num_data[2 if net else 1].

Suggested change
tdata[-1].append(str(num_data[0] if len(num_data) > 0 else 0))
tdata[-1].append(floatformat(num_data[2 if net else 1] if len(num_data) > (2 if net else 1) else 0, places))
tdata[-1].append(str(num_data[0]))
tdata[-1].append(floatformat(num_data[2 if net else 1], places))

Copilot uses AI. Check for mistakes.

table = Table(tdata, colWidths=colwidths, repeatRows=3)
table.setStyle(TableStyle(tstyledata))
Expand Down
7 changes: 6 additions & 1 deletion app/eventyay/plugins/ticketoutputpdf/exporters.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,10 +141,12 @@ def render(self, form_data):
)

o = PdfTicketOutput(Event.objects.none())
any_tickets = False
for op in qs:
if not op.generate_ticket:
continue


any_tickets = True
if op.order.event != o.event:
o = PdfTicketOutput(op.event)

Expand All @@ -156,6 +158,9 @@ def render(self, form_data):
outbuffer = o._draw_page(layout, op, op.order)
merger.append(ContentFile(outbuffer.read()))

if not any_tickets:
return None

outbuffer = BytesIO()
merger.write(outbuffer)
merger.close()
Expand Down
Loading