forked from CGM_Public/pretix_original
Compare commits
26 Commits
harmonize-
...
compact-ca
| 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
|
||||
|
||||
.. 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::
|
||||
|
||||
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.
|
||||
|
||||
.. 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
|
||||
payment_provider_text string Text to be printed below the product list with
|
||||
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
|
||||
lines list of objects The actual invoice contents
|
||||
├ position integer Number of the line within an invoice.
|
||||
@@ -178,6 +179,7 @@ Endpoints
|
||||
"internal_reference": "",
|
||||
"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_stamp": null,
|
||||
"footer_text": "Big Events LLC - Registration No. 123456 - VAT ID: EU0987654321",
|
||||
"lines": [
|
||||
{
|
||||
@@ -268,6 +270,7 @@ Endpoints
|
||||
"internal_reference": "",
|
||||
"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_stamp": null,
|
||||
"footer_text": "Big Events LLC - Registration No. 123456 - VAT ID: EU0987654321",
|
||||
"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
|
||||
notifications sent to this webhook. See below for
|
||||
valid values
|
||||
comment string Internal comment on this webhook, default ``null``
|
||||
===================================== ========================== =======================================================
|
||||
|
||||
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.changed``
|
||||
* ``pretix.subevent.deleted``
|
||||
* ``pretix.event.item.*``
|
||||
* ``pretix.event.live.activated``
|
||||
* ``pretix.event.live.deactivated``
|
||||
* ``pretix.event.testmode.activated``
|
||||
@@ -98,7 +100,8 @@ Endpoints
|
||||
"target_url": "https://httpstat.us/200",
|
||||
"all_events": false,
|
||||
"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",
|
||||
"all_events": false,
|
||||
"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
|
||||
@@ -162,7 +166,8 @@ Endpoints
|
||||
"target_url": "https://httpstat.us/200",
|
||||
"all_events": false,
|
||||
"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**:
|
||||
@@ -179,7 +184,8 @@ Endpoints
|
||||
"target_url": "https://httpstat.us/200",
|
||||
"all_events": false,
|
||||
"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
|
||||
@@ -224,7 +230,8 @@ Endpoints
|
||||
"target_url": "https://httpstat.us/200",
|
||||
"all_events": false,
|
||||
"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
|
||||
|
||||
@@ -102,6 +102,8 @@ The provider class
|
||||
|
||||
.. automethod:: render_invoice_text
|
||||
|
||||
.. automethod:: render_invoice_stamp
|
||||
|
||||
.. automethod:: order_change_allowed
|
||||
|
||||
.. automethod:: payment_prepare
|
||||
|
||||
18
src/pretix/api/migrations/0010_webhook_comment.py
Normal file
18
src/pretix/api/migrations/0010_webhook_comment.py
Normal file
@@ -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)
|
||||
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)
|
||||
comment = models.CharField(verbose_name=_("Comment"), max_length=255, null=True, blank=True)
|
||||
|
||||
class Meta:
|
||||
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_city', 'invoice_to_state', 'invoice_to_country', 'invoice_to_vat_id', 'invoice_to_beneficiary',
|
||||
'custom_field', 'date', 'refers', 'locale',
|
||||
'introductory_text', 'additional_text', 'payment_provider_text', 'footer_text', 'lines',
|
||||
'foreign_currency_display', 'foreign_currency_rate', 'foreign_currency_rate_date',
|
||||
'internal_reference')
|
||||
'introductory_text', 'additional_text', 'payment_provider_text', 'payment_provider_stamp',
|
||||
'footer_text', 'lines', 'foreign_currency_display', 'foreign_currency_rate',
|
||||
'foreign_currency_rate_date', 'internal_reference')
|
||||
|
||||
|
||||
class OrderPaymentCreateSerializer(I18nAwareModelSerializer):
|
||||
|
||||
@@ -55,7 +55,7 @@ class WebHookSerializer(I18nAwareModelSerializer):
|
||||
|
||||
class Meta:
|
||||
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):
|
||||
data = super().validate(data)
|
||||
|
||||
@@ -96,7 +96,7 @@ def get_all_webhook_events():
|
||||
return types
|
||||
|
||||
|
||||
class ParametrizedOrderWebhookEvent(WebhookEvent):
|
||||
class ParametrizedWebhookEvent(WebhookEvent):
|
||||
def __init__(self, action_type, verbose_name):
|
||||
self._action_type = action_type
|
||||
self._verbose_name = verbose_name
|
||||
@@ -110,6 +110,8 @@ class ParametrizedOrderWebhookEvent(WebhookEvent):
|
||||
def verbose_name(self):
|
||||
return self._verbose_name
|
||||
|
||||
|
||||
class ParametrizedOrderWebhookEvent(ParametrizedWebhookEvent):
|
||||
def build_payload(self, logentry: LogEntry):
|
||||
order = logentry.content_object
|
||||
if not order:
|
||||
@@ -124,19 +126,7 @@ class ParametrizedOrderWebhookEvent(WebhookEvent):
|
||||
}
|
||||
|
||||
|
||||
class ParametrizedEventWebhookEvent(WebhookEvent):
|
||||
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
|
||||
class ParametrizedEventWebhookEvent(ParametrizedWebhookEvent):
|
||||
|
||||
def build_payload(self, logentry: LogEntry):
|
||||
if logentry.action_type == 'pretix.event.deleted':
|
||||
@@ -160,19 +150,7 @@ class ParametrizedEventWebhookEvent(WebhookEvent):
|
||||
}
|
||||
|
||||
|
||||
class ParametrizedSubEventWebhookEvent(WebhookEvent):
|
||||
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
|
||||
class ParametrizedSubEventWebhookEvent(ParametrizedWebhookEvent):
|
||||
|
||||
def build_payload(self, logentry: LogEntry):
|
||||
# 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):
|
||||
|
||||
def build_payload(self, logentry: LogEntry):
|
||||
@@ -305,6 +296,11 @@ def register_default_webhook_events(sender, **kwargs):
|
||||
'pretix.subevent.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(
|
||||
'pretix.event.live.activated',
|
||||
_('Shop taken live'),
|
||||
|
||||
@@ -35,8 +35,8 @@ from django.utils.formats import date_format, localize
|
||||
from django.utils.translation import (
|
||||
get_language, gettext, gettext_lazy, pgettext,
|
||||
)
|
||||
from reportlab.lib import pagesizes
|
||||
from reportlab.lib.enums import TA_LEFT, TA_RIGHT
|
||||
from reportlab.lib import colors, pagesizes
|
||||
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_RIGHT
|
||||
from reportlab.lib.styles import ParagraphStyle, StyleSheet1
|
||||
from reportlab.lib.units import mm
|
||||
from reportlab.pdfbase import pdfmetrics
|
||||
@@ -44,8 +44,8 @@ from reportlab.pdfbase.pdfmetrics import stringWidth
|
||||
from reportlab.pdfbase.ttfonts import TTFont
|
||||
from reportlab.pdfgen.canvas import Canvas
|
||||
from reportlab.platypus import (
|
||||
BaseDocTemplate, Frame, KeepTogether, NextPageTemplate, PageTemplate,
|
||||
Paragraph, Spacer, Table, TableStyle,
|
||||
BaseDocTemplate, Flowable, Frame, KeepTogether, NextPageTemplate,
|
||||
PageTemplate, Paragraph, Spacer, Table, TableStyle,
|
||||
)
|
||||
|
||||
from pretix.base.decimal import round_decimal
|
||||
@@ -147,6 +147,8 @@ class BaseReportlabInvoiceRenderer(BaseInvoiceRenderer):
|
||||
"""
|
||||
stylesheet = StyleSheet1()
|
||||
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='Heading1', fontName=self.font_bold, fontSize=15, leading=15 * 1.2))
|
||||
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')
|
||||
|
||||
|
||||
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):
|
||||
identifier = 'classic'
|
||||
verbose_name = pgettext('invoice', 'Classic renderer (pretix 1.0)')
|
||||
@@ -612,10 +639,10 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
||||
tdata.append([
|
||||
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 self.invoice.order.status == Order.STATUS_PENDING:
|
||||
if not self.invoice.is_cancellation:
|
||||
if self.invoice.event.settings.invoice_show_payments and self.invoice.order.status == Order.STATUS_PENDING:
|
||||
pending_sum = self.invoice.order.pending_sum
|
||||
if pending_sum != total:
|
||||
tdata.append([pgettext('invoice', 'Received payments')] + (['', '', ''] if has_taxes else ['']) + [
|
||||
@@ -627,7 +654,7 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
||||
tstyledata += [
|
||||
('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'
|
||||
).exists():
|
||||
giftcard_sum = self.invoice.order.payments.filter(
|
||||
@@ -645,6 +672,13 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
||||
tstyledata += [
|
||||
('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.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
|
||||
:param payment_provider_text: A payment provider specific text
|
||||
: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
|
||||
:type footer_text: str
|
||||
: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)
|
||||
reverse_charge = models.BooleanField(default=False)
|
||||
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)
|
||||
|
||||
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 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
|
||||
def payment_form_fields(self) -> dict:
|
||||
"""
|
||||
|
||||
@@ -62,7 +62,7 @@ from django.utils.html import conditional_escape
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import gettext_lazy as _, pgettext
|
||||
from i18nfield.strings import LazyI18nString
|
||||
from PyPDF2 import PdfReader
|
||||
from pypdf import PdfReader
|
||||
from pytz import timezone
|
||||
from reportlab.graphics import renderPDF
|
||||
from reportlab.graphics.barcode.qr import QrCodeWidget
|
||||
@@ -848,12 +848,17 @@ class Renderer:
|
||||
'center': TA_CENTER,
|
||||
'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(
|
||||
name=uuid.uuid4().hex,
|
||||
fontName=font,
|
||||
fontSize=float(o['fontsize']),
|
||||
leading=float(o['fontsize']),
|
||||
autoLeading="max",
|
||||
leading=lineheight * float(o['fontsize']),
|
||||
# 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),
|
||||
alignment=align_map[o['align']]
|
||||
)
|
||||
@@ -882,8 +887,15 @@ class Renderer:
|
||||
if o.get('downward', False):
|
||||
canvas.translate(float(o['left']) * mm, float(o['bottom']) * mm)
|
||||
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:
|
||||
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.rotate(o.get('rotation', 0) * -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:
|
||||
return BytesIO(f.read())
|
||||
else:
|
||||
from PyPDF2 import PdfReader, PdfWriter, Transformation
|
||||
from PyPDF2.generic import RectangleObject
|
||||
from pypdf import PdfReader, PdfWriter, Transformation
|
||||
from pypdf.generic import RectangleObject
|
||||
buffer.seek(0)
|
||||
new_pdf = PdfReader(buffer)
|
||||
output = PdfWriter()
|
||||
|
||||
@@ -98,8 +98,10 @@ def build_invoice(invoice: Invoice) -> Invoice:
|
||||
payment = str(lp.payment_provider.render_invoice_text(invoice.order, lp))
|
||||
else:
|
||||
payment = str(lp.payment_provider.render_invoice_text(invoice.order))
|
||||
payment_stamp = lp.payment_provider.render_invoice_stamp(invoice.order, lp)
|
||||
else:
|
||||
payment = ""
|
||||
payment_stamp = None
|
||||
if invoice.event.settings.invoice_include_expire_date and invoice.order.status == Order.STATUS_PENDING:
|
||||
if payment:
|
||||
payment += "<br /><br />"
|
||||
@@ -111,6 +113,7 @@ def build_invoice(invoice: Invoice) -> Invoice:
|
||||
invoice.additional_text = str(additional).replace('\n', '<br />')
|
||||
invoice.footer_text = str(footer)
|
||||
invoice.payment_provider_text = str(payment).replace('\n', '<br />')
|
||||
invoice.payment_provider_stamp = str(payment_stamp) if payment_stamp else None
|
||||
|
||||
try:
|
||||
ia = invoice.order.invoice_address
|
||||
@@ -325,6 +328,7 @@ def generate_cancellation(invoice: Invoice, trigger_pdf=True):
|
||||
cancellation.is_cancellation = True
|
||||
cancellation.date = timezone.now().date()
|
||||
cancellation.payment_provider_text = ''
|
||||
cancellation.payment_provider_stamp = ''
|
||||
cancellation.file = None
|
||||
cancellation.sent_to_organizer = 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.footer_text = str(footer)
|
||||
invoice.payment_provider_text = str(payment).replace('\n', '<br />')
|
||||
invoice.payment_provider_stamp = _('paid')
|
||||
invoice.invoice_to_name = _("John Doe")
|
||||
invoice.invoice_to_street = _("214th Example Street")
|
||||
invoice.invoice_to_zipcode = _("012345")
|
||||
|
||||
@@ -293,14 +293,16 @@ class DateFrameWidget(forms.MultiWidget):
|
||||
if '/' in value:
|
||||
return [
|
||||
'custom',
|
||||
date.fromisoformat(value.split('/', 1)[0]),
|
||||
date.fromisoformat(value.split('/', 1)[-1]),
|
||||
date.fromisoformat(value.split('/', 1)[0]) if value.split('/', 1)[0] else None,
|
||||
date.fromisoformat(value.split('/', 1)[-1]) if value.split('/', 1)[-1] else None,
|
||||
]
|
||||
return [value, None, None]
|
||||
|
||||
def get_context(self, name, value, attrs):
|
||||
ctx = super().get_context(name, value, attrs)
|
||||
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
|
||||
|
||||
|
||||
|
||||
@@ -57,8 +57,8 @@ from pretix.base.forms.widgets import (
|
||||
from pretix.base.models import (
|
||||
Checkin, CheckinList, Device, Event, EventMetaProperty, EventMetaValue,
|
||||
Gate, Invoice, InvoiceAddress, Item, Order, OrderPayment, OrderPosition,
|
||||
OrderRefund, Organizer, Question, QuestionAnswer, SubEvent, Team,
|
||||
TeamAPIToken, TeamInvite,
|
||||
OrderRefund, Organizer, Question, QuestionAnswer, SubEvent,
|
||||
SubEventMetaValue, Team, TeamAPIToken, TeamInvite,
|
||||
)
|
||||
from pretix.base.signals import register_payment_providers
|
||||
from pretix.control.forms.widgets import Select2
|
||||
@@ -1116,9 +1116,25 @@ class SubEventFilterForm(FilterForm):
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.event = kwargs.pop('event')
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['date_from'].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):
|
||||
fdata = self.cleaned_data
|
||||
@@ -1181,6 +1197,31 @@ class SubEventFilterForm(FilterForm):
|
||||
if 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'):
|
||||
qs = qs.order_by(self.get_order_by())
|
||||
else:
|
||||
@@ -1188,6 +1229,10 @@ class SubEventFilterForm(FilterForm):
|
||||
|
||||
return qs
|
||||
|
||||
@cached_property
|
||||
def meta_properties(self):
|
||||
return self.event.organizer.meta_properties.filter(filter_allowed=True)
|
||||
|
||||
|
||||
class OrganizerFilterForm(FilterForm):
|
||||
orders = {
|
||||
|
||||
@@ -564,7 +564,7 @@ class WebHookForm(forms.ModelForm):
|
||||
|
||||
class Meta:
|
||||
model = WebHook
|
||||
fields = ['target_url', 'enabled', 'all_events', 'limit_events']
|
||||
fields = ['target_url', 'enabled', 'all_events', 'limit_events', 'comment']
|
||||
widgets = {
|
||||
'limit_events': forms.CheckboxSelectMultiple(attrs={
|
||||
'data-inverse-dependency': '#id_all_events'
|
||||
|
||||
@@ -23,6 +23,7 @@ from datetime import datetime
|
||||
from urllib.parse import urlencode
|
||||
|
||||
from django import forms
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.forms import formset_factory
|
||||
from django.forms.utils import ErrorDict
|
||||
from django.urls import reverse
|
||||
@@ -273,6 +274,13 @@ class SubEventItemForm(SubEventItemOrVariationFormMixin, forms.ModelForm):
|
||||
'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):
|
||||
def __init__(self, *args, **kwargs):
|
||||
@@ -293,6 +301,13 @@ class SubEventItemVariationForm(SubEventItemOrVariationFormMixin, forms.ModelFor
|
||||
'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):
|
||||
rel_available_from = RelativeDateTimeField(
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form_errors form %}
|
||||
{% bootstrap_field form.target_url layout="control" %}
|
||||
{% bootstrap_field form.comment layout="control" %}
|
||||
{% bootstrap_field form.enabled layout="control" %}
|
||||
{% bootstrap_field form.events layout="control" %}
|
||||
{% bootstrap_field form.all_events layout="control" %}
|
||||
|
||||
@@ -90,7 +90,7 @@
|
||||
</div>
|
||||
</details>
|
||||
{% 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 %}
|
||||
{% include "pretixcontrol/pagination.html" %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
<tr>
|
||||
<th>{% trans "Target URL" %}</th>
|
||||
<th>{% trans "Events" %}</th>
|
||||
<th>{% trans "Comment" %}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -61,6 +62,11 @@
|
||||
</ul>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if w.comment %}
|
||||
{{ w.comment }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-right flip">
|
||||
<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" %}">
|
||||
|
||||
@@ -275,6 +275,18 @@
|
||||
<input type="number" value="13" class="input-block-level form-control" step="0.1"
|
||||
id="toolbox-fontsize">
|
||||
</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">
|
||||
<label> </label><br>
|
||||
<div class="btn-group btn-group-justified" role="group">
|
||||
@@ -295,15 +307,6 @@
|
||||
</button>
|
||||
</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>
|
||||
<div class="btn-group btn-group-justified" id="toolbox-align">
|
||||
<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." %}
|
||||
</p>
|
||||
{% for f in itemvar_forms %}
|
||||
{% bootstrap_form_errors f %}
|
||||
<div class="form-group subevent-itemvar-group">
|
||||
<label class="col-md-3 control-label" for="id_{{ f.prefix }}-price">
|
||||
{% if f.variation %}{{ f.item }} – {{ f.variation }}{% else %}{{ f.item }}{% endif %}
|
||||
|
||||
@@ -151,6 +151,7 @@
|
||||
<fieldset>
|
||||
<legend>{% trans "Item prices" %}</legend>
|
||||
{% for f in itemvar_forms %}
|
||||
{% bootstrap_form_errors f %}
|
||||
<div class="form-group subevent-itemvar-group">
|
||||
<label class="col-md-3 control-label" for="id_{{ f.prefix }}-price">
|
||||
{% 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." %}
|
||||
</p>
|
||||
{% for f in itemvar_forms %}
|
||||
{% bootstrap_form_errors f %}
|
||||
<div class="form-group subevent-itemvar-group">
|
||||
<label class="col-md-3 control-label" for="id_{{ f.prefix }}-price">
|
||||
{% if f.variation %}{{ f.item }} – {{ f.variation }}{% else %}{{ f.item }}{% endif %}
|
||||
|
||||
@@ -50,6 +50,11 @@
|
||||
<div class="col-xs-12 one-line-checkboxes">
|
||||
{% bootstrap_field filter_form.weekday %}
|
||||
</div>
|
||||
{% for mf in meta_fields %}
|
||||
<div class="col-md-3 col-sm-6 col-xs-12">
|
||||
{% bootstrap_field mf %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<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_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/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/add$', item.ItemCreate.as_view(), name='event.items.add'),
|
||||
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.translation import gettext as _
|
||||
from django.views.generic import TemplateView
|
||||
from PyPDF2 import PdfReader, PdfWriter
|
||||
from PyPDF2.errors import PdfReadError
|
||||
from pypdf import PdfReader, PdfWriter
|
||||
from pypdf.errors import PdfReadError
|
||||
from reportlab.lib.units import mm
|
||||
|
||||
from pretix.base.i18n import language
|
||||
|
||||
@@ -110,7 +110,7 @@ class SubEventQueryMixin:
|
||||
|
||||
@cached_property
|
||||
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):
|
||||
@@ -125,6 +125,9 @@ class SubEventList(EventPermissionRequiredMixin, PaginationMixin, SubEventQueryM
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
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 = []
|
||||
for s in ctx['subevents']:
|
||||
|
||||
@@ -48,7 +48,8 @@ from django.utils.translation import gettext as _, pgettext
|
||||
|
||||
from pretix.base.models import (
|
||||
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.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):
|
||||
q = request.GET.get('q')
|
||||
propname = request.GET.get('property')
|
||||
|
||||
@@ -8,8 +8,8 @@ msgstr ""
|
||||
"Project-Id-Version: 1\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2023-02-01 13:31+0000\n"
|
||||
"PO-Revision-Date: 2023-02-01 13:45+0000\n"
|
||||
"Last-Translator: Raphael Michel <michel@rami.io>\n"
|
||||
"PO-Revision-Date: 2023-02-02 20:00+0000\n"
|
||||
"Last-Translator: juliusstoerrle <juliusstoerrle@gmx.de>\n"
|
||||
"Language-Team: German (informal) <https://translate.pretix.eu/projects/"
|
||||
"pretix/pretix/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 "
|
||||
"active membership on your account."
|
||||
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."
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/checkout_membership.html:38
|
||||
|
||||
@@ -7,16 +7,16 @@ msgstr ""
|
||||
"Project-Id-Version: 1\n"
|
||||
"Report-Msgid-Bugs-To: \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"
|
||||
"Language-Team: Dutch <https://translate.pretix.eu/projects/pretix/pretix/nl/"
|
||||
">\n"
|
||||
"Language-Team: Dutch <https://translate.pretix.eu/projects/pretix/pretix/nl/>"
|
||||
"\n"
|
||||
"Language: nl\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\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
|
||||
msgid ""
|
||||
@@ -149,7 +149,7 @@ msgstr ""
|
||||
|
||||
#: pretix/api/serializers/item.py:266
|
||||
msgid "Only admission products can currently be personalized."
|
||||
msgstr ""
|
||||
msgstr "Momenteel kunnen enkel toegangsproducten gepersonaliseerd worden."
|
||||
|
||||
#: pretix/api/serializers/item.py:277
|
||||
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."
|
||||
|
||||
#: pretix/api/serializers/order.py:74
|
||||
#, fuzzy, python-brace-format
|
||||
#| msgid "{value} is not a valid email address."
|
||||
#, python-brace-format
|
||||
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/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:78
|
||||
#, fuzzy
|
||||
#| msgid "Payer and reference"
|
||||
msgid "Payment reference"
|
||||
msgstr "Betaler en kenmerk"
|
||||
msgstr "Betalingsreferentie"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/boxoffice/payment.html:65
|
||||
msgid "Payment Application"
|
||||
|
||||
@@ -36,6 +36,7 @@ import json
|
||||
import logging
|
||||
from collections import OrderedDict
|
||||
from datetime import datetime, time, timedelta
|
||||
from decimal import Decimal
|
||||
from io import BytesIO
|
||||
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.utils.timezone import make_aware
|
||||
from django.utils.translation import gettext as _, gettext_lazy, pgettext_lazy
|
||||
from PyPDF2 import PdfMerger, PdfReader, PdfWriter, Transformation
|
||||
from PyPDF2.generic import RectangleObject
|
||||
from pypdf import PdfMerger, PdfReader, PdfWriter, Transformation
|
||||
from pypdf.generic import RectangleObject
|
||||
from reportlab.lib import pagesizes
|
||||
from reportlab.lib.units import mm
|
||||
from reportlab.pdfgen import canvas
|
||||
@@ -221,17 +222,17 @@ def render_pdf(event, positions, opt):
|
||||
di = i % badges_per_page
|
||||
if di == 0:
|
||||
nup_page = nup_pdf.add_blank_page(
|
||||
width=opt['pagesize'][0],
|
||||
height=opt['pagesize'][1],
|
||||
width=Decimal('%.5f' % (opt['pagesize'][0])),
|
||||
height=Decimal('%.5f' % (opt['pagesize'][1])),
|
||||
)
|
||||
tx = opt['margins'][3] + (di % opt['cols']) * opt['offsets'][0]
|
||||
ty = opt['margins'][2] + (opt['rows'] - 1 - (di // opt['cols'])) * opt['offsets'][1]
|
||||
page.add_transformation(Transformation().translate(tx, ty))
|
||||
page.mediabox = RectangleObject((
|
||||
page.mediabox.left.as_numeric() + tx,
|
||||
page.mediabox.bottom.as_numeric() + ty,
|
||||
page.mediabox.right.as_numeric() + tx,
|
||||
page.mediabox.top.as_numeric() + ty
|
||||
Decimal('%.5f' % (page.mediabox.left.as_numeric() + tx)),
|
||||
Decimal('%.5f' % (page.mediabox.bottom.as_numeric() + ty)),
|
||||
Decimal('%.5f' % (page.mediabox.right.as_numeric() + tx)),
|
||||
Decimal('%.5f' % (page.mediabox.top.as_numeric() + ty))
|
||||
))
|
||||
page.trimbox = page.mediabox
|
||||
nup_page.merge_page(page)
|
||||
|
||||
@@ -31,6 +31,7 @@ from django_scopes import ScopedManager
|
||||
from i18nfield.fields import I18nCharField, I18nTextField
|
||||
|
||||
from pretix.base.email import get_email_context
|
||||
from pretix.base.i18n import language
|
||||
from pretix.base.models import (
|
||||
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)
|
||||
|
||||
for o in orders:
|
||||
positions = list(o.positions.all())
|
||||
o_sent = False
|
||||
with language(o.locale, e.settings.region):
|
||||
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:
|
||||
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:
|
||||
... # ¯\_(ツ)_/¯
|
||||
ia = o.invoice_address
|
||||
except InvoiceAddress.DoesNotExist:
|
||||
ia = InvoiceAddress(order=o)
|
||||
|
||||
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:
|
||||
if send_to_orders and o.email:
|
||||
email_ctx = get_email_context(event=e, order=o, invoice_address=ia)
|
||||
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
|
||||
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
|
||||
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):
|
||||
|
||||
@@ -43,7 +43,7 @@ from django.db.models import OuterRef, Q, Subquery
|
||||
from django.db.models.functions import Cast, Coalesce
|
||||
from django.utils.timezone import now
|
||||
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.i18n import language
|
||||
|
||||
@@ -44,7 +44,7 @@ from django.http import HttpRequest
|
||||
from django.template.loader import get_template
|
||||
from django.utils.functional import cached_property
|
||||
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.models import Order, OrderPosition
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<span role="columnheader" aria-sort="none">{% trans "Price total" %}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div role="rowgroup">
|
||||
<div role="rowgroup" class="firstchild-in-panel">
|
||||
{% for line in cart.positions %}
|
||||
<div role="row" class="row cart-row {% if download %}has-downloads{% endif %}{% if editable %}editable{% endif %}">
|
||||
<div role="cell" class="product">
|
||||
|
||||
66
src/pretix/static/npm_dir/package-lock.json
generated
66
src/pretix/static/npm_dir/package-lock.json
generated
@@ -8,7 +8,7 @@
|
||||
"name": "pretix",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.20.7",
|
||||
"@babel/core": "^7.20.12",
|
||||
"@babel/preset-env": "^7.20.2",
|
||||
"@rollup/plugin-babel": "^6.0.3",
|
||||
"@rollup/plugin-node-resolve": "^15.0.1",
|
||||
@@ -50,24 +50,24 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/core": {
|
||||
"version": "7.20.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.7.tgz",
|
||||
"integrity": "sha512-t1ZjCluspe5DW24bn2Rr1CDb2v9rn/hROtg9a2tmd0+QYf4bsloYfLQzjG4qHPNMhWtKdGC33R5AxGR2Af2cBw==",
|
||||
"version": "7.20.12",
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.12.tgz",
|
||||
"integrity": "sha512-XsMfHovsUYHFMdrIHkZphTN/2Hzzi78R08NuHfDBehym2VsPDL6Zn/JAD/JQdnRvbSsbQc4mVaU1m6JgtTEElg==",
|
||||
"dependencies": {
|
||||
"@ampproject/remapping": "^2.1.0",
|
||||
"@babel/code-frame": "^7.18.6",
|
||||
"@babel/generator": "^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/parser": "^7.20.7",
|
||||
"@babel/template": "^7.20.7",
|
||||
"@babel/traverse": "^7.20.7",
|
||||
"@babel/traverse": "^7.20.12",
|
||||
"@babel/types": "^7.20.7",
|
||||
"convert-source-map": "^1.7.0",
|
||||
"debug": "^4.1.0",
|
||||
"gensync": "^1.0.0-beta.2",
|
||||
"json5": "^2.2.1",
|
||||
"json5": "^2.2.2",
|
||||
"semver": "^6.3.0"
|
||||
},
|
||||
"engines": {
|
||||
@@ -79,9 +79,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/core/node_modules/json5": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz",
|
||||
"integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==",
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
|
||||
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
|
||||
"bin": {
|
||||
"json5": "lib/cli.js"
|
||||
},
|
||||
@@ -475,9 +475,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/parser": {
|
||||
"version": "7.20.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.7.tgz",
|
||||
"integrity": "sha512-T3Z9oHybU+0vZlY9CiDSJQTD5ZapcW18ZctFMi0MOAl/4BjFF4ul7NVSARLdbGO5vDqy9eQiGTV0LtKfvCYvcg==",
|
||||
"version": "7.20.13",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.13.tgz",
|
||||
"integrity": "sha512-gFDLKMfpiXCsjt4za2JA9oTMn70CeseCehb11kRZgvd7+F67Hih3OHOK24cRrWECJ/ljfPGac6ygXAs/C8kIvw==",
|
||||
"bin": {
|
||||
"parser": "bin/babel-parser.js"
|
||||
},
|
||||
@@ -1541,9 +1541,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/traverse": {
|
||||
"version": "7.20.10",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.10.tgz",
|
||||
"integrity": "sha512-oSf1juCgymrSez8NI4A2sr4+uB/mFd9MXplYGPEBnfAuWmmyeVcHa6xLPiaRBcXkcb/28bgxmQLTVwFKE1yfsg==",
|
||||
"version": "7.20.13",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.13.tgz",
|
||||
"integrity": "sha512-kMJXfF0T6DIS9E8cgdLCSAL+cuCK+YEZHWiLK0SXpTo8YRj5lpJu3CDNKiIBCne4m9hhTIqUg6SYTAI39tAiVQ==",
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.18.6",
|
||||
"@babel/generator": "^7.20.7",
|
||||
@@ -1551,7 +1551,7 @@
|
||||
"@babel/helper-function-name": "^7.19.0",
|
||||
"@babel/helper-hoist-variables": "^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",
|
||||
"debug": "^4.1.0",
|
||||
"globals": "^11.1.0"
|
||||
@@ -4063,31 +4063,31 @@
|
||||
"integrity": "sha512-sEnuDPpOJR/fcafHMjpcpGN5M2jbUGUHwmuWKM/YdPzeEDJg8bgmbcWQFUfE32MQjti1koACvoPVsDe8Uq+idg=="
|
||||
},
|
||||
"@babel/core": {
|
||||
"version": "7.20.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.7.tgz",
|
||||
"integrity": "sha512-t1ZjCluspe5DW24bn2Rr1CDb2v9rn/hROtg9a2tmd0+QYf4bsloYfLQzjG4qHPNMhWtKdGC33R5AxGR2Af2cBw==",
|
||||
"version": "7.20.12",
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.12.tgz",
|
||||
"integrity": "sha512-XsMfHovsUYHFMdrIHkZphTN/2Hzzi78R08NuHfDBehym2VsPDL6Zn/JAD/JQdnRvbSsbQc4mVaU1m6JgtTEElg==",
|
||||
"requires": {
|
||||
"@ampproject/remapping": "^2.1.0",
|
||||
"@babel/code-frame": "^7.18.6",
|
||||
"@babel/generator": "^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/parser": "^7.20.7",
|
||||
"@babel/template": "^7.20.7",
|
||||
"@babel/traverse": "^7.20.7",
|
||||
"@babel/traverse": "^7.20.12",
|
||||
"@babel/types": "^7.20.7",
|
||||
"convert-source-map": "^1.7.0",
|
||||
"debug": "^4.1.0",
|
||||
"gensync": "^1.0.0-beta.2",
|
||||
"json5": "^2.2.1",
|
||||
"json5": "^2.2.2",
|
||||
"semver": "^6.3.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"json5": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz",
|
||||
"integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA=="
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
|
||||
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="
|
||||
},
|
||||
"semver": {
|
||||
"version": "6.3.0",
|
||||
@@ -4378,9 +4378,9 @@
|
||||
}
|
||||
},
|
||||
"@babel/parser": {
|
||||
"version": "7.20.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.7.tgz",
|
||||
"integrity": "sha512-T3Z9oHybU+0vZlY9CiDSJQTD5ZapcW18ZctFMi0MOAl/4BjFF4ul7NVSARLdbGO5vDqy9eQiGTV0LtKfvCYvcg=="
|
||||
"version": "7.20.13",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.13.tgz",
|
||||
"integrity": "sha512-gFDLKMfpiXCsjt4za2JA9oTMn70CeseCehb11kRZgvd7+F67Hih3OHOK24cRrWECJ/ljfPGac6ygXAs/C8kIvw=="
|
||||
},
|
||||
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": {
|
||||
"version": "7.18.6",
|
||||
@@ -5073,9 +5073,9 @@
|
||||
}
|
||||
},
|
||||
"@babel/traverse": {
|
||||
"version": "7.20.10",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.10.tgz",
|
||||
"integrity": "sha512-oSf1juCgymrSez8NI4A2sr4+uB/mFd9MXplYGPEBnfAuWmmyeVcHa6xLPiaRBcXkcb/28bgxmQLTVwFKE1yfsg==",
|
||||
"version": "7.20.13",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.13.tgz",
|
||||
"integrity": "sha512-kMJXfF0T6DIS9E8cgdLCSAL+cuCK+YEZHWiLK0SXpTo8YRj5lpJu3CDNKiIBCne4m9hhTIqUg6SYTAI39tAiVQ==",
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.18.6",
|
||||
"@babel/generator": "^7.20.7",
|
||||
@@ -5083,7 +5083,7 @@
|
||||
"@babel/helper-function-name": "^7.19.0",
|
||||
"@babel/helper-hoist-variables": "^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",
|
||||
"debug": "^4.1.0",
|
||||
"globals": "^11.1.0"
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"private": true,
|
||||
"scripts": {},
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.20.7",
|
||||
"@babel/core": "^7.20.12",
|
||||
"@babel/preset-env": "^7.20.2",
|
||||
"@rollup/plugin-babel": "^6.0.3",
|
||||
"@rollup/plugin-node-resolve": "^15.0.1",
|
||||
|
||||
@@ -119,6 +119,7 @@ $(document).ready(function () {
|
||||
date_tolerance: gettext('Tolerance (minutes)'),
|
||||
condition_add: gettext('Add condition'),
|
||||
minutes: gettext('minutes'),
|
||||
duplicate: gettext('Duplicate'),
|
||||
},
|
||||
hasRules: false,
|
||||
};
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
<template>
|
||||
<div v-bind:class="classObject">
|
||||
<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>
|
||||
<button type="button" class="checkin-rule-remove btn btn-xs btn-default" @click.prevent="wrapWithAND">AND
|
||||
@@ -260,6 +264,10 @@
|
||||
remove: function () {
|
||||
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>
|
||||
|
||||
@@ -10,25 +10,29 @@
|
||||
' <slot></slot>\n' +
|
||||
' </select>'),
|
||||
mounted: function () {
|
||||
var vm = this;
|
||||
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");
|
||||
this.build();
|
||||
},
|
||||
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 () {
|
||||
return {
|
||||
theme: "bootstrap",
|
||||
@@ -60,17 +64,29 @@
|
||||
},
|
||||
watch: {
|
||||
placeholder: function (val) {
|
||||
$(this.$el).empty().select2(this.opts());
|
||||
$(this.$el).select2("destroy");
|
||||
this.build();
|
||||
},
|
||||
required: function (val) {
|
||||
$(this.$el).empty().select2(this.opts());
|
||||
$(this.$el).select2("destroy");
|
||||
this.build();
|
||||
},
|
||||
url: function (val) {
|
||||
$(this.$el).empty().select2(this.opts());
|
||||
$(this.$el).select2("destroy");
|
||||
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 () {
|
||||
$(this.$el)
|
||||
|
||||
@@ -161,8 +161,8 @@ var editor = {
|
||||
left: editor._px2mm(left).toFixed(2),
|
||||
bottom: editor._px2mm(bottom).toFixed(2),
|
||||
fontsize: editor._px2pt(o.getFontSize()).toFixed(1),
|
||||
lineheight: o.lineHeight,
|
||||
color: col,
|
||||
//lineheight: o.lineHeight,
|
||||
fontfamily: o.fontFamily,
|
||||
bold: o.fontWeight === 'bold',
|
||||
italic: o.fontStyle === 'italic',
|
||||
@@ -241,7 +241,7 @@ var editor = {
|
||||
o = editor._add_text();
|
||||
o.setColor('rgb(' + d.color[0] + ',' + d.color[1] + ',' + d.color[2] + ')');
|
||||
o.setFontSize(editor._pt2px(d.fontsize));
|
||||
//o.setLineHeight(d.lineheight);
|
||||
o.setLineHeight(d.lineheight || 1);
|
||||
o.setFontFamily(d.fontfamily);
|
||||
o.setFontWeight(d.bold ? 'bold' : 'normal');
|
||||
o.setFontStyle(d.italic ? 'italic' : 'normal');
|
||||
@@ -483,7 +483,7 @@ var editor = {
|
||||
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-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").find("button[data-action=bold]").toggleClass('active', o.fontWeight === 'bold');
|
||||
$("#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") {
|
||||
o.setColor($("#toolbox-col").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.setFontWeight($("#toolbox").find("button[data-action=bold]").is('.active') ? 'bold' : 'normal');
|
||||
o.setFontStyle($("#toolbox").find("button[data-action=italic]").is('.active') ? 'italic' : 'normal');
|
||||
@@ -861,6 +861,7 @@ var editor = {
|
||||
thing.setCoords();
|
||||
editor._create_savepoint();
|
||||
break;
|
||||
case 8: /* Backspace */
|
||||
case 46: /* Delete */
|
||||
editor._delete();
|
||||
break;
|
||||
|
||||
@@ -6,7 +6,18 @@ $(function () {
|
||||
}
|
||||
|
||||
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();
|
||||
if (price) {
|
||||
var currency = $el.find("[name*=-default_price] + .input-group-addon").text();
|
||||
|
||||
@@ -362,7 +362,7 @@ Vue.component('pricebox', {
|
||||
}
|
||||
});
|
||||
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">'
|
||||
|
||||
// Variation description
|
||||
@@ -411,7 +411,7 @@ Vue.component('variation', {
|
||||
}
|
||||
});
|
||||
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">'
|
||||
|
||||
// Product description
|
||||
@@ -517,7 +517,7 @@ Vue.component('item', {
|
||||
}
|
||||
});
|
||||
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>'
|
||||
+ '<div class="pretix-widget-category-description" v-if="category.description" v-html="category.description">'
|
||||
+ '</div>'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.cart-row {
|
||||
/* Cart grid */
|
||||
padding: 10px 0;
|
||||
padding: 0.75*$line-height-computed 0;
|
||||
|
||||
.product>*:last-child {
|
||||
margin-bottom: 0;
|
||||
@@ -100,6 +100,9 @@
|
||||
}
|
||||
}
|
||||
|
||||
.cart .firstchild-in-panel .cart-row {
|
||||
padding-top: 0;
|
||||
}
|
||||
.cart [role=rowgroup]:last-child {
|
||||
border-top: 1px solid $table-border-color;
|
||||
}
|
||||
|
||||
@@ -96,9 +96,9 @@
|
||||
}
|
||||
|
||||
&.headline, &.simple {
|
||||
border-top: 2px solid transparent;
|
||||
border-top: 1px solid transparent;
|
||||
&::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 {
|
||||
position: relative;
|
||||
border-bottom: 2px solid transparent;
|
||||
border-bottom: 1px solid transparent;
|
||||
&::after {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 15px;
|
||||
width: calc(100% - 30px);
|
||||
content: '';
|
||||
border-bottom: 2px solid $table-border-color;
|
||||
border-bottom: 1px solid $table-border-color;
|
||||
}
|
||||
}
|
||||
article.item-with-variations .product-row:last-child:after {
|
||||
@@ -146,7 +146,7 @@ article.item-with-variations .product-row:last-child:after {
|
||||
}
|
||||
|
||||
.product-row {
|
||||
padding: 10px 0;
|
||||
padding: 0.75*$line-height-computed 0;
|
||||
|
||||
.count form {
|
||||
display: inline;
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
@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 {
|
||||
position: relative;
|
||||
padding-bottom: 9px;
|
||||
@@ -150,4 +157,4 @@ footer {
|
||||
border-top-left-radius: .25em;
|
||||
border-bottom-left-radius: .25em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -351,8 +351,8 @@ body.loading .container {
|
||||
|
||||
.panel-title a[data-toggle="collapse"], details .panel-title {
|
||||
display: flex;
|
||||
padding: 10px 15px;
|
||||
margin: -10px -15px;
|
||||
padding: 0.75*$line-height-computed;
|
||||
margin: -0.75*$line-height-computed;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
outline: 0;
|
||||
|
||||
@@ -240,7 +240,19 @@
|
||||
"pattern": "[a-zA-Z-]*"
|
||||
},
|
||||
"fontsize": {
|
||||
"description": "Font size.",
|
||||
"description": "Font size",
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"pattern": "^[0-9]+(\\.[0-9]+)?$"
|
||||
}
|
||||
]
|
||||
},
|
||||
"lineheight": {
|
||||
"description": "Line height",
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "number"
|
||||
|
||||
@@ -200,7 +200,7 @@ setup(
|
||||
# We can upgrade markdown again once django-bootstrap3 upgrades or once we drop Python 3.6 and 3.7
|
||||
'mt-940==4.23.*',
|
||||
'oauthlib==3.2.*',
|
||||
'openpyxl==3.0.*',
|
||||
'openpyxl==3.1.*',
|
||||
'packaging',
|
||||
'paypalrestsdk==1.13.*',
|
||||
'paypal-checkout-serversdk==1.0.*',
|
||||
@@ -211,8 +211,8 @@ setup(
|
||||
'psycopg2-binary',
|
||||
'pycountry',
|
||||
'pycparser==2.21',
|
||||
'pycryptodome==3.16.*',
|
||||
'PyPDF2==3.0.*',
|
||||
'pycryptodome==3.17.*',
|
||||
'pypdf==3.4.*',
|
||||
'python-bidi==0.4.*', # Support for Arabic in reportlab
|
||||
'python-dateutil==2.8.*',
|
||||
'python-u2flib-server==4.*',
|
||||
@@ -225,7 +225,7 @@ setup(
|
||||
'sepaxml==2.6.*',
|
||||
'slimit',
|
||||
'static3==0.7.*',
|
||||
'stripe==5.0.*',
|
||||
'stripe==5.1.*',
|
||||
'text-unidecode==1.*',
|
||||
'tlds>=2020041600',
|
||||
'tqdm==4.*',
|
||||
|
||||
@@ -176,6 +176,7 @@ TEST_INVOICE_RES = {
|
||||
"internal_reference": "",
|
||||
"additional_text": "",
|
||||
"payment_provider_text": "",
|
||||
"payment_provider_stamp": None,
|
||||
"footer_text": "",
|
||||
"foreign_currency_display": None,
|
||||
"foreign_currency_rate": None,
|
||||
|
||||
@@ -445,6 +445,7 @@ def test_order_create_invoice(token_client, organizer, event, order):
|
||||
'introductory_text': '',
|
||||
'additional_text': '',
|
||||
'payment_provider_text': '',
|
||||
'payment_provider_stamp': None,
|
||||
'footer_text': '',
|
||||
'lines': [
|
||||
{
|
||||
|
||||
@@ -47,6 +47,7 @@ TEST_WEBHOOK_RES = {
|
||||
"all_events": False,
|
||||
"limit_events": ['dummy'],
|
||||
"action_types": ['pretix.event.order.paid', 'pretix.event.order.placed'],
|
||||
"comment": None,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ from io import BytesIO
|
||||
import pytest
|
||||
from django.utils.timezone import now
|
||||
from django_scopes import scope
|
||||
from PyPDF2 import PdfReader
|
||||
from pypdf import PdfReader
|
||||
|
||||
from pretix.base.models import (
|
||||
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_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.signals import sendmail_run_rules
|
||||
|
||||
@@ -361,3 +361,22 @@ def test_sendmail_rule_disabled(event, order):
|
||||
|
||||
sendmail_run_rules(None)
|
||||
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
|
||||
from django.utils.timezone import now
|
||||
from django_scopes import scope
|
||||
from PyPDF2 import PdfReader
|
||||
from pypdf import PdfReader
|
||||
|
||||
from pretix.base.models import (
|
||||
Event, Item, ItemVariation, Order, OrderPosition, Organizer,
|
||||
|
||||
Reference in New Issue
Block a user