Compare commits

...

22 Commits

Author SHA1 Message Date
Martin Gross
39115152a6 Update src/pretix/base/customersso/oidc.py
Co-authored-by: Mira <weller@rami.io>
2025-01-30 13:39:22 +01:00
Martin Gross
7ce790dbb9 Actually fix indentation... 2025-01-30 13:02:49 +01:00
Martin Gross
9a4a0df625 isort 2025-01-30 12:39:46 +01:00
Martin Gross
a8376b9a79 Add missing imports; fix indentation 2025-01-30 12:38:12 +01:00
Martin Gross
b21041a833 Apply suggestions from code review
Co-authored-by: Mira <weller@rami.io>
2025-01-30 12:35:58 +01:00
Martin Gross
e8a716273e OIDC: Allow to add query parameters to Authorization UR 2025-01-29 11:55:12 +01:00
Richard Schreiber
59a7845ac4 [A11y] Improve focus handling for widget overlay
* move iframe after close-button to follow tab-order

* add missing prevActiveElement

* prepare focus-handling for error_message

* iframe.src through prop instead of directly accessing it

* do not change close button HTML-element for compatability

* make all overlay elements role=dialog and modals

* fix close button

* fix re-opening of iframe

* make error-message read out when shown

* Improve handling of frame_src with frame_loading

* manually focus continue or close button in alert-box

* fix btn-focus in transition

* Improve quantity group
2025-01-28 11:04:38 +01:00
Cornelius Kibelka
ef1024d231 Translations: Update Portuguese (Brazil)
Currently translated at 14.8% (870 of 5844 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/pt_BR/

powered by weblate
2025-01-28 10:13:32 +01:00
Cornelius Kibelka
71d0d72425 Translations: Update Portuguese (Brazil)
Currently translated at 14.1% (825 of 5844 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/pt_BR/

powered by weblate
2025-01-27 17:40:37 +01:00
Hijiri Umemoto
6252e526bf Translations: Update Japanese
Currently translated at 100.0% (5844 of 5844 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/ja/

powered by weblate
2025-01-27 17:40:37 +01:00
Rosariocastellana
f3d8a1c2e1 Translations: Update Italian
Currently translated at 24.2% (1419 of 5844 strings)

Translation: pretix/pretix
Translate-URL: https://translate.pretix.eu/projects/pretix/pretix/it/

powered by weblate
2025-01-27 17:40:37 +01:00
Martin Gross
148a3b2933 MetricsMiddleware: Do not record pretix_view_duration_seconds for urls with no url.url_name (#4784) 2025-01-27 16:46:58 +01:00
Mira
709633d6fb Register missing LogEntryTypes (#4779) 2025-01-27 15:35:29 +01:00
Mira
af3418db54 Fix company & vat_id dependencies on is_business (#4777) 2025-01-27 11:55:39 +01:00
Mira
ca8d253114 Bugfixes for LogEntryTypes refactoring (#4778) 2025-01-24 16:19:55 +01:00
Mira Weller
f014a9bbd3 Reapply "Implement hidden_if_item_available_mode option (Z#23177008) (#4776)"
This reverts commit 5cd7959e86.
2025-01-24 14:48:28 +01:00
Mira Weller
3e5bfb44d2 Revert "Preliminary migration"
This reverts commit 1736efbdc3.
2025-01-24 14:48:28 +01:00
Mira Weller
1736efbdc3 Preliminary migration 2025-01-24 14:08:13 +01:00
Mira Weller
5cd7959e86 Revert "Implement hidden_if_item_available_mode option (Z#23177008) (#4776)"
This reverts commit b847612e1a.
2025-01-24 14:07:55 +01:00
Mira
b847612e1a Implement hidden_if_item_available_mode option (Z#23177008) (#4776) 2025-01-24 11:24:50 +01:00
Mira
832f4e4d68 Define LogEntryTypes for all actions in pretix core, improve content_object handling (#4768)
Create LogEntryType definitions for all missing action_types (order changes, check-in events, settings changes of PaymentProviders and TicketOutputs).

Check whether the stored content_object is of the expected model type, preventing incorrect links.

Refactoring:
-    Move the base LogEntryType definitions for our models to their own file
-    Move HTML escaping into make_link to make it less likely to oversee in the LogEntryType definitions
-    Log pretix.event.order.deleted with the deleted Order model as content_object, matching the other *.deleted action_types
2025-01-24 10:05:19 +01:00
Mira
0a23aeece4 Allow 0% tax rate on event creation (#4756)
(but still warn if tax rate is not filled at all)
2025-01-23 12:59:39 +01:00
27 changed files with 814 additions and 607 deletions

View File

@@ -69,6 +69,10 @@ hidden_if_available integer **DEPRECATED*
hidden_if_item_available integer The internal ID of a different item, or ``null``. If
set, this item won't be shown publicly as long as this
other item is available.
hidden_if_item_available_mode string If ``hide`` (the default), this item is hidden in the shop
if unavailable due to the ``hidden_if_item_available`` setting.
If ``info``, the item is visible, but can't be purchased,
and a note explaining the unavailability is displayed.
require_voucher boolean If ``true``, this item can only be bought using a
voucher that is specifically assigned to this item.
hide_without_voucher boolean If ``true``, this item is only shown during the voucher
@@ -239,6 +243,10 @@ meta_data object Values set fo
The ``hidden_if_item_available`` attributes has been added, the ``hidden_if_available`` attribute has been
deprecated.
.. versionchanged:: 2025.01
The ``hidden_if_item_available_mode`` attributes has been added.
Notes
-----
@@ -308,6 +316,7 @@ Endpoints
"available_until_mode": "hide",
"hidden_if_available": null,
"hidden_if_item_available": null,
"hidden_if_item_available_mode": "hide",
"require_voucher": false,
"hide_without_voucher": false,
"allow_cancel": true,
@@ -459,6 +468,7 @@ Endpoints
"available_until_mode": "hide",
"hidden_if_available": null,
"hidden_if_item_available": null,
"hidden_if_item_available_mode": "hide",
"require_voucher": false,
"hide_without_voucher": false,
"allow_cancel": true,
@@ -589,6 +599,7 @@ Endpoints
"available_until_mode": "hide",
"hidden_if_available": null,
"hidden_if_item_available": null,
"hidden_if_item_available_mode": "hide",
"require_voucher": false,
"hide_without_voucher": false,
"allow_cancel": true,
@@ -705,6 +716,7 @@ Endpoints
"available_until_mode": "hide",
"hidden_if_available": null,
"hidden_if_item_available": null,
"hidden_if_item_available_mode": "hide",
"require_voucher": false,
"hide_without_voucher": false,
"allow_cancel": true,
@@ -855,6 +867,7 @@ Endpoints
"available_until_mode": "hide",
"hidden_if_available": null,
"hidden_if_item_available": null,
"hidden_if_item_available_mode": "hide",
"require_voucher": false,
"hide_without_voucher": false,
"generate_tickets": null,

View File

@@ -272,7 +272,7 @@ class ItemSerializer(SalesChannelMigrationMixin, I18nAwareModelSerializer):
'require_voucher', 'hide_without_voucher', 'allow_cancel', 'require_bundling',
'min_per_order', 'max_per_order', 'checkin_attention', 'checkin_text', 'has_variations', 'variations',
'addons', 'bundles', 'original_price', 'require_approval', 'generate_tickets',
'show_quota_left', 'hidden_if_available', 'hidden_if_item_available', 'allow_waitinglist',
'show_quota_left', 'hidden_if_available', 'hidden_if_item_available', 'hidden_if_item_available_mode', 'allow_waitinglist',
'issue_giftcard', 'meta_data',
'require_membership', 'require_membership_types', 'require_membership_hidden', 'grant_membership_type',
'grant_membership_duration_like_event', 'grant_membership_duration_days',

View File

@@ -24,7 +24,7 @@ import hashlib
import logging
import time
from datetime import datetime
from urllib.parse import urlencode, urljoin
from urllib.parse import parse_qsl, urlencode, urljoin
import jwt
import requests
@@ -139,6 +139,11 @@ def oidc_validate_and_complete_config(config):
)
)
if "query_parameters" in config and config["query_parameters"]:
config["query_parameters"] = urlencode(
parse_qsl(config["query_parameters"])
)
config['provider_config'] = provider_config
return config
@@ -154,6 +159,10 @@ def oidc_authorize_url(provider, state, redirect_uri):
'state': state,
'redirect_uri': redirect_uri,
}
if "query_parameters" in provider.configuration and provider.configuration["query_parameters"]:
params.update(parse_qsl(provider.configuration["query_parameters"]))
return endpoint + '?' + urlencode(params)

View File

@@ -1025,10 +1025,9 @@ class BaseInvoiceAddressForm(forms.ModelForm):
'autocomplete': 'address-level2',
}),
'company': forms.TextInput(attrs={
'data-display-dependency': '#id_is_business_1',
'autocomplete': 'organization',
}),
'vat_id': forms.TextInput(attrs={'data-display-dependency': '#id_is_business_1'}),
'vat_id': forms.TextInput(),
'internal_reference': forms.TextInput,
}
labels = {
@@ -1059,10 +1058,8 @@ class BaseInvoiceAddressForm(forms.ModelForm):
super().__init__(*args, **kwargs)
# If an individual or company address is acceptable, #id_is_business_0 == individual, _1 == company.
# However, if only company addresses are acceptable, #id_is_business_0 == company and is the only choice
self.fields["company"].widget.attrs["data-display-dependency"] = f'#id_{self.add_prefix("is_business")}_{int(not self.company_required)}'
self.fields["vat_id"].widget.attrs["data-display-dependency"] = f'#id_{self.add_prefix("is_business")}_{int(not self.company_required)}'
self.fields["company"].widget.attrs["data-display-dependency"] = f'input[name="{self.add_prefix("is_business")}"][value="business"]'
self.fields["vat_id"].widget.attrs["data-display-dependency"] = f'input[name="{self.add_prefix("is_business")}"][value="business"]'
if not self.ask_vat_id:
del self.fields['vat_id']
@@ -1143,9 +1140,9 @@ class BaseInvoiceAddressForm(forms.ModelForm):
)
if self.address_required and not self.company_required and not self.all_optional:
if not event.settings.invoice_name_required:
self.fields['name_parts'].widget.attrs['data-required-if'] = f'#id_{self.add_prefix("is_business")}_0'
self.fields['name_parts'].widget.attrs['data-required-if'] = f'input[name="{self.add_prefix("is_business")}"][value="individual"]'
self.fields['name_parts'].widget.attrs['data-no-required-attr'] = '1'
self.fields['company'].widget.attrs['data-required-if'] = f'#id_{self.add_prefix("is_business")}_1'
self.fields['company'].widget.attrs['data-required-if'] = f'input[name="{self.add_prefix("is_business")}"][value="business"]'
if not event.settings.invoice_address_beneficiary:
del self.fields['beneficiary']

View File

@@ -0,0 +1,165 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.
#
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
# this file, see <https://pretix.eu/about/en/license>.
#
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>.
#
from collections import defaultdict
from typing import Optional
from django.urls import reverse
from django.utils.html import format_html
from django.utils.translation import gettext_lazy as _
from pretix.base.signals import EventPluginRegistry
def make_link(a_map, wrapper, is_active=True, event=None, plugin_name=None):
if a_map:
if 'href' not in a_map:
a_map['val'] = format_html('<i>{val}</i>', **a_map)
elif is_active:
a_map['val'] = format_html('<a href="{href}">{val}</a>', **a_map)
elif event and plugin_name:
a_map['val'] = format_html(
'<i>{val}</i> <a href="{plugin_href}">'
'<span data-toggle="tooltip" title="{errmes}" class="fa fa-warning fa-fw"></span></a>',
**a_map,
errmes=_("The relevant plugin is currently not active. To activate it, click here to go to the plugin settings."),
plugin_href=reverse('control:event.settings.plugins', kwargs={
'organizer': event.organizer.slug,
'event': event.slug,
}) + '#plugin_' + plugin_name,
)
else:
a_map['val'] = format_html(
'<i>{val}</i> <span data-toggle="tooltip" title="{errmes}" class="fa fa-warning fa-fw"></span>',
**a_map,
errmes=_("The relevant plugin is currently not active."),
)
return format_html(wrapper, **a_map)
class LogEntryTypeRegistry(EventPluginRegistry):
def __init__(self):
super().__init__({'action_type': lambda o: getattr(o, 'action_type')})
def register(self, *objs):
for obj in objs:
if not isinstance(obj, LogEntryType):
raise TypeError('Entries must be derived from LogEntryType')
if obj.__module__.startswith('pretix.base.'):
raise TypeError('Must not register base classes, only derived ones')
return super().register(*objs)
def new_from_dict(self, data):
"""
Register multiple instance of a `LogEntryType` class with different `action_type`
and plain text strings, as given by the items of the specified data dictionary.
This method is designed to be used as a decorator as follows:
.. code-block:: python
@log_entry_types.new_from_dict({
'pretix.event.item.added': _('The product has been created.'),
'pretix.event.item.changed': _('The product has been changed.'),
# ...
})
class CoreItemLogEntryType(ItemLogEntryType):
# ...
:param data: action types and descriptions
``{"some_action_type": "Plain text description", ...}``
"""
def reg(clz):
for action_type, plain in data.items():
self.register(clz(action_type=action_type, plain=plain))
return clz
return reg
"""
Registry for LogEntry types.
Each entry in this registry should be an instance of a subclass of ``LogEntryType``.
They are annotated with their ``action_type`` and the defining ``plugin``.
"""
log_entry_types = LogEntryTypeRegistry()
class LogEntryType:
"""
Base class for a type of LogEntry, identified by its action_type.
"""
def __init__(self, action_type=None, plain=None):
if action_type:
self.action_type = action_type
if plain:
self.plain = plain
def display(self, logentry, data):
"""
Returns the message to be displayed for a given logentry of this type.
:return: `str` or `LazyI18nString`
"""
if hasattr(self, 'plain'):
plain = str(self.plain)
if '{' in plain:
data = defaultdict(lambda: '?', data)
return plain.format_map(data)
else:
return plain
def get_object_link_info(self, logentry) -> Optional[dict]:
"""
Return information to generate a link to the `content_object` of a given log entry.
Not implemented in the base class, causing the object link to be omitted.
:return: Dictionary with the keys ``href`` (URL to view/edit the object) and
``val`` (text for the anchor element)
"""
pass
def get_object_link(self, logentry):
a_map = self.get_object_link_info(logentry)
return make_link(a_map, self.object_link_wrapper)
object_link_wrapper = '{val}'
def shred_pii(self, logentry):
"""
To be used for shredding personally identified information contained in the data field of a LogEntry of this
type.
"""
raise NotImplementedError
class NoOpShredderMixin:
def shred_pii(self, logentry):
pass
class ClearDataShredderMixin:
def shred_pii(self, logentry):
logentry.data = None

View File

@@ -19,137 +19,20 @@
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>.
#
from collections import defaultdict
from typing import Optional
from django.urls import reverse
from django.utils.html import escape
from django.utils.translation import gettext_lazy as _, pgettext_lazy
from pretix.base.signals import EventPluginRegistry
from pretix.base.models import (
Discount, Item, ItemCategory, Order, Question, Quota, SubEvent, TaxRule,
Voucher,
)
def make_link(a_map, wrapper, is_active=True, event=None, plugin_name=None):
if a_map:
if is_active:
a_map['val'] = '<a href="{href}">{val}</a>'.format_map(a_map)
elif event and plugin_name:
a_map['val'] = (
'<i>{val}</i> <a href="{plugin_href}">'
'<span data-toggle="tooltip" title="{errmes}" class="fa fa-warning fa-fw"></span></a>'
).format_map({
**a_map,
"errmes": _("The relevant plugin is currently not active. To activate it, click here to go to the plugin settings."),
"plugin_href": reverse('control:event.settings.plugins', kwargs={
'organizer': event.organizer.slug,
'event': event.slug,
}) + '#plugin_' + plugin_name,
})
else:
a_map['val'] = '<i>{val}</i> <span data-toggle="tooltip" title="{errmes}" class="fa fa-warning fa-fw"></span>'.format_map({
**a_map,
"errmes": _("The relevant plugin is currently not active."),
})
return wrapper.format_map(a_map)
class LogEntryTypeRegistry(EventPluginRegistry):
def __init__(self):
super().__init__({'action_type': lambda o: getattr(o, 'action_type')})
def register(self, *objs):
for obj in objs:
if not isinstance(obj, LogEntryType):
raise TypeError('Entries must be derived from LogEntryType')
if obj.__module__ == LogEntryType.__module__:
raise TypeError('Must not register base classes, only derived ones')
return super().register(*objs)
def new_from_dict(self, data):
"""
Register multiple instance of a `LogEntryType` class with different `action_type`
and plain text strings, as given by the items of the specified data dictionary.
This method is designed to be used as a decorator as follows:
.. code-block:: python
@log_entry_types.new_from_dict({
'pretix.event.item.added': _('The product has been created.'),
'pretix.event.item.changed': _('The product has been changed.'),
# ...
})
class CoreItemLogEntryType(ItemLogEntryType):
# ...
:param data: action types and descriptions
``{"some_action_type": "Plain text description", ...}``
"""
def reg(clz):
for action_type, plain in data.items():
self.register(clz(action_type=action_type, plain=plain))
return clz
return reg
"""
Registry for LogEntry types.
Each entry in this registry should be an instance of a subclass of ``LogEntryType``.
They are annotated with their ``action_type`` and the defining ``plugin``.
"""
log_entry_types = LogEntryTypeRegistry()
class LogEntryType:
"""
Base class for a type of LogEntry, identified by its action_type.
"""
def __init__(self, action_type=None, plain=None):
if action_type:
self.action_type = action_type
if plain:
self.plain = plain
def display(self, logentry):
"""
Returns the message to be displayed for a given logentry of this type.
:return: `str` or `LazyI18nString`
"""
if hasattr(self, 'plain'):
plain = str(self.plain)
if '{' in plain:
data = defaultdict(lambda: '?', logentry.parsed_data)
return plain.format_map(data)
else:
return plain
def get_object_link_info(self, logentry) -> dict:
"""
Return information to generate a link to the `content_object` of a given log entry.
Not implemented in the base class, causing the object link to be omitted.
:return: Dictionary with the keys ``href`` (containing a URL to view/edit the object) and ``val`` (containing the
escaped text for the anchor element)
"""
pass
def get_object_link(self, logentry):
a_map = self.get_object_link_info(logentry)
return make_link(a_map, self.object_link_wrapper)
object_link_wrapper = '{val}'
def shred_pii(self, logentry):
"""
To be used for shredding personally identified information contained in the data field of a LogEntry of this
type.
"""
raise NotImplementedError
from .logentrytype_registry import ( # noqa
ClearDataShredderMixin, LogEntryType, NoOpShredderMixin, log_entry_types,
make_link, LogEntryTypeRegistry,
)
class EventLogEntryType(LogEntryType):
@@ -157,15 +40,27 @@ class EventLogEntryType(LogEntryType):
Base class for any `LogEntry` type whose `content_object` is either an `Event` itself or belongs to a specific `Event`.
"""
def get_object_link_info(self, logentry) -> dict:
if hasattr(self, 'object_link_viewname') and logentry.content_object:
def get_object_link_info(self, logentry) -> Optional[dict]:
if hasattr(self, 'object_link_viewname'):
content = logentry.content_object
if not content:
if logentry.content_type_id:
return {
'val': _('(deleted)'),
}
else:
return
if hasattr(self, 'content_type') and not isinstance(content, self.content_type):
return
return {
'href': reverse(self.object_link_viewname, kwargs={
'event': logentry.event.slug,
'organizer': logentry.event.organizer.slug,
**self.object_link_args(logentry.content_object),
**self.object_link_args(content),
}),
'val': escape(self.object_link_display_name(logentry.content_object)),
'val': self.object_link_display_name(logentry.content_object),
}
def object_link_args(self, content_object):
@@ -182,6 +77,7 @@ class EventLogEntryType(LogEntryType):
class OrderLogEntryType(EventLogEntryType):
object_link_wrapper = _('Order {val}')
object_link_viewname = 'control:event.order'
content_type = Order
def object_link_args(self, order):
return {'code': order.code}
@@ -194,6 +90,7 @@ class VoucherLogEntryType(EventLogEntryType):
object_link_wrapper = _('Voucher {val}')
object_link_viewname = 'control:event.voucher'
object_link_argname = 'voucher'
content_type = Voucher
def object_link_display_name(self, voucher):
if len(voucher.code) > 6:
@@ -205,49 +102,46 @@ class ItemLogEntryType(EventLogEntryType):
object_link_wrapper = _('Product {val}')
object_link_viewname = 'control:event.item'
object_link_argname = 'item'
content_type = Item
class SubEventLogEntryType(EventLogEntryType):
object_link_wrapper = pgettext_lazy('subevent', 'Date {val}')
object_link_viewname = 'control:event.subevent'
object_link_argname = 'subevent'
content_type = SubEvent
class QuotaLogEntryType(EventLogEntryType):
object_link_wrapper = _('Quota {val}')
object_link_viewname = 'control:event.items.quotas.show'
object_link_argname = 'quota'
content_type = Quota
class DiscountLogEntryType(EventLogEntryType):
object_link_wrapper = _('Discount {val}')
object_link_viewname = 'control:event.items.discounts.edit'
object_link_argname = 'discount'
content_type = Discount
class ItemCategoryLogEntryType(EventLogEntryType):
object_link_wrapper = _('Category {val}')
object_link_viewname = 'control:event.items.categories.edit'
object_link_argname = 'category'
content_type = ItemCategory
class QuestionLogEntryType(EventLogEntryType):
object_link_wrapper = _('Question {val}')
object_link_viewname = 'control:event.items.questions.show'
object_link_argname = 'question'
content_type = Question
class TaxRuleLogEntryType(EventLogEntryType):
object_link_wrapper = _('Tax rule {val}')
object_link_viewname = 'control:event.settings.tax.edit'
object_link_argname = 'rule'
class NoOpShredderMixin:
def shred_pii(self, logentry):
pass
class ClearDataShredderMixin:
def shred_pii(self, logentry):
logentry.data = None
content_type = TaxRule

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.2.16 on 2025-01-23 11:27
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("pretixbase", "0275_alter_question_valid_number_max_and_more"),
]
operations = [
migrations.AddField(
model_name="item",
name="hidden_if_item_available_mode",
field=models.CharField(default="hide", max_length=16),
),
]

View File

@@ -442,8 +442,12 @@ class Item(LoggedModel):
UNAVAIL_MODE_INFO = "info"
UNAVAIL_MODES = (
(UNAVAIL_MODE_HIDDEN, _("Hide product if unavailable")),
(UNAVAIL_MODE_INFO, _("Show info text if unavailable")),
(UNAVAIL_MODE_INFO, _("Show product with info on why its unavailable")),
)
UNAVAIL_MODE_ICONS = {
UNAVAIL_MODE_HIDDEN: 'eye-slash',
UNAVAIL_MODE_INFO: 'info'
}
MEDIA_POLICY_REUSE = 'reuse'
MEDIA_POLICY_NEW = 'new'
@@ -596,6 +600,11 @@ class Item(LoggedModel):
"be a short period in which both products are visible while all tickets of the referenced "
"product are reserved, but not yet sold.")
)
hidden_if_item_available_mode = models.CharField(
choices=UNAVAIL_MODES,
default=UNAVAIL_MODE_HIDDEN,
max_length=16,
)
require_voucher = models.BooleanField(
verbose_name=_('This product can only be bought using a voucher.'),
default=False,
@@ -885,6 +894,8 @@ class Item(LoggedModel):
return 'available_from'
elif subevent_item and subevent_item.available_until and subevent_item.available_until < now_dt:
return 'available_until'
elif self.hidden_if_item_available and self._dependency_available:
return 'hidden_if_item_available'
else:
return None

View File

@@ -40,7 +40,7 @@ from django.contrib.contenttypes.models import ContentType
from django.db import models
from django.utils.functional import cached_property
from pretix.base.logentrytypes import log_entry_types, make_link
from pretix.base.logentrytype_registry import log_entry_types, make_link
from pretix.base.signals import is_app_active, logentry_object_link
@@ -93,7 +93,7 @@ class LogEntry(models.Model):
def display(self):
log_entry_type, meta = log_entry_types.get(action_type=self.action_type)
if log_entry_type:
return log_entry_type.display(self)
return log_entry_type.display(self, self.parsed_data)
from ..signals import logentry_display

View File

@@ -355,7 +355,7 @@ class Order(LockModel, LoggedModel):
if not self.testmode:
raise TypeError("Only test mode orders can be deleted.")
self.event.log_action(
self.log_action(
'pretix.event.order.deleted', user=user, auth=auth,
data={
'code': self.code,

View File

@@ -231,7 +231,7 @@ class EventWizardBasicsForm(I18nModelForm):
raise ValidationError({
'timezone': _('Your default locale must be specified.')
})
if not data.get("no_taxes") and not data.get("tax_rate"):
if not data.get("no_taxes") and data.get("tax_rate") is None:
raise ValidationError({
'tax_rate': _('You have not specified a tax rate. If you do not want us to compute sales taxes, please '
'check "{field}" above.').format(field=self.fields["no_taxes"].label)

View File

@@ -476,6 +476,7 @@ class ItemCreateForm(I18nModelForm):
'show_quota_left',
'hidden_if_available',
'hidden_if_item_available',
'hidden_if_item_available_mode',
'require_bundling',
'require_membership',
'grant_membership_type',
@@ -646,18 +647,12 @@ class ItemUpdateForm(I18nModelForm):
self.fields['available_from_mode'].widget = ButtonGroupRadioSelect(
choices=self.fields['available_from_mode'].choices,
option_icons={
Item.UNAVAIL_MODE_HIDDEN: 'eye-slash',
Item.UNAVAIL_MODE_INFO: 'info'
}
option_icons=Item.UNAVAIL_MODE_ICONS
)
self.fields['available_until_mode'].widget = ButtonGroupRadioSelect(
choices=self.fields['available_until_mode'].choices,
option_icons={
Item.UNAVAIL_MODE_HIDDEN: 'eye-slash',
Item.UNAVAIL_MODE_INFO: 'info'
}
option_icons=Item.UNAVAIL_MODE_ICONS
)
self.fields['hide_without_voucher'].widget = ButtonGroupRadioSelect(
@@ -672,6 +667,11 @@ class ItemUpdateForm(I18nModelForm):
attrs={'data-checkbox-dependency': '#id_require_voucher'}
)
self.fields['hidden_if_item_available_mode'].widget = ButtonGroupRadioSelect(
choices=self.fields['hidden_if_item_available_mode'].choices,
option_icons=Item.UNAVAIL_MODE_ICONS
)
if self.instance.hidden_if_available_id:
self.fields['hidden_if_available'].queryset = self.event.quotas.all()
self.fields['hidden_if_available'].help_text = format_html(
@@ -853,6 +853,7 @@ class ItemUpdateForm(I18nModelForm):
'show_quota_left',
'hidden_if_available',
'hidden_if_item_available',
'hidden_if_item_available_mode',
'issue_giftcard',
'require_membership',
'require_membership_types',
@@ -970,18 +971,12 @@ class ItemVariationForm(I18nModelForm):
self.fields['available_from_mode'].widget = ButtonGroupRadioSelect(
choices=self.fields['available_from_mode'].choices,
option_icons={
Item.UNAVAIL_MODE_HIDDEN: 'eye-slash',
Item.UNAVAIL_MODE_INFO: 'info'
}
option_icons=Item.UNAVAIL_MODE_ICONS
)
self.fields['available_until_mode'].widget = ButtonGroupRadioSelect(
choices=self.fields['available_until_mode'].choices,
option_icons={
Item.UNAVAIL_MODE_HIDDEN: 'eye-slash',
Item.UNAVAIL_MODE_INFO: 'info'
}
option_icons=Item.UNAVAIL_MODE_ICONS
)
self.meta_fields = []

View File

@@ -1043,6 +1043,15 @@ class SSOProviderForm(I18nModelForm):
label=pgettext_lazy('sso_oidc', 'Phone field'),
required=False,
)
config_oidc_query_parameters = forms.CharField(
label=pgettext_lazy('sso_oidc', 'Query parameters'),
help_text=pgettext_lazy('sso_oidc', 'Optional query parameters, that will be added to calls to '
'the authorization endpoint. Enter as: {example}'.format(
example='<code>param1=value1&amp;param2=value2</code>'
),
),
required=False,
)
class Meta:
model = CustomerSSOProvider

View File

@@ -33,16 +33,16 @@
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under the License.
import json
from collections import defaultdict
from decimal import Decimal
from typing import Optional
import bleach
import dateutil.parser
from django.dispatch import receiver
from django.urls import reverse
from django.utils.formats import date_format
from django.utils.html import escape
from django.utils.html import escape, format_html
from django.utils.safestring import mark_safe
from django.utils.translation import gettext_lazy as _, pgettext_lazy
from i18nfield.strings import LazyI18nString
@@ -68,128 +68,212 @@ OVERVIEW_BANLIST = [
]
def _display_order_changed(event: Event, logentry: LogEntry):
data = json.loads(logentry.data)
class OrderChangeLogEntryType(OrderLogEntryType):
prefix = _('The order has been changed:')
text = _('The order has been changed:')
if logentry.action_type == 'pretix.event.order.changed.item':
def display(self, logentry, data):
return self.prefix + ' ' + self.display_prefixed(logentry.event, logentry, data)
def display_prefixed(self, event: Event, logentry: LogEntry, data):
return super().display(logentry, data)
class OrderPositionChangeLogEntryType(OrderChangeLogEntryType):
prefix = _('The order has been changed:')
def display(self, logentry, data):
return super().display(logentry, {**data, 'posid': data.get('positionid', '?')})
@log_entry_types.new()
class OrderItemChanged(OrderChangeLogEntryType):
action_type = 'pretix.event.order.changed.item'
def display_prefixed(self, event: Event, logentry: LogEntry, data):
old_item = str(event.items.get(pk=data['old_item']))
if data['old_variation']:
old_item += ' - ' + str(ItemVariation.objects.get(item__event=event, pk=data['old_variation']))
new_item = str(event.items.get(pk=data['new_item']))
if data['new_variation']:
new_item += ' - ' + str(ItemVariation.objects.get(item__event=event, pk=data['new_variation']))
return text + ' ' + _('Position #{posid}: {old_item} ({old_price}) changed '
'to {new_item} ({new_price}).').format(
return _('Position #{posid}: {old_item} ({old_price}) changed to {new_item} ({new_price}).').format(
posid=data.get('positionid', '?'),
old_item=old_item, new_item=new_item,
old_price=money_filter(Decimal(data['old_price']), event.currency),
new_price=money_filter(Decimal(data['new_price']), event.currency),
)
elif logentry.action_type == 'pretix.event.order.changed.membership':
return text + ' ' + _('Position #{posid}: Used membership changed.').format(
posid=data.get('positionid', '?'),
)
elif logentry.action_type == 'pretix.event.order.changed.seat':
return text + ' ' + _('Position #{posid}: Seat "{old_seat}" changed '
'to "{new_seat}".').format(
posid=data.get('positionid', '?'),
old_seat=data.get('old_seat'), new_seat=data.get('new_seat'),
)
elif logentry.action_type == 'pretix.event.order.changed.subevent':
@log_entry_types.new()
class OrderMembershipChanged(OrderPositionChangeLogEntryType):
action_type = 'pretix.event.order.changed.membership'
plain = _('Position #{posid}: Used membership changed.')
@log_entry_types.new()
class OrderSeatChanged(OrderPositionChangeLogEntryType):
action_type = 'pretix.event.order.changed.seat'
plain = _('Position #{posid}: Seat "{old_seat}" changed to "{new_seat}".')
@log_entry_types.new()
class OrderSubeventChanged(OrderChangeLogEntryType):
action_type = 'pretix.event.order.changed.subevent'
def display_prefixed(self, event: Event, logentry: LogEntry, data):
old_se = str(event.subevents.get(pk=data['old_subevent']))
new_se = str(event.subevents.get(pk=data['new_subevent']))
return text + ' ' + _('Position #{posid}: Event date "{old_event}" ({old_price}) changed '
'to "{new_event}" ({new_price}).').format(
return _('Position #{posid}: Event date "{old_event}" ({old_price}) changed '
'to "{new_event}" ({new_price}).').format(
posid=data.get('positionid', '?'),
old_event=old_se, new_event=new_se,
old_price=money_filter(Decimal(data['old_price']), event.currency),
new_price=money_filter(Decimal(data['new_price']), event.currency),
)
elif logentry.action_type == 'pretix.event.order.changed.price':
return text + ' ' + _('Price of position #{posid} changed from {old_price} '
'to {new_price}.').format(
@log_entry_types.new()
class OrderPriceChanged(OrderChangeLogEntryType):
action_type = 'pretix.event.order.changed.price'
def display_prefixed(self, event: Event, logentry: LogEntry, data):
return _('Price of position #{posid} changed from {old_price} to {new_price}.').format(
posid=data.get('positionid', '?'),
old_price=money_filter(Decimal(data['old_price']), event.currency),
new_price=money_filter(Decimal(data['new_price']), event.currency),
)
elif logentry.action_type == 'pretix.event.order.changed.tax_rule':
@log_entry_types.new()
class OrderTaxRuleChanged(OrderChangeLogEntryType):
action_type = 'pretix.event.order.changed.tax_rule'
def display_prefixed(self, event: Event, logentry: LogEntry, data):
if 'positionid' in data:
return text + ' ' + _('Tax rule of position #{posid} changed from {old_rule} '
'to {new_rule}.').format(
return _('Tax rule of position #{posid} changed from {old_rule} to {new_rule}.').format(
posid=data.get('positionid', '?'),
old_rule=TaxRule.objects.get(pk=data['old_taxrule']) if data['old_taxrule'] else '',
new_rule=TaxRule.objects.get(pk=data['new_taxrule']),
)
elif 'fee' in data:
return text + ' ' + _('Tax rule of fee #{fee} changed from {old_rule} '
'to {new_rule}.').format(
return _('Tax rule of fee #{fee} changed from {old_rule} to {new_rule}.').format(
fee=data.get('fee', '?'),
old_rule=TaxRule.objects.get(pk=data['old_taxrule']) if data['old_taxrule'] else '',
new_rule=TaxRule.objects.get(pk=data['new_taxrule']),
)
elif logentry.action_type == 'pretix.event.order.changed.addfee':
return text + ' ' + str(_('A fee has been added'))
elif logentry.action_type == 'pretix.event.order.changed.feevalue':
return text + ' ' + _('A fee was changed from {old_price} to {new_price}.').format(
@log_entry_types.new()
class OrderFeeAdded(OrderChangeLogEntryType):
action_type = 'pretix.event.order.changed.addfee'
plain = _('A fee has been added')
@log_entry_types.new()
class OrderFeeChanged(OrderChangeLogEntryType):
action_type = 'pretix.event.order.changed.feevalue'
def display_prefixed(self, event: Event, logentry: LogEntry, data):
return _('A fee was changed from {old_price} to {new_price}.').format(
old_price=money_filter(Decimal(data['old_price']), event.currency),
new_price=money_filter(Decimal(data['new_price']), event.currency),
)
elif logentry.action_type == 'pretix.event.order.changed.cancelfee':
return text + ' ' + _('A fee of {old_price} was removed.').format(
@log_entry_types.new()
class OrderFeeRemoved(OrderChangeLogEntryType):
action_type = 'pretix.event.order.changed.cancelfee'
def display_prefixed(self, event: Event, logentry: LogEntry, data):
return _('A fee of {old_price} was removed.').format(
old_price=money_filter(Decimal(data['old_price']), event.currency),
)
elif logentry.action_type == 'pretix.event.order.changed.cancel':
@log_entry_types.new()
class OrderCanceled(OrderChangeLogEntryType):
action_type = 'pretix.event.order.changed.cancel'
def display_prefixed(self, event: Event, logentry: LogEntry, data):
old_item = str(event.items.get(pk=data['old_item']))
if data['old_variation']:
old_item += ' - ' + str(ItemVariation.objects.get(pk=data['old_variation']))
return text + ' ' + _('Position #{posid} ({old_item}, {old_price}) canceled.').format(
return _('Position #{posid} ({old_item}, {old_price}) canceled.').format(
posid=data.get('positionid', '?'),
old_item=old_item,
old_price=money_filter(Decimal(data['old_price']), event.currency),
)
elif logentry.action_type == 'pretix.event.order.changed.add':
@log_entry_types.new()
class OrderPositionAdded(OrderChangeLogEntryType):
action_type = 'pretix.event.order.changed.add'
def display_prefixed(self, event: Event, logentry: LogEntry, data):
item = str(event.items.get(pk=data['item']))
if data['variation']:
item += ' - ' + str(ItemVariation.objects.get(item__event=event, pk=data['variation']))
if data['addon_to']:
addon_to = OrderPosition.objects.get(order__event=event, pk=data['addon_to'])
return text + ' ' + _('Position #{posid} created: {item} ({price}) as an add-on to '
'position #{addon_to}.').format(
return _('Position #{posid} created: {item} ({price}) as an add-on to position #{addon_to}.').format(
posid=data.get('positionid', '?'),
item=item, addon_to=addon_to.positionid,
price=money_filter(Decimal(data['price']), event.currency),
)
else:
return text + ' ' + _('Position #{posid} created: {item} ({price}).').format(
return _('Position #{posid} created: {item} ({price}).').format(
posid=data.get('positionid', '?'),
item=item,
price=money_filter(Decimal(data['price']), event.currency),
)
elif logentry.action_type == 'pretix.event.order.changed.secret':
return text + ' ' + _('A new secret has been generated for position #{posid}.').format(
posid=data.get('positionid', '?'),
)
elif logentry.action_type == 'pretix.event.order.changed.valid_from':
return text + ' ' + _('The validity start date for position #{posid} has been changed to {value}.').format(
@log_entry_types.new()
class OrderSecretChanged(OrderPositionChangeLogEntryType):
action_type = 'pretix.event.order.changed.secret'
plain = _('A new secret has been generated for position #{posid}.')
@log_entry_types.new()
class OrderValidFromChanged(OrderChangeLogEntryType):
action_type = 'pretix.event.order.changed.valid_from'
def display_prefixed(self, event: Event, logentry: LogEntry, data):
return _('The validity start date for position #{posid} has been changed to {value}.').format(
posid=data.get('positionid', '?'),
value=date_format(dateutil.parser.parse(data.get('new_value')), 'SHORT_DATETIME_FORMAT') if data.get(
'new_value') else ''
)
elif logentry.action_type == 'pretix.event.order.changed.valid_until':
return text + ' ' + _('The validity end date for position #{posid} has been changed to {value}.').format(
@log_entry_types.new()
class OrderValidUntilChanged(OrderChangeLogEntryType):
action_type = 'pretix.event.order.changed.valid_until'
def display_prefixed(self, event: Event, logentry: LogEntry, data):
return _('The validity end date for position #{posid} has been changed to {value}.').format(
posid=data.get('positionid', '?'),
value=date_format(dateutil.parser.parse(data.get('new_value')), 'SHORT_DATETIME_FORMAT') if data.get('new_value') else ''
)
elif logentry.action_type == 'pretix.event.order.changed.add_block':
return text + ' ' + _('A block has been added for position #{posid}.').format(
posid=data.get('positionid', '?'),
)
elif logentry.action_type == 'pretix.event.order.changed.remove_block':
return text + ' ' + _('A block has been removed for position #{posid}.').format(
posid=data.get('positionid', '?'),
)
elif logentry.action_type == 'pretix.event.order.changed.split':
@log_entry_types.new()
class OrderChangedBlockAdded(OrderPositionChangeLogEntryType):
action_type = 'pretix.event.order.changed.add_block'
plain = _('A block has been added for position #{posid}.')
@log_entry_types.new()
class OrderChangedBlockRemoved(OrderPositionChangeLogEntryType):
action_type = 'pretix.event.order.changed.remove_block'
plain = _('A block has been removed for position #{posid}.')
@log_entry_types.new()
class OrderChangedSplit(OrderChangeLogEntryType):
action_type = 'pretix.event.order.changed.split'
def display_prefixed(self, event: Event, logentry: LogEntry, data):
old_item = str(event.items.get(pk=data['old_item']))
if data['old_variation']:
old_item += ' - ' + str(ItemVariation.objects.get(pk=data['old_variation']))
@@ -198,194 +282,148 @@ def _display_order_changed(event: Event, logentry: LogEntry):
'organizer': event.organizer.slug,
'code': data['new_order']
})
return mark_safe(escape(text) + ' ' + _('Position #{posid} ({old_item}, {old_price}) split into new order: {order}').format(
return mark_safe(self.prefix + ' ' + _('Position #{posid} ({old_item}, {old_price}) split into new order: {order}').format(
old_item=escape(old_item),
posid=data.get('positionid', '?'),
order='<a href="{}">{}</a>'.format(url, data['new_order']),
old_price=money_filter(Decimal(data['old_price']), event.currency),
))
elif logentry.action_type == 'pretix.event.order.changed.split_from':
@log_entry_types.new()
class OrderChangedSplitFrom(OrderLogEntryType):
action_type = 'pretix.event.order.changed.split_from'
def display(self, logentry: LogEntry, data):
return _('This order has been created by splitting the order {order}').format(
order=data['original_order'],
)
def _display_checkin(event, logentry):
data = logentry.parsed_data
@log_entry_types.new_from_dict({
'pretix.event.checkin.unknown': (
_('Unknown scan of code "{barcode}" at {datetime} for list "{list}", type "{type}".'),
_('Unknown scan of code "{barcode}" for list "{list}", type "{type}".'),
),
'pretix.event.checkin.revoked': (
_('Scan of revoked code "{barcode}" at {datetime} for list "{list}", type "{type}", was uploaded.'),
_('Scan of revoked code "{barcode}" for list "{list}", type "{type}", was uploaded.'),
),
'pretix.event.checkin.denied': (
_('Denied scan of position #{posid} at {datetime} for list "{list}", type "{type}", error code "{errorcode}".'),
_('Denied scan of position #{posid} for list "{list}", type "{type}", error code "{errorcode}".'),
),
'pretix.control.views.checkin.reverted': _('The check-in of position #{posid} on list "{list}" has been reverted.'),
'pretix.event.checkin.reverted': _('The check-in of position #{posid} on list "{list}" has been reverted.'),
})
class CheckinErrorLogEntryType(OrderLogEntryType):
def display(self, logentry: LogEntry, data):
self.display_plain(self.plain, logentry, data)
show_dt = False
if 'datetime' in data:
dt = dateutil.parser.parse(data.get('datetime'))
show_dt = abs((logentry.datetime - dt).total_seconds()) > 5 or 'forced' in data
tz = event.timezone
dt_formatted = date_format(dt.astimezone(tz), "SHORT_DATETIME_FORMAT")
def display_plain(self, plain, logentry: LogEntry, data):
if isinstance(plain, tuple):
plain_with_dt, plain_without_dt = plain
else:
plain_with_dt, plain_without_dt = plain, plain
if 'list' in data:
try:
checkin_list = event.checkin_lists.get(pk=data.get('list')).name
except CheckinList.DoesNotExist:
checkin_list = _("(unknown)")
else:
checkin_list = _("(unknown)")
data = defaultdict(lambda: '?', data)
if logentry.action_type == 'pretix.event.checkin.unknown':
if show_dt:
return _(
'Unknown scan of code "{barcode}" at {datetime} for list "{list}", type "{type}".'
).format(
posid=data.get('positionid'),
type=data.get('type'),
barcode=data.get('barcode')[:16],
datetime=dt_formatted,
list=checkin_list
event = logentry.event
if 'list' in data:
try:
data['list'] = event.checkin_lists.get(pk=data.get('list')).name
except CheckinList.DoesNotExist:
data['list'] = _("(unknown)")
else:
data['list'] = _("(unknown)")
data['barcode'] = data.get('barcode', '')[:16]
data['posid'] = logentry.parsed_data.get('positionid', '?')
if 'datetime' in data:
dt = dateutil.parser.parse(data.get('datetime'))
if abs((logentry.datetime - dt).total_seconds()) > 5 or 'forced' in data:
tz = event.timezone
data['datetime'] = date_format(dt.astimezone(tz), "SHORT_DATETIME_FORMAT")
return str(plain_with_dt).format_map(data)
else:
return str(plain_without_dt).format_map(data)
@log_entry_types.new('pretix.event.checkin')
class CheckinLogEntryType(CheckinErrorLogEntryType):
def display(self, logentry: LogEntry, data):
if data.get('type') == Checkin.TYPE_EXIT:
return self.display_plain((
_('Position #{posid} has been checked out at {datetime} for list "{list}".'),
_('Position #{posid} has been checked out for list "{list}".'),
), logentry, data)
elif data.get('first'):
return self.display_plain((
_('Position #{posid} has been checked in at {datetime} for list "{list}".'),
_('Position #{posid} has been checked in for list "{list}".'),
), logentry, data)
elif data.get('forced'):
return self.display_plain(
_('A scan for position #{posid} at {datetime} for list "{list}" has been uploaded even though it has '
'been scanned already.'),
logentry, data
)
else:
return _(
'Unknown scan of code "{barcode}" for list "{list}", type "{type}".'
).format(
posid=data.get('positionid'),
type=data.get('type'),
barcode=data.get('barcode')[:16],
list=checkin_list
return self.display_plain(
_('Position #{posid} has been scanned and rejected because it has already been scanned before '
'on list "{list}".'),
logentry, data
)
if logentry.action_type == 'pretix.event.checkin.revoked':
if show_dt:
return _(
'Scan scan of revoked code "{barcode}" at {datetime} for list "{list}", type "{type}", was uploaded.'
).format(
posid=data.get('positionid'),
type=data.get('type'),
barcode=data.get('barcode')[:16],
datetime=dt_formatted,
list=checkin_list
)
else:
return _(
'Scan of revoked code "{barcode}" for list "{list}", type "{type}", was uploaded.'
).format(
posid=data.get('positionid'),
type=data.get('type'),
barcode=data.get('barcode')[:16],
list=checkin_list
)
if logentry.action_type == 'pretix.event.checkin.denied':
if show_dt:
return _(
'Denied scan of position #{posid} at {datetime} for list "{list}", type "{type}", '
'error code "{errorcode}".'
).format(
posid=data.get('positionid'),
type=data.get('type'),
errorcode=data.get('errorcode'),
datetime=dt_formatted,
list=checkin_list
)
else:
return _(
'Denied scan of position #{posid} for list "{list}", type "{type}", error code "{errorcode}".'
).format(
posid=data.get('positionid'),
type=data.get('type'),
errorcode=data.get('errorcode'),
list=checkin_list
)
@log_entry_types.new()
class OrderConsentLogEntryType(OrderLogEntryType):
action_type = 'pretix.event.order.consent'
if data.get('type') == Checkin.TYPE_EXIT:
if show_dt:
return _('Position #{posid} has been checked out at {datetime} for list "{list}".').format(
posid=data.get('positionid'),
datetime=dt_formatted,
list=checkin_list
)
def display(self, logentry: LogEntry, data):
return _('The user confirmed the following message: "{}"').format(
bleach.clean(data.get('msg'), tags=set(), strip=True)
)
@log_entry_types.new()
class OrderCanceledLogEntryType(OrderLogEntryType):
action_type = 'pretix.event.order.canceled'
def display(self, logentry: LogEntry, data):
comment = data.get('comment')
if comment:
return _('The order has been canceled (comment: "{comment}").').format(comment=comment)
else:
return _('Position #{posid} has been checked out for list "{list}".').format(
posid=data.get('positionid'),
list=checkin_list
)
if data.get('first'):
if show_dt:
return _('Position #{posid} has been checked in at {datetime} for list "{list}".').format(
posid=data.get('positionid'),
datetime=dt_formatted,
list=checkin_list
)
else:
return _('Position #{posid} has been checked in for list "{list}".').format(
posid=data.get('positionid'),
list=checkin_list
)
else:
if data.get('forced'):
return _(
'A scan for position #{posid} at {datetime} for list "{list}" has been uploaded even though it has '
'been scanned already.'.format(
posid=data.get('positionid'),
datetime=dt_formatted,
list=checkin_list
)
)
return _(
'Position #{posid} has been scanned and rejected because it has already been scanned before '
'on list "{list}".'.format(
posid=data.get('positionid'),
list=checkin_list
)
return _('The order has been canceled.')
@log_entry_types.new()
class OrderPrintLogEntryType(OrderLogEntryType):
action_type = 'pretix.event.order.print'
def display(self, logentry: LogEntry, data):
return _('Position #{posid} has been printed at {datetime} with type "{type}".').format(
posid=data.get('positionid'),
datetime=date_format(
dateutil.parser.parse(data["datetime"]).astimezone(logentry.event.timezone),
"SHORT_DATETIME_FORMAT"
),
type=dict(PrintLog.PRINT_TYPES)[data["type"]],
)
@receiver(signal=logentry_display, dispatch_uid="pretixcontrol_logentry_display")
def pretixcontrol_logentry_display(sender: Event, logentry: LogEntry, **kwargs):
if logentry.action_type.startswith('pretix.event.order.changed'):
return _display_order_changed(sender, logentry)
if logentry.action_type.startswith('pretix.event.payment.provider.'):
return _('The settings of a payment provider have been changed.')
if logentry.action_type.startswith('pretix.event.tickets.provider.'):
return _('The settings of a ticket output provider have been changed.')
if logentry.action_type == 'pretix.event.order.consent':
return _('The user confirmed the following message: "{}"').format(
bleach.clean(logentry.parsed_data.get('msg'), tags=set(), strip=True)
)
if logentry.action_type == 'pretix.event.order.canceled':
comment = logentry.parsed_data.get('comment')
if comment:
return _('The order has been canceled (comment: "{comment}").').format(comment=comment)
else:
return _('The order has been canceled.')
if logentry.action_type in ('pretix.control.views.checkin.reverted', 'pretix.event.checkin.reverted'):
if 'list' in logentry.parsed_data:
try:
checkin_list = sender.checkin_lists.get(pk=logentry.parsed_data.get('list')).name
except CheckinList.DoesNotExist:
checkin_list = _("(unknown)")
else:
checkin_list = _("(unknown)")
return _('The check-in of position #{posid} on list "{list}" has been reverted.').format(
posid=logentry.parsed_data.get('positionid'),
list=checkin_list,
)
if sender and logentry.action_type.startswith('pretix.event.checkin'):
return _display_checkin(sender, logentry)
if logentry.action_type == 'pretix.event.order.print':
return _('Position #{posid} has been printed at {datetime} with type "{type}".').format(
posid=logentry.parsed_data.get('positionid'),
datetime=date_format(
dateutil.parser.parse(logentry.parsed_data["datetime"]).astimezone(sender.timezone),
"SHORT_DATETIME_FORMAT"
),
type=dict(PrintLog.PRINT_TYPES)[logentry.parsed_data["type"]],
)
@receiver(signal=orderposition_blocked_display, dispatch_uid="pretixcontrol_orderposition_blocked_display")
def pretixcontrol_orderposition_blocked_display(sender: Event, orderposition, block_name, **kwargs):
@@ -396,6 +434,7 @@ def pretixcontrol_orderposition_blocked_display(sender: Event, orderposition, bl
@log_entry_types.new_from_dict({
'pretix.event.order.deleted': _('The test mode order {code} has been deleted.'),
'pretix.event.order.modified': _('The order details have been changed.'),
'pretix.event.order.unpaid': _('The order has been marked as unpaid.'),
'pretix.event.order.secret.changed': _('The order\'s secret has been changed.'),
@@ -486,17 +525,16 @@ class VoucherRedeemedLogEntryType(VoucherLogEntryType):
action_type = 'pretix.voucher.redeemed'
plain = _('The voucher has been redeemed in order {order_code}.')
def display(self, logentry):
data = json.loads(logentry.data)
data = defaultdict(lambda: '?', data)
def display(self, logentry, data):
url = reverse('control:event.order', kwargs={
'event': logentry.event.slug,
'organizer': logentry.event.organizer.slug,
'code': data['order_code']
'code': data.get('order_code', '?')
})
return mark_safe(self.plain.format(
order_code='<a href="{}">{}</a>'.format(url, data['order_code']),
))
return format_html(
self.plain,
order_code=format_html('<a href="{}">{}</a>', url, data.get('order_code', '?')),
)
@log_entry_types.new_from_dict({
@@ -519,8 +557,8 @@ class CoreTaxRuleLogEntryType(TaxRuleLogEntryType):
class TeamMembershipLogEntryType(LogEntryType):
def display(self, logentry):
return self.plain.format(user=logentry.parsed_data.get('email'))
def display(self, logentry, data):
return self.plain.format(user=data.get('email'))
@log_entry_types.new_from_dict({
@@ -537,9 +575,9 @@ class CoreTeamMembershipLogEntryType(TeamMembershipLogEntryType):
class TeamMemberJoinedLogEntryType(LogEntryType):
action_type = 'pretix.team.member.joined'
def display(self, logentry):
def display(self, logentry, data):
return _('{user} has joined the team using the invite sent to {email}.').format(
user=logentry.parsed_data.get('email'), email=logentry.parsed_data.get('invite_email')
user=data.get('email'), email=data.get('invite_email')
)
@@ -547,23 +585,23 @@ class TeamMemberJoinedLogEntryType(LogEntryType):
class UserSettingsChangedLogEntryType(LogEntryType):
action_type = 'pretix.user.settings.changed'
def display(self, logentry):
def display(self, logentry, data):
text = str(_('Your account settings have been changed.'))
if 'email' in logentry.parsed_data:
if 'email' in data:
text = text + ' ' + str(
_('Your email address has been changed to {email}.').format(email=logentry.parsed_data['email']))
if 'new_pw' in logentry.parsed_data:
_('Your email address has been changed to {email}.').format(email=data['email']))
if 'new_pw' in data:
text = text + ' ' + str(_('Your password has been changed.'))
if logentry.parsed_data.get('is_active') is True:
if data.get('is_active') is True:
text = text + ' ' + str(_('Your account has been enabled.'))
elif logentry.parsed_data.get('is_active') is False:
elif data.get('is_active') is False:
text = text + ' ' + str(_('Your account has been disabled.'))
return text
class UserImpersonatedLogEntryType(LogEntryType):
def display(self, logentry):
return self.plain.format(logentry.parsed_data['other_email'])
def display(self, logentry, data):
return self.plain.format(data['other_email'])
@log_entry_types.new_from_dict({
@@ -688,16 +726,13 @@ class CoreLogEntryType(LogEntryType):
@log_entry_types.new_from_dict({
'pretix.event.order.deleted': _('The test mode order {code} has been deleted.'),
'pretix.event.item_meta_property.added': _('A meta property has been added to this event.'),
'pretix.event.item_meta_property.deleted': _('A meta property has been removed from this event.'),
'pretix.event.item_meta_property.changed': _('A meta property has been changed on this event.'),
'pretix.event.checkinlist.added': _('The check-in list has been added.'),
'pretix.event.checkinlist.deleted': _('The check-in list has been deleted.'),
'pretix.event.checkinlists.deleted': _('The check-in list has been deleted.'), # backwards compatibility
'pretix.event.checkinlist.changed': _('The check-in list has been changed.'),
'pretix.event.settings': _('The event settings have been changed.'),
'pretix.event.tickets.settings': _('The ticket download settings have been changed.'),
'pretix.event.tickets.provider': _('The settings of a ticket output provider have been changed.'),
'pretix.event.payment.provider': _('The settings of a payment provider have been changed.'),
'pretix.event.live.activated': _('The shop has been taken live.'),
'pretix.event.live.deactivated': _('The shop has been taken offline.'),
'pretix.event.testmode.activated': _('The shop has been taken into test mode.'),
@@ -717,6 +752,19 @@ class CoreEventLogEntryType(EventLogEntryType):
pass
@log_entry_types.new_from_dict({
'pretix.event.checkinlist.added': _('The check-in list has been added.'),
'pretix.event.checkinlist.deleted': _('The check-in list has been deleted.'),
'pretix.event.checkinlists.deleted': _('The check-in list has been deleted.'), # backwards compatibility
'pretix.event.checkinlist.changed': _('The check-in list has been changed.'),
})
class CheckinlistLogEntryType(EventLogEntryType):
object_link_wrapper = _('Check-in list {val}')
object_link_viewname = 'control:event.orders.checkinlists.edit'
object_link_argname = 'list'
content_type = CheckinList
@log_entry_types.new_from_dict({
'pretix.event.plugins.enabled': _('The plugin has been enabled.'),
'pretix.event.plugins.disabled': _('The plugin has been disabled.'),
@@ -724,7 +772,7 @@ class CoreEventLogEntryType(EventLogEntryType):
class EventPluginStateLogEntryType(EventLogEntryType):
object_link_wrapper = _('Plugin {val}')
def get_object_link_info(self, logentry) -> dict:
def get_object_link_info(self, logentry) -> Optional[dict]:
if 'plugin' in logentry.parsed_data:
app = app_cache.get(logentry.parsed_data['plugin'])
if app and hasattr(app, 'PretixPluginMeta'):
@@ -759,17 +807,17 @@ class CoreItemLogEntryType(ItemLogEntryType):
'pretix.event.item.variation.changed': _('The variation "{value}" has been changed.'),
})
class VariationLogEntryType(ItemLogEntryType):
def display(self, logentry):
if 'value' not in logentry.parsed_data:
def display(self, logentry, data):
if 'value' not in data:
# Backwards compatibility
var = ItemVariation.objects.filter(id=logentry.parsed_data['id']).first()
var = ItemVariation.objects.filter(id=data['id']).first()
if var:
logentry.parsed_data['value'] = str(var.value)
data['value'] = str(var.value)
else:
logentry.parsed_data['value'] = '?'
data['value'] = '?'
else:
logentry.parsed_data['value'] = LazyI18nString(logentry.parsed_data['value'])
return super().display(logentry)
data['value'] = LazyI18nString(data['value'])
return super().display(logentry, data)
@log_entry_types.new_from_dict({
@@ -825,27 +873,27 @@ class CoreDiscountLogEntryType(DiscountLogEntryType):
class LegacyCheckinLogEntryType(OrderLogEntryType):
action_type = 'pretix.control.views.checkin'
def display(self, logentry):
def display(self, logentry, data):
# deprecated
dt = dateutil.parser.parse(logentry.parsed_data.get('datetime'))
dt = dateutil.parser.parse(data.get('datetime'))
tz = logentry.event.timezone
dt_formatted = date_format(dt.astimezone(tz), "SHORT_DATETIME_FORMAT")
if 'list' in logentry.parsed_data:
if 'list' in data:
try:
checkin_list = logentry.event.checkin_lists.get(pk=logentry.parsed_data.get('list')).name
checkin_list = logentry.event.checkin_lists.get(pk=data.get('list')).name
except CheckinList.DoesNotExist:
checkin_list = _("(unknown)")
else:
checkin_list = _("(unknown)")
if logentry.parsed_data.get('first'):
if data.get('first'):
return _('Position #{posid} has been checked in manually at {datetime} on list "{list}".').format(
posid=logentry.parsed_data.get('positionid'),
posid=data.get('positionid'),
datetime=dt_formatted,
list=checkin_list,
)
return _('Position #{posid} has been checked in again at {datetime} on list "{list}".').format(
posid=logentry.parsed_data.get('positionid'),
posid=data.get('positionid'),
datetime=dt_formatted,
list=checkin_list
)

View File

@@ -172,7 +172,7 @@
{% if form.hidden_if_available %}
{% bootstrap_field form.hidden_if_available layout="control" horizontal_field_class="col-md-7" %}
{% endif %}
{% bootstrap_field form.hidden_if_item_available layout="control" horizontal_field_class="col-md-7" %}
{% bootstrap_field form.hidden_if_item_available visibility_field=form.hidden_if_item_available_mode layout="control_with_visibility" %}
</fieldset>
{% for v in formsets.values %}
<fieldset>

View File

@@ -2,5 +2,5 @@
{% if mode == "hide" %}
<span class="pull-right text-muted unavail-mode-indicator" data-toggle="tooltip" title="{% trans "Hide product if unavailable" %}. {% if f.variation %}{% trans "You can change this option in the variation settings." %}{% else %}{% trans "You can change this option in the product settings." %}{% endif %}"><span class="fa fa-eye-slash"></span></span>
{% else %}
<span class="pull-right text-muted unavail-mode-indicator" data-toggle="tooltip" title="{% trans "Show info text if unavailable" %}. {% if f.variation %}{% trans "You can change this option in the variation settings." %}{% else %}{% trans "You can change this option in the product settings." %}{% endif %}"><span class="fa fa-info-circle"></span></span>
<span class="pull-right text-muted unavail-mode-indicator" data-toggle="tooltip" title="{% trans "Show product with info on why its unavailable" %}. {% if f.variation %}{% trans "You can change this option in the variation settings." %}{% else %}{% trans "You can change this option in the product settings." %}{% endif %}"><span class="fa fa-info-circle"></span></span>
{% endif %}

View File

@@ -491,8 +491,11 @@ class PaymentProviderSettings(EventSettingsViewMixin, EventPermissionRequiredMix
if self.form.is_valid():
if self.form.has_changed():
self.request.event.log_action(
'pretix.event.payment.provider.' + self.provider.identifier, user=self.request.user, data={
k: self.form.cleaned_data.get(k) for k in self.form.changed_data
'pretix.event.payment.provider', user=self.request.user, data={
'provider': self.provider.identifier,
'new_values': {
k: self.form.cleaned_data.get(k) for k in self.form.changed_data
}
}
)
self.form.save()
@@ -888,11 +891,14 @@ class TicketSettings(EventSettingsViewMixin, EventPermissionRequiredMixin, FormV
provider.form.save()
if provider.form.has_changed():
self.request.event.log_action(
'pretix.event.tickets.provider.' + provider.identifier, user=self.request.user, data={
k: (provider.form.cleaned_data.get(k).name
if isinstance(provider.form.cleaned_data.get(k), File)
else provider.form.cleaned_data.get(k))
for k in provider.form.changed_data
'pretix.event.tickets.provider', user=self.request.user, data={
'provider': provider.identifier,
'new_values': {
k: (provider.form.cleaned_data.get(k).name
if isinstance(provider.form.cleaned_data.get(k), File)
else provider.form.cleaned_data.get(k))
for k in provider.form.changed_data
}
}
)
tickets.invalidate_cache.apply_async(kwargs={'event': self.request.event.pk, 'provider': provider.identifier})

View File

@@ -313,7 +313,7 @@ class EventWizard(SafeSessionWizardView):
)
event.set_defaults()
if basics_data['tax_rate']:
if basics_data['tax_rate'] is not None:
if not event.settings.tax_rate_default or event.settings.tax_rate_default.rate != basics_data['tax_rate']:
event.settings.tax_rate_default = event.tax_rules.create(
name=LazyI18nString.from_gettext(gettext('VAT')),

View File

@@ -62,7 +62,8 @@ class MetricsMiddleware(object):
t0 = time.perf_counter()
resp = self.get_response(request)
tdiff = time.perf_counter() - t0
pretix_view_duration_seconds.observe(tdiff, status_code=resp.status_code, method=request.method,
url_name=url.namespace + ':' + url.url_name)
if url.url_name:
pretix_view_duration_seconds.observe(tdiff, status_code=resp.status_code, method=request.method,
url_name=url.namespace + ':' + url.url_name)
return resp

View File

@@ -8,8 +8,8 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-01-21 16:42+0000\n"
"PO-Revision-Date: 2024-11-24 01:00+0000\n"
"Last-Translator: gabriblas <github@unowen.simplelogin.com>\n"
"PO-Revision-Date: 2025-01-25 22:00+0000\n"
"Last-Translator: Rosariocastellana <rosariocastellana@gmail.com>\n"
"Language-Team: Italian <https://translate.pretix.eu/projects/pretix/pretix/"
"it/>\n"
"Language: it\n"
@@ -17,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.8.3\n"
"X-Generator: Weblate 5.9.2\n"
#: pretix/_base_settings.py:87
msgid "English"
@@ -93,7 +93,7 @@ msgstr "Italiano"
#: pretix/_base_settings.py:105
msgid "Japanese"
msgstr ""
msgstr "Giapponese"
#: pretix/_base_settings.py:106
msgid "Latvian"
@@ -3415,6 +3415,8 @@ msgid ""
"The relevant plugin is currently not active. To activate it, click here to "
"go to the plugin settings."
msgstr ""
"Il plugin in questione non è al momento attivo. Per attivarlo, clicca qui "
"per andare alle impostazioni del plugin."
#: pretix/base/logentrytypes.py:50
#, fuzzy
@@ -3425,48 +3427,48 @@ msgstr "Questo prodotto non è al momento disponibile."
#: pretix/base/logentrytypes.py:183
#, python-brace-format
msgid "Order {val}"
msgstr ""
msgstr "Ordine {val}"
#: pretix/base/logentrytypes.py:194
#, python-brace-format
msgid "Voucher {val}…"
msgstr ""
msgstr "Voucher {val}…"
#: pretix/base/logentrytypes.py:205
#, python-brace-format
msgid "Product {val}"
msgstr ""
msgstr "Prodotto {val}"
#: pretix/base/logentrytypes.py:211
#, python-brace-format
msgctxt "subevent"
msgid "Date {val}"
msgstr ""
msgstr "Data {val}"
#: pretix/base/logentrytypes.py:217
#, python-brace-format
msgid "Quota {val}"
msgstr ""
msgstr "Quota {val}"
#: pretix/base/logentrytypes.py:223
#, python-brace-format
msgid "Discount {val}"
msgstr ""
msgstr "Sconto {val}"
#: pretix/base/logentrytypes.py:229
#, python-brace-format
msgid "Category {val}"
msgstr ""
msgstr "Categoria {val}"
#: pretix/base/logentrytypes.py:235
#, python-brace-format
msgid "Question {val}"
msgstr ""
msgstr "Domanda {val}"
#: pretix/base/logentrytypes.py:241
#, python-brace-format
msgid "Tax rule {val}"
msgstr ""
msgstr "Regola fiscale {val}"
#: pretix/base/media.py:71
msgid "Barcode / QR-Code"
@@ -4771,7 +4773,7 @@ msgstr ""
#: pretix/base/models/items.py:455
msgid "Require either an existing or a new medium to be used"
msgstr ""
msgstr "Richiede l'utilizzo di un mezzo esistente o di uno nuovo"
#: pretix/base/models/items.py:471 pretix/base/models/items.py:1446
msgid "Category"

View File

@@ -8,7 +8,7 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-01-21 16:42+0000\n"
"PO-Revision-Date: 2025-01-22 16:00+0000\n"
"PO-Revision-Date: 2025-01-27 10:00+0000\n"
"Last-Translator: Hijiri Umemoto <hijiri@umemoto.org>\n"
"Language-Team: Japanese <https://translate.pretix.eu/projects/pretix/pretix/"
"ja/>\n"
@@ -2956,7 +2956,7 @@ msgstr ""
#: pretix/base/forms/user.py:56 pretix/control/forms/users.py:45
#: pretix/presale/forms/customer.py:283 pretix/presale/forms/customer.py:371
msgid "Please enter the same password twice"
msgstr "同じパスワードを2回入力してください"
msgstr "同じパスワードを回入力してください"
#: pretix/base/forms/auth.py:172 pretix/base/forms/auth.py:224
#: pretix/presale/forms/customer.py:296 pretix/presale/forms/customer.py:390
@@ -3401,13 +3401,12 @@ msgstr "イベントの日程: {date_range}"
msgid ""
"The relevant plugin is currently not active. To activate it, click here to "
"go to the plugin settings."
msgstr ""
msgstr "関連するプラグインは、現在アクティブではありません。アクティブにするには、こ"
"こをクリックしてプラグイン設定に進んでください。"
#: pretix/base/logentrytypes.py:50
#, fuzzy
#| msgid "This product is currently not available."
msgid "The relevant plugin is currently not active."
msgstr "この製品は現在利用できません。"
msgstr "関連するプラグインは、現在無効です。"
#: pretix/base/logentrytypes.py:183
#, python-brace-format
@@ -16353,22 +16352,17 @@ msgid "A user has been removed from the event team."
msgstr "ユーザーがイベントチームから削除されました。"
#: pretix/control/logdisplay.py:721
#, fuzzy
#| msgid "A plugin has been enabled."
msgid "The plugin has been enabled."
msgstr "プラグインが有効になりました。"
#: pretix/control/logdisplay.py:722
#, fuzzy
#| msgid "A plugin has been disabled."
msgid "The plugin has been disabled."
msgstr "プラグインが無効になっています。"
#: pretix/control/logdisplay.py:725
#, fuzzy, python-brace-format
#| msgid "Question {val}"
#, python-brace-format
msgid "Plugin {val}"
msgstr "質問{val}"
msgstr "プラグイン{val}"
#: pretix/control/logdisplay.py:741
msgid "The product has been created."
@@ -29723,10 +29717,9 @@ msgid "An email rule was deleted"
msgstr "電子メールのルールが削除されました"
#: pretix/plugins/sendmail/signals.py:140
#, fuzzy, python-brace-format
#| msgid "Tax rule {val}"
#, python-brace-format
msgid "Mail rule {val}"
msgstr "税金のルール {val}"
msgstr "メールのルール {val}"
#: pretix/plugins/sendmail/templates/pretixplugins/sendmail/history.html:8
msgid ""
@@ -32173,7 +32166,7 @@ msgstr "この製品の価格が自動割引によって削減されました。
#: pretix/presale/templates/pretixpresale/event/fragment_cart.html:346
#, python-format
msgid "%(percent)s %% Discount"
msgstr ""
msgstr "%(percent)s %% 割引"
#: pretix/presale/templates/pretixpresale/event/fragment_cart.html:277
#: pretix/presale/templates/pretixpresale/event/fragment_cart.html:350

View File

@@ -8,8 +8,8 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-01-21 16:42+0000\n"
"PO-Revision-Date: 2024-11-14 20:00+0000\n"
"Last-Translator: Gravity Fox <gravityraposa@gmail.com>\n"
"PO-Revision-Date: 2025-01-28 01:00+0000\n"
"Last-Translator: Cornelius Kibelka <ckibelka-ctr@wikimedia.org>\n"
"Language-Team: Portuguese (Brazil) <https://translate.pretix.eu/projects/"
"pretix/pretix/pt_BR/>\n"
"Language: pt_BR\n"
@@ -17,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n > 1;\n"
"X-Generator: Weblate 5.8.3\n"
"X-Generator: Weblate 5.9.2\n"
#: pretix/_base_settings.py:87
msgid "English"
@@ -93,7 +93,7 @@ msgstr "Italiano"
#: pretix/_base_settings.py:105
msgid "Japanese"
msgstr ""
msgstr "Japonês"
#: pretix/_base_settings.py:106
msgid "Latvian"
@@ -2436,10 +2436,8 @@ msgid "Download a spreadsheet of all payments or refunds of every order."
msgstr "Baixe uma planilha de todos os pagamentos e reembolsos de cada pedido."
#: pretix/base/exporters/orderlist.py:1012
#, fuzzy
#| msgid "Date of last payment"
msgid "Date range (payment date)"
msgstr "Data do último pagamento"
msgstr "Intervalo de datas (data de pagamento)"
#: pretix/base/exporters/orderlist.py:1015
msgid ""
@@ -2450,7 +2448,7 @@ msgstr ""
#: pretix/base/exporters/orderlist.py:1019
msgid "Date range (start of transaction)"
msgstr ""
msgstr "Intervalo de datas (início da transação)"
#: pretix/base/exporters/orderlist.py:1025
msgid "Payment states"
@@ -2576,10 +2574,8 @@ msgid "Current user's carts"
msgstr "Carrinhos de usuários atuais"
#: pretix/base/exporters/orderlist.py:1135
#, fuzzy
#| msgid "Paid orders"
msgid "Exited orders"
msgstr "Ordens pagas"
msgstr "Pedidos encerrados"
#: pretix/base/exporters/orderlist.py:1135
msgid "Current availability"
@@ -2593,22 +2589,19 @@ msgid "Infinite"
msgstr "Infinito"
#: pretix/base/exporters/orderlist.py:1181
#, fuzzy
#| msgid "Gift card"
msgid "Gift card transactions"
msgstr "Cartão Presente"
msgstr "Transações com cartões-presente"
#: pretix/base/exporters/orderlist.py:1183
#: pretix/base/exporters/orderlist.py:1288
#, fuzzy
#| msgid "Gift card"
msgctxt "export_category"
msgid "Gift cards"
msgstr "Cartão Presente"
msgstr "Cartões-presente"
#: pretix/base/exporters/orderlist.py:1184
msgid "Download a spreadsheet of all gift card transactions."
msgstr ""
"Faça o download de uma planilha de todas as transações com cartões-presente."
#: pretix/base/exporters/orderlist.py:1212
#: pretix/base/exporters/orderlist.py:1259
@@ -2619,10 +2612,8 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/organizers/giftcard.html:28
#: pretix/control/templates/pretixcontrol/organizers/giftcards.html:56
#: pretix/presale/templates/pretixpresale/event/fragment_cart.html:156
#, fuzzy
#| msgid "Gift card"
msgid "Gift card code"
msgstr "Cartão Presente"
msgstr "Código do cartão-presente"
#: pretix/base/exporters/orderlist.py:1213
#: pretix/base/exporters/orderlist.py:1302
@@ -2631,7 +2622,7 @@ msgstr "Cartão Presente"
#: pretix/control/forms/filter.py:1413 pretix/control/forms/filter.py:1416
#: pretix/control/templates/pretixcontrol/event/live.html:75
msgid "Test mode"
msgstr ""
msgstr "Modo de teste"
#: pretix/base/exporters/orderlist.py:1218 pretix/base/models/organizer.py:96
#: pretix/control/forms/event.py:110 pretix/control/forms/event.py:116
@@ -2678,41 +2669,41 @@ msgid "TEST MODE"
msgstr "MODO DE TESTE"
#: pretix/base/exporters/orderlist.py:1240
#, fuzzy
#| msgid "Gift card"
msgid "Gift card redemptions"
msgstr "Cartão Presente"
msgstr "Resgates de cartões-presente"
#: pretix/base/exporters/orderlist.py:1242
msgid ""
"Download a spreadsheet of all payments or refunds that involve gift cards."
msgstr ""
"Faça o download de uma planilha de todos os pagamentos ou reembolsos que "
"envolvam cartões-presente."
#: pretix/base/exporters/orderlist.py:1259
#: pretix/control/templates/pretixcontrol/giftcards/payment.html:16
msgid "Issuer"
msgstr ""
msgstr "Emissor"
#: pretix/base/exporters/orderlist.py:1286 pretix/control/navigation.py:538
#: pretix/control/navigation.py:556
#: pretix/control/templates/pretixcontrol/organizers/edit.html:156
#: pretix/plugins/reports/accountingreport.py:898
#, fuzzy
#| msgid "Gift card"
msgid "Gift cards"
msgstr "Cartão Presente"
msgstr "Cartões-presente"
#: pretix/base/exporters/orderlist.py:1289
msgid "Download a spreadsheet of all gift cards including their current value."
msgstr ""
"Faça o download de uma planilha de todos os cartões-presente, incluindo seu "
"valor atual."
#: pretix/base/exporters/orderlist.py:1296
msgid "Show value at"
msgstr ""
msgstr "Mostrar o valor em"
#: pretix/base/exporters/orderlist.py:1299
msgid "Defaults to the time of report."
msgstr ""
msgstr "Por padrão, a hora do relatório."
#: pretix/base/exporters/orderlist.py:1304
#: pretix/base/exporters/orderlist.py:1314 pretix/control/forms/filter.py:518
@@ -2738,20 +2729,20 @@ msgstr "Todos"
#: pretix/base/exporters/orderlist.py:1306 pretix/control/forms/filter.py:1417
msgid "Live"
msgstr ""
msgstr "Em tempo real"
#: pretix/base/exporters/orderlist.py:1315 pretix/control/forms/filter.py:1425
#: pretix/control/templates/pretixcontrol/pdf/index.html:252
msgid "Empty"
msgstr ""
msgstr "Vazio"
#: pretix/base/exporters/orderlist.py:1316 pretix/control/forms/filter.py:1426
msgid "Valid and with value"
msgstr ""
msgstr "Válido e com valor"
#: pretix/base/exporters/orderlist.py:1317 pretix/control/forms/filter.py:1427
msgid "Expired and with value"
msgstr ""
msgstr "Expirado e com valor"
#: pretix/base/exporters/orderlist.py:1318 pretix/control/forms/filter.py:227
#: pretix/control/forms/filter.py:1428 pretix/control/forms/filter.py:2097
@@ -2766,14 +2757,14 @@ msgstr "Expirado"
#: pretix/base/exporters/orderlist.py:1356 pretix/base/models/giftcards.py:98
msgid "Test mode card"
msgstr ""
msgstr "Cartão de modo de teste"
#: pretix/base/exporters/orderlist.py:1358
#: pretix/base/modelimport_orders.py:516 pretix/base/models/giftcards.py:102
#: pretix/control/templates/pretixcontrol/order/index.html:202
#: pretix/control/templates/pretixcontrol/organizers/giftcards.html:62
msgid "Expiry date"
msgstr ""
msgstr "Data de expiração"
#: pretix/base/exporters/orderlist.py:1359 pretix/control/forms/orders.py:875
msgid "Special terms and conditions"
@@ -2792,17 +2783,12 @@ msgid "Created in order"
msgstr "Criado em ordem"
#: pretix/base/exporters/orderlist.py:1363
#, fuzzy
#| msgctxt "invoice"
#| msgid "Invoice number"
msgid "Last invoice number of order"
msgstr "Número da fatura"
msgstr "Último número de fatura do pedido"
#: pretix/base/exporters/orderlist.py:1364
#, fuzzy
#| msgid "Only paid orders"
msgid "Last invoice date of order"
msgstr "Apenas ordens pagas"
msgstr "Data da última fatura do pedido"
#: pretix/base/exporters/reusablemedia.py:34 pretix/control/navigation.py:616
#: pretix/control/templates/pretixcontrol/organizers/edit.html:204
@@ -2830,11 +2816,9 @@ msgid "Media type"
msgstr "Tipo de mídia"
#: pretix/base/exporters/reusablemedia.py:47 pretix/base/models/media.py:73
#, fuzzy
#| msgid "Internal identifier"
msgctxt "reusable_medium"
msgid "Identifier"
msgstr "Identificador interno"
msgstr "Identificador"
#: pretix/base/exporters/reusablemedia.py:49 pretix/base/models/media.py:81
#: pretix/base/models/orders.py:265 pretix/base/models/orders.py:3094
@@ -2846,27 +2830,18 @@ msgstr "Data de validade"
#: pretix/base/exporters/reusablemedia.py:50 pretix/base/models/media.py:90
#: pretix/control/templates/pretixcontrol/order/index.html:215
#: pretix/presale/templates/pretixpresale/event/checkout_confirm.html:132
#, fuzzy
#| msgctxt "refund_source"
#| msgid "Customer"
msgid "Customer account"
msgstr "Cliente"
msgstr "Conta do cliente"
#: pretix/base/exporters/reusablemedia.py:51 pretix/base/models/media.py:97
#, fuzzy
#| msgid "Multiline text"
msgid "Linked ticket"
msgstr "Texto multilinha"
msgstr "Bilhete vinculado"
#: pretix/base/exporters/reusablemedia.py:52 pretix/base/models/media.py:104
#, fuzzy
#| msgid "Gift card"
msgid "Linked gift card"
msgstr "Cartão Presente"
msgstr "Cartão-presente vinculado"
#: pretix/base/exporters/waitinglist.py:42
#, fuzzy
#| msgid "Waiting list"
msgctxt "export_category"
msgid "Waiting list"
msgstr "Lista de espera"
@@ -2874,23 +2849,24 @@ msgstr "Lista de espera"
#: pretix/base/exporters/waitinglist.py:43
msgid "Download a spread sheet with all your waiting list data."
msgstr ""
"Faça o download de uma planilha com todos os dados de sua lista de espera."
#: pretix/base/exporters/waitinglist.py:49
#: pretix/control/templates/pretixcontrol/waitinglist/index.html:102
msgid "All entries"
msgstr ""
msgstr "Todos os registros"
#: pretix/base/exporters/waitinglist.py:54
#: pretix/control/templates/pretixcontrol/waitinglist/index.html:105
msgid "Waiting for a voucher"
msgstr ""
msgstr "Aguardando um voucher"
#: pretix/base/exporters/waitinglist.py:59
#: pretix/control/templates/pretixcontrol/waitinglist/index.html:107
#: pretix/control/templates/pretixcontrol/waitinglist/index.html:227
#: pretix/control/views/waitinglist.py:326
msgid "Voucher assigned"
msgstr ""
msgstr "Voucher atribuído"
#: pretix/base/exporters/waitinglist.py:64
#: pretix/control/templates/pretixcontrol/waitinglist/index.html:110
@@ -2903,7 +2879,7 @@ msgstr "Descrição da categoria"
#: pretix/control/templates/pretixcontrol/waitinglist/index.html:223
#: pretix/control/views/waitinglist.py:322
msgid "Voucher redeemed"
msgstr ""
msgstr "Voucher resgatado"
#: pretix/base/exporters/waitinglist.py:80
#: pretix/control/templates/pretixcontrol/waitinglist/index.html:116
@@ -2918,7 +2894,7 @@ msgstr "O carrinho expirou"
#: pretix/control/forms/event.py:1764
#: pretix/control/templates/pretixcontrol/items/index.html:38
msgid "Product name"
msgstr ""
msgstr "Nome do produto"
#: pretix/base/exporters/waitinglist.py:115
#: pretix/base/modelimport_orders.py:95 pretix/base/modelimport_vouchers.py:60
@@ -2932,7 +2908,7 @@ msgstr "Data"
#: pretix/base/exporters/waitinglist.py:119
#: pretix/control/views/waitinglist.py:308
msgid "Priority"
msgstr ""
msgstr "Prioridade"
#: pretix/base/exporters/waitinglist.py:121
#: pretix/base/modelimport_vouchers.py:39 pretix/base/models/vouchers.py:190
@@ -2951,7 +2927,7 @@ msgstr "Código do voucher"
#: pretix/base/forms/__init__.py:118
#, python-brace-format
msgid "You can use {markup_name} in this field."
msgstr ""
msgstr "Você pode usar {markup_name} nesse campo."
#: pretix/base/forms/__init__.py:178
#, python-format
@@ -2959,6 +2935,8 @@ msgid ""
"Due to technical reasons you cannot set inputs, that need to be masked (e.g. "
"passwords), to %(value)s."
msgstr ""
"Por motivos técnicos, não é possível definir entradas que precisam ser "
"mascaradas (por exemplo, senhas), como %(value)s."
#: pretix/base/forms/auth.py:61 pretix/base/forms/auth.py:179
msgid "Keep me logged in"
@@ -2966,12 +2944,12 @@ msgstr "Mantenha-me conectado"
#: pretix/base/forms/auth.py:65 pretix/base/forms/auth.py:272
msgid "This combination of credentials is not known to our system."
msgstr ""
msgstr "Essa combinação de credenciais não é conhecida pelo nosso sistema."
#: pretix/base/forms/auth.py:66 pretix/base/forms/user.py:57
#: pretix/presale/forms/customer.py:372 pretix/presale/forms/customer.py:444
msgid "For security reasons, please wait 5 minutes before you try again."
msgstr ""
msgstr "Por motivos de segurança, aguarde 5 minutos antes de tentar novamente."
#: pretix/base/forms/auth.py:67 pretix/base/forms/auth.py:273
msgid "This account is inactive."
@@ -3018,35 +2996,43 @@ msgstr "Referência interna"
#: pretix/base/forms/questions.py:320
msgctxt "phonenumber"
msgid "Phone number (without international area code)"
msgstr ""
msgstr "Número de telefone (sem código de área internacional)"
#: pretix/base/forms/questions.py:481
msgid ""
"You uploaded an image in landscape orientation. Please upload an image in "
"portrait orientation."
msgstr ""
"Você fez o upload de uma imagem na orientação paisagem. Carregue uma imagem "
"na orientação retrato."
#: pretix/base/forms/questions.py:484
msgid "Please upload an image where the width is 3/4 of the height."
msgstr ""
msgstr "Carregue uma imagem em que a largura seja 3/4 da altura."
#: pretix/base/forms/questions.py:487
msgid ""
"The file you uploaded has a very large number of pixels, please upload an "
"image no larger than 10000 x 10000 pixels."
msgstr ""
"O arquivo que você carregou tem um número muito grande de pixels. Carregue "
"uma imagem que não seja maior que 10.000 x 10.000 pixels."
#: pretix/base/forms/questions.py:490 pretix/helpers/images.py:75
msgid ""
"Upload a valid image. The file you uploaded was either not an image or a "
"corrupted image."
msgstr ""
"Faça upload de uma imagem válida. O arquivo que você carregou não era uma "
"imagem ou era uma imagem corrompida."
#: pretix/base/forms/questions.py:633 pretix/base/forms/questions.py:642
msgid ""
"If you keep this empty, the ticket will be valid starting at the time of "
"purchase."
msgstr ""
"Se você mantiver esse campo vazio, o bilhete será válido a partir do momento "
"da compra."
#: pretix/base/forms/questions.py:689 pretix/base/forms/questions.py:1014
msgid "Street and Number"
@@ -3064,16 +3050,20 @@ msgid ""
"Optional, but depending on the country you reside in we might need to charge "
"you additional taxes if you do not enter it."
msgstr ""
"Opcional, mas, dependendo do país em que você reside, talvez seja necessário "
"cobrar impostos adicionais se você não os inserir."
#: pretix/base/forms/questions.py:1071 pretix/base/forms/questions.py:1077
msgid "If you are registered in Switzerland, you can enter your UID instead."
msgstr ""
msgstr "Se você estiver registrado na Suíça, poderá inserir seu UID."
#: pretix/base/forms/questions.py:1075
msgid ""
"Optional, but it might be required for you to claim tax benefits on your "
"invoice depending on your and the sellers country of residence."
msgstr ""
"Opcional, mas pode ser necessário para que você solicite benefícios fiscais "
"em sua fatura, dependendo do seu país de residência e do país do vendedor."
#: pretix/base/forms/questions.py:1174
msgid "You need to provide a company name."
@@ -3109,7 +3099,7 @@ msgstr "Senha incorreta."
#: pretix/base/forms/user.py:58
msgid "Please choose a password different to your current one."
msgstr ""
msgstr "Escolha uma senha diferente da atual."
#: pretix/base/forms/user.py:63 pretix/presale/forms/customer.py:379
#: pretix/presale/forms/customer.py:448
@@ -3164,6 +3154,10 @@ msgid ""
"up. Please note: to use literal \"{\" or \"}\", you need to double them as "
"\"{{\" and \"}}\"."
msgstr ""
"Há um erro na sintaxe de seu placeholder. Verifique se os colchetes “{” de "
"abertura e “}” de fechamento em seus placeholders estão corretos. Observação:"
" para usar literalmente “{” ou “}”, você precisa duplicá-los como “{{” e "
"“}}”."
#: pretix/base/forms/validators.py:72 pretix/control/views/event.py:758
#, fuzzy, python-format
@@ -3348,7 +3342,7 @@ msgstr "Montante"
#, python-brace-format
msgctxt "invoice"
msgid "Single price: {net_price} net / {gross_price} gross"
msgstr ""
msgstr "Preço único: {net_price} líquido / {gross_price} bruto"
#: pretix/base/invoice.py:668
#, fuzzy, python-brace-format
@@ -3372,7 +3366,7 @@ msgstr "Ordens pagas"
#: pretix/base/invoice.py:707
msgctxt "invoice"
msgid "Outstanding payments"
msgstr ""
msgstr "Pagamentos pendentes"
#: pretix/base/invoice.py:724
#, fuzzy
@@ -3438,7 +3432,7 @@ msgstr ""
#: pretix/base/invoice.py:867
msgid "Default invoice renderer (European-style letter)"
msgstr ""
msgstr "Renderizador de faturas padrão (letra no estilo europeu)"
#: pretix/base/invoice.py:956
#, fuzzy
@@ -3450,7 +3444,7 @@ msgstr "Você precisa selecionar uma data."
#: pretix/base/invoice.py:1003
msgid "Simplified invoice renderer"
msgstr ""
msgstr "Renderizador de faturas simplificado"
#: pretix/base/invoice.py:1022
#, fuzzy, python-brace-format
@@ -3465,6 +3459,8 @@ msgid ""
"The relevant plugin is currently not active. To activate it, click here to "
"go to the plugin settings."
msgstr ""
"O plug-in relevante não está ativo no momento. Para ativá-lo, clique aqui "
"para acessar as configurações do plug-in."
#: pretix/base/logentrytypes.py:50
#, fuzzy
@@ -3496,7 +3492,7 @@ msgstr "Data {val}"
#: pretix/base/logentrytypes.py:217
#, python-brace-format
msgid "Quota {val}"
msgstr ""
msgstr "Contingente {val}"
#: pretix/base/logentrytypes.py:223
#, fuzzy, python-brace-format
@@ -3521,12 +3517,12 @@ msgstr "Norma fiscal {val}"
#: pretix/base/media.py:71
msgid "Barcode / QR-Code"
msgstr ""
msgstr "Código de barras / código QR"
#: pretix/base/media.py:88
#: pretix/control/templates/pretixcontrol/organizers/edit.html:237
msgid "NFC UID-based"
msgstr ""
msgstr "Baseado em NFC UID"
#: pretix/base/migrations/0077_auto_20171124_1629.py:33
#: pretix/base/migrations/0077_auto_20171124_1629_squashed_0088_auto_20180328_1217.py:35
@@ -3535,28 +3531,28 @@ msgstr "Lista padrão"
#: pretix/base/modelimport.py:112
msgid "Keep empty"
msgstr ""
msgstr "Manter vazio"
#: pretix/base/modelimport.py:139
#, python-brace-format
msgid "Invalid setting for column \"{header}\"."
msgstr ""
msgstr "Configuração inválida para a coluna “{header}”."
#: pretix/base/modelimport.py:199
#, python-brace-format
msgid "Could not parse {value} as a yes/no value."
msgstr ""
msgstr "Não foi possível analisar {value} como um valor sim/não."
#: pretix/base/modelimport.py:222
#, python-brace-format
msgid "Could not parse {value} as a date and time."
msgstr ""
msgstr "Não foi possível analisar {value} como uma data e hora."
#: pretix/base/modelimport.py:232 pretix/control/views/orders.py:1162
#: pretix/control/views/orders.py:1191 pretix/control/views/orders.py:1235
#: pretix/control/views/orders.py:1270 pretix/control/views/orders.py:1293
msgid "You entered an invalid number."
msgstr ""
msgstr "Você inseriu um número inválido."
#: pretix/base/modelimport.py:279 pretix/base/modelimport.py:291
#, fuzzy
@@ -3568,7 +3564,7 @@ msgstr "Nenhum evento arquivado encontrado."
#: pretix/base/modelimport.py:281 pretix/base/modelimport.py:293
msgctxt "subevent"
msgid "Multiple matching dates were found."
msgstr ""
msgstr "Foram encontradas várias datas correspondentes."
#: pretix/base/modelimport_orders.py:85
#, fuzzy
@@ -3591,7 +3587,7 @@ msgstr "Nenhum evento arquivado encontrado."
#: pretix/base/modelimport_orders.py:130
#: pretix/base/modelimport_vouchers.py:196
msgid "Multiple matching products were found."
msgstr ""
msgstr "Foram encontrados vários produtos correspondentes."
#: pretix/base/modelimport_orders.py:139
#: pretix/base/modelimport_vouchers.py:205 pretix/base/models/items.py:1245
@@ -3611,7 +3607,7 @@ msgstr "Informações da conta alteradas"
#: pretix/base/modelimport_vouchers.py:227
#: pretix/base/modelimport_vouchers.py:261
msgid "Multiple matching variations were found."
msgstr ""
msgstr "Foram encontradas diversas variações correspondentes."
#: pretix/base/modelimport_orders.py:164
#, fuzzy
@@ -3700,7 +3696,7 @@ msgstr "Tipo de dispositivo"
#: pretix/base/modelimport_orders.py:460
msgid "You cannot assign a position secret that already exists."
msgstr ""
msgstr "Não é possível atribuir um segredo de posição que já exista."
#: pretix/base/modelimport_orders.py:491
#, fuzzy
@@ -3788,7 +3784,7 @@ msgstr "Um voucher com esse código já existe."
#: pretix/base/models/vouchers.py:196 pretix/control/views/vouchers.py:120
#: pretix/presale/templates/pretixpresale/organizers/customer_membership.html:52
msgid "Maximum usages"
msgstr ""
msgstr "Usos máximos"
#: pretix/base/modelimport_vouchers.py:79
#, fuzzy
@@ -3814,20 +3810,22 @@ msgstr "Quantidade máxima por pedido"
#: pretix/base/modelimport_vouchers.py:119 pretix/base/models/vouchers.py:225
#: pretix/control/forms/filter.py:2106
msgid "Reserve ticket from quota"
msgstr ""
msgstr "Reservar bilhete da cota"
#: pretix/base/modelimport_vouchers.py:127 pretix/base/models/vouchers.py:233
msgid "Allow to bypass quota"
msgstr ""
msgstr "Permitir ignorar a cota"
#: pretix/base/modelimport_vouchers.py:135 pretix/base/models/vouchers.py:239
msgid "Price mode"
msgstr ""
msgstr "Modo de preço"
#: pretix/base/modelimport_vouchers.py:150
#, python-brace-format
msgid "Could not parse {value} as a price mode, use one of {options}."
msgstr ""
"Não foi possível analisar {value} como um modo de preço, use uma das "
"{options}."
#: pretix/base/modelimport_vouchers.py:160 pretix/base/models/vouchers.py:245
msgid "Voucher value"
@@ -3835,18 +3833,18 @@ msgstr "Valor do voucher"
#: pretix/base/modelimport_vouchers.py:165
msgid "It is pointless to set a value without a price mode."
msgstr ""
msgstr "Não faz sentido definir um valor sem um modo de preço."
#: pretix/base/modelimport_vouchers.py:237 pretix/base/models/items.py:2081
#: pretix/base/models/vouchers.py:272
#: pretix/control/templates/pretixcontrol/items/quota_edit.html:8
#: pretix/control/templates/pretixcontrol/items/quota_edit.html:15
msgid "Quota"
msgstr ""
msgstr "Cota"
#: pretix/base/modelimport_vouchers.py:253
msgid "You cannot specify a quota if you specified a product."
msgstr ""
msgstr "Não é possível especificar uma cota se você especificou um produto."
#: pretix/base/modelimport_vouchers.py:282 pretix/base/models/vouchers.py:495
#, fuzzy
@@ -3869,11 +3867,9 @@ msgid "Seat-specific vouchers can only be used once."
msgstr "Este produto não será vendido após a data indicada."
#: pretix/base/modelimport_vouchers.py:306 pretix/base/models/vouchers.py:519
#, fuzzy, python-brace-format
#| msgctxt "subevent"
#| msgid "You need to select a date."
#, python-brace-format
msgid "You need to choose the product \"{prod}\" for this seat."
msgstr "Você precisa selecionar uma data."
msgstr "Você precisa escolher o produto “{prod}” para esse assento."
#: pretix/base/modelimport_vouchers.py:318 pretix/base/models/vouchers.py:285
#: pretix/control/templates/pretixcontrol/vouchers/index.html:129
@@ -3884,17 +3880,20 @@ msgstr "Tag"
#: pretix/base/modelimport_vouchers.py:334 pretix/base/models/vouchers.py:297
msgid "Shows hidden products that match this voucher"
msgstr ""
msgstr "Mostra produtos ocultos que correspondem a esse voucher"
#: pretix/base/modelimport_vouchers.py:343 pretix/base/models/vouchers.py:301
msgid "Offer all add-on products for free when redeeming this voucher"
msgstr ""
"Ofereça todos os produtos adicionais gratuitamente ao resgatar este voucher"
#: pretix/base/modelimport_vouchers.py:351 pretix/base/models/vouchers.py:305
msgid ""
"Include all bundled products without a designated price when redeeming this "
"voucher"
msgstr ""
"Inclua todos os produtos incluídos em um pacote sem um preço designado ao "
"resgatar esse voucher"
#: pretix/base/models/auth.py:248
msgid "Is active"
@@ -3968,22 +3967,28 @@ msgid ""
"and valid for check-in regardless of which date they are purchased for. You "
"can limit their validity through the advanced check-in rules, though."
msgstr ""
"Se você escolher “todas as datas”, os bilhetes serão considerados parte "
"dessa lista e válidos para o check-in, independentemente da data para a qual "
"foram comprados. No entanto, você pode limitar a validade deles por meio das "
"regras avançadas de check-in."
#: pretix/base/models/checkin.py:65
msgctxt "checkin"
msgid "Ignore check-ins on this list in statistics"
msgstr ""
msgstr "Ignorar os check-ins dessa lista nas estatísticas"
#: pretix/base/models/checkin.py:69
msgctxt "checkin"
msgid "Tickets with a check-in on this list should be considered \"used\""
msgstr ""
msgstr "Os bilhetes com um check-in nessa lista devem ser considerados “usados”"
#: pretix/base/models/checkin.py:70
msgid ""
"This is relevant in various situations, e.g. for deciding if a ticket can "
"still be canceled by the customer."
msgstr ""
"Isso é relevante em várias situações, por exemplo, para decidir se um "
"bilhete ainda pode ser cancelado pelo cliente."
#: pretix/base/models/checkin.py:74
msgctxt "checkin"
@@ -4007,6 +4012,8 @@ msgstr ""
#: pretix/base/models/checkin.py:79
msgid "Allow checking in add-on tickets by scanning the main ticket"
msgstr ""
"Permitir o check-in de bilhetes adicionais por meio da leitura do bilhete "
"principal"
#: pretix/base/models/checkin.py:81
msgid ""
@@ -4014,6 +4021,9 @@ msgid ""
"there is always exactly one matching add-on ticket. Ambiguous scans will be "
"rejected.."
msgstr ""
"Um escaneamento só será possível se a lista de check-in estiver configurada "
"de forma que sempre haja exatamente um bilhete complementar correspondente. "
"Os escaneamentos ambíguos serão rejeitados..."
#: pretix/base/models/checkin.py:85 pretix/control/navigation.py:640
#: pretix/control/templates/pretixcontrol/organizers/gates.html:5
@@ -4028,19 +4038,23 @@ msgid ""
"Does not have any effect for the validation of tickets, only for the "
"automatic configuration of check-in devices."
msgstr ""
"Não tem efeito na validação de bilhetes, apenas na configuração automática "
"dos dispositivos de check-in."
#: pretix/base/models/checkin.py:90
msgid "Allow re-entering after an exit scan"
msgstr ""
msgstr "Permitir o reingresso após um escaneamento de saída"
#: pretix/base/models/checkin.py:94
msgid "Allow multiple entries per ticket"
msgstr ""
msgstr "Permitir várias entradas por bilhete"
#: pretix/base/models/checkin.py:95
msgid ""
"Use this option to turn off warnings if a ticket is scanned a second time."
msgstr ""
"Use essa opção para desativar os avisos se um bilhete for escaneado uma "
"segunda vez."
#: pretix/base/models/checkin.py:99
#, fuzzy
@@ -4056,7 +4070,7 @@ msgstr "País"
#: pretix/base/models/checkin.py:337
msgid "Exit"
msgstr ""
msgstr "Saída"
#: pretix/base/models/checkin.py:355
#, fuzzy

View File

@@ -51,8 +51,8 @@ def register_payment_provider(sender, **kwargs):
class PaypalEventLogEntryType(EventLogEntryType):
action_type = 'pretix.plugins.paypal.event'
def display(self, logentry):
event_type = logentry.parsed_data.get('event_type')
def display(self, logentry, data):
event_type = data.get('event_type')
text = None
plains = {
'PAYMENT.SALE.COMPLETED': _('Payment completed.'),

View File

@@ -5,6 +5,10 @@
<div class="col-md-2 col-sm-3 col-xs-6 availability-box unavailable">
<p><small><a href="#voucher">{% trans "Enter a voucher code below to buy this product." %}</a></small></p>
</div>
{% elif item.current_unavailability_reason == 'hidden_if_item_available' %}
<div class="col-md-2 col-sm-3 col-xs-6 availability-box unavailable">
<p><small>{% trans "Not available yet." %}</small></p>
</div>
{% elif item.current_unavailability_reason == 'available_from' or var.current_unavailability_reason == 'available_from' %}
<div class="col-md-2 col-sm-3 col-xs-6 availability-box unavailable">
<p><small>{% trans "Not available yet." %}</small></p>

View File

@@ -70,7 +70,7 @@ from pretix.base.models import (
)
from pretix.base.models.event import Event, SubEvent
from pretix.base.models.items import (
ItemAddOn, ItemBundle, SubEventItem, SubEventItemVariation,
Item, ItemAddOn, ItemBundle, SubEventItem, SubEventItemVariation,
)
from pretix.base.services.placeholders import PlaceholderContext
from pretix.base.services.quotas import QuotaAvailability
@@ -302,14 +302,14 @@ def get_grouped_items(event, *, channel: SalesChannel, subevent=None, voucher=No
if item.hidden_if_item_available:
if item.hidden_if_item_available.has_variations:
dependency_available = any(
item._dependency_available = any(
var.check_quotas(subevent=subevent, _cache=quota_cache, include_bundled=True)[0] == Quota.AVAILABILITY_OK
for var in item.hidden_if_item_available.available_variations
)
else:
q = item.hidden_if_item_available.check_quotas(subevent=subevent, _cache=quota_cache, include_bundled=True)
dependency_available = q[0] == Quota.AVAILABILITY_OK
if dependency_available:
item._dependency_available = q[0] == Quota.AVAILABILITY_OK
if item._dependency_available and item.hidden_if_item_available_mode == Item.UNAVAIL_MODE_HIDDEN:
item._remove = True
continue

View File

@@ -38,6 +38,7 @@ var strings = {
'unavailable_available_from': django.pgettext('widget', 'Not yet available'),
'unavailable_available_until': django.pgettext('widget', 'Not available anymore'),
'unavailable_active': django.pgettext('widget', 'Currently not available'),
'unavailable_hidden_if_item_available': django.pgettext('widget', 'Not yet available'),
'order_min': django.pgettext('widget', 'minimum amount to order: %s'),
'exit': django.pgettext('widget', 'Close ticket shop'),
'loading_error': django.pgettext('widget', 'The ticket shop could not be loaded.'),
@@ -229,7 +230,7 @@ Vue.component('availbox', {
+ ' v-bind:aria-label="label_select_item"'
+ '>'
+ '</label>'
+ '<div :class="count_group_classes" v-else>'
+ '<div :class="count_group_classes" v-else role="group" v-bind:aria-label="item.name">'
+ '<button v-if="!$root.use_native_spinners" type="button" @click.prevent.stop="on_step" data-step="-1" v-bind:data-controls="\'input_\' + input_name" class="pretix-widget-btn-default pretix-widget-item-count-dec" aria-label="' + strings.quantity_dec + '"><span>-</span></button>'
+ '<input type="number" inputmode="numeric" pattern="\d*" class="pretix-widget-item-count-multiple" placeholder="0" min="0"'
+ ' v-model="amount_selected" :max="order_max" :name="input_name" :id="\'input_\' + input_name"'
@@ -722,7 +723,6 @@ var shared_methods = {
},
buy_callback: function (data) {
if (data.redirect) {
var iframe = this.$root.overlay.$children[0].$refs['frame-container'].children[0];
if (data.cart_id) {
this.$root.cart_id = data.cart_id;
setCookie(this.$root.cookieName, data.cart_id, 30);
@@ -747,7 +747,7 @@ var shared_methods = {
}
this.$root.overlay.frame_loading = false;
} else {
iframe.src = url;
this.$root.overlay.frame_src = url;
}
} else {
this.async_task_id = data.async_id;
@@ -786,9 +786,7 @@ var shared_methods = {
if (this.$root.additionalURLParams) {
redirect_url += '&' + this.$root.additionalURLParams;
}
var iframe = this.$root.overlay.$children[0].$refs['frame-container'].children[0];
this.$root.overlay.frame_loading = true;
iframe.src = redirect_url;
this.$root.overlay.frame_src = redirect_url;
},
voucher_open: function (voucher) {
var redirect_url;
@@ -800,9 +798,7 @@ var shared_methods = {
redirect_url += '&' + this.$root.additionalURLParams;
}
if (this.$root.useIframe) {
var iframe = this.$root.overlay.$children[0].$refs['frame-container'].children[0];
this.$root.overlay.frame_loading = true;
iframe.src = redirect_url;
this.$root.overlay.frame_src = redirect_url;
} else {
window.open(redirect_url);
}
@@ -825,9 +821,7 @@ var shared_methods = {
redirect_url += '&' + this.$root.additionalURLParams;
}
if (this.$root.useIframe) {
var iframe = this.$root.overlay.$children[0].$refs['frame-container'].children[0];
this.$root.overlay.frame_loading = true;
iframe.src = redirect_url;
this.$root.overlay.frame_src = redirect_url;
} else {
window.open(redirect_url);
}
@@ -852,27 +846,27 @@ var shared_loading_fragment = (
);
var shared_iframe_fragment = (
'<div :class="frameClasses">'
'<div :class="frameClasses" role="dialog" aria-modal="true" >'
+ '<div class="pretix-widget-frame-loading" v-show="$root.frame_loading">'
+ '<svg width="256" height="256" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path class="pretix-widget-primary-color" d="M1152 896q0-106-75-181t-181-75-181 75-75 181 75 181 181 75 181-75 75-181zm512-109v222q0 12-8 23t-20 13l-185 28q-19 54-39 91 35 50 107 138 10 12 10 25t-9 23q-27 37-99 108t-94 71q-12 0-26-9l-138-108q-44 23-91 38-16 136-29 186-7 28-36 28h-222q-14 0-24.5-8.5t-11.5-21.5l-28-184q-49-16-90-37l-141 107q-10 9-25 9-14 0-25-11-126-114-165-168-7-10-7-23 0-12 8-23 15-21 51-66.5t54-70.5q-27-50-41-99l-183-27q-13-2-21-12.5t-8-23.5v-222q0-12 8-23t19-13l186-28q14-46 39-92-40-57-107-138-10-12-10-24 0-10 9-23 26-36 98.5-107.5t94.5-71.5q13 0 26 10l138 107q44-23 91-38 16-136 29-186 7-28 36-28h222q14 0 24.5 8.5t11.5 21.5l28 184q49 16 90 37l142-107q9-9 24-9 13 0 25 10 129 119 165 170 7 8 7 22 0 12-8 23-15 21-51 66.5t-54 70.5q26 50 41 98l183 28q13 2 21 12.5t8 23.5z"/></svg>'
+ '</div>'
+ '<div class="pretix-widget-frame-inner" ref="frame-container" v-show="$root.frame_shown">'
+ '<div class="pretix-widget-frame-close"><a href="#" @click.prevent.stop="close" role="button" aria-label="'+strings.close+'">'
+ '<svg height="16" viewBox="0 0 512 512" width="16" xmlns="http://www.w3.org/2000/svg"><path fill="#fff" d="M437.5,386.6L306.9,256l130.6-130.6c14.1-14.1,14.1-36.8,0-50.9c-14.1-14.1-36.8-14.1-50.9,0L256,205.1L125.4,74.5 c-14.1-14.1-36.8-14.1-50.9,0c-14.1,14.1-14.1,36.8,0,50.9L205.1,256L74.5,386.6c-14.1,14.1-14.1,36.8,0,50.9 c14.1,14.1,36.8,14.1,50.9,0L256,306.9l130.6,130.6c14.1,14.1,36.8,14.1,50.9,0C451.5,423.4,451.5,400.6,437.5,386.6z"/></svg>'
+ '</a></div>'
+ '<iframe frameborder="0" width="650" height="650" @load="iframeLoaded" '
+ ' :name="$root.parent.widget_id" src="about:blank" v-once'
+ ' allow="autoplay *; camera *; fullscreen *; payment *"'
+ ' referrerpolicy="origin">'
+ 'Please enable frames in your browser!'
+ '</iframe>'
+ '<div class="pretix-widget-frame-close"><a href="#" @click.prevent.stop="close" role="button" aria-label="'+strings.close+'">'
+ '<svg height="16" viewBox="0 0 512 512" width="16" xmlns="http://www.w3.org/2000/svg"><path fill="#fff" d="M437.5,386.6L306.9,256l130.6-130.6c14.1-14.1,14.1-36.8,0-50.9c-14.1-14.1-36.8-14.1-50.9,0L256,205.1L125.4,74.5 c-14.1-14.1-36.8-14.1-50.9,0c-14.1,14.1-14.1,36.8,0,50.9L205.1,256L74.5,386.6c-14.1,14.1-14.1,36.8,0,50.9 c14.1,14.1,36.8,14.1,50.9,0L256,306.9l130.6,130.6c14.1,14.1,36.8,14.1,50.9,0C451.5,423.4,451.5,400.6,437.5,386.6z"/></svg>'
+ '</a></div>'
+ '</div>'
+ '</div>'
);
var shared_alert_fragment = (
'<div :class="alertClasses">'
+ '<transition name="bounce">'
'<div :class="alertClasses" role="dialog" aria-modal="true" aria-live="polite">'
+ '<transition name="bounce" @after-enter="focusButton">'
+ '<div class="pretix-widget-alert-box" v-if="$root.error_message">'
+ '<p>{{ $root.error_message }}</p>'
+ '<p><button v-if="$root.error_url_after" @click.prevent.stop="errorContinue">' + strings.continue + '</button>'
@@ -965,8 +959,7 @@ Vue.component('pretix-overlay', {
window.open(this.$root.error_url_after);
return;
}
var iframe = this.$refs['frame-container'].children[0];
iframe.src = this.$root.error_url_after;
this.$root.overlay.frame_src = this.$root.error_url_after;
this.$root.frame_loading = true;
this.$root.error_message = null;
this.$root.error_url_after = null;
@@ -974,15 +967,22 @@ Vue.component('pretix-overlay', {
close: function () {
this.$root.frame_shown = false;
this.$root.parent.frame_dismissed = true;
this.$root.frame_src = "";
this.$root.parent.reload();
this.$root.parent.trigger_close_callback();
},
iframeLoaded: function () {
if (this.$root.frame_loading) {
this.$root.frame_loading = false;
this.$root.frame_shown = true;
if (this.$root.frame_src) {
this.$root.frame_shown = true;
}
}
}
},
focusButton: function () {
this.$el.querySelector(".pretix-widget-alert-box button").focus();
},
}
});
@@ -1737,8 +1737,7 @@ var shared_root_methods = {
} else {
url += '?iframe=1';
}
this.$root.overlay.$children[0].$refs['frame-container'].children[0].src = url;
this.$root.overlay.frame_loading = true;
this.$root.overlay.frame_src = url;
} else {
event.target.href = url;
return;
@@ -1901,9 +1900,7 @@ var shared_root_methods = {
redirect_url += '&' + this.$root.additionalURLParams;
}
if (this.$root.useIframe) {
var iframe = this.$root.overlay.$children[0].$refs['frame-container'].children[0];
this.$root.overlay.frame_loading = true;
iframe.src = redirect_url;
this.$root.overlay.frame_src = redirect_url;
} else {
window.open(redirect_url);
}
@@ -1927,9 +1924,7 @@ var shared_root_methods = {
redirect_url += '&' + this.$root.additionalURLParams;
}
if (this.$root.useIframe) {
var iframe = this.$root.overlay.$children[0].$refs['frame-container'].children[0];
this.$root.overlay.frame_loading = true;
iframe.src = redirect_url;
this.$root.overlay.frame_src = redirect_url;
} else {
window.open(redirect_url);
}
@@ -2066,9 +2061,41 @@ var create_overlay = function (app) {
error_url_after_new_tab: true,
error_message: null,
lightbox: null,
prevActiveElement: null,
}
},
props: {
frame_src: String,
},
methods: {
},
watch: {
frame_src: function (newValue, oldValue) {
// show loading spinner only when previously no frame_src was set
if (newValue && !oldValue) {
this.frame_loading = true;
}
// to close and unload the iframe, frame_src can be empty -> make it valid HTML with about:blank
this.$el.querySelector("iframe").src = newValue || "about:blank";
},
frame_shown: function (newValue) {
if (newValue) {
this.prevActiveElement = document.activeElement;
var btn = this.$el?.querySelector(".pretix-widget-frame-close a");
this.$nextTick(function () {
btn?.focus();
});
} else {
this.prevActiveElement?.focus();
}
},
error_message: function (newValue) {
if (newValue) {
this.prevActiveElement = document.activeElement;
} else {
this.prevActiveElement?.focus();
}
},
}
});
app.$root.overlay = framechild;

View File

@@ -323,6 +323,7 @@ TEST_ITEM_RES = {
"max_per_order": None,
"hidden_if_available": None,
"hidden_if_item_available": None,
"hidden_if_item_available_mode": "hide",
"checkin_attention": False,
"checkin_text": None,
"has_variations": False,