forked from CGM_Public/pretix_original
Compare commits
26
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
421c098ec2 | ||
|
|
e8785f4117 | ||
|
|
accb9f8b13 | ||
|
|
98d290992c | ||
|
|
9a56874083 | ||
|
|
82dd417a8e | ||
|
|
ba2c6e1e58 | ||
|
|
38b8269f14 | ||
|
|
749f5c7e6c | ||
|
|
d1e8504481 | ||
|
|
108408366e | ||
|
|
4543d8093f | ||
|
|
513a90f976 | ||
|
|
6f61155deb | ||
|
|
714ce28b6a | ||
|
|
b3bcad38a8 | ||
|
|
90978e5cab | ||
|
|
84fb481cdb | ||
|
|
854b41c955 | ||
|
|
79ee89bde9 | ||
|
|
d8d31bab51 | ||
|
|
d47bebb403 | ||
|
|
32927bfd4f | ||
|
|
41c8d646d9 | ||
|
|
c828873d21 | ||
|
|
b4e372ce04 |
@@ -104,6 +104,12 @@ Install ``pgloader``::
|
|||||||
|
|
||||||
# apt install pgloader
|
# apt install pgloader
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
If you are using Ubuntu 20.04, the ``pgloader`` version from the repositories seems to be incompatible with PostgreSQL
|
||||||
|
12+. You can install ``pgloader`` from the `PostgreSQL repositories`_ instead.
|
||||||
|
See also `this discussion <https://github.com/pretix/pretix/issues/3090>`_.
|
||||||
|
|
||||||
Create a new file ``/tmp/pretix.load``, replacing the MySQL and PostgreSQL connection strings with the correct user names, passwords, and/or database names::
|
Create a new file ``/tmp/pretix.load``, replacing the MySQL and PostgreSQL connection strings with the correct user names, passwords, and/or database names::
|
||||||
|
|
||||||
LOAD DATABASE
|
LOAD DATABASE
|
||||||
@@ -146,3 +152,5 @@ Now, restart pretix. Maybe stop your MySQL server as a verification step that yo
|
|||||||
And you're done! After you've verified everything has been copied correctly, you can delete the old MySQL database.
|
And you're done! After you've verified everything has been copied correctly, you can delete the old MySQL database.
|
||||||
|
|
||||||
.. note:: Don't forget to update your backup process to back up your PostgreSQL database instead of your MySQL database now.
|
.. note:: Don't forget to update your backup process to back up your PostgreSQL database instead of your MySQL database now.
|
||||||
|
|
||||||
|
.. _PostgreSQL repositories: https://wiki.postgresql.org/wiki/Apt
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ introductory_text string Text to be prin
|
|||||||
additional_text string Text to be printed below the product list
|
additional_text string Text to be printed below the product list
|
||||||
payment_provider_text string Text to be printed below the product list with
|
payment_provider_text string Text to be printed below the product list with
|
||||||
payment information
|
payment information
|
||||||
|
payment_provider_stamp string Short text to be visibly printed to indicate payment status
|
||||||
footer_text string Text to be printed in the page footer area
|
footer_text string Text to be printed in the page footer area
|
||||||
lines list of objects The actual invoice contents
|
lines list of objects The actual invoice contents
|
||||||
├ position integer Number of the line within an invoice.
|
├ position integer Number of the line within an invoice.
|
||||||
@@ -178,6 +179,7 @@ Endpoints
|
|||||||
"internal_reference": "",
|
"internal_reference": "",
|
||||||
"additional_text": "We are looking forward to see you on our conference!",
|
"additional_text": "We are looking forward to see you on our conference!",
|
||||||
"payment_provider_text": "Please transfer the money to our account ABC…",
|
"payment_provider_text": "Please transfer the money to our account ABC…",
|
||||||
|
"payment_provider_stamp": null,
|
||||||
"footer_text": "Big Events LLC - Registration No. 123456 - VAT ID: EU0987654321",
|
"footer_text": "Big Events LLC - Registration No. 123456 - VAT ID: EU0987654321",
|
||||||
"lines": [
|
"lines": [
|
||||||
{
|
{
|
||||||
@@ -268,6 +270,7 @@ Endpoints
|
|||||||
"internal_reference": "",
|
"internal_reference": "",
|
||||||
"additional_text": "We are looking forward to see you on our conference!",
|
"additional_text": "We are looking forward to see you on our conference!",
|
||||||
"payment_provider_text": "Please transfer the money to our account ABC…",
|
"payment_provider_text": "Please transfer the money to our account ABC…",
|
||||||
|
"payment_provider_stamp": null,
|
||||||
"footer_text": "Big Events LLC - Registration No. 123456 - VAT ID: EU0987654321",
|
"footer_text": "Big Events LLC - Registration No. 123456 - VAT ID: EU0987654321",
|
||||||
"lines": [
|
"lines": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ limit_events list of strings If ``all_events
|
|||||||
action_types list of strings A list of action type filters that limit the
|
action_types list of strings A list of action type filters that limit the
|
||||||
notifications sent to this webhook. See below for
|
notifications sent to this webhook. See below for
|
||||||
valid values
|
valid values
|
||||||
|
comment string Internal comment on this webhook, default ``null``
|
||||||
===================================== ========================== =======================================================
|
===================================== ========================== =======================================================
|
||||||
|
|
||||||
The following values for ``action_types`` are valid with pretix core:
|
The following values for ``action_types`` are valid with pretix core:
|
||||||
@@ -56,6 +57,7 @@ The following values for ``action_types`` are valid with pretix core:
|
|||||||
* ``pretix.subevent.added``
|
* ``pretix.subevent.added``
|
||||||
* ``pretix.subevent.changed``
|
* ``pretix.subevent.changed``
|
||||||
* ``pretix.subevent.deleted``
|
* ``pretix.subevent.deleted``
|
||||||
|
* ``pretix.event.item.*``
|
||||||
* ``pretix.event.live.activated``
|
* ``pretix.event.live.activated``
|
||||||
* ``pretix.event.live.deactivated``
|
* ``pretix.event.live.deactivated``
|
||||||
* ``pretix.event.testmode.activated``
|
* ``pretix.event.testmode.activated``
|
||||||
@@ -98,7 +100,8 @@ Endpoints
|
|||||||
"target_url": "https://httpstat.us/200",
|
"target_url": "https://httpstat.us/200",
|
||||||
"all_events": false,
|
"all_events": false,
|
||||||
"limit_events": ["democon"],
|
"limit_events": ["democon"],
|
||||||
"action_types": ["pretix.event.order.modified", "pretix.event.order.changed.*"]
|
"action_types": ["pretix.event.order.modified", "pretix.event.order.changed.*"],
|
||||||
|
"comment": null
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -135,7 +138,8 @@ Endpoints
|
|||||||
"target_url": "https://httpstat.us/200",
|
"target_url": "https://httpstat.us/200",
|
||||||
"all_events": false,
|
"all_events": false,
|
||||||
"limit_events": ["democon"],
|
"limit_events": ["democon"],
|
||||||
"action_types": ["pretix.event.order.modified", "pretix.event.order.changed.*"]
|
"action_types": ["pretix.event.order.modified", "pretix.event.order.changed.*"],
|
||||||
|
"comment": null
|
||||||
}
|
}
|
||||||
|
|
||||||
:param organizer: The ``slug`` field of the organizer to fetch
|
:param organizer: The ``slug`` field of the organizer to fetch
|
||||||
@@ -162,7 +166,8 @@ Endpoints
|
|||||||
"target_url": "https://httpstat.us/200",
|
"target_url": "https://httpstat.us/200",
|
||||||
"all_events": false,
|
"all_events": false,
|
||||||
"limit_events": ["democon"],
|
"limit_events": ["democon"],
|
||||||
"action_types": ["pretix.event.order.modified", "pretix.event.order.changed.*"]
|
"action_types": ["pretix.event.order.modified", "pretix.event.order.changed.*"],
|
||||||
|
"comment": "Called for changes"
|
||||||
}
|
}
|
||||||
|
|
||||||
**Example response**:
|
**Example response**:
|
||||||
@@ -179,7 +184,8 @@ Endpoints
|
|||||||
"target_url": "https://httpstat.us/200",
|
"target_url": "https://httpstat.us/200",
|
||||||
"all_events": false,
|
"all_events": false,
|
||||||
"limit_events": ["democon"],
|
"limit_events": ["democon"],
|
||||||
"action_types": ["pretix.event.order.modified", "pretix.event.order.changed.*"]
|
"action_types": ["pretix.event.order.modified", "pretix.event.order.changed.*"],
|
||||||
|
"comment": "Called for changes"
|
||||||
}
|
}
|
||||||
|
|
||||||
:param organizer: The ``slug`` field of the organizer to create a webhook for
|
:param organizer: The ``slug`` field of the organizer to create a webhook for
|
||||||
@@ -224,7 +230,8 @@ Endpoints
|
|||||||
"target_url": "https://httpstat.us/200",
|
"target_url": "https://httpstat.us/200",
|
||||||
"all_events": false,
|
"all_events": false,
|
||||||
"limit_events": ["democon"],
|
"limit_events": ["democon"],
|
||||||
"action_types": ["pretix.event.order.modified", "pretix.event.order.changed.*"]
|
"action_types": ["pretix.event.order.modified", "pretix.event.order.changed.*"],
|
||||||
|
"comment": null
|
||||||
}
|
}
|
||||||
|
|
||||||
:param organizer: The ``slug`` field of the organizer to modify
|
:param organizer: The ``slug`` field of the organizer to modify
|
||||||
|
|||||||
@@ -102,6 +102,8 @@ The provider class
|
|||||||
|
|
||||||
.. automethod:: render_invoice_text
|
.. automethod:: render_invoice_text
|
||||||
|
|
||||||
|
.. automethod:: render_invoice_stamp
|
||||||
|
|
||||||
.. automethod:: order_change_allowed
|
.. automethod:: order_change_allowed
|
||||||
|
|
||||||
.. automethod:: payment_prepare
|
.. automethod:: payment_prepare
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 3.2.17 on 2023-02-07 12:18
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('pretixapi', '0009_auto_20221217_1847'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='webhook',
|
||||||
|
name='comment',
|
||||||
|
field=models.CharField(max_length=255, null=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -112,6 +112,7 @@ class WebHook(models.Model):
|
|||||||
target_url = models.URLField(verbose_name=_("Target URL"), max_length=255)
|
target_url = models.URLField(verbose_name=_("Target URL"), max_length=255)
|
||||||
all_events = models.BooleanField(default=True, verbose_name=_("All events (including newly created ones)"))
|
all_events = models.BooleanField(default=True, verbose_name=_("All events (including newly created ones)"))
|
||||||
limit_events = models.ManyToManyField('pretixbase.Event', verbose_name=_("Limit to events"), blank=True)
|
limit_events = models.ManyToManyField('pretixbase.Event', verbose_name=_("Limit to events"), blank=True)
|
||||||
|
comment = models.CharField(verbose_name=_("Comment"), max_length=255, null=True, blank=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ('id',)
|
ordering = ('id',)
|
||||||
|
|||||||
@@ -1484,9 +1484,9 @@ class InvoiceSerializer(I18nAwareModelSerializer):
|
|||||||
'invoice_to', 'invoice_to_company', 'invoice_to_name', 'invoice_to_street', 'invoice_to_zipcode',
|
'invoice_to', 'invoice_to_company', 'invoice_to_name', 'invoice_to_street', 'invoice_to_zipcode',
|
||||||
'invoice_to_city', 'invoice_to_state', 'invoice_to_country', 'invoice_to_vat_id', 'invoice_to_beneficiary',
|
'invoice_to_city', 'invoice_to_state', 'invoice_to_country', 'invoice_to_vat_id', 'invoice_to_beneficiary',
|
||||||
'custom_field', 'date', 'refers', 'locale',
|
'custom_field', 'date', 'refers', 'locale',
|
||||||
'introductory_text', 'additional_text', 'payment_provider_text', 'footer_text', 'lines',
|
'introductory_text', 'additional_text', 'payment_provider_text', 'payment_provider_stamp',
|
||||||
'foreign_currency_display', 'foreign_currency_rate', 'foreign_currency_rate_date',
|
'footer_text', 'lines', 'foreign_currency_display', 'foreign_currency_rate',
|
||||||
'internal_reference')
|
'foreign_currency_rate_date', 'internal_reference')
|
||||||
|
|
||||||
|
|
||||||
class OrderPaymentCreateSerializer(I18nAwareModelSerializer):
|
class OrderPaymentCreateSerializer(I18nAwareModelSerializer):
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ class WebHookSerializer(I18nAwareModelSerializer):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = WebHook
|
model = WebHook
|
||||||
fields = ('id', 'enabled', 'target_url', 'all_events', 'limit_events', 'action_types')
|
fields = ('id', 'enabled', 'target_url', 'all_events', 'limit_events', 'action_types', 'comment')
|
||||||
|
|
||||||
def validate(self, data):
|
def validate(self, data):
|
||||||
data = super().validate(data)
|
data = super().validate(data)
|
||||||
|
|||||||
+23
-27
@@ -96,7 +96,7 @@ def get_all_webhook_events():
|
|||||||
return types
|
return types
|
||||||
|
|
||||||
|
|
||||||
class ParametrizedOrderWebhookEvent(WebhookEvent):
|
class ParametrizedWebhookEvent(WebhookEvent):
|
||||||
def __init__(self, action_type, verbose_name):
|
def __init__(self, action_type, verbose_name):
|
||||||
self._action_type = action_type
|
self._action_type = action_type
|
||||||
self._verbose_name = verbose_name
|
self._verbose_name = verbose_name
|
||||||
@@ -110,6 +110,8 @@ class ParametrizedOrderWebhookEvent(WebhookEvent):
|
|||||||
def verbose_name(self):
|
def verbose_name(self):
|
||||||
return self._verbose_name
|
return self._verbose_name
|
||||||
|
|
||||||
|
|
||||||
|
class ParametrizedOrderWebhookEvent(ParametrizedWebhookEvent):
|
||||||
def build_payload(self, logentry: LogEntry):
|
def build_payload(self, logentry: LogEntry):
|
||||||
order = logentry.content_object
|
order = logentry.content_object
|
||||||
if not order:
|
if not order:
|
||||||
@@ -124,19 +126,7 @@ class ParametrizedOrderWebhookEvent(WebhookEvent):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class ParametrizedEventWebhookEvent(WebhookEvent):
|
class ParametrizedEventWebhookEvent(ParametrizedWebhookEvent):
|
||||||
def __init__(self, action_type, verbose_name):
|
|
||||||
self._action_type = action_type
|
|
||||||
self._verbose_name = verbose_name
|
|
||||||
super().__init__()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def action_type(self):
|
|
||||||
return self._action_type
|
|
||||||
|
|
||||||
@property
|
|
||||||
def verbose_name(self):
|
|
||||||
return self._verbose_name
|
|
||||||
|
|
||||||
def build_payload(self, logentry: LogEntry):
|
def build_payload(self, logentry: LogEntry):
|
||||||
if logentry.action_type == 'pretix.event.deleted':
|
if logentry.action_type == 'pretix.event.deleted':
|
||||||
@@ -160,19 +150,7 @@ class ParametrizedEventWebhookEvent(WebhookEvent):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class ParametrizedSubEventWebhookEvent(WebhookEvent):
|
class ParametrizedSubEventWebhookEvent(ParametrizedWebhookEvent):
|
||||||
def __init__(self, action_type, verbose_name):
|
|
||||||
self._action_type = action_type
|
|
||||||
self._verbose_name = verbose_name
|
|
||||||
super().__init__()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def action_type(self):
|
|
||||||
return self._action_type
|
|
||||||
|
|
||||||
@property
|
|
||||||
def verbose_name(self):
|
|
||||||
return self._verbose_name
|
|
||||||
|
|
||||||
def build_payload(self, logentry: LogEntry):
|
def build_payload(self, logentry: LogEntry):
|
||||||
# do not use content_object, this is also called in deletion
|
# do not use content_object, this is also called in deletion
|
||||||
@@ -185,6 +163,19 @@ class ParametrizedSubEventWebhookEvent(WebhookEvent):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ParametrizedItemWebhookEvent(ParametrizedWebhookEvent):
|
||||||
|
|
||||||
|
def build_payload(self, logentry: LogEntry):
|
||||||
|
# do not use content_object, this is also called in deletion
|
||||||
|
return {
|
||||||
|
'notification_id': logentry.pk,
|
||||||
|
'organizer': logentry.event.organizer.slug,
|
||||||
|
'event': logentry.event.slug,
|
||||||
|
'item': logentry.object_id,
|
||||||
|
'action': logentry.action_type,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class ParametrizedOrderPositionWebhookEvent(ParametrizedOrderWebhookEvent):
|
class ParametrizedOrderPositionWebhookEvent(ParametrizedOrderWebhookEvent):
|
||||||
|
|
||||||
def build_payload(self, logentry: LogEntry):
|
def build_payload(self, logentry: LogEntry):
|
||||||
@@ -305,6 +296,11 @@ def register_default_webhook_events(sender, **kwargs):
|
|||||||
'pretix.subevent.deleted',
|
'pretix.subevent.deleted',
|
||||||
pgettext_lazy('subevent', 'Event series date deleted'),
|
pgettext_lazy('subevent', 'Event series date deleted'),
|
||||||
),
|
),
|
||||||
|
ParametrizedItemWebhookEvent(
|
||||||
|
'pretix.event.item.*',
|
||||||
|
_('Product changed (including product added or deleted and including changes to nested objects like '
|
||||||
|
'variations or bundles)'),
|
||||||
|
),
|
||||||
ParametrizedEventWebhookEvent(
|
ParametrizedEventWebhookEvent(
|
||||||
'pretix.event.live.activated',
|
'pretix.event.live.activated',
|
||||||
_('Shop taken live'),
|
_('Shop taken live'),
|
||||||
|
|||||||
@@ -35,8 +35,8 @@ from django.utils.formats import date_format, localize
|
|||||||
from django.utils.translation import (
|
from django.utils.translation import (
|
||||||
get_language, gettext, gettext_lazy, pgettext,
|
get_language, gettext, gettext_lazy, pgettext,
|
||||||
)
|
)
|
||||||
from reportlab.lib import pagesizes
|
from reportlab.lib import colors, pagesizes
|
||||||
from reportlab.lib.enums import TA_LEFT, TA_RIGHT
|
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_RIGHT
|
||||||
from reportlab.lib.styles import ParagraphStyle, StyleSheet1
|
from reportlab.lib.styles import ParagraphStyle, StyleSheet1
|
||||||
from reportlab.lib.units import mm
|
from reportlab.lib.units import mm
|
||||||
from reportlab.pdfbase import pdfmetrics
|
from reportlab.pdfbase import pdfmetrics
|
||||||
@@ -44,8 +44,8 @@ from reportlab.pdfbase.pdfmetrics import stringWidth
|
|||||||
from reportlab.pdfbase.ttfonts import TTFont
|
from reportlab.pdfbase.ttfonts import TTFont
|
||||||
from reportlab.pdfgen.canvas import Canvas
|
from reportlab.pdfgen.canvas import Canvas
|
||||||
from reportlab.platypus import (
|
from reportlab.platypus import (
|
||||||
BaseDocTemplate, Frame, KeepTogether, NextPageTemplate, PageTemplate,
|
BaseDocTemplate, Flowable, Frame, KeepTogether, NextPageTemplate,
|
||||||
Paragraph, Spacer, Table, TableStyle,
|
PageTemplate, Paragraph, Spacer, Table, TableStyle,
|
||||||
)
|
)
|
||||||
|
|
||||||
from pretix.base.decimal import round_decimal
|
from pretix.base.decimal import round_decimal
|
||||||
@@ -147,6 +147,8 @@ class BaseReportlabInvoiceRenderer(BaseInvoiceRenderer):
|
|||||||
"""
|
"""
|
||||||
stylesheet = StyleSheet1()
|
stylesheet = StyleSheet1()
|
||||||
stylesheet.add(ParagraphStyle(name='Normal', fontName=self.font_regular, fontSize=10, leading=12))
|
stylesheet.add(ParagraphStyle(name='Normal', fontName=self.font_regular, fontSize=10, leading=12))
|
||||||
|
stylesheet.add(ParagraphStyle(name='BoldInverseCenter', fontName=self.font_bold, fontSize=10, leading=12,
|
||||||
|
textColor=colors.white, alignment=TA_CENTER))
|
||||||
stylesheet.add(ParagraphStyle(name='InvoiceFrom', parent=stylesheet['Normal']))
|
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='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='FineprintHeading', fontName=self.font_bold, fontSize=8, leading=12))
|
||||||
@@ -249,6 +251,31 @@ class BaseReportlabInvoiceRenderer(BaseInvoiceRenderer):
|
|||||||
).strip().replace('<br>', '<br />').replace('\n', '<br />\n')
|
).strip().replace('<br>', '<br />').replace('\n', '<br />\n')
|
||||||
|
|
||||||
|
|
||||||
|
class PaidMarker(Flowable):
|
||||||
|
def __init__(self, text='paid', color=None, font='OpenSansBd', size=20):
|
||||||
|
super().__init__()
|
||||||
|
self.text = text
|
||||||
|
self.color = color
|
||||||
|
self.font = font
|
||||||
|
self.size = size
|
||||||
|
self._showBoundary = True
|
||||||
|
|
||||||
|
def wrap(self, availwidth, availheight):
|
||||||
|
# Fake a size, we don't care if we exceed the table
|
||||||
|
return 10, self.size / 2
|
||||||
|
|
||||||
|
def draw(self):
|
||||||
|
self.canv.translate(0, - self.size / 2)
|
||||||
|
self.canv.rotate(2)
|
||||||
|
self.canv.setFont(self.font, self.size)
|
||||||
|
self.canv.setFillColor(self.color)
|
||||||
|
width = self.canv.stringWidth(self.text, self.font, self.size)
|
||||||
|
self.canv.drawRightString(0, 0, self.text)
|
||||||
|
|
||||||
|
self.canv.setStrokeColor(self.color)
|
||||||
|
self.canv.roundRect(-width - self.size / 2, -self.size / 4, width + self.size, self.size + self.size / 4, 3)
|
||||||
|
|
||||||
|
|
||||||
class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
||||||
identifier = 'classic'
|
identifier = 'classic'
|
||||||
verbose_name = pgettext('invoice', 'Classic renderer (pretix 1.0)')
|
verbose_name = pgettext('invoice', 'Classic renderer (pretix 1.0)')
|
||||||
@@ -612,10 +639,10 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
|||||||
tdata.append([
|
tdata.append([
|
||||||
pgettext('invoice', 'Invoice total'), '', money_filter(total, self.invoice.event.currency)
|
pgettext('invoice', 'Invoice total'), '', money_filter(total, self.invoice.event.currency)
|
||||||
])
|
])
|
||||||
colwidths = [a * doc.width for a in (.65, .05, .30)]
|
colwidths = [a * doc.width for a in (.65, .20, .15)]
|
||||||
|
|
||||||
if self.invoice.event.settings.invoice_show_payments and not self.invoice.is_cancellation:
|
if not self.invoice.is_cancellation:
|
||||||
if self.invoice.order.status == Order.STATUS_PENDING:
|
if self.invoice.event.settings.invoice_show_payments and self.invoice.order.status == Order.STATUS_PENDING:
|
||||||
pending_sum = self.invoice.order.pending_sum
|
pending_sum = self.invoice.order.pending_sum
|
||||||
if pending_sum != total:
|
if pending_sum != total:
|
||||||
tdata.append([pgettext('invoice', 'Received payments')] + (['', '', ''] if has_taxes else ['']) + [
|
tdata.append([pgettext('invoice', 'Received payments')] + (['', '', ''] if has_taxes else ['']) + [
|
||||||
@@ -627,7 +654,7 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
|||||||
tstyledata += [
|
tstyledata += [
|
||||||
('FONTNAME', (0, len(tdata) - 3), (-1, len(tdata) - 3), self.font_bold),
|
('FONTNAME', (0, len(tdata) - 3), (-1, len(tdata) - 3), self.font_bold),
|
||||||
]
|
]
|
||||||
elif self.invoice.order.payments.filter(
|
elif self.invoice.event.settings.invoice_show_payments and self.invoice.order.payments.filter(
|
||||||
state__in=(OrderPayment.PAYMENT_STATE_CONFIRMED, OrderPayment.PAYMENT_STATE_REFUNDED), provider='giftcard'
|
state__in=(OrderPayment.PAYMENT_STATE_CONFIRMED, OrderPayment.PAYMENT_STATE_REFUNDED), provider='giftcard'
|
||||||
).exists():
|
).exists():
|
||||||
giftcard_sum = self.invoice.order.payments.filter(
|
giftcard_sum = self.invoice.order.payments.filter(
|
||||||
@@ -645,6 +672,13 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
|||||||
tstyledata += [
|
tstyledata += [
|
||||||
('FONTNAME', (0, len(tdata) - 3), (-1, len(tdata) - 3), self.font_bold),
|
('FONTNAME', (0, len(tdata) - 3), (-1, len(tdata) - 3), self.font_bold),
|
||||||
]
|
]
|
||||||
|
elif self.invoice.payment_provider_stamp:
|
||||||
|
pm = PaidMarker(
|
||||||
|
text=self.invoice.payment_provider_stamp,
|
||||||
|
color=colors.HexColor(self.event.settings.theme_color_success),
|
||||||
|
size=16
|
||||||
|
)
|
||||||
|
tdata[-1][-2] = pm
|
||||||
|
|
||||||
table = Table(tdata, colWidths=colwidths, repeatRows=1)
|
table = Table(tdata, colWidths=colwidths, repeatRows=1)
|
||||||
table.setStyle(TableStyle(tstyledata))
|
table.setStyle(TableStyle(tstyledata))
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# Generated by Django 3.2.17 on 2023-02-07 10:00
|
||||||
|
|
||||||
|
import django.core.serializers.json
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('pretixbase', '0228_scheduledeventexport_scheduledorganizerexport'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='invoice',
|
||||||
|
name='payment_provider_stamp',
|
||||||
|
field=models.CharField(max_length=100, null=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -95,6 +95,8 @@ class Invoice(models.Model):
|
|||||||
:type additional_text: str
|
:type additional_text: str
|
||||||
:param payment_provider_text: A payment provider specific text
|
:param payment_provider_text: A payment provider specific text
|
||||||
:type payment_provider_text: str
|
:type payment_provider_text: str
|
||||||
|
:param payment_provider_stamp: A payment provider specific stamp
|
||||||
|
:type payment_provider_stamp: str
|
||||||
:param footer_text: A footer text, displayed smaller and centered on every page
|
:param footer_text: A footer text, displayed smaller and centered on every page
|
||||||
:type footer_text: str
|
:type footer_text: str
|
||||||
:param foreign_currency_display: A different currency that taxes should also be displayed in.
|
:param foreign_currency_display: A different currency that taxes should also be displayed in.
|
||||||
@@ -144,6 +146,7 @@ class Invoice(models.Model):
|
|||||||
additional_text = models.TextField(blank=True)
|
additional_text = models.TextField(blank=True)
|
||||||
reverse_charge = models.BooleanField(default=False)
|
reverse_charge = models.BooleanField(default=False)
|
||||||
payment_provider_text = models.TextField(blank=True)
|
payment_provider_text = models.TextField(blank=True)
|
||||||
|
payment_provider_stamp = models.CharField(max_length=100, null=True, blank=True)
|
||||||
footer_text = models.TextField(blank=True)
|
footer_text = models.TextField(blank=True)
|
||||||
|
|
||||||
foreign_currency_display = models.CharField(max_length=50, null=True, blank=True)
|
foreign_currency_display = models.CharField(max_length=50, null=True, blank=True)
|
||||||
|
|||||||
@@ -464,6 +464,16 @@ class BasePaymentProvider:
|
|||||||
return pgettext_lazy('invoice', 'The payment for this invoice has already been received.')
|
return pgettext_lazy('invoice', 'The payment for this invoice has already been received.')
|
||||||
return self.settings.get('_invoice_text', as_type=LazyI18nString, default='')
|
return self.settings.get('_invoice_text', as_type=LazyI18nString, default='')
|
||||||
|
|
||||||
|
def render_invoice_stamp(self, order: Order, payment: OrderPayment) -> str:
|
||||||
|
"""
|
||||||
|
This is called when an invoice for an order with this payment provider is generated.
|
||||||
|
The default implementation returns "paid" if the order was already paid, and ``None``
|
||||||
|
otherwise. You can override this with a string, but it should be *really* short to make
|
||||||
|
the invoice look pretty.
|
||||||
|
"""
|
||||||
|
if order.status == Order.STATUS_PAID:
|
||||||
|
return _('paid')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def payment_form_fields(self) -> dict:
|
def payment_form_fields(self) -> dict:
|
||||||
"""
|
"""
|
||||||
|
|||||||
+18
-6
@@ -62,7 +62,7 @@ from django.utils.html import conditional_escape
|
|||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
from django.utils.translation import gettext_lazy as _, pgettext
|
from django.utils.translation import gettext_lazy as _, pgettext
|
||||||
from i18nfield.strings import LazyI18nString
|
from i18nfield.strings import LazyI18nString
|
||||||
from PyPDF2 import PdfReader
|
from pypdf import PdfReader
|
||||||
from pytz import timezone
|
from pytz import timezone
|
||||||
from reportlab.graphics import renderPDF
|
from reportlab.graphics import renderPDF
|
||||||
from reportlab.graphics.barcode.qr import QrCodeWidget
|
from reportlab.graphics.barcode.qr import QrCodeWidget
|
||||||
@@ -848,12 +848,17 @@ class Renderer:
|
|||||||
'center': TA_CENTER,
|
'center': TA_CENTER,
|
||||||
'right': TA_RIGHT
|
'right': TA_RIGHT
|
||||||
}
|
}
|
||||||
|
# lineheight display differs from browser canvas. This calc is just empirical values to get
|
||||||
|
# reportlab render similarly to browser canvas.
|
||||||
|
# for backwards compatability use „uncorrected“ lineheight of 1.0 instead of 1.15
|
||||||
|
lineheight = float(o['lineheight']) * 1.15 if 'lineheight' in o else 1.0
|
||||||
style = ParagraphStyle(
|
style = ParagraphStyle(
|
||||||
name=uuid.uuid4().hex,
|
name=uuid.uuid4().hex,
|
||||||
fontName=font,
|
fontName=font,
|
||||||
fontSize=float(o['fontsize']),
|
fontSize=float(o['fontsize']),
|
||||||
leading=float(o['fontsize']),
|
leading=lineheight * float(o['fontsize']),
|
||||||
autoLeading="max",
|
# for backwards compatability use autoLeading if no lineheight is given
|
||||||
|
autoLeading='off' if 'lineheight' in o else 'max',
|
||||||
textColor=Color(o['color'][0] / 255, o['color'][1] / 255, o['color'][2] / 255),
|
textColor=Color(o['color'][0] / 255, o['color'][1] / 255, o['color'][2] / 255),
|
||||||
alignment=align_map[o['align']]
|
alignment=align_map[o['align']]
|
||||||
)
|
)
|
||||||
@@ -882,8 +887,15 @@ class Renderer:
|
|||||||
if o.get('downward', False):
|
if o.get('downward', False):
|
||||||
canvas.translate(float(o['left']) * mm, float(o['bottom']) * mm)
|
canvas.translate(float(o['left']) * mm, float(o['bottom']) * mm)
|
||||||
canvas.rotate(o.get('rotation', 0) * -1)
|
canvas.rotate(o.get('rotation', 0) * -1)
|
||||||
p.drawOn(canvas, 0, -h - ad[1] / 2)
|
p.drawOn(canvas, 0, -h - ad[1] / 2.5)
|
||||||
else:
|
else:
|
||||||
|
if lineheight != 1.0:
|
||||||
|
# lineheight adds to ascent/descent offsets, just empirical values again to get
|
||||||
|
# reportlab to render similarly to browser canvas
|
||||||
|
ad = (
|
||||||
|
ad[0],
|
||||||
|
ad[1] + (lineheight - 1.0) * float(o['fontsize']) * 1.05
|
||||||
|
)
|
||||||
canvas.translate(float(o['left']) * mm, float(o['bottom']) * mm + h)
|
canvas.translate(float(o['left']) * mm, float(o['bottom']) * mm + h)
|
||||||
canvas.rotate(o.get('rotation', 0) * -1)
|
canvas.rotate(o.get('rotation', 0) * -1)
|
||||||
p.drawOn(canvas, 0, -h - ad[1])
|
p.drawOn(canvas, 0, -h - ad[1])
|
||||||
@@ -938,8 +950,8 @@ class Renderer:
|
|||||||
with open(os.path.join(d, 'out.pdf'), 'rb') as f:
|
with open(os.path.join(d, 'out.pdf'), 'rb') as f:
|
||||||
return BytesIO(f.read())
|
return BytesIO(f.read())
|
||||||
else:
|
else:
|
||||||
from PyPDF2 import PdfReader, PdfWriter, Transformation
|
from pypdf import PdfReader, PdfWriter, Transformation
|
||||||
from PyPDF2.generic import RectangleObject
|
from pypdf.generic import RectangleObject
|
||||||
buffer.seek(0)
|
buffer.seek(0)
|
||||||
new_pdf = PdfReader(buffer)
|
new_pdf = PdfReader(buffer)
|
||||||
output = PdfWriter()
|
output = PdfWriter()
|
||||||
|
|||||||
@@ -98,8 +98,10 @@ def build_invoice(invoice: Invoice) -> Invoice:
|
|||||||
payment = str(lp.payment_provider.render_invoice_text(invoice.order, lp))
|
payment = str(lp.payment_provider.render_invoice_text(invoice.order, lp))
|
||||||
else:
|
else:
|
||||||
payment = str(lp.payment_provider.render_invoice_text(invoice.order))
|
payment = str(lp.payment_provider.render_invoice_text(invoice.order))
|
||||||
|
payment_stamp = lp.payment_provider.render_invoice_stamp(invoice.order, lp)
|
||||||
else:
|
else:
|
||||||
payment = ""
|
payment = ""
|
||||||
|
payment_stamp = None
|
||||||
if invoice.event.settings.invoice_include_expire_date and invoice.order.status == Order.STATUS_PENDING:
|
if invoice.event.settings.invoice_include_expire_date and invoice.order.status == Order.STATUS_PENDING:
|
||||||
if payment:
|
if payment:
|
||||||
payment += "<br /><br />"
|
payment += "<br /><br />"
|
||||||
@@ -111,6 +113,7 @@ def build_invoice(invoice: Invoice) -> Invoice:
|
|||||||
invoice.additional_text = str(additional).replace('\n', '<br />')
|
invoice.additional_text = str(additional).replace('\n', '<br />')
|
||||||
invoice.footer_text = str(footer)
|
invoice.footer_text = str(footer)
|
||||||
invoice.payment_provider_text = str(payment).replace('\n', '<br />')
|
invoice.payment_provider_text = str(payment).replace('\n', '<br />')
|
||||||
|
invoice.payment_provider_stamp = str(payment_stamp) if payment_stamp else None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ia = invoice.order.invoice_address
|
ia = invoice.order.invoice_address
|
||||||
@@ -325,6 +328,7 @@ def generate_cancellation(invoice: Invoice, trigger_pdf=True):
|
|||||||
cancellation.is_cancellation = True
|
cancellation.is_cancellation = True
|
||||||
cancellation.date = timezone.now().date()
|
cancellation.date = timezone.now().date()
|
||||||
cancellation.payment_provider_text = ''
|
cancellation.payment_provider_text = ''
|
||||||
|
cancellation.payment_provider_stamp = ''
|
||||||
cancellation.file = None
|
cancellation.file = None
|
||||||
cancellation.sent_to_organizer = None
|
cancellation.sent_to_organizer = None
|
||||||
cancellation.sent_to_customer = None
|
cancellation.sent_to_customer = None
|
||||||
@@ -436,6 +440,7 @@ def build_preview_invoice_pdf(event):
|
|||||||
invoice.additional_text = str(additional).replace('\n', '<br />')
|
invoice.additional_text = str(additional).replace('\n', '<br />')
|
||||||
invoice.footer_text = str(footer)
|
invoice.footer_text = str(footer)
|
||||||
invoice.payment_provider_text = str(payment).replace('\n', '<br />')
|
invoice.payment_provider_text = str(payment).replace('\n', '<br />')
|
||||||
|
invoice.payment_provider_stamp = _('paid')
|
||||||
invoice.invoice_to_name = _("John Doe")
|
invoice.invoice_to_name = _("John Doe")
|
||||||
invoice.invoice_to_street = _("214th Example Street")
|
invoice.invoice_to_street = _("214th Example Street")
|
||||||
invoice.invoice_to_zipcode = _("012345")
|
invoice.invoice_to_zipcode = _("012345")
|
||||||
|
|||||||
@@ -293,14 +293,16 @@ class DateFrameWidget(forms.MultiWidget):
|
|||||||
if '/' in value:
|
if '/' in value:
|
||||||
return [
|
return [
|
||||||
'custom',
|
'custom',
|
||||||
date.fromisoformat(value.split('/', 1)[0]),
|
date.fromisoformat(value.split('/', 1)[0]) if value.split('/', 1)[0] else None,
|
||||||
date.fromisoformat(value.split('/', 1)[-1]),
|
date.fromisoformat(value.split('/', 1)[-1]) if value.split('/', 1)[-1] else None,
|
||||||
]
|
]
|
||||||
return [value, None, None]
|
return [value, None, None]
|
||||||
|
|
||||||
def get_context(self, name, value, attrs):
|
def get_context(self, name, value, attrs):
|
||||||
ctx = super().get_context(name, value, attrs)
|
ctx = super().get_context(name, value, attrs)
|
||||||
ctx['required'] = self.timeframe_choices[0][0] == 'unset'
|
ctx['required'] = self.timeframe_choices[0][0] == 'unset'
|
||||||
|
ctx['widget']['subwidgets'][1]['attrs'].pop('required', None)
|
||||||
|
ctx['widget']['subwidgets'][2]['attrs'].pop('required', None)
|
||||||
return ctx
|
return ctx
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -57,8 +57,8 @@ from pretix.base.forms.widgets import (
|
|||||||
from pretix.base.models import (
|
from pretix.base.models import (
|
||||||
Checkin, CheckinList, Device, Event, EventMetaProperty, EventMetaValue,
|
Checkin, CheckinList, Device, Event, EventMetaProperty, EventMetaValue,
|
||||||
Gate, Invoice, InvoiceAddress, Item, Order, OrderPayment, OrderPosition,
|
Gate, Invoice, InvoiceAddress, Item, Order, OrderPayment, OrderPosition,
|
||||||
OrderRefund, Organizer, Question, QuestionAnswer, SubEvent, Team,
|
OrderRefund, Organizer, Question, QuestionAnswer, SubEvent,
|
||||||
TeamAPIToken, TeamInvite,
|
SubEventMetaValue, Team, TeamAPIToken, TeamInvite,
|
||||||
)
|
)
|
||||||
from pretix.base.signals import register_payment_providers
|
from pretix.base.signals import register_payment_providers
|
||||||
from pretix.control.forms.widgets import Select2
|
from pretix.control.forms.widgets import Select2
|
||||||
@@ -1116,9 +1116,25 @@ class SubEventFilterForm(FilterForm):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
self.event = kwargs.pop('event')
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.fields['date_from'].widget = DatePickerWidget()
|
self.fields['date_from'].widget = DatePickerWidget()
|
||||||
self.fields['date_until'].widget = DatePickerWidget()
|
self.fields['date_until'].widget = DatePickerWidget()
|
||||||
|
for p in self.meta_properties.all():
|
||||||
|
self.fields['meta_{}'.format(p.name)] = forms.CharField(
|
||||||
|
label=p.name,
|
||||||
|
required=False,
|
||||||
|
widget=forms.TextInput(
|
||||||
|
attrs={
|
||||||
|
'data-typeahead-url': reverse('control:event.subevents.meta.typeahead', kwargs={
|
||||||
|
'organizer': self.event.organizer.slug,
|
||||||
|
'event': self.event.slug
|
||||||
|
}) + '?' + urlencode({
|
||||||
|
'property': p.name,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def filter_qs(self, qs):
|
def filter_qs(self, qs):
|
||||||
fdata = self.cleaned_data
|
fdata = self.cleaned_data
|
||||||
@@ -1181,6 +1197,31 @@ class SubEventFilterForm(FilterForm):
|
|||||||
if fdata.get('time_from'):
|
if fdata.get('time_from'):
|
||||||
qs = qs.filter(date_from__time__gte=fdata.get('time_from'))
|
qs = qs.filter(date_from__time__gte=fdata.get('time_from'))
|
||||||
|
|
||||||
|
filters_by_property_name = {}
|
||||||
|
for i, p in enumerate(self.meta_properties):
|
||||||
|
d = fdata.get('meta_{}'.format(p.name))
|
||||||
|
if d:
|
||||||
|
semv_with_value = SubEventMetaValue.objects.filter(
|
||||||
|
subevent=OuterRef('pk'),
|
||||||
|
property__pk=p.pk,
|
||||||
|
value=d
|
||||||
|
)
|
||||||
|
semv_with_any_value = SubEventMetaValue.objects.filter(
|
||||||
|
subevent=OuterRef('pk'),
|
||||||
|
property__pk=p.pk,
|
||||||
|
)
|
||||||
|
qs = qs.annotate(**{'attr_{}'.format(i): Exists(semv_with_value)})
|
||||||
|
if p.name in filters_by_property_name:
|
||||||
|
filters_by_property_name[p.name] |= Q(**{'attr_{}'.format(i): True})
|
||||||
|
else:
|
||||||
|
filters_by_property_name[p.name] = Q(**{'attr_{}'.format(i): True})
|
||||||
|
default = self.event.meta_data[p.name]
|
||||||
|
if default == d:
|
||||||
|
qs = qs.annotate(**{'attr_{}_any'.format(i): Exists(semv_with_any_value)})
|
||||||
|
filters_by_property_name[p.name] |= Q(**{'attr_{}_any'.format(i): False})
|
||||||
|
for f in filters_by_property_name.values():
|
||||||
|
qs = qs.filter(f)
|
||||||
|
|
||||||
if fdata.get('ordering'):
|
if fdata.get('ordering'):
|
||||||
qs = qs.order_by(self.get_order_by())
|
qs = qs.order_by(self.get_order_by())
|
||||||
else:
|
else:
|
||||||
@@ -1188,6 +1229,10 @@ class SubEventFilterForm(FilterForm):
|
|||||||
|
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def meta_properties(self):
|
||||||
|
return self.event.organizer.meta_properties.filter(filter_allowed=True)
|
||||||
|
|
||||||
|
|
||||||
class OrganizerFilterForm(FilterForm):
|
class OrganizerFilterForm(FilterForm):
|
||||||
orders = {
|
orders = {
|
||||||
|
|||||||
@@ -564,7 +564,7 @@ class WebHookForm(forms.ModelForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = WebHook
|
model = WebHook
|
||||||
fields = ['target_url', 'enabled', 'all_events', 'limit_events']
|
fields = ['target_url', 'enabled', 'all_events', 'limit_events', 'comment']
|
||||||
widgets = {
|
widgets = {
|
||||||
'limit_events': forms.CheckboxSelectMultiple(attrs={
|
'limit_events': forms.CheckboxSelectMultiple(attrs={
|
||||||
'data-inverse-dependency': '#id_all_events'
|
'data-inverse-dependency': '#id_all_events'
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ from datetime import datetime
|
|||||||
from urllib.parse import urlencode
|
from urllib.parse import urlencode
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
from django.forms import formset_factory
|
from django.forms import formset_factory
|
||||||
from django.forms.utils import ErrorDict
|
from django.forms.utils import ErrorDict
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
@@ -273,6 +274,13 @@ class SubEventItemForm(SubEventItemOrVariationFormMixin, forms.ModelForm):
|
|||||||
'available_until': SplitDateTimeField,
|
'available_until': SplitDateTimeField,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
d = super().clean()
|
||||||
|
if d.get('available_from') and d.get('available_until'):
|
||||||
|
if d.get('available_from') > d.get('available_until'):
|
||||||
|
raise ValidationError(_('The end of availability should be after the start of availability.'))
|
||||||
|
return d
|
||||||
|
|
||||||
|
|
||||||
class SubEventItemVariationForm(SubEventItemOrVariationFormMixin, forms.ModelForm):
|
class SubEventItemVariationForm(SubEventItemOrVariationFormMixin, forms.ModelForm):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
@@ -293,6 +301,13 @@ class SubEventItemVariationForm(SubEventItemOrVariationFormMixin, forms.ModelFor
|
|||||||
'available_until': SplitDateTimeField,
|
'available_until': SplitDateTimeField,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
d = super().clean()
|
||||||
|
if d.get('available_from') and d.get('available_until'):
|
||||||
|
if d.get('available_from') > d.get('available_until'):
|
||||||
|
raise ValidationError(_('The end of availability should be after the start of availability.'))
|
||||||
|
return d
|
||||||
|
|
||||||
|
|
||||||
class BulkSubEventItemForm(SubEventItemForm):
|
class BulkSubEventItemForm(SubEventItemForm):
|
||||||
rel_available_from = RelativeDateTimeField(
|
rel_available_from = RelativeDateTimeField(
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{% bootstrap_form_errors form %}
|
{% bootstrap_form_errors form %}
|
||||||
{% bootstrap_field form.target_url layout="control" %}
|
{% bootstrap_field form.target_url layout="control" %}
|
||||||
|
{% bootstrap_field form.comment layout="control" %}
|
||||||
{% bootstrap_field form.enabled layout="control" %}
|
{% bootstrap_field form.enabled layout="control" %}
|
||||||
{% bootstrap_field form.events layout="control" %}
|
{% bootstrap_field form.events layout="control" %}
|
||||||
{% bootstrap_field form.all_events layout="control" %}
|
{% bootstrap_field form.all_events layout="control" %}
|
||||||
|
|||||||
@@ -90,7 +90,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<div class="alert-info">{% trans "This webhook did not receive any events in the last 30 days." %}</div>
|
<div class="alert alert-info">{% trans "This webhook did not receive any events in the last 30 days." %}</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% include "pretixcontrol/pagination.html" %}
|
{% include "pretixcontrol/pagination.html" %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -35,6 +35,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th>{% trans "Target URL" %}</th>
|
<th>{% trans "Target URL" %}</th>
|
||||||
<th>{% trans "Events" %}</th>
|
<th>{% trans "Events" %}</th>
|
||||||
|
<th>{% trans "Comment" %}</th>
|
||||||
<th></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -61,6 +62,11 @@
|
|||||||
</ul>
|
</ul>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
|
<td>
|
||||||
|
{% if w.comment %}
|
||||||
|
{{ w.comment }}
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
<td class="text-right flip">
|
<td class="text-right flip">
|
||||||
<a href="{% url "control:organizer.webhook.edit" organizer=request.organizer.slug webhook=w.id %}"
|
<a href="{% url "control:organizer.webhook.edit" organizer=request.organizer.slug webhook=w.id %}"
|
||||||
class="btn btn-default btn-sm" data-toggle="tooltip" title="{% trans "Edit" %}">
|
class="btn btn-default btn-sm" data-toggle="tooltip" title="{% trans "Edit" %}">
|
||||||
|
|||||||
@@ -275,6 +275,18 @@
|
|||||||
<input type="number" value="13" class="input-block-level form-control" step="0.1"
|
<input type="number" value="13" class="input-block-level form-control" step="0.1"
|
||||||
id="toolbox-fontsize">
|
id="toolbox-fontsize">
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<label>{% trans "Line height" %}</label><br>
|
||||||
|
<input type="number" value="1" class="input-block-level form-control" step="0.1"
|
||||||
|
id="toolbox-lineheight">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row control-group text">
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<label>{% trans "Text color" %}</label><br>
|
||||||
|
<input type="text" value="#000000" class="input-block-level form-control colorpickerfield"
|
||||||
|
id="toolbox-col">
|
||||||
|
</div>
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
<label> </label><br>
|
<label> </label><br>
|
||||||
<div class="btn-group btn-group-justified" role="group">
|
<div class="btn-group btn-group-justified" role="group">
|
||||||
@@ -295,15 +307,6 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row control-group text">
|
|
||||||
<div class="col-sm-6">
|
|
||||||
<label>{% trans "Text color" %}</label><br>
|
|
||||||
<input type="text" value="#000000" class="input-block-level form-control colorpickerfield"
|
|
||||||
id="toolbox-col">
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-6">
|
|
||||||
<label> </label><br>
|
<label> </label><br>
|
||||||
<div class="btn-group btn-group-justified" id="toolbox-align">
|
<div class="btn-group btn-group-justified" id="toolbox-align">
|
||||||
<div class="btn-group" role="group">
|
<div class="btn-group" role="group">
|
||||||
|
|||||||
@@ -487,6 +487,7 @@
|
|||||||
{% trans "These settings are optional, if you leave them empty, the default values from the product settings will be used." %}
|
{% trans "These settings are optional, if you leave them empty, the default values from the product settings will be used." %}
|
||||||
</p>
|
</p>
|
||||||
{% for f in itemvar_forms %}
|
{% for f in itemvar_forms %}
|
||||||
|
{% bootstrap_form_errors f %}
|
||||||
<div class="form-group subevent-itemvar-group">
|
<div class="form-group subevent-itemvar-group">
|
||||||
<label class="col-md-3 control-label" for="id_{{ f.prefix }}-price">
|
<label class="col-md-3 control-label" for="id_{{ f.prefix }}-price">
|
||||||
{% if f.variation %}{{ f.item }} – {{ f.variation }}{% else %}{{ f.item }}{% endif %}
|
{% if f.variation %}{{ f.item }} – {{ f.variation }}{% else %}{{ f.item }}{% endif %}
|
||||||
|
|||||||
@@ -151,6 +151,7 @@
|
|||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>{% trans "Item prices" %}</legend>
|
<legend>{% trans "Item prices" %}</legend>
|
||||||
{% for f in itemvar_forms %}
|
{% for f in itemvar_forms %}
|
||||||
|
{% bootstrap_form_errors f %}
|
||||||
<div class="form-group subevent-itemvar-group">
|
<div class="form-group subevent-itemvar-group">
|
||||||
<label class="col-md-3 control-label" for="id_{{ f.prefix }}-price">
|
<label class="col-md-3 control-label" for="id_{{ f.prefix }}-price">
|
||||||
{% if f.variation %}{{ f.item }} – {{ f.variation }}{% else %}{{ f.item }}{% endif %}
|
{% if f.variation %}{{ f.item }} – {{ f.variation }}{% else %}{{ f.item }}{% endif %}
|
||||||
|
|||||||
@@ -129,6 +129,7 @@
|
|||||||
{% trans "These settings are optional, if you leave them empty, the default values from the product settings will be used." %}
|
{% trans "These settings are optional, if you leave them empty, the default values from the product settings will be used." %}
|
||||||
</p>
|
</p>
|
||||||
{% for f in itemvar_forms %}
|
{% for f in itemvar_forms %}
|
||||||
|
{% bootstrap_form_errors f %}
|
||||||
<div class="form-group subevent-itemvar-group">
|
<div class="form-group subevent-itemvar-group">
|
||||||
<label class="col-md-3 control-label" for="id_{{ f.prefix }}-price">
|
<label class="col-md-3 control-label" for="id_{{ f.prefix }}-price">
|
||||||
{% if f.variation %}{{ f.item }} – {{ f.variation }}{% else %}{{ f.item }}{% endif %}
|
{% if f.variation %}{{ f.item }} – {{ f.variation }}{% else %}{{ f.item }}{% endif %}
|
||||||
|
|||||||
@@ -50,6 +50,11 @@
|
|||||||
<div class="col-xs-12 one-line-checkboxes">
|
<div class="col-xs-12 one-line-checkboxes">
|
||||||
{% bootstrap_field filter_form.weekday %}
|
{% bootstrap_field filter_form.weekday %}
|
||||||
</div>
|
</div>
|
||||||
|
{% for mf in meta_fields %}
|
||||||
|
<div class="col-md-3 col-sm-6 col-xs-12">
|
||||||
|
{% bootstrap_field mf %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="text-right flip">
|
<div class="text-right flip">
|
||||||
|
|||||||
@@ -261,6 +261,7 @@ urlpatterns = [
|
|||||||
re_path(r'^subevents/bulk_add$', subevents.SubEventBulkCreate.as_view(), name='event.subevents.bulk'),
|
re_path(r'^subevents/bulk_add$', subevents.SubEventBulkCreate.as_view(), name='event.subevents.bulk'),
|
||||||
re_path(r'^subevents/bulk_action$', subevents.SubEventBulkAction.as_view(), name='event.subevents.bulkaction'),
|
re_path(r'^subevents/bulk_action$', subevents.SubEventBulkAction.as_view(), name='event.subevents.bulkaction'),
|
||||||
re_path(r'^subevents/bulk_edit$', subevents.SubEventBulkEdit.as_view(), name='event.subevents.bulkedit'),
|
re_path(r'^subevents/bulk_edit$', subevents.SubEventBulkEdit.as_view(), name='event.subevents.bulkedit'),
|
||||||
|
re_path(r'^subevents/typeahead/meta/$', typeahead.subevent_meta_values, name='event.subevents.meta.typeahead'),
|
||||||
re_path(r'^items/$', item.ItemList.as_view(), name='event.items'),
|
re_path(r'^items/$', item.ItemList.as_view(), name='event.items'),
|
||||||
re_path(r'^items/add$', item.ItemCreate.as_view(), name='event.items.add'),
|
re_path(r'^items/add$', item.ItemCreate.as_view(), name='event.items.add'),
|
||||||
re_path(r'^items/(?P<item>\d+)/$', item.ItemUpdateGeneral.as_view(), name='event.item'),
|
re_path(r'^items/(?P<item>\d+)/$', item.ItemUpdateGeneral.as_view(), name='event.item'),
|
||||||
|
|||||||
@@ -39,8 +39,8 @@ from django.utils.crypto import get_random_string
|
|||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
from django.views.generic import TemplateView
|
from django.views.generic import TemplateView
|
||||||
from PyPDF2 import PdfReader, PdfWriter
|
from pypdf import PdfReader, PdfWriter
|
||||||
from PyPDF2.errors import PdfReadError
|
from pypdf.errors import PdfReadError
|
||||||
from reportlab.lib.units import mm
|
from reportlab.lib.units import mm
|
||||||
|
|
||||||
from pretix.base.i18n import language
|
from pretix.base.i18n import language
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ class SubEventQueryMixin:
|
|||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def filter_form(self):
|
def filter_form(self):
|
||||||
return SubEventFilterForm(data=self.request_data, prefix='filter')
|
return SubEventFilterForm(data=self.request_data, prefix='filter', event=self.request.event)
|
||||||
|
|
||||||
|
|
||||||
class SubEventList(EventPermissionRequiredMixin, PaginationMixin, SubEventQueryMixin, ListView):
|
class SubEventList(EventPermissionRequiredMixin, PaginationMixin, SubEventQueryMixin, ListView):
|
||||||
@@ -125,6 +125,9 @@ class SubEventList(EventPermissionRequiredMixin, PaginationMixin, SubEventQueryM
|
|||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
ctx = super().get_context_data(**kwargs)
|
ctx = super().get_context_data(**kwargs)
|
||||||
ctx['filter_form'] = self.filter_form
|
ctx['filter_form'] = self.filter_form
|
||||||
|
ctx['meta_fields'] = [
|
||||||
|
self.filter_form['meta_{}'.format(p.name)] for p in self.request.organizer.meta_properties.filter(filter_allowed=True)
|
||||||
|
]
|
||||||
|
|
||||||
quotas = []
|
quotas = []
|
||||||
for s in ctx['subevents']:
|
for s in ctx['subevents']:
|
||||||
|
|||||||
@@ -48,7 +48,8 @@ from django.utils.translation import gettext as _, pgettext
|
|||||||
|
|
||||||
from pretix.base.models import (
|
from pretix.base.models import (
|
||||||
EventMetaProperty, EventMetaValue, ItemMetaProperty, ItemMetaValue,
|
EventMetaProperty, EventMetaValue, ItemMetaProperty, ItemMetaValue,
|
||||||
ItemVariation, ItemVariationMetaValue, Order, Organizer, User, Voucher,
|
ItemVariation, ItemVariationMetaValue, Order, Organizer, SubEventMetaValue,
|
||||||
|
User, Voucher,
|
||||||
)
|
)
|
||||||
from pretix.control.forms.event import EventWizardCopyForm
|
from pretix.control.forms.event import EventWizardCopyForm
|
||||||
from pretix.control.permissions import (
|
from pretix.control.permissions import (
|
||||||
@@ -743,6 +744,38 @@ def meta_values(request):
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
def subevent_meta_values(request, organizer, event):
|
||||||
|
q = request.GET.get('q')
|
||||||
|
propname = request.GET.get('property')
|
||||||
|
|
||||||
|
matches = SubEventMetaValue.objects.filter(
|
||||||
|
value__icontains=q,
|
||||||
|
property__name=propname,
|
||||||
|
subevent__event_id=request.event.pk,
|
||||||
|
)
|
||||||
|
event_matches = EventMetaValue.objects.filter(
|
||||||
|
value__icontains=q,
|
||||||
|
property__name=propname,
|
||||||
|
event_id=request.event.pk,
|
||||||
|
)
|
||||||
|
defaults = EventMetaProperty.objects.filter(
|
||||||
|
default__icontains=q,
|
||||||
|
name=propname,
|
||||||
|
organizer_id=request.organizer.pk,
|
||||||
|
)
|
||||||
|
|
||||||
|
return JsonResponse({
|
||||||
|
'results': [
|
||||||
|
{'name': v, 'id': v}
|
||||||
|
for v in sorted(
|
||||||
|
set(defaults.values_list('default', flat=True)[:10]) |
|
||||||
|
set(matches.values_list('value', flat=True)[:10]) |
|
||||||
|
set(event_matches.values_list('value', flat=True)[:10])
|
||||||
|
)
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
def item_meta_values(request, organizer, event):
|
def item_meta_values(request, organizer, event):
|
||||||
q = request.GET.get('q')
|
q = request.GET.get('q')
|
||||||
propname = request.GET.get('property')
|
propname = request.GET.get('property')
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ msgstr ""
|
|||||||
"Project-Id-Version: 1\n"
|
"Project-Id-Version: 1\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2023-02-01 13:31+0000\n"
|
"POT-Creation-Date: 2023-02-01 13:31+0000\n"
|
||||||
"PO-Revision-Date: 2023-02-01 13:45+0000\n"
|
"PO-Revision-Date: 2023-02-02 20:00+0000\n"
|
||||||
"Last-Translator: Raphael Michel <michel@rami.io>\n"
|
"Last-Translator: juliusstoerrle <juliusstoerrle@gmx.de>\n"
|
||||||
"Language-Team: German (informal) <https://translate.pretix.eu/projects/"
|
"Language-Team: German (informal) <https://translate.pretix.eu/projects/"
|
||||||
"pretix/pretix/de_Informal/>\n"
|
"pretix/pretix/de_Informal/>\n"
|
||||||
"Language: de_Informal\n"
|
"Language: de_Informal\n"
|
||||||
@@ -27838,7 +27838,7 @@ msgid ""
|
|||||||
"Some of the products in your cart can only be purchased if there is an "
|
"Some of the products in your cart can only be purchased if there is an "
|
||||||
"active membership on your account."
|
"active membership on your account."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Manche der Produkte in Ihrem Warenkorb kannst du nur buchen, wenn eine "
|
"Manche der Produkte in deinem Warenkorb kannst du nur buchen, wenn eine "
|
||||||
"aktive Mitgliedschaft in deinem Kundenkonto vorliegt."
|
"aktive Mitgliedschaft in deinem Kundenkonto vorliegt."
|
||||||
|
|
||||||
#: pretix/presale/templates/pretixpresale/event/checkout_membership.html:38
|
#: pretix/presale/templates/pretixpresale/event/checkout_membership.html:38
|
||||||
|
|||||||
@@ -7,16 +7,16 @@ msgstr ""
|
|||||||
"Project-Id-Version: 1\n"
|
"Project-Id-Version: 1\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2023-02-01 13:31+0000\n"
|
"POT-Creation-Date: 2023-02-01 13:31+0000\n"
|
||||||
"PO-Revision-Date: 2022-09-11 21:00+0000\n"
|
"PO-Revision-Date: 2023-02-07 10:00+0000\n"
|
||||||
"Last-Translator: Thomas Vranken <thvranken@gmail.com>\n"
|
"Last-Translator: Thomas Vranken <thvranken@gmail.com>\n"
|
||||||
"Language-Team: Dutch <https://translate.pretix.eu/projects/pretix/pretix/nl/"
|
"Language-Team: Dutch <https://translate.pretix.eu/projects/pretix/pretix/nl/>"
|
||||||
">\n"
|
"\n"
|
||||||
"Language: nl\n"
|
"Language: nl\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||||
"X-Generator: Weblate 4.14\n"
|
"X-Generator: Weblate 4.15.2\n"
|
||||||
|
|
||||||
#: pretix/api/auth/devicesecurity.py:28
|
#: pretix/api/auth/devicesecurity.py:28
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -149,7 +149,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: pretix/api/serializers/item.py:266
|
#: pretix/api/serializers/item.py:266
|
||||||
msgid "Only admission products can currently be personalized."
|
msgid "Only admission products can currently be personalized."
|
||||||
msgstr ""
|
msgstr "Momenteel kunnen enkel toegangsproducten gepersonaliseerd worden."
|
||||||
|
|
||||||
#: pretix/api/serializers/item.py:277
|
#: pretix/api/serializers/item.py:277
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -186,10 +186,9 @@ msgid "This type of question cannot be asked during check-in."
|
|||||||
msgstr "Deze soort vraag kan niet bij het inchecken worden gesteld."
|
msgstr "Deze soort vraag kan niet bij het inchecken worden gesteld."
|
||||||
|
|
||||||
#: pretix/api/serializers/order.py:74
|
#: pretix/api/serializers/order.py:74
|
||||||
#, fuzzy, python-brace-format
|
#, python-brace-format
|
||||||
#| msgid "{value} is not a valid email address."
|
|
||||||
msgid "\"{input}\" is not a valid choice."
|
msgid "\"{input}\" is not a valid choice."
|
||||||
msgstr "{value} is geen geldig e-mailadres."
|
msgstr "\"{input}\" is geen geldige keuze."
|
||||||
|
|
||||||
#: pretix/api/serializers/order.py:1167 pretix/api/views/cart.py:210
|
#: pretix/api/serializers/order.py:1167 pretix/api/views/cart.py:210
|
||||||
#: pretix/base/services/orders.py:1392
|
#: pretix/base/services/orders.py:1392
|
||||||
@@ -15336,10 +15335,8 @@ msgstr "Server-transactiecode"
|
|||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/boxoffice/payment.html:62
|
#: pretix/control/templates/pretixcontrol/boxoffice/payment.html:62
|
||||||
#: pretix/control/templates/pretixcontrol/boxoffice/payment.html:78
|
#: pretix/control/templates/pretixcontrol/boxoffice/payment.html:78
|
||||||
#, fuzzy
|
|
||||||
#| msgid "Payer and reference"
|
|
||||||
msgid "Payment reference"
|
msgid "Payment reference"
|
||||||
msgstr "Betaler en kenmerk"
|
msgstr "Betalingsreferentie"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/boxoffice/payment.html:65
|
#: pretix/control/templates/pretixcontrol/boxoffice/payment.html:65
|
||||||
msgid "Payment Application"
|
msgid "Payment Application"
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import json
|
|||||||
import logging
|
import logging
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from datetime import datetime, time, timedelta
|
from datetime import datetime, time, timedelta
|
||||||
|
from decimal import Decimal
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from typing import Tuple
|
from typing import Tuple
|
||||||
|
|
||||||
@@ -50,8 +51,8 @@ from django.db.models import Exists, OuterRef, Q, Subquery
|
|||||||
from django.db.models.functions import Cast, Coalesce
|
from django.db.models.functions import Cast, Coalesce
|
||||||
from django.utils.timezone import make_aware
|
from django.utils.timezone import make_aware
|
||||||
from django.utils.translation import gettext as _, gettext_lazy, pgettext_lazy
|
from django.utils.translation import gettext as _, gettext_lazy, pgettext_lazy
|
||||||
from PyPDF2 import PdfMerger, PdfReader, PdfWriter, Transformation
|
from pypdf import PdfMerger, PdfReader, PdfWriter, Transformation
|
||||||
from PyPDF2.generic import RectangleObject
|
from pypdf.generic import RectangleObject
|
||||||
from reportlab.lib import pagesizes
|
from reportlab.lib import pagesizes
|
||||||
from reportlab.lib.units import mm
|
from reportlab.lib.units import mm
|
||||||
from reportlab.pdfgen import canvas
|
from reportlab.pdfgen import canvas
|
||||||
@@ -221,17 +222,17 @@ def render_pdf(event, positions, opt):
|
|||||||
di = i % badges_per_page
|
di = i % badges_per_page
|
||||||
if di == 0:
|
if di == 0:
|
||||||
nup_page = nup_pdf.add_blank_page(
|
nup_page = nup_pdf.add_blank_page(
|
||||||
width=opt['pagesize'][0],
|
width=Decimal('%.5f' % (opt['pagesize'][0])),
|
||||||
height=opt['pagesize'][1],
|
height=Decimal('%.5f' % (opt['pagesize'][1])),
|
||||||
)
|
)
|
||||||
tx = opt['margins'][3] + (di % opt['cols']) * opt['offsets'][0]
|
tx = opt['margins'][3] + (di % opt['cols']) * opt['offsets'][0]
|
||||||
ty = opt['margins'][2] + (opt['rows'] - 1 - (di // opt['cols'])) * opt['offsets'][1]
|
ty = opt['margins'][2] + (opt['rows'] - 1 - (di // opt['cols'])) * opt['offsets'][1]
|
||||||
page.add_transformation(Transformation().translate(tx, ty))
|
page.add_transformation(Transformation().translate(tx, ty))
|
||||||
page.mediabox = RectangleObject((
|
page.mediabox = RectangleObject((
|
||||||
page.mediabox.left.as_numeric() + tx,
|
Decimal('%.5f' % (page.mediabox.left.as_numeric() + tx)),
|
||||||
page.mediabox.bottom.as_numeric() + ty,
|
Decimal('%.5f' % (page.mediabox.bottom.as_numeric() + ty)),
|
||||||
page.mediabox.right.as_numeric() + tx,
|
Decimal('%.5f' % (page.mediabox.right.as_numeric() + tx)),
|
||||||
page.mediabox.top.as_numeric() + ty
|
Decimal('%.5f' % (page.mediabox.top.as_numeric() + ty))
|
||||||
))
|
))
|
||||||
page.trimbox = page.mediabox
|
page.trimbox = page.mediabox
|
||||||
nup_page.merge_page(page)
|
nup_page.merge_page(page)
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ from django_scopes import ScopedManager
|
|||||||
from i18nfield.fields import I18nCharField, I18nTextField
|
from i18nfield.fields import I18nCharField, I18nTextField
|
||||||
|
|
||||||
from pretix.base.email import get_email_context
|
from pretix.base.email import get_email_context
|
||||||
|
from pretix.base.i18n import language
|
||||||
from pretix.base.models import (
|
from pretix.base.models import (
|
||||||
Event, InvoiceAddress, Item, Order, OrderPosition, SubEvent,
|
Event, InvoiceAddress, Item, Order, OrderPosition, SubEvent,
|
||||||
)
|
)
|
||||||
@@ -133,44 +134,45 @@ class ScheduledMail(models.Model):
|
|||||||
send_to_attendees = self.rule.send_to in (Rule.ATTENDEES, Rule.BOTH)
|
send_to_attendees = self.rule.send_to in (Rule.ATTENDEES, Rule.BOTH)
|
||||||
|
|
||||||
for o in orders:
|
for o in orders:
|
||||||
positions = list(o.positions.all())
|
with language(o.locale, e.settings.region):
|
||||||
o_sent = False
|
positions = list(o.positions.all())
|
||||||
|
o_sent = False
|
||||||
|
|
||||||
try:
|
|
||||||
ia = o.invoice_address
|
|
||||||
except InvoiceAddress.DoesNotExist:
|
|
||||||
ia = InvoiceAddress(order=o)
|
|
||||||
|
|
||||||
if send_to_orders and o.email:
|
|
||||||
email_ctx = get_email_context(event=e, order=o, invoice_address=ia)
|
|
||||||
try:
|
try:
|
||||||
o.send_mail(self.rule.subject, self.rule.template, email_ctx,
|
ia = o.invoice_address
|
||||||
log_entry_type='pretix.plugins.sendmail.rule.order.email.sent')
|
except InvoiceAddress.DoesNotExist:
|
||||||
o_sent = True
|
ia = InvoiceAddress(order=o)
|
||||||
except SendMailException:
|
|
||||||
... # ¯\_(ツ)_/¯
|
|
||||||
|
|
||||||
if send_to_attendees:
|
if send_to_orders and o.email:
|
||||||
if not self.rule.all_products:
|
email_ctx = get_email_context(event=e, order=o, invoice_address=ia)
|
||||||
positions = [p for p in positions if p.item_id in limit_products]
|
|
||||||
if self.subevent_id:
|
|
||||||
positions = [p for p in positions if p.subevent_id == self.subevent_id]
|
|
||||||
|
|
||||||
for p in positions:
|
|
||||||
try:
|
try:
|
||||||
if p.attendee_email and (p.attendee_email != o.email or not o_sent):
|
o.send_mail(self.rule.subject, self.rule.template, email_ctx,
|
||||||
email_ctx = get_email_context(event=e, order=o, invoice_address=ia, position=p)
|
log_entry_type='pretix.plugins.sendmail.rule.order.email.sent')
|
||||||
p.send_mail(self.rule.subject, self.rule.template, email_ctx,
|
o_sent = True
|
||||||
log_entry_type='pretix.plugins.sendmail.rule.order.position.email.sent')
|
|
||||||
elif not o_sent and o.email:
|
|
||||||
email_ctx = get_email_context(event=e, order=o, invoice_address=ia)
|
|
||||||
o.send_mail(self.rule.subject, self.rule.template, email_ctx,
|
|
||||||
log_entry_type='pretix.plugins.sendmail.rule.order.email.sent')
|
|
||||||
o_sent = True
|
|
||||||
except SendMailException:
|
except SendMailException:
|
||||||
... # ¯\_(ツ)_/¯
|
... # ¯\_(ツ)_/¯
|
||||||
|
|
||||||
self.last_successful_order_id = o.pk
|
if send_to_attendees:
|
||||||
|
if not self.rule.all_products:
|
||||||
|
positions = [p for p in positions if p.item_id in limit_products]
|
||||||
|
if self.subevent_id:
|
||||||
|
positions = [p for p in positions if p.subevent_id == self.subevent_id]
|
||||||
|
|
||||||
|
for p in positions:
|
||||||
|
try:
|
||||||
|
if p.attendee_email and (p.attendee_email != o.email or not o_sent):
|
||||||
|
email_ctx = get_email_context(event=e, order=o, invoice_address=ia, position=p)
|
||||||
|
p.send_mail(self.rule.subject, self.rule.template, email_ctx,
|
||||||
|
log_entry_type='pretix.plugins.sendmail.rule.order.position.email.sent')
|
||||||
|
elif not o_sent and o.email:
|
||||||
|
email_ctx = get_email_context(event=e, order=o, invoice_address=ia)
|
||||||
|
o.send_mail(self.rule.subject, self.rule.template, email_ctx,
|
||||||
|
log_entry_type='pretix.plugins.sendmail.rule.order.email.sent')
|
||||||
|
o_sent = True
|
||||||
|
except SendMailException:
|
||||||
|
... # ¯\_(ツ)_/¯
|
||||||
|
|
||||||
|
self.last_successful_order_id = o.pk
|
||||||
|
|
||||||
|
|
||||||
class Rule(models.Model, LoggingMixin):
|
class Rule(models.Model, LoggingMixin):
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ from django.db.models import OuterRef, Q, Subquery
|
|||||||
from django.db.models.functions import Cast, Coalesce
|
from django.db.models.functions import Cast, Coalesce
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
from django.utils.translation import gettext as _, gettext_lazy, pgettext_lazy
|
from django.utils.translation import gettext as _, gettext_lazy, pgettext_lazy
|
||||||
from PyPDF2 import PdfMerger
|
from pypdf import PdfMerger
|
||||||
|
|
||||||
from pretix.base.exporter import BaseExporter
|
from pretix.base.exporter import BaseExporter
|
||||||
from pretix.base.i18n import language
|
from pretix.base.i18n import language
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ from django.http import HttpRequest
|
|||||||
from django.template.loader import get_template
|
from django.template.loader import get_template
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from PyPDF2 import PdfMerger
|
from pypdf import PdfMerger
|
||||||
|
|
||||||
from pretix.base.i18n import language
|
from pretix.base.i18n import language
|
||||||
from pretix.base.models import Order, OrderPosition
|
from pretix.base.models import Order, OrderPosition
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
<span role="columnheader" aria-sort="none">{% trans "Price total" %}</span>
|
<span role="columnheader" aria-sort="none">{% trans "Price total" %}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div role="rowgroup">
|
<div role="rowgroup" class="firstchild-in-panel">
|
||||||
{% for line in cart.positions %}
|
{% for line in cart.positions %}
|
||||||
<div role="row" class="row cart-row {% if download %}has-downloads{% endif %}{% if editable %}editable{% endif %}">
|
<div role="row" class="row cart-row {% if download %}has-downloads{% endif %}{% if editable %}editable{% endif %}">
|
||||||
<div role="cell" class="product">
|
<div role="cell" class="product">
|
||||||
|
|||||||
+33
-33
@@ -8,7 +8,7 @@
|
|||||||
"name": "pretix",
|
"name": "pretix",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/core": "^7.20.7",
|
"@babel/core": "^7.20.12",
|
||||||
"@babel/preset-env": "^7.20.2",
|
"@babel/preset-env": "^7.20.2",
|
||||||
"@rollup/plugin-babel": "^6.0.3",
|
"@rollup/plugin-babel": "^6.0.3",
|
||||||
"@rollup/plugin-node-resolve": "^15.0.1",
|
"@rollup/plugin-node-resolve": "^15.0.1",
|
||||||
@@ -50,24 +50,24 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/core": {
|
"node_modules/@babel/core": {
|
||||||
"version": "7.20.7",
|
"version": "7.20.12",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.7.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.12.tgz",
|
||||||
"integrity": "sha512-t1ZjCluspe5DW24bn2Rr1CDb2v9rn/hROtg9a2tmd0+QYf4bsloYfLQzjG4qHPNMhWtKdGC33R5AxGR2Af2cBw==",
|
"integrity": "sha512-XsMfHovsUYHFMdrIHkZphTN/2Hzzi78R08NuHfDBehym2VsPDL6Zn/JAD/JQdnRvbSsbQc4mVaU1m6JgtTEElg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ampproject/remapping": "^2.1.0",
|
"@ampproject/remapping": "^2.1.0",
|
||||||
"@babel/code-frame": "^7.18.6",
|
"@babel/code-frame": "^7.18.6",
|
||||||
"@babel/generator": "^7.20.7",
|
"@babel/generator": "^7.20.7",
|
||||||
"@babel/helper-compilation-targets": "^7.20.7",
|
"@babel/helper-compilation-targets": "^7.20.7",
|
||||||
"@babel/helper-module-transforms": "^7.20.7",
|
"@babel/helper-module-transforms": "^7.20.11",
|
||||||
"@babel/helpers": "^7.20.7",
|
"@babel/helpers": "^7.20.7",
|
||||||
"@babel/parser": "^7.20.7",
|
"@babel/parser": "^7.20.7",
|
||||||
"@babel/template": "^7.20.7",
|
"@babel/template": "^7.20.7",
|
||||||
"@babel/traverse": "^7.20.7",
|
"@babel/traverse": "^7.20.12",
|
||||||
"@babel/types": "^7.20.7",
|
"@babel/types": "^7.20.7",
|
||||||
"convert-source-map": "^1.7.0",
|
"convert-source-map": "^1.7.0",
|
||||||
"debug": "^4.1.0",
|
"debug": "^4.1.0",
|
||||||
"gensync": "^1.0.0-beta.2",
|
"gensync": "^1.0.0-beta.2",
|
||||||
"json5": "^2.2.1",
|
"json5": "^2.2.2",
|
||||||
"semver": "^6.3.0"
|
"semver": "^6.3.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -79,9 +79,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/core/node_modules/json5": {
|
"node_modules/@babel/core/node_modules/json5": {
|
||||||
"version": "2.2.1",
|
"version": "2.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
|
||||||
"integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==",
|
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
|
||||||
"bin": {
|
"bin": {
|
||||||
"json5": "lib/cli.js"
|
"json5": "lib/cli.js"
|
||||||
},
|
},
|
||||||
@@ -475,9 +475,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/parser": {
|
"node_modules/@babel/parser": {
|
||||||
"version": "7.20.7",
|
"version": "7.20.13",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.7.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.13.tgz",
|
||||||
"integrity": "sha512-T3Z9oHybU+0vZlY9CiDSJQTD5ZapcW18ZctFMi0MOAl/4BjFF4ul7NVSARLdbGO5vDqy9eQiGTV0LtKfvCYvcg==",
|
"integrity": "sha512-gFDLKMfpiXCsjt4za2JA9oTMn70CeseCehb11kRZgvd7+F67Hih3OHOK24cRrWECJ/ljfPGac6ygXAs/C8kIvw==",
|
||||||
"bin": {
|
"bin": {
|
||||||
"parser": "bin/babel-parser.js"
|
"parser": "bin/babel-parser.js"
|
||||||
},
|
},
|
||||||
@@ -1541,9 +1541,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/traverse": {
|
"node_modules/@babel/traverse": {
|
||||||
"version": "7.20.10",
|
"version": "7.20.13",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.10.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.13.tgz",
|
||||||
"integrity": "sha512-oSf1juCgymrSez8NI4A2sr4+uB/mFd9MXplYGPEBnfAuWmmyeVcHa6xLPiaRBcXkcb/28bgxmQLTVwFKE1yfsg==",
|
"integrity": "sha512-kMJXfF0T6DIS9E8cgdLCSAL+cuCK+YEZHWiLK0SXpTo8YRj5lpJu3CDNKiIBCne4m9hhTIqUg6SYTAI39tAiVQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/code-frame": "^7.18.6",
|
"@babel/code-frame": "^7.18.6",
|
||||||
"@babel/generator": "^7.20.7",
|
"@babel/generator": "^7.20.7",
|
||||||
@@ -1551,7 +1551,7 @@
|
|||||||
"@babel/helper-function-name": "^7.19.0",
|
"@babel/helper-function-name": "^7.19.0",
|
||||||
"@babel/helper-hoist-variables": "^7.18.6",
|
"@babel/helper-hoist-variables": "^7.18.6",
|
||||||
"@babel/helper-split-export-declaration": "^7.18.6",
|
"@babel/helper-split-export-declaration": "^7.18.6",
|
||||||
"@babel/parser": "^7.20.7",
|
"@babel/parser": "^7.20.13",
|
||||||
"@babel/types": "^7.20.7",
|
"@babel/types": "^7.20.7",
|
||||||
"debug": "^4.1.0",
|
"debug": "^4.1.0",
|
||||||
"globals": "^11.1.0"
|
"globals": "^11.1.0"
|
||||||
@@ -4063,31 +4063,31 @@
|
|||||||
"integrity": "sha512-sEnuDPpOJR/fcafHMjpcpGN5M2jbUGUHwmuWKM/YdPzeEDJg8bgmbcWQFUfE32MQjti1koACvoPVsDe8Uq+idg=="
|
"integrity": "sha512-sEnuDPpOJR/fcafHMjpcpGN5M2jbUGUHwmuWKM/YdPzeEDJg8bgmbcWQFUfE32MQjti1koACvoPVsDe8Uq+idg=="
|
||||||
},
|
},
|
||||||
"@babel/core": {
|
"@babel/core": {
|
||||||
"version": "7.20.7",
|
"version": "7.20.12",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.7.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.12.tgz",
|
||||||
"integrity": "sha512-t1ZjCluspe5DW24bn2Rr1CDb2v9rn/hROtg9a2tmd0+QYf4bsloYfLQzjG4qHPNMhWtKdGC33R5AxGR2Af2cBw==",
|
"integrity": "sha512-XsMfHovsUYHFMdrIHkZphTN/2Hzzi78R08NuHfDBehym2VsPDL6Zn/JAD/JQdnRvbSsbQc4mVaU1m6JgtTEElg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@ampproject/remapping": "^2.1.0",
|
"@ampproject/remapping": "^2.1.0",
|
||||||
"@babel/code-frame": "^7.18.6",
|
"@babel/code-frame": "^7.18.6",
|
||||||
"@babel/generator": "^7.20.7",
|
"@babel/generator": "^7.20.7",
|
||||||
"@babel/helper-compilation-targets": "^7.20.7",
|
"@babel/helper-compilation-targets": "^7.20.7",
|
||||||
"@babel/helper-module-transforms": "^7.20.7",
|
"@babel/helper-module-transforms": "^7.20.11",
|
||||||
"@babel/helpers": "^7.20.7",
|
"@babel/helpers": "^7.20.7",
|
||||||
"@babel/parser": "^7.20.7",
|
"@babel/parser": "^7.20.7",
|
||||||
"@babel/template": "^7.20.7",
|
"@babel/template": "^7.20.7",
|
||||||
"@babel/traverse": "^7.20.7",
|
"@babel/traverse": "^7.20.12",
|
||||||
"@babel/types": "^7.20.7",
|
"@babel/types": "^7.20.7",
|
||||||
"convert-source-map": "^1.7.0",
|
"convert-source-map": "^1.7.0",
|
||||||
"debug": "^4.1.0",
|
"debug": "^4.1.0",
|
||||||
"gensync": "^1.0.0-beta.2",
|
"gensync": "^1.0.0-beta.2",
|
||||||
"json5": "^2.2.1",
|
"json5": "^2.2.2",
|
||||||
"semver": "^6.3.0"
|
"semver": "^6.3.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"json5": {
|
"json5": {
|
||||||
"version": "2.2.1",
|
"version": "2.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
|
||||||
"integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA=="
|
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="
|
||||||
},
|
},
|
||||||
"semver": {
|
"semver": {
|
||||||
"version": "6.3.0",
|
"version": "6.3.0",
|
||||||
@@ -4378,9 +4378,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@babel/parser": {
|
"@babel/parser": {
|
||||||
"version": "7.20.7",
|
"version": "7.20.13",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.7.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.13.tgz",
|
||||||
"integrity": "sha512-T3Z9oHybU+0vZlY9CiDSJQTD5ZapcW18ZctFMi0MOAl/4BjFF4ul7NVSARLdbGO5vDqy9eQiGTV0LtKfvCYvcg=="
|
"integrity": "sha512-gFDLKMfpiXCsjt4za2JA9oTMn70CeseCehb11kRZgvd7+F67Hih3OHOK24cRrWECJ/ljfPGac6ygXAs/C8kIvw=="
|
||||||
},
|
},
|
||||||
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": {
|
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": {
|
||||||
"version": "7.18.6",
|
"version": "7.18.6",
|
||||||
@@ -5073,9 +5073,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@babel/traverse": {
|
"@babel/traverse": {
|
||||||
"version": "7.20.10",
|
"version": "7.20.13",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.10.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.13.tgz",
|
||||||
"integrity": "sha512-oSf1juCgymrSez8NI4A2sr4+uB/mFd9MXplYGPEBnfAuWmmyeVcHa6xLPiaRBcXkcb/28bgxmQLTVwFKE1yfsg==",
|
"integrity": "sha512-kMJXfF0T6DIS9E8cgdLCSAL+cuCK+YEZHWiLK0SXpTo8YRj5lpJu3CDNKiIBCne4m9hhTIqUg6SYTAI39tAiVQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/code-frame": "^7.18.6",
|
"@babel/code-frame": "^7.18.6",
|
||||||
"@babel/generator": "^7.20.7",
|
"@babel/generator": "^7.20.7",
|
||||||
@@ -5083,7 +5083,7 @@
|
|||||||
"@babel/helper-function-name": "^7.19.0",
|
"@babel/helper-function-name": "^7.19.0",
|
||||||
"@babel/helper-hoist-variables": "^7.18.6",
|
"@babel/helper-hoist-variables": "^7.18.6",
|
||||||
"@babel/helper-split-export-declaration": "^7.18.6",
|
"@babel/helper-split-export-declaration": "^7.18.6",
|
||||||
"@babel/parser": "^7.20.7",
|
"@babel/parser": "^7.20.13",
|
||||||
"@babel/types": "^7.20.7",
|
"@babel/types": "^7.20.7",
|
||||||
"debug": "^4.1.0",
|
"debug": "^4.1.0",
|
||||||
"globals": "^11.1.0"
|
"globals": "^11.1.0"
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {},
|
"scripts": {},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/core": "^7.20.7",
|
"@babel/core": "^7.20.12",
|
||||||
"@babel/preset-env": "^7.20.2",
|
"@babel/preset-env": "^7.20.2",
|
||||||
"@rollup/plugin-babel": "^6.0.3",
|
"@rollup/plugin-babel": "^6.0.3",
|
||||||
"@rollup/plugin-node-resolve": "^15.0.1",
|
"@rollup/plugin-node-resolve": "^15.0.1",
|
||||||
|
|||||||
@@ -119,6 +119,7 @@ $(document).ready(function () {
|
|||||||
date_tolerance: gettext('Tolerance (minutes)'),
|
date_tolerance: gettext('Tolerance (minutes)'),
|
||||||
condition_add: gettext('Add condition'),
|
condition_add: gettext('Add condition'),
|
||||||
minutes: gettext('minutes'),
|
minutes: gettext('minutes'),
|
||||||
|
duplicate: gettext('Duplicate'),
|
||||||
},
|
},
|
||||||
hasRules: false,
|
hasRules: false,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-bind:class="classObject">
|
<div v-bind:class="classObject">
|
||||||
<div class="btn-group pull-right">
|
<div class="btn-group pull-right">
|
||||||
|
<button type="button" class="checkin-rule-remove btn btn-xs btn-default" @click.prevent="duplicate"
|
||||||
|
v-if="level > 0" data-toggle="tooltip" :title="texts.duplicate">
|
||||||
|
<span class="fa fa-copy"></span>
|
||||||
|
</button>
|
||||||
<button type="button" class="checkin-rule-remove btn btn-xs btn-default" @click.prevent="wrapWithOR">OR
|
<button type="button" class="checkin-rule-remove btn btn-xs btn-default" @click.prevent="wrapWithOR">OR
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="checkin-rule-remove btn btn-xs btn-default" @click.prevent="wrapWithAND">AND
|
<button type="button" class="checkin-rule-remove btn btn-xs btn-default" @click.prevent="wrapWithAND">AND
|
||||||
@@ -260,6 +264,10 @@
|
|||||||
remove: function () {
|
remove: function () {
|
||||||
this.$parent.rule[this.$parent.operator].splice(this.index, 1);
|
this.$parent.rule[this.$parent.operator].splice(this.index, 1);
|
||||||
},
|
},
|
||||||
|
duplicate: function () {
|
||||||
|
var r = JSON.parse(JSON.stringify(this.rule));
|
||||||
|
this.$parent.rule[this.$parent.operator].splice(this.index, 0, r);
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -10,25 +10,29 @@
|
|||||||
' <slot></slot>\n' +
|
' <slot></slot>\n' +
|
||||||
' </select>'),
|
' </select>'),
|
||||||
mounted: function () {
|
mounted: function () {
|
||||||
var vm = this;
|
this.build();
|
||||||
var multiple = this.multiple;
|
|
||||||
$(this.$el)
|
|
||||||
.select2(this.opts())
|
|
||||||
.val(this.value || "")
|
|
||||||
.trigger("change")
|
|
||||||
// emit event on change.
|
|
||||||
.on("change", function (e) {
|
|
||||||
vm.$emit("input", $(this).select2('data'));
|
|
||||||
});
|
|
||||||
if (vm.value) {
|
|
||||||
for (var i = 0; i < vm.value["objectList"].length; i++) {
|
|
||||||
var option = new Option(vm.value["objectList"][i]["lookup"][2], vm.value["objectList"][i]["lookup"][1], true, true);
|
|
||||||
$(vm.$el).append(option);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$(vm.$el).trigger("change");
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
build: function () {
|
||||||
|
var vm = this;
|
||||||
|
var multiple = this.multiple;
|
||||||
|
$(this.$el)
|
||||||
|
.empty()
|
||||||
|
.select2(this.opts())
|
||||||
|
.val(this.value || "")
|
||||||
|
.trigger("change")
|
||||||
|
// emit event on change.
|
||||||
|
.on("change", function (e) {
|
||||||
|
vm.$emit("input", $(this).select2('data'));
|
||||||
|
});
|
||||||
|
if (vm.value) {
|
||||||
|
for (var i = 0; i < vm.value["objectList"].length; i++) {
|
||||||
|
var option = new Option(vm.value["objectList"][i]["lookup"][2], vm.value["objectList"][i]["lookup"][1], true, true);
|
||||||
|
$(vm.$el).append(option);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$(vm.$el).trigger("change");
|
||||||
|
},
|
||||||
opts: function () {
|
opts: function () {
|
||||||
return {
|
return {
|
||||||
theme: "bootstrap",
|
theme: "bootstrap",
|
||||||
@@ -60,17 +64,29 @@
|
|||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
placeholder: function (val) {
|
placeholder: function (val) {
|
||||||
$(this.$el).empty().select2(this.opts());
|
$(this.$el).select2("destroy");
|
||||||
this.build();
|
this.build();
|
||||||
},
|
},
|
||||||
required: function (val) {
|
required: function (val) {
|
||||||
$(this.$el).empty().select2(this.opts());
|
$(this.$el).select2("destroy");
|
||||||
this.build();
|
this.build();
|
||||||
},
|
},
|
||||||
url: function (val) {
|
url: function (val) {
|
||||||
$(this.$el).empty().select2(this.opts());
|
$(this.$el).select2("destroy");
|
||||||
this.build();
|
this.build();
|
||||||
},
|
},
|
||||||
|
value: function (newval, oldval) {
|
||||||
|
if (JSON.stringify(newval) !== JSON.stringify(oldval)) {
|
||||||
|
$(this.$el).empty();
|
||||||
|
if (newval) {
|
||||||
|
for (var i = 0; i < newval["objectList"].length; i++) {
|
||||||
|
var option = new Option(newval["objectList"][i]["lookup"][2], newval["objectList"][i]["lookup"][1], true, true);
|
||||||
|
$(this.$el).append(option);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$(this.$el).trigger("change");
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
destroyed: function () {
|
destroyed: function () {
|
||||||
$(this.$el)
|
$(this.$el)
|
||||||
|
|||||||
@@ -161,8 +161,8 @@ var editor = {
|
|||||||
left: editor._px2mm(left).toFixed(2),
|
left: editor._px2mm(left).toFixed(2),
|
||||||
bottom: editor._px2mm(bottom).toFixed(2),
|
bottom: editor._px2mm(bottom).toFixed(2),
|
||||||
fontsize: editor._px2pt(o.getFontSize()).toFixed(1),
|
fontsize: editor._px2pt(o.getFontSize()).toFixed(1),
|
||||||
|
lineheight: o.lineHeight,
|
||||||
color: col,
|
color: col,
|
||||||
//lineheight: o.lineHeight,
|
|
||||||
fontfamily: o.fontFamily,
|
fontfamily: o.fontFamily,
|
||||||
bold: o.fontWeight === 'bold',
|
bold: o.fontWeight === 'bold',
|
||||||
italic: o.fontStyle === 'italic',
|
italic: o.fontStyle === 'italic',
|
||||||
@@ -241,7 +241,7 @@ var editor = {
|
|||||||
o = editor._add_text();
|
o = editor._add_text();
|
||||||
o.setColor('rgb(' + d.color[0] + ',' + d.color[1] + ',' + d.color[2] + ')');
|
o.setColor('rgb(' + d.color[0] + ',' + d.color[1] + ',' + d.color[2] + ')');
|
||||||
o.setFontSize(editor._pt2px(d.fontsize));
|
o.setFontSize(editor._pt2px(d.fontsize));
|
||||||
//o.setLineHeight(d.lineheight);
|
o.setLineHeight(d.lineheight || 1);
|
||||||
o.setFontFamily(d.fontfamily);
|
o.setFontFamily(d.fontfamily);
|
||||||
o.setFontWeight(d.bold ? 'bold' : 'normal');
|
o.setFontWeight(d.bold ? 'bold' : 'normal');
|
||||||
o.setFontStyle(d.italic ? 'italic' : 'normal');
|
o.setFontStyle(d.italic ? 'italic' : 'normal');
|
||||||
@@ -483,7 +483,7 @@ var editor = {
|
|||||||
var col = (new fabric.Color(o.getFill()))._source;
|
var col = (new fabric.Color(o.getFill()))._source;
|
||||||
$("#toolbox-col").val("#" + ((1 << 24) + (col[0] << 16) + (col[1] << 8) + col[2]).toString(16).slice(1));
|
$("#toolbox-col").val("#" + ((1 << 24) + (col[0] << 16) + (col[1] << 8) + col[2]).toString(16).slice(1));
|
||||||
$("#toolbox-fontsize").val(editor._px2pt(o.fontSize).toFixed(1));
|
$("#toolbox-fontsize").val(editor._px2pt(o.fontSize).toFixed(1));
|
||||||
//$("#toolbox-lineheight").val(o.lineHeight);
|
$("#toolbox-lineheight").val(o.lineHeight || 1);
|
||||||
$("#toolbox-fontfamily").val(o.fontFamily);
|
$("#toolbox-fontfamily").val(o.fontFamily);
|
||||||
$("#toolbox").find("button[data-action=bold]").toggleClass('active', o.fontWeight === 'bold');
|
$("#toolbox").find("button[data-action=bold]").toggleClass('active', o.fontWeight === 'bold');
|
||||||
$("#toolbox").find("button[data-action=italic]").toggleClass('active', o.fontStyle === 'italic');
|
$("#toolbox").find("button[data-action=italic]").toggleClass('active', o.fontStyle === 'italic');
|
||||||
@@ -600,7 +600,7 @@ var editor = {
|
|||||||
} else if (o.type === "textarea" || o.type === "text") {
|
} else if (o.type === "textarea" || o.type === "text") {
|
||||||
o.setColor($("#toolbox-col").val());
|
o.setColor($("#toolbox-col").val());
|
||||||
o.setFontSize(editor._pt2px($("#toolbox-fontsize").val()));
|
o.setFontSize(editor._pt2px($("#toolbox-fontsize").val()));
|
||||||
//o.setLineHeight($("#toolbox-lineheight").val());
|
o.setLineHeight($("#toolbox-lineheight").val() || 1);
|
||||||
o.setFontFamily($("#toolbox-fontfamily").val());
|
o.setFontFamily($("#toolbox-fontfamily").val());
|
||||||
o.setFontWeight($("#toolbox").find("button[data-action=bold]").is('.active') ? 'bold' : 'normal');
|
o.setFontWeight($("#toolbox").find("button[data-action=bold]").is('.active') ? 'bold' : 'normal');
|
||||||
o.setFontStyle($("#toolbox").find("button[data-action=italic]").is('.active') ? 'italic' : 'normal');
|
o.setFontStyle($("#toolbox").find("button[data-action=italic]").is('.active') ? 'italic' : 'normal');
|
||||||
@@ -861,6 +861,7 @@ var editor = {
|
|||||||
thing.setCoords();
|
thing.setCoords();
|
||||||
editor._create_savepoint();
|
editor._create_savepoint();
|
||||||
break;
|
break;
|
||||||
|
case 8: /* Backspace */
|
||||||
case 46: /* Delete */
|
case 46: /* Delete */
|
||||||
editor._delete();
|
editor._delete();
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -6,7 +6,18 @@ $(function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function update_variation_summary($el) {
|
function update_variation_summary($el) {
|
||||||
var var_name = $el.find("input[name*=-value_]").filter(function () {return !!this.value}).first().val();
|
var var_names = Object.fromEntries(
|
||||||
|
$el
|
||||||
|
.find("input[name*=-value_]")
|
||||||
|
.filter(function () {
|
||||||
|
return !!this.value;
|
||||||
|
})
|
||||||
|
.map(function () {
|
||||||
|
return [[this.getAttribute("lang"), this.value]];
|
||||||
|
})
|
||||||
|
.get()
|
||||||
|
);
|
||||||
|
var var_name = i18nToString(var_names);
|
||||||
var price = $el.find("input[name*=-default_price]").val();
|
var price = $el.find("input[name*=-default_price]").val();
|
||||||
if (price) {
|
if (price) {
|
||||||
var currency = $el.find("[name*=-default_price] + .input-group-addon").text();
|
var currency = $el.find("[name*=-default_price] + .input-group-addon").text();
|
||||||
|
|||||||
@@ -362,7 +362,7 @@ Vue.component('pricebox', {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
Vue.component('variation', {
|
Vue.component('variation', {
|
||||||
template: ('<div class="pretix-widget-variation">'
|
template: ('<div class="pretix-widget-variation" :data-id="variation.id">'
|
||||||
+ '<div class="pretix-widget-item-row">'
|
+ '<div class="pretix-widget-item-row">'
|
||||||
|
|
||||||
// Variation description
|
// Variation description
|
||||||
@@ -411,7 +411,7 @@ Vue.component('variation', {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
Vue.component('item', {
|
Vue.component('item', {
|
||||||
template: ('<div v-bind:class="classObject">'
|
template: ('<div v-bind:class="classObject" :data-id="item.id">'
|
||||||
+ '<div class="pretix-widget-item-row pretix-widget-main-item-row">'
|
+ '<div class="pretix-widget-item-row pretix-widget-main-item-row">'
|
||||||
|
|
||||||
// Product description
|
// Product description
|
||||||
@@ -517,7 +517,7 @@ Vue.component('item', {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
Vue.component('category', {
|
Vue.component('category', {
|
||||||
template: ('<div class="pretix-widget-category">'
|
template: ('<div class="pretix-widget-category" :data-id="category.id">'
|
||||||
+ '<h3 class="pretix-widget-category-name" v-if="category.name">{{ category.name }}</h3>'
|
+ '<h3 class="pretix-widget-category-name" v-if="category.name">{{ category.name }}</h3>'
|
||||||
+ '<div class="pretix-widget-category-description" v-if="category.description" v-html="category.description">'
|
+ '<div class="pretix-widget-category-description" v-if="category.description" v-html="category.description">'
|
||||||
+ '</div>'
|
+ '</div>'
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
.cart-row {
|
.cart-row {
|
||||||
/* Cart grid */
|
/* Cart grid */
|
||||||
padding: 10px 0;
|
padding: 0.75*$line-height-computed 0;
|
||||||
|
|
||||||
.product>*:last-child {
|
.product>*:last-child {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
@@ -100,6 +100,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cart .firstchild-in-panel .cart-row {
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
.cart [role=rowgroup]:last-child {
|
.cart [role=rowgroup]:last-child {
|
||||||
border-top: 1px solid $table-border-color;
|
border-top: 1px solid $table-border-color;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,9 +96,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.headline, &.simple {
|
&.headline, &.simple {
|
||||||
border-top: 2px solid transparent;
|
border-top: 1px solid transparent;
|
||||||
&::before {
|
&::before {
|
||||||
border-top: 2px solid $table-border-color;
|
border-top: 1px solid $table-border-color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -110,14 +110,14 @@ article.item-with-variations {
|
|||||||
|
|
||||||
article.item-with-variations:last-child, .product-row:last-child {
|
article.item-with-variations:last-child, .product-row:last-child {
|
||||||
position: relative;
|
position: relative;
|
||||||
border-bottom: 2px solid transparent;
|
border-bottom: 1px solid transparent;
|
||||||
&::after {
|
&::after {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 15px;
|
left: 15px;
|
||||||
width: calc(100% - 30px);
|
width: calc(100% - 30px);
|
||||||
content: '';
|
content: '';
|
||||||
border-bottom: 2px solid $table-border-color;
|
border-bottom: 1px solid $table-border-color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
article.item-with-variations .product-row:last-child:after {
|
article.item-with-variations .product-row:last-child:after {
|
||||||
@@ -146,7 +146,7 @@ article.item-with-variations .product-row:last-child:after {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.product-row {
|
.product-row {
|
||||||
padding: 10px 0;
|
padding: 0.75*$line-height-computed 0;
|
||||||
|
|
||||||
.count form {
|
.count form {
|
||||||
display: inline;
|
display: inline;
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
@import "../../pretixbase/scss/_contrast.scss";
|
@import "../../pretixbase/scss/_contrast.scss";
|
||||||
|
|
||||||
|
h1, .h1,
|
||||||
|
h2, .h2,
|
||||||
|
h3, .h3 {
|
||||||
|
margin-top: 2.25 * $line-height-computed;
|
||||||
|
margin-bottom: 0.75 * $line-height-computed;
|
||||||
|
}
|
||||||
|
|
||||||
.page-header {
|
.page-header {
|
||||||
position: relative;
|
position: relative;
|
||||||
padding-bottom: 9px;
|
padding-bottom: 9px;
|
||||||
@@ -150,4 +157,4 @@ footer {
|
|||||||
border-top-left-radius: .25em;
|
border-top-left-radius: .25em;
|
||||||
border-bottom-left-radius: .25em;
|
border-bottom-left-radius: .25em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -351,8 +351,8 @@ body.loading .container {
|
|||||||
|
|
||||||
.panel-title a[data-toggle="collapse"], details .panel-title {
|
.panel-title a[data-toggle="collapse"], details .panel-title {
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: 10px 15px;
|
padding: 0.75*$line-height-computed;
|
||||||
margin: -10px -15px;
|
margin: -0.75*$line-height-computed;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
outline: 0;
|
outline: 0;
|
||||||
|
|||||||
@@ -240,7 +240,19 @@
|
|||||||
"pattern": "[a-zA-Z-]*"
|
"pattern": "[a-zA-Z-]*"
|
||||||
},
|
},
|
||||||
"fontsize": {
|
"fontsize": {
|
||||||
"description": "Font size.",
|
"description": "Font size",
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "^[0-9]+(\\.[0-9]+)?$"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"lineheight": {
|
||||||
|
"description": "Line height",
|
||||||
"oneOf": [
|
"oneOf": [
|
||||||
{
|
{
|
||||||
"type": "number"
|
"type": "number"
|
||||||
|
|||||||
+4
-4
@@ -200,7 +200,7 @@ setup(
|
|||||||
# We can upgrade markdown again once django-bootstrap3 upgrades or once we drop Python 3.6 and 3.7
|
# We can upgrade markdown again once django-bootstrap3 upgrades or once we drop Python 3.6 and 3.7
|
||||||
'mt-940==4.23.*',
|
'mt-940==4.23.*',
|
||||||
'oauthlib==3.2.*',
|
'oauthlib==3.2.*',
|
||||||
'openpyxl==3.0.*',
|
'openpyxl==3.1.*',
|
||||||
'packaging',
|
'packaging',
|
||||||
'paypalrestsdk==1.13.*',
|
'paypalrestsdk==1.13.*',
|
||||||
'paypal-checkout-serversdk==1.0.*',
|
'paypal-checkout-serversdk==1.0.*',
|
||||||
@@ -211,8 +211,8 @@ setup(
|
|||||||
'psycopg2-binary',
|
'psycopg2-binary',
|
||||||
'pycountry',
|
'pycountry',
|
||||||
'pycparser==2.21',
|
'pycparser==2.21',
|
||||||
'pycryptodome==3.16.*',
|
'pycryptodome==3.17.*',
|
||||||
'PyPDF2==3.0.*',
|
'pypdf==3.4.*',
|
||||||
'python-bidi==0.4.*', # Support for Arabic in reportlab
|
'python-bidi==0.4.*', # Support for Arabic in reportlab
|
||||||
'python-dateutil==2.8.*',
|
'python-dateutil==2.8.*',
|
||||||
'python-u2flib-server==4.*',
|
'python-u2flib-server==4.*',
|
||||||
@@ -225,7 +225,7 @@ setup(
|
|||||||
'sepaxml==2.6.*',
|
'sepaxml==2.6.*',
|
||||||
'slimit',
|
'slimit',
|
||||||
'static3==0.7.*',
|
'static3==0.7.*',
|
||||||
'stripe==5.0.*',
|
'stripe==5.1.*',
|
||||||
'text-unidecode==1.*',
|
'text-unidecode==1.*',
|
||||||
'tlds>=2020041600',
|
'tlds>=2020041600',
|
||||||
'tqdm==4.*',
|
'tqdm==4.*',
|
||||||
|
|||||||
@@ -176,6 +176,7 @@ TEST_INVOICE_RES = {
|
|||||||
"internal_reference": "",
|
"internal_reference": "",
|
||||||
"additional_text": "",
|
"additional_text": "",
|
||||||
"payment_provider_text": "",
|
"payment_provider_text": "",
|
||||||
|
"payment_provider_stamp": None,
|
||||||
"footer_text": "",
|
"footer_text": "",
|
||||||
"foreign_currency_display": None,
|
"foreign_currency_display": None,
|
||||||
"foreign_currency_rate": None,
|
"foreign_currency_rate": None,
|
||||||
|
|||||||
@@ -445,6 +445,7 @@ def test_order_create_invoice(token_client, organizer, event, order):
|
|||||||
'introductory_text': '',
|
'introductory_text': '',
|
||||||
'additional_text': '',
|
'additional_text': '',
|
||||||
'payment_provider_text': '',
|
'payment_provider_text': '',
|
||||||
|
'payment_provider_stamp': None,
|
||||||
'footer_text': '',
|
'footer_text': '',
|
||||||
'lines': [
|
'lines': [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ TEST_WEBHOOK_RES = {
|
|||||||
"all_events": False,
|
"all_events": False,
|
||||||
"limit_events": ['dummy'],
|
"limit_events": ['dummy'],
|
||||||
"action_types": ['pretix.event.order.paid', 'pretix.event.order.placed'],
|
"action_types": ['pretix.event.order.paid', 'pretix.event.order.placed'],
|
||||||
|
"comment": None,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ from io import BytesIO
|
|||||||
import pytest
|
import pytest
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
from django_scopes import scope
|
from django_scopes import scope
|
||||||
from PyPDF2 import PdfReader
|
from pypdf import PdfReader
|
||||||
|
|
||||||
from pretix.base.models import (
|
from pretix.base.models import (
|
||||||
Event, Item, ItemVariation, Order, OrderPosition, Organizer,
|
Event, Item, ItemVariation, Order, OrderPosition, Organizer,
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ from django.core import mail as djmail
|
|||||||
from django.utils.timezone import now, utc
|
from django.utils.timezone import now, utc
|
||||||
from django_scopes import scopes_disabled
|
from django_scopes import scopes_disabled
|
||||||
|
|
||||||
from pretix.base.models import Order
|
from pretix.base.models import InvoiceAddress, Order
|
||||||
from pretix.plugins.sendmail.models import Rule, ScheduledMail
|
from pretix.plugins.sendmail.models import Rule, ScheduledMail
|
||||||
from pretix.plugins.sendmail.signals import sendmail_run_rules
|
from pretix.plugins.sendmail.signals import sendmail_run_rules
|
||||||
|
|
||||||
@@ -361,3 +361,22 @@ def test_sendmail_rule_disabled(event, order):
|
|||||||
|
|
||||||
sendmail_run_rules(None)
|
sendmail_run_rules(None)
|
||||||
assert len(djmail.outbox) == 0
|
assert len(djmail.outbox) == 0
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
@scopes_disabled()
|
||||||
|
def test_sendmail_context_localization(event, order, pos):
|
||||||
|
order.locale = 'de'
|
||||||
|
order.save()
|
||||||
|
event.settings.name_scheme = 'salutation_given_family'
|
||||||
|
InvoiceAddress.objects.create(
|
||||||
|
order=order,
|
||||||
|
name_parts={'_scheme': 'salutation_given_family', 'salutation': 'Mr', 'given_name': 'Max', 'family_name': 'Mustermann'}
|
||||||
|
)
|
||||||
|
|
||||||
|
djmail.outbox = []
|
||||||
|
event.sendmail_rules.create(send_date=dt_now - datetime.timedelta(hours=1), include_pending=True,
|
||||||
|
subject='meow', template='Hallo {name_for_salutation}')
|
||||||
|
|
||||||
|
sendmail_run_rules(None)
|
||||||
|
assert "Hallo Herr Mustermann" in djmail.outbox[0].body
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ from io import BytesIO
|
|||||||
import pytest
|
import pytest
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
from django_scopes import scope
|
from django_scopes import scope
|
||||||
from PyPDF2 import PdfReader
|
from pypdf import PdfReader
|
||||||
|
|
||||||
from pretix.base.models import (
|
from pretix.base.models import (
|
||||||
Event, Item, ItemVariation, Order, OrderPosition, Organizer,
|
Event, Item, ItemVariation, Order, OrderPosition, Organizer,
|
||||||
|
|||||||
Reference in New Issue
Block a user