Compare commits

...

9 Commits

Author SHA1 Message Date
Raphael Michel
83c297c0a8 Allow to transfer tickets 2018-09-20 21:11:12 +02:00
Raphael Michel
60d1f02d26 Remove deprecated template part 2018-09-20 20:25:23 +02:00
Raphael Michel
b384f71b64 Fail silently if attachment could not be found 2018-09-13 12:58:08 +02:00
Raphael Michel
10dd5278e7 Fix bug in previous commit 2018-09-13 12:32:07 +02:00
Raphael Michel
befa6527e4 Attach invoice to order approval email 2018-09-13 12:19:30 +02:00
Raphael Michel
00497630cb Merge pull request #1015 from thegcat/patch-1
Correct typo
2018-09-12 08:38:56 +02:00
Felix Schäfer
95cd457de1 Correct typo
The Header is `Content-Type` not `Content`.
2018-09-11 22:11:48 +02:00
Raphael Michel
7518c9e3e0 Do not install python34.txt on release CI 2018-09-11 18:24:00 +02:00
Raphael Michel
6a999835e2 Bump version 2018-09-11 18:23:10 +02:00
12 changed files with 181 additions and 65 deletions

View File

@@ -17,7 +17,7 @@ pypi:
- virtualenv env - virtualenv env
- source env/bin/activate - source env/bin/activate
- pip install -U pip wheel setuptools - pip install -U pip wheel setuptools
- XDG_CACHE_HOME=/cache pip3 install -Ur src/requirements.txt -r src/requirements/dev.txt -r src/requirements/py34.txt - XDG_CACHE_HOME=/cache pip3 install -Ur src/requirements.txt -r src/requirements/dev.txt
- cd src - cd src
- python setup.py sdist - python setup.py sdist
- pip install dist/pretix-*.tar.gz - pip install dist/pretix-*.tar.gz

View File

@@ -128,7 +128,7 @@ Endpoints
POST /api/v1/organizers/bigevents/events/sampleconf/questions/1/options/ HTTP/1.1 POST /api/v1/organizers/bigevents/events/sampleconf/questions/1/options/ HTTP/1.1
Host: pretix.eu Host: pretix.eu
Accept: application/json, text/javascript Accept: application/json, text/javascript
Content: application/json Content-Type: application/json
{ {
"identifier": "LVETRWVU", "identifier": "LVETRWVU",

View File

@@ -1 +1 @@
__version__ = "2.0.0" __version__ = "2.1.0.dev0"

View File

@@ -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.'),

View File

@@ -176,11 +176,15 @@ def mail_send_task(*args, to: List[str], subject: str, body: str, html: str, sen
invoices = Invoice.objects.filter(pk__in=invoices) invoices = Invoice.objects.filter(pk__in=invoices)
for inv in invoices: for inv in invoices:
if inv.file: if inv.file:
email.attach( try:
'{}.pdf'.format(inv.number), email.attach(
inv.file.file.read(), '{}.pdf'.format(inv.number),
'application/pdf' inv.file.file.read(),
) 'application/pdf'
)
except:
logger.exception('Could not attach invoice to email')
pass
if event: if event:
event = Event.objects.get(id=event) event = Event.objects.get(id=event)
backend = event.get_mail_backend() backend = event.get_mail_backend()

View File

@@ -199,7 +199,7 @@ def approve_order(order, user=None, send_mail: bool=True, auth=None):
invoice = order.invoices.last() # Might be generated by plugin already invoice = order.invoices.last() # Might be generated by plugin already
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:
generate_invoice( invoice = generate_invoice(
order, order,
trigger_pdf=not order.event.settings.invoice_email_attachment or not order.email trigger_pdf=not order.event.settings.invoice_email_attachment or not order.email
) )
@@ -237,7 +237,8 @@ def approve_order(order, user=None, send_mail: bool=True, auth=None):
try: try:
order.send_mail( order.send_mail(
email_subject, email_template, email_context, email_subject, email_template, email_context,
'pretix.event.order.email.order_approved', user 'pretix.event.order.email.order_approved', user,
invoices=[invoice] if invoice and order.event.settings.invoice_email_attachment else []
) )
except SendMailException: except SendMailException:
logger.exception('Order approved email could not be sent') logger.exception('Order approved email could not be sent')

View File

@@ -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:

View 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.'))

View File

@@ -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 %}

View File

@@ -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 %}

View File

@@ -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'),

View File

@@ -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')
)