mirror of
https://github.com/pretix/pretix.git
synced 2025-12-13 12:42:26 +00:00
Compare commits
11 Commits
subevent-e
...
fix-rrule
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
22d8158356 | ||
|
|
bff0f54bf8 | ||
|
|
50c1c9c724 | ||
|
|
802268df46 | ||
|
|
a823f261f3 | ||
|
|
59a754f913 | ||
|
|
82eca01e5c | ||
|
|
943f594b6b | ||
|
|
15cbb3a416 | ||
|
|
f447e7b9c4 | ||
|
|
dcf473c543 |
@@ -91,7 +91,7 @@ dependencies = [
|
|||||||
"redis==6.4.*",
|
"redis==6.4.*",
|
||||||
"reportlab==4.4.*",
|
"reportlab==4.4.*",
|
||||||
"requests==2.32.*",
|
"requests==2.32.*",
|
||||||
"sentry-sdk==2.38.*",
|
"sentry-sdk==2.40.*",
|
||||||
"sepaxml==2.6.*",
|
"sepaxml==2.6.*",
|
||||||
"stripe==7.9.*",
|
"stripe==7.9.*",
|
||||||
"text-unidecode==1.*",
|
"text-unidecode==1.*",
|
||||||
|
|||||||
@@ -764,7 +764,13 @@ class EventOrderViewSet(OrderViewSetMixin, viewsets.ModelViewSet):
|
|||||||
) and not order.invoices.last()
|
) and not order.invoices.last()
|
||||||
invoice = None
|
invoice = None
|
||||||
if gen_invoice:
|
if gen_invoice:
|
||||||
invoice = generate_invoice(order, trigger_pdf=True)
|
try:
|
||||||
|
invoice = generate_invoice(order, trigger_pdf=True)
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception("Could not generate invoice.")
|
||||||
|
order.log_action("pretix.event.order.invoice.failed", data={
|
||||||
|
"exception": str(e)
|
||||||
|
})
|
||||||
|
|
||||||
# Refresh serializer only after running signals
|
# Refresh serializer only after running signals
|
||||||
prefetch_related_objects([order], self._positions_prefetch(request))
|
prefetch_related_objects([order], self._positions_prefetch(request))
|
||||||
|
|||||||
@@ -391,7 +391,7 @@ class OutboundSyncProvider:
|
|||||||
def sync_order(self, order):
|
def sync_order(self, order):
|
||||||
if not self.should_sync_order(order):
|
if not self.should_sync_order(order):
|
||||||
logger.debug("Skipping order %r", order)
|
logger.debug("Skipping order %r", order)
|
||||||
return
|
return {}
|
||||||
|
|
||||||
logger.debug("Syncing order %r", order)
|
logger.debug("Syncing order %r", order)
|
||||||
positions = list(
|
positions = list(
|
||||||
|
|||||||
@@ -125,6 +125,7 @@ class InvoiceExporter(InvoiceExporterMixin, BaseExporter):
|
|||||||
identifier = 'invoices'
|
identifier = 'invoices'
|
||||||
verbose_name = _('All invoices')
|
verbose_name = _('All invoices')
|
||||||
description = _('Download all invoices created by the system as a ZIP file of PDF files.')
|
description = _('Download all invoices created by the system as a ZIP file of PDF files.')
|
||||||
|
repeatable_read = False
|
||||||
|
|
||||||
def render(self, form_data: dict, output_file=None):
|
def render(self, form_data: dict, output_file=None):
|
||||||
qs = self.invoices_queryset(form_data).filter(shredded=False)
|
qs = self.invoices_queryset(form_data).filter(shredded=False)
|
||||||
|
|||||||
@@ -1162,7 +1162,7 @@ class QuotaListExporter(ListExporter):
|
|||||||
yield headers
|
yield headers
|
||||||
|
|
||||||
quotas = list(self.event.quotas.select_related('subevent'))
|
quotas = list(self.event.quotas.select_related('subevent'))
|
||||||
qa = QuotaAvailability(full_results=True, allow_repeatable_read=False)
|
qa = QuotaAvailability(full_results=True, allow_repeatable_read=True)
|
||||||
qa.queue(*quotas)
|
qa.queue(*quotas)
|
||||||
qa.compute()
|
qa.compute()
|
||||||
|
|
||||||
|
|||||||
@@ -1969,14 +1969,20 @@ class OrderPayment(models.Model):
|
|||||||
self.order.invoice_dirty
|
self.order.invoice_dirty
|
||||||
)
|
)
|
||||||
if gen_invoice:
|
if gen_invoice:
|
||||||
if invoices:
|
try:
|
||||||
last_i = self.order.invoices.filter(is_cancellation=False).last()
|
if invoices:
|
||||||
if not last_i.canceled:
|
last_i = self.order.invoices.filter(is_cancellation=False).last()
|
||||||
generate_cancellation(last_i)
|
if not last_i.canceled:
|
||||||
invoice = generate_invoice(
|
generate_cancellation(last_i)
|
||||||
self.order,
|
invoice = generate_invoice(
|
||||||
trigger_pdf=not send_mail or not self.order.event.settings.invoice_email_attachment
|
self.order,
|
||||||
)
|
trigger_pdf=not send_mail or not self.order.event.settings.invoice_email_attachment
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception("Could not generate invoice.")
|
||||||
|
self.order.log_action("pretix.event.order.invoice.failed", data={
|
||||||
|
"exception": str(e)
|
||||||
|
})
|
||||||
|
|
||||||
transmit_invoice_task = invoice_transmission_separately(invoice)
|
transmit_invoice_task = invoice_transmission_separately(invoice)
|
||||||
transmit_invoice_mail = not transmit_invoice_task and self.order.event.settings.invoice_email_attachment and self.order.email
|
transmit_invoice_mail = not transmit_invoice_task and self.order.event.settings.invoice_email_attachment and self.order.email
|
||||||
|
|||||||
@@ -671,6 +671,7 @@ def send_invoices_to_organizer(sender, **kwargs):
|
|||||||
event=i.event,
|
event=i.event,
|
||||||
invoices=[i],
|
invoices=[i],
|
||||||
auto_email=True,
|
auto_email=True,
|
||||||
|
plain_text_only=True,
|
||||||
)
|
)
|
||||||
i.sent_to_organizer = True
|
i.sent_to_organizer = True
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
# 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/>.
|
||||||
#
|
#
|
||||||
|
import logging
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
@@ -43,6 +44,8 @@ from pretix.base.services.tasks import ProfiledEventTask
|
|||||||
from pretix.base.signals import order_paid, order_placed
|
from pretix.base.signals import order_paid, order_placed
|
||||||
from pretix.celery_app import app
|
from pretix.celery_app import app
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def _validate(cf: CachedFile, charset: str, cols: List[ImportColumn], settings: dict):
|
def _validate(cf: CachedFile, charset: str, cols: List[ImportColumn], settings: dict):
|
||||||
try:
|
try:
|
||||||
@@ -233,7 +236,13 @@ def import_orders(event: Event, fileid: str, settings: dict, locale: str, user,
|
|||||||
(event.settings.get('invoice_generate') == 'paid' and o.status == Order.STATUS_PAID)
|
(event.settings.get('invoice_generate') == 'paid' and o.status == Order.STATUS_PAID)
|
||||||
) and not o.invoices.last()
|
) and not o.invoices.last()
|
||||||
if gen_invoice:
|
if gen_invoice:
|
||||||
generate_invoice(o, trigger_pdf=True)
|
try:
|
||||||
|
generate_invoice(o, trigger_pdf=True)
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception("Could not generate invoice.")
|
||||||
|
o.log_action("pretix.event.order.invoice.failed", data={
|
||||||
|
"exception": str(e)
|
||||||
|
})
|
||||||
except DataImportError:
|
except DataImportError:
|
||||||
raise ValidationError(_('We were not able to process your request completely as the server was too busy. '
|
raise ValidationError(_('We were not able to process your request completely as the server was too busy. '
|
||||||
'Please try again.'))
|
'Please try again.'))
|
||||||
|
|||||||
@@ -264,7 +264,13 @@ def reactivate_order(order: Order, force: bool=False, user: User=None, auth=None
|
|||||||
|
|
||||||
num_invoices = order.invoices.filter(is_cancellation=False).count()
|
num_invoices = order.invoices.filter(is_cancellation=False).count()
|
||||||
if num_invoices > 0 and order.invoices.filter(is_cancellation=True).count() >= num_invoices and invoice_qualified(order):
|
if num_invoices > 0 and order.invoices.filter(is_cancellation=True).count() >= num_invoices and invoice_qualified(order):
|
||||||
generate_invoice(order)
|
try:
|
||||||
|
generate_invoice(order)
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception("Could not generate invoice.")
|
||||||
|
order.log_action("pretix.event.order.invoice.failed", data={
|
||||||
|
"exception": str(e)
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
def extend_order(order: Order, new_date: datetime, force: bool=False, valid_if_pending: bool=None, user: User=None, auth=None):
|
def extend_order(order: Order, new_date: datetime, force: bool=False, valid_if_pending: bool=None, user: User=None, auth=None):
|
||||||
@@ -312,7 +318,13 @@ def extend_order(order: Order, new_date: datetime, force: bool=False, valid_if_p
|
|||||||
if was_expired:
|
if was_expired:
|
||||||
num_invoices = order.invoices.filter(is_cancellation=False).count()
|
num_invoices = order.invoices.filter(is_cancellation=False).count()
|
||||||
if num_invoices > 0 and order.invoices.filter(is_cancellation=True).count() >= num_invoices and invoice_qualified(order):
|
if num_invoices > 0 and order.invoices.filter(is_cancellation=True).count() >= num_invoices and invoice_qualified(order):
|
||||||
generate_invoice(order)
|
try:
|
||||||
|
generate_invoice(order)
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception("Could not generate invoice.")
|
||||||
|
order.log_action("pretix.event.order.invoice.failed", data={
|
||||||
|
"exception": str(e)
|
||||||
|
})
|
||||||
order.create_transactions()
|
order.create_transactions()
|
||||||
|
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
@@ -397,13 +409,19 @@ def approve_order(order, user=None, send_mail: bool=True, auth=None, force=False
|
|||||||
|
|
||||||
if order.event.settings.get('invoice_generate') == 'True' and invoice_qualified(order):
|
if order.event.settings.get('invoice_generate') == 'True' and invoice_qualified(order):
|
||||||
if not invoice:
|
if not invoice:
|
||||||
invoice = generate_invoice(
|
try:
|
||||||
order,
|
invoice = generate_invoice(
|
||||||
# send_mail will trigger PDF generation later
|
order,
|
||||||
trigger_pdf=not transmit_invoice_mail
|
# send_mail will trigger PDF generation later
|
||||||
)
|
trigger_pdf=not transmit_invoice_mail
|
||||||
if transmit_invoice_task:
|
)
|
||||||
transmit_invoice.apply_async(args=(order.event_id, invoice.pk, False))
|
if transmit_invoice_task:
|
||||||
|
transmit_invoice.apply_async(args=(order.event_id, invoice.pk, False))
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception("Could not generate invoice.")
|
||||||
|
order.log_action("pretix.event.order.invoice.failed", data={
|
||||||
|
"exception": str(e)
|
||||||
|
})
|
||||||
|
|
||||||
if send_mail:
|
if send_mail:
|
||||||
with language(order.locale, order.event.settings.region):
|
with language(order.locale, order.event.settings.region):
|
||||||
@@ -608,7 +626,13 @@ def _cancel_order(order, user=None, send_mail: bool=True, api_token=None, device
|
|||||||
order.save(update_fields=['status', 'cancellation_date', 'total'])
|
order.save(update_fields=['status', 'cancellation_date', 'total'])
|
||||||
|
|
||||||
if cancel_invoice and i:
|
if cancel_invoice and i:
|
||||||
invoices.append(generate_invoice(order))
|
try:
|
||||||
|
invoices.append(generate_invoice(order))
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception("Could not generate invoice.")
|
||||||
|
order.log_action("pretix.event.order.invoice.failed", data={
|
||||||
|
"exception": str(e)
|
||||||
|
})
|
||||||
else:
|
else:
|
||||||
order.status = Order.STATUS_CANCELED
|
order.status = Order.STATUS_CANCELED
|
||||||
order.cancellation_date = now()
|
order.cancellation_date = now()
|
||||||
@@ -1306,13 +1330,19 @@ def _perform_order(event: Event, payment_requests: List[dict], position_ids: Lis
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
if invoice_required:
|
if invoice_required:
|
||||||
invoice = generate_invoice(
|
try:
|
||||||
order,
|
invoice = generate_invoice(
|
||||||
# send_mail will trigger PDF generation later
|
order,
|
||||||
trigger_pdf=not transmit_invoice_mail
|
# send_mail will trigger PDF generation later
|
||||||
)
|
trigger_pdf=not transmit_invoice_mail
|
||||||
if transmit_invoice_task:
|
)
|
||||||
transmit_invoice.apply_async(args=(event.pk, invoice.pk, False))
|
if transmit_invoice_task:
|
||||||
|
transmit_invoice.apply_async(args=(event.pk, invoice.pk, False))
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception("Could not generate invoice.")
|
||||||
|
order.log_action("pretix.event.order.invoice.failed", data={
|
||||||
|
"exception": str(e)
|
||||||
|
})
|
||||||
|
|
||||||
if order.email:
|
if order.email:
|
||||||
if order.require_approval:
|
if order.require_approval:
|
||||||
@@ -2701,7 +2731,13 @@ class OrderChangeManager:
|
|||||||
)
|
)
|
||||||
|
|
||||||
if split_order.total != Decimal('0.00') and self.order.invoices.filter(is_cancellation=False).last():
|
if split_order.total != Decimal('0.00') and self.order.invoices.filter(is_cancellation=False).last():
|
||||||
generate_invoice(split_order)
|
try:
|
||||||
|
generate_invoice(split_order)
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception("Could not generate invoice.")
|
||||||
|
split_order.log_action("pretix.event.order.invoice.failed", data={
|
||||||
|
"exception": str(e)
|
||||||
|
})
|
||||||
|
|
||||||
order_split.send(sender=self.order.event, original=self.order, split_order=split_order)
|
order_split.send(sender=self.order.event, original=self.order, split_order=split_order)
|
||||||
return split_order
|
return split_order
|
||||||
@@ -2812,15 +2848,27 @@ class OrderChangeManager:
|
|||||||
|
|
||||||
if order_now_qualified:
|
if order_now_qualified:
|
||||||
if invoice_should_be_generated_now:
|
if invoice_should_be_generated_now:
|
||||||
if i and not i.canceled:
|
try:
|
||||||
self._invoices.append(generate_cancellation(i))
|
if i and not i.canceled:
|
||||||
self._invoices.append(generate_invoice(self.order))
|
self._invoices.append(generate_cancellation(i))
|
||||||
|
self._invoices.append(generate_invoice(self.order))
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception("Could not generate invoice.")
|
||||||
|
self.order.log_action("pretix.event.order.invoice.failed", data={
|
||||||
|
"exception": str(e)
|
||||||
|
})
|
||||||
elif invoice_should_be_generated_later:
|
elif invoice_should_be_generated_later:
|
||||||
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:
|
||||||
if i and not i.canceled:
|
try:
|
||||||
self._invoices.append(generate_cancellation(i))
|
if i and not i.canceled:
|
||||||
|
self._invoices.append(generate_cancellation(i))
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception("Could not generate invoice.")
|
||||||
|
self.order.log_action("pretix.event.order.invoice.failed", data={
|
||||||
|
"exception": str(e)
|
||||||
|
})
|
||||||
|
|
||||||
def _check_complete_cancel(self):
|
def _check_complete_cancel(self):
|
||||||
current = self.order.positions.count()
|
current = self.order.positions.count()
|
||||||
@@ -3246,8 +3294,14 @@ def change_payment_provider(order: Order, payment_provider, amount=None, new_pay
|
|||||||
has_active_invoice = i and not i.canceled
|
has_active_invoice = i and not i.canceled
|
||||||
|
|
||||||
if has_active_invoice and order.total != oldtotal:
|
if has_active_invoice and order.total != oldtotal:
|
||||||
generate_cancellation(i)
|
try:
|
||||||
generate_invoice(order)
|
generate_cancellation(i)
|
||||||
|
generate_invoice(order)
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception("Could not generate invoice.")
|
||||||
|
order.log_action("pretix.event.order.invoice.failed", data={
|
||||||
|
"exception": str(e)
|
||||||
|
})
|
||||||
new_invoice_created = True
|
new_invoice_created = True
|
||||||
|
|
||||||
elif (not has_active_invoice or order.invoice_dirty) and invoice_qualified(order):
|
elif (not has_active_invoice or order.invoice_dirty) and invoice_qualified(order):
|
||||||
@@ -3255,13 +3309,19 @@ def change_payment_provider(order: Order, payment_provider, amount=None, new_pay
|
|||||||
order.event.settings.get('invoice_generate') == 'paid' and
|
order.event.settings.get('invoice_generate') == 'paid' and
|
||||||
new_payment.payment_provider.requires_invoice_immediately
|
new_payment.payment_provider.requires_invoice_immediately
|
||||||
):
|
):
|
||||||
if has_active_invoice:
|
try:
|
||||||
generate_cancellation(i)
|
if has_active_invoice:
|
||||||
i = generate_invoice(order)
|
generate_cancellation(i)
|
||||||
new_invoice_created = True
|
i = generate_invoice(order)
|
||||||
order.log_action('pretix.event.order.invoice.generated', data={
|
new_invoice_created = True
|
||||||
'invoice': i.pk
|
order.log_action('pretix.event.order.invoice.generated', data={
|
||||||
})
|
'invoice': i.pk
|
||||||
|
})
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception("Could not generate invoice.")
|
||||||
|
order.log_action("pretix.event.order.invoice.failed", data={
|
||||||
|
"exception": str(e)
|
||||||
|
})
|
||||||
|
|
||||||
order.create_transactions()
|
order.create_transactions()
|
||||||
return old_fee, new_fee, fee, new_payment, new_invoice_created
|
return old_fee, new_fee, fee, new_payment, new_invoice_created
|
||||||
|
|||||||
@@ -390,8 +390,7 @@ class QuotaFormSet(I18nInlineFormSet):
|
|||||||
use_required_attribute=False,
|
use_required_attribute=False,
|
||||||
locales=self.locales,
|
locales=self.locales,
|
||||||
event=self.event,
|
event=self.event,
|
||||||
items=self.items,
|
items=self.items
|
||||||
searchable_selection=self.searchable_selection,
|
|
||||||
)
|
)
|
||||||
self.add_fields(form, None)
|
self.add_fields(form, None)
|
||||||
return form
|
return form
|
||||||
|
|||||||
@@ -455,7 +455,7 @@ class OrderDataSyncSuccessLogEntryType(OrderDataSyncLogEntryType):
|
|||||||
links.append(", ".join(
|
links.append(", ".join(
|
||||||
prov.get_external_link_html(logentry.event, obj['external_link_href'], obj['external_link_display_name'])
|
prov.get_external_link_html(logentry.event, obj['external_link_href'], obj['external_link_display_name'])
|
||||||
for obj in objs
|
for obj in objs
|
||||||
if obj and 'external_link_href' in obj and 'external_link_display_name' in obj
|
if obj and obj.get('external_link_href') and obj.get('external_link_display_name')
|
||||||
))
|
))
|
||||||
|
|
||||||
return mark_safe(escape(super().display(logentry, data)) + "".join("<p>" + link + "</p>" for link in links))
|
return mark_safe(escape(super().display(logentry, data)) + "".join("<p>" + link + "</p>" for link in links))
|
||||||
@@ -522,6 +522,7 @@ def pretixcontrol_orderposition_blocked_display(sender: Event, orderposition, bl
|
|||||||
'pretix.event.order.customer.changed': _('The customer account has been changed.'),
|
'pretix.event.order.customer.changed': _('The customer account has been changed.'),
|
||||||
'pretix.event.order.locale.changed': _('The order locale has been changed.'),
|
'pretix.event.order.locale.changed': _('The order locale has been changed.'),
|
||||||
'pretix.event.order.invoice.generated': _('The invoice has been generated.'),
|
'pretix.event.order.invoice.generated': _('The invoice has been generated.'),
|
||||||
|
'pretix.event.order.invoice.failed': _('The invoice could not be generated.'),
|
||||||
'pretix.event.order.invoice.regenerated': _('The invoice has been regenerated.'),
|
'pretix.event.order.invoice.regenerated': _('The invoice has been regenerated.'),
|
||||||
'pretix.event.order.invoice.reissued': _('The invoice has been reissued.'),
|
'pretix.event.order.invoice.reissued': _('The invoice has been reissued.'),
|
||||||
'pretix.event.order.invoice.sent': _('The invoice {full_invoice_no} has been sent.'),
|
'pretix.event.order.invoice.sent': _('The invoice {full_invoice_no} has been sent.'),
|
||||||
|
|||||||
@@ -487,32 +487,30 @@
|
|||||||
{% trans "These settings are optional, if you leave them empty, the default values from the product settings will be used." %}
|
{% trans "These settings are optional, if you leave them empty, the default values from the product settings will be used." %}
|
||||||
</p>
|
</p>
|
||||||
{% for f in itemvar_forms %}
|
{% for f in itemvar_forms %}
|
||||||
<div data-itemvar="{{ f.item.id }}{% if f.variation %}-{{ f.variation.id }}{% endif %}">
|
{% bootstrap_form_errors f %}
|
||||||
{% bootstrap_form_errors f %}
|
<div class="form-group subevent-itemvar-group">
|
||||||
<div class="form-group subevent-itemvar-group">
|
<label class="col-md-3 control-label" for="id_{{ f.prefix }}-price">
|
||||||
<label class="col-md-3 control-label" for="id_{{ f.prefix }}-price">
|
{% if f.variation %}{{ f.item }} – {{ f.variation }}{% else %}{{ f.item }}{% endif %}
|
||||||
{% if f.variation %}{{ f.item }} – {{ f.variation }}{% else %}{{ f.item }}{% endif %}
|
</label>
|
||||||
</label>
|
<div class="col-md-4">
|
||||||
<div class="col-md-4">
|
<label for="{{ f.price.id_for_label }}" class="text-muted">{% trans "Price" %}</label><br>
|
||||||
<label for="{{ f.price.id_for_label }}" class="text-muted">{% trans "Price" %}</label><br>
|
{% bootstrap_field f.price addon_after=request.event.currency form_group_class="" layout="inline" %}
|
||||||
{% bootstrap_field f.price addon_after=request.event.currency form_group_class="" layout="inline" %}
|
|
||||||
</div>
|
|
||||||
<div class="col-md-4">
|
|
||||||
<br>
|
|
||||||
{% bootstrap_field f.disabled layout="inline" form_group_class="" %}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group subevent-itemvar-group">
|
<div class="col-md-4">
|
||||||
<div class="col-md-4 col-md-offset-3">
|
<br>
|
||||||
<label for="{{ f.rel_available_from.id_for_label }}" class="text-muted">{% trans "Available from" %}</label>
|
{% bootstrap_field f.disabled layout="inline" form_group_class="" %}
|
||||||
{% include "pretixcontrol/subevents/fragment_unavail_mode_indicator.html" with mode=f.available_from_mode %}<br>
|
</div>
|
||||||
{% bootstrap_field f.rel_available_from form_group_class="" layout="inline" %}
|
</div>
|
||||||
</div>
|
<div class="form-group subevent-itemvar-group">
|
||||||
<div class="col-md-4">
|
<div class="col-md-4 col-md-offset-3">
|
||||||
<label for="{{ f.rel_available_until.id_for_label }}" class="text-muted">{% trans "Available until" %}</label>
|
<label for="{{ f.rel_available_from.id_for_label }}" class="text-muted">{% trans "Available from" %}</label>
|
||||||
{% include "pretixcontrol/subevents/fragment_unavail_mode_indicator.html" with mode=f.available_until_mode %}<br>
|
{% include "pretixcontrol/subevents/fragment_unavail_mode_indicator.html" with mode=f.available_from_mode %}<br>
|
||||||
{% bootstrap_field f.rel_available_until form_group_class="" layout="inline" %}
|
{% bootstrap_field f.rel_available_from form_group_class="" layout="inline" %}
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label for="{{ f.rel_available_until.id_for_label }}" class="text-muted">{% trans "Available until" %}</label>
|
||||||
|
{% include "pretixcontrol/subevents/fragment_unavail_mode_indicator.html" with mode=f.available_until_mode %}<br>
|
||||||
|
{% bootstrap_field f.rel_available_until form_group_class="" layout="inline" %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@@ -627,7 +625,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<div class="form-group submit-group submit-group-sticky">
|
<div class="form-group submit-group">
|
||||||
<button type="submit" class="btn btn-primary btn-save">
|
<button type="submit" class="btn btn-primary btn-save">
|
||||||
{% trans "Save" %}
|
{% trans "Save" %}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -363,7 +363,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<div class="form-group submit-group submit-group-sticky">
|
<div class="form-group submit-group">
|
||||||
<button type="submit" class="btn btn-primary btn-save">
|
<button type="submit" class="btn btn-primary btn-save">
|
||||||
{% trans "Save" %}
|
{% trans "Save" %}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -130,32 +130,30 @@
|
|||||||
{% trans "These settings are optional, if you leave them empty, the default values from the product settings will be used." %}
|
{% trans "These settings are optional, if you leave them empty, the default values from the product settings will be used." %}
|
||||||
</p>
|
</p>
|
||||||
{% for f in itemvar_forms %}
|
{% for f in itemvar_forms %}
|
||||||
<div data-itemvar="{{ f.item.id }}{% if f.variation %}-{{ f.variation.id }}{% endif %}">
|
{% bootstrap_form_errors f %}
|
||||||
{% bootstrap_form_errors f %}
|
<div class="form-group subevent-itemvar-group">
|
||||||
<div class="form-group subevent-itemvar-group">
|
<label class="col-md-3 control-label" for="id_{{ f.prefix }}-price">
|
||||||
<label class="col-md-3 control-label" for="id_{{ f.prefix }}-price">
|
{% if f.variation %}{{ f.item }} – {{ f.variation }}{% else %}{{ f.item }}{% endif %}
|
||||||
{% if f.variation %}{{ f.item }} – {{ f.variation }}{% else %}{{ f.item }}{% endif %}
|
</label>
|
||||||
</label>
|
<div class="col-md-4">
|
||||||
<div class="col-md-4">
|
<label for="{{ f.price.id_for_label }}" class="text-muted">{% trans "Price" %}</label><br>
|
||||||
<label for="{{ f.price.id_for_label }}" class="text-muted">{% trans "Price" %}</label><br>
|
{% bootstrap_field f.price addon_after=request.event.currency form_group_class="" layout="inline" %}
|
||||||
{% bootstrap_field f.price addon_after=request.event.currency form_group_class="" layout="inline" %}
|
|
||||||
</div>
|
|
||||||
<div class="col-md-4">
|
|
||||||
<br>
|
|
||||||
{% bootstrap_field f.disabled layout="inline" form_group_class="" %}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group subevent-itemvar-group">
|
<div class="col-md-4">
|
||||||
<div class="col-md-4 col-md-offset-3">
|
<br>
|
||||||
<label for="{{ f.available_from.id_for_label }}" class="text-muted">{% trans "Available from" %}</label>
|
{% bootstrap_field f.disabled layout="inline" form_group_class="" %}
|
||||||
{% include "pretixcontrol/subevents/fragment_unavail_mode_indicator.html" with mode=f.available_from_mode %}<br>
|
</div>
|
||||||
{% bootstrap_field f.available_from form_group_class="foo" layout="inline" %}
|
</div>
|
||||||
</div>
|
<div class="form-group subevent-itemvar-group">
|
||||||
<div class="col-md-4">
|
<div class="col-md-4 col-md-offset-3">
|
||||||
<label for="{{ f.available_until.id_for_label }}" class="text-muted">{% trans "Available until" %}</label>
|
<label for="{{ f.available_from.id_for_label }}" class="text-muted">{% trans "Available from" %}</label>
|
||||||
{% include "pretixcontrol/subevents/fragment_unavail_mode_indicator.html" with mode=f.available_until_mode %}<br>
|
{% include "pretixcontrol/subevents/fragment_unavail_mode_indicator.html" with mode=f.available_from_mode %}<br>
|
||||||
{% bootstrap_field f.available_until form_group_class="" layout="inline" %}
|
{% bootstrap_field f.available_from form_group_class="foo" layout="inline" %}
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label for="{{ f.available_until.id_for_label }}" class="text-muted">{% trans "Available until" %}</label>
|
||||||
|
{% include "pretixcontrol/subevents/fragment_unavail_mode_indicator.html" with mode=f.available_until_mode %}<br>
|
||||||
|
{% bootstrap_field f.available_until form_group_class="" layout="inline" %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@@ -284,7 +282,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group submit-group submit-group-sticky">
|
<div class="form-group submit-group">
|
||||||
<button type="submit" class="btn btn-primary btn-save">
|
<button type="submit" class="btn btn-primary btn-save">
|
||||||
{% trans "Save" %}
|
{% trans "Save" %}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -78,8 +78,8 @@ class ControlSyncJob(OrderView):
|
|||||||
prov, meta = datasync_providers.get(active_in=self.request.event, identifier=provider)
|
prov, meta = datasync_providers.get(active_in=self.request.event, identifier=provider)
|
||||||
|
|
||||||
if self.request.POST.get("queue_sync") == "true":
|
if self.request.POST.get("queue_sync") == "true":
|
||||||
prov.enqueue_order(self.order, 'user')
|
prov.enqueue_order(self.order, 'user', immediate=True)
|
||||||
messages.success(self.request, _('The sync job has been enqueued and will run in the next minutes.'))
|
messages.success(self.request, _('The sync job has been set to run as soon as possible.'))
|
||||||
elif self.request.POST.get("cancel_job"):
|
elif self.request.POST.get("cancel_job"):
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import copy
|
|||||||
import hmac
|
import hmac
|
||||||
import inspect
|
import inspect
|
||||||
import json
|
import json
|
||||||
|
import logging
|
||||||
import mimetypes
|
import mimetypes
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
@@ -98,6 +99,8 @@ from pretix.presale.views import (
|
|||||||
from pretix.presale.views.event import get_grouped_items
|
from pretix.presale.views.event import get_grouped_items
|
||||||
from pretix.presale.views.robots import NoSearchIndexViewMixin
|
from pretix.presale.views.robots import NoSearchIndexViewMixin
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class OrderDetailMixin(NoSearchIndexViewMixin):
|
class OrderDetailMixin(NoSearchIndexViewMixin):
|
||||||
|
|
||||||
@@ -734,11 +737,18 @@ class OrderInvoiceCreate(EventViewMixin, OrderDetailMixin, View):
|
|||||||
elif self.order.invoices.exists():
|
elif self.order.invoices.exists():
|
||||||
messages.error(self.request, _('An invoice for this order already exists.'))
|
messages.error(self.request, _('An invoice for this order already exists.'))
|
||||||
else:
|
else:
|
||||||
i = generate_invoice(self.order)
|
try:
|
||||||
self.order.log_action('pretix.event.order.invoice.generated', data={
|
i = generate_invoice(self.order)
|
||||||
'invoice': i.pk
|
self.order.log_action('pretix.event.order.invoice.generated', data={
|
||||||
})
|
'invoice': i.pk
|
||||||
messages.success(self.request, _('The invoice has been generated.'))
|
})
|
||||||
|
messages.success(self.request, _('The invoice has been generated.'))
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception("Could not generate invoice.")
|
||||||
|
self.order.log_action("pretix.event.order.invoice.failed", data={
|
||||||
|
"exception": str(e)
|
||||||
|
})
|
||||||
|
messages.error(self.request, _('Invoice generation has failed, please reach out to the organizer.'))
|
||||||
return redirect(self.get_order_url())
|
return redirect(self.get_order_url())
|
||||||
|
|
||||||
|
|
||||||
@@ -807,24 +817,37 @@ class OrderModify(EventViewMixin, OrderDetailMixin, OrderQuestionsViewMixin, Tem
|
|||||||
elif self.order.invoices.exists():
|
elif self.order.invoices.exists():
|
||||||
messages.error(self.request, _('An invoice for this order already exists.'))
|
messages.error(self.request, _('An invoice for this order already exists.'))
|
||||||
else:
|
else:
|
||||||
i = generate_invoice(self.order)
|
try:
|
||||||
self.order.log_action('pretix.event.order.invoice.generated', data={
|
i = generate_invoice(self.order)
|
||||||
'invoice': i.pk
|
self.order.log_action('pretix.event.order.invoice.generated', data={
|
||||||
})
|
'invoice': i.pk
|
||||||
messages.success(self.request, _('The invoice has been generated.'))
|
})
|
||||||
|
messages.success(self.request, _('The invoice has been generated.'))
|
||||||
|
except Exception as e:
|
||||||
|
logger.exception("Could not generate invoice.")
|
||||||
|
self.order.log_action("pretix.event.order.invoice.failed", data={
|
||||||
|
"exception": str(e)
|
||||||
|
})
|
||||||
|
messages.error(self.request, _('Invoice generation has failed, please reach out to the organizer.'))
|
||||||
elif self.request.event.settings.invoice_reissue_after_modify:
|
elif self.request.event.settings.invoice_reissue_after_modify:
|
||||||
if self.invoice_form.changed_data:
|
if self.invoice_form.changed_data:
|
||||||
inv = self.order.invoices.last()
|
try:
|
||||||
if inv and not inv.canceled and not inv.shredded:
|
inv = self.order.invoices.last()
|
||||||
c = generate_cancellation(inv)
|
if inv and not inv.canceled and not inv.shredded:
|
||||||
if self.order.status != Order.STATUS_CANCELED:
|
c = generate_cancellation(inv)
|
||||||
inv = generate_invoice(self.order)
|
if self.order.status != Order.STATUS_CANCELED:
|
||||||
else:
|
inv = generate_invoice(self.order)
|
||||||
inv = c
|
else:
|
||||||
self.order.log_action('pretix.event.order.invoice.reissued', data={
|
inv = c
|
||||||
'invoice': inv.pk
|
self.order.log_action('pretix.event.order.invoice.reissued', data={
|
||||||
|
'invoice': inv.pk
|
||||||
|
})
|
||||||
|
messages.success(self.request, _('The invoice has been reissued.'))
|
||||||
|
except Exception as e:
|
||||||
|
self.order.log_action("pretix.event.order.invoice.failed", data={
|
||||||
|
"exception": str(e)
|
||||||
})
|
})
|
||||||
messages.success(self.request, _('The invoice has been reissued.'))
|
logger.exception("Could not generate invoice.")
|
||||||
|
|
||||||
invalidate_cache.apply_async(kwargs={'event': self.request.event.pk, 'order': self.order.pk})
|
invalidate_cache.apply_async(kwargs={'event': self.request.event.pk, 'order': self.order.pk})
|
||||||
CachedTicket.objects.filter(order_position__order=self.order).delete()
|
CachedTicket.objects.filter(order_position__order=self.order).delete()
|
||||||
|
|||||||
@@ -330,9 +330,11 @@ var ajaxErrDialog = {
|
|||||||
$("#ajaxerr .links").html("<a class='btn btn-default ajaxerr-close'>"
|
$("#ajaxerr .links").html("<a class='btn btn-default ajaxerr-close'>"
|
||||||
+ gettext("Close message") + "</a>");
|
+ gettext("Close message") + "</a>");
|
||||||
$("body").addClass("ajaxerr has-modal-dialog");
|
$("body").addClass("ajaxerr has-modal-dialog");
|
||||||
|
$("#ajaxerr").prop("hidden", false);
|
||||||
},
|
},
|
||||||
hide: function () {
|
hide: function () {
|
||||||
"use strict";
|
"use strict";
|
||||||
$("body").removeClass("ajaxerr has-modal-dialog");
|
$("body").removeClass("ajaxerr has-modal-dialog");
|
||||||
|
$("#ajaxerr").prop("hidden", true);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ var i18nToString = function (i18nstring) {
|
|||||||
$(document).ajaxError(function (event, jqXHR, settings, thrownError) {
|
$(document).ajaxError(function (event, jqXHR, settings, thrownError) {
|
||||||
waitingDialog.hide();
|
waitingDialog.hide();
|
||||||
var c = $(jqXHR.responseText).filter('.container');
|
var c = $(jqXHR.responseText).filter('.container');
|
||||||
if (jqXHR.responseText.indexOf("<!-- pretix-login-marker -->") !== -1) {
|
if (jqXHR.responseText && jqXHR.responseText.indexOf("<!-- pretix-login-marker -->") !== -1) {
|
||||||
location.href = '/control/login?next=' + encodeURIComponent(location.pathname + location.search + location.hash)
|
location.href = '/control/login?next=' + encodeURIComponent(location.pathname + location.search + location.hash)
|
||||||
} else if (c.length > 0) {
|
} else if (c.length > 0) {
|
||||||
ajaxErrDialog.show(c.first().html());
|
ajaxErrDialog.show(c.first().html());
|
||||||
@@ -485,6 +485,7 @@ var form_handlers = function (el) {
|
|||||||
theme: "bootstrap",
|
theme: "bootstrap",
|
||||||
language: $("body").attr("data-select2-locale"),
|
language: $("body").attr("data-select2-locale"),
|
||||||
data: JSON.parse($(this.getAttribute('data-select2-src')).text()),
|
data: JSON.parse($(this.getAttribute('data-select2-src')).text()),
|
||||||
|
width: '100%',
|
||||||
}).val(selectedValue).trigger('change');
|
}).val(selectedValue).trigger('change');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ $(document).on("pretix:bind-forms", function () {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// RRule editor
|
|
||||||
function rrule_preview() {
|
function rrule_preview() {
|
||||||
var ruleset = new rrule.RRuleSet();
|
var ruleset = new rrule.RRuleSet();
|
||||||
|
|
||||||
@@ -122,14 +121,7 @@ $(document).on("pretix:bind-forms", function () {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$("#rrule-formset").on("change keydown keyup keypress dp.change", "input, select", function () {
|
|
||||||
rrule_preview();
|
|
||||||
});
|
|
||||||
rrule_preview();
|
|
||||||
|
|
||||||
$("#rrule-formset").on("formAdded", "div", function (event) {rrule_bind_form($(event.target)); });
|
|
||||||
|
|
||||||
// Timeslot editor
|
|
||||||
$("#subevent_add_many_slots_go").on("click", function () {
|
$("#subevent_add_many_slots_go").on("click", function () {
|
||||||
$("#time-formset [data-formset-form]").each(function () {
|
$("#time-formset [data-formset-form]").each(function () {
|
||||||
var tf = $(this).find("[name$=time_from]").val()
|
var tf = $(this).find("[name$=time_from]").val()
|
||||||
@@ -175,45 +167,13 @@ $(document).on("pretix:bind-forms", function () {
|
|||||||
$(this).addClass("hidden");
|
$(this).addClass("hidden");
|
||||||
});
|
});
|
||||||
|
|
||||||
// Hide config for products that are not for sale
|
$("#rrule-formset").on("change keydown keyup keypress dp.change", "input, select", function () {
|
||||||
function quota_form_handlers(el) {
|
rrule_preview();
|
||||||
// searchable_selection = True
|
});
|
||||||
el.find('[id^="id_quotas-"]').on("select2:select select2:unselect", () => {
|
rrule_preview();
|
||||||
update_item_visibility();
|
|
||||||
});
|
|
||||||
// searchable_selection = False
|
|
||||||
el.find('input[id^="id_quotas-"][id*=itemvars_]').on("change", () => {
|
|
||||||
update_item_visibility();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
function update_item_visibility() {
|
|
||||||
const itemvars = [];
|
|
||||||
|
|
||||||
// searchable_selection = True
|
$("#rrule-formset").on("formAdded", "div", function (event) { rrule_bind_form($(event.target)); });
|
||||||
$("select[id^=id_quotas-][id$=-itemvars]").filter((idx, el) => {
|
|
||||||
return !$(el).closest('[data-formset-form]').is('[data-formset-form-deleted]');
|
|
||||||
}).each((_, e) => itemvars.push(...$(e).val()));
|
|
||||||
// searchable_selection = False
|
|
||||||
$("input[id^=id_quotas-][id*=itemvars_]:checked").filter((idx, el) => {
|
|
||||||
return !$(el).closest('[data-formset-form]').is('[data-formset-form-deleted]');
|
|
||||||
}).each((_, e) => itemvars.push($(e).val()));
|
|
||||||
|
|
||||||
$("div[data-itemvar]").each(function (idx, e) {
|
|
||||||
const el = $(e);
|
|
||||||
el.prop("hidden", !itemvars.includes(el.attr("data-itemvar")) && !el.find(".has-error, .alert-danger").length);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
$('[data-formset-prefix="quotas"]').on("formDeleted", "div", () => {
|
|
||||||
update_item_visibility();
|
|
||||||
}).on("formAdded", "div", (event) => {
|
|
||||||
quota_form_handlers($(event.target));
|
|
||||||
update_item_visibility();
|
|
||||||
})
|
|
||||||
quota_form_handlers($("body"));
|
|
||||||
update_item_visibility();
|
|
||||||
|
|
||||||
// Auto-set name of check-in list
|
|
||||||
var $namef = $("input[id^=id_name]").first();
|
var $namef = $("input[id^=id_name]").first();
|
||||||
var lastValue = $namef.val();
|
var lastValue = $namef.val();
|
||||||
$namef.change(function () {
|
$namef.change(function () {
|
||||||
|
|||||||
@@ -63,10 +63,6 @@ td > .form-group > .checkbox {
|
|||||||
@include box-shadow(none);
|
@include box-shadow(none);
|
||||||
}
|
}
|
||||||
|
|
||||||
div[data-formset-body], div[data-formset-form], div[data-nested-formset-form], div[data-nested-formset-body], details[data-formset-form] {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-plugins .panel-title {
|
.form-plugins .panel-title {
|
||||||
line-height: 34px;
|
line-height: 34px;
|
||||||
}
|
}
|
||||||
@@ -95,12 +91,6 @@ div[data-formset-body], div[data-formset-form], div[data-nested-formset-form], d
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.submit-group-sticky {
|
|
||||||
position: sticky;
|
|
||||||
bottom: 0;
|
|
||||||
z-index: 100;
|
|
||||||
}
|
|
||||||
|
|
||||||
.panel .form-group:last-child {
|
.panel .form-group:last-child {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|||||||
2
src/pretix/static/rrule/rrule.js
vendored
2
src/pretix/static/rrule/rrule.js
vendored
@@ -1934,7 +1934,7 @@ function buildPoslist(bysetpos, timeset, start, end, ii, dayset) {
|
|||||||
function iter(iterResult, options) {
|
function iter(iterResult, options) {
|
||||||
var dtstart = options.dtstart, freq = options.freq, interval = options.interval, until = options.until, bysetpos = options.bysetpos;
|
var dtstart = options.dtstart, freq = options.freq, interval = options.interval, until = options.until, bysetpos = options.bysetpos;
|
||||||
var count = options.count;
|
var count = options.count;
|
||||||
if (count <= 0 || interval <= 0) {
|
if (count === 0 || count < 0 || interval === 0 || interval < 0) {
|
||||||
return emitResult(iterResult);
|
return emitResult(iterResult);
|
||||||
}
|
}
|
||||||
var counterDate = datetime_DateTime.fromDate(dtstart);
|
var counterDate = datetime_DateTime.fromDate(dtstart);
|
||||||
|
|||||||
@@ -327,6 +327,26 @@ def test_enqueue_order_twice(event):
|
|||||||
SimpleOrderSync.enqueue_order(order, 'testcase_2nd')
|
SimpleOrderSync.enqueue_order(order, 'testcase_2nd')
|
||||||
|
|
||||||
|
|
||||||
|
class DoNothingSync(SimpleOrderSync):
|
||||||
|
|
||||||
|
def should_sync_order(self, order):
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_should_not_sync(event):
|
||||||
|
_register_with_fake_plugin_name(datasync_providers, DoNothingSync, 'testplugin')
|
||||||
|
|
||||||
|
DoNothingSync.fake_api_client = FakeSyncAPI()
|
||||||
|
|
||||||
|
for order in event.orders.order_by("code").all():
|
||||||
|
DoNothingSync.enqueue_order(order, 'testcase')
|
||||||
|
|
||||||
|
sync_all()
|
||||||
|
|
||||||
|
assert DoNothingSync.fake_api_client.fake_database == {}
|
||||||
|
|
||||||
|
|
||||||
StaticMappingWithAssociations = namedtuple('StaticMappingWithAssociations', (
|
StaticMappingWithAssociations = namedtuple('StaticMappingWithAssociations', (
|
||||||
'id', 'pretix_model', 'external_object_type', 'pretix_id_field', 'external_id_field', 'property_mappings', 'association_mappings'
|
'id', 'pretix_model', 'external_object_type', 'pretix_id_field', 'external_id_field', 'property_mappings', 'association_mappings'
|
||||||
))
|
))
|
||||||
|
|||||||
Reference in New Issue
Block a user