From 83c297c0a835ecfd8317227cf351e2a56b7b21b6 Mon Sep 17 00:00:00 2001 From: Raphael Michel Date: Thu, 20 Sep 2018 21:11:12 +0200 Subject: [PATCH] Allow to transfer tickets --- src/pretix/base/models/orders.py | 43 +++++++++++++++++++ src/pretix/control/views/orders.py | 37 +--------------- src/pretix/presale/forms/order.py | 33 ++++++++++++++ .../templates/pretixpresale/event/order.html | 21 ++++++--- .../pretixpresale/event/order_transfer.html | 32 ++++++++++++++ src/pretix/presale/urls.py | 3 ++ src/pretix/presale/views/order.py | 40 ++++++++++++++++- 7 files changed, 166 insertions(+), 43 deletions(-) create mode 100644 src/pretix/presale/forms/order.py create mode 100644 src/pretix/presale/templates/pretixpresale/event/order_transfer.html diff --git a/src/pretix/base/models/orders.py b/src/pretix/base/models/orders.py index d1348c67d..091616f13 100644 --- a/src/pretix/base/models/orders.py +++ b/src/pretix/base/models/orders.py @@ -10,6 +10,7 @@ from typing import Any, Dict, List, Union import dateutil import pytz from django.conf import settings +from django.contrib import messages from django.db import models, transaction from django.db.models import ( Case, Exists, F, Max, OuterRef, Q, Subquery, Sum, Value, When, @@ -17,6 +18,7 @@ from django.db.models import ( from django.db.models.functions import Coalesce from django.db.models.signals import post_delete from django.dispatch import receiver +from django.shortcuts import redirect from django.urls import reverse from django.utils.crypto import get_random_string from django.utils.encoding import escape_uri_path @@ -460,6 +462,47 @@ class Order(LoggedModel): return self._is_still_available(count_waitinglist=count_waitinglist) + def regenerate_secrets(self, user=None): + self.secret = generate_secret() + for op in self.positions.all(): + op.secret = generate_position_secret() + op.save(update_fields=['secret']) + CachedTicket.objects.filter(order_position__order=self).delete() + CachedCombinedTicket.objects.filter(order=self).delete() + self.log_action('pretix.event.order.secret.changed', user=user) + self.save(update_fields=['secret']) + + def resend_link(self, user=None): + from pretix.base.services.mail import SendMailException + from pretix.multidomain.urlreverse import build_absolute_uri + + with language(self.locale): + try: + try: + invoice_name = self.invoice_address.name + invoice_company = self.invoice_address.company + except InvoiceAddress.DoesNotExist: + invoice_name = "" + invoice_company = "" + email_template = self.event.settings.mail_text_resend_link + email_context = { + 'event': self.event.name, + 'url': build_absolute_uri(self.event, 'presale:event.order', kwargs={ + 'order': self.code, + 'secret': self.secret + }), + 'invoice_name': invoice_name, + 'invoice_company': invoice_company, + } + email_subject = _('Your order: %(code)s') % {'code': self.code} + self.send_mail( + email_subject, email_template, email_context, + 'pretix.event.order.email.resend', user=user + ) + except SendMailException: + messages.error(self.request, _('There was an error sending the mail. Please try again later.')) + return redirect(self.get_order_url()) + def _is_still_available(self, now_dt: datetime=None, count_waitinglist=True) -> Union[bool, str]: error_messages = { 'unavailable': _('The ordered product "{item}" is no longer available.'), diff --git a/src/pretix/control/views/orders.py b/src/pretix/control/views/orders.py index e7d980093..f03c66a0e 100644 --- a/src/pretix/control/views/orders.py +++ b/src/pretix/control/views/orders.py @@ -30,7 +30,6 @@ from pretix.base.i18n import language from pretix.base.models import ( CachedCombinedTicket, CachedFile, CachedTicket, Invoice, InvoiceAddress, Item, ItemVariation, LogEntry, Order, QuestionAnswer, Quota, - generate_position_secret, generate_secret, ) from pretix.base.models.event import SubEvent from pretix.base.models.orders import OrderFee, OrderPayment, OrderRefund @@ -845,33 +844,7 @@ class OrderResendLink(OrderView): permission = 'can_change_orders' def post(self, *args, **kwargs): - with language(self.order.locale): - try: - try: - invoice_name = self.order.invoice_address.name - invoice_company = self.order.invoice_address.company - except InvoiceAddress.DoesNotExist: - invoice_name = "" - invoice_company = "" - email_template = self.order.event.settings.mail_text_resend_link - email_context = { - 'event': self.order.event.name, - 'url': build_absolute_uri(self.order.event, 'presale:event.order', kwargs={ - 'order': self.order.code, - 'secret': self.order.secret - }), - 'invoice_name': invoice_name, - 'invoice_company': invoice_company, - } - email_subject = _('Your order: %(code)s') % {'code': self.order.code} - self.order.send_mail( - email_subject, email_template, email_context, - 'pretix.event.order.email.resend', user=self.request.user - ) - except SendMailException: - messages.error(self.request, _('There was an error sending the mail. Please try again later.')) - return redirect(self.get_order_url()) - + self.order.resend_link(self.request.user) messages.success(self.request, _('The email has been queued to be sent.')) return redirect(self.get_order_url()) @@ -1166,13 +1139,7 @@ class OrderContactChange(OrderView): ) if self.form.cleaned_data['regenerate_secrets']: changed = True - self.order.secret = generate_secret() - for op in self.order.positions.all(): - op.secret = generate_position_secret() - op.save() - CachedTicket.objects.filter(order_position__order=self.order).delete() - CachedCombinedTicket.objects.filter(order=self.order).delete() - self.order.log_action('pretix.event.order.secret.changed', user=self.request.user) + self.order.regenerate_secrets(self.request.user) self.form.save() if changed: diff --git a/src/pretix/presale/forms/order.py b/src/pretix/presale/forms/order.py new file mode 100644 index 000000000..4c14d870c --- /dev/null +++ b/src/pretix/presale/forms/order.py @@ -0,0 +1,33 @@ +from django import forms +from django.core.exceptions import ValidationError +from django.utils.translation import ugettext_lazy as _ + +from pretix.base.validators import EmailBlacklistValidator + + +class ChangeContactForm(forms.Form): + email = forms.EmailField(label=_('E-mail'), + help_text=_('Make sure to enter a valid email address. We will send an email containing ' + 'the new link to the ticket there.'), + validators=[EmailBlacklistValidator()]) + email_repeat = forms.EmailField( + label=_('E-mail address (repeated)'), + help_text=_('Please enter the same email address again to make sure you typed it correctly.') + ) + check_noaccess = forms.BooleanField( + label=_('I have understood that after this operation, I will no longer have access to these tickets. The link ' + 'of the ticket order will be changed and the new link will be sent to the given email address.') + ) + check_printed = forms.BooleanField( + label=_('I have understood that after this operation, all printed or downloaded tickets from this order will ' + 'be invalid and need to be downloaded again.') + ) + check_data = forms.BooleanField( + label=_('I have understood that after this operation, the new owner will have access to all personal data ' + 'included in my ticket order, such as information given for the tickets, my invoicing address, or ' + 'previous invoices.') + ) + + def clean(self): + if self.cleaned_data.get('email').lower() != self.cleaned_data.get('email_repeat').lower(): + raise ValidationError(_('Please enter the same email address twice.')) diff --git a/src/pretix/presale/templates/pretixpresale/event/order.html b/src/pretix/presale/templates/pretixpresale/event/order.html index 914f812ff..747eb8f1a 100644 --- a/src/pretix/presale/templates/pretixpresale/event/order.html +++ b/src/pretix/presale/templates/pretixpresale/event/order.html @@ -223,17 +223,24 @@ {% endif %}
- {% if order.can_user_cancel %} -
-
-

+

+
+

+ {% if order.status == "p" or order.status == "n" or order.status == "e" %} + + + {% trans "Transfer order" %} + + {% endif %} + {% if order.can_user_cancel %} {% trans "Cancel order" %} -

-
+ {% endif %} +

- {% endif %} +
{% endblock %} diff --git a/src/pretix/presale/templates/pretixpresale/event/order_transfer.html b/src/pretix/presale/templates/pretixpresale/event/order_transfer.html new file mode 100644 index 000000000..41406aace --- /dev/null +++ b/src/pretix/presale/templates/pretixpresale/event/order_transfer.html @@ -0,0 +1,32 @@ +{% extends "pretixpresale/event/base.html" %} +{% load i18n %} +{% load eventurl %} +{% load bootstrap3 %} +{% block title %}{% trans "Transfer order" %}{% endblock %} +{% block content %} +

+ {% blocktrans trimmed with code=order.code %} + Transfer order: {{ code }} + {% endblocktrans %} +

+ +
+ {% csrf_token %} + {% bootstrap_form form layout='horizontal' %} +
+ +
+ +
+
+
+
+ +{% endblock %} diff --git a/src/pretix/presale/urls.py b/src/pretix/presale/urls.py index a9156a2e1..4e77deed2 100644 --- a/src/pretix/presale/urls.py +++ b/src/pretix/presale/urls.py @@ -59,6 +59,9 @@ event_patterns = [ url(r'^order/(?P[^/]+)/(?P[A-Za-z0-9]+)/cancel/do$', pretix.presale.views.order.OrderCancelDo.as_view(), name='event.order.cancel.do'), + url(r'^order/(?P[^/]+)/(?P[A-Za-z0-9]+)/transfer$', + pretix.presale.views.order.OrderTransfer.as_view(), + name='event.order.transfer'), url(r'^order/(?P[^/]+)/(?P[A-Za-z0-9]+)/modify$', pretix.presale.views.order.OrderModify.as_view(), name='event.order.modify'), diff --git a/src/pretix/presale/views/order.py b/src/pretix/presale/views/order.py index 4a27266ce..4cf10085b 100644 --- a/src/pretix/presale/views/order.py +++ b/src/pretix/presale/views/order.py @@ -13,7 +13,7 @@ from django.utils.functional import cached_property from django.utils.timezone import now from django.utils.translation import ugettext_lazy as _ from django.views.decorators.clickjacking import xframe_options_exempt -from django.views.generic import TemplateView, View +from django.views.generic import FormView, TemplateView, View from pretix.base.models import CachedTicket, Invoice, Order, OrderPosition from pretix.base.models.orders import ( @@ -34,6 +34,7 @@ from pretix.base.views.tasks import AsyncAction from pretix.helpers.safedownload import check_token from pretix.multidomain.urlreverse import build_absolute_uri, eventreverse from pretix.presale.forms.checkout import InvoiceAddressForm, QuestionsForm +from pretix.presale.forms.order import ChangeContactForm from pretix.presale.views import CartMixin, EventViewMixin from pretix.presale.views.robots import NoSearchIndexViewMixin @@ -773,3 +774,40 @@ class InvoiceDownload(EventViewMixin, OrderDetailMixin, View): return self.get(request, *args, **kwargs) resp['Content-Disposition'] = 'attachment; filename="{}.pdf"'.format(invoice.number) return resp + + +@method_decorator(xframe_options_exempt, 'dispatch') +class OrderTransfer(EventViewMixin, OrderDetailMixin, FormView): + template_name = "pretixpresale/event/order_transfer.html" + form_class = ChangeContactForm + + def dispatch(self, request, *args, **kwargs): + self.request = request + self.kwargs = kwargs + if not self.order: + raise Http404(_('Unknown order code or not authorized to access this order.')) + return super().dispatch(request, *args, **kwargs) + + def get_context_data(self, **kwargs): + ctx = super().get_context_data(**kwargs) + ctx['order'] = self.order + return ctx + + def form_valid(self, form): + with transaction.atomic(): + self.order.email = form.cleaned_data['email'] + self.order.log_action( + 'pretix.event.order.contact.changed', + data={ + 'old_email': self.order.email, + 'new_email': form.cleaned_data['email'], + }, + user=self.request.user, + ) + self.order.regenerate_secrets() + self.order.resend_link() + messages.success(self.request, _('The ticket order has been transfered and an email will be sent to the ' + 'new owner.')) + return redirect( + eventreverse(self.request.event, 'presale:event.index') + )