forked from CGM_Public/pretix_original
1065 lines
42 KiB
Python
1065 lines
42 KiB
Python
import re
|
|
from collections import OrderedDict
|
|
from datetime import timedelta
|
|
from urllib.parse import urlsplit
|
|
|
|
from django.conf import settings
|
|
from django.contrib import messages
|
|
from django.contrib.contenttypes.models import ContentType
|
|
from django.core.files import File
|
|
from django.core.urlresolvers import reverse
|
|
from django.db import transaction
|
|
from django.db.models import ProtectedError
|
|
from django.http import (
|
|
Http404, HttpResponse, HttpResponseBadRequest, HttpResponseNotAllowed,
|
|
JsonResponse,
|
|
)
|
|
from django.shortcuts import get_object_or_404, redirect
|
|
from django.utils import translation
|
|
from django.utils.formats import date_format
|
|
from django.utils.functional import cached_property
|
|
from django.utils.timezone import now
|
|
from django.utils.translation import ugettext, ugettext_lazy as _
|
|
from django.views.generic import DeleteView, FormView, ListView
|
|
from django.views.generic.base import TemplateView, View
|
|
from django.views.generic.detail import SingleObjectMixin
|
|
from i18nfield.strings import LazyI18nString
|
|
from pytz import timezone
|
|
|
|
from pretix.base.models import (
|
|
CachedCombinedTicket, CachedTicket, Event, Item, ItemVariation, LogEntry,
|
|
Order, RequiredAction, TaxRule, Voucher,
|
|
)
|
|
from pretix.base.models.event import EventMetaValue
|
|
from pretix.base.services import tickets
|
|
from pretix.base.services.invoices import build_preview_invoice_pdf
|
|
from pretix.base.signals import event_live_issues, register_ticket_outputs
|
|
from pretix.control.forms.event import (
|
|
CommentForm, DisplaySettingsForm, EventDeleteForm, EventMetaValueForm,
|
|
EventSettingsForm, EventUpdateForm, InvoiceSettingsForm, MailSettingsForm,
|
|
PaymentSettingsForm, ProviderForm, TaxRuleForm, TicketSettingsForm,
|
|
WidgetCodeForm,
|
|
)
|
|
from pretix.control.permissions import EventPermissionRequiredMixin
|
|
from pretix.control.signals import nav_event_settings
|
|
from pretix.helpers.urls import build_absolute_uri
|
|
from pretix.multidomain.urlreverse import get_domain
|
|
from pretix.presale.style import regenerate_css
|
|
|
|
from . import CreateView, PaginationMixin, UpdateView
|
|
from ..logdisplay import OVERVIEW_BLACKLIST
|
|
|
|
|
|
class EventSettingsViewMixin:
|
|
def get_context_data(self, **kwargs):
|
|
ctx = super().get_context_data(**kwargs)
|
|
ctx['nav_event_settings'] = []
|
|
ctx['is_event_settings'] = True
|
|
|
|
for recv, retv in nav_event_settings.send(sender=self.request.event, request=self.request):
|
|
ctx['nav_event_settings'] += retv
|
|
ctx['nav_event_settings'].sort(key=lambda n: n['label'])
|
|
return ctx
|
|
|
|
|
|
class MetaDataEditorMixin:
|
|
meta_form = EventMetaValueForm
|
|
meta_model = EventMetaValue
|
|
|
|
@cached_property
|
|
def meta_forms(self):
|
|
if hasattr(self, 'object') and self.object:
|
|
val_instances = {
|
|
v.property_id: v for v in self.object.meta_values.all()
|
|
}
|
|
else:
|
|
val_instances = {}
|
|
|
|
formlist = []
|
|
|
|
for p in self.request.organizer.meta_properties.all():
|
|
formlist.append(self._make_meta_form(p, val_instances))
|
|
return formlist
|
|
|
|
def _make_meta_form(self, p, val_instances):
|
|
return self.meta_form(
|
|
prefix='prop-{}'.format(p.pk),
|
|
property=p,
|
|
instance=val_instances.get(p.pk, self.meta_model(property=p, event=self.object)),
|
|
data=(self.request.POST if self.request.method == "POST" else None)
|
|
)
|
|
|
|
def save_meta(self):
|
|
for f in self.meta_forms:
|
|
if f.cleaned_data.get('value'):
|
|
f.save()
|
|
elif f.instance and f.instance.pk:
|
|
f.delete()
|
|
|
|
|
|
class EventUpdate(EventSettingsViewMixin, EventPermissionRequiredMixin, MetaDataEditorMixin, UpdateView):
|
|
model = Event
|
|
form_class = EventUpdateForm
|
|
template_name = 'pretixcontrol/event/settings.html'
|
|
permission = 'can_change_event_settings'
|
|
|
|
@cached_property
|
|
def object(self) -> Event:
|
|
return self.request.event
|
|
|
|
def get_object(self, queryset=None) -> Event:
|
|
return self.object
|
|
|
|
@cached_property
|
|
def sform(self):
|
|
return EventSettingsForm(
|
|
obj=self.object,
|
|
prefix='settings',
|
|
data=self.request.POST if self.request.method == 'POST' else None
|
|
)
|
|
|
|
def get_context_data(self, *args, **kwargs) -> dict:
|
|
context = super().get_context_data(*args, **kwargs)
|
|
context['sform'] = self.sform
|
|
context['meta_forms'] = self.meta_forms
|
|
return context
|
|
|
|
@transaction.atomic
|
|
def form_valid(self, form):
|
|
self.sform.save()
|
|
self.save_meta()
|
|
|
|
if self.sform.has_changed():
|
|
self.request.event.log_action('pretix.event.settings', user=self.request.user, data={
|
|
k: self.request.event.settings.get(k) for k in self.sform.changed_data
|
|
})
|
|
if form.has_changed():
|
|
self.request.event.log_action('pretix.event.changed', user=self.request.user, data={
|
|
k: getattr(self.request.event, k) for k in form.changed_data
|
|
})
|
|
messages.success(self.request, _('Your changes have been saved.'))
|
|
return super().form_valid(form)
|
|
|
|
def get_success_url(self) -> str:
|
|
return reverse('control:event.settings', kwargs={
|
|
'organizer': self.object.organizer.slug,
|
|
'event': self.object.slug,
|
|
})
|
|
|
|
def post(self, request, *args, **kwargs):
|
|
form = self.get_form()
|
|
if form.is_valid() and self.sform.is_valid() and all([f.is_valid() for f in self.meta_forms]):
|
|
# reset timezone
|
|
zone = timezone(self.sform.cleaned_data['timezone'])
|
|
event = form.instance
|
|
event.date_from = self.reset_timezone(zone, event.date_from)
|
|
event.date_to = self.reset_timezone(zone, event.date_to)
|
|
event.presale_start = self.reset_timezone(zone, event.presale_start)
|
|
event.presale_end = self.reset_timezone(zone, event.presale_end)
|
|
return self.form_valid(form)
|
|
else:
|
|
messages.error(self.request, _('We could not save your changes. See below for details.'))
|
|
return self.form_invalid(form)
|
|
|
|
@staticmethod
|
|
def reset_timezone(tz, dt):
|
|
return tz.localize(dt.replace(tzinfo=None)) if dt is not None else None
|
|
|
|
|
|
class EventPlugins(EventSettingsViewMixin, EventPermissionRequiredMixin, TemplateView, SingleObjectMixin):
|
|
model = Event
|
|
context_object_name = 'event'
|
|
permission = 'can_change_event_settings'
|
|
template_name = 'pretixcontrol/event/plugins.html'
|
|
|
|
def get_object(self, queryset=None) -> Event:
|
|
return self.request.event
|
|
|
|
def get_context_data(self, *args, **kwargs) -> dict:
|
|
from pretix.base.plugins import get_all_plugins
|
|
|
|
context = super().get_context_data(*args, **kwargs)
|
|
context['plugins'] = [p for p in get_all_plugins() if not p.name.startswith('.')
|
|
and getattr(p, 'visible', True)]
|
|
context['plugins_active'] = self.object.get_plugins()
|
|
return context
|
|
|
|
def get(self, request, *args, **kwargs):
|
|
self.object = self.get_object()
|
|
context = self.get_context_data(object=self.object)
|
|
return self.render_to_response(context)
|
|
|
|
def post(self, request, *args, **kwargs):
|
|
from pretix.base.plugins import get_all_plugins
|
|
|
|
self.object = self.get_object()
|
|
|
|
plugins_active = self.object.get_plugins()
|
|
plugins_available = {
|
|
p.module: p for p in get_all_plugins()
|
|
if not p.name.startswith('.') and getattr(p, 'visible', True)
|
|
}
|
|
|
|
with transaction.atomic():
|
|
for key, value in request.POST.items():
|
|
if key.startswith("plugin:"):
|
|
module = key.split(":")[1]
|
|
if value == "enable" and module in plugins_available:
|
|
if getattr(plugins_available[module], 'restricted', False):
|
|
if not request.user.is_superuser:
|
|
continue
|
|
|
|
if hasattr(plugins_available[module].app, 'installed'):
|
|
getattr(plugins_available[module].app, 'installed')(self.request.event)
|
|
|
|
self.request.event.log_action('pretix.event.plugins.enabled', user=self.request.user,
|
|
data={'plugin': module})
|
|
if module not in plugins_active:
|
|
plugins_active.append(module)
|
|
else:
|
|
self.request.event.log_action('pretix.event.plugins.disabled', user=self.request.user,
|
|
data={'plugin': module})
|
|
if module in plugins_active:
|
|
plugins_active.remove(module)
|
|
self.object.plugins = ",".join(plugins_active)
|
|
self.object.save()
|
|
messages.success(self.request, _('Your changes have been saved.'))
|
|
return redirect(self.get_success_url())
|
|
|
|
def get_success_url(self) -> str:
|
|
return reverse('control:event.settings.plugins', kwargs={
|
|
'organizer': self.get_object().organizer.slug,
|
|
'event': self.get_object().slug,
|
|
})
|
|
|
|
|
|
class PaymentSettings(EventSettingsViewMixin, EventPermissionRequiredMixin, TemplateView, SingleObjectMixin):
|
|
model = Event
|
|
context_object_name = 'event'
|
|
permission = 'can_change_event_settings'
|
|
template_name = 'pretixcontrol/event/payment.html'
|
|
|
|
def get_object(self, queryset=None) -> Event:
|
|
return self.request.event
|
|
|
|
@cached_property
|
|
def provider_forms(self) -> list:
|
|
providers = []
|
|
for provider in self.request.event.get_payment_providers().values():
|
|
provider.form = ProviderForm(
|
|
obj=self.request.event,
|
|
settingspref=provider.settings.get_prefix(),
|
|
data=(self.request.POST if self.request.method == 'POST' else None)
|
|
)
|
|
provider.form.fields = OrderedDict(
|
|
[
|
|
('%s%s' % (provider.settings.get_prefix(), k), v)
|
|
for k, v in provider.settings_form_fields.items()
|
|
]
|
|
)
|
|
provider.settings_content = provider.settings_content_render(self.request)
|
|
provider.form.prepare_fields()
|
|
if provider.settings_content or provider.form.fields:
|
|
# Exclude providers which do not provide any settings
|
|
providers.append(provider)
|
|
return providers
|
|
|
|
def get_context_data(self, *args, **kwargs) -> dict:
|
|
context = super().get_context_data(*args, **kwargs)
|
|
context['sform'] = self.sform
|
|
return context
|
|
|
|
@cached_property
|
|
def sform(self):
|
|
return PaymentSettingsForm(
|
|
obj=self.object,
|
|
prefix='settings',
|
|
data=self.request.POST if self.request.method == 'POST' else None
|
|
)
|
|
|
|
def get(self, request, *args, **kwargs):
|
|
self.object = self.get_object()
|
|
context = self.get_context_data(object=self.object)
|
|
context['providers'] = self.provider_forms
|
|
return self.render_to_response(context)
|
|
|
|
@transaction.atomic
|
|
def post(self, request, *args, **kwargs):
|
|
self.object = self.get_object()
|
|
success = self.sform.is_valid()
|
|
if success:
|
|
self.sform.save()
|
|
if self.sform.has_changed():
|
|
self.request.event.log_action('pretix.event.settings', user=self.request.user, data={
|
|
k: self.request.event.settings.get(k) for k in self.sform.changed_data
|
|
})
|
|
for provider in self.provider_forms:
|
|
if provider.form.is_valid():
|
|
if provider.form.has_changed():
|
|
self.request.event.log_action(
|
|
'pretix.event.payment.provider.' + provider.identifier, user=self.request.user, data={
|
|
k: provider.form.cleaned_data.get(k) for k in provider.form.changed_data
|
|
}
|
|
)
|
|
provider.form.save()
|
|
else:
|
|
success = False
|
|
if success:
|
|
messages.success(self.request, _('Your changes have been saved.'))
|
|
return redirect(self.get_success_url())
|
|
else:
|
|
messages.error(self.request, _('We could not save your changes. See below for details.'))
|
|
return self.get(request)
|
|
|
|
def get_success_url(self) -> str:
|
|
return reverse('control:event.settings.payment', kwargs={
|
|
'organizer': self.get_object().organizer.slug,
|
|
'event': self.get_object().slug,
|
|
})
|
|
|
|
|
|
class EventSettingsFormView(EventPermissionRequiredMixin, FormView):
|
|
model = Event
|
|
permission = 'can_change_event_settings'
|
|
|
|
def get_context_data(self, *args, **kwargs) -> dict:
|
|
context = super().get_context_data(*args, **kwargs)
|
|
return context
|
|
|
|
def get_form_kwargs(self):
|
|
kwargs = super().get_form_kwargs()
|
|
kwargs['obj'] = self.request.event
|
|
return kwargs
|
|
|
|
def _save_decoupled(self, form):
|
|
# Save fields that are currently only set via the organizer but should be decoupled
|
|
fields = set()
|
|
for f in self.request.POST.getlist("decouple"):
|
|
fields |= set(f.split(","))
|
|
for f in fields:
|
|
if f not in form.fields:
|
|
continue
|
|
if f not in self.request.event.settings._cache():
|
|
self.request.event.settings.set(f, self.request.event.settings.get(f))
|
|
|
|
@transaction.atomic
|
|
def post(self, request, *args, **kwargs):
|
|
form = self.get_form()
|
|
if form.is_valid():
|
|
form.save()
|
|
self._save_decoupled(form)
|
|
if form.has_changed():
|
|
self.request.event.log_action(
|
|
'pretix.event.settings', user=self.request.user, data={
|
|
k: (form.cleaned_data.get(k).name
|
|
if isinstance(form.cleaned_data.get(k), File)
|
|
else form.cleaned_data.get(k))
|
|
for k in form.changed_data
|
|
}
|
|
)
|
|
messages.success(self.request, _('Your changes have been saved.'))
|
|
return redirect(self.get_success_url())
|
|
else:
|
|
messages.error(self.request, _('We could not save your changes. See below for details.'))
|
|
return self.get(request)
|
|
|
|
|
|
class InvoiceSettings(EventSettingsViewMixin, EventSettingsFormView):
|
|
model = Event
|
|
form_class = InvoiceSettingsForm
|
|
template_name = 'pretixcontrol/event/invoicing.html'
|
|
permission = 'can_change_event_settings'
|
|
|
|
def get_success_url(self) -> str:
|
|
if 'preview' in self.request.POST:
|
|
return reverse('control:event.settings.invoice.preview', kwargs={
|
|
'organizer': self.request.event.organizer.slug,
|
|
'event': self.request.event.slug
|
|
})
|
|
return reverse('control:event.settings.invoice', kwargs={
|
|
'organizer': self.request.event.organizer.slug,
|
|
'event': self.request.event.slug
|
|
})
|
|
|
|
|
|
class InvoicePreview(EventPermissionRequiredMixin, View):
|
|
permission = 'can_change_event_settings'
|
|
|
|
def get(self, request, *args, **kwargs):
|
|
fname, ftype, fcontent = build_preview_invoice_pdf(request.event)
|
|
resp = HttpResponse(fcontent, content_type=ftype)
|
|
resp['Content-Disposition'] = 'attachment; filename="{}"'.format(fname)
|
|
return resp
|
|
|
|
|
|
class DisplaySettings(EventSettingsViewMixin, EventSettingsFormView):
|
|
model = Event
|
|
form_class = DisplaySettingsForm
|
|
template_name = 'pretixcontrol/event/display.html'
|
|
permission = 'can_change_event_settings'
|
|
|
|
def get_success_url(self) -> str:
|
|
return reverse('control:event.settings.display', kwargs={
|
|
'organizer': self.request.event.organizer.slug,
|
|
'event': self.request.event.slug
|
|
})
|
|
|
|
@transaction.atomic
|
|
def post(self, request, *args, **kwargs):
|
|
form = self.get_form()
|
|
if form.is_valid():
|
|
form.save()
|
|
self._save_decoupled(form)
|
|
if form.has_changed():
|
|
self.request.event.log_action(
|
|
'pretix.event.settings', user=self.request.user, data={
|
|
k: (form.cleaned_data.get(k).name
|
|
if isinstance(form.cleaned_data.get(k), File)
|
|
else form.cleaned_data.get(k))
|
|
for k in form.changed_data
|
|
}
|
|
)
|
|
regenerate_css.apply_async(args=(self.request.event.pk,))
|
|
messages.success(self.request, _('Your changes have been saved. Please note that it can '
|
|
'take a short period of time until your changes become '
|
|
'active.'))
|
|
return redirect(self.get_success_url())
|
|
else:
|
|
messages.error(self.request, _('We could not save your changes. See below for details.'))
|
|
return self.get(request)
|
|
|
|
|
|
class MailSettings(EventSettingsViewMixin, EventSettingsFormView):
|
|
model = Event
|
|
form_class = MailSettingsForm
|
|
template_name = 'pretixcontrol/event/mail.html'
|
|
permission = 'can_change_event_settings'
|
|
|
|
def get_success_url(self) -> str:
|
|
return reverse('control:event.settings.mail', kwargs={
|
|
'organizer': self.request.event.organizer.slug,
|
|
'event': self.request.event.slug
|
|
})
|
|
|
|
@transaction.atomic
|
|
def post(self, request, *args, **kwargs):
|
|
form = self.get_form()
|
|
if form.is_valid():
|
|
form.save()
|
|
if form.has_changed():
|
|
self.request.event.log_action(
|
|
'pretix.event.settings', user=self.request.user, data={
|
|
k: form.cleaned_data.get(k) for k in form.changed_data
|
|
}
|
|
)
|
|
|
|
if request.POST.get('test', '0').strip() == '1':
|
|
backend = self.request.event.get_mail_backend(force_custom=True)
|
|
try:
|
|
backend.test(self.request.event.settings.mail_from)
|
|
except Exception as e:
|
|
messages.warning(self.request, _('An error occurred while contacting the SMTP server: %s') % str(e))
|
|
else:
|
|
if form.cleaned_data.get('smtp_use_custom'):
|
|
messages.success(self.request, _('Your changes have been saved and the connection attempt to '
|
|
'your SMTP server was successful.'))
|
|
else:
|
|
messages.success(self.request, _('We\'ve been able to contact the SMTP server you configured. '
|
|
'Remember to check the "use custom SMTP server" checkbox, '
|
|
'otherwise your SMTP server will not be used.'))
|
|
else:
|
|
messages.success(self.request, _('Your changes have been saved.'))
|
|
return redirect(self.get_success_url())
|
|
else:
|
|
messages.error(self.request, _('We could not save your changes. See below for details.'))
|
|
return self.get(request)
|
|
|
|
|
|
class MailSettingsPreview(EventPermissionRequiredMixin, View):
|
|
permission = 'can_change_event_settings'
|
|
|
|
# return the origin text if key is missing in dict
|
|
class SafeDict(dict):
|
|
def __missing__(self, key):
|
|
return '{' + key + '}'
|
|
|
|
@staticmethod
|
|
def generate_order_fullname(slug, code):
|
|
return '{event}-{code}'.format(event=slug.upper(), code=code)
|
|
|
|
# create data which depend on locale
|
|
def localized_data(self):
|
|
return {
|
|
'date': date_format(now() + timedelta(days=7), 'SHORT_DATE_FORMAT'),
|
|
'expire_date': date_format(now() + timedelta(days=15), 'SHORT_DATE_FORMAT'),
|
|
'payment_info': _('{} {} has been transferred to account <9999-9999-9999-9999> at {}').format(
|
|
42.23, self.request.event.currency, date_format(now(), 'SHORT_DATETIME_FORMAT'))
|
|
}
|
|
|
|
# create index-language mapping
|
|
@cached_property
|
|
def supported_locale(self):
|
|
locales = {}
|
|
for idx, val in enumerate(settings.LANGUAGES):
|
|
if val[0] in self.request.event.settings.locales:
|
|
locales[str(idx)] = val[0]
|
|
return locales
|
|
|
|
@cached_property
|
|
def items(self):
|
|
return {
|
|
'mail_text_order_placed': ['total', 'currency', 'date', 'invoice_company',
|
|
'event', 'payment_info', 'url', 'invoice_name'],
|
|
'mail_text_order_paid': ['event', 'url', 'invoice_name', 'invoice_company', 'payment_info'],
|
|
'mail_text_order_free': ['event', 'url', 'invoice_name', 'invoice_company'],
|
|
'mail_text_resend_link': ['event', 'url', 'invoice_name', 'invoice_company'],
|
|
'mail_text_resend_all_links': ['event', 'orders'],
|
|
'mail_text_order_changed': ['event', 'url', 'invoice_name', 'invoice_company'],
|
|
'mail_text_order_expire_warning': ['event', 'url', 'expire_date', 'invoice_name', 'invoice_company'],
|
|
'mail_text_waiting_list': ['event', 'url', 'product', 'hours', 'code'],
|
|
'mail_text_order_canceled': ['code', 'event', 'url'],
|
|
'mail_text_order_custom_mail': ['expire_date', 'event', 'code', 'date', 'url',
|
|
'invoice_name', 'invoice_company'],
|
|
'mail_text_download_reminder': ['event', 'url']
|
|
}
|
|
|
|
@cached_property
|
|
def base_data(self):
|
|
user_orders = [
|
|
{'code': 'F8VVL', 'secret': '6zzjnumtsx136ddy'},
|
|
{'code': 'HIDHK', 'secret': '98kusd8ofsj8dnkd'},
|
|
{'code': 'OPKSB', 'secret': '09pjdksflosk3njd'}
|
|
]
|
|
orders = [' - {} - {}'.format(self.generate_order_fullname(self.request.event.slug, order['code']),
|
|
self.generate_order_url(order['code'], order['secret']))
|
|
for order in user_orders]
|
|
return {
|
|
'event': self.request.event.name,
|
|
'total': 42.23,
|
|
'currency': self.request.event.currency,
|
|
'url': self.generate_order_url(user_orders[0]['code'], user_orders[0]['secret']),
|
|
'orders': '\n'.join(orders),
|
|
'hours': self.request.event.settings.waiting_list_hours,
|
|
'product': _('Sample Admission Ticket'),
|
|
'code': '68CYU2H6ZTP3WLK5',
|
|
'invoice_name': _('John Doe'),
|
|
'invoice_company': _('Sample Corporation'),
|
|
'payment_info': _('Please transfer money to this bank account: 9999-9999-9999-9999')
|
|
}
|
|
|
|
def generate_order_url(self, code, secret):
|
|
return build_absolute_uri('presale:event.order', kwargs={
|
|
'event': self.request.event.slug,
|
|
'organizer': self.request.event.organizer.slug,
|
|
'order': code,
|
|
'secret': secret
|
|
})
|
|
|
|
# get all supported placeholders with dummy values
|
|
def placeholders(self, item):
|
|
supported = {}
|
|
local_data = self.localized_data()
|
|
for key in self.items.get(item):
|
|
supported[key] = self.base_data.get(key) if key in self.base_data else local_data.get(key)
|
|
return self.SafeDict(supported)
|
|
|
|
def post(self, request, *args, **kwargs):
|
|
preview_item = request.POST.get('item', '')
|
|
if preview_item not in self.items:
|
|
return HttpResponseBadRequest(_('invalid item'))
|
|
|
|
regex = r"^" + re.escape(preview_item) + r"_(?P<idx>[\d+])$"
|
|
msgs = {}
|
|
for k, v in request.POST.items():
|
|
# only accept allowed fields
|
|
matched = re.search(regex, k)
|
|
if matched is not None:
|
|
idx = matched.group('idx')
|
|
if idx in self.supported_locale:
|
|
with translation.override(self.supported_locale[idx]):
|
|
msgs[self.supported_locale[idx]] = v.format_map(self.placeholders(preview_item))
|
|
|
|
return JsonResponse({
|
|
'item': preview_item,
|
|
'msgs': msgs
|
|
})
|
|
|
|
|
|
class TicketSettingsPreview(EventPermissionRequiredMixin, View):
|
|
permission = 'can_change_event_settings'
|
|
|
|
@cached_property
|
|
def output(self):
|
|
responses = register_ticket_outputs.send(self.request.event)
|
|
for receiver, response in responses:
|
|
provider = response(self.request.event)
|
|
if provider.identifier == self.kwargs.get('output'):
|
|
return provider
|
|
|
|
def get(self, request, *args, **kwargs):
|
|
if not self.output:
|
|
messages.error(request, _('You requested an invalid ticket output type.'))
|
|
return redirect(self.get_error_url())
|
|
|
|
fname, mimet, data = tickets.preview(self.request.event.pk, self.output.identifier)
|
|
resp = HttpResponse(data, content_type=mimet)
|
|
ftype = fname.split(".")[-1]
|
|
resp['Content-Disposition'] = 'attachment; filename="ticket-preview.{}"'.format(ftype)
|
|
return resp
|
|
|
|
def get_error_url(self) -> str:
|
|
return reverse('control:event.settings.tickets', kwargs={
|
|
'organizer': self.request.event.organizer.slug,
|
|
'event': self.request.event.slug
|
|
})
|
|
|
|
|
|
class TicketSettings(EventSettingsViewMixin, EventPermissionRequiredMixin, FormView):
|
|
model = Event
|
|
form_class = TicketSettingsForm
|
|
template_name = 'pretixcontrol/event/tickets.html'
|
|
permission = 'can_change_event_settings'
|
|
|
|
def get_context_data(self, *args, **kwargs) -> dict:
|
|
context = super().get_context_data(*args, **kwargs)
|
|
context['providers'] = self.provider_forms
|
|
|
|
context['any_enabled'] = False
|
|
responses = register_ticket_outputs.send(self.request.event)
|
|
for receiver, response in responses:
|
|
provider = response(self.request.event)
|
|
if provider.is_enabled:
|
|
context['any_enabled'] = True
|
|
break
|
|
|
|
return context
|
|
|
|
def get_success_url(self) -> str:
|
|
return reverse('control:event.settings.tickets', kwargs={
|
|
'organizer': self.request.event.organizer.slug,
|
|
'event': self.request.event.slug
|
|
})
|
|
|
|
def get_form_kwargs(self):
|
|
kwargs = super().get_form_kwargs()
|
|
kwargs['obj'] = self.request.event
|
|
return kwargs
|
|
|
|
def get_form(self, form_class=None):
|
|
form = super().get_form(form_class)
|
|
form.prepare_fields()
|
|
return form
|
|
|
|
def form_invalid(self, form):
|
|
messages.error(self.request, _('We could not save your changes. See below for details.'))
|
|
return super().form_invalid(form)
|
|
|
|
@transaction.atomic
|
|
def post(self, request, *args, **kwargs):
|
|
success = True
|
|
for provider in self.provider_forms:
|
|
if provider.form.is_valid():
|
|
provider.form.save()
|
|
if provider.form.has_changed():
|
|
self.request.event.log_action(
|
|
'pretix.event.tickets.provider.' + provider.identifier, user=self.request.user, data={
|
|
k: (provider.form.cleaned_data.get(k).name
|
|
if isinstance(provider.form.cleaned_data.get(k), File)
|
|
else provider.form.cleaned_data.get(k))
|
|
for k in provider.form.changed_data
|
|
}
|
|
)
|
|
CachedTicket.objects.filter(
|
|
order_position__order__event=self.request.event, provider=provider.identifier
|
|
).delete()
|
|
CachedCombinedTicket.objects.filter(
|
|
order__event=self.request.event, provider=provider.identifier
|
|
).delete()
|
|
else:
|
|
success = False
|
|
form = self.get_form(self.get_form_class())
|
|
if success and form.is_valid():
|
|
form.save()
|
|
if form.has_changed():
|
|
self.request.event.log_action(
|
|
'pretix.event.tickets.settings', user=self.request.user, data={
|
|
k: form.cleaned_data.get(k) for k in form.changed_data
|
|
}
|
|
)
|
|
|
|
messages.success(self.request, _('Your changes have been saved.'))
|
|
return redirect(self.get_success_url())
|
|
else:
|
|
return self.form_invalid(form)
|
|
|
|
@cached_property
|
|
def provider_forms(self) -> list:
|
|
providers = []
|
|
responses = register_ticket_outputs.send(self.request.event)
|
|
for receiver, response in responses:
|
|
provider = response(self.request.event)
|
|
provider.form = ProviderForm(
|
|
obj=self.request.event,
|
|
settingspref='ticketoutput_%s_' % provider.identifier,
|
|
data=(self.request.POST if self.request.method == 'POST' else None),
|
|
files=(self.request.FILES if self.request.method == 'POST' else None)
|
|
)
|
|
provider.form.fields = OrderedDict(
|
|
[
|
|
('ticketoutput_%s_%s' % (provider.identifier, k), v)
|
|
for k, v in provider.settings_form_fields.items()
|
|
]
|
|
)
|
|
provider.settings_content = provider.settings_content_render(self.request)
|
|
provider.form.prepare_fields()
|
|
|
|
provider.preview_allowed = True
|
|
for k, v in provider.settings_form_fields.items():
|
|
if v.required and not self.request.event.settings.get('ticketoutput_%s_%s' % (provider.identifier, k)):
|
|
provider.preview_allowed = False
|
|
break
|
|
|
|
providers.append(provider)
|
|
return providers
|
|
|
|
|
|
class EventPermissions(EventSettingsViewMixin, EventPermissionRequiredMixin, TemplateView):
|
|
template_name = 'pretixcontrol/event/permissions.html'
|
|
|
|
|
|
class EventLive(EventPermissionRequiredMixin, TemplateView):
|
|
permission = 'can_change_event_settings'
|
|
template_name = 'pretixcontrol/event/live.html'
|
|
|
|
def get_context_data(self, **kwargs):
|
|
ctx = super().get_context_data(**kwargs)
|
|
ctx['issues'] = self.issues
|
|
return ctx
|
|
|
|
@cached_property
|
|
def issues(self):
|
|
issues = []
|
|
has_paid_things = (
|
|
Item.objects.filter(event=self.request.event, default_price__gt=0).exists()
|
|
or ItemVariation.objects.filter(item__event=self.request.event, default_price__gt=0).exists()
|
|
)
|
|
|
|
has_payment_provider = False
|
|
for provider in self.request.event.get_payment_providers().values():
|
|
if provider.is_enabled and provider.identifier != 'free':
|
|
has_payment_provider = True
|
|
break
|
|
|
|
if has_paid_things and not has_payment_provider:
|
|
issues.append(_('You have configured at least one paid product but have not enabled any payment methods.'))
|
|
|
|
if not self.request.event.quotas.exists():
|
|
issues.append(_('You need to configure at least one quota to sell anything.'))
|
|
|
|
responses = event_live_issues.send(self.request.event)
|
|
for receiver, response in sorted(responses, key=lambda r: str(r[0])):
|
|
if response:
|
|
issues.append(response)
|
|
|
|
return issues
|
|
|
|
def post(self, request, *args, **kwargs):
|
|
if request.POST.get("live") == "true" and not self.issues:
|
|
request.event.live = True
|
|
request.event.save()
|
|
self.request.event.log_action(
|
|
'pretix.event.live.activated', user=self.request.user, data={}
|
|
)
|
|
messages.success(self.request, _('Your shop is live now!'))
|
|
elif request.POST.get("live") == "false":
|
|
request.event.live = False
|
|
request.event.save()
|
|
self.request.event.log_action(
|
|
'pretix.event.live.deactivated', user=self.request.user, data={}
|
|
)
|
|
messages.success(self.request, _('We\'ve taken your shop down. You can re-enable it whenever you want!'))
|
|
return redirect(self.get_success_url())
|
|
|
|
def get_success_url(self) -> str:
|
|
return reverse('control:event.live', kwargs={
|
|
'organizer': self.request.event.organizer.slug,
|
|
'event': self.request.event.slug
|
|
})
|
|
|
|
|
|
class EventDelete(EventPermissionRequiredMixin, FormView):
|
|
permission = 'can_change_event_settings'
|
|
template_name = 'pretixcontrol/event/delete.html'
|
|
form_class = EventDeleteForm
|
|
|
|
def post(self, request, *args, **kwargs):
|
|
if not self.request.event.allow_delete():
|
|
messages.error(self.request, _('This event can not be deleted.'))
|
|
return self.get(self.request, *self.args, **self.kwargs)
|
|
return super().post(request, *args, **kwargs)
|
|
|
|
def get_form_kwargs(self):
|
|
kwargs = super().get_form_kwargs()
|
|
kwargs['user'] = self.request.user
|
|
kwargs['event'] = self.request.event
|
|
return kwargs
|
|
|
|
def form_valid(self, form):
|
|
try:
|
|
with transaction.atomic():
|
|
self.request.organizer.log_action(
|
|
'pretix.event.deleted', user=self.request.user,
|
|
data={
|
|
'event_id': self.request.event.pk,
|
|
'name': str(self.request.event.name),
|
|
'logentries': list(self.request.event.logentry_set.values_list('pk', flat=True))
|
|
}
|
|
)
|
|
self.request.event.items.all().delete()
|
|
self.request.event.subevents.all().delete()
|
|
self.request.event.delete()
|
|
messages.success(self.request, _('The event has been deleted.'))
|
|
return redirect(self.get_success_url())
|
|
except ProtectedError:
|
|
messages.error(self.request, _('The event could not be deleted as some constraints (e.g. data created by '
|
|
'plug-ins) do not allow it.'))
|
|
return self.get(self.request, *self.args, **self.kwargs)
|
|
|
|
def get_success_url(self) -> str:
|
|
return reverse('control:index')
|
|
|
|
|
|
class EventLog(EventPermissionRequiredMixin, ListView):
|
|
template_name = 'pretixcontrol/event/logs.html'
|
|
model = LogEntry
|
|
context_object_name = 'logs'
|
|
paginate_by = 20
|
|
|
|
def get_queryset(self):
|
|
qs = self.request.event.logentry_set.all().select_related('user', 'content_type').order_by('-datetime')
|
|
qs = qs.exclude(action_type__in=OVERVIEW_BLACKLIST)
|
|
if not self.request.user.has_event_permission(self.request.organizer, self.request.event, 'can_view_orders'):
|
|
qs = qs.exclude(content_type=ContentType.objects.get_for_model(Order))
|
|
if not self.request.user.has_event_permission(self.request.organizer, self.request.event, 'can_view_vouchers'):
|
|
qs = qs.exclude(content_type=ContentType.objects.get_for_model(Voucher))
|
|
|
|
if self.request.GET.get('user') == 'yes':
|
|
qs = qs.filter(user__isnull=False)
|
|
elif self.request.GET.get('user') == 'no':
|
|
qs = qs.filter(user__isnull=True)
|
|
elif self.request.GET.get('user'):
|
|
qs = qs.filter(user_id=self.request.GET.get('user'))
|
|
|
|
return qs
|
|
|
|
def get_context_data(self, **kwargs):
|
|
ctx = super().get_context_data()
|
|
ctx['userlist'] = self.request.event.logentry_set.order_by().distinct().values('user__id', 'user__email')
|
|
return ctx
|
|
|
|
|
|
class EventActions(EventPermissionRequiredMixin, ListView):
|
|
template_name = 'pretixcontrol/event/actions.html'
|
|
model = RequiredAction
|
|
context_object_name = 'actions'
|
|
paginate_by = 20
|
|
permission = 'can_change_orders'
|
|
|
|
def get_queryset(self):
|
|
qs = self.request.event.requiredaction_set.filter(done=False)
|
|
return qs
|
|
|
|
def get_context_data(self, **kwargs):
|
|
ctx = super().get_context_data()
|
|
for a in ctx['actions']:
|
|
a.display = a.display(self.request)
|
|
return ctx
|
|
|
|
|
|
class EventActionDiscard(EventPermissionRequiredMixin, View):
|
|
permission = 'can_change_orders'
|
|
|
|
def get(self, request, **kwargs):
|
|
action = get_object_or_404(RequiredAction, event=request.event, pk=kwargs.get('id'))
|
|
action.done = True
|
|
action.user = request.user
|
|
action.save()
|
|
messages.success(self.request, _('The issue has been marked as resolved!'))
|
|
return redirect(self.get_success_url())
|
|
|
|
def get_success_url(self) -> str:
|
|
return reverse('control:event.index', kwargs={
|
|
'organizer': self.request.event.organizer.slug,
|
|
'event': self.request.event.slug
|
|
})
|
|
|
|
|
|
class EventComment(EventPermissionRequiredMixin, View):
|
|
permission = 'can_change_event_settings'
|
|
|
|
def post(self, *args, **kwargs):
|
|
form = CommentForm(self.request.POST)
|
|
if form.is_valid():
|
|
self.request.event.comment = form.cleaned_data.get('comment')
|
|
self.request.event.save()
|
|
self.request.event.log_action('pretix.event.comment', user=self.request.user, data={
|
|
'new_comment': form.cleaned_data.get('comment')
|
|
})
|
|
messages.success(self.request, _('The comment has been updated.'))
|
|
else:
|
|
messages.error(self.request, _('Could not update the comment.'))
|
|
return redirect(self.get_success_url())
|
|
|
|
def get(self, *args, **kwargs):
|
|
return HttpResponseNotAllowed(['POST'])
|
|
|
|
def get_success_url(self) -> str:
|
|
return reverse('control:event.index', kwargs={
|
|
'organizer': self.request.event.organizer.slug,
|
|
'event': self.request.event.slug
|
|
})
|
|
|
|
|
|
class TaxList(EventSettingsViewMixin, EventPermissionRequiredMixin, PaginationMixin, ListView):
|
|
model = TaxRule
|
|
context_object_name = 'taxrules'
|
|
template_name = 'pretixcontrol/event/tax_index.html'
|
|
permission = 'can_change_event_settings'
|
|
|
|
def get_queryset(self):
|
|
return self.request.event.tax_rules.all()
|
|
|
|
|
|
class TaxCreate(EventSettingsViewMixin, EventPermissionRequiredMixin, CreateView):
|
|
model = TaxRule
|
|
form_class = TaxRuleForm
|
|
template_name = 'pretixcontrol/event/tax_edit.html'
|
|
permission = 'can_change_event_settings'
|
|
context_object_name = 'taxrule'
|
|
|
|
def get_success_url(self) -> str:
|
|
return reverse('control:event.settings.tax', kwargs={
|
|
'organizer': self.request.event.organizer.slug,
|
|
'event': self.request.event.slug,
|
|
})
|
|
|
|
def get_initial(self):
|
|
return {
|
|
'name': LazyI18nString.from_gettext(ugettext('VAT'))
|
|
}
|
|
|
|
@transaction.atomic
|
|
def form_valid(self, form):
|
|
form.instance.event = self.request.event
|
|
messages.success(self.request, _('The new tax rule has been created.'))
|
|
ret = super().form_valid(form)
|
|
form.instance.log_action('pretix.event.taxrule.added', user=self.request.user, data=dict(form.cleaned_data))
|
|
return ret
|
|
|
|
def form_invalid(self, form):
|
|
messages.error(self.request, _('We could not save your changes. See below for details.'))
|
|
return super().form_invalid(form)
|
|
|
|
|
|
class TaxUpdate(EventSettingsViewMixin, EventPermissionRequiredMixin, UpdateView):
|
|
model = TaxRule
|
|
form_class = TaxRuleForm
|
|
template_name = 'pretixcontrol/event/tax_edit.html'
|
|
permission = 'can_change_event_settings'
|
|
context_object_name = 'rule'
|
|
|
|
def get_object(self, queryset=None) -> TaxRule:
|
|
try:
|
|
return self.request.event.tax_rules.get(
|
|
id=self.kwargs['rule']
|
|
)
|
|
except TaxRule.DoesNotExist:
|
|
raise Http404(_("The requested tax rule does not exist."))
|
|
|
|
@transaction.atomic
|
|
def form_valid(self, form):
|
|
messages.success(self.request, _('Your changes have been saved.'))
|
|
if form.has_changed():
|
|
self.object.log_action(
|
|
'pretix.event.taxrule.changed', user=self.request.user, data={
|
|
k: form.cleaned_data.get(k) for k in form.changed_data
|
|
}
|
|
)
|
|
return super().form_valid(form)
|
|
|
|
def get_success_url(self) -> str:
|
|
return reverse('control:event.settings.tax', kwargs={
|
|
'organizer': self.request.event.organizer.slug,
|
|
'event': self.request.event.slug,
|
|
})
|
|
|
|
def form_invalid(self, form):
|
|
messages.error(self.request, _('We could not save your changes. See below for details.'))
|
|
return super().form_invalid(form)
|
|
|
|
|
|
class TaxDelete(EventSettingsViewMixin, EventPermissionRequiredMixin, DeleteView):
|
|
model = TaxRule
|
|
template_name = 'pretixcontrol/event/tax_delete.html'
|
|
permission = 'can_change_event_settings'
|
|
context_object_name = 'taxrule'
|
|
|
|
def get_object(self, queryset=None) -> TaxRule:
|
|
try:
|
|
return self.request.event.tax_rules.get(
|
|
id=self.kwargs['rule']
|
|
)
|
|
except TaxRule.DoesNotExist:
|
|
raise Http404(_("The requested tax rule does not exist."))
|
|
|
|
@transaction.atomic
|
|
def delete(self, request, *args, **kwargs):
|
|
self.object = self.get_object()
|
|
success_url = self.get_success_url()
|
|
if self.object.allow_delete():
|
|
self.object.log_action(action='pretix.event.taxrule.deleted', user=request.user)
|
|
self.object.delete()
|
|
messages.success(self.request, _('The selected tax rule has been deleted.'))
|
|
else:
|
|
messages.error(self.request, _('The selected tax rule can not be deleted.'))
|
|
return redirect(success_url)
|
|
|
|
def get_success_url(self) -> str:
|
|
return reverse('control:event.settings.tax', kwargs={
|
|
'organizer': self.request.event.organizer.slug,
|
|
'event': self.request.event.slug,
|
|
})
|
|
|
|
def get_context_data(self, *args, **kwargs) -> dict:
|
|
context = super().get_context_data(*args, **kwargs)
|
|
context['possible'] = self.object.allow_delete()
|
|
return context
|
|
|
|
|
|
class WidgetSettings(EventSettingsViewMixin, EventPermissionRequiredMixin, FormView):
|
|
template_name = 'pretixcontrol/event/widget.html'
|
|
permission = 'can_change_event_settings'
|
|
form_class = WidgetCodeForm
|
|
|
|
def get_form_kwargs(self):
|
|
kwargs = super().get_form_kwargs()
|
|
kwargs['event'] = self.request.event
|
|
return kwargs
|
|
|
|
def form_valid(self, form):
|
|
ctx = self.get_context_data()
|
|
ctx['form'] = form
|
|
ctx['valid'] = True
|
|
return self.render_to_response(ctx)
|
|
|
|
def get_context_data(self, **kwargs):
|
|
ctx = super().get_context_data(**kwargs)
|
|
ctx['urlprefix'] = settings.SITE_URL
|
|
domain = get_domain(self.request.organizer)
|
|
if domain:
|
|
siteurlsplit = urlsplit(settings.SITE_URL)
|
|
if siteurlsplit.port and siteurlsplit.port not in (80, 443):
|
|
domain = '%s:%d' % (domain, siteurlsplit.port)
|
|
ctx['urlprefix'] = '%s://%s' % (siteurlsplit.scheme, domain)
|
|
return ctx
|