forked from CGM_Public/pretix_original
Allow to send e-mails to attendees individually (#1299)
* . * Add a position detail page to the frontend * Mail templates * Send mails * Send reminder email * Add position support to sendmail plugin * Add and fix some tests * Fix failing test on real databases
This commit is contained in:
@@ -169,6 +169,15 @@ This signal is sent out to display additional information on the order detail pa
|
||||
As with all plugin signals, the ``sender`` keyword argument will contain the event.
|
||||
"""
|
||||
|
||||
position_info = EventPluginSignal(
|
||||
providing_args=["order", "position"]
|
||||
)
|
||||
"""
|
||||
This signal is sent out to display additional information on the position detail page
|
||||
|
||||
As with all plugin signals, the ``sender`` keyword argument will contain the event.
|
||||
"""
|
||||
|
||||
process_request = EventPluginSignal(
|
||||
providing_args=["request"]
|
||||
)
|
||||
|
||||
@@ -64,7 +64,7 @@
|
||||
<div class="download-desktop">
|
||||
{% if line.generate_ticket %}
|
||||
{% for b in download_buttons %}
|
||||
<form action="{% eventurl event "presale:event.order.download" secret=order.secret order=order.code output=b.identifier position=line.id %}"
|
||||
<form action="{% if position_page %}{% eventurl event "presale:event.order.position.download" secret=line.web_secret order=order.code output=b.identifier pid=line.pk position=line.positionid %}{% else %}{% eventurl event "presale:event.order.download" secret=order.secret order=order.code output=b.identifier position=line.pk %}{% endif %}"
|
||||
method="post" data-asynctask data-asynctask-download class="download-btn-form">
|
||||
{% csrf_token %}
|
||||
<button type="submit"
|
||||
@@ -157,7 +157,7 @@
|
||||
<div class="download-mobile">
|
||||
{% if line.generate_ticket %}
|
||||
{% for b in download_buttons %}
|
||||
<form action="{% eventurl event "presale:event.order.download" secret=order.secret order=order.code output=b.identifier position=line.id %}"
|
||||
<form action="{% if position_page %}{% eventurl event "presale:event.order.position.download" secret=line.web_secret order=order.code pid=line.pk output=b.identifier position=line.positionid %}{% else %}{% eventurl event "presale:event.order.download" secret=order.secret order=order.code output=b.identifier position=line.id %}{% endif %}"
|
||||
method="post" data-asynctask data-asynctask-download class="download-btn-form">
|
||||
{% csrf_token %}
|
||||
<button type="submit"
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
{% load i18n %}
|
||||
{% load eventurl %}
|
||||
{% if can_download and download_buttons and order.count_positions %}
|
||||
<div class="alert alert-info info-download">
|
||||
{% blocktrans trimmed %}
|
||||
You can download your tickets using the buttons below. Please have your ticket ready when entering the event.
|
||||
{% endblocktrans %}
|
||||
</div>
|
||||
{% if cart.positions|length > 1 and can_download_multi %}
|
||||
<p class="info-download">
|
||||
{% trans "Download all tickets at once:" %}
|
||||
{% for b in download_buttons %}
|
||||
{% if b.multi %}
|
||||
<form action="{% eventurl event "presale:event.order.download.combined" secret=order.secret order=order.code output=b.identifier %}"
|
||||
method="post" data-asynctask data-asynctask-download class="download-btn-form">
|
||||
{% csrf_token %}
|
||||
<button type="submit"
|
||||
class="btn btn-sm {% if b.identifier == "pdf" %}btn-primary{% else %}btn-default{% endif %}">
|
||||
<span class="fa {{ b.icon }}"></span> {{ b.text }}
|
||||
</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</p>
|
||||
{% endif %}
|
||||
{% elif not download_buttons and ticket_download_date %}
|
||||
{% if order.status == 'p' %}
|
||||
<div class="alert alert-info info-download">
|
||||
{% blocktrans trimmed with date=ticket_download_date|date:"SHORT_DATE_FORMAT" %}
|
||||
You will be able to download your tickets here starting on {{ date }}.
|
||||
{% endblocktrans %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
@@ -111,38 +111,7 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if can_download and download_buttons and order.count_positions %}
|
||||
<div class="alert alert-info info-download">
|
||||
{% blocktrans trimmed %}
|
||||
You can download your tickets using the buttons below. Please have your ticket ready when entering the event.
|
||||
{% endblocktrans %}
|
||||
</div>
|
||||
{% if cart.positions|length > 1 and can_download_multi %}
|
||||
<p class="info-download">
|
||||
{% trans "Download all tickets at once:" %}
|
||||
{% for b in download_buttons %}
|
||||
{% if b.multi %}
|
||||
<form action="{% eventurl event "presale:event.order.download.combined" secret=order.secret order=order.code output=b.identifier %}"
|
||||
method="post" data-asynctask data-asynctask-download class="download-btn-form">
|
||||
{% csrf_token %}
|
||||
<button type="submit"
|
||||
class="btn btn-sm {% if b.identifier == "pdf" %}btn-primary{% else %}btn-default{% endif %}">
|
||||
<span class="fa {{ b.icon }}"></span> {{ b.text }}
|
||||
</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</p>
|
||||
{% endif %}
|
||||
{% elif not download_buttons and ticket_download_date %}
|
||||
{% if order.status == 'p' %}
|
||||
<div class="alert alert-info info-download">
|
||||
{% blocktrans trimmed with date=ticket_download_date|date:"SHORT_DATE_FORMAT" %}
|
||||
You will be able to download your tickets here starting on {{ date }}.
|
||||
{% endblocktrans %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% include "pretixpresale/event/fragment_downloads.html" %}
|
||||
<div class="panel panel-primary cart">
|
||||
<div class="panel-heading">
|
||||
{% if order.can_modify_answers %}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
{% extends "pretixpresale/event/base.html" %}
|
||||
{% load i18n %}
|
||||
{% load bootstrap3 %}
|
||||
{% load eventsignal %}
|
||||
{% load money %}
|
||||
{% load eventurl %}
|
||||
{% block title %}{% trans "Registration details" %}{% endblock %}
|
||||
{% block content %}
|
||||
<h2>
|
||||
{% blocktrans trimmed %}
|
||||
Your registration
|
||||
{% endblocktrans %}
|
||||
{% if order.testmode %}
|
||||
<span class="label label-warning">{% trans "TEST MODE" %}</span>
|
||||
{% endif %}
|
||||
{% if backend_user %}
|
||||
<a href="{% url "control:event.order" event=request.event.slug organizer=request.organizer.slug code=order.code %}" class="btn btn-default">
|
||||
{% trans "View in backend" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% include "pretixpresale/event/fragment_order_status.html" with order=order class="pull-right" %}
|
||||
<div class="clearfix"></div>
|
||||
</h2>
|
||||
{% include "pretixpresale/event/fragment_downloads.html" %}
|
||||
<div class="panel panel-primary cart">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">
|
||||
{% trans "Your items" %}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
{% include "pretixpresale/event/fragment_cart.html" with cart=cart event=request.event download=can_download position_page=True editable=False %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-primary">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">
|
||||
{% trans "Additional information" %}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<p>
|
||||
{% blocktrans trimmed with email="<strong>"|add:order.email|add:"</strong>"|safe %}
|
||||
This order is managed for you by {{ email }}. Please contact them for any questions regarding
|
||||
payment, cancellation or changes to this order.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{% eventsignal event "pretix.presale.signals.position_info" order=order position=position %}
|
||||
{% endblock %}
|
||||
@@ -49,6 +49,7 @@ event_patterns = [
|
||||
name='event.cart.add'),
|
||||
|
||||
url(r'resend/$', pretix.presale.views.user.ResendLinkView.as_view(), name='event.resend_link'),
|
||||
|
||||
url(r'^order/(?P<order>[^/]+)/(?P<secret>[A-Za-z0-9]+)/open/(?P<hash>[a-z0-9]+)/$', pretix.presale.views.order.OrderOpen.as_view(),
|
||||
name='event.order.open'),
|
||||
url(r'^order/(?P<order>[^/]+)/(?P<secret>[A-Za-z0-9]+)/$', pretix.presale.views.order.OrderDetails.as_view(),
|
||||
@@ -89,6 +90,14 @@ event_patterns = [
|
||||
url(r'^order/(?P<order>[^/]+)/(?P<secret>[A-Za-z0-9]+)/invoice/(?P<invoice>[0-9]+)$',
|
||||
pretix.presale.views.order.InvoiceDownload.as_view(),
|
||||
name='event.invoice.download'),
|
||||
|
||||
url(r'^ticket/(?P<order>[^/]+)/(?P<position>\d+)/(?P<secret>[A-Za-z0-9]+)/$',
|
||||
pretix.presale.views.order.OrderPositionDetails.as_view(),
|
||||
name='event.order.position'),
|
||||
url(r'^ticket/(?P<order>[^/]+)/(?P<position>\d+)/(?P<secret>[A-Za-z0-9]+)/download/(?P<pid>[0-9]+)/(?P<output>[^/]+)$',
|
||||
pretix.presale.views.order.OrderPositionDownload.as_view(),
|
||||
name='event.order.position.download'),
|
||||
|
||||
url(r'^ical/?$',
|
||||
pretix.presale.views.event.EventIcalDownload.as_view(),
|
||||
name='event.ical.download'),
|
||||
|
||||
@@ -65,6 +65,39 @@ class OrderDetailMixin(NoSearchIndexViewMixin):
|
||||
})
|
||||
|
||||
|
||||
class OrderPositionDetailMixin(NoSearchIndexViewMixin):
|
||||
@cached_property
|
||||
def position(self):
|
||||
p = OrderPosition.objects.filter(
|
||||
order__event=self.request.event,
|
||||
addon_to__isnull=True,
|
||||
order__code=self.kwargs['order'],
|
||||
positionid=self.kwargs['position']
|
||||
).select_related('order', 'order__event').first()
|
||||
if p:
|
||||
if p.web_secret.lower() == self.kwargs['secret'].lower():
|
||||
return p
|
||||
else:
|
||||
return None
|
||||
else:
|
||||
# Do a comparison as well to harden timing attacks
|
||||
if 'abcdefghijklmnopq'.lower() == self.kwargs['secret'].lower():
|
||||
return None
|
||||
else:
|
||||
return None
|
||||
|
||||
@cached_property
|
||||
def order(self):
|
||||
return self.position.order if self.position else None
|
||||
|
||||
def get_position_url(self):
|
||||
return eventreverse(self.request.event, 'presale:event.order.position', kwargs={
|
||||
'order': self.order.code,
|
||||
'secret': self.position.web_secret,
|
||||
'position': self.position.positionid,
|
||||
})
|
||||
|
||||
|
||||
@method_decorator(xframe_options_exempt, 'dispatch')
|
||||
class OrderOpen(EventViewMixin, OrderDetailMixin, View):
|
||||
def get(self, request, *args, **kwargs):
|
||||
@@ -76,8 +109,28 @@ class OrderOpen(EventViewMixin, OrderDetailMixin, View):
|
||||
return redirect(self.get_order_url())
|
||||
|
||||
|
||||
class TicketPageMixin:
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
|
||||
ctx['order'] = self.order
|
||||
|
||||
can_download = all([r for rr, r in allow_ticket_download.send(self.request.event, order=self.order)])
|
||||
if self.request.event.settings.ticket_download_date:
|
||||
ctx['ticket_download_date'] = self.order.ticket_download_date
|
||||
ctx['can_download'] = can_download and self.order.ticket_download_available and self.order.positions_with_tickets
|
||||
ctx['download_buttons'] = self.download_buttons
|
||||
|
||||
ctx['backend_user'] = (
|
||||
self.request.user.is_authenticated
|
||||
and self.request.user.has_event_permission(self.request.organizer, self.request.event, 'can_view_orders', request=self.request)
|
||||
)
|
||||
return ctx
|
||||
|
||||
|
||||
@method_decorator(xframe_options_exempt, 'dispatch')
|
||||
class OrderDetails(EventViewMixin, OrderDetailMixin, CartMixin, TemplateView):
|
||||
class OrderDetails(EventViewMixin, OrderDetailMixin, CartMixin, TicketPageMixin, TemplateView):
|
||||
template_name = "pretixpresale/event/order.html"
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
@@ -105,13 +158,6 @@ class OrderDetails(EventViewMixin, OrderDetailMixin, CartMixin, TemplateView):
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
ctx['order'] = self.order
|
||||
|
||||
can_download = all([r for rr, r in allow_ticket_download.send(self.request.event, order=self.order)])
|
||||
if self.request.event.settings.ticket_download_date:
|
||||
ctx['ticket_download_date'] = self.order.ticket_download_date
|
||||
ctx['can_download'] = can_download and self.order.ticket_download_available and self.order.positions_with_tickets
|
||||
ctx['download_buttons'] = self.download_buttons
|
||||
ctx['cart'] = self.get_cart(
|
||||
answers=True, downloads=ctx['can_download'],
|
||||
queryset=self.order.positions.select_related('tax_rule'),
|
||||
@@ -142,11 +188,6 @@ class OrderDetails(EventViewMixin, OrderDetailMixin, CartMixin, TemplateView):
|
||||
self.order.total != Decimal('0.00') or not self.request.event.settings.invoice_address_not_asked_free
|
||||
)
|
||||
|
||||
ctx['backend_user'] = (
|
||||
self.request.user.is_authenticated
|
||||
and self.request.user.has_event_permission(self.request.organizer, self.request.event, 'can_view_orders', request=self.request)
|
||||
)
|
||||
|
||||
if self.order.status == Order.STATUS_PENDING:
|
||||
ctx['pending_sum'] = self.order.pending_sum
|
||||
|
||||
@@ -179,6 +220,47 @@ class OrderDetails(EventViewMixin, OrderDetailMixin, CartMixin, TemplateView):
|
||||
return ctx
|
||||
|
||||
|
||||
@method_decorator(xframe_options_exempt, 'dispatch')
|
||||
class OrderPositionDetails(EventViewMixin, OrderPositionDetailMixin, CartMixin, TicketPageMixin, TemplateView):
|
||||
template_name = "pretixpresale/event/position.html"
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
self.kwargs = kwargs
|
||||
if not self.position:
|
||||
raise Http404(_('Unknown order code or not authorized to access this order.'))
|
||||
return super().get(request, *args, **kwargs)
|
||||
|
||||
@cached_property
|
||||
def download_buttons(self):
|
||||
buttons = []
|
||||
|
||||
responses = register_ticket_outputs.send(self.request.event)
|
||||
for receiver, response in responses:
|
||||
provider = response(self.request.event)
|
||||
if not provider.is_enabled:
|
||||
continue
|
||||
buttons.append({
|
||||
'text': provider.download_button_text or 'Download',
|
||||
'icon': provider.download_button_icon or 'fa-download',
|
||||
'identifier': provider.identifier,
|
||||
'multi': provider.multi_download_enabled
|
||||
})
|
||||
return buttons
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
ctx['can_download_multi'] = False
|
||||
ctx['position'] = self.position
|
||||
ctx['cart'] = self.get_cart(
|
||||
answers=True, downloads=ctx['can_download'],
|
||||
queryset=self.order.positions.select_related('tax_rule').filter(
|
||||
Q(pk=self.position.pk) | Q(addon_to__id=self.position.pk)
|
||||
),
|
||||
order=self.order
|
||||
)
|
||||
return ctx
|
||||
|
||||
|
||||
@method_decorator(xframe_options_exempt, 'dispatch')
|
||||
class OrderPaymentStart(EventViewMixin, OrderDetailMixin, TemplateView):
|
||||
"""
|
||||
@@ -669,22 +751,10 @@ class AnswerDownload(EventViewMixin, OrderDetailMixin, View):
|
||||
return resp
|
||||
|
||||
|
||||
@method_decorator(xframe_options_exempt, 'dispatch')
|
||||
class OrderDownload(EventViewMixin, OrderDetailMixin, AsyncAction, View):
|
||||
task = generate
|
||||
|
||||
class OrderDownloadMixin:
|
||||
def get_success_url(self, value):
|
||||
return self.get_self_url()
|
||||
|
||||
def get_error_url(self):
|
||||
return self.get_order_url()
|
||||
|
||||
def get_self_url(self):
|
||||
return eventreverse(self.request.event,
|
||||
'presale:event.order.download' if 'position' in self.kwargs
|
||||
else 'presale:event.order.download.combined',
|
||||
kwargs=self.kwargs)
|
||||
|
||||
@cached_property
|
||||
def output(self):
|
||||
if not all([r for rr, r in allow_ticket_download.send(self.request.event, order=self.order)]):
|
||||
@@ -695,13 +765,6 @@ class OrderDownload(EventViewMixin, OrderDetailMixin, AsyncAction, View):
|
||||
if provider.identifier == self.kwargs.get('output'):
|
||||
return provider
|
||||
|
||||
@cached_property
|
||||
def order_position(self):
|
||||
try:
|
||||
return self.order.positions.get(pk=self.kwargs.get('position'))
|
||||
except OrderPosition.DoesNotExist:
|
||||
return None
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
if not self.output or not self.output.is_enabled:
|
||||
return self.error(_('You requested an invalid ticket output type.'))
|
||||
@@ -770,6 +833,51 @@ class OrderDownload(EventViewMixin, OrderDetailMixin, AsyncAction, View):
|
||||
return ct
|
||||
|
||||
|
||||
@method_decorator(xframe_options_exempt, 'dispatch')
|
||||
class OrderDownload(OrderDownloadMixin, EventViewMixin, OrderDetailMixin, AsyncAction, View):
|
||||
task = generate
|
||||
|
||||
def get_error_url(self):
|
||||
return self.get_order_url()
|
||||
|
||||
def get_self_url(self):
|
||||
return eventreverse(self.request.event,
|
||||
'presale:event.order.download' if 'position' in self.kwargs
|
||||
else 'presale:event.order.download.combined',
|
||||
kwargs=self.kwargs)
|
||||
|
||||
@cached_property
|
||||
def order_position(self):
|
||||
try:
|
||||
return self.order.positions.get(pk=self.kwargs.get('position'))
|
||||
except OrderPosition.DoesNotExist:
|
||||
return None
|
||||
|
||||
|
||||
@method_decorator(xframe_options_exempt, 'dispatch')
|
||||
class OrderPositionDownload(OrderDownloadMixin, EventViewMixin, OrderPositionDetailMixin, AsyncAction, View):
|
||||
task = generate
|
||||
|
||||
def get_error_url(self):
|
||||
return self.get_position_url()
|
||||
|
||||
def get_self_url(self):
|
||||
return eventreverse(self.request.event,
|
||||
'presale:event.order.position.download',
|
||||
kwargs=self.kwargs)
|
||||
|
||||
@cached_property
|
||||
def order_position(self):
|
||||
try:
|
||||
return self.order.positions.get(
|
||||
Q(pk=self.kwargs.get('pid')) & Q(
|
||||
Q(pk=self.position.pk) | Q(addon_to__id=self.position.pk)
|
||||
)
|
||||
)
|
||||
except OrderPosition.DoesNotExist:
|
||||
return None
|
||||
|
||||
|
||||
@method_decorator(xframe_options_exempt, 'dispatch')
|
||||
class InvoiceDownload(EventViewMixin, OrderDetailMixin, View):
|
||||
|
||||
|
||||
Reference in New Issue
Block a user