Compare commits

..

3 Commits

Author SHA1 Message Date
Raphael Michel
039a00edd4 Bump to 4.3.1 2022-01-26 13:43:38 +01:00
Raphael Michel
a3fb4f7868 [SECURITY] Make redirect view dependent on referer 2022-01-26 13:43:33 +01:00
Raphael Michel
7a3a6399e9 [SECURITY] Fix (non-exploitable) XSS issue 2022-01-26 13:43:33 +01:00
224 changed files with 44775 additions and 107120 deletions

View File

@@ -1,40 +0,0 @@
.. _`admin-errors`:
Dealing with errors
===================
If you encounter an error in pretix, please follow the following steps to debug it:
* If the error message is shown on a **white page** and the last line of the error includes "nginx", the error is not with pretix
directly but with your nginx webserver. This might mean that pretix is not running, but it could also be something else.
Please first check your nginx error log. The default location is ``/var/log/nginx/error.log``.
* If it turns out pretix is not running, check the output of ``docker logs pretix`` for a docker installation and
``journalctl -u pretix-web.service`` for a manual installation.
* If the error message is an "**Internal Server Error**" in purple pretix design, please check pretix' log file which by default is at
``/var/pretix-data/logs/pretix.log`` if you installed with docker and ``/var/pretix/data/logs/pretix.log`` otherwise. If you don't
know how to interpret it, open a discussion on GitHub with the relevant parts of the log file.
* If the error message includes ``/usr/bin/env: node: No such file or directory``, you forgot to install ``node.js``
* If the error message includes ``OfflineGenerationError``, you might have forgot to run the ``rebuild`` step after a pretix update
or plugin installation.
* If the error message mentions your database server or redis server, make sure these are running and accessible.
* If pretix loads fine but certain actions (creating carts, orders, or exports, downloading tickets, sending emails) **take forever**,
``pretix-worker`` is not running. Check the output of ``docker logs pretix`` for a docker installation and
``journalctl -u pretix-worker.service`` for a manual installation.
* If the page loads but all **styles are missing**, you probably forgot to update your nginx configuration file after an upgrade of your
operating system's python version.
If you are unable to debug the issue any further, please open a **discussion** on GitHub in our `Q&A Forum`_. Do **not** open an issue
right away, since most things turn out not to be a bug in pretix but a mistake in your server configuration. Make sure to include
relevant log excerpts in your question.
If you're a pretix Enterprise customer, you can also reach out to support@pretix.eu with your issue right away.
.. _Q&A Forum: https://github.com/pretix/pretix/discussions/categories/q-a

View File

@@ -9,9 +9,7 @@ This documentation is for everyone who wants to install pretix on a server.
:maxdepth: 2
installation/index
updates
config
maintainance
scaling
errors
indexes

View File

@@ -50,7 +50,7 @@ Here is the currently recommended set of commands::
CREATE INDEX CONCURRENTLY pretix_addidx_orderpos_name
ON pretixbase_orderposition
USING gin (upper("attendee_name_cached") gin_trgm_ops);
CREATE INDEX CONCURRENTLY pretix_addidx_orderpos_secret
CREATE INDEX CONCURRENTLY pretix_addidx_orderpos_scret
ON pretixbase_orderposition
USING gin (upper("secret") gin_trgm_ops);
CREATE INDEX CONCURRENTLY pretix_addidx_orderpos_email

View File

@@ -256,8 +256,6 @@ create an event and start selling tickets!
You should probably read :ref:`maintainance` next.
.. _`docker_updates`:
Updates
-------
@@ -273,8 +271,6 @@ Restarting the service can take a few seconds, especially if the update requires
Replace ``stable`` above with a specific version number like ``1.0`` or with ``latest`` for the development
version, if you want to.
Make sure to also read :ref:`update_notes` and the release notes of the version you are updating to.
.. _`docker_plugininstall`:
Install a plugin

View File

@@ -280,8 +280,6 @@ create an event and start selling tickets!
You should probably read :ref:`maintainance` next.
.. _`manual_updates`:
Updates
-------
@@ -296,7 +294,6 @@ To upgrade to a new pretix release, pull the latest code changes and run the fol
(venv)$ python -m pretix updatestyles
# systemctl restart pretix-web pretix-worker
Make sure to also read :ref:`update_notes` and the release notes of the version you are updating to.
.. _`manual_plugininstall`:

View File

@@ -9,8 +9,6 @@ If you host your own pretix instance, you also need to care about the availabili
of your service and the safety of your data yourself. This page gives you some
information that you might need to do so properly.
.. _`backups`:
Backups
-------

View File

@@ -1,44 +0,0 @@
.. _`update_notes`:
Update notes
============
pretix receives regular feature and bugfix updates and we highly encourage you to always update to
the latest version for maximum quality and security. Updates are announces on our `blog`_. There are
usually 10 feature updates in a year, so you can expect a new release almost every month.
Pure bugfix releases are only issued in case of very critical bugs or security vulnerabilities. In these
case, we'll publish bugfix releases for the last three stable release branches.
Compatibility to plugins and in very rare cases API clients may break. For in-depth details on the
API changes of every version, please refer to the release notes published on our blog.
Upgrade steps
-------------
For the actual upgrade, you can usually just follow the steps from the installation guide for :ref:`manual installations <manual_updates>`
or :ref:`docker installations <docker_updates>` respectively.
Generally, it is always strongly recommended to perform a :ref:`backup <backups>` first.
It is possible to skip versions during updates, although we recommend not skipping over major version numbers
(i.e. if you want to go from 2.4 to 4.4, first upgrade to 3.0, then upgrade to 4.0, then to 4.4).
In addition to these standard update steps, the following list issues steps that should be taken when you upgrade
to specific versions for pretix. If you're skipping versions, please read the instructions for every version in
between as well.
Upgrade to 4.4.0 or newer
"""""""""""""""""""""""""
pretix 4.4 introduces a new data structure to store historical financial data. If you already have existing
data in your database, you will need to back-fill this data or you might get incorrect reports! This is not
done automatically as part of the usual update steps since it can take a while on large databases and you might
want to do it in parallel while the system is already running again. Please execute the following command::
(venv)$ python -m pretix create_order_transactions
Or, with a docker installation::
$ docker exec -it pretix.service pretix create_order_transactions
.. _blog: https://pretix.eu/about/en/blog/

View File

@@ -25,8 +25,6 @@ description multi-lingual string A public descri
Markdown syntax or can be ``null``.
position integer An integer, used for sorting
require_membership boolean If ``true``, booking this variation requires an active membership.
require_membership_hidden boolean If ``true`` and ``require_membership`` is set, this variation will
be hidden from users without a valid membership.
require_membership_types list of integers Internal IDs of membership types valid if ``require_membership`` is ``true``
sales_channels list of strings Sales channels this variation is available on, such as
``"web"`` or ``"resellers"``. Defaults to all existing sales channels.
@@ -77,7 +75,6 @@ Endpoints
},
"active": true,
"require_membership": false,
"require_membership_hidden": false,
"require_membership_types": [],
"sales_channels": ["web"],
"available_from": null,
@@ -98,7 +95,6 @@ Endpoints
},
"active": true,
"require_membership": false,
"require_membership_hidden": false,
"require_membership_types": [],
"description": {},
"position": 1,
@@ -148,7 +144,6 @@ Endpoints
"original_price": null,
"active": true,
"require_membership": false,
"require_membership_hidden": false,
"require_membership_types": [],
"sales_channels": ["web"],
"available_from": null,
@@ -184,7 +179,6 @@ Endpoints
"default_price": "10.00",
"active": true,
"require_membership": false,
"require_membership_hidden": false,
"require_membership_types": [],
"sales_channels": ["web"],
"available_from": null,
@@ -210,7 +204,6 @@ Endpoints
"original_price": null,
"active": true,
"require_membership": false,
"require_membership_hidden": false,
"require_membership_types": [],
"sales_channels": ["web"],
"available_from": null,
@@ -267,7 +260,6 @@ Endpoints
"original_price": null,
"active": false,
"require_membership": false,
"require_membership_hidden": false,
"require_membership_types": [],
"sales_channels": ["web"],
"available_from": null,

View File

@@ -70,8 +70,6 @@ require_approval boolean If ``true``, or
paid.
require_bundling boolean If ``true``, this item is only available as part of bundles.
require_membership boolean If ``true``, booking this item requires an active membership.
require_membership_hidden boolean If ``true`` and ``require_membership`` is set, this product will
be hidden from users without a valid membership.
require_membership_types list of integers Internal IDs of membership types valid if ``require_membership`` is ``true``
grant_membership_type integer If set to the internal ID of a membership type, purchasing this item will
create a membership of the given type.
@@ -107,8 +105,6 @@ variations list of objects A list with one
├ active boolean If ``false``, this variation will not be sold or shown.
├ description multi-lingual string A public description of the variation. May contain
├ require_membership boolean If ``true``, booking this variation requires an active membership.
├ require_membership_hidden boolean If ``true`` and ``require_membership`` is set, this variation will
be hidden from users without a valid membership.
├ require_membership_types list of integers Internal IDs of membership types valid if ``require_membership`` is ``true``
Markdown syntax or can be ``null``.
├ sales_channels list of strings Sales channels this variation is available on, such as
@@ -159,10 +155,6 @@ meta_data object Values set for
The attributes ``require_membership``, ``require_membership_types``, ``grant_membership_type``, ``grant_membership_duration_like_event``,
``grant_membership_duration_days`` and ``grant_membership_duration_months`` have been added.
.. versionchanged:: 4.4
The attributes ``require_membership_hidden`` attribute has been added.
Notes
-----

View File

@@ -128,10 +128,6 @@ last_modified datetime Last modificati
The ``custom_followup_at`` attribute has been added.
.. versionchanged:: 4.4
The ``item`` and ``variation`` query parameters have been added.
.. _order-position-resource:
@@ -419,8 +415,6 @@ List of all orders
:query string code: Only return orders that match the given order code
:query string status: Only return orders in the given order status (see above)
:query string search: Only return orders matching a given search query
:query integer item: Only return orders with a position that contains this item ID. *Warning:* Result will also include orders if they contain mixed items, and it will even return orders where the item is only contained in a canceled position.
:query integer variation: Only return orders with a position that contains this variation ID. *Warning:* Result will also include orders if they contain mixed items and variations, and it will even return orders where the variation is only contained in a canceled position.
:query boolean testmode: Only return orders with ``testmode`` set to ``true`` or ``false``
:query boolean require_approval: If set to ``true`` or ``false``, only categories with this value for the field
``require_approval`` will be returned.

View File

@@ -92,9 +92,6 @@ Carts and Orders
.. autoclass:: pretix.base.models.OrderRefund
:members:
.. autoclass:: pretix.base.models.Transaction
:members:
.. autoclass:: pretix.base.models.CartPosition
:members:

View File

@@ -17,7 +17,6 @@ bic
BIC
boolean
booleans
bugfix
cancelled
casted
Ceph
@@ -78,7 +77,6 @@ mixin
mixins
multi
multidomain
multiplicator
namespace
namespaced
namespaces

View File

@@ -19,4 +19,4 @@
# 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/>.
#
__version__ = "4.4.0.dev0"
__version__ = "4.3.1"

View File

@@ -704,7 +704,6 @@ class EventSettingsSerializer(SettingsSerializer):
'payment_term_accept_late',
'payment_explanation',
'payment_pending_hidden',
'mail_days_order_expire_warning',
'ticket_download',
'ticket_download_date',
'ticket_download_addons',

View File

@@ -59,7 +59,7 @@ class InlineItemVariationSerializer(I18nAwareModelSerializer):
model = ItemVariation
fields = ('id', 'value', 'active', 'description',
'position', 'default_price', 'price', 'original_price',
'require_membership', 'require_membership_types', 'require_membership_hidden', 'available_from', 'available_until',
'require_membership', 'require_membership_types', 'available_from', 'available_until',
'sales_channels', 'hide_without_voucher',)
def __init__(self, *args, **kwargs):
@@ -75,7 +75,7 @@ class ItemVariationSerializer(I18nAwareModelSerializer):
model = ItemVariation
fields = ('id', 'value', 'active', 'description',
'position', 'default_price', 'price', 'original_price',
'require_membership', 'require_membership_types', 'require_membership_hidden', 'available_from', 'available_until',
'require_membership', 'require_membership_types', 'available_from', 'available_until',
'sales_channels', 'hide_without_voucher',)
def __init__(self, *args, **kwargs):
@@ -175,7 +175,7 @@ class ItemSerializer(I18nAwareModelSerializer):
'min_per_order', 'max_per_order', 'checkin_attention', 'has_variations', 'variations',
'addons', 'bundles', 'original_price', 'require_approval', 'generate_tickets',
'show_quota_left', 'hidden_if_available', 'allow_waitinglist', 'issue_giftcard', 'meta_data',
'require_membership', 'require_membership_types', 'require_membership_hidden', 'grant_membership_type',
'require_membership', 'require_membership_types', 'grant_membership_type',
'grant_membership_duration_like_event', 'grant_membership_duration_days',
'grant_membership_duration_months')
read_only_fields = ('has_variations',)

View File

@@ -1404,7 +1404,6 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
state=OrderPayment.PAYMENT_STATE_CREATED
)
order.create_transactions(is_new=True, fees=fees, positions=pos_map.values())
return order

View File

@@ -430,13 +430,7 @@ class CheckinListPositionViewSet(viewsets.ReadOnlyModelViewSet):
if self.kwargs['pk'].isnumeric():
op = queryset.get(Q(pk=self.kwargs['pk']) | Q(secret=self.kwargs['pk']))
else:
# In application/x-www-form-urlencoded, you can encodes space ' ' with '+' instead of '%20'.
# `id`, however, is part of a path where this technically is not allowed. Old versions of our
# scan apps still do it, so we try work around it!
try:
op = queryset.get(secret=self.kwargs['pk'])
except OrderPosition.DoesNotExist:
op = queryset.get(secret=self.kwargs['pk'].replace('+', ' '))
op = queryset.get(secret=self.kwargs['pk'])
except OrderPosition.DoesNotExist:
revoked_matches = list(self.request.event.revoked_secrets.filter(secret=self.kwargs['pk']))
if len(revoked_matches) == 0:

View File

@@ -92,8 +92,6 @@ with scopes_disabled():
subevent_after = django_filters.IsoDateTimeFilter(method='subevent_after_qs')
subevent_before = django_filters.IsoDateTimeFilter(method='subevent_before_qs')
search = django_filters.CharFilter(method='search_qs')
item = django_filters.CharFilter(field_name='all_positions', lookup_expr='item_id')
variation = django_filters.CharFilter(field_name='all_positions', lookup_expr='variation_id')
class Meta:
model = Order
@@ -216,9 +214,7 @@ class OrderViewSet(viewsets.ModelViewSet):
'positions',
opq.all().prefetch_related(
Prefetch('checkins', queryset=Checkin.objects.all()),
'item', 'variation',
Prefetch('answers', queryset=QuestionAnswer.objects.prefetch_related('options', 'question').order_by('question__position')),
'seat',
'item', 'variation', 'answers', 'answers__options', 'answers__question', 'seat',
)
)
)

View File

@@ -46,8 +46,7 @@ class PretixBaseConfig(AppConfig):
from . import invoice # NOQA
from . import notifications # NOQA
from . import email # NOQA
from .services import auth, checkin, checks, export, mail, tickets, cart, orderimport, orders, invoices, cleanup, update_check, quotas, notifications, vouchers # NOQA
from .models import _transactions # NOQA
from .services import auth, checkin, export, mail, tickets, cart, orderimport, orders, invoices, cleanup, update_check, quotas, notifications, vouchers # NOQA
from django.conf import settings
try:

View File

@@ -539,22 +539,6 @@ def base_placeholders(sender, **kwargs):
'voucher_list', ['voucher_list'], lambda voucher_list: ' \n'.join(voucher_list),
' 68CYU2H6ZTP3WLK5\n 7MB94KKPVEPSMVF2'
),
SimpleFunctionalMailTextPlaceholder(
# join vouchers with two spaces at end of line so markdown-parser inserts a <br>
'voucher_url_list', ['event', 'voucher_list'],
lambda event, voucher_list: ' \n'.join([
build_absolute_uri(
event, 'presale:event.redeem'
) + '?voucher=' + c
for c in voucher_list
]),
lambda event: ' \n'.join([
build_absolute_uri(
event, 'presale:event.redeem'
) + '?voucher=' + c
for c in ['68CYU2H6ZTP3WLK5', '7MB94KKPVEPSMVF2']
]),
),
SimpleFunctionalMailTextPlaceholder(
'url', ['event', 'voucher_list'], lambda event, voucher_list: build_absolute_uri(event, 'presale:event.index', kwargs={
'event': event.slug,

View File

@@ -55,20 +55,16 @@ class JSONExporter(BaseExporter):
'name': str(self.event.organizer.name),
'slug': self.event.organizer.slug
},
'meta_data': self.event.meta_data,
'categories': [
{
'id': category.id,
'name': str(category.name),
'description': str(category.description),
'position': category.position,
'internal_name': category.internal_name
} for category in self.event.categories.all()
],
'items': [
{
'id': item.id,
'position': item.position,
'name': str(item.name),
'internal_name': str(item.internal_name),
'category': item.category_id,
@@ -77,35 +73,13 @@ class JSONExporter(BaseExporter):
'tax_name': str(item.tax_rule.name) if item.tax_rule else None,
'admission': item.admission,
'active': item.active,
'sales_channels': item.sales_channels,
'description': str(item.description),
'available_from': item.available_from,
'available_until': item.available_until,
'require_voucher': item.require_voucher,
'hide_without_voucher': item.hide_without_voucher,
'allow_cancel': item.allow_cancel,
'require_bundling': item.require_bundling,
'min_per_order': item.min_per_order,
'max_per_order': item.max_per_order,
'checkin_attention': item.checkin_attention,
'original_price': item.original_price,
'issue_giftcard': item.issue_giftcard,
'meta_data': item.meta_data,
'require_membership': item.require_membership,
'variations': [
{
'id': variation.id,
'active': variation.active,
'price': variation.default_price if variation.default_price is not None else
item.default_price,
'name': str(variation),
'description': str(variation.description),
'position': variation.position,
'require_membership': variation.require_membership,
'sales_channels': variation.sales_channels,
'available_from': variation.available_from,
'available_until': variation.available_until,
'hide_without_voucher': variation.hide_without_voucher,
'name': str(variation)
} for variation in item.variations.all()
]
} for item in self.event.items.select_related('tax_rule').prefetch_related('variations')
@@ -113,13 +87,7 @@ class JSONExporter(BaseExporter):
'questions': [
{
'id': question.id,
'identifier': question.identifier,
'required': question.required,
'question': str(question.question),
'position': question.position,
'hidden': question.hidden,
'ask_during_checkin': question.ask_during_checkin,
'help_text': str(question.help_text),
'type': question.type
} for question in self.event.questions.all()
],
@@ -127,18 +95,7 @@ class JSONExporter(BaseExporter):
{
'code': order.code,
'status': order.status,
'customer': order.customer.identifier if order.customer else None,
'testmode': order.testmode,
'user': order.email,
'email': order.email,
'phone': str(order.phone),
'locale': order.locale,
'comment': order.comment,
'custom_followup_at': order.custom_followup_at,
'require_approval': order.require_approval,
'checkin_attention': order.checkin_attention,
'sales_channel': order.sales_channel,
'expires': order.expires,
'datetime': order.datetime,
'fees': [
{
@@ -151,21 +108,11 @@ class JSONExporter(BaseExporter):
'positions': [
{
'id': position.id,
'positionid': position.positionid,
'item': position.item_id,
'variation': position.variation_id,
'subevent': position.subevent_id,
'seat': position.seat.seat_guid if position.seat else None,
'price': position.price,
'tax_rate': position.tax_rate,
'tax_value': position.tax_value,
'attendee_name': position.attendee_name,
'attendee_email': position.attendee_email,
'company': position.company,
'street': position.street,
'zipcode': position.zipcode,
'country': str(position.country) if position.country else None,
'state': position.state,
'secret': position.secret,
'addon_to': position.addon_to_id,
'answers': [
@@ -177,30 +124,15 @@ class JSONExporter(BaseExporter):
} for position in order.positions.all()
]
} for order in
self.event.orders.all().prefetch_related('positions', 'positions__answers', 'positions__seat', 'customer', 'fees')
self.event.orders.all().prefetch_related('positions', 'positions__answers', 'fees')
],
'quotas': [
{
'id': quota.id,
'size': quota.size,
'subevent': quota.subevent_id,
'items': [item.id for item in quota.items.all()],
'variations': [variation.id for variation in quota.variations.all()],
} for quota in self.event.quotas.all().prefetch_related('items', 'variations')
],
'subevents': [
{
'id': se.id,
'name': str(se.name),
'location': str(se.location),
'date_from': se.date_from,
'date_to': se.date_to,
'date_admission': se.date_admission,
'geo_lat': se.geo_lat,
'geo_lon': se.geo_lon,
'is_public': se.is_public,
'meta_data': se.meta_data,
} for se in self.event.subevents.all()
]
}
}

View File

@@ -33,7 +33,6 @@
# License for the specific language governing permissions and limitations under the License.
from collections import OrderedDict
from datetime import date, datetime, time
from decimal import Decimal
import dateutil
@@ -43,10 +42,10 @@ from django.db.models import (
Case, CharField, Count, DateTimeField, F, IntegerField, Max, Min, OuterRef,
Q, Subquery, Sum, When,
)
from django.db.models.functions import Coalesce
from django.db.models.functions import Coalesce, TruncDate
from django.dispatch import receiver
from django.utils.functional import cached_property
from django.utils.timezone import get_current_timezone, make_aware, now
from django.utils.timezone import get_current_timezone, now
from django.utils.translation import gettext as _, gettext_lazy, pgettext
from pretix.base.models import (
@@ -182,43 +181,41 @@ class OrderListExporter(MultiSheetListExporter):
if form_data.get('date_from'):
date_value = form_data.get('date_from')
if not isinstance(date_value, date):
if isinstance(date_value, str):
date_value = dateutil.parser.parse(date_value).date()
datetime_value = make_aware(datetime.combine(date_value, time(0, 0, 0)), self.timezone)
filters[f'{rel}datetime__gte'] = datetime_value
annotations['date'] = TruncDate(f'{rel}datetime')
filters['date__gte'] = date_value
if form_data.get('date_to'):
date_value = form_data.get('date_to')
if not isinstance(date_value, date):
if isinstance(date_value, str):
date_value = dateutil.parser.parse(date_value).date()
datetime_value = make_aware(datetime.combine(date_value, time(23, 59, 59, 999999)), self.timezone)
filters[f'{rel}datetime__lte'] = datetime_value
annotations['date'] = TruncDate(f'{rel}datetime')
filters['date__lte'] = date_value
if form_data.get('event_date_from'):
date_value = form_data.get('event_date_from')
if not isinstance(date_value, date):
if isinstance(date_value, str):
date_value = dateutil.parser.parse(date_value).date()
datetime_value = make_aware(datetime.combine(date_value, time(0, 0, 0)), self.timezone)
annotations['event_date_max'] = Case(
When(**{f'{rel}event__has_subevents': True}, then=Max(f'{rel}all_positions__subevent__date_from')),
default=F(f'{rel}event__date_from'),
)
filters['event_date_max__gte'] = datetime_value
filters['event_date_max__gte'] = date_value
if form_data.get('event_date_to'):
date_value = form_data.get('event_date_to')
if not isinstance(date_value, date):
if isinstance(date_value, str):
date_value = dateutil.parser.parse(date_value).date()
datetime_value = make_aware(datetime.combine(date_value, time(23, 59, 59, 999999)), self.timezone)
annotations['event_date_min'] = Case(
When(**{f'{rel}event__has_subevents': True}, then=Min(f'{rel}all_positions__subevent__date_from')),
default=F(f'{rel}event__date_from'),
)
filters['event_date_min__lte'] = datetime_value
filters['event_date_min__lte'] = date_value
if filters:
return qs.annotate(**annotations).filter(**filters)
@@ -873,78 +870,6 @@ class QuotaListExporter(ListExporter):
return '{}_quotas'.format(self.event.slug)
def generate_GiftCardTransactionListExporter(organizer): # hackhack
class GiftcardTransactionListExporter(ListExporter):
identifier = 'giftcardtransactionlist'
verbose_name = gettext_lazy('Gift card transactions')
@property
def additional_form_fields(self):
d = [
('date_from',
forms.DateField(
label=_('Start date'),
widget=forms.DateInput(attrs={'class': 'datepickerfield'}),
required=False,
)),
('date_to',
forms.DateField(
label=_('End date'),
widget=forms.DateInput(attrs={'class': 'datepickerfield'}),
required=False,
)),
]
d = OrderedDict(d)
return d
def iterate_list(self, form_data):
qs = GiftCardTransaction.objects.filter(
card__issuer=organizer,
).order_by('datetime').select_related('card', 'order', 'order__event')
if form_data.get('date_from'):
date_value = form_data.get('date_from')
if isinstance(date_value, str):
date_value = dateutil.parser.parse(date_value).date()
qs = qs.filter(
datetime__gte=make_aware(datetime.combine(date_value, time(0, 0, 0)), self.timezone)
)
if form_data.get('date_to'):
date_value = form_data.get('date_to')
if isinstance(date_value, str):
date_value = dateutil.parser.parse(date_value).date()
qs = qs.filter(
datetime__lte=make_aware(datetime.combine(date_value, time(23, 59, 59, 999999)), self.timezone)
)
headers = [
_('Gift card code'),
_('Test mode'),
_('Date'),
_('Amount'),
_('Currency'),
_('Order'),
]
yield headers
for obj in qs:
row = [
obj.card.secret,
_('TEST MODE') if obj.card.testmode else '',
obj.datetime.astimezone(self.timezone).strftime('%Y-%m-%d %H:%M:%S'),
obj.value,
obj.card.currency,
obj.order.full_code if obj.order else None,
]
yield row
def get_filename(self):
return '{}_giftcardtransactions'.format(organizer.slug)
return GiftcardTransactionListExporter
class GiftcardRedemptionListExporter(ListExporter):
identifier = 'giftcardredemptionlist'
verbose_name = gettext_lazy('Gift card redemptions')
@@ -1137,8 +1062,3 @@ def register_multievent_i_giftcardredemptionlist_exporter(sender, **kwargs):
@receiver(register_multievent_data_exporters, dispatch_uid="multiexporter_giftcardlist")
def register_multievent_i_giftcardlist_exporter(sender, **kwargs):
return generate_GiftCardListExporter(sender)
@receiver(register_multievent_data_exporters, dispatch_uid="multiexporter_giftcardtransactionlist")
def register_multievent_i_giftcardtransactionlist_exporter(sender, **kwargs):
return generate_GiftCardTransactionListExporter(sender)

View File

@@ -37,10 +37,13 @@ import json
import logging
from decimal import Decimal
from io import BytesIO
from urllib.error import HTTPError
import dateutil.parser
import pycountry
import pytz
import vat_moss.errors
import vat_moss.id
from babel import Locale
from django import forms
from django.conf import settings
@@ -49,7 +52,7 @@ from django.core.exceptions import ValidationError
from django.core.files.uploadedfile import SimpleUploadedFile
from django.core.validators import MaxValueValidator, MinValueValidator
from django.db.models import QuerySet
from django.forms import Select, widgets
from django.forms import Select
from django.utils import translation
from django.utils.formats import date_format
from django.utils.html import escape
@@ -73,9 +76,8 @@ from pretix.base.i18n import (
get_babel_locale, get_language_without_region, language,
)
from pretix.base.models import InvoiceAddress, Question, QuestionOption
from pretix.base.models.tax import VAT_ID_COUNTRIES, ask_for_vat_id
from pretix.base.services.tax import (
VATIDFinalError, VATIDTemporaryError, validate_vat_id,
from pretix.base.models.tax import (
EU_COUNTRIES, cc_to_vat_prefix, is_eu_country,
)
from pretix.base.settings import (
COUNTRIES_WITH_STATE_IN_ADDRESS, PERSON_NAME_SALUTATIONS,
@@ -152,9 +154,8 @@ class NamePartsWidget(forms.MultiWidget):
final_attrs,
id='%s_%s' % (id_, i),
title=self.scheme['fields'][i][1],
placeholder=self.scheme['fields'][i][1],
)
if not isinstance(widget, widgets.Select):
these_attrs['placeholder'] = self.scheme['fields'][i][1]
if self.scheme['fields'][i][0] in REQUIRED_NAME_PARTS:
if self.field.required:
these_attrs['required'] = 'required'
@@ -900,7 +901,7 @@ class BaseInvoiceAddressForm(forms.ModelForm):
'data-display-dependency': '#id_is_business_1',
'autocomplete': 'organization',
}),
'vat_id': forms.TextInput(attrs={'data-display-dependency': '#id_is_business_1', 'data-countries-with-vat-id': ','.join(VAT_ID_COUNTRIES)}),
'vat_id': forms.TextInput(attrs={'data-display-dependency': '#id_is_business_1', 'data-countries-in-eu': ','.join(EU_COUNTRIES)}),
'internal_reference': forms.TextInput,
}
labels = {
@@ -920,18 +921,6 @@ class BaseInvoiceAddressForm(forms.ModelForm):
super().__init__(*args, **kwargs)
if not event.settings.invoice_address_vatid:
del self.fields['vat_id']
elif self.validate_vat_id:
self.fields['vat_id'].help_text = '<br/>'.join([
str(_('Optional, but depending on the country you reside in we might need to charge you '
'additional taxes if you do not enter it.')),
str(_('If you are registered in Switzerland, you can enter your UID instead.')),
])
else:
self.fields['vat_id'].help_text = '<br/>'.join([
str(_('Optional, but it might be required for you to claim tax benefits on your invoice '
'depending on your and the sellers country of residence.')),
str(_('If you are registered in Switzerland, you can enter your UID instead.')),
])
self.fields['country'].choices = CachedCountries()
@@ -963,7 +952,7 @@ class BaseInvoiceAddressForm(forms.ModelForm):
self.fields['state'].widget.is_required = True
# Without JavaScript the VAT ID field is not hidden, so we empty the field if a country outside the EU is selected.
if cc and not ask_for_vat_id(cc) and fprefix + 'vat_id' in self.data:
if cc and not is_eu_country(cc) and fprefix + 'vat_id' in self.data:
self.data = self.data.copy()
del self.data[fprefix + 'vat_id']
@@ -1013,7 +1002,7 @@ class BaseInvoiceAddressForm(forms.ModelForm):
if not data.get('is_business'):
data['company'] = ''
data['vat_id'] = ''
if data.get('is_business') and not ask_for_vat_id(data.get('country')):
if data.get('is_business') and not is_eu_country(data.get('country')):
data['vat_id'] = ''
if self.event.settings.invoice_address_required:
if data.get('is_business') and not data.get('company'):
@@ -1036,19 +1025,36 @@ class BaseInvoiceAddressForm(forms.ModelForm):
# Do not save the country if it is the only field set -- we don't know the user even checked it!
self.cleaned_data['country'] = ''
if data.get('vat_id') and is_eu_country(data.get('country')) and data.get('vat_id')[:2] != cc_to_vat_prefix(str(data.get('country'))):
raise ValidationError(_('Your VAT ID does not match the selected country.'))
if self.validate_vat_id and self.instance.vat_id_validated and 'vat_id' not in self.changed_data:
pass
elif self.validate_vat_id and data.get('is_business') and ask_for_vat_id(data.get('country')) and data.get('vat_id'):
elif self.validate_vat_id and data.get('is_business') and is_eu_country(data.get('country')) and data.get('vat_id'):
try:
normalized_id = validate_vat_id(data.get('vat_id'), str(data.get('country')))
self.instance.vat_id_validated = True
self.instance.vat_id = normalized_id
except VATIDFinalError as e:
raise ValidationError(e.message)
except VATIDTemporaryError as e:
result = vat_moss.id.validate(data.get('vat_id'))
if result:
country_code, normalized_id, company_name = result
self.instance.vat_id_validated = True
self.instance.vat_id = normalized_id
except (vat_moss.errors.InvalidError, ValueError):
raise ValidationError(_('This VAT ID is not valid. Please re-check your input.'))
except vat_moss.errors.WebServiceUnavailableError:
logger.exception('VAT ID checking failed for country {}'.format(data.get('country')))
self.instance.vat_id_validated = False
if self.request and self.vat_warning:
messages.warning(self.request, e.message)
messages.warning(self.request, _('Your VAT ID could not be checked, as the VAT checking service of '
'your country is currently not available. We will therefore '
'need to charge VAT on your invoice. You can get the tax amount '
'back via the VAT reimbursement process.'))
except (vat_moss.errors.WebServiceError, HTTPError):
logger.exception('VAT ID checking failed for country {}'.format(data.get('country')))
self.instance.vat_id_validated = False
if self.request and self.vat_warning:
messages.warning(self.request, _('Your VAT ID could not be checked, as the VAT checking service of '
'your country returned an incorrect result. We will therefore '
'need to charge VAT on your invoice. Please contact support to '
'resolve this manually.'))
else:
self.instance.vat_id_validated = False

View File

@@ -595,7 +595,7 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
table.setStyle(TableStyle(tstyledata))
story.append(table)
story.append(Spacer(1, 10 * mm))
story.append(Spacer(1, 15 * mm))
if self.invoice.payment_provider_text:
story.append(Paragraph(
@@ -611,14 +611,12 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
self.invoice.additional_text,
self.stylesheet['Normal']
))
story.append(Spacer(1, 5 * mm))
story.append(Spacer(1, 15 * mm))
tstyledata = [
('ALIGN', (1, 0), (-1, -1), 'RIGHT'),
('LEFTPADDING', (0, 0), (0, -1), 0),
('RIGHTPADDING', (-1, 0), (-1, -1), 0),
('TOPPADDING', (0, 0), (-1, -1), 1),
('BOTTOMPADDING', (0, 0), (-1, -1), 1),
('FONTSIZE', (0, 0), (-1, -1), 8),
('FONTNAME', (0, 0), (-1, -1), self.font_regular),
]

View File

@@ -1,79 +0,0 @@
#
# 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 decimal import Decimal
from django.core.management.base import BaseCommand
from django.db import models
from django.db.models import Case, F, OuterRef, Q, Subquery, Sum, Value, When
from django.db.models.functions import Coalesce
from django_scopes import scopes_disabled
from pretix.base.models import Order, OrderFee, OrderPosition
from pretix.base.models.orders import Transaction
class Command(BaseCommand):
help = "Check order for consistency with their transactions"
@scopes_disabled()
def handle(self, *args, **options):
qs = Order.objects.annotate(
position_total=Coalesce(
Subquery(
OrderPosition.objects.filter(
order=OuterRef('pk')
).order_by().values('order').annotate(p=Sum('price')).values('p'),
output_field=models.DecimalField(decimal_places=2, max_digits=10)
), Value(Decimal(0)), output_field=models.DecimalField(decimal_places=2, max_digits=10)
),
fee_total=Coalesce(
Subquery(
OrderFee.objects.filter(
order=OuterRef('pk')
).order_by().values('order').annotate(p=Sum('value')).values('p'),
output_field=models.DecimalField(decimal_places=2, max_digits=10)
), Value(Decimal(0)), output_field=models.DecimalField(decimal_places=2, max_digits=10)
),
tx_total=Coalesce(
Subquery(
Transaction.objects.filter(
order=OuterRef('pk')
).order_by().values('order').annotate(p=Sum(F('price') * F('count'))).values('p'),
output_field=models.DecimalField(decimal_places=2, max_digits=10)
), Value(Decimal(0)), output_field=models.DecimalField(decimal_places=2, max_digits=10)
),
).annotate(
correct_total=Case(
When(Q(status=Order.STATUS_CANCELED) | Q(status=Order.STATUS_EXPIRED) | Q(require_approval=True),
then=Value(Decimal(0))),
default=F('position_total') + F('fee_total'),
output_field=models.DecimalField(decimal_places=2, max_digits=10)
),
).exclude(
total=F('position_total') + F('fee_total'),
tx_total=F('correct_total')
).select_related('event')
for o in qs:
print(f"Error in order {o.full_code}: status={o.status}, sum(positions)+sum(fees)={o.position_total + o.fee_total}, "
f"order.total={o.total}, sum(transactions)={o.tx_total}, expected={o.correct_total}")
self.stderr.write(self.style.SUCCESS(f'Check completed.'))

View File

@@ -1,95 +0,0 @@
#
# 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/>.
#
import time
from django.core.management.base import BaseCommand
from django.db.models import F, Max, Q
from django.utils.timezone import now
from django_scopes import scopes_disabled
from tqdm import tqdm
from pretix.base.models import Order
class Command(BaseCommand):
help = "Create missing order transactions"
def add_arguments(self, parser):
parser.add_argument(
"--slowdown",
dest="interval",
type=int,
default=0,
help="Interval for staggered execution. If set to a value different then zero, we will "
"wait this many milliseconds between every order we process.",
)
@scopes_disabled()
def handle(self, *args, **options):
t = 0
qs = Order.objects.annotate(
last_transaction=Max('transactions__created')
).filter(
Q(last_transaction__isnull=True) | Q(last_modified__gt=F('last_transaction')),
require_approval=False,
).prefetch_related(
'all_positions', 'all_fees'
).order_by(
'pk'
)
last_pk = 0
with tqdm(total=qs.count()) as pbar:
while True:
batch = list(qs.filter(pk__gt=last_pk)[:5000])
if not batch:
break
for o in batch:
if o.last_transaction is None:
tn = o.create_transactions(
positions=o.all_positions.all(),
fees=o.all_fees.all(),
dt_now=o.datetime,
migrated=True,
is_new=True,
_backfill_before_cancellation=True,
)
o.create_transactions(
positions=o.all_positions.all(),
fees=o.all_fees.all(),
dt_now=o.cancellation_date or (o.expires if o.status == Order.STATUS_EXPIRED else o.datetime),
migrated=True,
)
else:
tn = o.create_transactions(
positions=o.all_positions.all(),
fees=o.all_fees.all(),
dt_now=now(),
migrated=True,
)
if tn:
t += 1
time.sleep(0)
pbar.update(1)
last_pk = batch[-1].pk
self.stderr.write(self.style.SUCCESS(f'Created transactions for {t} orders.'))

View File

@@ -1,21 +0,0 @@
# Generated by Django 3.2.4 on 2021-09-30 10:25
from datetime import datetime
from django.db import migrations, models
from pytz import UTC
class Migration(migrations.Migration):
dependencies = [
('pretixbase', '0197_auto_20210914_0814'),
]
operations = [
migrations.AddField(
model_name='invoice',
name='sent_to_customer',
field=models.DateTimeField(blank=True, null=True, default=UTC.localize(datetime(1970, 1, 1, 0, 0, 0, 0))),
preserve_default=False,
),
]

View File

@@ -1,23 +0,0 @@
# Generated by Django 3.2.4 on 2021-10-05 10:50
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('pretixbase', '0198_invoice_sent_to_customer'),
]
operations = [
migrations.AddField(
model_name='item',
name='require_membership_hidden',
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name='itemvariation',
name='require_membership_hidden',
field=models.BooleanField(default=False),
),
]

View File

@@ -1,38 +0,0 @@
# Generated by Django 3.2.4 on 2021-10-18 10:27
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('pretixbase', '0199_auto_20211005_1050'),
]
operations = [
migrations.CreateModel(
name='Transaction',
fields=[
('id', models.BigAutoField(primary_key=True, serialize=False)),
('created', models.DateTimeField(auto_now_add=True, db_index=True)),
('datetime', models.DateTimeField(db_index=True)),
('migrated', models.BooleanField(default=False)),
('positionid', models.PositiveIntegerField(default=1, null=True)),
('count', models.IntegerField(default=1)),
('price', models.DecimalField(decimal_places=2, max_digits=10)),
('tax_rate', models.DecimalField(decimal_places=2, max_digits=7)),
('tax_value', models.DecimalField(decimal_places=2, max_digits=10)),
('fee_type', models.CharField(max_length=100, null=True)),
('internal_type', models.CharField(max_length=255, null=True)),
('item', models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='pretixbase.item')),
('order', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='transactions', to='pretixbase.order')),
('subevent', models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='pretixbase.subevent')),
('tax_rule', models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='pretixbase.taxrule')),
('variation', models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='pretixbase.itemvariation')),
],
options={
'ordering': ('datetime', 'pk'),
},
),
]

View File

@@ -1,29 +0,0 @@
# Generated by Django 3.2.4 on 2021-10-29 09:58
from django.db import migrations, models
import pretix.base.models.base
class Migration(migrations.Migration):
dependencies = [
('pretixbase', '0200_transaction'),
]
operations = [
migrations.CreateModel(
name='Check',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False)),
('created', models.DateTimeField(auto_now_add=True)),
('result', models.CharField(max_length=190)),
('check_type', models.CharField(max_length=190)),
('log', models.TextField()),
],
options={
'abstract': False,
},
bases=(models.Model, pretix.base.models.base.LoggingMixin),
),
]

View File

@@ -23,7 +23,6 @@ from ..settings import GlobalSettingsObject_SettingsStore
from .auth import U2FDevice, User, WebAuthnDevice
from .base import CachedFile, LoggedModel, cachedfile_name
from .checkin import Checkin, CheckinList
from .checks import Check
from .customers import Customer
from .devices import Device, Gate
from .event import (
@@ -43,9 +42,8 @@ from .notifications import NotificationSetting
from .orders import (
AbstractPosition, CachedCombinedTicket, CachedTicket, CartPosition,
InvoiceAddress, Order, OrderFee, OrderPayment, OrderPosition, OrderRefund,
QuestionAnswer, RevokedTicketSecret, Transaction,
cachedcombinedticket_name, cachedticket_name, generate_position_secret,
generate_secret,
QuestionAnswer, RevokedTicketSecret, cachedcombinedticket_name,
cachedticket_name, generate_position_secret, generate_secret,
)
from .organizer import (
Organizer, Organizer_SettingsStore, Team, TeamAPIToken, TeamInvite,

View File

@@ -1,113 +0,0 @@
#
# 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/>.
#
"""
This module contains helper functions that are supposed to call out code paths missing calls to
``Order.create_transaction()`` by actively breaking them. Read the docstring of the ``Transaction`` class for a
detailed reasoning why this exists.
"""
import inspect
import logging
import os
import threading
from django.conf import settings
from django.db import transaction
dirty_transactions = threading.local()
logger = logging.getLogger(__name__)
fail_loudly = os.getenv('PRETIX_DIRTY_TRANSACTIONS_QUIET', 'false') not in ('true', 'True', 'on', '1')
class DirtyTransactionsForOrderException(Exception):
pass
def _fail(message):
if fail_loudly:
raise DirtyTransactionsForOrderException(message)
else:
if settings.SENTRY_ENABLED:
import sentry_sdk
sentry_sdk.capture_message(message, "fatal")
logger.warning(message, stack_info=True)
def _check_for_dirty_orders():
if getattr(dirty_transactions, 'order_ids', None) is None:
dirty_transactions.order_ids = set()
try:
if dirty_transactions.order_ids and dirty_transactions.order_ids != {None}:
_fail(
f"In the transaction that just ended, you created or modified an Order, OrderPosition, or OrderFee "
f"object in a way that you should have called `order.create_transactions()` afterwards. The transaction "
f"still went through and your data can be fixed with the `create_order_transactions` management command "
f"but you should update your code to prevent this from happening. Affected order IDs: {dirty_transactions.order_ids}"
)
finally:
dirty_transactions.order_ids.clear()
def _transactions_mark_order_dirty(order_id, using=None):
if "PYTEST_CURRENT_TEST" in os.environ:
# We don't care about Order.objects.create() calls in test code so let's try to figure out if this is test code
# or not.
for frame in inspect.stack():
if 'pretix/base/models/orders' in frame.filename:
continue
elif 'test_' in frame.filename or 'conftest.py in frame.filename':
return
elif 'pretix/' in frame.filename or 'pretix_' in frame.filename:
# This went through non-test code, let's consider it non-test
break
if order_id is None:
return
conn = transaction.get_connection(using)
if not conn.in_atomic_block:
_fail(
"You modified an Order, OrderPosition, or OrderFee object in a way that should create "
"a new Transaction object within the same database transaction, however you are not "
"doing it inside a database transaction!"
)
if getattr(dirty_transactions, 'order_ids', None) is None:
dirty_transactions.order_ids = set()
if _check_for_dirty_orders not in [func for savepoint_id, func in conn.run_on_commit]:
transaction.on_commit(_check_for_dirty_orders, using)
dirty_transactions.order_ids.clear() # This is necessary to clean up after old threads with rollbacked transactions
dirty_transactions.order_ids.add(order_id)
def _transactions_mark_order_clean(order_id):
if getattr(dirty_transactions, 'order_ids', None) is None:
dirty_transactions.order_ids = set()
try:
dirty_transactions.order_ids.remove(order_id)
except KeyError:
pass

View File

@@ -1,54 +0,0 @@
#
# 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/>.
#
# This file is based on an earlier version of pretix which was released under the Apache License 2.0. The full text of
# the Apache License 2.0 can be obtained at <http://www.apache.org/licenses/LICENSE-2.0>.
#
# This file may have since been changed and any changes are released under the terms of AGPLv3 as described above. A
# full history of changes and contributors is available at <https://github.com/pretix/pretix>.
#
# This file contains Apache-licensed contributions copyrighted by: Jakob Schnell
#
# Unless required by applicable law or agreed to in writing, software distributed under the Apache License 2.0 is
# 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.
from django.db import models
from django.utils.translation import gettext_lazy as _
from pretix.base.models import LoggedModel
class Check(LoggedModel):
RESULT_OK = 'ok'
RESULT_WARNING = 'warning'
RESULT_ERROR = 'error'
RESULTS = (
(RESULT_OK, _('OK')),
(RESULT_WARNING, _('Warning')),
(RESULT_ERROR, _('Error')),
)
created = models.DateTimeField(auto_now_add=True)
result = models.CharField(max_length=190, choices=RESULTS)
check_type = models.CharField(max_length=190)
log = models.TextField()

View File

@@ -25,7 +25,6 @@ from django.contrib.auth.hashers import (
check_password, is_password_usable, make_password,
)
from django.db import models
from django.db.models import F, Q
from django.utils.crypto import get_random_string, salted_hmac
from django.utils.translation import gettext_lazy as _, pgettext_lazy
from django_scopes import ScopedManager, scopes_disabled
@@ -184,12 +183,6 @@ class Customer(LoggedModel):
def stored_addresses(self):
return self.invoice_addresses(manager='profiles')
def usable_memberships(self, for_event, testmode=False):
return self.memberships.active(for_event).with_usages().filter(
Q(membership_type__max_usages__isnull=True) | Q(usages__lt=F('membership_type__max_usages')),
testmode=testmode,
)
class AttendeeProfile(models.Model):
customer = models.ForeignKey(

View File

@@ -57,7 +57,6 @@ from django.urls import reverse
from django.utils.crypto import get_random_string
from django.utils.formats import date_format
from django.utils.functional import cached_property
from django.utils.html import format_html
from django.utils.timezone import make_aware, now
from django.utils.translation import gettext, gettext_lazy as _
from django_scopes import ScopedManager, scopes_disabled
@@ -146,7 +145,7 @@ class EventMixin:
("SHORT_" if short else "") + ("DATETIME_FORMAT" if self.settings.show_times and show_times else "DATE_FORMAT")
)
def get_date_range_display(self, tz=None, force_show_end=False, as_html=False) -> str:
def get_date_range_display(self, tz=None, force_show_end=False) -> str:
"""
Returns a formatted string containing the start date and the end date
of the event with respect to the current locale and to the ``show_date_to``
@@ -154,17 +153,8 @@ class EventMixin:
"""
tz = tz or self.timezone
if (not self.settings.show_date_to and not force_show_end) or not self.date_to:
if as_html:
return format_html(
"<time datetime=\"{}\">{}</time>",
_date(self.date_from.astimezone(tz), "Y-m-d"),
_date(self.date_from.astimezone(tz), "DATE_FORMAT"),
)
return _date(self.date_from.astimezone(tz), "DATE_FORMAT")
return daterange(self.date_from.astimezone(tz), self.date_to.astimezone(tz), as_html)
def get_date_range_display_as_html(self, tz=None, force_show_end=False) -> str:
return self.get_date_range_display(tz, force_show_end, as_html=True)
return daterange(self.date_from.astimezone(tz), self.date_to.astimezone(tz))
def get_time_range_display(self, tz=None, force_show_end=False) -> str:
"""
@@ -1431,7 +1421,7 @@ class SubEvent(EventMixin, LoggedModel):
return self.event.currency
def allow_delete(self):
return not self.orderposition_set.exists() and not self.transaction_set.exists()
return not self.orderposition_set.exists()
def delete(self, *args, **kwargs):
clear_cache = kwargs.pop('clear_cache', False)

View File

@@ -159,8 +159,6 @@ class Invoice(models.Model):
# False: The invoice wasn't sent and never will, because sending was not configured at the time of the check.
sent_to_organizer = models.BooleanField(null=True, blank=True)
sent_to_customer = models.DateTimeField(null=True, blank=True)
file = models.FileField(null=True, blank=True, upload_to=invoice_filename, max_length=255)
objects = ScopedManager(organizer='event__organizer')
@@ -302,9 +300,6 @@ class Invoice(models.Model):
def __repr__(self):
return '<Invoice {} / {}>'.format(self.full_invoice_no, self.pk)
def __str__(self):
return self.full_invoice_no
class InvoiceLine(models.Model):
"""

View File

@@ -523,12 +523,6 @@ class Item(LoggedModel):
verbose_name=_('Allowed membership types'),
blank=True,
)
require_membership_hidden = models.BooleanField(
verbose_name=_('Hide without a valid membership'),
help_text=_('Do not show this unless the customer is logged in and has a valid membership. Be aware that '
'this means it will never be visible in the widget.'),
default=False,
)
grant_membership_type = models.ForeignKey(
'MembershipType',
null=True, blank=True,
@@ -693,9 +687,9 @@ class Item(LoggedModel):
return res
def allow_delete(self):
from pretix.base.models.orders import OrderPosition, Transaction
from pretix.base.models.orders import OrderPosition
return not Transaction.objects.filter(item=self).exists() and not OrderPosition.all.filter(item=self).exists()
return not OrderPosition.all.filter(item=self).exists()
@property
def includes_mixed_tax_rate(self):
@@ -808,12 +802,6 @@ class ItemVariation(models.Model):
verbose_name=_('Membership types'),
blank=True,
)
require_membership_hidden = models.BooleanField(
verbose_name=_('Hide without a valid membership'),
help_text=_('Do not show this unless the customer is logged in and has a valid membership. Be aware that '
'this means it will never be visible in the widget.'),
default=False,
)
available_from = models.DateTimeField(
verbose_name=_("Available from"),
null=True, blank=True,
@@ -958,13 +946,10 @@ class ItemVariation(models.Model):
return self.position < other.position
def allow_delete(self):
from pretix.base.models.orders import (
CartPosition, OrderPosition, Transaction,
)
from pretix.base.models.orders import CartPosition, OrderPosition
return (
not Transaction.objects.filter(variation=self).exists()
and not OrderPosition.objects.filter(variation=self).exists()
not OrderPosition.objects.filter(variation=self).exists()
and not CartPosition.objects.filter(variation=self).exists()
)

View File

@@ -95,7 +95,6 @@ class MembershipQuerySet(models.QuerySet):
def active(self, ev):
return self.filter(
canceled=False,
date_start__lte=ev.date_from,
date_end__gte=ev.date_from
)
@@ -176,7 +175,7 @@ class Membership(models.Model):
else:
dt = now()
return not self.canceled and dt >= self.date_start and dt <= self.date_end
return dt >= self.date_start and dt <= self.date_end
def allow_delete(self):
return self.testmode and not self.orderposition_set.exists()

View File

@@ -80,9 +80,6 @@ from pretix.base.settings import PERSON_NAME_SCHEMES
from pretix.base.signals import order_gracefully_delete
from ...helpers.countries import CachedCountries, FastCountryField
from ._transactions import (
_fail, _transactions_mark_order_clean, _transactions_mark_order_dirty,
)
from .base import LockModel, LoggedModel
from .event import Event, SubEvent
from .items import Item, ItemVariation, Question, QuestionOption, Quota
@@ -265,11 +262,6 @@ class Order(LockModel, LoggedModel):
def __str__(self):
return self.full_code
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if 'require_approval' not in self.get_deferred_fields() and 'status' not in self.get_deferred_fields():
self.__initial_status_paid_or_pending = self.status in (Order.STATUS_PENDING, Order.STATUS_PAID) and not self.require_approval
def gracefully_delete(self, user=None, auth=None):
from . import GiftCard, GiftCardTransaction, Membership, Voucher
@@ -297,7 +289,6 @@ class Order(LockModel, LoggedModel):
OrderPosition.all.filter(order=self, addon_to__isnull=False).delete()
OrderPosition.all.filter(order=self).delete()
OrderFee.all.filter(order=self).delete()
Transaction.objects.filter(order=self).delete()
self.refunds.all().delete()
self.payments.all().delete()
self.event.cache.delete('complain_testmode_orders')
@@ -453,27 +444,7 @@ class Order(LockModel, LoggedModel):
self.datetime = now()
if not self.expires:
self.set_expires()
is_new = not self.pk
update_fields = kwargs.get('update_fields', [])
if 'require_approval' not in self.get_deferred_fields() and 'status' not in self.get_deferred_fields():
status_paid_or_pending = self.status in (Order.STATUS_PENDING, Order.STATUS_PAID) and not self.require_approval
if status_paid_or_pending != self.__initial_status_paid_or_pending:
_transactions_mark_order_dirty(self.pk, using=kwargs.get('using', None))
elif (
not kwargs.get('force_save_with_deferred_fields', None) and
(not update_fields or ('require_approval' not in update_fields and 'status' not in update_fields))
):
_fail("It is unsafe to call save() on an OrderFee with deferred fields since we can't check if you missed "
"creating a transaction. Call save(force_save_with_deferred_fields=True) if you really want to do "
"this.")
r = super().save(**kwargs)
if is_new:
_transactions_mark_order_dirty(self.pk, using=kwargs.get('using', None))
return r
super().save(**kwargs)
def touch(self):
self.save(update_fields=['last_modified'])
@@ -1028,59 +999,6 @@ class Order(LockModel, LoggedModel):
continue
yield op
def create_transactions(self, is_new=False, positions=None, fees=None, dt_now=None, migrated=False,
_backfill_before_cancellation=False, save=True):
dt_now = dt_now or now()
# Count the transactions we already have
current_transaction_count = Counter()
if not is_new:
for t in Transaction.objects.filter(order=self): # do not use related manager, we want to avoid cached data
current_transaction_count[Transaction.key(t)] += t.count
# Count the transactions we'd actually need
target_transaction_count = Counter()
if (_backfill_before_cancellation or self.status in (Order.STATUS_PENDING, Order.STATUS_PAID)) and not self.require_approval:
positions = self.positions.all() if positions is None else positions
for p in positions:
if p.canceled and not _backfill_before_cancellation:
continue
target_transaction_count[Transaction.key(p)] += 1
fees = self.fees.all() if fees is None else fees
for f in fees:
if f.canceled and not _backfill_before_cancellation:
continue
target_transaction_count[Transaction.key(f)] += 1
keys = set(target_transaction_count.keys()) | set(current_transaction_count.keys())
create = []
for k in keys:
positionid, itemid, variationid, subeventid, price, taxrate, taxruleid, taxvalue, feetype, internaltype = k
d = target_transaction_count[k] - current_transaction_count[k]
if d:
create.append(Transaction(
order=self,
datetime=dt_now,
migrated=migrated,
positionid=positionid,
count=d,
item_id=itemid,
variation_id=variationid,
subevent_id=subeventid,
price=price,
tax_rate=taxrate,
tax_rule_id=taxruleid,
tax_value=taxvalue,
fee_type=feetype,
internal_type=internaltype,
))
create.sort(key=lambda t: (0 if t.count < 0 else 1, t.positionid or 0))
if save:
Transaction.objects.bulk_create(create)
_transactions_mark_order_clean(self.pk)
return create
def answerfile_name(instance, filename: str) -> str:
secret = get_random_string(length=32, allowed_chars=string.ascii_letters + string.digits)
@@ -1550,7 +1468,6 @@ class OrderPayment(models.Model):
'message': can_be_paid
}, user=user, auth=auth)
raise Quota.QuotaExceededException(can_be_paid)
status_change = self.order.status != Order.STATUS_PENDING
self.order.status = Order.STATUS_PAID
self.order.save(update_fields=['status'])
@@ -1564,8 +1481,6 @@ class OrderPayment(models.Model):
if overpaid:
self.order.log_action('pretix.event.order.overpaid', {}, user=user, auth=auth)
order_paid.send(self.order.event, order=self.order)
if status_change:
self.order.create_transactions()
def fail(self, info=None, user=None, auth=None):
"""
@@ -2043,12 +1958,6 @@ class OrderFee(models.Model):
def net_value(self):
return self.value - self.tax_value
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if not self.get_deferred_fields():
self.__initial_transaction_key = Transaction.key(self)
self.__initial_canceled = self.canceled
def __str__(self):
if self.description:
return '{} - {}'.format(self.get_fee_type_display(), self.description)
@@ -2087,15 +1996,6 @@ class OrderFee(models.Model):
if self.tax_rate is None:
self._calculate_tax()
self.order.touch()
if not self.get_deferred_fields():
if Transaction.key(self) != self.__initial_transaction_key or self.canceled != self.__initial_canceled or not self.pk:
_transactions_mark_order_dirty(self.order_id, using=kwargs.get('using', None))
elif not kwargs.get('force_save_with_deferred_fields', None):
_fail("It is unsafe to call save() on an OrderFee with deferred fields since we can't check if you missed "
"creating a transaction. Call save(force_save_with_deferred_fields=True) if you really want to do "
"this.")
return super().save(*args, **kwargs)
def delete(self, **kwargs):
@@ -2110,7 +2010,7 @@ class OrderPosition(AbstractPosition):
AbstractPosition.
The default ``OrderPosition.objects`` manager only contains fees that are not ``canceled``. If
you want all objects, you need to use ``OrderPosition.all`` instead.
you ant all objects, you need to use ``OrderPosition.all`` instead.
:param order: The order this position is a part of
:type order: Order
@@ -2161,12 +2061,6 @@ class OrderPosition(AbstractPosition):
all = ScopedManager(organizer='order__event__organizer')
objects = ActivePositionManager()
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if not self.get_deferred_fields():
self.__initial_transaction_key = Transaction.key(self)
self.__initial_canceled = self.canceled
class Meta:
verbose_name = _("Order position")
verbose_name_plural = _("Order positions")
@@ -2210,7 +2104,6 @@ class OrderPosition(AbstractPosition):
op._calculate_tax()
op.positionid = i + 1
op.save()
ops.append(op)
cp_mapping[cartpos.pk] = op
for answ in cartpos.answers.all():
answ.orderposition = op
@@ -2276,14 +2169,6 @@ class OrderPosition(AbstractPosition):
if not self.pseudonymization_id:
self.assign_pseudonymization_id()
if not self.get_deferred_fields():
if Transaction.key(self) != self.__initial_transaction_key or self.canceled != self.__initial_canceled or not self.pk:
_transactions_mark_order_dirty(self.order_id, using=kwargs.get('using', None))
elif not kwargs.get('force_save_with_deferred_fields', None):
_fail("It is unsafe to call save() on an OrderFee with deferred fields since we can't check if you missed "
"creating a transaction. Call save(force_save_with_deferred_fields=True) if you really want to do "
"this.")
return super().save(*args, **kwargs)
@scopes_disabled()
@@ -2327,7 +2212,7 @@ class OrderPosition(AbstractPosition):
:param attach_ical: Attach relevant ICS files
"""
from pretix.base.services.mail import (
SendMailException, TolerantDict, mail, render_mail,
SendMailException, mail, render_mail,
)
if not self.attendee_email:
@@ -2340,7 +2225,6 @@ class OrderPosition(AbstractPosition):
recipient = self.attendee_email
try:
email_content = render_mail(template, context)
subject = str(subject).format_map(TolerantDict(context))
mail(
recipient, subject, template, context,
self.event, self.order.locale, order=self.order, headers=headers, sender=sender,
@@ -2379,151 +2263,6 @@ class OrderPosition(AbstractPosition):
)
class Transaction(models.Model):
"""
Transactions are a data structure that is redundant on the first sight but makes it possible to create good
financial reporting.
To understand this, think of "orders" as something like a contractual relationship between the organizer and the
customer which requires to customer to pay some money and the organizer to provide a ticket.
The ``Order``, ``OrderPosition``, and ``OrderFee`` models combined give a representation of the current contractual
status of this relationship, i.e. how much and what is owed. The ``OrderPayment`` and ``OrderRefund`` models indicate
the "other side" of the relationship, i.e. how much of the financial obligation has been met so far.
However, while ``OrderPayment`` and ``OrderRefund`` objects are "final" and no longer change once they reached their
final state, ``Order``, ``OrderPosition`` and ``OrderFee`` are highly mutable and can change at any time, e.g. if
the customer moves their booking to a different day or a discount is applied retroactively.
Therefore those models can be used to answer the question "how many tickets of type X have been sold for my event
as of today?" but they cannot accurately answer the question "how many tickets of type X have been sold for my event
as of last month?" because they lack this kind of historical information.
Transactions help here because they are "immutable copies" or "modification records" of call positions and fees
at the time of their creation and change. They only record data that is usually relevant for financial reporting,
such as amounts, prices, products and dates involved. They do not record data like attendee names etc.
Even before the introduction of the Transaction Model pretix *did* store historical data for auditability in the
LogEntry model. However, it's almost impossible to do efficient reporting on that data.
Transactions should never be generated manually but only through the ``order.create_transactions()``
method which should be called **within the same database transaction**.
The big downside of this approach is that you need to remember to update transaction records every time you change
or create orders in new code paths. The mechanism introduced in ``pretix.base.models._transactions`` as well as
the ``save()`` methods of ``Order``, ``OrderPosition`` and ``OrderFee`` intends to help you notice if you missed
it. The only thing this *doesn't* catch is usage of ``OrderPosition.objects.bulk_create`` (and likewise for
``bulk_update`` and ``OrderFee``).
:param id: ID of the transaction
:param order: Order the transaction belongs to
:param datetime: Date and time of the transaction
:param migrated: Whether this object was reconstructed because the order was created before transactions where introduced
:param positionid: Affected Position ID, in case this transaction represents a change in an order position
:param count: An amount, multiplicator for price etc. For order positions this can *currently* only be -1 or +1, for
fees it can also be more in special cases
:param item: ``Item``, in case this transaction represents a change in an order position
:param variation: ``ItemVariation``, in case this transaction represents a change in an order position
:param subevent: ``subevent``, in case this transaction represents a change in an order position
:param price: Price of the changed position
:param tax_rate: Tax rate of the changed position
:param tax_rule: Used tax rule
:param tax_value: Tax value in event currency
:param fee_type: Fee type code in case this transaction represents a change in an order fee
:param internal_type: Internal fee type in case this transaction represents a change in an order fee
"""
id = models.BigAutoField(primary_key=True)
order = models.ForeignKey(
Order,
verbose_name=_("Order"),
related_name='transactions',
on_delete=models.PROTECT
)
created = models.DateTimeField(
auto_now_add=True,
db_index=True,
)
datetime = models.DateTimeField(
verbose_name=_("Date"),
db_index=True,
)
migrated = models.BooleanField(
default=False
)
positionid = models.PositiveIntegerField(default=1, null=True, blank=True)
count = models.IntegerField(
default=1
)
item = models.ForeignKey(
Item,
null=True, blank=True,
verbose_name=_("Item"),
on_delete=models.PROTECT
)
variation = models.ForeignKey(
ItemVariation,
null=True, blank=True,
verbose_name=_("Variation"),
on_delete=models.PROTECT
)
subevent = models.ForeignKey(
SubEvent,
null=True, blank=True,
on_delete=models.PROTECT,
verbose_name=pgettext_lazy("subevent", "Date"),
)
price = models.DecimalField(
decimal_places=2, max_digits=10,
verbose_name=_("Price")
)
tax_rate = models.DecimalField(
max_digits=7, decimal_places=2,
verbose_name=_('Tax rate')
)
tax_rule = models.ForeignKey(
'TaxRule',
on_delete=models.PROTECT,
null=True, blank=True
)
tax_value = models.DecimalField(
max_digits=10, decimal_places=2,
verbose_name=_('Tax value')
)
fee_type = models.CharField(
max_length=100, choices=OrderFee.FEE_TYPES, null=True, blank=True
)
internal_type = models.CharField(max_length=255, null=True, blank=True)
class Meta:
ordering = 'datetime', 'pk'
def save(self, *args, **kwargs):
if not self.fee_type and not self.item:
raise ValidationError('Should set either item or fee type')
return super().save(*args, **kwargs)
@staticmethod
def key(obj):
if isinstance(obj, Transaction):
return (obj.positionid, obj.item_id, obj.variation_id, obj.subevent_id, obj.price, obj.tax_rate,
obj.tax_rule_id, obj.tax_value, obj.fee_type, obj.internal_type)
elif isinstance(obj, OrderPosition):
return (obj.positionid, obj.item_id, obj.variation_id, obj.subevent_id, obj.price, obj.tax_rate,
obj.tax_rule_id, obj.tax_value, None, None)
elif isinstance(obj, OrderFee):
return (None, None, None, None, obj.value, obj.tax_rate,
obj.tax_rule_id, obj.tax_value, obj.fee_type, obj.internal_type)
raise ValueError('invalid state') # noqa
@property
def full_price(self):
return self.price * self.count
@property
def full_tax_value(self):
return self.tax_value * self.count
class CartPosition(AbstractPosition):
"""
A cart position is similar to an order line, except that it is not
@@ -2612,7 +2351,8 @@ class InvoiceAddress(models.Model):
country = FastCountryField(verbose_name=_('Country'), blank=False, blank_label=_('Select country'),
countries=CachedCountries)
state = models.CharField(max_length=255, verbose_name=pgettext_lazy('address', 'State'), blank=True)
vat_id = models.CharField(max_length=255, blank=True, verbose_name=_('VAT ID'))
vat_id = models.CharField(max_length=255, blank=True, verbose_name=_('VAT ID'),
help_text=_('Only for business customers within the EU.'))
vat_id_validated = models.BooleanField(default=False)
custom_field = models.CharField(max_length=255, null=True, blank=True)
internal_reference = models.TextField(

View File

@@ -25,6 +25,7 @@ from decimal import Decimal
from django.core.exceptions import ValidationError
from django.db import models
from django.utils.formats import localize
from django.utils.timezone import get_current_timezone, now
from django.utils.translation import gettext_lazy as _, pgettext
from i18nfield.fields import I18nCharField
from i18nfield.strings import LazyI18nString
@@ -92,7 +93,7 @@ TAXED_ZERO = TaxedPrice(
EU_COUNTRIES = {
'AT', 'BE', 'BG', 'HR', 'CY', 'CZ', 'DK', 'EE', 'FI', 'FR', 'DE', 'GR', 'HU', 'IE', 'IT', 'LV', 'LT', 'LU', 'MT',
'NL', 'PL', 'PT', 'RO', 'SK', 'SI', 'ES', 'SE',
'NL', 'PL', 'PT', 'RO', 'SK', 'SI', 'ES', 'SE', 'GB'
}
EU_CURRENCIES = {
'BG': 'BGN',
@@ -105,21 +106,17 @@ EU_CURRENCIES = {
'RO': 'RON',
'SE': 'SEK'
}
VAT_ID_COUNTRIES = EU_COUNTRIES | {'CH'}
def is_eu_country(cc):
cc = str(cc)
return cc in EU_COUNTRIES
def ask_for_vat_id(cc):
cc = str(cc)
return cc in VAT_ID_COUNTRIES
if cc == 'GB':
return now().astimezone(get_current_timezone()).year <= 2020
else:
return cc in EU_COUNTRIES
def cc_to_vat_prefix(country_code):
country_code = str(country_code)
if country_code == 'GR':
return 'EL'
return country_code
@@ -165,13 +162,10 @@ class TaxRule(LoggedModel):
pass
def allow_delete(self):
from pretix.base.models.orders import (
OrderFee, OrderPosition, Transaction,
)
from pretix.base.models.orders import OrderFee, OrderPosition
return (
not Transaction.objects.filter(tax_rule=self, order__event=self.event).exists()
and not OrderFee.objects.filter(tax_rule=self, order__event=self.event).exists()
not OrderFee.objects.filter(tax_rule=self, order__event=self.event).exists()
and not OrderPosition.all.filter(tax_rule=self, order__event=self.event).exists()
and not self.event.items.filter(tax_rule=self).exists()
and self.event.settings.tax_rate_default != self

View File

@@ -1,191 +0,0 @@
#
# 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/>.
#
import logging
from datetime import timedelta
from django.db import models
from django.db.models import Case, F, OuterRef, Q, Subquery, Sum, Value, When
from django.db.models.functions import Cast, Coalesce, StrIndex, Substr
from django.dispatch import receiver
from django.utils.timezone import now
from django_scopes import scopes_disabled
from pretix.base.models import Check, Invoice, Order, OrderFee, OrderPosition
from pretix.base.models.orders import Transaction
from pretix.base.signals import periodic_task
from pretix.celery_app import app
from pretix.helpers.periodic import minimum_interval
logger = logging.getLogger(__name__)
def check_order_transactions():
qs = Order.objects.annotate(
position_total=Coalesce(
Subquery(
OrderPosition.objects.filter(
order=OuterRef('pk')
).order_by().values('order').annotate(p=Sum('price')).values('p'),
output_field=models.DecimalField(decimal_places=2, max_digits=10)
), Value(0), output_field=models.DecimalField(decimal_places=2, max_digits=10)
),
fee_total=Coalesce(
Subquery(
OrderFee.objects.filter(
order=OuterRef('pk')
).order_by().values('order').annotate(p=Sum('value')).values('p'),
output_field=models.DecimalField(decimal_places=2, max_digits=10)
), Value(0), output_field=models.DecimalField(decimal_places=2, max_digits=10)
),
tx_total=Coalesce(
Subquery(
Transaction.objects.filter(
order=OuterRef('pk')
).order_by().values('order').annotate(p=Sum(F('price') * F('count'))).values('p'),
output_field=models.DecimalField(decimal_places=2, max_digits=10)
), Value(0), output_field=models.DecimalField(decimal_places=2, max_digits=10)
),
).annotate(
correct_total=Case(
When(Q(status=Order.STATUS_CANCELED) | Q(status=Order.STATUS_EXPIRED) | Q(require_approval=True),
then=0),
default=F('position_total') + F('fee_total'),
output_field=models.DecimalField(decimal_places=2, max_digits=10)
),
).exclude(
tx_total=F('correct_total')
).select_related('event')
for o in qs:
yield [
Check.RESULT_ERROR,
f'Order {o.full_code} has a wrong total: Status is {o.status} and sum of positions and fees is '
f'{o.position_total + o.fee_total}, so sum of transactions should be {o.correct_total} but is {o.tx_total}'
]
yield [
Check.RESULT_OK,
'Check completed.'
]
def check_order_total():
qs = Order.objects.annotate(
position_total=Coalesce(
Subquery(
OrderPosition.objects.filter(
order=OuterRef('pk')
).order_by().values('order').annotate(p=Sum('price')).values('p'),
output_field=models.DecimalField(decimal_places=2, max_digits=10)
), Value(0), output_field=models.DecimalField(decimal_places=2, max_digits=10)
),
fee_total=Coalesce(
Subquery(
OrderFee.objects.filter(
order=OuterRef('pk')
).order_by().values('order').annotate(p=Sum('value')).values('p'),
output_field=models.DecimalField(decimal_places=2, max_digits=10)
), Value(0), output_field=models.DecimalField(decimal_places=2, max_digits=10)
),
).exclude(
total=F('position_total') + F('fee_total'),
).select_related('event')
for o in qs:
if o.total != o.position_total + o.fee_total:
yield [
Check.RESULT_ERROR,
f'Order {o.full_code} has a wrong total: Sum of positions and fees is '
f'{o.position_total + o.fee_total}, but total is {o.total}'
]
yield [
Check.RESULT_OK,
'Check completed.'
]
def check_invoice_gaps():
group_qs = Invoice.objects.annotate(
sub_prefix=Substr('invoice_no', 1, StrIndex('invoice_no', Value('-'))),
).order_by().values(
'organizer', 'prefix', 'sub_prefix', 'organizer__slug'
)
for g in group_qs:
numbers = Invoice.objects.filter(
prefix=g['prefix'], organizer=g['organizer']
)
if g['sub_prefix']:
numbers = numbers.filter(invoice_no__startswith=g['sub_prefix']).alias(
real_number=Cast(Substr('invoice_no', StrIndex('invoice_no', Value('-')) + 1), models.IntegerField())
).order_by('real_number')
else:
numbers = numbers.exclude(invoice_no__contains='-').order_by('invoice_no')
numbers = list(numbers.values_list('invoice_no', flat=True))
previous_n = "(initial state)"
previous_numeric_part = 0
for n in numbers:
numeric_part = int(n.split("-")[-1])
if numeric_part != previous_numeric_part + 1:
print(g)
yield [
Check.RESULT_WARNING,
f'Organizer {g["organizer__slug"]}, prefix {g["prefix"]}, invoice {n} follows on {previous_n} with gap'
]
previous_n = n
previous_numeric_part = numeric_part
yield [
Check.RESULT_OK,
'Check completed.'
]
@app.task()
@scopes_disabled()
def run_default_consistency_checks():
check_functions = [
('pretix.orders.transactions', check_order_transactions),
('pretix.orders.total', check_order_total),
('pretix.invoices.gaps', check_invoice_gaps),
]
for check_type, check_function in check_functions:
r = Check.RESULT_OK
log = []
try:
for result, logline in check_function():
if result == Check.RESULT_WARNING and r == Check.RESULT_OK:
r = Check.RESULT_WARNING
elif result == Check.RESULT_ERROR:
r = Check.RESULT_ERROR
log.append(f'[{result}] {logline}')
except Exception as e:
logger.exception('Could not run consistency check')
r = Check.RESULT_ERROR
log.append(f'[error] Check aborted: {e}')
Check.objects.create(result=r, check_type=check_type, log='\n'.join(log))
Check.objects.filter(created__lt=now() - timedelta(days=90)).delete()
@receiver(signal=periodic_task)
@minimum_interval(minutes_after_success=24 * 60)
def periodic_consistency_checks(sender, **kwargs):
run_default_consistency_checks.apply_async()

View File

@@ -291,7 +291,6 @@ def generate_cancellation(invoice: Invoice, trigger_pdf=True):
cancellation.payment_provider_text = ''
cancellation.file = None
cancellation.sent_to_organizer = None
cancellation.sent_to_customer = None
with language(invoice.locale, invoice.event.settings.region):
cancellation.invoice_from = invoice.event.settings.get('invoice_address_from')
cancellation.invoice_from_name = invoice.event.settings.get('invoice_address_from_name')
@@ -347,8 +346,8 @@ def invoice_pdf_task(invoice: int):
i.file.delete()
with language(i.locale, i.event.settings.region):
fname, ftype, fcontent = i.event.invoice_renderer.generate(i)
i.file.save(fname, ContentFile(fcontent), save=False)
i.save(update_fields=['file'])
i.file.save(fname, ContentFile(fcontent))
i.save()
return i.file.name

View File

@@ -32,7 +32,7 @@
# Unless required by applicable law or agreed to in writing, software distributed under the Apache License 2.0 is
# 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 hashlib
import inspect
import logging
import os
@@ -57,7 +57,7 @@ from django.core.mail import (
from django.core.mail.message import SafeMIMEText
from django.db import transaction
from django.template.loader import get_template
from django.utils.timezone import now, override
from django.utils.timezone import override
from django.utils.translation import gettext as _, pgettext
from django_scopes import scope, scopes_disabled
from i18nfield.strings import LazyI18nString
@@ -438,7 +438,6 @@ def mail_send_task(self, *args, to: List[str], subject: str, body: str, html: st
email = email_filter.send_chained(event, 'message', message=email, order=order, user=user)
invoices_sent = []
if invoices:
invoices = Invoice.objects.filter(pk__in=invoices)
for inv in invoices:
@@ -450,7 +449,6 @@ def mail_send_task(self, *args, to: List[str], subject: str, body: str, html: st
inv.file.file.read(),
'application/pdf'
)
invoices_sent.append(inv)
except:
logger.exception('Could not attach invoice to email')
pass
@@ -474,30 +472,10 @@ def mail_send_task(self, *args, to: List[str], subject: str, body: str, html: st
try:
backend.send_messages([email])
except (smtplib.SMTPResponseException, smtplib.SMTPSenderRefused) as e:
if e.smtp_code in (101, 111, 421, 422, 431, 432, 442, 447, 452):
if e.smtp_code == 432 and settings.HAS_REDIS:
# This is likely Microsoft Exchange Online which has a pretty bad rate limit of max. 3 concurrent
# SMTP connections which is *easily* exceeded with many celery threads. Just retrying with exponential
# backoff won't be good enough if we have a lot of emails, instead we'll need to make sure our retry
# intervals scatter such that the email won't all be retried at the same time again and cause the
# same problem.
# See also https://docs.microsoft.com/en-us/exchange/troubleshoot/send-emails/smtp-submission-improvements
from django_redis import get_redis_connection
redis_key = "pretix_mail_retry_" + hashlib.sha1(f"{getattr(backend, 'username', '_')}@{getattr(backend, 'host', '_')}".encode()).hexdigest()
rc = get_redis_connection("redis")
cnt = rc.incr(redis_key)
rc.expire(redis_key, 300)
max_retries = 10
retry_after = 30 + cnt * 10
else:
# Most likely some other kind of temporary failure, retry again (but pretty soon)
max_retries = 5
retry_after = 2 ** (self.request.retries * 3) # max is 2 ** (4*3) = 4096 seconds = 68 minutes
if e.smtp_code in (101, 111, 421, 422, 431, 442, 447, 452):
# Most likely temporary, retry again (but pretty soon)
try:
self.retry(max_retries=max_retries, countdown=retry_after)
self.retry(max_retries=5, countdown=2 ** (self.request.retries * 3)) # max is 2 ** (4*3) = 4096 seconds = 68 minutes
except MaxRetriesExceededError:
if log_target:
log_target.log_action(
@@ -580,10 +558,6 @@ def mail_send_task(self, *args, to: List[str], subject: str, body: str, html: st
)
logger.exception('Error sending email')
raise SendMailException('Failed to send an email to {}.'.format(to))
else:
for i in invoices_sent:
i.sent_to_customer = now()
i.save(update_fields=['sent_to_customer'])
def mail_send(*args, **kwargs):

View File

@@ -33,7 +33,6 @@ from pretix.base.models import (
CachedFile, Event, InvoiceAddress, Order, OrderPayment, OrderPosition,
User,
)
from pretix.base.models.orders import Transaction
from pretix.base.orderimport import get_all_columns
from pretix.base.services.invoices import generate_invoice, invoice_qualified
from pretix.base.services.tasks import ProfiledEventTask
@@ -147,7 +146,6 @@ def import_orders(event: Event, fileid: str, settings: dict, locale: str, user)
# quota check?
with event.lock():
with transaction.atomic():
save_transactions = []
for o in orders:
o.total = sum([c.price for c in o._positions]) # currently no support for fees
if o.total == Decimal('0.00'):
@@ -189,8 +187,6 @@ def import_orders(event: Event, fileid: str, settings: dict, locale: str, user)
user=user,
data={'source': 'import'}
)
save_transactions += o.create_transactions(is_new=True, fees=[], positions=o._positions, save=False)
Transaction.objects.bulk_create(save_transactions)
for o in orders:
with language(o.locale, event.settings.region):

View File

@@ -181,7 +181,6 @@ def reactivate_order(order: Order, force: bool=False, user: User=None, auth=None
for m in position.granted_memberships.all():
m.canceled = False
m.save()
order.create_transactions()
else:
raise OrderError(is_available)
@@ -203,7 +202,6 @@ def extend_order(order: Order, new_date: datetime, force: bool=False, user: User
if new_date < now():
raise OrderError(_('The new expiry date needs to be in the future.'))
@transaction.atomic
def change(was_expired=True):
order.expires = new_date
if was_expired:
@@ -223,7 +221,6 @@ def extend_order(order: Order, new_date: datetime, force: bool=False, user: User
num_invoices = order.invoices.filter(is_cancellation=False).count()
if num_invoices > 0 and order.invoices.filter(is_cancellation=True).count() >= num_invoices and invoice_qualified(order):
generate_invoice(order)
order.create_transactions()
if order.status == Order.STATUS_PENDING:
change(was_expired=False)
@@ -265,7 +262,6 @@ def mark_order_expired(order, user=None, auth=None):
i = order.invoices.filter(is_cancellation=False).last()
if i and not i.refered.exists():
generate_cancellation(i)
order.create_transactions()
order_expired.send(order.event, order=order)
return order
@@ -284,7 +280,6 @@ def approve_order(order, user=None, send_mail: bool=True, auth=None, force=False
order.require_approval = False
order.set_expires(now(), order.event.subevents.filter(id__in=[p.subevent_id for p in order.positions.all()]))
order.save(update_fields=['require_approval', 'expires'])
order.create_transactions()
order.log_action('pretix.event.order.approved', user=user, auth=auth)
if order.total == Decimal('0.00'):
@@ -357,7 +352,6 @@ def deny_order(order, comment='', user=None, send_mail: bool=True, auth=None):
for position in order.positions.all():
if position.voucher:
Voucher.objects.filter(pk=position.voucher.pk).update(redeemed=Greatest(0, F('redeemed') - 1))
order.create_transactions()
order_denied.send(order.event, order=order)
@@ -478,8 +472,6 @@ def _cancel_order(order, user=None, send_mail: bool=True, api_token=None, device
data={'cancellation_fee': cancellation_fee})
order.cancellation_requests.all().delete()
order.create_transactions()
if send_mail:
email_template = order.event.settings.mail_text_order_canceled
with language(order.locale, order.event.settings.region):
@@ -912,8 +904,7 @@ def _create_order(event: Event, email: str, positions: List[CartPosition], now_d
fee=pf
)
orderpositions = OrderPosition.transform_cart_positions(positions, order)
order.create_transactions(positions=orderpositions, fees=fees, is_new=True)
OrderPosition.transform_cart_positions(positions, order)
order.log_action('pretix.event.order.placed')
if order.require_approval:
order.log_action('pretix.event.order.placed.require_approval')
@@ -1561,16 +1552,17 @@ class OrderChangeManager:
self.order.save()
elif self.open_payment:
try:
self.open_payment.payment_provider.cancel_payment(self.open_payment)
self.order.log_action(
'pretix.event.order.payment.canceled',
{
'local_id': self.open_payment.local_id,
'provider': self.open_payment.provider,
},
user=self.user,
auth=self.auth
)
with transaction.atomic():
self.open_payment.payment_provider.cancel_payment(self.open_payment)
self.order.log_action(
'pretix.event.order.payment.canceled',
{
'local_id': self.open_payment.local_id,
'provider': self.open_payment.provider,
},
user=self.user,
auth=self.auth
)
except PaymentException as e:
self.order.log_action(
'pretix.event.order.payment.canceled.failed',
@@ -1585,11 +1577,12 @@ class OrderChangeManager:
elif self.order.status in (Order.STATUS_PENDING, Order.STATUS_EXPIRED) and self._totaldiff > 0:
if self.open_payment:
try:
self.open_payment.payment_provider.cancel_payment(self.open_payment)
self.order.log_action('pretix.event.order.payment.canceled', {
'local_id': self.open_payment.local_id,
'provider': self.open_payment.provider,
}, user=self.user, auth=self.auth)
with transaction.atomic():
self.open_payment.payment_provider.cancel_payment(self.open_payment)
self.order.log_action('pretix.event.order.payment.canceled', {
'local_id': self.open_payment.local_id,
'provider': self.open_payment.provider,
}, user=self.user, auth=self.auth)
except PaymentException as e:
self.order.log_action(
'pretix.event.order.payment.canceled.failed',
@@ -2129,13 +2122,10 @@ class OrderChangeManager:
self._recalculate_total_and_payment_fee()
if self.order.status in (Order.STATUS_PENDING, Order.STATUS_PAID):
self._reissue_invoice()
self._check_paid_price_change()
self._check_paid_to_free()
self._clear_tickets_cache()
self.order.touch()
self.order.create_transactions()
if self.split_order:
self.split_order.create_transactions()
self._check_paid_price_change()
self._check_paid_to_free()
if self.notify:
notify_user_changed_order(
@@ -2409,7 +2399,6 @@ def change_payment_provider(order: Order, payment_provider, amount=None, new_pay
generate_cancellation(i)
generate_invoice(order)
order.create_transactions()
return old_fee, new_fee, fee, new_payment

View File

@@ -1,130 +0,0 @@
#
# 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/>.
#
import logging
import re
from urllib.error import HTTPError
import vat_moss.errors
import vat_moss.id
from django.utils.translation import gettext_lazy as _
from zeep import Client, Transport
from zeep.cache import SqliteCache
from zeep.exceptions import Fault
from pretix.base.models.tax import cc_to_vat_prefix, is_eu_country
logger = logging.getLogger(__name__)
class VATIDError(Exception):
def __init__(self, message):
self.message = message
class VATIDFinalError(VATIDError):
pass
class VATIDTemporaryError(VATIDError):
pass
def _validate_vat_id_EU(vat_id, country_code):
if vat_id[:2] != cc_to_vat_prefix(country_code):
raise VATIDFinalError(_('Your VAT ID does not match the selected country.'))
try:
result = vat_moss.id.validate(vat_id)
if result:
country_code, normalized_id, company_name = result
return normalized_id
except (vat_moss.errors.InvalidError, ValueError):
raise VATIDFinalError(_('This VAT ID is not valid. Please re-check your input.'))
except vat_moss.errors.WebServiceUnavailableError:
logger.exception('VAT ID checking failed for country {}'.format(country_code))
raise VATIDTemporaryError(_(
'Your VAT ID could not be checked, as the VAT checking service of '
'your country is currently not available. We will therefore '
'need to charge VAT on your invoice. You can get the tax amount '
'back via the VAT reimbursement process.'
))
except (vat_moss.errors.WebServiceError, HTTPError):
logger.exception('VAT ID checking failed for country {}'.format(country_code))
raise VATIDTemporaryError(_(
'Your VAT ID could not be checked, as the VAT checking service of '
'your country returned an incorrect result. We will therefore '
'need to charge VAT on your invoice. Please contact support to '
'resolve this manually.'
))
def _validate_vat_id_CH(vat_id, country_code):
if vat_id[:3] != 'CHE':
raise VATIDFinalError(_('Your VAT ID does not match the selected country.'))
vat_id = re.sub('[^A-Z0-9]', '', vat_id.replace('HR', '').replace('MWST', ''))
try:
transport = Transport(cache=SqliteCache())
client = Client(
'https://www.uid-wse-a.admin.ch/V5.0/PublicServices.svc?wsdl',
transport=transport
)
if not client.service.ValidateUID(uid=vat_id):
raise VATIDFinalError(_('This VAT ID is not valid. Please re-check your input.'))
return vat_id
except Fault as e:
if e.message == 'Data_validation_failed':
raise VATIDFinalError(_('This VAT ID is not valid. Please re-check your input.'))
elif e.message == 'Request_limit_exceeded':
logger.exception('VAT ID checking failed for country {} due to request limit'.format(country_code))
raise VATIDTemporaryError(_(
'Your VAT ID could not be checked, as the VAT checking service of '
'your country returned an incorrect result. We will therefore '
'need to charge VAT on your invoice. Please contact support to '
'resolve this manually.'
))
else:
logger.exception('VAT ID checking failed for country {}'.format(country_code))
raise VATIDTemporaryError(_(
'Your VAT ID could not be checked, as the VAT checking service of '
'your country returned an incorrect result. We will therefore '
'need to charge VAT on your invoice. Please contact support to '
'resolve this manually.'
))
except:
logger.exception('VAT ID checking failed for country {}'.format(country_code))
raise VATIDTemporaryError(_(
'Your VAT ID could not be checked, as the VAT checking service of '
'your country is currently not available. We will therefore '
'need to charge VAT on your invoice. You can get the tax amount '
'back via the VAT reimbursement process.'
))
def validate_vat_id(vat_id, country_code):
country_code = str(country_code)
if is_eu_country(country_code):
return _validate_vat_id_EU(vat_id, country_code)
elif country_code == 'CH':
return _validate_vat_id_CH(vat_id, country_code)
raise VATIDTemporaryError(f'VAT ID should not be entered for country {country_code}')

View File

@@ -48,12 +48,10 @@ from django.core.validators import (
MaxValueValidator, MinValueValidator, RegexValidator,
)
from django.db.models import Model
from django.utils.functional import lazy
from django.utils.text import format_lazy
from django.utils.translation import (
gettext, gettext_lazy as _, gettext_noop, pgettext, pgettext_lazy,
gettext_lazy as _, gettext_noop, pgettext, pgettext_lazy,
)
from django_countries.fields import Country
from hierarkey.models import GlobalSettingsBase, Hierarkey
from i18nfield.forms import I18nFormField, I18nTextarea, I18nTextInput
from i18nfield.strings import LazyI18nString
@@ -63,7 +61,7 @@ from pretix.api.serializers.fields import (
ListMultipleChoiceField, UploadedFileField,
)
from pretix.api.serializers.i18n import I18nField
from pretix.base.models.tax import VAT_ID_COUNTRIES, TaxRule
from pretix.base.models.tax import TaxRule
from pretix.base.reldate import (
RelativeDateField, RelativeDateTimeField, RelativeDateWrapper,
SerializerRelativeDateField, SerializerRelativeDateTimeField,
@@ -372,11 +370,7 @@ DEFAULTS = {
'serializer_class': serializers.BooleanField,
'form_kwargs': dict(
label=_("Ask for VAT ID"),
help_text=format_lazy(
_("Only works if an invoice address is asked for. VAT ID is never required and only requested from "
"business customers in the following countries: {countries}"),
countries=lazy(lambda *args: ', '.join(sorted(gettext(Country(cc).name) for cc in VAT_ID_COUNTRIES)), str)()
),
help_text=_("Does only work if an invoice address is asked for. VAT ID is not required."),
widget=forms.CheckboxInput(attrs={'data-checkbox-dependency': '#id_invoice_address_asked'}),
)
},
@@ -1715,17 +1709,6 @@ Best regards,
Your {event} team"""))
},
'mail_days_order_expire_warning': {
'form_class': forms.IntegerField,
'serializer_class': serializers.IntegerField,
'serializer_kwargs': dict(
min_value=0,
),
'form_kwargs': dict(
label=_("Number of days"),
min_value=0,
help_text=_("This email will be sent out this many days before the order expires. If the "
"value is 0, the mail will never be sent.")
),
'type': int,
'default': '3'
},

View File

@@ -134,10 +134,6 @@
height: auto;
}
img {
display: block;
}
.content table {
width: 100%;
}

View File

@@ -0,0 +1,26 @@
{% extends "error.html" %}
{% load i18n %}
{% load rich_text %}
{% load static %}
{% block title %}{% trans "Redirect" %}{% endblock %}
{% block content %}
<i class="fa fa-link fa-fw big-icon"></i>
<div class="error-details">
<h1>{% trans "Redirect" %}</h1>
<h3>
{% blocktrans trimmed with host="<strong>"|add:hostname|add:"</strong>"|safe %}
The link you clicked on wants to redirect you to a destination on the website {{ host }}.
{% endblocktrans %}
{% blocktrans trimmed %}
Please only proceed if you trust this website to be safe.
{% endblocktrans %}
</h3>
<p>
<a href="{{ url }}" class="btn btn-primary btn-lg">
{% blocktrans trimmed with host=hostname %}
Proceed to {{ host }}
{% endblocktrans %}
</a>
</p>
</div>
{% endblock %}

View File

@@ -34,8 +34,6 @@ register = template.Library()
def money_filter(value: Decimal, arg='', hide_currency=False):
if isinstance(value, (float, int)):
value = Decimal(value)
if value is None:
value = Decimal('0.00')
if not isinstance(value, Decimal):
if value == '':
return value

View File

@@ -24,6 +24,21 @@ import urllib.parse
from django.core import signing
from django.http import HttpResponseBadRequest, HttpResponseRedirect
from django.urls import reverse
from django.shortcuts import render
def _is_samesite_referer(request):
referer = request.META.get('HTTP_REFERER')
if referer is None:
return False
referer = urllib.parse.urlparse(referer)
# Make sure we have a valid URL for Referer.
if '' in (referer.scheme, referer.netloc):
return False
return (referer.scheme, referer.netloc) == (request.scheme, request.get_host())
def redir_view(request):
@@ -32,6 +47,14 @@ def redir_view(request):
url = signer.unsign(request.GET.get('url', ''))
except signing.BadSignature:
return HttpResponseBadRequest('Invalid parameter')
if not _is_samesite_referer(request):
u = urllib.parse.urlparse(url)
return render(request, 'pretixbase/redirect.html', {
'hostname': u.hostname,
'url': url,
})
r = HttpResponseRedirect(url)
r['X-Robots-Tag'] = 'noindex'
return r

View File

@@ -607,7 +607,6 @@ class ItemUpdateForm(I18nModelForm):
'issue_giftcard',
'require_membership',
'require_membership_types',
'require_membership_hidden',
'grant_membership_type',
'grant_membership_duration_like_event',
'grant_membership_duration_days',
@@ -714,7 +713,6 @@ class ItemVariationForm(I18nModelForm):
'original_price',
'description',
'require_membership',
'require_membership_hidden',
'require_membership_types',
'available_from',
'available_until',

View File

@@ -57,8 +57,7 @@ from pretix.base.forms.widgets import (
DatePickerWidget, SplitDateTimePickerWidget,
)
from pretix.base.models import (
Invoice, InvoiceAddress, ItemAddOn, Order, OrderFee, OrderPosition,
TaxRule,
InvoiceAddress, ItemAddOn, Order, OrderFee, OrderPosition, TaxRule,
)
from pretix.base.models.event import SubEvent
from pretix.base.services.pricing import get_price
@@ -610,17 +609,6 @@ class OrderMailForm(forms.Form):
label=_("Subject"),
required=True
)
attach_tickets = forms.BooleanField(
label=_("Attach tickets"),
help_text=_("Will be ignored if all tickets in this order exceed a given size limit to ensure email deliverability."),
required=False
)
attach_invoices = forms.ModelMultipleChoiceField(
label=_("Attach invoices"),
widget=forms.CheckboxSelectMultiple,
required=False,
queryset=Invoice.objects.none()
)
def _set_field_placeholders(self, fn, base_parameters):
phs = [
@@ -653,7 +641,6 @@ class OrderMailForm(forms.Form):
widget=forms.Textarea,
initial=order.event.settings.mail_text_order_custom_mail.localize(order.locale),
)
self.fields['attach_invoices'].queryset = order.invoices.all()
self._set_field_placeholders('message', ['event', 'order'])
@@ -661,7 +648,6 @@ class OrderPositionMailForm(OrderMailForm):
def __init__(self, *args, **kwargs):
position = self.position = kwargs.pop('position')
super().__init__(*args, **kwargs)
del self.fields['attach_invoices']
self.fields['sendto'].initial = position.attendee_email
self.fields['message'] = forms.CharField(
label=_("Message"),

View File

@@ -422,11 +422,6 @@ def get_global_navigation(request):
'url': reverse('control:global.license'),
'active': (url.url_name == 'global.license'),
},
{
'label': _('Consistency check'),
'url': reverse('control:global.consistency'),
'active': (url.url_name == 'global.consistency'),
},
]
})

View File

@@ -59,7 +59,7 @@
<script type="text/javascript" src="{% static "colorpicker/bootstrap-colorpicker.js" %}"></script>
<script type="text/javascript" src="{% static "fileupload/jquery.ui.widget.js" %}"></script>
<script type="text/javascript" src="{% static "fileupload/jquery.fileupload.js" %}"></script>
<script type="text/javascript" src="{% static "lightbox/js/lightbox.js" %}"></script>
<script type="text/javascript" src="{% static "lightbox/js/lightbox.min.js" %}"></script>
<script type="text/javascript" src="{% static "are-you-sure/jquery.are-you-sure.js" %}"></script>
{% endcompress %}
{{ html_head|safe }}

View File

@@ -74,17 +74,17 @@
{{ c.datetime|date:"SHORT_DATETIME_FORMAT" }}
{% if c.type == "exit" %}
{% if c.auto_checked_in %}
<span class="fa fa-fw fa-hourglass-end" data-toggle="tooltip_html"
<span class="fa fa-fw fa-hourglass-end" data-toggle="tooltip"
title="{% blocktrans trimmed with date=c.datetime|date:'SHORT_DATETIME_FORMAT' %}Automatically marked not present: {{ date }}{% endblocktrans %}"></span>
{% endif %}
{% elif c.forced and c.successful %}
<span class="fa fa-fw fa-warning" data-toggle="tooltip_html"
<span class="fa fa-fw fa-warning" data-toggle="tooltip"
title="{% blocktrans trimmed with date=c.datetime|date:'SHORT_DATETIME_FORMAT' %}Additional entry scan: {{ date }}{% endblocktrans %}"></span>
{% elif c.forced and not c.successful %}
<br>
<small class="text-muted">{% trans "Failed in offline mode" %}</small>
{% elif c.auto_checked_in %}
<span class="fa fa-fw fa-magic" data-toggle="tooltip_html"
<span class="fa fa-fw fa-magic" data-toggle="tooltip"
title="{% blocktrans trimmed with date=c.datetime|date:'SHORT_DATETIME_FORMAT' %}Automatically checked in: {{ date }}{% endblocktrans %}"></span>
{% endif %}
</td>

View File

@@ -1,6 +1,6 @@
{% load i18n %}
<div class="quotabox availability" data-toggle="tooltip_html" data-placement="top"
title="{% trans "Quota:" %} {{ q.name }}<br>{% blocktrans with date=q.cached_availability_time|date:"SHORT_DATETIME_FORMAT" %}Numbers as of {{ date }}{% endblocktrans %}">
title="{% trans "Quota:" %} {{ q.name|force_escape|force_escape }}<br>{% blocktrans with date=q.cached_availability_time|date:"SHORT_DATETIME_FORMAT" %}Numbers as of {{ date }}{% endblocktrans %}">
{% if q.size|default_if_none:"NONE" == "NONE" %}
<div class="progress">
<div class="progress-bar progress-bar-success progress-bar-100">

View File

@@ -1,6 +1,6 @@
{% load i18n %}
<a class="quotabox" data-toggle="tooltip_html" data-placement="top"
title="{% trans "Quota:" %} {{ q.name }}{% if q.cached_avail.1 is not None %}<br>{% blocktrans with num=q.cached_avail.1 %}Currently available: {{ num }}{% endblocktrans %}{% endif %}"
title="{% trans "Quota:" %} {{ q.name|force_escape|force_escape }}{% if q.cached_avail.1 is not None %}<br>{% blocktrans with num=q.cached_avail.1 %}Currently available: {{ num }}{% endblocktrans %}{% endif %}"
href="{% url "control:event.items.quotas.show" event=q.event.slug organizer=q.event.organizer.slug quota=q.pk %}">
{% if q.size|default_if_none:"NONE" == "NONE" %}
<div class="progress">

View File

@@ -1,33 +0,0 @@
{% extends "pretixcontrol/global_settings_base.html" %}
{% load i18n %}
{% load bootstrap3 %}
{% block inner %}
<h2>{% trans "Consistency checks" %}</h2>
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th>{% trans "Date" %}</th>
<th>{% trans "Check type" %}</th>
<th>{% trans "Result" %}</th>
</tr>
</thead>
<tbody>
{% for r in results %}
<tr class="{% if r.result == "error" %}danger{% elif r.result == "warning" %}warning{% endif %}">
<td>{{ r.created|date:"SHORT_DATETIME_FORMAT" }}</td>
<td>{{ r.check_type }}</td>
<td>{{ r.get_result_display }}</td>
<td>
<a href="{% url "control:global.consistency.detail" pk=r.pk %}" class="btn btn-default">{% trans "Show log" %}</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock %}

View File

@@ -1,11 +0,0 @@
{% extends "pretixcontrol/global_settings_base.html" %}
{% load i18n %}
{% load bootstrap3 %}
{% block inner %}
<h2>{% trans "Consistency check" %}</h2>
<p>{{ result.created|date:"SHORT_DATETIME_FORMAT" }}</p>
<p>{{ result.check_type }}</p>
<p>{{ result.get_result_display }}</p>
<pre>{{ result.log }}</pre>
{% endblock %}

View File

@@ -77,7 +77,6 @@
{% bootstrap_field form.require_membership layout="control" %}
<div data-display-dependency="#{{ form.require_membership.id_for_label }}">
{% bootstrap_field form.require_membership_types layout="control" %}
{% bootstrap_field form.require_membership_hidden layout="control" %}
</div>
{% endif %}
</div>
@@ -148,7 +147,6 @@
{% bootstrap_field formset.empty_form.require_membership layout="control" %}
<div data-display-dependency="#{{ formset.empty_form.require_membership.id_for_label }}">
{% bootstrap_field formset.empty_form.require_membership_types layout="control" %}
{% bootstrap_field formset.empty_form.require_membership_hidden layout="control" %}
</div>
{% endif %}
</div>

View File

@@ -105,7 +105,6 @@
{% bootstrap_field form.require_membership layout="control" %}
<div data-display-dependency="#{{ form.require_membership.id_for_label }}">
{% bootstrap_field form.require_membership_types layout="control" %}
{% bootstrap_field form.require_membership_hidden layout="control" %}
</div>
{% endif %}
{% bootstrap_field form.allow_cancel layout="control" %}

View File

@@ -24,8 +24,6 @@
<a href="{% url "control:event.items.categories.add" organizer=request.event.organizer.slug event=request.event.slug %}" class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Create a new category" %}
</a>
</p>
<form method="post">
{% csrf_token %}
<div class="table-responsive">
<table class="table table-hover">
<thead>
@@ -35,16 +33,15 @@
<th class="action-col-2"></th>
</tr>
</thead>
<tbody data-dnd-url="{% url "control:event.items.categories.reorder" organizer=request.event.organizer.slug event=request.event.slug %}">
<tbody>
{% for c in categories %}
<tr data-dnd-id="{{ c.id }}">
<tr>
<td>
<strong><a href="{% url "control:event.items.categories.edit" organizer=request.event.organizer.slug event=request.event.slug category=c.id %}">{{ c.internal_name|default:c.name }}</a></strong>
</td>
<td>
<button formaction="{% url "control:event.items.categories.up" organizer=request.event.organizer.slug event=request.event.slug category=c.id %}" class="btn btn-default btn-sm sortable-up"{% if forloop.counter0 == 0 and not page_obj.has_previous %} disabled{% endif %}><i class="fa fa-arrow-up"></i></button>
<button formaction="{% url "control:event.items.categories.down" organizer=request.event.organizer.slug event=request.event.slug category=c.id %}" class="btn btn-default btn-sm sortable-down"{% if forloop.revcounter0 == 0 and not page_obj.has_next %} disabled{% endif %}><i class="fa fa-arrow-down"></i></button>
<span class="dnd-container"></span>
<a href="{% url "control:event.items.categories.up" organizer=request.event.organizer.slug event=request.event.slug category=c.id %}" class="btn btn-default btn-sm {% if forloop.counter0 == 0 and is_paginated and not page_obj.has_previous %}disabled{% endif %}"><i class="fa fa-arrow-up"></i></a>
<a href="{% url "control:event.items.categories.down" organizer=request.event.organizer.slug event=request.event.slug category=c.id %}" class="btn btn-default btn-sm {% if forloop.revcounter0 == 0 and is_paginated and not page_obj.has_next %}disabled{% endif %}"><i class="fa fa-arrow-down"></i></a>
</td>
<td class="text-right flip">
<a href="{% url "control:event.items.categories.edit" organizer=request.event.organizer.slug event=request.event.slug category=c.id %}" class="btn btn-default btn-sm"><i class="fa fa-edit"></i></a>
@@ -59,7 +56,6 @@
</tbody>
</table>
</div>
</form>
{% include "pretixcontrol/pagination.html" %}
{% endif %}
{% endblock %}

View File

@@ -26,8 +26,6 @@
<a href="{% url "control:event.items.add" organizer=request.event.organizer.slug event=request.event.slug %}"
class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Create a new product" %}</a>
</p>
<form method="post">
{% csrf_token %}
<div class="table-responsive">
<table class="table table-condensed table-hover">
<thead>
@@ -38,16 +36,15 @@
<th class="iconcol"></th>
<th class="iconcol"></th>
<th>{% trans "Category" %}</th>
<th class="action-col-2"><span class="sr-only">Move</span></th>
<th class="action-col-2"><span class="sr-only">Edit</span></th>
<th class="action-col-2"></th>
<th class="action-col-2"></th>
</tr>
</thead>
<tbody>
{% regroup items by category as cat_list %}
{% for c in cat_list %}
<tbody data-dnd-url="{% url "control:event.items.reorder" organizer=request.event.organizer.slug event=request.event.slug %}">
{% for i in c.list %}
{% if forloop.counter0 == 0 and i.category %}<tr class="sortable-disabled"><th colspan="8" scope="colgroup" class="text-muted">{{ i.category.name }}</th></tr>{% endif %}
<tr data-dnd-id="{{ i.id }}" {% if not i.active %}class="row-muted"{% endif %}>
<tr {% if not i.active %}class="row-muted"{% endif %}>
<td><strong>
{% if not i.active %}<strike>{% endif %}
<a href="{% url "control:event.item" organizer=request.event.organizer.slug event=request.event.slug item=i.id %}">{{ i }}</a>
@@ -108,9 +105,8 @@
</td>
<td>{% if i.category %}{{ i.category.name }}{% endif %}</td>
<td>
<button formaction="{% url "control:event.items.up" organizer=request.event.organizer.slug event=request.event.slug item=i.id %}" class="btn btn-default btn-sm sortable-up"{% if forloop.counter0 == 0 %} disabled{% endif %}><i class="fa fa-arrow-up"></i></button>
<button formaction="{% url "control:event.items.down" organizer=request.event.organizer.slug event=request.event.slug item=i.id %}" class="btn btn-default btn-sm sortable-down"{% if forloop.revcounter0 == 0 %} disabled{% endif %}><i class="fa fa-arrow-down"></i></button>
<span class="dnd-container"></span>
<a href="{% url "control:event.items.up" organizer=request.event.organizer.slug event=request.event.slug item=i.id %}" class="btn btn-default btn-sm {% if forloop.counter0 == 0 and is_paginated and not page_obj.has_previous %}disabled{% endif %}"><i class="fa fa-arrow-up"></i></a>
<a href="{% url "control:event.items.down" organizer=request.event.organizer.slug event=request.event.slug item=i.id %}" class="btn btn-default btn-sm {% if forloop.revcounter0 == 0 and is_paginated and not page_obj.has_next %}disabled{% endif %}"><i class="fa fa-arrow-down"></i></a>
</td>
<td class="text-right flip col-actions">
<a href="{% url "control:event.item" organizer=request.event.organizer.slug event=request.event.slug item=i.id %}" class="btn btn-default btn-sm"><i class="fa fa-edit"></i></a>
@@ -119,11 +115,10 @@
</td>
</tr>
{% endfor %}
</tbody>
{% endfor %}
</tbody>
</table>
</div>
</form>
{% include "pretixcontrol/pagination.html" %}
{% endif %}
{% endblock %}

View File

@@ -52,22 +52,18 @@
{% if order.status == 'n' or order.status == 'e' %}
<a href="{% url "control:event.order.transition" event=request.event.slug organizer=request.event.organizer.slug code=order.code %}?status=p"
class="btn {% if overpaid >= 0 %}btn-success{% else %}btn-default{% endif %}">
<span class="fa fa-check"></span>
{% trans "Mark as paid" %}
</a>
<a href="{% url "control:event.order.extend" event=request.event.slug organizer=request.event.organizer.slug code=order.code %}" class="btn btn-default">
<span class="fa fa-clock-o"></span>
{% trans "Extend payment term" %}
</a>
{% endif %}
{% if order.cancel_allowed %}
<a href="{% url "control:event.order.transition" event=request.event.slug organizer=request.event.organizer.slug code=order.code %}?status=c" class="btn btn-default">
<span class="fa fa-ban"></span>
{% trans "Cancel order" %}
</a>
{% elif order.status == 'c' %}
<a href="{% url "control:event.order.reactivate" event=request.event.slug organizer=request.event.organizer.slug code=order.code %}" class="btn btn-default">
<span class="fa fa-reply"></span>
{% trans "Reactivate order" %}
</a>
{% endif %}
@@ -75,17 +71,11 @@
<a href="{% eventurl request.event "presale:event.order" order=order.code secret=order.secret %}"
class="btn btn-default" target="_blank">
<span class="fa fa-eye"></span>
{% trans "View order as user" %}
</a>
<a href="{% url "control:event.order.mail_history" event=request.event.slug organizer=request.event.organizer.slug code=order.code %}" class="btn btn-default">
<span class="fa fa-envelope-o"></span>
{% trans "View email history" %}
</a>
<a href="{% url "control:event.order.transactions" event=request.event.slug organizer=request.event.organizer.slug code=order.code %}" class="btn btn-default">
<span class="fa fa-list"></span>
{% trans "View transaction history" %}
</a>
</div>
</div>
</form>
@@ -244,23 +234,7 @@
{% for i in invoices %}
<a href="{% url "control:event.invoice.download" invoice=i.pk event=request.event.slug organizer=request.event.organizer.slug %}">
{% if i.is_cancellation %}{% trans "Cancellation" context "invoice" %}{% else %}{% trans "Invoice" %}{% endif %}
{{ i.number }}</a>
({{ i.date|date:"SHORT_DATE_FORMAT" }})
{% if i.sent_to_customer.year == 1970 %}
<span class="fa-stack fa-stack-small" data-toggle="tooltip" title="{% trans "We don't know if this invoice was emailed to the customer since it was created before our system tracked this information" %}">
<span class="fa fa-background fa-envelope text-muted fa-stack-1x"></span>
<span class="fa fa-question fa-stack-1x fa-stack-shifted"></span>
</span>
{% elif i.sent_to_customer %}
<span class="fa-stack fa-stack-small" data-toggle="tooltip" title="{% trans "Invoice was emailed to customer" %}">
<span class="fa fa-background fa-envelope text-muted fa-stack-1x"></span>
<span class="fa fa-check text-success fa-stack-1x fa-stack-shifted"></span>
</span>
{% else %}
<span class="fa-stack fa-stack-small" data-toggle="tooltip" title="{% trans "Invoice was not yet emailed to customer" %}">
<span class="fa fa-background fa-envelope text-muted fa-stack-1x"></span>
</span>
{% endif %}
{{ i.number }}</a> ({{ i.date|date:"SHORT_DATE_FORMAT" }})
{% if not i.canceled %}
{% if request.event.settings.invoice_regenerate_allowed %}
<form class="form-inline helper-display-inline" method="post"
@@ -294,12 +268,6 @@
<br/>
{% endif %}
{% endfor %}
{% if invoices_send_link %}
<br/>
<a class="btn btn-default btn-xs" href="{{ invoices_send_link }}">
{% trans "Email invoices" %}
</a>
{% endif %}
{% if can_generate_invoice %}
<br/>
<form class="form-inline helper-display-inline" method="post"
@@ -360,19 +328,19 @@
{% if line.checkins.all %}
{% for c in line.all_checkins.all %}
{% if not c.successful %}
<span class="fa fa-fw fa-exclamation-circle text-danger" data-toggle="tooltip_html" title="{{ c.list.name }}<br>{% blocktrans trimmed with date=c.datetime|date:'SHORT_DATETIME_FORMAT' %}Denied scan: {{ date }}{% endblocktrans %}<br>{{ c.get_error_reason_display }}{% if c.gate %}<br>{{ c.gate }}{% endif %}"></span>
<span class="fa fa-fw fa-exclamation-circle text-danger" data-toggle="tooltip_html" title="{{ c.list.name|force_escape|force_escape }}<br>{% blocktrans trimmed with date=c.datetime|date:'SHORT_DATETIME_FORMAT' %}Denied scan: {{ date }}{% endblocktrans %}<br>{{ c.get_error_reason_display }}{% if c.gate %}<br>{{ c.gate }}{% endif %}"></span>
{% elif c.type == "exit" %}
{% if c.auto_checked_in %}
<span class="fa fa-fw text-success fa-hourglass-end" data-toggle="tooltip_html" title="{{ c.list.name }}<br>{% blocktrans trimmed with date=c.datetime|date:'SHORT_DATETIME_FORMAT' %}Automatically marked not present: {{ date }}{% endblocktrans %}"></span>
<span class="fa fa-fw text-success fa-hourglass-end" data-toggle="tooltip_html" title="{{ c.list.name|force_escape|force_escape }}<br>{% blocktrans trimmed with date=c.datetime|date:'SHORT_DATETIME_FORMAT' %}Automatically marked not present: {{ date }}{% endblocktrans %}"></span>
{% else %}
<span class="fa fa-fw text-success fa-sign-out" data-toggle="tooltip_html" title="{{ c.list.name }}<br>{% blocktrans trimmed with date=c.datetime|date:'SHORT_DATETIME_FORMAT' %}Exit scan: {{ date }}{% endblocktrans %}{% if c.gate %}<br>{{ c.gate }}{% endif %}"></span>
<span class="fa fa-fw text-success fa-sign-out" data-toggle="tooltip_html" title="{{ c.list.name|force_escape|force_escape }}<br>{% blocktrans trimmed with date=c.datetime|date:'SHORT_DATETIME_FORMAT' %}Exit scan: {{ date }}{% endblocktrans %}{% if c.gate %}<br>{{ c.gate }}{% endif %}"></span>
{% endif %}
{% elif c.forced %}
<span class="fa fa-fw fa-warning text-warning" data-toggle="tooltip_html" title="{{ c.list.name }}<br>{% blocktrans trimmed with date=c.datetime|date:'SHORT_DATETIME_FORMAT' %}Additional entry scan: {{ date }}{% endblocktrans %}{% if c.gate %}<br>{{ c.gate }}{% endif %}"></span>
<span class="fa fa-fw fa-warning text-warning" data-toggle="tooltip_html" title="{{ c.list.name|force_escape|force_escape }}<br>{% blocktrans trimmed with date=c.datetime|date:'SHORT_DATETIME_FORMAT' %}Additional entry scan: {{ date }}{% endblocktrans %}{% if c.gate %}<br>{{ c.gate }}{% endif %}"></span>
{% elif c.auto_checked_in %}
<span class="fa fa-fw fa-magic text-success" data-toggle="tooltip_html" title="{{ c.list.name }}<br>{% blocktrans trimmed with date=c.datetime|date:'SHORT_DATETIME_FORMAT' %}Automatically checked in: {{ date }}{% endblocktrans %}{% if c.gate %}<br>{{ c.gate }}{% endif %}"></span>
<span class="fa fa-fw fa-magic text-success" data-toggle="tooltip_html" title="{{ c.list.name|force_escape|force_escape }}<br>{% blocktrans trimmed with date=c.datetime|date:'SHORT_DATETIME_FORMAT' %}Automatically checked in: {{ date }}{% endblocktrans %}{% if c.gate %}<br>{{ c.gate }}{% endif %}"></span>
{% else %}
<span class="fa fa-fw fa-check text-success" data-toggle="tooltip_html" title="{{ c.list.name }}<br>{% blocktrans trimmed with date=c.datetime|date:'SHORT_DATETIME_FORMAT' %}Entry scan: {{ date }}{% endblocktrans %}{% if c.gate %}<br>{{ c.gate }}{% endif %}"></span>
<span class="fa fa-fw fa-check text-success" data-toggle="tooltip_html" title="{{ c.list.name|force_escape|force_escape }}<br>{% blocktrans trimmed with date=c.datetime|date:'SHORT_DATETIME_FORMAT' %}Entry scan: {{ date }}{% endblocktrans %}{% if c.gate %}<br>{{ c.gate }}{% endif %}"></span>
{% endif %}
{% endfor %}
{% endif %}

View File

@@ -28,7 +28,7 @@
<br/><span class="fa fa-fw fa-envelope-o"></span> {{ log.parsed_data.recipient }}
{% endif %}
</p>
{% if log.parsed_data.message.items %}
{% if log.parsed_data.subject.items %}
<div class="alert alert-info">
{% blocktrans trimmed %}
This email has been sent with an older version of pretix. We are therefore not able to

View File

@@ -18,10 +18,6 @@
{% bootstrap_field form.sendto layout='horizontal' %}
{% bootstrap_field form.subject layout='horizontal' %}
{% bootstrap_field form.message layout='horizontal' %}
{% bootstrap_field form.attach_tickets layout='horizontal' %}
{% if form.attach_invoices %}
{% bootstrap_field form.attach_invoices layout='horizontal' %}
{% endif %}
{% if request.method == "POST" %}
<fieldset>
<legend>{% trans "E-mail preview" %}</legend>

View File

@@ -1,81 +0,0 @@
{% extends "pretixcontrol/event/base.html" %}
{% load i18n %}
{% load bootstrap3 %}
{% load money %}
{% block title %}{% trans "Transaction history" %}{% endblock %}
{% block content %}
<h1>
{% trans "Transaction history" %}
<a class="btn btn-link btn-lg"
href="{% url "control:event.order" event=request.event.slug organizer=request.event.organizer.slug code=order.code %}">
{% blocktrans trimmed with order=order.code %}
Back to order {{ order }}
{% endblocktrans %}
</a>
</h1>
<table class="table table-condensed table-hover">
<thead>
<tr>
<th>{% trans "Date" %}</th>
<th>{% trans "Product" %}</th>
<th class="text-right flip">{% trans "Tax rate" %}</th>
<th class="text-right flip">{% trans "Quantity" %}</th>
<th class="text-right flip">{% trans "Single price" %}</th>
<th class="text-right flip">{% trans "Total tax value" %}</th>
<th class="text-right flip">{% trans "Total price" %}</th>
</tr>
</thead>
<tbody>
{% for t in transactions %}
<tr class="{% if t.count < 0 %}text-danger{% endif %}">
<td>
{{ t.datetime|date:"SHORT_DATETIME_FORMAT" }}
{% if t.migrated %}
<span class="fa fa-warning text-warning"
data-toggle="tooltip"
title="{% trans 'This order was created before we introduced this table, therefore this data might be inaccurate.' %}"
></span>
{% endif %}
</td>
<td>
{% if t.item %}
{{ t.item }}
{% if t.variation %}
{{ t.variation }}
{% endif %}
{% endif %}
{% if t.fee_type %}
{{ t.get_fee_type_display }}
{% endif %}
{% if t.subevent %}
<br>{{ t.subevent }}
{% endif %}
</td>
<td class="text-right flip">{{ t.tax_rate }} %</td>
<td class="text-right flip">{{ t.count }} &times;</td>
<td class="text-right flip">{{ t.price|money:request.event.currency }}</td>
<td class="text-right flip">{{ t.full_tax_value|money:request.event.currency }}</td>
<td class="text-right flip">{{ t.full_price|money:request.event.currency }}</td>
</tr>
{% endfor %}
</tbody>
<tfoot>
<tr class="{% if t.count < 0 %}text-danger{% endif %}">
<td>
<strong>{% trans "Sum" %}</strong>
</td>
<td>
</td>
<td></td>
<td class="text-right flip">
<strong>
{{ sums.count }}
</strong>
</td>
<td></td>
<td class="text-right flip"><strong>{{ sums.full_tax_value|money:request.event.currency }}</strong></td>
<td class="text-right flip"><strong>{{ sums.full_price|money:request.event.currency }}</strong></td>
</tr>
</tfoot>
</table>
{% endblock %}

View File

@@ -55,8 +55,6 @@ urlpatterns = [
re_path(r'^global/settings/$', global_settings.GlobalSettingsView.as_view(), name='global.settings'),
re_path(r'^global/update/$', global_settings.UpdateCheckView.as_view(), name='global.update'),
re_path(r'^global/license/$', global_settings.LicenseCheckView.as_view(), name='global.license'),
re_path(r'^global/consistency/$', global_settings.ConsistencyCheckListView.as_view(), name='global.consistency'),
re_path(r'^global/consistency/(?P<pk>\d+)/$', global_settings.ConsistencyCheckDetailView.as_view(), name='global.consistency.detail'),
re_path(r'^global/message/$', global_settings.MessageView.as_view(), name='global.message'),
re_path(r'^logdetail/$', global_settings.LogDetailView.as_view(), name='global.logdetail'),
re_path(r'^logdetail/payment/$', global_settings.PaymentDetailView.as_view(), name='global.paymentdetail'),
@@ -241,7 +239,6 @@ urlpatterns = [
re_path(r'^items/(?P<item>\d+)/$', item.ItemUpdateGeneral.as_view(), name='event.item'),
re_path(r'^items/(?P<item>\d+)/up$', item.item_move_up, name='event.items.up'),
re_path(r'^items/(?P<item>\d+)/down$', item.item_move_down, name='event.items.down'),
re_path(r'^items/reorder$', item.reorder_items, name='event.items.reorder'),
re_path(r'^items/(?P<item>\d+)/delete$', item.ItemDelete.as_view(), name='event.items.delete'),
re_path(r'^items/typeahead/meta/$', typeahead.item_meta_values, name='event.items.meta.typeahead'),
re_path(r'^items/select2$', typeahead.items_select2, name='event.items.select2'),
@@ -253,7 +250,6 @@ urlpatterns = [
re_path(r'^categories/(?P<category>\d+)/up$', item.category_move_up, name='event.items.categories.up'),
re_path(r'^categories/(?P<category>\d+)/down$', item.category_move_down,
name='event.items.categories.down'),
re_path(r'^categories/reorder$', item.reorder_categories, name='event.items.categories.reorder'),
re_path(r'^categories/(?P<category>\d+)/$', item.CategoryUpdate.as_view(),
name='event.items.categories.edit'),
re_path(r'^categories/add$', item.CategoryCreate.as_view(), name='event.items.categories.add'),
@@ -347,7 +343,6 @@ urlpatterns = [
re_path(r'^orders/(?P<code>[0-9A-Z]+)/cancellationrequests/(?P<req>\d+)/delete$',
orders.OrderCancellationRequestDelete.as_view(),
name='event.order.cancellationrequests.delete'),
re_path(r'^orders/(?P<code>[0-9A-Z]+)/transactions/$', orders.OrderTransactions.as_view(), name='event.order.transactions'),
re_path(r'^orders/(?P<code>[0-9A-Z]+)/$', orders.OrderDetail.as_view(), name='event.order'),
re_path(r'^invoice/(?P<invoice>[^/]+)$', orders.InvoiceDownload.as_view(),
name='event.invoice.download'),

View File

@@ -39,9 +39,9 @@ from django.shortcuts import get_object_or_404, redirect, reverse
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
from django.views import View
from django.views.generic import FormView, ListView, TemplateView, DetailView
from django.views.generic import FormView, TemplateView
from pretix.base.models import Check, LogEntry, OrderPayment, OrderRefund
from pretix.base.models import LogEntry, OrderPayment, OrderRefund
from pretix.base.services.update_check import check_result_table, update_check
from pretix.base.settings import GlobalSettingsObject
from pretix.control.forms.global_settings import (
@@ -265,15 +265,3 @@ class LicenseCheckView(StaffMemberRequiredMixin, FormView):
))
return res
class ConsistencyCheckListView(AdministratorPermissionRequiredMixin, ListView):
queryset = Check.objects.order_by('-created').defer('log')
context_object_name = 'results'
template_name = 'pretixcontrol/global_consistency.html'
class ConsistencyCheckDetailView(AdministratorPermissionRequiredMixin, DetailView):
queryset = Check.objects.all()
context_object_name = 'result'
template_name = 'pretixcontrol/global_consistency_detail.html'

View File

@@ -53,7 +53,6 @@ from django.urls import resolve, reverse
from django.utils.functional import cached_property
from django.utils.timezone import now
from django.utils.translation import gettext, gettext_lazy as _
from django.views.decorators.http import require_http_methods
from django.views.generic import ListView
from django.views.generic.detail import DetailView, SingleObjectMixin
from django.views.generic.edit import DeleteView
@@ -102,8 +101,7 @@ class ItemList(ListView):
).annotate(
var_count=Count('variations')
).prefetch_related("category").order_by(
F('category__position').desc(nulls_first=True),
'category', 'position'
'category__position', 'category', 'position'
)
def get_context_data(self, **kwargs):
@@ -140,7 +138,6 @@ def item_move(request, item, up=True):
@event_permission_required("can_change_items")
@require_http_methods(["POST"])
def item_move_up(request, organizer, event, item):
item_move(request, item, up=True)
return redirect('control:event.items',
@@ -149,7 +146,6 @@ def item_move_up(request, organizer, event, item):
@event_permission_required("can_change_items")
@require_http_methods(["POST"])
def item_move_down(request, organizer, event, item):
item_move(request, item, up=False)
return redirect('control:event.items',
@@ -157,38 +153,6 @@ def item_move_down(request, organizer, event, item):
event=request.event.slug)
@transaction.atomic
@event_permission_required("can_change_items")
@require_http_methods(["POST"])
def reorder_items(request, organizer, event):
try:
ids = json.loads(request.body.decode('utf-8'))['ids']
except (JSONDecodeError, KeyError, ValueError):
return HttpResponseBadRequest("expected JSON: {ids:[]}")
input_items = list(request.event.items.filter(id__in=[i for i in ids if i.isdigit()]))
if len(input_items) != len(ids):
raise Http404(_("Some of the provided item ids are invalid."))
item_categories = {i.category_id for i in input_items}
if len(item_categories) > 1:
raise Http404(_("You cannot reorder items spanning different categories."))
# get first and only category
item_category = next(iter(item_categories))
if len(input_items) != request.event.items.filter(category=item_category).count():
raise Http404(_("Not all items have been selected."))
for i in input_items:
pos = ids.index(str(i.pk))
if pos != i.position: # Save unneccessary UPDATE queries
i.position = pos
i.save(update_fields=['position'])
return HttpResponse()
class CategoryDelete(EventPermissionRequiredMixin, DeleteView):
model = ItemCategory
form_class = CategoryForm
@@ -343,7 +307,6 @@ def category_move(request, category, up=True):
@event_permission_required("can_change_items")
@require_http_methods(["POST"])
def category_move_up(request, organizer, event, category):
category_move(request, category, up=True)
return redirect('control:event.items.categories',
@@ -352,7 +315,6 @@ def category_move_up(request, organizer, event, category):
@event_permission_required("can_change_items")
@require_http_methods(["POST"])
def category_move_down(request, organizer, event, category):
category_move(request, category, up=False)
return redirect('control:event.items.categories',
@@ -360,32 +322,6 @@ def category_move_down(request, organizer, event, category):
event=request.event.slug)
@transaction.atomic
@event_permission_required("can_change_items")
@require_http_methods(["POST"])
def reorder_categories(request, organizer, event):
try:
ids = json.loads(request.body.decode('utf-8'))['ids']
except (JSONDecodeError, KeyError, ValueError):
return HttpResponseBadRequest("expected JSON: {ids:[]}")
input_categories = list(request.event.categories.filter(id__in=[i for i in ids if i.isdigit()]))
if len(input_categories) != len(ids):
raise Http404(_("Some of the provided category ids are invalid."))
if len(input_categories) != request.event.categories.count():
raise Http404(_("Not all categories have been selected."))
for c in input_categories:
pos = ids.index(str(c.pk))
if pos != c.position: # Save unneccessary UPDATE queries
c.position = pos
c.save(update_fields=['position'])
return HttpResponse()
FakeQuestion = namedtuple(
'FakeQuestion', 'id question position required'
)
@@ -487,21 +423,18 @@ class QuestionList(ListView):
@transaction.atomic
@event_permission_required("can_change_items")
@require_http_methods(["POST"])
def reorder_questions(request, organizer, event):
try:
ids = json.loads(request.body.decode('utf-8'))['ids']
except (JSONDecodeError, KeyError, ValueError):
return HttpResponseBadRequest("expected JSON: {ids:[]}")
# filter system_questions - normal questions are int/digit, system_questions strings
custom_question_ids = [i for i in ids if i.isdigit()]
input_questions = list(request.event.questions.filter(id__in=custom_question_ids))
input_questions = request.event.questions.filter(id__in=[i for i in ids if i.isdigit()])
if len(input_questions) != len(custom_question_ids):
if input_questions.count() != len([i for i in ids if i.isdigit()]):
raise Http404(_("Some of the provided question ids are invalid."))
if len(input_questions) != request.event.questions.count():
if input_questions.count() != request.event.questions.count():
raise Http404(_("Not all questions have been selected."))
for q in input_questions:

View File

@@ -42,6 +42,7 @@ from datetime import datetime, time, timedelta
from decimal import Decimal, DecimalException
from urllib.parse import quote, urlencode
import vat_moss.id
from django import forms
from django.conf import settings
from django.contrib import messages
@@ -49,7 +50,7 @@ from django.core.exceptions import ValidationError
from django.core.files import File
from django.db import transaction
from django.db.models import (
Count, Exists, F, IntegerField, OuterRef, Prefetch, ProtectedError, Q,
Count, Exists, IntegerField, OuterRef, Prefetch, ProtectedError, Q,
Subquery, Sum,
)
from django.forms import formset_factory
@@ -64,7 +65,7 @@ from django.utils.formats import date_format, get_format
from django.utils.functional import cached_property
from django.utils.http import is_safe_url
from django.utils.timezone import make_aware, now
from django.utils.translation import gettext, gettext_lazy as _, ngettext
from django.utils.translation import gettext, gettext_lazy as _
from django.views.generic import (
DetailView, FormView, ListView, TemplateView, View,
)
@@ -82,7 +83,7 @@ from pretix.base.models import (
from pretix.base.models.orders import (
CancellationRequest, OrderFee, OrderPayment, OrderPosition, OrderRefund,
)
from pretix.base.models.tax import ask_for_vat_id
from pretix.base.models.tax import cc_to_vat_prefix, is_eu_country
from pretix.base.payment import PaymentException
from pretix.base.secrets import assign_ticket_secret
from pretix.base.services import tickets
@@ -102,9 +103,6 @@ from pretix.base.services.orders import (
notify_user_changed_order, reactivate_order,
)
from pretix.base.services.stats import order_overview
from pretix.base.services.tax import (
VATIDFinalError, VATIDTemporaryError, validate_vat_id,
)
from pretix.base.services.tickets import generate
from pretix.base.signals import (
order_modified, register_data_exporters, register_ticket_outputs,
@@ -315,27 +313,6 @@ class OrderDetail(OrderView):
ctx['download_buttons'] = self.download_buttons
ctx['payment_refund_sum'] = self.order.payment_refund_sum
ctx['pending_sum'] = self.order.pending_sum
unsent_invoices = [ii.pk for ii in ctx['invoices'] if not ii.sent_to_customer]
if unsent_invoices:
ctx['invoices_send_link'] = reverse('control:event.order.sendmail', kwargs={
'event': self.request.event.slug,
'organizer': self.request.event.organizer.slug,
'code': self.order.code
}) + '?' + urlencode({
'subject': ngettext('Your invoice', 'Your invoices', len(unsent_invoices)),
'message': ngettext(
'Hello,\n\nplease find your invoice attached to this email.\n\n'
'Your {event} team',
'Hello,\n\nplease find your invoices attached to this email.\n\n'
'Your {event} team',
len(unsent_invoices)
).format(
event="{event}",
),
'attach_invoices': unsent_invoices
}, doseq=True)
return ctx
@cached_property
@@ -403,23 +380,6 @@ class OrderDetail(OrderView):
}
class OrderTransactions(OrderView):
template_name = 'pretixcontrol/order/transactions.html'
permission = 'can_view_orders'
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx['transactions'] = self.order.transactions.select_related(
'item', 'variation', 'subevent'
).order_by('datetime')
ctx['sums'] = self.order.transactions.aggregate(
count=Sum('count'),
full_price=Sum(F('count') * F('price')),
full_tax_value=Sum(F('count') * F('tax_value')),
)
return ctx
class OrderDownload(AsyncAction, OrderView):
task = generate
permission = 'can_view_orders'
@@ -1332,18 +1292,26 @@ class OrderCheckVATID(OrderView):
messages.error(self.request, _('No country specified.'))
return redirect(self.get_order_url())
if not ask_for_vat_id(ia.country):
messages.error(self.request, _('VAT ID could not be checked since this country is not supported.'))
if not is_eu_country(ia.country):
messages.error(self.request, _('VAT ID could not be checked since a non-EU country has been '
'specified.'))
return redirect(self.get_order_url())
if ia.vat_id[:2] != cc_to_vat_prefix(str(ia.country)):
messages.error(self.request, _('Your VAT ID does not match the selected country.'))
return redirect(self.get_order_url())
try:
normalized_id = validate_vat_id(ia.vat_id, str(ia.country))
ia.vat_id_validated = True
ia.vat_id = normalized_id
ia.save()
except VATIDFinalError as e:
messages.error(self.request, e.message)
except VATIDTemporaryError:
result = vat_moss.id.validate(ia.vat_id)
if result:
country_code, normalized_id, company_name = result
ia.vat_id_validated = True
ia.vat_id = normalized_id
ia.save()
except vat_moss.errors.InvalidError:
messages.error(self.request, _('This VAT ID is not valid.'))
except vat_moss.errors.WebServiceUnavailableError:
logger.exception('VAT ID checking failed for country {}'.format(ia.country))
messages.error(self.request, _('The VAT ID could not be checked, as the VAT checking service of '
'the country is currently not available.'))
else:
@@ -1986,8 +1954,6 @@ class OrderSendMail(EventPermissionRequiredMixin, OrderViewMixin, FormView):
kwargs['initial']['subject'] = self.request.GET.get('subject')
if self.request.GET.get('message'):
kwargs['initial']['message'] = self.request.GET.get('message')
if self.request.GET.getlist('attach_invoices'):
kwargs['initial']['attach_invoices'] = self.order.invoices.filter(pk__in=self.request.GET.getlist('attach_invoices'))
return kwargs
def form_invalid(self, form):
@@ -2016,9 +1982,7 @@ class OrderSendMail(EventPermissionRequiredMixin, OrderViewMixin, FormView):
order.send_mail(
form.cleaned_data['subject'], email_template,
email_context, 'pretix.event.order.email.custom_sent',
self.request.user, auto_email=False,
attach_tickets=form.cleaned_data.get('attach_tickets', False),
invoices=form.cleaned_data.get('attach_invoices', []),
self.request.user, auto_email=False
)
messages.success(self.request,
_('Your message has been queued and will be sent to {}.'.format(order.email)))
@@ -2083,8 +2047,7 @@ class OrderPositionSendMail(OrderSendMail):
email_template,
email_context,
'pretix.event.order.position.email.custom_sent',
self.request.user,
attach_tickets=form.cleaned_data.get('attach_tickets', False),
self.request.user
)
messages.success(self.request,
_('Your message has been queued and will be sent to {}.'.format(position.attendee_email)))

View File

@@ -273,7 +273,6 @@ class SubEventEditorMixin(MetaDataEditorMixin):
'size': q.size,
'name': q.name,
'release_after_exit': q.release_after_exit,
'ignore_for_event_availability': q.ignore_for_event_availability,
'itemvars': [str(i.pk) for i in q.items.all()] + [
'{}-{}'.format(v.item_id, v.pk) for v in q.variations.all()
]

View File

@@ -33,67 +33,36 @@
# License for the specific language governing permissions and limitations under the License.
from django.template.defaultfilters import date as _date
from django.utils.html import format_html
from django.utils.translation import get_language, gettext_lazy as _
def daterange(df, dt, as_html=False):
def daterange(df, dt):
lng = get_language()
if df.year == dt.year and df.month == dt.month and df.day == dt.day:
if as_html:
base_format = format_html("<time datetime=\"{}\">{{}}</time>", _date(df, "Y-m-d"))
else:
base_format = "{}"
else:
if as_html:
base_format = format_html("<time datetime=\"{}\">{{}}</time>{{}}<time datetime=\"{}\">{{}}</time>", _date(df, "Y-m-d"), _date(dt, "Y-m-d"))
else:
base_format = "{}{}{}"
if lng.startswith("de"):
if df.year == dt.year and df.month == dt.month and df.day == dt.day:
return format_html(base_format, _date(df, "j. F Y"))
return "{}".format(_date(df, "j. F Y"))
elif df.year == dt.year and df.month == dt.month:
return format_html(base_format, _date(df, "j."), "", _date(dt, "j. F Y"))
return "{}.{}".format(_date(df, "j"), _date(dt, "j. F Y"))
elif df.year == dt.year:
return format_html(base_format, _date(df, "j. F"), " ", _date(dt, "j. F Y"))
return "{} {}".format(_date(df, "j. F"), _date(dt, "j. F Y"))
elif lng.startswith("en"):
if df.year == dt.year and df.month == dt.month and df.day == dt.day:
return format_html(base_format, _date(df, "N jS, Y"))
return "{}".format(_date(df, "N jS, Y"))
elif df.year == dt.year and df.month == dt.month:
return format_html(base_format, _date(df, "N jS"), " ", _date(dt, "jS, Y"))
return "{} {}".format(_date(df, "N jS"), _date(dt, "jS, Y"))
elif df.year == dt.year:
return format_html(base_format, _date(df, "N jS"), " ", _date(dt, "N jS, Y"))
return "{} {}".format(_date(df, "N jS"), _date(dt, "N jS, Y"))
elif lng.startswith("es"):
if df.year == dt.year and df.month == dt.month and df.day == dt.day:
return format_html(base_format, _date(df, "DATE_FORMAT"))
return "{}".format(_date(df, "DATE_FORMAT"))
elif df.year == dt.year and df.month == dt.month:
return format_html(
base_format,
_date(df, "j"),
" - ",
"{} de {} de {}".format(_date(dt, "j"), _date(dt, "F"), _date(dt, "Y"))
)
return "{} - {} de {} de {}".format(_date(df, "j"), _date(dt, "j"), _date(dt, "F"), _date(dt, "Y"))
elif df.year == dt.year:
return format_html(
base_format,
"{} de {}".format(_date(df, "j"), _date(df, "F")),
" - ",
"{} de {} de {}".format(_date(dt, "j"), _date(dt, "F"), _date(dt, "Y"))
)
return "{} de {} - {} de {} de {}".format(_date(df, "j"), _date(df, "F"), _date(dt, "j"), _date(dt, "F"), _date(dt, "Y"))
if df.year == dt.year and df.month == dt.month and df.day == dt.day:
return format_html(base_format, _date(df, "DATE_FORMAT"))
if as_html:
base_format = "<time datetime=\"{}\">{}</time>"
return format_html(
"{date_from} {date_to}",
date_from=format_html(base_format, _date(df, "Y-m-d"), _date(df, "DATE_FORMAT")),
date_to=format_html(base_format, _date(dt, "Y-m-d"), _date(dt, "DATE_FORMAT")),
)
return _date(df, "DATE_FORMAT")
return _("{date_from} {date_to}").format(
date_from=_date(df, "DATE_FORMAT"),
date_to=_date(dt, "DATE_FORMAT"),
date_from=_date(df, "DATE_FORMAT"), date_to=_date(dt, "DATE_FORMAT")
)

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-10-17 14:57+0000\n"
"POT-Creation-Date: 2021-09-27 18:49+0000\n"
"PO-Revision-Date: 2021-09-15 11:22+0000\n"
"Last-Translator: Mohamed Tawfiq <mtawfiq@wafyapp.com>\n"
"Language-Team: Arabic <https://translate.pretix.eu/projects/pretix/pretix-js/"
@@ -199,14 +199,6 @@ msgstr "تذاكر سارية المفعول"
msgid "Currently inside"
msgstr "حاليا بالداخل"
#: pretix/static/lightbox/js/lightbox.js:90
#: pretix/static/lightbox/js/lightbox.min.js:13
#, fuzzy
#| msgctxt "widget"
#| msgid "Close"
msgid "close"
msgstr "إغلاق"
#: pretix/static/pretixbase/js/asynctask.js:43
#: pretix/static/pretixbase/js/asynctask.js:119
msgid ""
@@ -269,7 +261,7 @@ msgstr ""
"نعمل الآن على ارسال طلبك إلى الخادم، إذا أستغرقت العملية أكثر من دقيقة، يرجى "
"التحقق من اتصالك بالإنترنت ثم أعد تحميل الصفحة مرة أخرى."
#: pretix/static/pretixbase/js/asynctask.js:264
#: pretix/static/pretixbase/js/asynctask.js:262
#: pretix/static/pretixcontrol/js/ui/main.js:34
msgid "Close message"
msgstr "أغلق الرسالة"
@@ -498,25 +490,17 @@ msgstr[3] "أيام"
msgstr[4] "أيام عديدة"
msgstr[5] "أخرى"
#: pretix/static/pretixpresale/js/ui/cart.js:43
#, fuzzy
#| msgid "The items in your cart are no longer reserved for you."
msgid ""
"The items in your cart are no longer reserved for you. You can still "
"complete your order as long as theyre available."
#: pretix/static/pretixpresale/js/ui/cart.js:39
msgid "The items in your cart are no longer reserved for you."
msgstr "لم تعد العناصر الموجودة في عربة التسوق الخاصة بك محجوزة لك."
#: pretix/static/pretixpresale/js/ui/cart.js:45
#: pretix/static/pretixpresale/js/ui/cart.js:41
msgid "Cart expired"
msgstr "انتهت صلاحية عربة التسوق"
#: pretix/static/pretixpresale/js/ui/cart.js:50
#, fuzzy
#| msgid "The items in your cart are reserved for you for one minute."
#| msgid_plural ""
#| "The items in your cart are reserved for you for {num} minutes."
msgid "The items in your cart are reserved for you for one minute."
msgid_plural "The items in your cart are reserved for you for {num} minutes."
#: pretix/static/pretixpresale/js/ui/cart.js:46
msgid "The items in your cart are reserved for you for one minute."
msgid_plural "The items in your cart are reserved for you for {num} minutes."
msgstr[0] "سيتم حجز العناصر لك في سلة التسوق لمدة {num}."
msgstr[1] "سيتم حجز العناصر لك في سلة التسوق لمدة دقيقة {num}."
msgstr[2] "سيتم حجز العناصر لك في سلة التسوق لمدة دقيقتين {num}."
@@ -536,20 +520,20 @@ msgstr "ستسترد %(currency)%(amount)"
msgid "Please enter the amount the organizer can keep."
msgstr "الرجاء إدخال المبلغ الذي يمكن للمنظم الاحتفاظ به."
#: pretix/static/pretixpresale/js/ui/main.js:335
#: pretix/static/pretixpresale/js/ui/main.js:309
msgid "Please enter a quantity for one of the ticket types."
msgstr "الرجاء إدخال عدد لأحد أنواع التذاكر."
#: pretix/static/pretixpresale/js/ui/main.js:371
#: pretix/static/pretixpresale/js/ui/main.js:345
msgid "required"
msgstr "مطلوب"
#: pretix/static/pretixpresale/js/ui/main.js:473
#: pretix/static/pretixpresale/js/ui/main.js:491
#: pretix/static/pretixpresale/js/ui/main.js:447
#: pretix/static/pretixpresale/js/ui/main.js:465
msgid "Time zone:"
msgstr "المنطقة الزمنية:"
#: pretix/static/pretixpresale/js/ui/main.js:482
#: pretix/static/pretixpresale/js/ui/main.js:456
msgid "Your local time:"
msgstr "التوقيت المحلي:"

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-10-17 14:57+0000\n"
"POT-Creation-Date: 2021-09-27 18:49+0000\n"
"PO-Revision-Date: 2020-12-19 07:00+0000\n"
"Last-Translator: albert <albert.serra.monner@gmail.com>\n"
"Language-Team: Catalan <https://translate.pretix.eu/projects/pretix/pretix-"
@@ -194,11 +194,6 @@ msgstr ""
msgid "Currently inside"
msgstr ""
#: pretix/static/lightbox/js/lightbox.js:90
#: pretix/static/lightbox/js/lightbox.min.js:13
msgid "close"
msgstr ""
#: pretix/static/pretixbase/js/asynctask.js:43
#: pretix/static/pretixbase/js/asynctask.js:119
msgid ""
@@ -254,7 +249,7 @@ msgid ""
"page and try again."
msgstr ""
#: pretix/static/pretixbase/js/asynctask.js:264
#: pretix/static/pretixbase/js/asynctask.js:262
#: pretix/static/pretixcontrol/js/ui/main.js:34
msgid "Close message"
msgstr ""
@@ -479,25 +474,19 @@ msgid_plural "({num} more dates)"
msgstr[0] ""
msgstr[1] ""
#: pretix/static/pretixpresale/js/ui/cart.js:43
#, fuzzy
#| msgid "The items in your cart are no longer reserved for you."
msgid ""
"The items in your cart are no longer reserved for you. You can still "
"complete your order as long as theyre available."
#: pretix/static/pretixpresale/js/ui/cart.js:39
msgid "The items in your cart are no longer reserved for you."
msgstr "El contingut de la cistella ja no el teniu reservat."
#: pretix/static/pretixpresale/js/ui/cart.js:45
#: pretix/static/pretixpresale/js/ui/cart.js:41
msgid "Cart expired"
msgstr "Cistella expirada"
#: pretix/static/pretixpresale/js/ui/cart.js:50
#, fuzzy
#| msgid "The items in your cart are no longer reserved for you."
msgid "The items in your cart are reserved for you for one minute."
msgid_plural "The items in your cart are reserved for you for {num} minutes."
msgstr[0] "El contingut de la cistella ja no el teniu reservat."
msgstr[1] "El contingut de la cistella ja no el teniu reservat."
#: pretix/static/pretixpresale/js/ui/cart.js:46
msgid "The items in your cart are reserved for you for one minute."
msgid_plural "The items in your cart are reserved for you for {num} minutes."
msgstr[0] ""
msgstr[1] ""
#: pretix/static/pretixpresale/js/ui/main.js:144
msgid "The organizer keeps %(currency)s %(amount)s"
@@ -511,22 +500,22 @@ msgstr ""
msgid "Please enter the amount the organizer can keep."
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:335
#: pretix/static/pretixpresale/js/ui/main.js:309
msgid "Please enter a quantity for one of the ticket types."
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:371
#: pretix/static/pretixpresale/js/ui/main.js:345
#, fuzzy
#| msgid "Cart expired"
msgid "required"
msgstr "Cistella expirada"
#: pretix/static/pretixpresale/js/ui/main.js:473
#: pretix/static/pretixpresale/js/ui/main.js:491
#: pretix/static/pretixpresale/js/ui/main.js:447
#: pretix/static/pretixpresale/js/ui/main.js:465
msgid "Time zone:"
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:482
#: pretix/static/pretixpresale/js/ui/main.js:456
msgid "Your local time:"
msgstr ""

File diff suppressed because it is too large Load Diff

View File

@@ -7,9 +7,9 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-10-17 14:57+0000\n"
"PO-Revision-Date: 2021-10-18 19:00+0000\n"
"Last-Translator: Tony Pavlik <kontakt@playton.cz>\n"
"POT-Creation-Date: 2021-09-27 18:49+0000\n"
"PO-Revision-Date: 2021-08-11 05:00+0000\n"
"Last-Translator: Michael <michael.happl@gmx.at>\n"
"Language-Team: Czech <https://translate.pretix.eu/projects/pretix/pretix-js/"
"cs/>\n"
"Language: cs\n"
@@ -17,7 +17,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
"X-Generator: Weblate 4.8\n"
"X-Generator: Weblate 4.6\n"
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
@@ -194,11 +194,6 @@ msgstr "Platné vstupenky"
msgid "Currently inside"
msgstr "Právě uvnitř"
#: pretix/static/lightbox/js/lightbox.js:90
#: pretix/static/lightbox/js/lightbox.min.js:13
msgid "close"
msgstr "zavřít"
#: pretix/static/pretixbase/js/asynctask.js:43
#: pretix/static/pretixbase/js/asynctask.js:119
msgid ""
@@ -266,7 +261,7 @@ msgstr ""
"prosím zkontrolujte své internetové připojení a znovu načtěte stránku a "
"zkuste to znovu."
#: pretix/static/pretixbase/js/asynctask.js:264
#: pretix/static/pretixbase/js/asynctask.js:262
#: pretix/static/pretixcontrol/js/ui/main.js:34
msgid "Close message"
msgstr "Zavřít zprávu"
@@ -314,7 +309,7 @@ msgstr "Počet záznamů od půlnoci"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:79
msgid "Number of days with a previous entry"
msgstr "Počet dní bez úprav"
msgstr ""
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:97
msgid "All of the conditions below (AND)"
@@ -495,23 +490,21 @@ msgstr[0] "(jeden další termín)"
msgstr[1] "({num} další termíny)"
msgstr[2] "({num} dalších termínů)"
#: pretix/static/pretixpresale/js/ui/cart.js:43
msgid ""
"The items in your cart are no longer reserved for you. You can still "
"complete your order as long as theyre available."
msgstr ""
"Produkty v nákupním košíku již nejsou pro vás rezervovány. Pokud je lístek "
"stále dostupný, můžete objednávku dokončit."
#: pretix/static/pretixpresale/js/ui/cart.js:39
msgid "The items in your cart are no longer reserved for you."
msgstr "Produkty v nákupním košíku již nejsou pro vás rezervovány."
#: pretix/static/pretixpresale/js/ui/cart.js:45
#: pretix/static/pretixpresale/js/ui/cart.js:41
msgid "Cart expired"
msgstr "Nákupní košík vypršel"
#: pretix/static/pretixpresale/js/ui/cart.js:50
msgid "The items in your cart are reserved for you for one minute."
msgid_plural "The items in your cart are reserved for you for {num} minutes."
msgstr[0] "Produkty v nákupním košíku jsou pro vás rezervovány na jednu minutu."
msgstr[1] "Produkty v nákupním košíku jsou pro vás rezervovány na {num} minuty."
#: pretix/static/pretixpresale/js/ui/cart.js:46
msgid "The items in your cart are reserved for you for one minute."
msgid_plural "The items in your cart are reserved for you for {num} minutes."
msgstr[0] ""
"Produkty v nákupním košíku jsou pro vás rezervovány po dobu jedné minuty."
msgstr[1] ""
"Produkty v nákupním košíku jsou pro vás rezervovány na další {num} minuty."
msgstr[2] ""
"Produkty v nákupním košíku jsou pro vás rezervovány na dalších {num} minut."
@@ -527,20 +520,20 @@ msgstr "Dostanete %(currency)s %(amount)s zpět"
msgid "Please enter the amount the organizer can keep."
msgstr "Zadejte částku, kterou si organizátor může ponechat."
#: pretix/static/pretixpresale/js/ui/main.js:335
#: pretix/static/pretixpresale/js/ui/main.js:309
msgid "Please enter a quantity for one of the ticket types."
msgstr "Zadejte prosím množství pro jeden z typů vstupenek."
#: pretix/static/pretixpresale/js/ui/main.js:371
#: pretix/static/pretixpresale/js/ui/main.js:345
msgid "required"
msgstr "povinný"
#: pretix/static/pretixpresale/js/ui/main.js:473
#: pretix/static/pretixpresale/js/ui/main.js:491
#: pretix/static/pretixpresale/js/ui/main.js:447
#: pretix/static/pretixpresale/js/ui/main.js:465
msgid "Time zone:"
msgstr "Časové pásmo:"
#: pretix/static/pretixpresale/js/ui/main.js:482
#: pretix/static/pretixpresale/js/ui/main.js:456
msgid "Your local time:"
msgstr "Místní čas:"
@@ -682,7 +675,7 @@ msgstr "Uplatnit"
#: pretix/static/pretixpresale/js/widget/widget.js:44
msgctxt "widget"
msgid "Voucher code"
msgstr "Kód voucheru"
msgstr "Kód poukázky"
#: pretix/static/pretixpresale/js/widget/widget.js:45
msgctxt "widget"

File diff suppressed because it is too large Load Diff

View File

@@ -6,7 +6,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-10-17 14:57+0000\n"
"POT-Creation-Date: 2021-09-27 18:49+0000\n"
"PO-Revision-Date: 2021-09-13 09:48+0000\n"
"Last-Translator: Mie Frydensbjerg <mif@aarhus.dk>\n"
"Language-Team: Danish <https://translate.pretix.eu/projects/pretix/pretix-js/"
@@ -206,14 +206,6 @@ msgstr ""
msgid "Currently inside"
msgstr ""
#: pretix/static/lightbox/js/lightbox.js:90
#: pretix/static/lightbox/js/lightbox.min.js:13
#, fuzzy
#| msgctxt "widget"
#| msgid "Close"
msgid "close"
msgstr "Luk"
#: pretix/static/pretixbase/js/asynctask.js:43
#: pretix/static/pretixbase/js/asynctask.js:119
#, fuzzy
@@ -291,7 +283,7 @@ msgstr ""
"Din forespørgsel bliver sendt til serveren. Hvis det tager mere end et "
"minut, så tjek din internetforbindelse, genindlæs siden og prøv igen."
#: pretix/static/pretixbase/js/asynctask.js:264
#: pretix/static/pretixbase/js/asynctask.js:262
#: pretix/static/pretixcontrol/js/ui/main.js:34
msgid "Close message"
msgstr "Luk besked"
@@ -524,25 +516,17 @@ msgid_plural "({num} more dates)"
msgstr[0] "(en dato mere)"
msgstr[1] "({num} datoer mere)"
#: pretix/static/pretixpresale/js/ui/cart.js:43
#, fuzzy
#| msgid "The items in your cart are no longer reserved for you."
msgid ""
"The items in your cart are no longer reserved for you. You can still "
"complete your order as long as theyre available."
#: pretix/static/pretixpresale/js/ui/cart.js:39
msgid "The items in your cart are no longer reserved for you."
msgstr "Varerne i din kurv er ikke længere reserverede for dig."
#: pretix/static/pretixpresale/js/ui/cart.js:45
#: pretix/static/pretixpresale/js/ui/cart.js:41
msgid "Cart expired"
msgstr "Kurv udløbet"
#: pretix/static/pretixpresale/js/ui/cart.js:50
#, fuzzy
#| msgid "The items in your cart are reserved for you for one minute."
#| msgid_plural ""
#| "The items in your cart are reserved for you for {num} minutes."
msgid "The items in your cart are reserved for you for one minute."
msgid_plural "The items in your cart are reserved for you for {num} minutes."
#: pretix/static/pretixpresale/js/ui/cart.js:46
msgid "The items in your cart are reserved for you for one minute."
msgid_plural "The items in your cart are reserved for you for {num} minutes."
msgstr[0] "Varerne i din kurv er reserveret for dig i et minut."
msgstr[1] "Varerne i din kurv er reserveret for dig i {num} minutter."
@@ -564,22 +548,22 @@ msgstr "fra %(currency)s %(price)s"
msgid "Please enter the amount the organizer can keep."
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:335
#: pretix/static/pretixpresale/js/ui/main.js:309
msgid "Please enter a quantity for one of the ticket types."
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:371
#: pretix/static/pretixpresale/js/ui/main.js:345
#, fuzzy
#| msgid "Cart expired"
msgid "required"
msgstr "Kurv udløbet"
#: pretix/static/pretixpresale/js/ui/main.js:473
#: pretix/static/pretixpresale/js/ui/main.js:491
#: pretix/static/pretixpresale/js/ui/main.js:447
#: pretix/static/pretixpresale/js/ui/main.js:465
msgid "Time zone:"
msgstr "Tidszone:"
#: pretix/static/pretixpresale/js/ui/main.js:482
#: pretix/static/pretixpresale/js/ui/main.js:456
msgid "Your local time:"
msgstr "Din lokaltid:"

File diff suppressed because it is too large Load Diff

View File

@@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-10-17 14:57+0000\n"
"PO-Revision-Date: 2021-10-25 15:40+0000\n"
"POT-Creation-Date: 2021-09-27 18:49+0000\n"
"PO-Revision-Date: 2021-08-18 01:00+0000\n"
"Last-Translator: Raphael Michel <michel@rami.io>\n"
"Language-Team: German <https://translate.pretix.eu/projects/pretix/pretix-js/"
"de/>\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 4.8\n"
"X-Generator: Weblate 4.6\n"
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
@@ -194,11 +194,6 @@ msgstr "Gültige Tickets"
msgid "Currently inside"
msgstr "Derzeit anwesend"
#: pretix/static/lightbox/js/lightbox.js:90
#: pretix/static/lightbox/js/lightbox.min.js:13
msgid "close"
msgstr "schließen"
#: pretix/static/pretixbase/js/asynctask.js:43
#: pretix/static/pretixbase/js/asynctask.js:119
msgid ""
@@ -269,7 +264,7 @@ msgstr ""
"dauert, prüfen Sie bitte Ihre Internetverbindung. Danach können Sie diese "
"Seite neu laden und es erneut versuchen."
#: pretix/static/pretixbase/js/asynctask.js:264
#: pretix/static/pretixbase/js/asynctask.js:262
#: pretix/static/pretixcontrol/js/ui/main.js:34
msgid "Close message"
msgstr "Schließen"
@@ -500,22 +495,17 @@ msgid_plural "({num} more dates)"
msgstr[0] "(ein weiterer Termin)"
msgstr[1] "({num} weitere Termine)"
#: pretix/static/pretixpresale/js/ui/cart.js:43
msgid ""
"The items in your cart are no longer reserved for you. You can still "
"complete your order as long as theyre available."
msgstr ""
"Die Produkte in Ihrem Warenkorb sind nicht mehr für Sie reserviert. Sie "
"können die Bestellung trotzdem abschließen, solange die Produkte noch "
"verfügbar sind."
#: pretix/static/pretixpresale/js/ui/cart.js:39
msgid "The items in your cart are no longer reserved for you."
msgstr "Die Produkte in Ihrem Warenkorb sind nicht mehr für Sie reserviert."
#: pretix/static/pretixpresale/js/ui/cart.js:45
#: pretix/static/pretixpresale/js/ui/cart.js:41
msgid "Cart expired"
msgstr "Warenkorb abgelaufen"
#: pretix/static/pretixpresale/js/ui/cart.js:50
msgid "The items in your cart are reserved for you for one minute."
msgid_plural "The items in your cart are reserved for you for {num} minutes."
#: pretix/static/pretixpresale/js/ui/cart.js:46
msgid "The items in your cart are reserved for you for one minute."
msgid_plural "The items in your cart are reserved for you for {num} minutes."
msgstr[0] ""
"Die Produkte in Ihrem Warenkorb sind noch eine Minute für Sie reserviert."
msgstr[1] ""
@@ -533,20 +523,20 @@ msgstr "Sie erhalten %(currency)s %(amount)s zurück"
msgid "Please enter the amount the organizer can keep."
msgstr "Bitte geben Sie den Betrag ein, den der Veranstalter einbehalten darf."
#: pretix/static/pretixpresale/js/ui/main.js:335
#: pretix/static/pretixpresale/js/ui/main.js:309
msgid "Please enter a quantity for one of the ticket types."
msgstr "Bitte tragen Sie eine Menge für eines der Produkte ein."
#: pretix/static/pretixpresale/js/ui/main.js:371
#: pretix/static/pretixpresale/js/ui/main.js:345
msgid "required"
msgstr "verpflichtend"
#: pretix/static/pretixpresale/js/ui/main.js:473
#: pretix/static/pretixpresale/js/ui/main.js:491
#: pretix/static/pretixpresale/js/ui/main.js:447
#: pretix/static/pretixpresale/js/ui/main.js:465
msgid "Time zone:"
msgstr "Zeitzone:"
#: pretix/static/pretixpresale/js/ui/main.js:482
#: pretix/static/pretixpresale/js/ui/main.js:456
msgid "Your local time:"
msgstr "Deine lokale Zeit:"

View File

@@ -60,7 +60,6 @@ Cronjob
csv
Customer
Debug
deliverability
Di
Do
Doe
@@ -78,7 +77,6 @@ Einlassdatum
Einlassuhrzeit
email
Enterprise
EPC
EPS
Erstattungsbetrag
Erstattungsliste
@@ -104,7 +102,6 @@ Geo
geocoding
gescannt
ggf
GiroCode
giropay
GPL
Guide
@@ -188,7 +185,6 @@ pt
px
QR
Rabattierung
Reader
Rechnungs
Rechungsdatei
Reddit
@@ -263,7 +259,6 @@ txt
uhrzeit
ungespeicherte
unkategorisiert
Unkategorisierte
unlöschbar
unlöschbaren
untenstehende

File diff suppressed because it is too large Load Diff

View File

@@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-10-17 14:57+0000\n"
"PO-Revision-Date: 2021-10-25 15:40+0000\n"
"POT-Creation-Date: 2021-09-27 18:49+0000\n"
"PO-Revision-Date: 2021-08-18 01:00+0000\n"
"Last-Translator: Raphael Michel <michel@rami.io>\n"
"Language-Team: German (informal) <https://translate.pretix.eu/projects/"
"pretix/pretix-js/de_Informal/>\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 4.8\n"
"X-Generator: Weblate 4.6\n"
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
@@ -194,11 +194,6 @@ msgstr "Gültige Tickets"
msgid "Currently inside"
msgstr "Derzeit anwesend"
#: pretix/static/lightbox/js/lightbox.js:90
#: pretix/static/lightbox/js/lightbox.min.js:13
msgid "close"
msgstr "schließen"
#: pretix/static/pretixbase/js/asynctask.js:43
#: pretix/static/pretixbase/js/asynctask.js:119
msgid ""
@@ -268,7 +263,7 @@ msgstr ""
"dauert, prüfe bitte deine Internetverbindung. Danach kannst du diese Seite "
"neu laden und es erneut versuchen."
#: pretix/static/pretixbase/js/asynctask.js:264
#: pretix/static/pretixbase/js/asynctask.js:262
#: pretix/static/pretixcontrol/js/ui/main.js:34
msgid "Close message"
msgstr "Schließen"
@@ -499,22 +494,17 @@ msgid_plural "({num} more dates)"
msgstr[0] "(ein weiterer Termin)"
msgstr[1] "({num} weitere Termine)"
#: pretix/static/pretixpresale/js/ui/cart.js:43
msgid ""
"The items in your cart are no longer reserved for you. You can still "
"complete your order as long as theyre available."
msgstr ""
"Die Produkte in deinem Warenkorb sind nicht mehr für dich reserviert. Du "
"kannst die Bestellung trotzdem abschließen, solange die Produkte noch "
"verfügbar sind."
#: pretix/static/pretixpresale/js/ui/cart.js:39
msgid "The items in your cart are no longer reserved for you."
msgstr "Die Produkte in deinem Warenkorb sind nicht mehr für dich reserviert."
#: pretix/static/pretixpresale/js/ui/cart.js:45
#: pretix/static/pretixpresale/js/ui/cart.js:41
msgid "Cart expired"
msgstr "Warenkorb abgelaufen"
#: pretix/static/pretixpresale/js/ui/cart.js:50
msgid "The items in your cart are reserved for you for one minute."
msgid_plural "The items in your cart are reserved for you for {num} minutes."
#: pretix/static/pretixpresale/js/ui/cart.js:46
msgid "The items in your cart are reserved for you for one minute."
msgid_plural "The items in your cart are reserved for you for {num} minutes."
msgstr[0] ""
"Die Produkte in deinem Warenkorb sind noch eine Minute für dich reserviert."
msgstr[1] ""
@@ -532,20 +522,20 @@ msgstr "Du erhältst %(currency)s %(amount)s zurück"
msgid "Please enter the amount the organizer can keep."
msgstr "Bitte gib den Betrag ein, den der Veranstalter einbehalten darf."
#: pretix/static/pretixpresale/js/ui/main.js:335
#: pretix/static/pretixpresale/js/ui/main.js:309
msgid "Please enter a quantity for one of the ticket types."
msgstr "Bitte trage eine Menge für eines der Produkte ein."
#: pretix/static/pretixpresale/js/ui/main.js:371
#: pretix/static/pretixpresale/js/ui/main.js:345
msgid "required"
msgstr "verpflichtend"
#: pretix/static/pretixpresale/js/ui/main.js:473
#: pretix/static/pretixpresale/js/ui/main.js:491
#: pretix/static/pretixpresale/js/ui/main.js:447
#: pretix/static/pretixpresale/js/ui/main.js:465
msgid "Time zone:"
msgstr "Zeitzone:"
#: pretix/static/pretixpresale/js/ui/main.js:482
#: pretix/static/pretixpresale/js/ui/main.js:456
msgid "Your local time:"
msgstr "Deine lokale Zeit:"

View File

@@ -60,7 +60,6 @@ Cronjob
csv
Customer
Debug
deliverability
Di
Do
Doe
@@ -78,7 +77,6 @@ Einlassdatum
Einlassuhrzeit
email
Enterprise
EPC
EPS
Erstattungsbetrag
Erstattungsliste
@@ -104,7 +102,6 @@ Geo
geocoding
gescannt
ggf
GiroCode
giropay
GPL
Guide
@@ -188,7 +185,6 @@ pt
px
QR
Rabattierung
Reader
Rechnungs
Rechungsdatei
Reddit
@@ -263,7 +259,6 @@ txt
uhrzeit
ungespeicherte
unkategorisiert
Unkategorisierte
unlöschbar
unlöschbaren
untenstehende

File diff suppressed because it is too large Load Diff

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-10-17 14:57+0000\n"
"POT-Creation-Date: 2021-09-27 18:49+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -193,11 +193,6 @@ msgstr ""
msgid "Currently inside"
msgstr ""
#: pretix/static/lightbox/js/lightbox.js:90
#: pretix/static/lightbox/js/lightbox.min.js:13
msgid "close"
msgstr ""
#: pretix/static/pretixbase/js/asynctask.js:43
#: pretix/static/pretixbase/js/asynctask.js:119
msgid ""
@@ -253,7 +248,7 @@ msgid ""
"page and try again."
msgstr ""
#: pretix/static/pretixbase/js/asynctask.js:264
#: pretix/static/pretixbase/js/asynctask.js:262
#: pretix/static/pretixcontrol/js/ui/main.js:34
msgid "Close message"
msgstr ""
@@ -478,19 +473,17 @@ msgid_plural "({num} more dates)"
msgstr[0] ""
msgstr[1] ""
#: pretix/static/pretixpresale/js/ui/cart.js:43
msgid ""
"The items in your cart are no longer reserved for you. You can still "
"complete your order as long as theyre available."
#: pretix/static/pretixpresale/js/ui/cart.js:39
msgid "The items in your cart are no longer reserved for you."
msgstr ""
#: pretix/static/pretixpresale/js/ui/cart.js:45
#: pretix/static/pretixpresale/js/ui/cart.js:41
msgid "Cart expired"
msgstr ""
#: pretix/static/pretixpresale/js/ui/cart.js:50
msgid "The items in your cart are reserved for you for one minute."
msgid_plural "The items in your cart are reserved for you for {num} minutes."
#: pretix/static/pretixpresale/js/ui/cart.js:46
msgid "The items in your cart are reserved for you for one minute."
msgid_plural "The items in your cart are reserved for you for {num} minutes."
msgstr[0] ""
msgstr[1] ""
@@ -506,20 +499,20 @@ msgstr ""
msgid "Please enter the amount the organizer can keep."
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:335
#: pretix/static/pretixpresale/js/ui/main.js:309
msgid "Please enter a quantity for one of the ticket types."
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:371
#: pretix/static/pretixpresale/js/ui/main.js:345
msgid "required"
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:473
#: pretix/static/pretixpresale/js/ui/main.js:491
#: pretix/static/pretixpresale/js/ui/main.js:447
#: pretix/static/pretixpresale/js/ui/main.js:465
msgid "Time zone:"
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:482
#: pretix/static/pretixpresale/js/ui/main.js:456
msgid "Your local time:"
msgstr ""

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-10-17 14:57+0000\n"
"POT-Creation-Date: 2021-09-27 18:49+0000\n"
"PO-Revision-Date: 2019-10-03 19:00+0000\n"
"Last-Translator: Chris Spy <chrispiropoulou@hotmail.com>\n"
"Language-Team: Greek <https://translate.pretix.eu/projects/pretix/pretix-js/"
@@ -209,14 +209,6 @@ msgstr ""
msgid "Currently inside"
msgstr ""
#: pretix/static/lightbox/js/lightbox.js:90
#: pretix/static/lightbox/js/lightbox.min.js:13
#, fuzzy
#| msgctxt "widget"
#| msgid "Close"
msgid "close"
msgstr "Κλείσιμο"
#: pretix/static/pretixbase/js/asynctask.js:43
#: pretix/static/pretixbase/js/asynctask.js:119
#, fuzzy
@@ -298,7 +290,7 @@ msgstr ""
"περισσότερο από ένα λεπτό, ελέγξτε τη σύνδεσή σας στο διαδίκτυο και στη "
"συνέχεια επαναλάβετε τη φόρτωση αυτής της σελίδας και δοκιμάστε ξανά."
#: pretix/static/pretixbase/js/asynctask.js:264
#: pretix/static/pretixbase/js/asynctask.js:262
#: pretix/static/pretixcontrol/js/ui/main.js:34
msgid "Close message"
msgstr "Κλείσιμο μηνύματος"
@@ -536,25 +528,17 @@ msgid_plural "({num} more dates)"
msgstr[0] "(μία παραπάνω ημερομηνία)"
msgstr[1] "( {num} περισσότερες ημερομηνίες)"
#: pretix/static/pretixpresale/js/ui/cart.js:43
#, fuzzy
#| msgid "The items in your cart are no longer reserved for you."
msgid ""
"The items in your cart are no longer reserved for you. You can still "
"complete your order as long as theyre available."
#: pretix/static/pretixpresale/js/ui/cart.js:39
msgid "The items in your cart are no longer reserved for you."
msgstr "Τα αντικείμενα στο καλάθι σας δεν είναι πλέον αποκλειστικά για εσάς."
#: pretix/static/pretixpresale/js/ui/cart.js:45
#: pretix/static/pretixpresale/js/ui/cart.js:41
msgid "Cart expired"
msgstr "Το καλάθι έληξε"
#: pretix/static/pretixpresale/js/ui/cart.js:50
#, fuzzy
#| msgid "The items in your cart are reserved for you for one minute."
#| msgid_plural ""
#| "The items in your cart are reserved for you for {num} minutes."
msgid "The items in your cart are reserved for you for one minute."
msgid_plural "The items in your cart are reserved for you for {num} minutes."
#: pretix/static/pretixpresale/js/ui/cart.js:46
msgid "The items in your cart are reserved for you for one minute."
msgid_plural "The items in your cart are reserved for you for {num} minutes."
msgstr[0] "Τα είδη στο καλάθι θα παραμείνουν δεσμευμένα για ένα λεπτό."
msgstr[1] "Τα είδη στο καλάθι θα παραμείνουν δεσμευμένα για {num} λεπτά."
@@ -576,22 +560,22 @@ msgstr "απο %(currency)s %(price)s"
msgid "Please enter the amount the organizer can keep."
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:335
#: pretix/static/pretixpresale/js/ui/main.js:309
msgid "Please enter a quantity for one of the ticket types."
msgstr "Εισαγάγετε μια ποσότητα για έναν από τους τύπους εισιτηρίων."
#: pretix/static/pretixpresale/js/ui/main.js:371
#: pretix/static/pretixpresale/js/ui/main.js:345
#, fuzzy
#| msgid "Cart expired"
msgid "required"
msgstr "Το καλάθι έληξε"
#: pretix/static/pretixpresale/js/ui/main.js:473
#: pretix/static/pretixpresale/js/ui/main.js:491
#: pretix/static/pretixpresale/js/ui/main.js:447
#: pretix/static/pretixpresale/js/ui/main.js:465
msgid "Time zone:"
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:482
#: pretix/static/pretixpresale/js/ui/main.js:456
msgid "Your local time:"
msgstr ""

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-10-17 14:57+0000\n"
"POT-Creation-Date: 2021-09-27 18:49+0000\n"
"PO-Revision-Date: 2021-09-06 09:00+0000\n"
"Last-Translator: rauxenz <noquedandireccionesdisponibles@gmail.com>\n"
"Language-Team: Spanish <https://translate.pretix.eu/projects/pretix/pretix-"
@@ -194,14 +194,6 @@ msgstr "Tickets válidos"
msgid "Currently inside"
msgstr "Actualmente adentro"
#: pretix/static/lightbox/js/lightbox.js:90
#: pretix/static/lightbox/js/lightbox.min.js:13
#, fuzzy
#| msgctxt "widget"
#| msgid "Close"
msgid "close"
msgstr "Cerrar"
#: pretix/static/pretixbase/js/asynctask.js:43
#: pretix/static/pretixbase/js/asynctask.js:119
msgid ""
@@ -269,7 +261,7 @@ msgstr ""
"minuto, por favor, revise su conexión a Internet, recargue la página e "
"intente nuevamente."
#: pretix/static/pretixbase/js/asynctask.js:264
#: pretix/static/pretixbase/js/asynctask.js:262
#: pretix/static/pretixcontrol/js/ui/main.js:34
msgid "Close message"
msgstr "Cerrar mensaje"
@@ -501,25 +493,17 @@ msgid_plural "({num} more dates)"
msgstr[0] "(una fecha más)"
msgstr[1] "({num} más fechas)"
#: pretix/static/pretixpresale/js/ui/cart.js:43
#, fuzzy
#| msgid "The items in your cart are no longer reserved for you."
msgid ""
"The items in your cart are no longer reserved for you. You can still "
"complete your order as long as theyre available."
#: pretix/static/pretixpresale/js/ui/cart.js:39
msgid "The items in your cart are no longer reserved for you."
msgstr "Los elementos en su carrito de compras ya no se encuentran reservados."
#: pretix/static/pretixpresale/js/ui/cart.js:45
#: pretix/static/pretixpresale/js/ui/cart.js:41
msgid "Cart expired"
msgstr "El carrito de compras ha expirado"
#: pretix/static/pretixpresale/js/ui/cart.js:50
#, fuzzy
#| msgid "The items in your cart are reserved for you for one minute."
#| msgid_plural ""
#| "The items in your cart are reserved for you for {num} minutes."
msgid "The items in your cart are reserved for you for one minute."
msgid_plural "The items in your cart are reserved for you for {num} minutes."
#: pretix/static/pretixpresale/js/ui/cart.js:46
msgid "The items in your cart are reserved for you for one minute."
msgid_plural "The items in your cart are reserved for you for {num} minutes."
msgstr[0] ""
"Los elementos en su carrito de compras se han reservado por un minuto."
msgstr[1] ""
@@ -537,20 +521,20 @@ msgstr "Obtienes %(currency)s %(price)s de vuelta"
msgid "Please enter the amount the organizer can keep."
msgstr "Por favor, ingrese el monto que el organizador puede quedarse."
#: pretix/static/pretixpresale/js/ui/main.js:335
#: pretix/static/pretixpresale/js/ui/main.js:309
msgid "Please enter a quantity for one of the ticket types."
msgstr "Por favor, introduzca un valor para cada tipo de entrada."
#: pretix/static/pretixpresale/js/ui/main.js:371
#: pretix/static/pretixpresale/js/ui/main.js:345
msgid "required"
msgstr "campo requerido"
#: pretix/static/pretixpresale/js/ui/main.js:473
#: pretix/static/pretixpresale/js/ui/main.js:491
#: pretix/static/pretixpresale/js/ui/main.js:447
#: pretix/static/pretixpresale/js/ui/main.js:465
msgid "Time zone:"
msgstr "Zona horaria:"
#: pretix/static/pretixpresale/js/ui/main.js:482
#: pretix/static/pretixpresale/js/ui/main.js:456
msgid "Your local time:"
msgstr "Su hora local:"

File diff suppressed because it is too large Load Diff

View File

@@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-10-17 14:57+0000\n"
"PO-Revision-Date: 2021-10-04 21:00+0000\n"
"POT-Creation-Date: 2021-09-27 18:49+0000\n"
"PO-Revision-Date: 2021-06-17 00:00+0000\n"
"Last-Translator: Jaakko Rinta-Filppula <jaakko@r-f.fi>\n"
"Language-Team: Finnish <https://translate.pretix.eu/projects/pretix/pretix-"
"js/fi/>\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 4.8\n"
"X-Generator: Weblate 4.6\n"
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
@@ -176,8 +176,10 @@ msgid "Unknown ticket"
msgstr "Tuntematon lippu"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:57
#, fuzzy
#| msgid "Ticket not paid"
msgid "Ticket type not allowed here"
msgstr "Lipputyyppi ei sallittu"
msgstr "Lippu maksamatta"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:59
msgid "Entry not allowed"
@@ -203,14 +205,6 @@ msgstr ""
msgid "Currently inside"
msgstr ""
#: pretix/static/lightbox/js/lightbox.js:90
#: pretix/static/lightbox/js/lightbox.min.js:13
#, fuzzy
#| msgctxt "widget"
#| msgid "Close"
msgid "close"
msgstr "Sulje"
#: pretix/static/pretixbase/js/asynctask.js:43
#: pretix/static/pretixbase/js/asynctask.js:119
msgid ""
@@ -268,7 +262,7 @@ msgid ""
"page and try again."
msgstr ""
#: pretix/static/pretixbase/js/asynctask.js:264
#: pretix/static/pretixbase/js/asynctask.js:262
#: pretix/static/pretixcontrol/js/ui/main.js:34
msgid "Close message"
msgstr "Sulje viesti"
@@ -497,25 +491,19 @@ msgid_plural "({num} more dates)"
msgstr[0] ""
msgstr[1] ""
#: pretix/static/pretixpresale/js/ui/cart.js:43
#, fuzzy
#| msgid "The items in your cart are no longer reserved for you."
msgid ""
"The items in your cart are no longer reserved for you. You can still "
"complete your order as long as theyre available."
#: pretix/static/pretixpresale/js/ui/cart.js:39
msgid "The items in your cart are no longer reserved for you."
msgstr "Ostoskorissasi olevat tuotteet eivät ole enää varattu sinulle."
#: pretix/static/pretixpresale/js/ui/cart.js:45
#: pretix/static/pretixpresale/js/ui/cart.js:41
msgid "Cart expired"
msgstr "Ostoskori on vanhentunut"
#: pretix/static/pretixpresale/js/ui/cart.js:50
#, fuzzy
#| msgid "The items in your cart are no longer reserved for you."
msgid "The items in your cart are reserved for you for one minute."
msgid_plural "The items in your cart are reserved for you for {num} minutes."
msgstr[0] "Ostoskorissasi olevat tuotteet eivät ole enää varattu sinulle."
msgstr[1] "Ostoskorissasi olevat tuotteet eivät ole enää varattu sinulle."
#: pretix/static/pretixpresale/js/ui/cart.js:46
msgid "The items in your cart are reserved for you for one minute."
msgid_plural "The items in your cart are reserved for you for {num} minutes."
msgstr[0] ""
msgstr[1] ""
#: pretix/static/pretixpresale/js/ui/main.js:144
msgid "The organizer keeps %(currency)s %(amount)s"
@@ -529,22 +517,22 @@ msgstr ""
msgid "Please enter the amount the organizer can keep."
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:335
#: pretix/static/pretixpresale/js/ui/main.js:309
msgid "Please enter a quantity for one of the ticket types."
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:371
#: pretix/static/pretixpresale/js/ui/main.js:345
#, fuzzy
#| msgid "Cart expired"
msgid "required"
msgstr "Ostoskori on vanhentunut"
#: pretix/static/pretixpresale/js/ui/main.js:473
#: pretix/static/pretixpresale/js/ui/main.js:491
#: pretix/static/pretixpresale/js/ui/main.js:447
#: pretix/static/pretixpresale/js/ui/main.js:465
msgid "Time zone:"
msgstr "Aikavyöhyke:"
#: pretix/static/pretixpresale/js/ui/main.js:482
#: pretix/static/pretixpresale/js/ui/main.js:456
msgid "Your local time:"
msgstr ""

File diff suppressed because it is too large Load Diff

View File

@@ -6,9 +6,9 @@ msgid ""
msgstr ""
"Project-Id-Version: French\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-10-17 14:57+0000\n"
"PO-Revision-Date: 2021-10-01 23:00+0000\n"
"Last-Translator: Fabian Rodriguez <magicfab@legoutdulibre.com>\n"
"POT-Creation-Date: 2021-09-27 18:49+0000\n"
"PO-Revision-Date: 2020-09-15 17:00+0000\n"
"Last-Translator: Martin Gross <martin@pc-coholic.de>\n"
"Language-Team: French <https://translate.pretix.eu/projects/pretix/pretix-js/"
"fr/>\n"
"Language: fr\n"
@@ -16,7 +16,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 4.8\n"
"X-Generator: Weblate 3.10.3\n"
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
@@ -73,7 +73,7 @@ msgstr ""
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:33
msgid "Search results"
msgstr "Résultats de recherche"
msgstr ""
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:34
#, fuzzy
@@ -88,7 +88,7 @@ msgstr ""
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:36
msgid "This ticket requires special attention"
msgstr "Ce ticket nécessite une attention particulière"
msgstr ""
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:37
msgid "Switch direction"
@@ -100,7 +100,7 @@ msgstr ""
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:39
msgid "Exit"
msgstr "Quitter"
msgstr ""
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:40
msgid "Scan a ticket or search and press return…"
@@ -121,7 +121,7 @@ msgstr ""
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:44
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:45
msgid "Canceled"
msgstr "Annulé"
msgstr ""
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:46
#, fuzzy
@@ -132,7 +132,7 @@ msgstr "Echanger"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:47
msgid "Cancel"
msgstr "Annuler"
msgstr ""
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:48
#, fuzzy
@@ -144,7 +144,7 @@ msgstr "Continuer"
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:49
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:58
msgid "Ticket not paid"
msgstr "Ticket non payé"
msgstr ""
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:50
msgid "This ticket is not yet paid. Do you want to continue anyways?"
@@ -206,14 +206,6 @@ msgstr ""
msgid "Currently inside"
msgstr ""
#: pretix/static/lightbox/js/lightbox.js:90
#: pretix/static/lightbox/js/lightbox.min.js:13
#, fuzzy
#| msgctxt "widget"
#| msgid "Close"
msgid "close"
msgstr "Fermer"
#: pretix/static/pretixbase/js/asynctask.js:43
#: pretix/static/pretixbase/js/asynctask.js:119
#, fuzzy
@@ -291,7 +283,7 @@ msgstr ""
"d'une minute, veuillez vérifier votre connexion Internet, puis recharger "
"cette page et réessayer."
#: pretix/static/pretixbase/js/asynctask.js:264
#: pretix/static/pretixbase/js/asynctask.js:262
#: pretix/static/pretixcontrol/js/ui/main.js:34
msgid "Close message"
msgstr "Fermer le message"
@@ -418,7 +410,7 @@ msgstr "Zone de code-barres"
#: pretix/static/pretixcontrol/js/ui/editor.js:533
msgid "Powered by pretix"
msgstr "Propulsé par pretix"
msgstr "Généré par pretix"
#: pretix/static/pretixcontrol/js/ui/editor.js:535
msgid "Object"
@@ -457,7 +449,8 @@ msgstr "Erreur inconnue."
#: pretix/static/pretixcontrol/js/ui/main.js:271
msgid "Your color has great contrast and is very easy to read!"
msgstr "Votre choix couleur a un bon contraste et il est très facile à lire!"
msgstr ""
"Votre choix de couleur est très facile à lire, il a un excellent contraste !"
#: pretix/static/pretixcontrol/js/ui/main.js:275
msgid "Your color has decent contrast and is probably good-enough to read!"
@@ -528,25 +521,17 @@ msgid_plural "({num} more dates)"
msgstr[0] "(une date en plus)"
msgstr[1] "({num} plus de dates)"
#: pretix/static/pretixpresale/js/ui/cart.js:43
#, fuzzy
#| msgid "The items in your cart are no longer reserved for you."
msgid ""
"The items in your cart are no longer reserved for you. You can still "
"complete your order as long as theyre available."
#: pretix/static/pretixpresale/js/ui/cart.js:39
msgid "The items in your cart are no longer reserved for you."
msgstr "Les articles de votre panier ne vous sont plus réservés."
#: pretix/static/pretixpresale/js/ui/cart.js:45
#: pretix/static/pretixpresale/js/ui/cart.js:41
msgid "Cart expired"
msgstr "Panier expiré"
#: pretix/static/pretixpresale/js/ui/cart.js:50
#, fuzzy
#| msgid "The items in your cart are reserved for you for one minute."
#| msgid_plural ""
#| "The items in your cart are reserved for you for {num} minutes."
msgid "The items in your cart are reserved for you for one minute."
msgid_plural "The items in your cart are reserved for you for {num} minutes."
#: pretix/static/pretixpresale/js/ui/cart.js:46
msgid "The items in your cart are reserved for you for one minute."
msgid_plural "The items in your cart are reserved for you for {num} minutes."
msgstr[0] "Les articles de votre panier vous sont réservés pour une minute."
msgstr[1] "Les articles de votre panier vous sont réservés pour {num} minutes."
@@ -568,22 +553,22 @@ msgstr "de %(currency)s %(price)s"
msgid "Please enter the amount the organizer can keep."
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:335
#: pretix/static/pretixpresale/js/ui/main.js:309
msgid "Please enter a quantity for one of the ticket types."
msgstr "SVP entrez une quantité pour un de vos types de billets."
#: pretix/static/pretixpresale/js/ui/main.js:371
#: pretix/static/pretixpresale/js/ui/main.js:345
#, fuzzy
#| msgid "Cart expired"
msgid "required"
msgstr "Panier expiré"
#: pretix/static/pretixpresale/js/ui/main.js:473
#: pretix/static/pretixpresale/js/ui/main.js:491
#: pretix/static/pretixpresale/js/ui/main.js:447
#: pretix/static/pretixpresale/js/ui/main.js:465
msgid "Time zone:"
msgstr ""
#: pretix/static/pretixpresale/js/ui/main.js:482
#: pretix/static/pretixpresale/js/ui/main.js:456
msgid "Your local time:"
msgstr ""

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

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