mirror of
https://github.com/pretix/pretix.git
synced 2025-12-11 01:22:28 +00:00
Compare commits
2 Commits
mapping-js
...
transfer-t
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
83c297c0a8 | ||
|
|
60d1f02d26 |
@@ -10,6 +10,7 @@ from typing import Any, Dict, List, Union
|
|||||||
import dateutil
|
import dateutil
|
||||||
import pytz
|
import pytz
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.contrib import messages
|
||||||
from django.db import models, transaction
|
from django.db import models, transaction
|
||||||
from django.db.models import (
|
from django.db.models import (
|
||||||
Case, Exists, F, Max, OuterRef, Q, Subquery, Sum, Value, When,
|
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.functions import Coalesce
|
||||||
from django.db.models.signals import post_delete
|
from django.db.models.signals import post_delete
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
|
from django.shortcuts import redirect
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.crypto import get_random_string
|
from django.utils.crypto import get_random_string
|
||||||
from django.utils.encoding import escape_uri_path
|
from django.utils.encoding import escape_uri_path
|
||||||
@@ -460,6 +462,47 @@ class Order(LoggedModel):
|
|||||||
|
|
||||||
return self._is_still_available(count_waitinglist=count_waitinglist)
|
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]:
|
def _is_still_available(self, now_dt: datetime=None, count_waitinglist=True) -> Union[bool, str]:
|
||||||
error_messages = {
|
error_messages = {
|
||||||
'unavailable': _('The ordered product "{item}" is no longer available.'),
|
'unavailable': _('The ordered product "{item}" is no longer available.'),
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ from pretix.base.i18n import language
|
|||||||
from pretix.base.models import (
|
from pretix.base.models import (
|
||||||
CachedCombinedTicket, CachedFile, CachedTicket, Invoice, InvoiceAddress,
|
CachedCombinedTicket, CachedFile, CachedTicket, Invoice, InvoiceAddress,
|
||||||
Item, ItemVariation, LogEntry, Order, QuestionAnswer, Quota,
|
Item, ItemVariation, LogEntry, Order, QuestionAnswer, Quota,
|
||||||
generate_position_secret, generate_secret,
|
|
||||||
)
|
)
|
||||||
from pretix.base.models.event import SubEvent
|
from pretix.base.models.event import SubEvent
|
||||||
from pretix.base.models.orders import OrderFee, OrderPayment, OrderRefund
|
from pretix.base.models.orders import OrderFee, OrderPayment, OrderRefund
|
||||||
@@ -845,33 +844,7 @@ class OrderResendLink(OrderView):
|
|||||||
permission = 'can_change_orders'
|
permission = 'can_change_orders'
|
||||||
|
|
||||||
def post(self, *args, **kwargs):
|
def post(self, *args, **kwargs):
|
||||||
with language(self.order.locale):
|
self.order.resend_link(self.request.user)
|
||||||
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())
|
|
||||||
|
|
||||||
messages.success(self.request, _('The email has been queued to be sent.'))
|
messages.success(self.request, _('The email has been queued to be sent.'))
|
||||||
return redirect(self.get_order_url())
|
return redirect(self.get_order_url())
|
||||||
|
|
||||||
@@ -1166,13 +1139,7 @@ class OrderContactChange(OrderView):
|
|||||||
)
|
)
|
||||||
if self.form.cleaned_data['regenerate_secrets']:
|
if self.form.cleaned_data['regenerate_secrets']:
|
||||||
changed = True
|
changed = True
|
||||||
self.order.secret = generate_secret()
|
self.order.regenerate_secrets(self.request.user)
|
||||||
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.form.save()
|
self.form.save()
|
||||||
if changed:
|
if changed:
|
||||||
|
|||||||
33
src/pretix/presale/forms/order.py
Normal file
33
src/pretix/presale/forms/order.py
Normal file
@@ -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.'))
|
||||||
@@ -223,29 +223,24 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="clearfix"></div>
|
<div class="clearfix"></div>
|
||||||
</div>
|
</div>
|
||||||
{% if order.can_user_cancel %}
|
<div class="row">
|
||||||
<div class="row">
|
<div class="col-md-12 text-right">
|
||||||
<div class="col-md-12 text-right">
|
<p>
|
||||||
<p>
|
{% if order.status == "p" or order.status == "n" or order.status == "e" %}
|
||||||
|
<a href="{% eventurl event 'presale:event.order.transfer' secret=order.secret order=order.code %}"
|
||||||
|
class="btn btn-default">
|
||||||
|
<span class="fa fa-mail-forward"></span>
|
||||||
|
{% trans "Transfer order" %}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
{% if order.can_user_cancel %}
|
||||||
<a href="{% eventurl event 'presale:event.order.cancel' secret=order.secret order=order.code %}"
|
<a href="{% eventurl event 'presale:event.order.cancel' secret=order.secret order=order.code %}"
|
||||||
class="btn btn-danger">
|
class="btn btn-danger">
|
||||||
<span class="fa fa-remove"></span>
|
<span class="fa fa-remove"></span>
|
||||||
{% trans "Cancel order" %}
|
{% trans "Cancel order" %}
|
||||||
</a>
|
</a>
|
||||||
</p>
|
{% endif %}
|
||||||
</div>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
</div>
|
||||||
{% if order.status == "p" and payment %}
|
|
||||||
<div class="panel panel-success">
|
|
||||||
<div class="panel-heading">
|
|
||||||
<h3 class="panel-title">
|
|
||||||
{% trans "Payment" %}
|
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
<div class="panel-body">
|
|
||||||
{{ payment }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
{% extends "pretixpresale/event/base.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load eventurl %}
|
||||||
|
{% load bootstrap3 %}
|
||||||
|
{% block title %}{% trans "Transfer order" %}{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
<h2>
|
||||||
|
{% blocktrans trimmed with code=order.code %}
|
||||||
|
Transfer order: {{ code }}
|
||||||
|
{% endblocktrans %}
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<form method="post" action="" class="form-horizontal">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% bootstrap_form form layout='horizontal' %}
|
||||||
|
<div class="row checkout-button-row">
|
||||||
|
<div class="col-md-4">
|
||||||
|
<a class="btn btn-block btn-default btn-lg"
|
||||||
|
href="{% eventurl request.event "presale:event.order" secret=order.secret order=order.code %}">
|
||||||
|
{% trans "No, take me back" %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 col-md-offset-4">
|
||||||
|
<button class="btn btn-block btn-danger btn-lg" type="submit">
|
||||||
|
{% trans "Yes, transfer order" %}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="clearfix"></div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
@@ -59,6 +59,9 @@ event_patterns = [
|
|||||||
url(r'^order/(?P<order>[^/]+)/(?P<secret>[A-Za-z0-9]+)/cancel/do$',
|
url(r'^order/(?P<order>[^/]+)/(?P<secret>[A-Za-z0-9]+)/cancel/do$',
|
||||||
pretix.presale.views.order.OrderCancelDo.as_view(),
|
pretix.presale.views.order.OrderCancelDo.as_view(),
|
||||||
name='event.order.cancel.do'),
|
name='event.order.cancel.do'),
|
||||||
|
url(r'^order/(?P<order>[^/]+)/(?P<secret>[A-Za-z0-9]+)/transfer$',
|
||||||
|
pretix.presale.views.order.OrderTransfer.as_view(),
|
||||||
|
name='event.order.transfer'),
|
||||||
url(r'^order/(?P<order>[^/]+)/(?P<secret>[A-Za-z0-9]+)/modify$',
|
url(r'^order/(?P<order>[^/]+)/(?P<secret>[A-Za-z0-9]+)/modify$',
|
||||||
pretix.presale.views.order.OrderModify.as_view(),
|
pretix.presale.views.order.OrderModify.as_view(),
|
||||||
name='event.order.modify'),
|
name='event.order.modify'),
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ from django.utils.functional import cached_property
|
|||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.views.decorators.clickjacking import xframe_options_exempt
|
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 import CachedTicket, Invoice, Order, OrderPosition
|
||||||
from pretix.base.models.orders import (
|
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.helpers.safedownload import check_token
|
||||||
from pretix.multidomain.urlreverse import build_absolute_uri, eventreverse
|
from pretix.multidomain.urlreverse import build_absolute_uri, eventreverse
|
||||||
from pretix.presale.forms.checkout import InvoiceAddressForm, QuestionsForm
|
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 import CartMixin, EventViewMixin
|
||||||
from pretix.presale.views.robots import NoSearchIndexViewMixin
|
from pretix.presale.views.robots import NoSearchIndexViewMixin
|
||||||
|
|
||||||
@@ -773,3 +774,40 @@ class InvoiceDownload(EventViewMixin, OrderDetailMixin, View):
|
|||||||
return self.get(request, *args, **kwargs)
|
return self.get(request, *args, **kwargs)
|
||||||
resp['Content-Disposition'] = 'attachment; filename="{}.pdf"'.format(invoice.number)
|
resp['Content-Disposition'] = 'attachment; filename="{}.pdf"'.format(invoice.number)
|
||||||
return resp
|
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')
|
||||||
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user