Compare commits

..

6 Commits

Author SHA1 Message Date
Raphael Michel
8c0214f334 Fix sqlparse version 2019-04-07 14:09:21 +02:00
Raphael Michel
3b4261884e Deal with deprecation warnings 2019-04-07 14:09:21 +02:00
Raphael Michel
68033e5d7d Resolve naive datetime warnings in test suite 2019-04-07 14:09:21 +02:00
Raphael Michel
f14c12546d Provide explicit orderings to all models used in paginated queries 2019-04-07 14:09:21 +02:00
Raphael Michel
289878689b Update to Django 2.2 and recent versions of similar packages 2019-04-07 14:09:21 +02:00
Raphael Michel
80a2e80b1c Upgrade django and stuff 2019-04-07 14:09:21 +02:00
102 changed files with 11297 additions and 12383 deletions

View File

@@ -18,18 +18,12 @@ default_price money (string) The price set d
price money (string) The price used for this variation. This is either the
same as ``default_price`` if that value is set or equal
to the item's ``default_price`` (read-only).
original_price money (string) An original price, shown for comparison, not used
for price calculations (or ``null``).
active boolean If ``false``, this variation will not be sold or shown.
description multi-lingual string A public description of the variation. May contain
Markdown syntax or can be ``null``.
position integer An integer, used for sorting
===================================== ========================== =======================================================
.. versionchanged:: 2.7
The attribute ``original_price`` has been added.
.. versionchanged:: 1.12
This resource has been added.
@@ -73,8 +67,7 @@ Endpoints
},
"position": 0,
"default_price": "223.00",
"price": 223.0,
"original_price": null,
"price": 223.0
},
{
"id": 3,
@@ -127,7 +120,6 @@ Endpoints
},
"default_price": "10.00",
"price": "10.00",
"original_price": null,
"active": true,
"description": null,
"position": 0
@@ -175,7 +167,6 @@ Endpoints
"value": {"en": "Student"},
"default_price": "10.00",
"price": "10.00",
"original_price": null,
"active": true,
"description": null,
"position": 0
@@ -225,7 +216,6 @@ Endpoints
"value": {"en": "Student"},
"default_price": "10.00",
"price": "10.00",
"original_price": null,
"active": false,
"description": null,
"position": 1

View File

@@ -29,8 +29,7 @@ free_price boolean If ``true``, cu
they buy the product (however, the price can't be set
lower than the price defined by ``default_price`` or
otherwise).
tax_rate decimal (string) The VAT rate to be applied for this item (read-only,
set through ``tax_rule``).
tax_rate decimal (string) The VAT rate to be applied for this item.
tax_rule integer The internal ID of the applied tax rule (or ``null``).
admission boolean ``true`` for items that grant admission to the event
(such as primary tickets) and ``false`` for others
@@ -82,8 +81,6 @@ variations list of objects A list with one
├ price money (string) The price used for this variation. This is either the
same as ``default_price`` if that value is set or equal
to the item's ``default_price``.
├ original_price money (string) An original price, shown for comparison, not used
for price calculations (or ``null``).
├ active boolean If ``false``, this variation will not be sold or shown.
├ description multi-lingual string A public description of the variation. May contain
Markdown syntax or can be ``null``.
@@ -108,10 +105,6 @@ bundles list of objects Definition of b
taxation. This is not added to the price.
===================================== ========================== =======================================================
.. versionchanged:: 2.7
The attribute ``original_price`` has been added for ``variations``.
.. versionchanged:: 1.7
The attribute ``tax_rule`` has been added. ``tax_rate`` is kept for compatibility. The attribute
@@ -214,7 +207,6 @@ Endpoints
"value": {"en": "Student"},
"default_price": "10.00",
"price": "10.00",
"original_price": null,
"active": true,
"description": null,
"position": 0
@@ -223,7 +215,6 @@ Endpoints
"value": {"en": "Regular"},
"default_price": null,
"price": "23.00",
"original_price": null,
"active": true,
"description": null,
"position": 1
@@ -305,7 +296,6 @@ Endpoints
"value": {"en": "Student"},
"default_price": "10.00",
"price": "10.00",
"original_price": null,
"active": true,
"description": null,
"position": 0
@@ -314,7 +304,6 @@ Endpoints
"value": {"en": "Regular"},
"default_price": null,
"price": "23.00",
"original_price": null,
"active": true,
"description": null,
"position": 1
@@ -376,7 +365,6 @@ Endpoints
"value": {"en": "Student"},
"default_price": "10.00",
"price": "10.00",
"original_price": null,
"active": true,
"description": null,
"position": 0
@@ -385,7 +373,6 @@ Endpoints
"value": {"en": "Regular"},
"default_price": null,
"price": "23.00",
"original_price": null,
"active": true,
"description": null,
"position": 1
@@ -436,7 +423,6 @@ Endpoints
"value": {"en": "Student"},
"default_price": "10.00",
"price": "10.00",
"original_price": null,
"active": true,
"description": null,
"position": 0
@@ -445,7 +431,6 @@ Endpoints
"value": {"en": "Regular"},
"default_price": null,
"price": "23.00",
"original_price": null,
"active": true,
"description": null,
"position": 1
@@ -527,7 +512,6 @@ Endpoints
"value": {"en": "Student"},
"default_price": "10.00",
"price": "10.00",
"original_price": null,
"active": true,
"description": null,
"position": 0
@@ -536,7 +520,6 @@ Endpoints
"value": {"en": "Regular"},
"default_price": null,
"price": "23.00",
"original_price": null,
"active": true,
"description": null,
"position": 1

View File

@@ -212,8 +212,8 @@ class SubEventSerializer(I18nAwareModelSerializer):
Event.clean_dates(data.get('date_from'), data.get('date_to'))
Event.clean_presale(data.get('presale_start'), data.get('presale_end'))
SubEvent.clean_items(event, [item['item'] for item in full_data.get('subeventitem_set', [])])
SubEvent.clean_variations(event, [item['variation'] for item in full_data.get('subeventitemvariation_set', [])])
SubEvent.clean_items(event, [item['item'] for item in full_data.get('subeventitem_set')])
SubEvent.clean_variations(event, [item['variation'] for item in full_data.get('subeventitemvariation_set')])
return data
def validate_item_price_overrides(self, data):

View File

@@ -19,7 +19,7 @@ class InlineItemVariationSerializer(I18nAwareModelSerializer):
class Meta:
model = ItemVariation
fields = ('id', 'value', 'active', 'description',
'position', 'default_price', 'price', 'original_price')
'position', 'default_price', 'price')
class ItemVariationSerializer(I18nAwareModelSerializer):
@@ -29,7 +29,7 @@ class ItemVariationSerializer(I18nAwareModelSerializer):
class Meta:
model = ItemVariation
fields = ('id', 'value', 'active', 'description',
'position', 'default_price', 'price', 'original_price')
'position', 'default_price', 'price')
class InlineItemBundleSerializer(serializers.ModelSerializer):

View File

@@ -14,8 +14,8 @@ from pretix.api.serializers.i18n import I18nAwareModelSerializer
from pretix.base.channels import get_all_sales_channels
from pretix.base.i18n import language
from pretix.base.models import (
Checkin, Invoice, InvoiceAddress, InvoiceLine, Item, ItemVariation, Order,
OrderPosition, Question, QuestionAnswer, SubEvent,
Checkin, Invoice, InvoiceAddress, InvoiceLine, Order, OrderPosition,
Question, QuestionAnswer,
)
from pretix.base.models.orders import (
CartPosition, OrderFee, OrderPayment, OrderRefund,
@@ -288,23 +288,6 @@ class OrderSerializer(I18nAwareModelSerializer):
return instance
class PriceCalcSerializer(serializers.Serializer):
item = serializers.PrimaryKeyRelatedField(queryset=Item.objects.none(), required=False, allow_null=True)
variation = serializers.PrimaryKeyRelatedField(queryset=ItemVariation.objects.none(), required=False, allow_null=True)
subevent = serializers.PrimaryKeyRelatedField(queryset=SubEvent.objects.none(), required=False, allow_null=True)
locale = serializers.CharField(allow_null=True, required=False)
def __init__(self, *args, **kwargs):
event = kwargs.pop('event')
super().__init__(*args, **kwargs)
self.fields['item'].queryset = event.items.all()
self.fields['variation'].queryset = ItemVariation.objects.filter(item__event=event)
if event.has_subevents:
self.fields['subevent'].queryset = event.subevents.all()
else:
del self.fields['subevent']
class AnswerCreateSerializer(I18nAwareModelSerializer):
class Meta:

View File

@@ -23,4 +23,4 @@ def cleanup_webhook_logs(sender, **kwargs):
@receiver(periodic_task)
def cleanup_api_logs(sender, **kwargs):
ApiCall.objects.filter(created__lte=now() - timedelta(hours=24)).delete()
ApiCall.objects.filter(datetime__lte=now() - timedelta(hours=24)).delete()

View File

@@ -24,16 +24,14 @@ from pretix.api.models import OAuthAccessToken
from pretix.api.serializers.order import (
InvoiceSerializer, OrderCreateSerializer, OrderPaymentSerializer,
OrderPositionSerializer, OrderRefundCreateSerializer,
OrderRefundSerializer, OrderSerializer, PriceCalcSerializer,
OrderRefundSerializer, OrderSerializer,
)
from pretix.base.i18n import language
from pretix.base.models import (
CachedCombinedTicket, CachedTicket, Device, Event, Invoice, InvoiceAddress,
Order, OrderPayment, OrderPosition, OrderRefund, Quota, TeamAPIToken,
CachedCombinedTicket, CachedTicket, Device, Event, Invoice, Order,
OrderPayment, OrderPosition, OrderRefund, Quota, TeamAPIToken,
generate_position_secret, generate_secret,
)
from pretix.base.payment import PaymentException
from pretix.base.services import tickets
from pretix.base.services.invoices import (
generate_cancellation, generate_invoice, invoice_pdf, invoice_qualified,
regenerate_invoice,
@@ -43,12 +41,10 @@ from pretix.base.services.orders import (
OrderChangeManager, OrderError, approve_order, cancel_order, deny_order,
extend_order, mark_order_expired, mark_order_refunded,
)
from pretix.base.services.pricing import get_price
from pretix.base.services.tickets import generate
from pretix.base.signals import (
order_modified, order_placed, register_ticket_outputs,
)
from pretix.base.templatetags.money import money_filter
class OrderFilter(FilterSet):
@@ -374,8 +370,6 @@ class OrderViewSet(viewsets.ModelViewSet):
order.save(update_fields=['secret'])
CachedTicket.objects.filter(order_position__order=order).delete()
CachedCombinedTicket.objects.filter(order=order).delete()
tickets.invalidate_cache.apply_async(kwargs={'event': self.request.event.pk,
'order': order.pk})
order.log_action(
'pretix.event.order.secret.changed',
user=self.request.user,
@@ -625,83 +619,6 @@ class OrderPositionViewSet(mixins.DestroyModelMixin, viewsets.ReadOnlyModelViewS
return prov
raise NotFound('Unknown output provider.')
@action(detail=True, methods=['POST'], url_name='price_calc')
def price_calc(self, request, *args, **kwargs):
"""
This calculates the price assuming a change of product or subevent. This endpoint
is deliberately not documented and considered a private API, only to be used by
pretix' web interface.
Sample input:
{
"item": 2,
"variation": null,
"subevent": 3
}
Sample output:
{
"gross": "2.34",
"gross_formatted": "2,34",
"net": "2.34",
"tax": "0.00",
"rate": "0.00",
"name": "VAT"
}
"""
serializer = PriceCalcSerializer(data=request.data, event=request.event)
serializer.is_valid(raise_exception=True)
data = serializer.validated_data
pos = self.get_object()
try:
ia = pos.order.invoice_address
except InvoiceAddress.DoesNotExist:
ia = InvoiceAddress()
kwargs = {
'item': pos.item,
'variation': pos.variation,
'voucher': pos.voucher,
'subevent': pos.subevent,
'addon_to': pos.addon_to,
'invoice_address': ia,
}
if data.get('item'):
item = data.get('item')
kwargs['item'] = item
if item.has_variations:
variation = data.get('variation') or pos.variation
if not variation:
raise ValidationError('No variation given')
if variation.item != item:
raise ValidationError('Variation does not belong to item')
kwargs['variation'] = variation
else:
variation = None
kwargs['variation'] = None
if pos.voucher and not pos.voucher.applies_to(item, variation):
kwargs['voucher'] = None
if data.get('subevent'):
kwargs['subevent'] = data.get('subevent')
price = get_price(**kwargs)
with language(data.get('locale') or self.request.event.settings.locale):
return Response({
'gross': price.gross,
'gross_formatted': money_filter(price.gross, self.request.event.currency, hide_currency=True),
'net': price.net,
'rate': price.rate,
'name': str(price.name),
'tax': price.tax,
})
@action(detail=True, url_name='download', url_path='download/(?P<output>[^/]+)')
def download(self, request, output, **kwargs):
provider = self._get_output_provider(output)

View File

@@ -9,17 +9,14 @@ import vat_moss.exchange_rates
from django.contrib.staticfiles import finders
from django.dispatch import receiver
from django.utils.formats import date_format, localize
from django.utils.translation import (
get_language, pgettext, ugettext, ugettext_lazy,
)
from django.utils.translation import get_language, pgettext, ugettext
from PIL.Image import BICUBIC
from reportlab.lib import pagesizes
from reportlab.lib.enums import TA_LEFT, TA_RIGHT
from reportlab.lib.enums import TA_LEFT
from reportlab.lib.styles import ParagraphStyle, StyleSheet1
from reportlab.lib.units import mm
from reportlab.lib.utils import ImageReader
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.pdfmetrics import stringWidth
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.pdfgen.canvas import Canvas
from reportlab.platypus import (
@@ -125,7 +122,6 @@ class BaseReportlabInvoiceRenderer(BaseInvoiceRenderer):
"""
stylesheet = StyleSheet1()
stylesheet.add(ParagraphStyle(name='Normal', fontName=self.font_regular, fontSize=10, leading=12))
stylesheet.add(ParagraphStyle(name='InvoiceFrom', parent=stylesheet['Normal']))
stylesheet.add(ParagraphStyle(name='Heading1', fontName=self.font_bold, fontSize=15, leading=15 * 1.2))
stylesheet.add(ParagraphStyle(name='FineprintHeading', fontName=self.font_bold, fontSize=8, leading=12))
stylesheet.add(ParagraphStyle(name='Fineprint', fontName=self.font_regular, fontSize=8, leading=10))
@@ -258,64 +254,49 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
canvas.restoreState()
invoice_to_width = 85 * mm
invoice_to_height = 50 * mm
invoice_to_left = 25 * mm
invoice_to_top = 52 * mm
def _draw_invoice_to(self, canvas):
p = Paragraph(self.invoice.address_invoice_to.strip().replace('\n', '<br />\n'), style=self.stylesheet['Normal'])
p.wrapOn(canvas, self.invoice_to_width, self.invoice_to_height)
p_size = p.wrap(self.invoice_to_width, self.invoice_to_height)
p.drawOn(canvas, self.invoice_to_left, self.pagesize[1] - p_size[1] - self.invoice_to_top)
invoice_from_width = 70 * mm
invoice_from_height = 50 * mm
invoice_from_left = 25 * mm
invoice_from_top = 17 * mm
p = Paragraph(self.invoice.invoice_to.strip().replace('\n', '<br />\n'), style=self.stylesheet['Normal'])
p.wrapOn(canvas, 85 * mm, 50 * mm)
p_size = p.wrap(85 * mm, 50 * mm)
p.drawOn(canvas, 25 * mm, (297 - 52) * mm - p_size[1])
def _draw_invoice_from(self, canvas):
p = Paragraph(self.invoice.full_invoice_from.strip().replace('\n', '<br />\n'), style=self.stylesheet[
'InvoiceFrom'])
p.wrapOn(canvas, self.invoice_from_width, self.invoice_from_height)
p_size = p.wrap(self.invoice_from_width, self.invoice_from_height)
p.drawOn(canvas, self.invoice_from_left, self.pagesize[1] - p_size[1] - self.invoice_from_top)
p = Paragraph(self.invoice.full_invoice_from.strip().replace('\n', '<br />\n'), style=self.stylesheet['Normal'])
p.wrapOn(canvas, 70 * mm, 50 * mm)
p_size = p.wrap(70 * mm, 50 * mm)
p.drawOn(canvas, 25 * mm, (297 - 17) * mm - p_size[1])
def _on_first_page(self, canvas: Canvas, doc):
canvas.setCreator('pretix.eu')
canvas.setTitle(pgettext('invoice', 'Invoice {num}').format(num=self.invoice.number))
canvas.saveState()
canvas.setFont(self.font_regular, 8)
if self.invoice.order.testmode:
canvas.saveState()
canvas.setFont('OpenSansBd', 30)
canvas.setFillColorRGB(32, 0, 0)
canvas.drawRightString(self.pagesize[0] - 20 * mm, (297 - 100) * mm, ugettext('TEST MODE'))
canvas.restoreState()
for i, line in enumerate(self.invoice.footer_text.split('\n')[::-1]):
canvas.drawCentredString(self.pagesize[0] / 2, 25 + (3.5 * i) * mm, line.strip())
def _draw_invoice_from_label(self, canvas):
textobject = canvas.beginText(25 * mm, (297 - 15) * mm)
textobject.setFont(self.font_bold, 8)
textobject.textLine(self._upper(pgettext('invoice', 'Invoice from')))
canvas.drawText(textobject)
def _draw_invoice_to_label(self, canvas):
self._draw_invoice_from(canvas)
textobject = canvas.beginText(25 * mm, (297 - 50) * mm)
textobject.setFont(self.font_bold, 8)
textobject.textLine(self._upper(pgettext('invoice', 'Invoice to')))
canvas.drawText(textobject)
logo_width = 25 * mm
logo_height = 25 * mm
logo_left = 95 * mm
logo_top = 13 * mm
logo_anchor = 'n'
self._draw_invoice_to(canvas)
def _draw_logo(self, canvas):
if self.invoice.event.settings.invoice_logo_image:
logo_file = self.invoice.event.settings.get('invoice_logo_image', binary_file=True)
ir = ThumbnailingImageReader(logo_file)
try:
ir.resize(self.logo_width, self.logo_height, 300)
except:
logger.exception("Can not resize image")
pass
canvas.drawImage(ir,
self.logo_left,
self.pagesize[1] - self.logo_height - self.logo_top,
width=self.logo_width, height=self.logo_height,
preserveAspectRatio=True, anchor=self.logo_anchor,
mask='auto')
def _draw_metadata(self, canvas):
textobject = canvas.beginText(125 * mm, (297 - 38) * mm)
textobject.setFont(self.font_bold, 8)
textobject.textLine(self._upper(pgettext('invoice', 'Order code')))
@@ -367,37 +348,37 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
canvas.drawText(textobject)
event_left = 125 * mm
event_top = 17 * mm
event_width = 65 * mm
event_height = 50 * mm
if self.invoice.event.settings.invoice_logo_image:
logo_file = self.invoice.event.settings.get('invoice_logo_image', binary_file=True)
ir = ThumbnailingImageReader(logo_file)
try:
ir.resize(25 * mm, 25 * mm, 300)
except:
logger.exception("Can not resize image")
pass
canvas.drawImage(ir,
95 * mm, (297 - 38) * mm,
width=25 * mm, height=25 * mm,
preserveAspectRatio=True, anchor='n',
mask='auto')
def _draw_event_label(self, canvas):
textobject = canvas.beginText(125 * mm, (297 - 15) * mm)
textobject.setFont(self.font_bold, 8)
textobject.textLine(self._upper(pgettext('invoice', 'Event')))
canvas.drawText(textobject)
def _draw_event(self, canvas):
def shorten(txt):
txt = str(txt)
p = Paragraph(txt.strip().replace('\n', '<br />\n'), style=self.stylesheet['Normal'])
p_size = p.wrap(self.event_width, self.event_height)
p_size = p.wrap(65 * mm, 50 * mm)
while p_size[1] > 2 * self.stylesheet['Normal'].leading:
txt = ' '.join(txt.replace('', '').split()[:-1]) + ''
p = Paragraph(txt.strip().replace('\n', '<br />\n'), style=self.stylesheet['Normal'])
p_size = p.wrap(self.event_width, self.event_height)
p_size = p.wrap(65 * mm, 50 * mm)
return txt
if not self.invoice.event.has_subevents:
if self.invoice.event.settings.show_date_to and self.invoice.event.date_to:
p_str = (
shorten(self.invoice.event.name) + '\n' +
pgettext('invoice', '{from_date}\nuntil {to_date}').format(
shorten(self.invoice.event.name) + '\n' + pgettext('invoice', '{from_date}\nuntil {to_date}').format(
from_date=self.invoice.event.get_date_from_display(),
to_date=self.invoice.event.get_date_to_display()
)
to_date=self.invoice.event.get_date_to_display())
)
else:
p_str = (
@@ -407,38 +388,15 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
p_str = shorten(self.invoice.event.name)
p = Paragraph(p_str.strip().replace('\n', '<br />\n'), style=self.stylesheet['Normal'])
p.wrapOn(canvas, self.event_width, self.event_height)
p_size = p.wrap(self.event_width, self.event_height)
p.drawOn(canvas, self.event_left, self.pagesize[1] - self.event_top - p_size[1])
self._draw_event_label(canvas)
p.wrapOn(canvas, 65 * mm, 50 * mm)
p_size = p.wrap(65 * mm, 50 * mm)
p.drawOn(canvas, 125 * mm, (297 - 17) * mm - p_size[1])
def _draw_footer(self, canvas):
canvas.setFont(self.font_regular, 8)
for i, line in enumerate(self.invoice.footer_text.split('\n')[::-1]):
canvas.drawCentredString(self.pagesize[0] / 2, 25 + (3.5 * i) * mm, line.strip())
textobject = canvas.beginText(125 * mm, (297 - 15) * mm)
textobject.setFont(self.font_bold, 8)
textobject.textLine(self._upper(pgettext('invoice', 'Event')))
canvas.drawText(textobject)
def _draw_testmode(self, canvas):
if self.invoice.order.testmode:
canvas.saveState()
canvas.setFont('OpenSansBd', 30)
canvas.setFillColorRGB(32, 0, 0)
canvas.drawRightString(self.pagesize[0] - 20 * mm, (297 - 100) * mm, ugettext('TEST MODE'))
canvas.restoreState()
def _on_first_page(self, canvas: Canvas, doc):
canvas.setCreator('pretix.eu')
canvas.setTitle(pgettext('invoice', 'Invoice {num}').format(num=self.invoice.number))
canvas.saveState()
self._draw_footer(canvas)
self._draw_testmode(canvas)
self._draw_invoice_from_label(canvas)
self._draw_invoice_from(canvas)
self._draw_invoice_to_label(canvas)
self._draw_invoice_to(canvas)
self._draw_metadata(canvas)
self._draw_logo(canvas)
self._draw_event(canvas)
canvas.restoreState()
def _get_first_page_frames(self, doc):
@@ -476,13 +434,6 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
self.stylesheet['Normal']
))
if self.invoice.invoice_to_vat_id:
story.append(Paragraph(
pgettext('invoice', 'Customer VAT ID') + ':<br />' +
bleach.clean(self.invoice.invoice_to_vat_id, tags=[]).replace("\n", "<br />\n"),
self.stylesheet['Normal']
))
if self.invoice.invoice_to_beneficiary:
story.append(Paragraph(
pgettext('invoice', 'Beneficiary') + ':<br />' +
@@ -603,7 +554,6 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
colwidths = [a * doc.width for a in (.25, .15, .15, .15, .3)]
table = Table(tdata, colWidths=colwidths, repeatRows=2, hAlign=TA_LEFT)
table.setStyle(TableStyle(tstyledata))
story.append(Spacer(5 * mm, 5 * mm))
story.append(KeepTogether([
Paragraph(pgettext('invoice', 'Included taxes'), self.stylesheet['FineprintHeading']),
table
@@ -657,114 +607,6 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
return story
class Modern1Renderer(ClassicInvoiceRenderer):
identifier = 'modern1'
verbose_name = ugettext_lazy('Modern Invoice Renderer (pretix 2.7)')
bottom_margin = 16.9 * mm
top_margin = 16.9 * mm
right_margin = 20 * mm
invoice_to_height = 27.3 * mm
invoice_to_width = 80 * mm
invoice_to_left = 25 * mm
invoice_to_top = (40 + 17.7) * mm
invoice_from_left = 125 * mm
invoice_from_top = 50 * mm
invoice_from_width = pagesizes.A4[0] - invoice_from_left - right_margin
invoice_from_height = 50 * mm
logo_width = 75 * mm
logo_height = 25 * mm
logo_left = pagesizes.A4[0] - logo_width - right_margin
logo_top = top_margin
logo_anchor = 'e'
event_left = 25 * mm
event_top = top_margin
event_width = 80 * mm
event_height = 25 * mm
def _get_stylesheet(self):
stylesheet = super()._get_stylesheet()
stylesheet.add(ParagraphStyle(name='Sender', fontName=self.font_regular, fontSize=8, leading=10))
stylesheet['InvoiceFrom'].alignment = TA_RIGHT
return stylesheet
def _draw_invoice_from(self, canvas):
if not self.invoice.invoice_from:
return
c = self.invoice.address_invoice_from.strip().split('\n')
p = Paragraph(' · '.join(c), style=self.stylesheet['Sender'])
p.wrapOn(canvas, self.invoice_to_width, 15.7 * mm)
p.drawOn(canvas, self.invoice_to_left, self.pagesize[1] - self.invoice_to_top + 2 * mm)
super()._draw_invoice_from(canvas)
def _draw_invoice_to_label(self, canvas):
pass
def _draw_invoice_from_label(self, canvas):
pass
def _draw_event_label(self, canvas):
pass
def _get_first_page_frames(self, doc):
footer_length = 3.5 * len(self.invoice.footer_text.split('\n')) * mm
return [
Frame(doc.leftMargin, doc.bottomMargin, doc.width, doc.height - 95 * mm,
leftPadding=0, rightPadding=0, topPadding=0, bottomPadding=footer_length,
id='normal')
]
def _draw_metadata(self, canvas):
begin_top = 100 * mm
textobject = canvas.beginText(self.left_margin, self.pagesize[1] - begin_top)
textobject.setFont(self.font_regular, 8)
textobject.textLine(pgettext('invoice', 'Order code'))
textobject.moveCursor(0, 5)
textobject.setFont(self.font_regular, 10)
textobject.textLine(self.invoice.order.full_code)
canvas.drawText(textobject)
if self.invoice.is_cancellation:
textobject = canvas.beginText(self.left_margin + 50 * mm, self.pagesize[1] - begin_top)
textobject.setFont(self.font_regular, 8)
textobject.textLine(pgettext('invoice', 'Cancellation number'))
textobject.moveCursor(0, 5)
textobject.setFont(self.font_regular, 10)
textobject.textLine(self.invoice.number)
canvas.drawText(textobject)
textobject = canvas.beginText(self.left_margin + 100 * mm, self.pagesize[1] - begin_top)
textobject.setFont(self.font_regular, 8)
textobject.textLine(pgettext('invoice', 'Original invoice'))
textobject.moveCursor(0, 5)
textobject.setFont(self.font_regular, 10)
textobject.textLine(self.invoice.refers.number)
canvas.drawText(textobject)
else:
textobject = canvas.beginText(self.left_margin + 70 * mm, self.pagesize[1] - begin_top)
textobject.textLine(pgettext('invoice', 'Invoice number'))
textobject.moveCursor(0, 5)
textobject.setFont(self.font_regular, 10)
textobject.textLine(self.invoice.number)
canvas.drawText(textobject)
p = Paragraph(date_format(self.invoice.date, "DATE_FORMAT"), style=self.stylesheet['Normal'])
w = stringWidth(p.text, p.frags[0].fontName, p.frags[0].fontSize)
p.wrapOn(canvas, w, 15 * mm)
date_x = self.pagesize[0] - w - self.right_margin
p.drawOn(canvas, date_x, self.pagesize[1] - begin_top - 10 - 6)
textobject = canvas.beginText(date_x, self.pagesize[1] - begin_top)
textobject.setFont(self.font_regular, 8)
if self.invoice.is_cancellation:
textobject.textLine(pgettext('invoice', 'Cancellation date'))
else:
textobject.textLine(pgettext('invoice', 'Invoice date'))
canvas.drawText(textobject)
@receiver(register_invoice_renderers, dispatch_uid="invoice_renderer_classic")
def recv_classic(sender, **kwargs):
return [ClassicInvoiceRenderer, Modern1Renderer]
return ClassicInvoiceRenderer

View File

@@ -1,23 +0,0 @@
# Generated by Django 2.2 on 2019-04-18 11:49
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('pretixbase', '0116_auto_20190402_0722'),
]
operations = [
migrations.AddField(
model_name='itemvariation',
name='original_price',
field=models.DecimalField(blank=True, decimal_places=2, help_text='If set, this will be displayed next to '
'the current price to show that the '
'current price is a discounted one. '
'This is just a cosmetic setting and '
'will not actually impact pricing.',
max_digits=7, null=True, verbose_name='Original price'),
),
]

View File

@@ -117,35 +117,12 @@ class Invoice(models.Model):
self.invoice_from_name,
self.invoice_from,
(self.invoice_from_zipcode or "") + " " + (self.invoice_from_city or ""),
self.invoice_from_country.name if self.invoice_from_country else "",
str(self.invoice_from_country),
pgettext("invoice", "VAT-ID: %s") % self.invoice_from_vat_id if self.invoice_from_vat_id else "",
pgettext("invoice", "Tax ID: %s") % self.invoice_from_tax_id if self.invoice_from_tax_id else "",
]
return '\n'.join([p.strip() for p in parts if p and p.strip()])
@property
def address_invoice_from(self):
parts = [
self.invoice_from_name,
self.invoice_from,
(self.invoice_from_zipcode or "") + " " + (self.invoice_from_city or ""),
self.invoice_from_country.name if self.invoice_from_country else "",
]
return '\n'.join([p.strip() for p in parts if p and p.strip()])
@property
def address_invoice_to(self):
if self.invoice_to and not self.invoice_to_company and not self.invoice_to_name:
return self.invoice_to
parts = [
self.invoice_to_company,
self.invoice_to_name,
self.invoice_to_street,
(self.invoice_to_zipcode or "") + " " + (self.invoice_to_city or ""),
self.invoice_to_country.name if self.invoice_to_country else "",
]
return '\n'.join([p.strip() for p in parts if p and p.strip()])
def _get_numeric_invoice_number(self):
numeric_invoices = Invoice.objects.filter(
event__organizer=self.event.organizer,

View File

@@ -550,8 +550,6 @@ class ItemVariation(models.Model):
:type active: bool
:param default_price: This variation's default price
:type default_price: decimal.Decimal
:param original_price: The item's "original" price. Will not be used for any calculations, will just be shown.
:type original_price: decimal.Decimal
"""
item = models.ForeignKey(
Item,
@@ -580,13 +578,6 @@ class ItemVariation(models.Model):
null=True, blank=True,
verbose_name=_("Default price"),
)
original_price = models.DecimalField(
verbose_name=_('Original price'),
blank=True, null=True,
max_digits=7, decimal_places=2,
help_text=_('If set, this will be displayed next to the current price to show that the current price is a '
'discounted one. This is just a cosmetic setting and will not actually impact pricing.')
)
class Meta:
verbose_name = _("Product variation")
@@ -1400,7 +1391,7 @@ class Quota(LoggedModel):
@staticmethod
def clean_variations(items, variations):
for variation in (variations or []):
for variation in variations:
if variation.item not in items:
raise ValidationError(_('All variations must belong to an item contained in the items list.'))
break

View File

@@ -1159,7 +1159,7 @@ class OrderPayment(models.Model):
self.order.log_action('pretix.event.order.overpaid', {}, user=user, auth=auth)
order_paid.send(self.order.event, order=self.order)
def confirm(self, count_waitinglist=True, send_mail=True, force=False, user=None, auth=None, mail_text='', lock=True):
def confirm(self, count_waitinglist=True, send_mail=True, force=False, user=None, auth=None, mail_text=''):
"""
Marks the payment as complete. If possible, this also marks the order as paid if no further
payment is required
@@ -1218,7 +1218,7 @@ class OrderPayment(models.Model):
if payment_sum - refund_sum < self.order.total:
return
if (self.order.status == Order.STATUS_PENDING and self.order.expires > now() + timedelta(hours=12)) or not lock:
if self.order.status == Order.STATUS_PENDING and self.order.expires > now() + timedelta(hours=12):
# Performance optimization. In this case, there's really no reason to lock everything and an atomic
# database transaction is more than enough.
with transaction.atomic():

View File

@@ -15,7 +15,6 @@ from pretix.base.email import ClassicMailRenderer
from pretix.base.i18n import language
from pretix.base.models import Event, Invoice, InvoiceAddress, Order
from pretix.base.services.invoices import invoice_pdf_task
from pretix.base.services.tasks import TransactionAwareTask
from pretix.base.services.tickets import get_tickets_for_order
from pretix.base.signals import email_filter
from pretix.celery_app import app
@@ -178,7 +177,7 @@ def mail(email: str, subject: str, template: Union[str, LazyI18nString],
chain(*task_chain).apply_async()
@app.task(base=TransactionAwareTask, bind=True)
@app.task(bind=True)
def mail_send_task(self, *args, to: List[str], subject: str, body: str, html: str, sender: str,
event: int=None, headers: dict=None, bcc: List[str]=None, invoices: List[int]=None,
order: int=None, attach_tickets=False) -> bool:
@@ -220,23 +219,14 @@ def mail_send_task(self, *args, to: List[str], subject: str, body: str, html: st
args.append((name, content, ct.type))
attach_size += len(content)
if attach_size < 4 * 1024 * 1024:
if attach_tickets < 4 * 1024 * 1024:
# Do not attach more than 4MB, it will bounce way to often.
for a in args:
try:
email.attach(*a)
except:
pass
else:
order.log_action(
'pretix.event.order.email.error',
data={
'subject': 'Attachments skipped',
'message': 'Attachment have not been send because {} bytes are likely too large to arrive.'.format(attach_size),
'recipient': '',
'invoices': [],
}
)
email = email_filter.send_chained(event, 'message', message=email, order=order)

View File

@@ -28,13 +28,12 @@ from pretix.base.models import (
from pretix.base.models.event import SubEvent
from pretix.base.models.items import ItemBundle
from pretix.base.models.orders import (
InvoiceAddress, OrderFee, OrderRefund, generate_position_secret,
generate_secret,
CachedCombinedTicket, CachedTicket, InvoiceAddress, OrderFee, OrderRefund,
generate_position_secret, generate_secret,
)
from pretix.base.models.organizer import TeamAPIToken
from pretix.base.models.tax import TaxedPrice
from pretix.base.payment import BasePaymentProvider, PaymentException
from pretix.base.services import tickets
from pretix.base.services.invoices import (
generate_cancellation, generate_invoice, invoice_qualified,
)
@@ -568,7 +567,6 @@ def _create_order(event: Event, email: str, positions: List[CartPosition], now_d
meta_info: dict=None, sales_channel: str='web'):
fees, pf = _get_fees(positions, payment_provider, address, meta_info, event)
total = sum([c.price for c in positions]) + sum([c.value for c in fees])
p = None
with transaction.atomic():
order = Order(
@@ -602,7 +600,7 @@ def _create_order(event: Event, email: str, positions: List[CartPosition], now_d
fee.save()
if payment_provider and not order.require_approval:
p = order.payments.create(
order.payments.create(
state=OrderPayment.PAYMENT_STATE_CREATED,
provider=payment_provider.identifier,
amount=total,
@@ -618,7 +616,7 @@ def _create_order(event: Event, email: str, positions: List[CartPosition], now_d
order.log_action('pretix.event.order.consent', data={'msg': msg})
order_placed.send(event, order=order)
return order, p
return order
def _perform_order(event: str, payment_provider: str, position_ids: List[str],
@@ -650,12 +648,8 @@ def _perform_order(event: str, payment_provider: str, position_ids: List[str],
if len(position_ids) != len(positions):
raise OrderError(error_messages['internal'])
_check_positions(event, now_dt, positions, address=addr)
order, payment = _create_order(event, email, positions, now_dt, pprov,
locale=locale, address=addr, meta_info=meta_info, sales_channel=sales_channel)
free_order_flow = payment and payment_provider == 'free' and order.total == Decimal('0.00') and not order.require_approval
if free_order_flow:
payment.confirm(send_mail=False, lock=False)
order = _create_order(event, email, positions, now_dt, pprov,
locale=locale, address=addr, meta_info=meta_info, sales_channel=sales_channel)
invoice = order.invoices.last() # Might be generated by plugin already
if event.settings.get('invoice_generate') == 'True' and invoice_qualified(order):
@@ -670,7 +664,7 @@ def _perform_order(event: str, payment_provider: str, position_ids: List[str],
if order.require_approval:
email_template = event.settings.mail_text_order_placed_require_approval
log_entry = 'pretix.event.order.email.order_placed_require_approval'
elif free_order_flow:
elif payment_provider == 'free':
email_template = event.settings.mail_text_order_free
log_entry = 'pretix.event.order.email.order_free'
else:
@@ -846,8 +840,8 @@ class OrderChangeManager:
'addon_invalid': _('The selected base position does not allow you to add this product as an add-on.'),
'subevent_required': _('You need to choose a subevent for the new position.'),
}
ItemOperation = namedtuple('ItemOperation', ('position', 'item', 'variation'))
SubeventOperation = namedtuple('SubeventOperation', ('position', 'subevent'))
ItemOperation = namedtuple('ItemOperation', ('position', 'item', 'variation', 'price'))
SubeventOperation = namedtuple('SubeventOperation', ('position', 'subevent', 'price'))
PriceOperation = namedtuple('PriceOperation', ('position', 'price'))
CancelOperation = namedtuple('CancelOperation', ('position',))
AddOperation = namedtuple('AddOperation', ('item', 'variation', 'price', 'addon_to', 'subevent'))
@@ -858,7 +852,6 @@ class OrderChangeManager:
self.order = order
self.user = user
self.auth = auth
self.event = order.event
self.split_order = None
self._committed = False
self._totaldiff = 0
@@ -867,18 +860,33 @@ class OrderChangeManager:
self.notify = notify
self._invoice_dirty = False
def change_item(self, position: OrderPosition, item: Item, variation: Optional[ItemVariation]):
def change_item(self, position: OrderPosition, item: Item, variation: Optional[ItemVariation], keep_price=False):
if (not variation and item.has_variations) or (variation and variation.item_id != item.pk):
raise OrderError(self.error_messages['product_without_variation'])
if keep_price:
price = TaxedPrice(gross=position.price, net=position.price - position.tax_value,
tax=position.tax_value, rate=position.tax_rate,
name=position.tax_rule.name if position.tax_rule else None)
else:
price = get_price(item, variation, voucher=position.voucher, subevent=position.subevent,
invoice_address=self._invoice_address)
if price is None: # NOQA
raise OrderError(self.error_messages['product_invalid'])
new_quotas = (variation.quotas.filter(subevent=position.subevent)
if variation else item.quotas.filter(subevent=position.subevent))
if not new_quotas:
raise OrderError(self.error_messages['quota_missing'])
if self.order.event.settings.invoice_include_free or price.gross != Decimal('0.00') or position.price != Decimal('0.00'):
self._invoice_dirty = True
self._totaldiff += price.gross - position.price
self._quotadiff.update(new_quotas)
self._quotadiff.subtract(position.quotas)
self._operations.append(self.ItemOperation(position, item, variation))
self._operations.append(self.ItemOperation(position, item, variation, price))
def change_subevent(self, position: OrderPosition, subevent: SubEvent):
price = get_price(position.item, position.variation, voucher=position.voucher, subevent=subevent,
@@ -892,15 +900,19 @@ class OrderChangeManager:
if not new_quotas:
raise OrderError(self.error_messages['quota_missing'])
if self.order.event.settings.invoice_include_free or price.gross != Decimal('0.00') or position.price != Decimal('0.00'):
self._invoice_dirty = True
self._totaldiff += price.gross - position.price
self._quotadiff.update(new_quotas)
self._quotadiff.subtract(position.quotas)
self._operations.append(self.SubeventOperation(position, subevent))
self._operations.append(self.SubeventOperation(position, subevent, price))
def regenerate_secret(self, position: OrderPosition):
self._operations.append(self.RegenerateSecretOperation(position))
def change_price(self, position: OrderPosition, price: Decimal):
price = position.item.tax(price, base_price_is='gross')
price = position.item.tax(price)
self._totaldiff += price.gross - position.price
@@ -1057,11 +1069,14 @@ class OrderChangeManager:
'new_variation': op.variation.pk if op.variation else None,
'old_price': op.position.price,
'addon_to': op.position.addon_to_id,
'new_price': op.position.price
'new_price': op.price.gross
})
op.position.item = op.item
op.position.variation = op.variation
op.position._calculate_tax()
op.position.price = op.price.gross
op.position.tax_rate = op.price.rate
op.position.tax_value = op.price.tax
op.position.tax_rule = op.item.tax_rule
op.position.save()
elif isinstance(op, self.SubeventOperation):
self.order.log_action('pretix.event.order.changed.subevent', user=self.user, auth=self.auth, data={
@@ -1070,9 +1085,13 @@ class OrderChangeManager:
'old_subevent': op.position.subevent.pk,
'new_subevent': op.subevent.pk,
'old_price': op.position.price,
'new_price': op.position.price
'new_price': op.price.gross
})
op.position.subevent = op.subevent
op.position.price = op.price.gross
op.position.tax_rate = op.price.rate
op.position.tax_value = op.price.tax
op.position.tax_rule = op.position.item.tax_rule
op.position.save()
elif isinstance(op, self.PriceOperation):
self.order.log_action('pretix.event.order.changed.price', user=self.user, auth=self.auth, data={
@@ -1083,7 +1102,9 @@ class OrderChangeManager:
'new_price': op.price.gross
})
op.position.price = op.price.gross
op.position._calculate_tax()
op.position.tax_rate = op.price.rate
op.position.tax_value = op.price.tax
op.position.tax_rule = op.position.item.tax_rule
op.position.save()
elif isinstance(op, self.CancelOperation):
for opa in op.position.addons.all():
@@ -1133,8 +1154,8 @@ class OrderChangeManager:
elif isinstance(op, self.RegenerateSecretOperation):
op.position.secret = generate_position_secret()
op.position.save()
tickets.invalidate_cache.apply_async(kwargs={'event': self.event.pk,
'order': self.order.pk})
CachedTicket.objects.filter(order_position__order=self.order).delete()
CachedCombinedTicket.objects.filter(order=self.order).delete()
self.order.log_action('pretix.event.order.changed.secret', user=self.user, auth=self.auth, data={
'position': op.position.pk,
'positionid': op.position.positionid,
@@ -1367,11 +1388,11 @@ class OrderChangeManager:
order_changed.send(self.order.event, order=self.order)
def _clear_tickets_cache(self):
tickets.invalidate_cache.apply_async(kwargs={'event': self.event.pk,
'order': self.order.pk})
CachedTicket.objects.filter(order_position__order=self.order).delete()
CachedCombinedTicket.objects.filter(order=self.order).delete()
if self.split_order:
tickets.invalidate_cache.apply_async(kwargs={'event': self.event.pk,
'order': self.split_order.pk})
CachedTicket.objects.filter(order_position__order=self.split_order).delete()
CachedCombinedTicket.objects.filter(order=self.split_order).delete()
def _get_payment_provider(self):
lp = self.order.payments.last()

View File

@@ -123,10 +123,8 @@ def get_tickets_for_order(order):
order=order, provider=p.identifier, file__isnull=False
).last()
if not ct or not ct.file:
retval = generate_order(order.pk, p.identifier)
if not retval:
continue
ct = CachedCombinedTicket.objects.get(pk=retval)
retval = generate.apply(args=('order', order.pk, p.identifier))
ct = CachedCombinedTicket.objects.get(pk=retval.get())
tickets.append((
"{}-{}-{}{}".format(
order.event.slug.upper(), order.code, ct.provider, ct.extension,
@@ -142,10 +140,8 @@ def get_tickets_for_order(order):
order_position=pos, provider=p.identifier, file__isnull=False
).last()
if not ct or not ct.file:
retval = generate_orderposition(pos.pk, p.identifier)
if not retval:
continue
ct = CachedCombinedTicket.objects.get(pk=retval)
retval = generate.apply(args=('orderposition', pos.pk, p.identifier))
ct = CachedTicket.objects.get(pk=retval.get())
tickets.append((
"{}-{}-{}-{}{}".format(
order.event.slug.upper(), order.code, pos.positionid, ct.provider, ct.extension,
@@ -156,26 +152,3 @@ def get_tickets_for_order(order):
logger.exception('Failed to generate ticket.')
return tickets
@app.task(base=ProfiledTask)
def invalidate_cache(event: int, item: int=None, provider: str=None, order: int=None, **kwargs):
event = Event.objects.get(id=event)
qs = CachedTicket.objects.filter(order_position__order__event=event)
qsc = CachedCombinedTicket.objects.filter(order__event=event)
if item:
qs = qs.filter(order_position__item_id=item)
if provider:
qs = qs.filter(provider=provider)
qsc = qsc.filter(provider=provider)
if order:
qs = qs.filter(order_position__order_id=order)
qsc = qsc.filter(order_id=order)
for ct in qs:
ct.delete()
for ct in qsc:
ct.delete()

View File

@@ -0,0 +1,66 @@
{% load i18n %}{% load static from staticfiles %}
<link rel="stylesheet" href="{% static 'debug_toolbar/css/print.css' %}" type="text/css" media="print" />
<link rel="stylesheet" href="{% static 'debug_toolbar/css/toolbar.css' %}" type="text/css" />
<!-- Prevent our copy of jQuery from registering as an AMD module on sites that use RequireJS. -->
<script src="{% static 'debug_toolbar/js/jquery_pre.js' %}"></script>
<script type="text/javascript" src="{% static "jquery/js/jquery-2.1.1.min.js" %}"></script>
<script src="{% static 'debug_toolbar/js/jquery_post.js' %}"></script>
<script src="{% static 'debug_toolbar/js/toolbar.js' %}"></script>
<div id="djDebug" class="djdt-hidden" dir="ltr"
data-store-id="{{ toolbar.store_id }}" data-render-panel-url="{% url 'djdt:render_panel' %}"
{{ toolbar.config.ROOT_TAG_EXTRA_ATTRS|safe }}>
<div class="djdt-hidden" id="djDebugToolbar">
<ul id="djDebugPanelList">
{% if toolbar.panels %}
<li><a id="djHideToolBarButton" href="#" title="{% trans "Hide toolbar" %}">{% trans "Hide" %} &#187;</a></li>
{% else %}
<li id="djDebugButton">DEBUG</li>
{% endif %}
{% for panel in toolbar.panels %}
<li class="djDebugPanelButton">
<input type="checkbox" data-cookie="djdt{{ panel.panel_id }}" {% if panel.enabled %}checked="checked" title="{% trans "Disable for next and successive requests" %}"{% else %}title="{% trans "Enable for next and successive requests" %}"{% endif %} />
{% if panel.has_content and panel.enabled %}
<a href="#" title="{{ panel.title }}" class="{{ panel.panel_id }}">
{% else %}
<div class="djdt-contentless{% if not panel.enabled %} djdt-disabled{% endif %}">
{% endif %}
{{ panel.nav_title }}
{% if panel.enabled %}
{% with panel.nav_subtitle as subtitle %}
{% if subtitle %}<br /><small>{{ subtitle }}</small>{% endif %}
{% endwith %}
{% endif %}
{% if panel.has_content and panel.enabled %}
</a>
{% else %}
</div>
{% endif %}
</li>
{% endfor %}
</ul>
</div>
<div class="djdt-hidden" id="djDebugToolbarHandle">
<span title="{% trans "Show toolbar" %}" id="djShowToolBarButton">&#171;</span>
</div>
{% for panel in toolbar.panels %}
{% if panel.has_content and panel.enabled %}
<div id="{{ panel.panel_id }}" class="djdt-panelContent">
<div class="djDebugPanelTitle">
<a href="" class="djDebugClose"></a>
<h3>{{ panel.title|safe }}</h3>
</div>
<div class="djDebugPanelContent">
{% if toolbar.store_id %}
<img src="{% static 'debug_toolbar/img/ajax-loader.gif' %}" alt="loading" class="djdt-loader" />
<div class="djdt-scroll"></div>
{% else %}
<div class="djdt-scroll">{{ panel.content }}</div>
{% endif %}
</div>
</div>
{% endif %}
{% endfor %}
<div id="djDebugWindow" class="djdt-panelContent"></div>
</div>

View File

@@ -56,9 +56,7 @@ def page_not_found(request, exception):
}
template = get_template('404.html')
body = template.render(context, request)
r = HttpResponseNotFound(body)
r.xframe_options_exempt = True
return r
return HttpResponseNotFound(body)
@requires_csrf_token
@@ -67,9 +65,7 @@ def server_error(request):
template = loader.get_template('500.html')
except TemplateDoesNotExist:
return HttpResponseServerError('<h1>Server Error (500)</h1>', content_type='text/html')
r = HttpResponseServerError(template.render({
return HttpResponseServerError(template.render({
'request': request,
'sentry_event_id': last_event_id(),
}))
r.xframe_options_exempt = True
return r

View File

@@ -463,7 +463,6 @@ class ItemVariationForm(I18nModelForm):
'value',
'active',
'default_price',
'original_price',
'description',
]

View File

@@ -9,7 +9,9 @@ from django.utils.timezone import now
from django.utils.translation import pgettext_lazy, ugettext_lazy as _
from pretix.base.forms import I18nModelForm, PlaceholderValidator
from pretix.base.models import InvoiceAddress, ItemAddOn, Order, OrderPosition
from pretix.base.models import (
InvoiceAddress, Item, ItemAddOn, Order, OrderPosition,
)
from pretix.base.models.event import SubEvent
from pretix.base.services.pricing import get_price
from pretix.control.forms.widgets import Select2
@@ -148,6 +150,15 @@ class CommentForm(I18nModelForm):
}
class SubEventChoiceField(forms.ModelChoiceField):
def label_from_instance(self, obj):
p = get_price(self.instance.item, self.instance.variation,
voucher=self.instance.voucher,
subevent=obj)
return '{} {} ({})'.format(obj.name, obj.get_date_range_display(),
p.print(self.instance.order.event.currency))
class OtherOperationsForm(forms.Form):
recalculate_taxes = forms.BooleanField(
label=_('Re-calculate taxes'),
@@ -254,13 +265,12 @@ class OrderPositionAddForm(forms.Form):
class OrderPositionChangeForm(forms.Form):
itemvar = forms.ChoiceField(
required=False,
)
subevent = forms.ModelChoiceField(
itemvar = forms.ChoiceField()
subevent = SubEventChoiceField(
SubEvent.objects.none(),
required=False,
empty_label=_('(Unchanged)')
label=pgettext_lazy('subevent', 'New date'),
required=True,
empty_label=None
)
price = forms.DecimalField(
required=False,
@@ -268,49 +278,53 @@ class OrderPositionChangeForm(forms.Form):
localize=True,
label=_('New price (gross)')
)
operation_secret = forms.BooleanField(
operation = forms.ChoiceField(
required=False,
label=_('Generate a new secret')
)
operation_cancel = forms.BooleanField(
required=False,
label=_('Cancel this position')
)
operation_split = forms.BooleanField(
required=False,
label=_('Split into new order')
widget=forms.RadioSelect,
choices=(
('product', 'Change product'),
('price', 'Change price'),
('subevent', 'Change event date'),
('cancel', 'Remove product'),
('split', 'Split into new order'),
('secret', 'Regenerate secret'),
)
)
change_product_keep_price = forms.BooleanField(required=False)
def __init__(self, *args, **kwargs):
instance = kwargs.pop('instance')
initial = kwargs.get('initial', {})
if instance.item.tax_rule and not instance.item.tax_rule.price_includes_tax:
initial['price'] = instance.price - instance.tax_value
else:
initial['price'] = instance.price
try:
ia = instance.order.invoice_address
except InvoiceAddress.DoesNotExist:
ia = None
if instance:
try:
if instance.variation:
initial['itemvar'] = '%d-%d' % (instance.item.pk, instance.variation.pk)
elif instance.item:
initial['itemvar'] = str(instance.item.pk)
except Item.DoesNotExist:
pass
if instance.item.tax_rule and not instance.item.tax_rule.price_includes_tax:
initial['price'] = instance.price - instance.tax_value
else:
initial['price'] = instance.price
initial['subevent'] = instance.subevent
kwargs['initial'] = initial
super().__init__(*args, **kwargs)
if instance.order.event.has_subevents:
self.fields['subevent'].instance = instance
self.fields['subevent'].queryset = instance.order.event.subevents.all()
self.fields['subevent'].widget = Select2(
attrs={
'data-model-select2': 'event',
'data-select2-url': reverse('control:event.subevents.select2', kwargs={
'event': instance.order.event.slug,
'organizer': instance.order.event.organizer.slug,
}),
'data-placeholder': _('(Unchanged)')
}
)
self.fields['subevent'].widget.choices = self.fields['subevent'].choices
else:
del self.fields['subevent']
choices = [
('', _('(Unchanged)'))
]
choices = []
for i in instance.order.event.items.prefetch_related('variations').all():
pname = str(i)
if not i.is_available():
@@ -319,10 +333,14 @@ class OrderPositionChangeForm(forms.Form):
if variations:
for v in variations:
p = get_price(i, v, voucher=instance.voucher, subevent=instance.subevent,
invoice_address=ia)
choices.append(('%d-%d' % (i.pk, v.pk),
'%s %s' % (pname, v.value)))
'%s %s (%s)' % (pname, v.value, p.print(instance.order.event.currency))))
else:
choices.append((str(i.pk), pname))
p = get_price(i, None, voucher=instance.voucher, subevent=instance.subevent,
invoice_address=ia)
choices.append((str(i.pk), '%s (%s)' % (pname, p.print(instance.order.event.currency))))
self.fields['itemvar'].choices = choices
change_decimal_field(self.fields['price'], instance.order.event.currency)

View File

@@ -389,9 +389,6 @@ def pretixcontrol_logentry_display(sender: Event, logentry: LogEntry, **kwargs):
if logentry.action_type == 'pretix.team.invite.created':
return _('{user} has been invited to the team.').format(user=data.get('email'))
if logentry.action_type == 'pretix.team.invite.resent':
return _('Invite for {user} has been resent.').format(user=data.get('email'))
if logentry.action_type == 'pretix.team.invite.deleted':
return _('The invite for {user} has been revoked.').format(user=data.get('email'))

View File

@@ -43,7 +43,6 @@
<script type="text/javascript" src="{% static "pretixcontrol/js/ui/subevent.js" %}"></script>
<script type="text/javascript" src="{% static "pretixcontrol/js/ui/question.js" %}"></script>
<script type="text/javascript" src="{% static "pretixcontrol/js/ui/mail.js" %}"></script>
<script type="text/javascript" src="{% static "pretixcontrol/js/ui/orderchange.js" %}"></script>
<script type="text/javascript" src="{% static "pretixcontrol/js/ui/typeahead.js" %}"></script>
<script type="text/javascript" src="{% static "pretixcontrol/js/ui/quicksetup.js" %}"></script>
<script type="text/javascript" src="{% static "pretixbase/js/details.js" %}"></script>

View File

@@ -95,15 +95,7 @@
{% endif %}
</td>
<td>{{ e.item }}{% if e.variation %} {{ e.variation }}{% endif %}</td>
<td>
{% if e.addon_to and e.addon_to.attendee_email %}
{{ e.addon_to.attendee_email }}
{% elif e.attendee_email %}
{{ e.attendee_email }}
{% else %}
{{ e.order.email }}
{% endif %}
</td>
<td>{{ e.order.email }}</td>
<td>
{% if e.addon_to %}
{{ e.addon_to.attendee_name }}

View File

@@ -45,7 +45,6 @@
{% bootstrap_form_errors form %}
{% bootstrap_field form.active layout="control" %}
{% bootstrap_field form.default_price addon_after=request.event.currency layout="control" %}
{% bootstrap_field form.original_price addon_after=request.event.currency layout="control" %}
{% bootstrap_field form.description layout="control" %}
</div>
</div>
@@ -79,7 +78,6 @@
<div class="panel-body form-horizontal">
{% bootstrap_field formset.empty_form.active layout="control" %}
{% bootstrap_field formset.empty_form.default_price addon_after=request.event.currency layout="control" %}
{% bootstrap_field formset.empty_form.original_price addon_after=request.event.currency layout="control" %}
{% bootstrap_field formset.empty_form.description layout="control" %}
</div>
</div>

View File

@@ -1,7 +1,6 @@
{% extends "pretixcontrol/event/base.html" %}
{% load i18n %}
{% load bootstrap3 %}
{% load money %}
{% block title %}
{% blocktrans trimmed with code=order.code %}
Change order: {{ code }}
@@ -72,87 +71,92 @@
</h3>
</div>
<div class="panel-body">
<div class="form-order-change" data-pricecalc-endpoint="{% url "api-v1:orderposition-price_calc" organizer=order.event.organizer.slug event=order.event.slug pk=position.pk %}">
<div class="form-inline form-order-change">
{% bootstrap_form_errors position.form %}
{% if position.custom_error %}
<div class="alert alert-danger">
{{ position.custom_error }}
</div>
{% endif %}
<div class="row">
<div class="col-sm-5 col-sm-offset-3">
<strong>{% trans "Current value" %}</strong>
</div>
<div class="col-sm-4">
<strong>{% trans "Change to" %}</strong>
</div>
<p class="help-block">
{% trans "Ticket secret:" %} {{ position.secret|slice:":12" }}…
</p>
<div class="radio">
<label>
<input name="{{ position.form.prefix }}-operation" type="radio" value=""
{% if not position.form.operation.value %}checked="checked"{% endif %}>
{% trans "Keep unchanged" %}
</label>
</div>
<div class="row">
<div class="col-sm-3">
<strong>{% trans "Product" %}</strong>
</div>
<div class="col-sm-5">
{{ position.item }}
{% if position.variation %}
{{ position.variation }}
{% endif %}
</div>
<div class="col-sm-4">
<div class="radio">
<label>
<input name="{{ position.form.prefix }}-operation" type="radio" value="product"
{% if position.form.operation.value == "product" %}checked="checked"{% endif %}>
{% trans "Change product to" %}
{% bootstrap_field position.form.itemvar layout='inline' %}
</div>
</label>
<label class="checkbox">
{{ position.form.change_product_keep_price }}
{% trans "Keep price the same" %}
</label>
</div>
{% if request.event.has_subevents %}
<div class="row">
<div class="col-sm-3">
<strong>{% trans "Date" context "subevent" %}</strong>
</div>
<div class="col-sm-5">
{{ position.subevent }}
</div>
<div class="col-sm-4">
<div class="radio">
<label>
<input name="{{ position.form.prefix }}-operation" type="radio" value="subevent"
{% if position.form.operation.value == "subevent" %}checked="checked"{% endif %}>
{% trans "Change date to" context "subevent" %}
{% bootstrap_field position.form.subevent layout='inline' %}
</div>
</label>
</div>
{% endif %}
<div class="row">
<div class="col-sm-3">
<strong>{% trans "Price" %}</strong>
</div>
<div class="col-sm-5">
{{ position.price|money:request.event.currency }}<br>
{% if position.tax_rate %}
<small>{% blocktrans trimmed with rate=position.tax_rate name=position.tax_rule.name %}
<strong>incl.</strong> {{ rate }}% {{ name }}
{% endblocktrans %}</small>
{% endif %}
</div>
<div class="col-sm-4 field-container">
<div class="radio">
<label>
<input name="{{ position.form.prefix }}-operation" type="radio" value="price"
{% if position.form.operation.value == "price" %}checked="checked"{% endif %}>
{% trans "Change price to" %}
{% bootstrap_field position.form.price addon_after=request.event.currency layout='inline' %}
<small><strong>{% trans "including all taxes" %}</strong></small>
</div>
{% if position.apply_tax %}
{% if position.item.tax_rule and not position.item.tax_rule.price_includes_tax %}
{% blocktrans trimmed with rate=position.item.tax_rule.rate name=position.item.tax_rule.name %}
<strong>plus</strong> {{ rate }}% {{ name }}
{% endblocktrans %}
{% elif position.item.tax_rule %}
{% blocktrans trimmed with rate=position.item.tax_rule.rate name=position.item.tax_rule.name %}
<strong>incl.</strong> {{ rate }}% {{ name }}
{% endblocktrans %}
{% endif %}
{% else %}
{% trans "no taxes apply" %}
{% endif %}
</label>
</div>
<div class="row">
<div class="col-sm-3">
<strong>{% trans "Ticket secret" %}</strong>
</div>
<div class="col-sm-5">
{{ position.secret|slice:":12" }}…
</div>
<div class="col-sm-4">
{% bootstrap_field position.form.operation_secret layout='inline' %}
</div>
<div class="radio">
<label>
<input name="{{ position.form.prefix }}-operation" type="radio" value="split"
{% if position.form.operation.value == "split" %}checked="checked"{% endif %}>
{% trans "Split into new order" %}
</label>
</div>
<div class="radio">
<label>
<input name="{{ position.form.prefix }}-operation" type="radio" value="secret"
{% if position.form.operation.value == "secret" %}checked="checked"{% endif %}>
{% trans "Generate a new secret" %}
</label>
</div>
<div class="radio">
<label>
<input name="{{ position.form.prefix }}-operation" type="radio" value="cancel"
{% if position.form.operation.value == "cancel" %}checked="checked"{% endif %}>
{% trans "Cancel position" %}
{% if position.addons.exists %}
<em class="text-danger">
{% trans "Removing this position will also remove all add-ons to this position." %}
</em>
{% endif %}
</label>
</div>
{% bootstrap_field position.form.operation_cancel layout='inline' %}
{% bootstrap_field position.form.operation_split layout='inline' %}
{% if position.addons.exists %}
<em class="text-danger">
{% trans "Removing this position will also remove all add-ons to this position." %}
</em>
{% endif %}
</div>
</div>
</div>

View File

@@ -489,27 +489,7 @@
{% if p.html_info %}
<tr>
<td colspan="1"></td>
<td colspan="5">
{{ p.html_info|safe }}
{% if staff_session %}
<p>
<a href="" class="btn btn-default btn-xs" data-expandpayment data-id="{{ p.pk }}">
<span class="fa-eye fa fa-fw"></span>
{% trans "Inspect" %}
</a>
</p>
{% endif %}
</td>
</tr>
{% elif staff_session %}
<tr>
<td colspan="1"></td>
<td colspan="5">
<a href="" class="btn btn-default btn-xs" data-expandpayment data-id="{{ p.pk }}">
<span class="fa-eye fa fa-fw"></span>
{% trans "Inspect" %}
</a>
</td>
<td colspan="5">{{ p.html_info|safe }}</td>
</tr>
{% endif %}
{% endfor %}
@@ -594,18 +574,6 @@
{% endif %}
</td>
</tr>
{% if staff_session %}
<tr>
<td colspan="1"></td>
<td colspan="7">
<a href="" class="btn btn-default btn-xs" data-expandrefund
data-id="{{ r.pk }}">
<span class="fa-eye fa fa-fw"></span>
{% trans "Inspect" %}
</a>
</td>
</tr>
{% endif %}
{% endfor %}
</tbody>
</table>

View File

@@ -51,11 +51,6 @@
{{ i.email }}
<span class="fa fa-envelope-o" data-toggle="tooltip"
title="{% trans "invited, pending response" %}"></span>
<button type="submit" name="resend-invite" value="{{ i.id }}"
data-toggle="tooltip" title="{% trans "resend invite" %}"
class="btn-invisible">
<span class="fa fa-repeat"></span>
</button>
</td>
<td class="text-right">
<button type="submit" name="remove-invite" value="{{ i.id }}"

View File

@@ -19,8 +19,6 @@ urlpatterns = [
url(r'^global/update/$', global_settings.UpdateCheckView.as_view(), name='global.update'),
url(r'^global/message/$', global_settings.MessageView.as_view(), name='global.message'),
url(r'^logdetail/$', global_settings.LogDetailView.as_view(), name='global.logdetail'),
url(r'^logdetail/payment/$', global_settings.PaymentDetailView.as_view(), name='global.paymentdetail'),
url(r'^logdetail/refund/$', global_settings.RefundDetailView.as_view(), name='global.refunddetail'),
url(r'^reauth/$', user.ReauthView.as_view(), name='user.reauth'),
url(r'^sudo/$', user.StartStaffSession.as_view(), name='user.sudo'),
url(r'^sudo/stop/$', user.StopStaffSession.as_view(), name='user.sudo.stop'),

View File

@@ -31,7 +31,8 @@ from pytz import timezone
from pretix.base.channels import get_all_sales_channels
from pretix.base.i18n import LazyCurrencyNumber
from pretix.base.models import (
Event, LogEntry, Order, RequiredAction, TaxRule, Voucher,
CachedCombinedTicket, CachedTicket, Event, LogEntry, Order, RequiredAction,
TaxRule, Voucher,
)
from pretix.base.models.event import EventMetaValue
from pretix.base.services import tickets
@@ -788,7 +789,12 @@ class TicketSettings(EventSettingsViewMixin, EventPermissionRequiredMixin, FormV
for k in provider.form.changed_data
}
)
tickets.invalidate_cache.apply_async(kwargs={'event': self.request.event.pk, 'provider': provider.identifier})
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())

View File

@@ -5,7 +5,7 @@ from django.utils.translation import ugettext_lazy as _
from django.views import View
from django.views.generic import FormView, TemplateView
from pretix.base.models import LogEntry, OrderPayment, OrderRefund
from pretix.base.models import LogEntry
from pretix.base.services.update_check import check_result_table, update_check
from pretix.base.settings import GlobalSettingsObject
from pretix.control.forms.global_settings import (
@@ -71,15 +71,3 @@ class LogDetailView(AdministratorPermissionRequiredMixin, View):
def get(self, request, *args, **kwargs):
le = get_object_or_404(LogEntry, pk=request.GET.get('pk'))
return JsonResponse({'data': le.parsed_data})
class PaymentDetailView(AdministratorPermissionRequiredMixin, View):
def get(self, request, *args, **kwargs):
p = get_object_or_404(OrderPayment, pk=request.GET.get('pk'))
return JsonResponse({'data': p.info_data})
class RefundDetailView(AdministratorPermissionRequiredMixin, View):
def get(self, request, *args, **kwargs):
p = get_object_or_404(OrderRefund, pk=request.GET.get('pk'))
return JsonResponse({'data': p.info_data})

View File

@@ -18,12 +18,11 @@ from django.views.generic.edit import DeleteView
from pretix.base.forms import I18nFormSet
from pretix.base.models import (
CartPosition, Item, ItemCategory, ItemVariation, Order, Question,
QuestionAnswer, QuestionOption, Quota, Voucher,
CachedTicket, CartPosition, Item, ItemCategory, ItemVariation, Order,
Question, QuestionAnswer, QuestionOption, Quota, Voucher,
)
from pretix.base.models.event import SubEvent
from pretix.base.models.items import ItemAddOn, ItemBundle
from pretix.base.services.tickets import invalidate_cache
from pretix.control.forms.item import (
CategoryForm, ItemAddOnForm, ItemAddOnsFormSet, ItemBundleForm,
ItemBundleFormSet, ItemCreateForm, ItemUpdateForm, ItemVariationForm,
@@ -876,7 +875,7 @@ class ItemUpdateGeneral(ItemDetailMixin, EventPermissionRequiredMixin, UpdateVie
self.object.log_action(
'pretix.event.item.changed', user=self.request.user, data=data
)
invalidate_cache.apply_async(kwargs={'event': self.request.event.pk, 'item': self.object.pk})
CachedTicket.objects.filter(order_position__item=self.item).delete()
for f in self.plugin_forms:
f.save()
return super().form_valid(form)

View File

@@ -43,7 +43,6 @@ from pretix.base.models.orders import (
)
from pretix.base.models.tax import EU_COUNTRIES
from pretix.base.payment import PaymentException
from pretix.base.services import tickets
from pretix.base.services.export import export
from pretix.base.services.invoices import (
generate_cancellation, generate_invoice, invoice_pdf, invoice_pdf_task,
@@ -1240,11 +1239,7 @@ class OrderChange(OrderView):
return False
try:
if p.form.cleaned_data['operation_cancel']:
ocm.cancel(p)
continue
if p.form.cleaned_data['itemvar']:
if p.form.cleaned_data['operation'] == 'product':
if '-' in p.form.cleaned_data['itemvar']:
itemid, varid = p.form.cleaned_data['itemvar'].split('-')
else:
@@ -1255,19 +1250,16 @@ class OrderChange(OrderView):
variation = ItemVariation.objects.get(pk=varid, item=item)
else:
variation = None
if item != p.item or variation != p.variation:
ocm.change_item(p, item, variation)
if self.request.event.has_subevents and p.form.cleaned_data['subevent'] and p.form.cleaned_data['subevent'] != p.subevent:
ocm.change_subevent(p, p.form.cleaned_data['subevent'])
if p.form.cleaned_data['price'] != p.price:
ocm.change_item(p, item, variation, keep_price=p.form.cleaned_data['change_product_keep_price'])
elif p.form.cleaned_data['operation'] == 'price':
ocm.change_price(p, p.form.cleaned_data['price'])
if p.form.cleaned_data['operation_split']:
elif p.form.cleaned_data['operation'] == 'subevent':
ocm.change_subevent(p, p.form.cleaned_data['subevent'])
elif p.form.cleaned_data['operation'] == 'cancel':
ocm.cancel(p)
elif p.form.cleaned_data['operation'] == 'split':
ocm.split(p)
if p.form.cleaned_data['operation_secret']:
elif p.form.cleaned_data['operation'] == 'secret':
ocm.regenerate_secret(p)
except OrderError as e:
@@ -1327,7 +1319,8 @@ class OrderModifyInformation(OrderQuestionsViewMixin, OrderView):
'you need to do this manually.')
messages.success(self.request, _(success_message))
tickets.invalidate_cache.apply_async(kwargs={'event': self.request.event.pk, 'order': self.order.pk})
CachedTicket.objects.filter(order_position__order=self.order).delete()
CachedCombinedTicket.objects.filter(order=self.order).delete()
order_modified.send(sender=self.request.event, order=self.order)
return redirect(self.get_order_url())
@@ -1370,7 +1363,8 @@ class OrderContactChange(OrderView):
for op in self.order.all_positions.all():
op.secret = generate_position_secret()
op.save()
tickets.invalidate_cache.apply_async(kwargs={'event': self.request.event.pk, 'order': self.order.pk})
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()

View File

@@ -518,7 +518,7 @@ class TeamMemberView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin,
if 'remove-member' in request.POST:
try:
user = User.objects.get(pk=request.POST.get('remove-member'))
except (User.DoesNotExist, ValueError):
except User.DoesNotExist:
pass
else:
other_admin_teams = self.request.organizer.teams.exclude(pk=self.object.pk).filter(
@@ -542,7 +542,7 @@ class TeamMemberView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin,
elif 'remove-invite' in request.POST:
try:
invite = self.object.invites.get(pk=request.POST.get('remove-invite'))
except (TeamInvite.DoesNotExist, ValueError):
except TeamInvite.DoesNotExist:
messages.error(self.request, _('Invalid invite selected.'))
return redirect(self.get_success_url())
else:
@@ -555,26 +555,10 @@ class TeamMemberView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin,
messages.success(self.request, _('The invite has been revoked.'))
return redirect(self.get_success_url())
elif 'resend-invite' in request.POST:
try:
invite = self.object.invites.get(pk=request.POST.get('resend-invite'))
except (TeamInvite.DoesNotExist, ValueError):
messages.error(self.request, _('Invalid invite selected.'))
return redirect(self.get_success_url())
else:
self._send_invite(invite)
self.object.log_action(
'pretix.team.invite.resent', user=self.request.user, data={
'email': invite.email
}
)
messages.success(self.request, _('The invite has been resent.'))
return redirect(self.get_success_url())
elif 'remove-token' in request.POST:
try:
token = self.object.tokens.get(pk=request.POST.get('remove-token'))
except (TeamAPIToken.DoesNotExist, ValueError):
except TeamAPIToken.DoesNotExist:
messages.error(self.request, _('Invalid token selected.'))
return redirect(self.get_success_url())
else:

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-04-16 11:35+0000\n"
"POT-Creation-Date: 2019-04-03 13:19+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Automatically generated\n"
"Language-Team: none\n"

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-04-16 11:35+0000\n"
"POT-Creation-Date: 2019-04-03 13:19+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Automatically generated\n"
"Language-Team: none\n"

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-04-16 11:35+0000\n"
"POT-Creation-Date: 2019-04-03 13:19+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Automatically generated\n"
"Language-Team: none\n"

File diff suppressed because it is too large Load Diff

View File

@@ -6,7 +6,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-04-16 11:35+0000\n"
"POT-Creation-Date: 2019-04-03 13:19+0000\n"
"PO-Revision-Date: 2018-04-24 14:22+0000\n"
"Last-Translator: Pernille Thorsen <perth@aarhus.dk>\n"
"Language-Team: Danish <https://translate.pretix.eu/projects/pretix/pretix-js/"

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-04-16 11:35+0000\n"
"POT-Creation-Date: 2019-04-03 13:19+0000\n"
"PO-Revision-Date: 2019-03-22 15:00+0000\n"
"Last-Translator: Raphael Michel <michel@rami.io>\n"
"Language-Team: German <https://translate.pretix.eu/projects/pretix/pretix-js/"

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-04-16 11:35+0000\n"
"POT-Creation-Date: 2019-04-03 13:19+0000\n"
"PO-Revision-Date: 2019-03-22 15:00+0000\n"
"Last-Translator: Raphael Michel <michel@rami.io>\n"
"Language-Team: German (informal) <https://translate.pretix.eu/projects/"

File diff suppressed because it is too large Load Diff

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-04-16 11:35+0000\n"
"POT-Creation-Date: 2019-04-03 13:19+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-04-16 11:35+0000\n"
"POT-Creation-Date: 2019-04-03 13:19+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Automatically generated\n"
"Language-Team: none\n"

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-04-16 11:35+0000\n"
"POT-Creation-Date: 2019-04-03 13:19+0000\n"
"PO-Revision-Date: 2019-03-31 08:00+0000\n"
"Last-Translator: oocf <oswaldocerna@gmail.com>\n"
"Language-Team: Spanish <https://translate.pretix.eu/projects/pretix/pretix-"

File diff suppressed because it is too large Load Diff

View File

@@ -6,7 +6,7 @@ msgid ""
msgstr ""
"Project-Id-Version: French\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-04-16 11:35+0000\n"
"POT-Creation-Date: 2019-04-03 13:19+0000\n"
"PO-Revision-Date: 2018-10-28 10:23+0000\n"
"Last-Translator: Arnaud Vergnet <keplyx@gmail.com>\n"
"Language-Team: French <https://translate.pretix.eu/projects/pretix/pretix-js/"

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-04-16 11:35+0000\n"
"POT-Creation-Date: 2019-04-03 13:19+0000\n"
"PO-Revision-Date: 2019-01-02 08:20+0000\n"
"Last-Translator: amefad <fame@libero.it>\n"
"Language-Team: Italian <https://translate.pretix.eu/projects/pretix/pretix-"

File diff suppressed because it is too large Load Diff

View File

@@ -6,8 +6,8 @@ msgid ""
msgstr ""
"Project-Id-Version: 1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-04-16 11:35+0000\n"
"PO-Revision-Date: 2019-04-16 05:00+0000\n"
"POT-Creation-Date: 2019-04-03 13:19+0000\n"
"PO-Revision-Date: 2019-03-24 05:00+0000\n"
"Last-Translator: Maarten van den Berg <maartenberg1@gmail.com>\n"
"Language-Team: Dutch <https://translate.pretix.eu/projects/pretix/pretix-js/"
"nl/>\n"
@@ -249,9 +249,10 @@ msgstr "Winkelwagen is verlopen"
#: pretix/static/pretixpresale/js/ui/cart.js:46
msgid "The items in your cart are reserved for you for one minute."
msgid_plural "The items in your cart are reserved for you for {num} minutes."
msgstr[0] "De items in uw winkelwagen zijn nog één voor u gereserveerd."
msgstr[0] ""
"De items in uw winkelwagen zijn voor u gereserveerd voor één minuut."
msgstr[1] ""
"De items in uw winkelwagen zijn nog {num} minuten voor u gereserveerd."
"De items in uw winkelwagen zijn voor u gereserveerd voor {num} minuten."
#: pretix/static/pretixpresale/js/widget/widget.js:14
msgctxt "widget"

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-04-16 11:35+0000\n"
"POT-Creation-Date: 2019-04-03 13:19+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Automatically generated\n"
"Language-Team: none\n"

File diff suppressed because it is too large Load Diff

View File

@@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-04-16 11:35+0000\n"
"PO-Revision-Date: 2019-04-16 05:00+0000\n"
"POT-Creation-Date: 2019-04-03 13:19+0000\n"
"PO-Revision-Date: 2019-03-27 01:00+0000\n"
"Last-Translator: Maarten van den Berg <maartenberg1@gmail.com>\n"
"Language-Team: Dutch (informal) <https://translate.pretix.eu/projects/pretix/"
"pretix-js/nl_Informal/>\n"
@@ -252,9 +252,9 @@ msgstr "Winkelwagen is verlopen"
msgid "The items in your cart are reserved for you for one minute."
msgid_plural "The items in your cart are reserved for you for {num} minutes."
msgstr[0] ""
"De items in je winkelwagen zijn nog één minuut voor je gereserveerd."
"De items in je winkelwagen zijn voor je gereserveerd voor één minuut."
msgstr[1] ""
"De items in je winkelwagen zijn nog {num} minuten voor je gereserveerd."
"De items in je winkelwagen zijn voor je gereserveerd voor {num} minuten."
#: pretix/static/pretixpresale/js/widget/widget.js:14
msgctxt "widget"

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-04-16 11:35+0000\n"
"POT-Creation-Date: 2019-04-03 13:19+0000\n"
"PO-Revision-Date: 2019-03-15 11:19+0000\n"
"Last-Translator: Serge Bazanski <q3k@hackerspace.pl>\n"
"Language-Team: Polish <https://translate.pretix.eu/projects/pretix/pretix-js/"

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-04-16 11:35+0000\n"
"POT-Creation-Date: 2019-04-03 13:19+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Automatically generated\n"
"Language-Team: none\n"

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-04-16 11:35+0000\n"
"POT-Creation-Date: 2019-04-03 13:19+0000\n"
"PO-Revision-Date: 2019-03-19 09:00+0000\n"
"Last-Translator: Vitor Reis <vitor.reis7@gmail.com>\n"
"Language-Team: Portuguese (Brazil) <https://translate.pretix.eu/projects/"

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-04-16 11:35+0000\n"
"POT-Creation-Date: 2019-04-03 13:19+0000\n"
"PO-Revision-Date: 2019-01-02 08:21+0000\n"
"Last-Translator: Alexey Zh <write2aracon@gmail.com>\n"
"Language-Team: Russian <https://translate.pretix.eu/projects/pretix/pretix-"

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-04-16 11:35+0000\n"
"POT-Creation-Date: 2019-04-03 13:19+0000\n"
"PO-Revision-Date: 2018-09-03 06:36+0000\n"
"Last-Translator: Yunus Fırat Pişkin <firat.piskin@idvlabs.com>\n"
"Language-Team: Turkish <https://translate.pretix.eu/projects/pretix/pretix-"

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-04-16 11:35+0000\n"
"POT-Creation-Date: 2019-04-03 13:19+0000\n"
"PO-Revision-Date: 2019-03-28 14:00+0000\n"
"Last-Translator: yichengsd <sunzl@jxepub.com>\n"
"Language-Team: Chinese (Simplified) <https://translate.pretix.eu/projects/"

View File

@@ -28,7 +28,8 @@ def parse(data, hint):
resrow['amount'] = re.sub('[^0-9,+.-]', '', resrow['amount'])
if hint.get('date') is not None:
resrow['date'] = row[int(hint.get('date'))].strip()
if len(resrow['amount']) == 0 or 'amount' not in resrow or resrow['date'] == '':
if len(resrow['amount']) == 0 or 'amount' not in resrow \
or len(resrow['reference']) == 0 or resrow['date'] == '':
# This is probably a headline or something other special.
continue
if resrow['reference'] or resrow['payer']:

View File

@@ -132,7 +132,6 @@ class BankTransfer(BasePaymentProvider):
d.move_to_end('bank_details_sepa_iban', last=False)
d.move_to_end('bank_details_sepa_name', last=False)
d.move_to_end('bank_details_type', last=False)
d.move_to_end('ack', last=False)
d.move_to_end('_enabled', last=False)
return d

View File

@@ -1,7 +1,7 @@
from django import forms
from django.utils.translation import ugettext_lazy as _
from pretix.base.services import tickets
from pretix.base.models import CachedCombinedTicket, CachedTicket
from .models import TicketLayout, TicketLayoutItem
@@ -18,7 +18,7 @@ class TicketLayoutItemForm(forms.ModelForm):
fields = ('layout',)
def __init__(self, *args, **kwargs):
self.event = kwargs.pop('event')
event = kwargs.pop('event')
self.sales_channel = kwargs.pop('sales_channel')
super().__init__(*args, **kwargs)
if self.sales_channel.identifier != 'web':
@@ -29,7 +29,7 @@ class TicketLayoutItemForm(forms.ModelForm):
else:
self.fields['layout'].label = _('PDF ticket layout')
self.fields['layout'].empty_label = _('(Event default)')
self.fields['layout'].queryset = self.event.ticket_layouts.all()
self.fields['layout'].queryset = event.ticket_layouts.all()
self.fields['layout'].required = False
def save(self, commit=True):
@@ -41,5 +41,9 @@ class TicketLayoutItemForm(forms.ModelForm):
return
else:
return super().save(commit=commit)
tickets.invalidate_cache.apply_async(kwargs={'event': self.event.pk, 'provider': 'pdf',
'item': self.instance.item_id})
CachedTicket.objects.filter(
order_position__item_id=self.instance.item, provider='pdf'
).delete()
CachedCombinedTicket.objects.filter(
order__all_positions__item=self.instance.item
).delete()

View File

@@ -18,9 +18,10 @@ from django.views.generic import CreateView, DeleteView, DetailView, ListView
from reportlab.lib import pagesizes
from reportlab.pdfgen import canvas
from pretix.base.models import CachedFile, OrderPosition
from pretix.base.models import (
CachedCombinedTicket, CachedFile, CachedTicket, OrderPosition,
)
from pretix.base.pdf import Renderer
from pretix.base.services.tickets import invalidate_cache
from pretix.control.permissions import EventPermissionRequiredMixin
from pretix.control.views.pdf import BaseEditorView
from pretix.plugins.ticketoutputpdf.forms import TicketLayoutForm
@@ -39,7 +40,12 @@ class EditorView(BaseEditorView):
def save_layout(self):
super().save_layout()
invalidate_cache.apply_async(kwargs={'event': self.request.event.pk, 'provider': 'pdf'})
CachedTicket.objects.filter(
order_position__order__event=self.request.event, provider='pdf'
).delete()
CachedCombinedTicket.objects.filter(
order__event=self.request.event, provider='pdf'
).delete()
def get_layout_settings_key(self):
return 'ticketoutput_pdf_layout'
@@ -206,7 +212,10 @@ class LayoutEditorView(BaseEditorView):
self.layout.save(update_fields=['layout'])
self.layout.log_action(action='pretix.plugins.ticketoutputpdf.layout.changed', user=self.request.user,
data={'layout': self.request.POST.get("data")})
invalidate_cache.apply_async(kwargs={'event': self.request.event.pk, 'provider': 'pdf'})
for ct in CachedTicket.objects.filter(order_position__order__event=self.request.event, provider='pdf'):
ct.delete()
for ct in CachedCombinedTicket.objects.filter(order__event=self.request.event, provider='pdf'):
ct.delete()
def get_default_background(self):
return static('pretixpresale/pdf/ticket_default_a4.pdf')
@@ -242,4 +251,9 @@ class LayoutEditorView(BaseEditorView):
if self.layout.background:
self.layout.background.delete()
self.layout.background.save('background.pdf', f.file)
invalidate_cache.apply_async(kwargs={'event': self.request.event.pk, 'provider': 'pdf'})
CachedTicket.objects.filter(
order_position__order__event=self.request.event, provider='pdf'
).delete()
CachedCombinedTicket.objects.filter(
order__event=self.request.event, provider='pdf'
).delete()

View File

@@ -14,7 +14,7 @@ from django.utils.translation import (
from django.views.generic.base import TemplateResponseMixin
from pretix.base.models import Order
from pretix.base.models.orders import InvoiceAddress, OrderPayment
from pretix.base.models.orders import InvoiceAddress
from pretix.base.services.cart import (
get_fees, set_cart_addons, update_tax_rates,
)
@@ -721,7 +721,7 @@ class ConfirmStep(CartMixin, AsyncAction, TemplateFlowStep):
return self.get_step_url(self.request)
def get_order_url(self, order):
payment = order.payments.filter(state__in=[OrderPayment.PAYMENT_STATE_CREATED, OrderPayment.PAYMENT_STATE_PENDING]).first()
payment = order.payments.first()
if not payment:
return eventreverse(self.request.event, 'presale:event.order', kwargs={
'order': order.code,

View File

@@ -16,12 +16,12 @@ class Command(BaseCommand):
help = "Re-generate all custom stylesheets and scripts"
def handle(self, *args, **options):
for es in Event_SettingsStore.objects.filter(key="presale_css_file"):
regenerate_css.apply_async(args=(es.object_id,))
for es in Organizer_SettingsStore.objects.filter(key="presale_css_file"):
regenerate_organizer_css.apply_async(args=(es.object_id,))
for es in Event_SettingsStore.objects.filter(key="presale_css_file").order_by('-object__date_from'):
regenerate_css.apply_async(args=(es.object_id,))
gs = GlobalSettingsObject()
for lc, ll in settings.LANGUAGES:
data = generate_widget_js(lc).encode()

View File

@@ -1,7 +1,6 @@
{% extends "pretixpresale/event/checkout_base.html" %}
{% load i18n %}
{% load bootstrap3 %}
{% load rich_text %}
{% block inner %}
<p>
{% trans "For some of the products in your cart, you can choose additional options before you continue." %}
@@ -31,9 +30,6 @@
{% for c in form.categories %}
<fieldset>
<legend>{{ c.category.name }}</legend>
{% if c.category.description %}
{{ c.category.description|rich_text }}
{% endif %}
<p>
{% if c.min_count == c.max_count %}
{% blocktrans trimmed count min_count=c.min_count %}

View File

@@ -270,10 +270,7 @@
{% endif %}
</div>
<div class="col-md-2 col-xs-6 price">
{% if var.original_price %}
<del>{{ var.original_price|money:event.currency }}</del>
<ins>
{% elif item.original_price %}
{% if item.original_price %}
<del>{{ item.original_price|money:event.currency }}</del>
<ins>
{% endif %}
@@ -297,7 +294,7 @@
{% else %}
{{ var.display_price.gross|money:event.currency }}
{% endif %}
{% if item.original_price or var.original_price %}
{% if item.original_price %}
</ins>
{% endif %}
{% if item.includes_mixed_tax_rate %}

View File

@@ -107,13 +107,13 @@
{% endif %}
{% endif %}
{% if can_download and download_buttons and order.count_positions %}
<div class="alert alert-info info-download">
<div class="alert alert-info">
{% 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">
<p>
{% trans "Download all tickets at once:" %}
{% for b in download_buttons %}
{% if b.multi %}
@@ -131,7 +131,7 @@
{% endif %}
{% elif not download_buttons and ticket_download_date %}
{% if order.status == 'p' %}
<div class="alert alert-info info-download">
<div class="alert alert-info">
{% blocktrans trimmed with date=ticket_download_date|date:"SHORT_DATE_FORMAT" %}
You will be able to download your tickets here starting on {{ date }}.
{% endblocktrans %}
@@ -249,7 +249,7 @@
<div class="clearfix"></div>
</div>
{% if order.cancel_allowed %}
<div class="panel panel-primary panel-cancellation">
<div class="panel panel-primary cart">
<div class="panel-heading">
<h3 class="panel-title">
{% trans "Cancellation" context "action" %}

View File

@@ -29,7 +29,7 @@ from pretix.base.services.invoices import (
)
from pretix.base.services.mail import SendMailException
from pretix.base.services.orders import cancel_order, change_payment_provider
from pretix.base.services.tickets import generate, invalidate_cache
from pretix.base.services.tickets import generate
from pretix.base.signals import (
allow_ticket_download, order_modified, register_ticket_outputs,
)
@@ -548,7 +548,6 @@ class OrderModify(EventViewMixin, OrderDetailMixin, OrderQuestionsViewMixin, Tem
'to regenerate your invoice.')
messages.success(self.request, _(success_message))
invalidate_cache.apply_async(kwargs={'event': self.request.event.pk, 'order': self.order.pk})
CachedTicket.objects.filter(order_position__order=self.order).delete()
CachedCombinedTicket.objects.filter(order=self.order).delete()
return redirect(self.get_order_url())

View File

@@ -90,12 +90,7 @@ def generate_widget_js(lang):
c.translation = DjangoTranslation(lang, domain='djangojs')
catalog, plural = c.get_catalog(), c.get_plural()
str_wl = (
'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su',
'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August',
'September', 'October', 'November', 'December'
)
catalog = dict((k, v) for k, v in catalog.items() if k.startswith('widget\u0004') or k in str_wl)
catalog = dict((k, v) for k, v in catalog.items() if k.startswith('widget\u0004'))
template = Engine().from_string(js_catalog_template)
context = Context({
'catalog_str': indent(json.dumps(
@@ -294,14 +289,12 @@ class WidgetAPIProductList(EventListMixin, View):
event = ev.event
else:
event = ev
tz = pytz.timezone(e['timezone'])
events.append({
'name': str(ev.name),
'time': date_format(ev.date_from.astimezone(tz), 'TIME_FORMAT') if e.get('time') and event.settings.show_times else
None,
'time': date_format(e['time'], 'TIME_FORMAT') if e.get('time') and event.settings.show_times else None,
'continued': e['continued'],
'date_range': ev.get_date_range_display() + (
" " + date_format(ev.date_from.astimezone(tz), "TIME_FORMAT") if event.settings.show_times else ""
" " + date_format(ev.date_from, "TIME_FORMAT") if event.settings.show_times else ""
),
'availability': self._get_availability(ev, event),
'event_url': build_absolute_uri(event, 'presale:event.index'),
@@ -372,12 +365,11 @@ class WidgetAPIProductList(EventListMixin, View):
evs = self.request.event.subevents_sorted(
self.request.event.subevents_annotated(self.request.sales_channel)
)
tz = pytz.timezone(request.event.settings.timezone)
data['events'] = [
{
'name': str(ev.name),
'date_range': ev.get_date_range_display(tz) + (
(" " + ev.get_time_from_display(tz)) if ev.event.settings.show_times else ""
'date_range': ev.get_date_range_display() + (
" " + date_format(ev.date_from, "TIME_FORMAT") if ev.event.settings.show_times else ""
),
'availability': self._get_availability(ev, ev.event),
'event_url': build_absolute_uri(ev.event, 'presale:event.index'),
@@ -396,8 +388,8 @@ class WidgetAPIProductList(EventListMixin, View):
)
avail = {'color': 'none', 'text': ugettext('Event series')}
else:
dr = event.get_date_range_display(tz) + (
" " + event.get_time_from_display(tz) if event.settings.show_times else ""
dr = event.get_date_range_display() + (
" " + date_format(event.date_from, "TIME_FORMAT") if event.settings.show_times else ""
)
avail = self._get_availability(event, event)
data['events'].append({

View File

@@ -632,12 +632,12 @@ CELERY_TASK_ROUTES = ([
('pretix.base.services.cart.*', {'queue': 'checkout'}),
('pretix.base.services.orders.*', {'queue': 'checkout'}),
('pretix.base.services.mail.*', {'queue': 'mail'}),
('pretix.base.services.style.*', {'queue': 'background'}),
('pretix.base.services.update_check.*', {'queue': 'background'}),
('pretix.base.services.quotas.*', {'queue': 'background'}),
('pretix.base.services.waitinglist.*', {'queue': 'background'}),
('pretix.base.services.notifications.*', {'queue': 'notifications'}),
('pretix.api.webhooks.*', {'queue': 'notifications'}),
('pretix.presale.style.*', {'queue': 'background'}),
('pretix.plugins.banktransfer.*', {'queue': 'background'}),
],)

View File

@@ -74,11 +74,6 @@
outline: 0;
}
.btn-invisible {
background: transparent;
border: transparent;
}
.alert-primary, .alert-warning, .alert-info, .alert-success, .alert-danger {
color: #3b3b3b;
}

View File

@@ -605,18 +605,12 @@ $(function () {
}
});
$("a[data-expandlogs], a[data-expandrefund], a[data-expandpayment]").click(function (e) {
$("a[data-expandlogs]").click(function (e) {
e.preventDefault();
var $a = $(this);
var id = $(this).attr("data-id");
$a.find(".fa").removeClass("fa-eye").addClass("fa-cog fa-spin");
var url = '/control/logdetail/';
if ($a.is("[data-expandrefund]")) {
url += 'refund/'
} else if ($a.is("[data-expandpayment]")) {
url += 'payment/'
}
$.getJSON(url + '?pk=' + id, function (data) {
$.getJSON('/control/logdetail/?pk=' + id, function (data) {
if ($a.parent().tagName === "p") {
$("<pre>").html(JSON.stringify(data.data, null, 2)).insertAfter($a.parent());
} else {

View File

@@ -1,51 +0,0 @@
/*global $, gettext*/
$(function () {
// Question view
if (!$(".form-order-change").length) {
return;
}
$(".form-order-change").each(function () {
var url = $(this).attr("data-pricecalc-endpoint");
var $itemvar = $(this).find("[name*=itemvar]");
var $subevent = $(this).find("[name*=subevent]");
var $price = $(this).find("[name*=price]");
var update_price = function () {
console.log(url);
var itemvar = $itemvar.val();
var item = null;
var variation = null;
if (itemvar.indexOf("-")) {
item = parseInt(itemvar.split("-")[0]);
variation = parseInt(itemvar.split("-")[1]);
} else {
item = parseInt(itemvar);
}
$price.closest(".field-container").append("<small class=\"loading-indicator\"><span class=\"fa fa-cog fa-spin\"></span> " +
gettext("Calculating default price…") + "</small>");
$.ajax(
{
'type': 'POST',
'url': url,
'headers': {'X-CSRFToken': $("input[name=csrfmiddlewaretoken]").val()},
'data': JSON.stringify({
'item': item,
'variation': variation,
'subevent': $subevent.val(),
'locale': $("body").attr("data-pretixlocale"),
}),
'contentType': "application/json",
'success': function (data) {
$price.val(data.gross_formatted);
$price.closest(".field-container").find(".loading-indicator").remove();
},
// 'error': …
'context': this,
'dataType': 'json',
'timeout': 30000
}
);
};
$itemvar.on("change", update_price);
$subevent.on("change", update_price);
});
});

View File

@@ -407,12 +407,6 @@ table td > .checkbox input[type="checkbox"] {
}
}
.form-order-change {
.row {
padding: 5px 0;
}
}
@media(max-width: $screen-xs-max) {
.nameparts-form-group {
display: block;
@@ -461,4 +455,3 @@ table td > .checkbox input[type="checkbox"] {
}
}
}

View File

@@ -1,65 +0,0 @@
@media print {
body {
font-size: 12px;
}
a[href]:after {
content: none;
}
.container {
width: 100%;
padding: 0;
}
.page-header {
margin-top: 0;
}
.page-header .pull-right,
.thank-you,
.panel-heading a,
.download-desktop,
.download-mobile,
.info-download,
.panel-cancellation,
footer {
display: none !important;
}
.cart-row .product {
width: 50%;
}
.cart-row.has-downloads .product {
width: 80%;
}
.cart-row .count {
width: 10%;
}
.cart-row .singleprice {
width: 20%;
clear: none;
}
.cart-row .totalprice {
width: 20%;
}
.panel-body .text-right a.btn {
display: none;
}
@media(max-width: $screen-sm-max) {
.cart-row .product {
width: 50%;
}
.cart-row.has-downloads .product {
width: 80%;
}
}
@media (min-width: $screen-sm-min) {
.dl-horizontal dt {
width: 100px;
}
.dl-horizontal dd {
margin-left: 120px;
}
}
@media (min-width: 0px) {
@include make-grid(sm);
@include make-grid(md);
@include make-grid(lg);
}
}

View File

@@ -290,4 +290,3 @@ h2 .label {
@import "_iframe.scss";
@import "_a11y.scss";
@import "_print.scss";

View File

@@ -82,7 +82,7 @@ setup(
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Framework :: Django :: 2.2'
'Framework :: Django :: 2.1'
],
keywords='tickets web shop ecommerce',

View File

@@ -319,7 +319,6 @@ def test_item_detail_variations(token_client, organizer, event, team, item):
"active": True,
"description": None,
"position": 0,
"original_price": None
}]
res["has_variations"] = True
resp = token_client.get('/api/v1/organizers/{}/events/{}/items/{}/'.format(organizer.slug, event.slug,
@@ -942,8 +941,7 @@ TEST_VARIATIONS_RES = {
"description": None,
"position": 0,
"default_price": None,
"price": "23.00",
"original_price": None
"price": "23.00"
}
TEST_VARIATIONS_UPDATE = {
@@ -954,7 +952,6 @@ TEST_VARIATIONS_UPDATE = {
"description": None,
"position": 1,
"default_price": "20.0",
"original_price": None
}
@@ -990,7 +987,6 @@ def test_variations_create(token_client, organizer, event, item, variation):
"description": None,
"position": 1,
"default_price": None,
"original_price": "23.42",
"price": 23.0
},
format='json'
@@ -1029,7 +1025,6 @@ def test_variations_update(token_client, organizer, event, item, item3, variatio
res["id"] = variation.pk
res["price"] = "20.00"
res["default_price"] = "20.00"
res["original_price"] = "50.00"
resp = token_client.patch(
'/api/v1/organizers/{}/events/{}/items/{}/variations/{}/'.format(organizer.slug, event.slug, item.pk, variation.pk),
{
@@ -1037,8 +1032,7 @@ def test_variations_update(token_client, organizer, event, item, item3, variatio
"en": "ChildC2"
},
"position": 1,
"default_price": "20.00",
"original_price": "50.00"
"default_price": "20.00"
},
format='json'
)

View File

@@ -2798,222 +2798,3 @@ def test_order_resend_link(token_client, organizer, event, order):
), format='json', data={}
)
assert resp.status_code == 400
@pytest.mark.django_db
def test_orderposition_price_calculation(token_client, organizer, event, order, item):
op = order.positions.first()
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/orderpositions/{}/price_calc/'.format(organizer.slug, event.slug, op.pk),
data={
}
)
assert resp.status_code == 200
assert resp.data == {
'gross': Decimal('23.00'),
'gross_formatted': '23.00',
'name': '',
'net': Decimal('23.00'),
'rate': Decimal('0.00'),
'tax': Decimal('0.00')
}
@pytest.mark.django_db
def test_orderposition_price_calculation_item_with_tax(token_client, organizer, event, order, item, taxrule):
item2 = event.items.create(name="Budget Ticket", default_price=23, tax_rule=taxrule)
op = order.positions.first()
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/orderpositions/{}/price_calc/'.format(organizer.slug, event.slug, op.pk),
data={
'item': item2.pk
}
)
assert resp.status_code == 200
assert resp.data == {
'gross': Decimal('23.00'),
'gross_formatted': '23.00',
'name': '',
'net': Decimal('19.33'),
'rate': Decimal('19.00'),
'tax': Decimal('3.67')
}
@pytest.mark.django_db
def test_orderposition_price_calculation_item_with_variation(token_client, organizer, event, order):
item2 = event.items.create(name="Budget Ticket", default_price=23)
var = item2.variations.create(default_price=12, value="XS")
op = order.positions.first()
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/orderpositions/{}/price_calc/'.format(organizer.slug, event.slug, op.pk),
data={
'item': item2.pk,
'variation': var.pk
}
)
assert resp.status_code == 200
assert resp.data == {
'gross': Decimal('12.00'),
'gross_formatted': '12.00',
'name': '',
'net': Decimal('12.00'),
'rate': Decimal('0.00'),
'tax': Decimal('0.00')
}
@pytest.mark.django_db
def test_orderposition_price_calculation_subevent(token_client, organizer, event, order, subevent):
item2 = event.items.create(name="Budget Ticket", default_price=23)
op = order.positions.first()
op.subevent = subevent
op.save()
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/orderpositions/{}/price_calc/'.format(organizer.slug, event.slug, op.pk),
data={
'item': item2.pk,
'subevent': subevent.pk
}
)
assert resp.status_code == 200
assert resp.data == {
'gross': Decimal('23.00'),
'gross_formatted': '23.00',
'name': '',
'net': Decimal('23.00'),
'rate': Decimal('0.00'),
'tax': Decimal('0.00')
}
@pytest.mark.django_db
def test_orderposition_price_calculation_subevent_with_override(token_client, organizer, event, order, subevent):
item2 = event.items.create(name="Budget Ticket", default_price=23)
se2 = event.subevents.create(name="Foobar", date_from=datetime.datetime(2017, 12, 27, 10, 0, 0, tzinfo=UTC))
se2.subeventitem_set.create(item=item2, price=12)
op = order.positions.first()
op.subevent = subevent
op.save()
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/orderpositions/{}/price_calc/'.format(organizer.slug, event.slug, op.pk),
data={
'item': item2.pk,
'subevent': se2.pk
}
)
assert resp.status_code == 200
assert resp.data == {
'gross': Decimal('12.00'),
'gross_formatted': '12.00',
'name': '',
'net': Decimal('12.00'),
'rate': Decimal('0.00'),
'tax': Decimal('0.00')
}
@pytest.mark.django_db
def test_orderposition_price_calculation_voucher_matching(token_client, organizer, event, order, subevent, item):
item2 = event.items.create(name="Budget Ticket", default_price=23)
q = event.quotas.create(name="Quota")
q.items.add(item)
q.items.add(item2)
voucher = event.vouchers.create(price_mode="set", value=15, quota=q)
op = order.positions.first()
op.voucher = voucher
op.save()
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/orderpositions/{}/price_calc/'.format(organizer.slug, event.slug, op.pk),
data={
'item': item2.pk,
}
)
assert resp.status_code == 200
assert resp.data == {
'gross': Decimal('15.00'),
'gross_formatted': '15.00',
'name': '',
'net': Decimal('15.00'),
'rate': Decimal('0.00'),
'tax': Decimal('0.00')
}
@pytest.mark.django_db
def test_orderposition_price_calculation_voucher_not_matching(token_client, organizer, event, order, subevent, item):
item2 = event.items.create(name="Budget Ticket", default_price=23)
q = event.quotas.create(name="Quota")
q.items.add(item)
voucher = event.vouchers.create(price_mode="set", value=15, quota=q)
op = order.positions.first()
op.voucher = voucher
op.save()
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/orderpositions/{}/price_calc/'.format(organizer.slug, event.slug, op.pk),
data={
'item': item2.pk,
}
)
assert resp.status_code == 200
assert resp.data == {
'gross': Decimal('23.00'),
'gross_formatted': '23.00',
'name': '',
'net': Decimal('23.00'),
'rate': Decimal('0.00'),
'tax': Decimal('0.00')
}
@pytest.mark.django_db
def test_orderposition_price_calculation_net_price(token_client, organizer, event, order, subevent, item, taxrule):
taxrule.price_includes_tax = False
taxrule.save()
item2 = event.items.create(name="Budget Ticket", default_price=10, tax_rule=taxrule)
op = order.positions.first()
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/orderpositions/{}/price_calc/'.format(organizer.slug, event.slug, op.pk),
data={
'item': item2.pk,
}
)
assert resp.status_code == 200
assert resp.data == {
'gross': Decimal('11.90'),
'gross_formatted': '11.90',
'name': '',
'net': Decimal('10.00'),
'rate': Decimal('19.00'),
'tax': Decimal('1.90')
}
@pytest.mark.django_db
def test_orderposition_price_calculation_reverse_charge(token_client, organizer, event, order, subevent, item, taxrule):
taxrule.price_includes_tax = False
taxrule.eu_reverse_charge = True
taxrule.home_country = Country('DE')
taxrule.save()
order.invoice_address.is_business = True
order.invoice_address.vat_id = 'ATU1234567'
order.invoice_address.vat_id_validated = True
order.invoice_address.country = Country('AT')
order.invoice_address.save()
item2 = event.items.create(name="Budget Ticket", default_price=10, tax_rule=taxrule)
op = order.positions.first()
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/orderpositions/{}/price_calc/'.format(organizer.slug, event.slug, op.pk),
data={
'item': item2.pk,
}
)
assert resp.status_code == 200
assert resp.data == {
'gross': Decimal('10.00'),
'gross_formatted': '10.00',
'name': '',
'net': Decimal('10.00'),
'rate': Decimal('0.00'),
'tax': Decimal('0.00')
}

View File

@@ -25,7 +25,6 @@ event_permission_sub_urls = [
('get', 'can_view_orders', 'orders/', 200),
('get', 'can_view_orders', 'orderpositions/', 200),
('delete', 'can_change_orders', 'orderpositions/1/', 404),
('post', 'can_change_orders', 'orderpositions/1/price_calc/', 404),
('get', 'can_view_vouchers', 'vouchers/', 200),
('get', 'can_view_orders', 'invoices/', 200),
('get', 'can_view_orders', 'invoices/1/', 404),

View File

@@ -41,7 +41,7 @@ def test_expiry_days(event):
event.settings.set('payment_term_weekdays', False)
order = _create_order(event, email='dummy@example.org', positions=[],
now_dt=today, payment_provider=FreeOrderProvider(event),
locale='de')[0]
locale='de')
assert (order.expires - today).days == 5
@@ -52,14 +52,14 @@ def test_expiry_weekdays(event):
event.settings.set('payment_term_weekdays', True)
order = _create_order(event, email='dummy@example.org', positions=[],
now_dt=today, payment_provider=FreeOrderProvider(event),
locale='de')[0]
locale='de')
assert (order.expires - today).days == 6
assert order.expires.weekday() == 0
today = make_aware(datetime(2016, 9, 19, 15, 0, 0, 0))
order = _create_order(event, email='dummy@example.org', positions=[],
now_dt=today, payment_provider=FreeOrderProvider(event),
locale='de')[0]
locale='de')
assert (order.expires - today).days == 7
assert order.expires.weekday() == 0
@@ -72,12 +72,12 @@ def test_expiry_last(event):
event.settings.set('payment_term_last', now() + timedelta(days=3))
order = _create_order(event, email='dummy@example.org', positions=[],
now_dt=today, payment_provider=FreeOrderProvider(event),
locale='de')[0]
locale='de')
assert (order.expires - today).days == 3
event.settings.set('payment_term_last', now() + timedelta(days=7))
order = _create_order(event, email='dummy@example.org', positions=[],
now_dt=today, payment_provider=FreeOrderProvider(event),
locale='de')[0]
locale='de')
assert (order.expires - today).days == 5
@@ -93,7 +93,7 @@ def test_expiry_last_relative(event):
))
order = _create_order(event, email='dummy@example.org', positions=[],
now_dt=today, payment_provider=FreeOrderProvider(event),
locale='de')[0]
locale='de')
assert (order.expires - today).days == 3
@@ -124,7 +124,7 @@ def test_expiry_last_relative_subevents(event):
))
order = _create_order(event, email='dummy@example.org', positions=[cp1, cp2],
now_dt=today, payment_provider=FreeOrderProvider(event),
locale='de')[0]
locale='de')
assert (order.expires - today).days == 6
@@ -136,7 +136,7 @@ def test_expiry_dst(event):
today = tz.localize(datetime(2016, 10, 29, 12, 0, 0)).astimezone(utc)
order = _create_order(event, email='dummy@example.org', positions=[],
now_dt=today, payment_provider=FreeOrderProvider(event),
locale='de')[0]
locale='de')
localex = order.expires.astimezone(tz)
assert (localex.hour, localex.minute) == (23, 59)
@@ -611,27 +611,49 @@ class OrderChangeManagerTests(TestCase):
self.op1.refresh_from_db()
self.order.refresh_from_db()
assert self.op1.subevent == se2
assert self.op1.price == Decimal('23.00')
assert self.op1.price == 12
assert self.order.total == self.op1.price + self.op2.price
def test_change_subevent_with_price_success(self):
def test_change_subevent_reverse_charge(self):
self._enable_reverse_charge()
self.event.has_subevents = True
self.event.save()
se1 = self.event.subevents.create(name="Foo", date_from=now())
se2 = self.event.subevents.create(name="Bar", date_from=now())
SubEventItem.objects.create(subevent=se2, item=self.ticket, price=12)
SubEventItem.objects.create(subevent=se2, item=self.ticket, price=10.7)
self.op1.subevent = se1
self.op1.save()
self.quota.subevent = se2
self.quota.save()
self.ocm.change_subevent(self.op1, se2)
self.ocm.change_price(self.op1, Decimal('12.00'))
self.ocm.commit()
self.op1.refresh_from_db()
self.order.refresh_from_db()
assert self.op1.subevent == se2
assert self.op1.price == Decimal('12.00')
assert self.op1.price == Decimal('10.00')
assert self.op1.tax_value == Decimal('0.00')
assert self.order.total == self.op1.price + self.op2.price
def test_change_subevent_net_price(self):
self.event.has_subevents = True
self.event.save()
se1 = self.event.subevents.create(name="Foo", date_from=now())
se2 = self.event.subevents.create(name="Bar", date_from=now())
self.tr7.price_includes_tax = False
self.tr7.save()
SubEventItem.objects.create(subevent=se2, item=self.ticket, price=10)
self.op1.subevent = se1
self.op1.save()
self.quota.subevent = se2
self.quota.save()
self.ocm.change_subevent(self.op1, se2)
self.ocm.commit()
self.op1.refresh_from_db()
self.order.refresh_from_db()
assert self.op1.subevent == se2
assert self.op1.price == Decimal('10.70')
assert self.order.total == self.op1.price + self.op2.price
def test_change_subevent_sold_out(self):
@@ -658,14 +680,14 @@ class OrderChangeManagerTests(TestCase):
def test_change_item_keep_price(self):
p = self.op1.price
self.ocm.change_item(self.op1, self.shirt, None)
tv = self.op1.tax_value
self.ocm.change_item(self.op1, self.shirt, None, keep_price=True)
self.ocm.commit()
self.op1.refresh_from_db()
self.order.refresh_from_db()
assert self.op1.item == self.shirt
assert self.op1.price == p
assert self.op1.tax_value == Decimal('3.67')
assert self.op1.tax_rule == self.shirt.tax_rule
assert self.op1.tax_value == tv
def test_change_item_success(self):
self.ocm.change_item(self.op1, self.shirt, None)
@@ -673,23 +695,36 @@ class OrderChangeManagerTests(TestCase):
self.op1.refresh_from_db()
self.order.refresh_from_db()
assert self.op1.item == self.shirt
assert self.op1.price == Decimal('23.00')
assert self.op1.price == self.shirt.default_price
assert self.op1.tax_rate == self.shirt.tax_rule.rate
assert round_decimal(self.op1.price * (1 - 100 / (100 + self.op1.tax_rate))) == self.op1.tax_value
assert self.order.total == self.op1.price + self.op2.price
def test_change_item_with_price_success(self):
def test_change_item_net_price_success(self):
self.tr19.price_includes_tax = False
self.tr19.save()
self.ocm.change_item(self.op1, self.shirt, None)
self.ocm.change_price(self.op1, Decimal('12.00'))
self.ocm.commit()
self.op1.refresh_from_db()
self.order.refresh_from_db()
assert self.op1.item == self.shirt
assert self.op1.price == Decimal('12.00')
assert self.op1.price == Decimal('14.28')
assert self.op1.tax_rate == self.shirt.tax_rule.rate
assert round_decimal(self.op1.price * (1 - 100 / (100 + self.op1.tax_rate))) == self.op1.tax_value
assert self.order.total == self.op1.price + self.op2.price
def test_change_item_reverse_charge(self):
self._enable_reverse_charge()
self.ocm.change_item(self.op1, self.shirt, None)
self.ocm.commit()
self.op1.refresh_from_db()
self.order.refresh_from_db()
assert self.op1.item == self.shirt
assert self.op1.price == Decimal('10.08')
assert self.op1.tax_rate == Decimal('0.00')
assert self.op1.tax_value == Decimal('0.00')
assert self.order.total == self.op1.price + self.op2.price
def test_change_price_success(self):
self.ocm.change_price(self.op1, Decimal('24.00'))
self.ocm.commit()
@@ -703,7 +738,7 @@ class OrderChangeManagerTests(TestCase):
def test_change_price_net_success(self):
self.tr7.price_includes_tax = False
self.tr7.save()
self.ocm.change_price(self.op1, Decimal('10.70'))
self.ocm.change_price(self.op1, Decimal('10.00'))
self.ocm.commit()
self.op1.refresh_from_db()
self.order.refresh_from_db()

View File

@@ -308,15 +308,6 @@ class FakeRedis(object):
def __init__(self):
self.storage = {}
def pipeline(self):
return self
def hincrbyfloat(self, rkey, key, amount):
return self
def commit(self):
return self
def exists(self, rkey):
print(rkey in self.storage)
return rkey in self.storage
@@ -361,7 +352,6 @@ class PasswordRecoveryFormTest(TestCase):
fake_redis = FakeRedis()
m = self.monkeypatch
m.setattr('django_redis.get_redis_connection', fake_redis.get_redis_connection, raising=False)
m.setattr('pretix.base.metrics.redis', fake_redis, raising=False)
djmail.outbox = []

View File

@@ -935,9 +935,11 @@ class OrderChangeTests(SoupTest):
self.client.post('/control/event/{}/{}/orders/{}/change'.format(
self.event.organizer.slug, self.event.slug, self.order.code
), {
'op-{}-operation'.format(self.op1.pk): 'product',
'op-{}-itemvar'.format(self.op1.pk): str(self.shirt.pk),
'op-{}-price'.format(self.op1.pk): str('12.00'),
'add-itemvar': str(self.ticket.pk),
'op-{}-operation'.format(self.op2.pk): '',
'op-{}-itemvar'.format(self.op2.pk): str(self.ticket.pk),
'add-itemvar'.format(self.op2.pk): str(self.ticket.pk),
})
self.op1.refresh_from_db()
self.order.refresh_from_db()
@@ -963,9 +965,14 @@ class OrderChangeTests(SoupTest):
self.client.post('/control/event/{}/{}/orders/{}/change'.format(
self.event.organizer.slug, self.event.slug, self.order.code
), {
'op-{}-operation'.format(self.op1.pk): 'subevent',
'op-{}-subevent'.format(self.op1.pk): str(se2.pk),
'add-itemvar': str(self.ticket.pk),
'add-subevent': str(se1.pk),
'op-{}-itemvar'.format(self.op1.pk): str(self.ticket.pk),
'op-{}-operation'.format(self.op2.pk): '',
'op-{}-itemvar'.format(self.op2.pk): str(self.ticket.pk),
'op-{}-subevent'.format(self.op2.pk): str(se1.pk),
'add-itemvar'.format(self.op2.pk): str(self.ticket.pk),
'add-subevent'.format(self.op2.pk): str(se1.pk),
})
self.op1.refresh_from_db()
self.op2.refresh_from_db()
@@ -982,7 +989,7 @@ class OrderChangeTests(SoupTest):
'op-{}-price'.format(self.op1.pk): '24.00',
'op-{}-operation'.format(self.op2.pk): '',
'op-{}-itemvar'.format(self.op2.pk): str(self.ticket.pk),
'add-itemvar': str(self.ticket.pk),
'add-itemvar'.format(self.op2.pk): str(self.ticket.pk),
})
self.op1.refresh_from_db()
self.order.refresh_from_db()
@@ -994,8 +1001,13 @@ class OrderChangeTests(SoupTest):
self.client.post('/control/event/{}/{}/orders/{}/change'.format(
self.event.organizer.slug, self.event.slug, self.order.code
), {
'op-{}-operation_cancel'.format(self.op1.pk): 'on',
'add-itemvar': str(self.ticket.pk),
'op-{}-operation'.format(self.op1.pk): 'cancel',
'op-{}-itemvar'.format(self.op1.pk): str(self.ticket.pk),
'op-{}-price'.format(self.op1.pk): str(self.op1.price),
'op-{}-operation'.format(self.op2.pk): '',
'op-{}-itemvar'.format(self.op2.pk): str(self.ticket.pk),
'op-{}-price'.format(self.op2.pk): str(self.op2.price),
'add-itemvar'.format(self.op2.pk): str(self.ticket.pk),
})
self.order.refresh_from_db()
assert self.order.positions.count() == 1
@@ -1005,6 +1017,12 @@ class OrderChangeTests(SoupTest):
self.client.post('/control/event/{}/{}/orders/{}/change'.format(
self.event.organizer.slug, self.event.slug, self.order.code
), {
'op-{}-operation'.format(self.op1.pk): '',
'op-{}-operation'.format(self.op2.pk): '',
'op-{}-itemvar'.format(self.op2.pk): str(self.ticket.pk),
'op-{}-price'.format(self.op2.pk): str(self.op2.price),
'op-{}-itemvar'.format(self.op1.pk): str(self.ticket.pk),
'op-{}-price'.format(self.op1.pk): str(self.op1.price),
'add-itemvar': str(self.shirt.pk),
'add-do': 'on',
'add-price': '14.00',
@@ -1029,7 +1047,7 @@ class OrderChangeTests(SoupTest):
self.event.organizer.slug, self.event.slug, self.order.code
), {
'other-recalculate_taxes': 'on',
'add-itemvar': str(self.ticket.pk),
'add-itemvar'.format(self.op2.pk): str(self.ticket.pk),
'op-{}-operation'.format(self.op1.pk): '',
'op-{}-operation'.format(self.op2.pk): '',
'op-{}-itemvar'.format(self.op2.pk): str(self.ticket.pk),

View File

@@ -27,8 +27,6 @@ def env():
superuser_urls = [
"global/settings/",
"logdetail/",
"logdetail/payment/",
"logdetail/refund/",
"users/select2",
"users/",
"users/add",

View File

@@ -103,21 +103,6 @@ def test_team_remove_token(event, admin_user, admin_team, client):
assert not tk.active
@pytest.mark.django_db
def test_team_resend_invite(event, admin_user, admin_team, client):
client.login(email='dummy@dummy.dummy', password='dummy')
djmail.outbox = []
inv = admin_team.invites.create(email='foo@example.org')
resp = client.post('/control/organizer/dummy/team/{}/'.format(admin_team.pk), {
'resend-invite': str(inv.pk)
}, follow=True)
assert 'Admin team' in resp.rendered_content
assert admin_user.email in resp.rendered_content
assert 'foo@example.org' in resp.rendered_content
assert len(djmail.outbox) == 1
@pytest.mark.django_db
def test_team_revoke_invite(event, admin_user, admin_team, client):
client.login(email='dummy@dummy.dummy', password='dummy')
@@ -240,20 +225,6 @@ def test_remove_last_admin_team(event, admin_user, admin_team, client):
assert 'alert-danger' in resp.rendered_content
@pytest.mark.django_db
def test_resend_invalid_invite(event, admin_user, admin_team, client):
client.login(email='dummy@dummy.dummy', password='dummy')
djmail.outbox = []
inv = admin_team.invites.create(email='foo@example.org')
resp = client.post('/control/organizer/dummy/team/{}/'.format(admin_team.pk), {
'resend-invite': inv.pk + 1
}, follow=True)
assert b'alert-danger' in resp.content
assert b'Invalid invite selected.' in resp.content
assert len(djmail.outbox) == 0
@pytest.mark.django_db
def test_invite_invalid_token(event, admin_team, client):
i = admin_team.invites.create(email='foo@bar.com')

View File

@@ -11,7 +11,7 @@ from pretix.base.models import (
Event, Item, Order, OrderFee, OrderPayment, OrderPosition, Organizer,
Quota, Team, User,
)
from pretix.plugins.banktransfer.models import BankImportJob, BankTransaction
from pretix.plugins.banktransfer.models import BankImportJob
from pretix.plugins.banktransfer.tasks import process_banktransfers
@@ -300,19 +300,6 @@ def test_wrong_event_organizer(env, orga_job):
assert env[2].status == Order.STATUS_PENDING
@pytest.mark.django_db
def test_keep_unmatched(env, orga_job):
process_banktransfers(orga_job, [{
'payer': 'Karla Kundin',
'reference': 'No useful reference',
'date': '2016-01-26',
'amount': '23.00'
}])
job = BankImportJob.objects.last()
t = job.transactions.last()
assert t.state == BankTransaction.STATE_NOMATCH
@pytest.mark.django_db
def test_import_very_long_csv_file(client, env):
client.login(email='dummy@dummy.dummy', password='dummy')

Some files were not shown because too many files have changed in this diff Show More