forked from CGM_Public/pretix_original
Compare commits
1 Commits
v4.5.2
...
consistenc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e2dad8fdcc |
15
.github/dependabot.yml
vendored
15
.github/dependabot.yml
vendored
@@ -1,15 +0,0 @@
|
||||
# To get started with Dependabot version updates, you'll need to specify which
|
||||
# package ecosystems to update and where the package manifests are located.
|
||||
# Please see the documentation for all configuration options:
|
||||
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "pip"
|
||||
directory: "/src"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/src/pretix/static/npm_dir"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
@@ -26,13 +26,6 @@ In addition to these standard update steps, the following list issues steps that
|
||||
to specific versions for pretix. If you're skipping versions, please read the instructions for every version in
|
||||
between as well.
|
||||
|
||||
Upgrade to 3.17.0 or newer
|
||||
""""""""""""""""""""""""""
|
||||
|
||||
pretix 3.17 introduces a dependency on ``nodejs``, so you should install it on your system::
|
||||
|
||||
# apt install nodejs npm
|
||||
|
||||
Upgrade to 4.4.0 or newer
|
||||
"""""""""""""""""""""""""
|
||||
|
||||
|
||||
@@ -31,6 +31,5 @@ Resources and endpoints
|
||||
webhooks
|
||||
seatingplans
|
||||
exporters
|
||||
sendmail_rules
|
||||
billing_invoices
|
||||
billing_var
|
||||
|
||||
@@ -78,12 +78,6 @@ lines list of objects The actual invo
|
||||
an event series not created by a product (e.g. shipping or
|
||||
cancellation fees) as well as whenever the respective (sub)event
|
||||
has no end date set.
|
||||
├ event_location string Location of the (sub)event this line was created for as it
|
||||
was set during invoice creation. Can be ``null`` for all invoice
|
||||
lines created before this was introduced as well as for lines in
|
||||
an event series not created by a product (e.g. shipping or
|
||||
cancellation fees) as well as whenever the respective (sub)event
|
||||
has no location set.
|
||||
├ attendee_name string Attendee name at time of invoice creation. Can be ``null`` if no
|
||||
name was set or if names are configured to not be added to invoices.
|
||||
├ gross_value money (string) Price including taxes
|
||||
@@ -116,10 +110,6 @@ internal_reference string Customer's refe
|
||||
|
||||
The attributes ``fee_type`` and ``fee_internal_type`` have been added.
|
||||
|
||||
.. versionchanged:: 4.1
|
||||
|
||||
The attribute ``lines.event_location`` has been added.
|
||||
|
||||
|
||||
Endpoints
|
||||
---------
|
||||
@@ -189,7 +179,6 @@ Endpoints
|
||||
"fee_internal_type": null,
|
||||
"event_date_from": "2017-12-27T10:00:00Z",
|
||||
"event_date_to": null,
|
||||
"event_location": "Heidelberg",
|
||||
"attendee_name": null,
|
||||
"gross_value": "23.00",
|
||||
"tax_value": "0.00",
|
||||
@@ -278,7 +267,6 @@ Endpoints
|
||||
"fee_internal_type": null,
|
||||
"event_date_from": "2017-12-27T10:00:00Z",
|
||||
"event_date_to": null,
|
||||
"event_location": "Heidelberg",
|
||||
"attendee_name": null,
|
||||
"gross_value": "23.00",
|
||||
"tax_value": "0.00",
|
||||
|
||||
@@ -1,281 +0,0 @@
|
||||
Automated email rules
|
||||
=====================
|
||||
|
||||
Resource description
|
||||
--------------------
|
||||
|
||||
Automated email rules that specify emails that the system will send automatically at a specific point in time, e.g.
|
||||
the day of the event.
|
||||
|
||||
.. rst-class:: rest-resource-table
|
||||
|
||||
===================================== ========================== =======================================================
|
||||
Field Type Description
|
||||
===================================== ========================== =======================================================
|
||||
id integer Internal ID of the rule
|
||||
enabled boolean If ``false``, the rule is ignored
|
||||
subject multi-lingual string The subject of the email
|
||||
template multi-lingual string The body of the email
|
||||
all_products boolean If ``true``, the email is sent to buyers of all products
|
||||
limit_products list of integers List of product IDs, if ``all_products`` is not set
|
||||
include_pending boolean If ``true``, the email is sent to pending orders. If ``false``,
|
||||
only paid orders are considered.
|
||||
date_is_absolute boolean If ``true``, the email is set at a specific point in time.
|
||||
send_date datetime If ``date_is_absolute`` is set: Date and time to send the email.
|
||||
send_offset_days integer If ``date_is_absolute`` is not set, this is the number of days
|
||||
before/after the email is sent.
|
||||
send_offset_time time If ``date_is_absolute`` is not set, this is the time of day the
|
||||
email is sent on the day specified by ``send_offset_days``.
|
||||
offset_to_event_end boolean If ``true``, ``send_offset_days`` is relative to the event end
|
||||
date. Otherwise it is relative to the event start date.
|
||||
offset_is_after boolean If ``true``, ``send_offset_days`` is the number of days **after**
|
||||
the event start or end date. Otherwise it is the number of days
|
||||
**before**.
|
||||
send_to string Can be ``"orders"`` if the email should be sent to customers
|
||||
(one email per order),
|
||||
``"attendees"`` if the email should be sent to every attendee,
|
||||
or ``"both"``.
|
||||
date. Otherwise it is relative to the event start date.
|
||||
===================================== ========================== =======================================================
|
||||
|
||||
|
||||
Endpoints
|
||||
---------
|
||||
|
||||
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/sendmail_rules/
|
||||
|
||||
Returns a list of all rules configured for an event.
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
GET /api/v1/organizers/bigevents/events/sampleconf/sendmail_rules/ HTTP/1.1
|
||||
Host: pretix.eu
|
||||
Accept: application/json, text/javascript
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Vary: Accept
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"count": 1,
|
||||
"next": null,
|
||||
"previous": null,
|
||||
"results": [
|
||||
{
|
||||
"id": 1,
|
||||
"enabled": true,
|
||||
"subject": {"en": "See you tomorrow!"},
|
||||
"template": {"en": "Don't forget your tickets, download them at {url}"},
|
||||
"all_products": true,
|
||||
"limit_products": [],
|
||||
"include_pending": false,
|
||||
"send_date": null,
|
||||
"send_offset_days": 1,
|
||||
"send_offset_time": "18:00",
|
||||
"date_is_absolute": false,
|
||||
"offset_to_event_end": false,
|
||||
"offset_is_after": false,
|
||||
"send_to": "orders"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
:query page: The page number in case of a multi-page result set, default is 1
|
||||
:param organizer: The ``slug`` field of a valid organizer
|
||||
:param event: The ``slug`` field of the event to fetch
|
||||
:statuscode 200: no error
|
||||
:statuscode 401: Authentication failure
|
||||
:statuscode 403: The requested organizer does not exist **or** you have no permission to view it.
|
||||
|
||||
.. http:get:: /api/v1/organizers/(organizer)/events/(event)/sendmail_rules/(id)/
|
||||
|
||||
Returns information on one rule, identified by its ID.
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
GET /api/v1/organizers/bigevents/events/sampleconf/sendmail_rules/1/ HTTP/1.1
|
||||
Host: pretix.eu
|
||||
Accept: application/json, text/javascript
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Vary: Accept
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"id": 1,
|
||||
"enabled": true,
|
||||
"subject": {"en": "See you tomorrow!"},
|
||||
"template": {"en": "Don't forget your tickets, download them at {url}"},
|
||||
"all_products": true,
|
||||
"limit_products": [],
|
||||
"include_pending": false,
|
||||
"send_date": null,
|
||||
"send_offset_days": 1,
|
||||
"send_offset_time": "18:00",
|
||||
"date_is_absolute": false,
|
||||
"offset_to_event_end": false,
|
||||
"offset_is_after": false,
|
||||
"send_to": "orders"
|
||||
}
|
||||
|
||||
:param organizer: The ``slug`` field of the organizer to fetch
|
||||
:param event: The ``slug`` field of the event to fetch
|
||||
:param id: The ``id`` field of the rule to fetch
|
||||
:statuscode 200: no error
|
||||
:statuscode 401: Authentication failure
|
||||
:statuscode 403: The requested organizer/event/rule does not exist **or** you have no permission to view it.
|
||||
|
||||
.. http:post:: /api/v1/organizers/(organizer)/events/(event)/sendmail_rules/
|
||||
|
||||
Create a new rule.
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
POST /api/v1/organizers/bigevents/events/sampleconf/sendmail_rules/ HTTP/1.1
|
||||
Host: pretix.eu
|
||||
Accept: application/json, text/javascript
|
||||
Content-Type: application/json
|
||||
Content-Length: 166
|
||||
|
||||
{
|
||||
"enabled": true,
|
||||
"subject": {"en": "See you tomorrow!"},
|
||||
"template": {"en": "Don't forget your tickets, download them at {url}"},
|
||||
"all_products": true,
|
||||
"limit_products": [],
|
||||
"include_pending": false,
|
||||
"send_date": null,
|
||||
"send_offset_days": 1,
|
||||
"send_offset_time": "18:00",
|
||||
"date_is_absolute": false,
|
||||
"offset_to_event_end": false,
|
||||
"offset_is_after": false,
|
||||
"send_to": "orders"
|
||||
}
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 201 Created
|
||||
Vary: Accept
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"id": 1,
|
||||
"enabled": true,
|
||||
"subject": {"en": "See you tomorrow!"},
|
||||
"template": {"en": "Don't forget your tickets, download them at {url}"},
|
||||
"all_products": true,
|
||||
"limit_products": [],
|
||||
"include_pending": false,
|
||||
"send_date": null,
|
||||
"send_offset_days": 1,
|
||||
"send_offset_time": "18:00",
|
||||
"date_is_absolute": false,
|
||||
"offset_to_event_end": false,
|
||||
"offset_is_after": false,
|
||||
"send_to": "orders"
|
||||
}
|
||||
|
||||
:param organizer: The ``slug`` field of the organizer to create a rule for
|
||||
:param event: The ``slug`` field of the event to create a rule for
|
||||
:statuscode 201: no error
|
||||
:statuscode 400: The rule could not be created due to invalid submitted data.
|
||||
:statuscode 401: Authentication failure
|
||||
:statuscode 403: The requested organizer/event does not exist **or** you have no permission to create rules.
|
||||
|
||||
|
||||
.. http:patch:: /api/v1/organizers/(organizer)/events/(event)/sendmail_rules/(id)/
|
||||
|
||||
Update a rule. You can also use ``PUT`` instead of ``PATCH``. With ``PUT``, you have to provide all fields of
|
||||
the resource, other fields will be reset to default. With ``PATCH``, you only need to provide the fields that you
|
||||
want to change.
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
PATCH /api/v1/organizers/bigevents/events/sampleconf/sendmail_rules/1/ HTTP/1.1
|
||||
Host: pretix.eu
|
||||
Accept: application/json, text/javascript
|
||||
Content-Type: application/json
|
||||
Content-Length: 34
|
||||
|
||||
{
|
||||
"enabled": false,
|
||||
}
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Vary: Accept
|
||||
Content-Type: text/javascript
|
||||
|
||||
{
|
||||
"id": 1,
|
||||
"enabled": false,
|
||||
"subject": {"en": "See you tomorrow!"},
|
||||
"template": {"en": "Don't forget your tickets, download them at {url}"},
|
||||
"all_products": true,
|
||||
"limit_products": [],
|
||||
"include_pending": false,
|
||||
"send_date": null,
|
||||
"send_offset_days": 1,
|
||||
"send_offset_time": "18:00",
|
||||
"date_is_absolute": false,
|
||||
"offset_to_event_end": false,
|
||||
"offset_is_after": false,
|
||||
"send_to": "orders"
|
||||
}
|
||||
|
||||
:param organizer: The ``slug`` field of the organizer to modify
|
||||
:param event: The ``slug`` field of the event to modify
|
||||
:param id: The ``id`` field of the rule to modify
|
||||
:statuscode 200: no error
|
||||
:statuscode 400: The rule could not be modified due to invalid submitted data.
|
||||
:statuscode 401: Authentication failure
|
||||
:statuscode 403: The requested organizer/event/rule does not exist **or** you have no permission to change it.
|
||||
|
||||
|
||||
.. http:delete:: /api/v1/organizers/(organizer)/events/(event)/sendmail_rules/(id)/
|
||||
|
||||
Delete a rule.
|
||||
|
||||
**Example request**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
DELETE /api/v1/organizers/bigevents/events/sampleconf/sendmail_rules/1/ HTTP/1.1
|
||||
Host: pretix.eu
|
||||
Accept: application/json, text/javascript
|
||||
|
||||
**Example response**:
|
||||
|
||||
.. sourcecode:: http
|
||||
|
||||
HTTP/1.1 204 No Content
|
||||
Vary: Accept
|
||||
|
||||
:param organizer: The ``slug`` field of the organizer to modify
|
||||
:param event: The ``slug`` field of the event to modify
|
||||
:param id: The ``id`` field of the rule to delete
|
||||
:statuscode 204: no error
|
||||
:statuscode 401: Authentication failure
|
||||
:statuscode 403: The requested organizer/event/rule does not exist **or** you have no permission to change it **or** this rule cannot be deleted since it is currently in use.
|
||||
@@ -1,11 +1,6 @@
|
||||
.. spelling:: Rebase rebasing
|
||||
|
||||
Coding style and quality
|
||||
========================
|
||||
|
||||
Code
|
||||
----
|
||||
|
||||
* Basically, we want all python code to follow the `PEP 8`_ standard. There are a few exceptions where
|
||||
we see things differently or just aren't that strict. The ``setup.cfg`` file in the project's source
|
||||
folder contains definitions that allow `flake8`_ to check for violations automatically. See :ref:`checksandtests`
|
||||
@@ -25,62 +20,8 @@ Code
|
||||
test suite are in the style of Python's unit test module. If you extend those files, you might continue in this style,
|
||||
but please use ``pytest`` style for any new test files.
|
||||
|
||||
Commits and Pull Requests
|
||||
-------------------------
|
||||
|
||||
|
||||
|
||||
Most commits should start as pull requests, therefore this applies to the titles of pull requests as well since
|
||||
the pull request title will become the commit message on merge. We prefer merging with GitHub's "Squash and merge"
|
||||
feature if the PR contains multiple commits that do not carry value to keep. If there is value in keeping the
|
||||
individual commits, we use "Rebase and merge" instead. Merge commits should be avoided.
|
||||
|
||||
* The commit message should start with a single subject line and can optionally be followed by a commit message body.
|
||||
|
||||
* The subject line should be the shortest possible representation of what the commit changes. Someone who reviewed
|
||||
the commit should able to immediately remember the commit in a couple of weeks based on the subject line and tell
|
||||
it apart from other commits.
|
||||
|
||||
* If there's additional useful information that we should keep, such as reasoning behind the commit, you can
|
||||
add a longer body, separated from the first line by a blank line.
|
||||
|
||||
* The body should explain **what** you changed and more importantly **why** you changed it. There's no need to iterate
|
||||
**how** you changed something.
|
||||
|
||||
* The subject line should be capitalized ("Add new feature" instead of "add new feature") and should not end with a period
|
||||
("Add new feature" instead of "Add new feature.")
|
||||
|
||||
* The subject line should be written in imperative mood, as if you were giving a command what the computer should do if the
|
||||
commit is applied. This is how generated commit messages by git itself are already written ("Merge branch …", "Revert …")
|
||||
and makes for short and consistent messages.
|
||||
|
||||
* Good: "Fix typo in template"
|
||||
* Good: "Add Chinese translation"
|
||||
* Good: "Remove deprecated method"
|
||||
* Good: "Bump version to 4.4.0"
|
||||
* Bad: "Fixed bug with …"
|
||||
* Bad: "Fixes bug with …"
|
||||
* Bad: "Fixing bug …"
|
||||
|
||||
* If all changes in your commit are in context of a single feature or e.g. a bundled plugin, it makes sense to prefix the
|
||||
subject line with the name of that feature. Examples:
|
||||
|
||||
* "API: Add support for PATCH on customers"
|
||||
* "Docs: Add chapter on alpaca feeding"
|
||||
* "Stripe: Fix duplicate payments"
|
||||
* "Order change form: Fix incorrect validation"
|
||||
|
||||
* If your commit references a GitHub issue that is fully resolved by your commit, start your subject line with the issue
|
||||
ID in the form of "Fix #1234 -- Crash in order list". In this case, you can omit the verb "Fix" at the beginning of the
|
||||
second part of the message to avoid repetition of the word "fix". If your commit only partially resolves the issue, use
|
||||
"Refs #1234 -- Crash in order list" instead.
|
||||
|
||||
* Applies to pretix employees only: If your commit references a sentry issue, please put it in parentheses at the end
|
||||
of the subject line or inside the body ("Fix crash in order list (PRETIXEU-ABC)"). If your commit references a support
|
||||
ticket, please put it in parentheses at the end of the subject line with a "Z#" prefix ("Fix crash in order list (Z#12345)").
|
||||
|
||||
* If your PR was open for a while and might cause conflicts on merge, please prefer rebasing it (``git rebase -i master``)
|
||||
over merging ``master`` into your branch unless it is prohibitively complicated.
|
||||
* Please keep the first line of your commit messages short. When referencing an issue, please phrase it like
|
||||
``Fix #123 -- Problems with order creation`` or ``Refs #123 -- Fix this part of that bug``.
|
||||
|
||||
|
||||
.. _PEP 8: https://legacy.python.org/dev/peps/pep-0008/
|
||||
|
||||
@@ -26,7 +26,7 @@ Your should install the following on your system:
|
||||
* ``libssl`` (Debian package: ``libssl-dev``)
|
||||
* ``libxml2`` (Debian package ``libxml2-dev``)
|
||||
* ``libxslt`` (Debian package ``libxslt1-dev``)
|
||||
* ``libenchant-2-2`` (Debian package ``libenchant-2-2``)
|
||||
* ``libenchant1c2a`` (Debian package ``libenchant1c2a``)
|
||||
* ``msgfmt`` (Debian package ``gettext``)
|
||||
* ``git``
|
||||
|
||||
@@ -51,12 +51,7 @@ the dependencies might fail::
|
||||
|
||||
Working with the code
|
||||
---------------------
|
||||
If you do not have a recent installation of ``nodejs``, install it now::
|
||||
|
||||
curl -sL https://deb.nodesource.com/setup_17.x | sudo -E bash -
|
||||
sudo apt install nodejs
|
||||
|
||||
To make sure it is on your path variable, close and reopen your terminal. Now, install the Python-level dependencies of pretix::
|
||||
The first thing you need are all the main application's dependencies::
|
||||
|
||||
cd src/
|
||||
pip3 install -e ".[dev]"
|
||||
|
||||
@@ -34,7 +34,5 @@ git push
|
||||
# Unlock Weblate
|
||||
for c in $COMPONENTS; do
|
||||
wlc unlock $c;
|
||||
done
|
||||
for c in $COMPONENTS; do
|
||||
wlc pull $c;
|
||||
done
|
||||
|
||||
@@ -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.5.2"
|
||||
__version__ = "4.4.0.dev0"
|
||||
|
||||
@@ -734,7 +734,6 @@ class EventSettingsSerializer(SettingsSerializer):
|
||||
'invoice_numbers_prefix_cancellations',
|
||||
'invoice_numbers_counter_length',
|
||||
'invoice_attendee_name',
|
||||
'invoice_event_location',
|
||||
'invoice_include_expire_date',
|
||||
'invoice_address_explanation_text',
|
||||
'invoice_email_attachment',
|
||||
@@ -764,7 +763,6 @@ class EventSettingsSerializer(SettingsSerializer):
|
||||
'cancel_allow_user_paid_refund_as_giftcard',
|
||||
'cancel_allow_user_paid_require_approval',
|
||||
'change_allow_user_variation',
|
||||
'change_allow_user_addons',
|
||||
'change_allow_user_until',
|
||||
'change_allow_user_price',
|
||||
'primary_color',
|
||||
|
||||
@@ -1428,7 +1428,7 @@ class InlineInvoiceLineSerializer(I18nAwareModelSerializer):
|
||||
model = InvoiceLine
|
||||
fields = ('position', 'description', 'item', 'variation', 'attendee_name', 'event_date_from',
|
||||
'event_date_to', 'gross_value', 'tax_value', 'tax_rate', 'tax_name', 'fee_type',
|
||||
'fee_internal_type', 'event_location')
|
||||
'fee_internal_type')
|
||||
|
||||
|
||||
class InvoiceSerializer(I18nAwareModelSerializer):
|
||||
|
||||
@@ -69,7 +69,7 @@ class ExportersMixin:
|
||||
cf = get_object_or_404(CachedFile, id=kwargs['cfid'])
|
||||
if cf.file:
|
||||
resp = ChunkBasedFileResponse(cf.file.file, content_type=cf.type)
|
||||
resp['Content-Disposition'] = 'attachment; filename="{}"'.format(cf.filename).encode("ascii", "ignore")
|
||||
resp['Content-Disposition'] = 'attachment; filename="{}"'.format(cf.filename)
|
||||
return resp
|
||||
elif not settings.HAS_CELERY:
|
||||
return Response(
|
||||
|
||||
@@ -46,7 +46,7 @@ class PretixBaseConfig(AppConfig):
|
||||
from . import invoice # NOQA
|
||||
from . import notifications # NOQA
|
||||
from . import email # NOQA
|
||||
from .services import auth, checkin, export, mail, tickets, cart, orderimport, orders, invoices, cleanup, update_check, quotas, notifications, vouchers # 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 django.conf import settings
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
# License for the specific language governing permissions and limitations under the License.
|
||||
|
||||
import io
|
||||
import re
|
||||
import tempfile
|
||||
from collections import OrderedDict, namedtuple
|
||||
from decimal import Decimal
|
||||
@@ -45,13 +46,26 @@ from django.conf import settings
|
||||
from django.db.models import QuerySet
|
||||
from django.utils.formats import localize
|
||||
from django.utils.translation import gettext, gettext_lazy as _
|
||||
from openpyxl import Workbook
|
||||
from openpyxl.cell.cell import ILLEGAL_CHARACTERS_RE, KNOWN_TYPES, Cell
|
||||
|
||||
from pretix.base.models import Event
|
||||
from pretix.helpers.safe_openpyxl import ( # NOQA: backwards compatibility for plugins using excel_safe
|
||||
SafeWorkbook, remove_invalid_excel_chars as excel_safe,
|
||||
)
|
||||
|
||||
__ = excel_safe # just so the compatbility import above is "used" and doesn't get removed by linter
|
||||
|
||||
def excel_safe(val):
|
||||
if isinstance(val, Cell):
|
||||
return val
|
||||
|
||||
if not isinstance(val, KNOWN_TYPES):
|
||||
val = str(val)
|
||||
|
||||
if isinstance(val, bytes):
|
||||
val = val.decode("utf-8", errors="ignore")
|
||||
|
||||
if isinstance(val, str):
|
||||
val = re.sub(ILLEGAL_CHARACTERS_RE, '', val)
|
||||
|
||||
return val
|
||||
|
||||
|
||||
class BaseExporter:
|
||||
@@ -214,7 +228,7 @@ class ListExporter(BaseExporter):
|
||||
pass
|
||||
|
||||
def _render_xlsx(self, form_data, output_file=None):
|
||||
wb = SafeWorkbook(write_only=True)
|
||||
wb = Workbook(write_only=True)
|
||||
ws = wb.create_sheet()
|
||||
self.prepare_xlsx_sheet(ws)
|
||||
try:
|
||||
@@ -228,7 +242,7 @@ class ListExporter(BaseExporter):
|
||||
total = line.total
|
||||
continue
|
||||
ws.append([
|
||||
val for val in line
|
||||
excel_safe(val) for val in line
|
||||
])
|
||||
if total:
|
||||
counter += 1
|
||||
@@ -333,7 +347,7 @@ class MultiSheetListExporter(ListExporter):
|
||||
return self.get_filename() + '.csv', 'text/csv', output.getvalue().encode("utf-8")
|
||||
|
||||
def _render_xlsx(self, form_data, output_file=None):
|
||||
wb = SafeWorkbook(write_only=True)
|
||||
wb = Workbook(write_only=True)
|
||||
n_sheets = len(self.sheets)
|
||||
for i_sheet, (s, l) in enumerate(self.sheets):
|
||||
ws = wb.create_sheet(str(l))
|
||||
@@ -347,7 +361,8 @@ class MultiSheetListExporter(ListExporter):
|
||||
total = line.total
|
||||
continue
|
||||
ws.append([
|
||||
val for val in line
|
||||
excel_safe(val)
|
||||
for val in line
|
||||
])
|
||||
if total:
|
||||
counter += 1
|
||||
|
||||
@@ -324,6 +324,7 @@ class InvoiceDataExporter(InvoiceExporterMixin, MultiSheetListExporter):
|
||||
_('Tax rate'),
|
||||
_('Tax name'),
|
||||
_('Event start date'),
|
||||
|
||||
_('Date'),
|
||||
_('Order code'),
|
||||
_('E-mail address'),
|
||||
@@ -347,8 +348,6 @@ class InvoiceDataExporter(InvoiceExporterMixin, MultiSheetListExporter):
|
||||
_('Invoice recipient:') + ' ' + _('Beneficiary'),
|
||||
_('Invoice recipient:') + ' ' + _('Internal reference'),
|
||||
_('Payment providers'),
|
||||
_('Event end date'),
|
||||
_('Location'),
|
||||
]
|
||||
|
||||
p_providers = OrderPayment.objects.filter(
|
||||
@@ -407,9 +406,7 @@ class InvoiceDataExporter(InvoiceExporterMixin, MultiSheetListExporter):
|
||||
', '.join([
|
||||
str(self.providers.get(p, p)) for p in sorted(set((l.payment_providers or '').split(',')))
|
||||
if p and p != 'free'
|
||||
]),
|
||||
date_format(l.event_date_to, "SHORT_DATE_FORMAT") if l.event_date_to else "",
|
||||
l.event_location or "",
|
||||
])
|
||||
]
|
||||
|
||||
@cached_property
|
||||
|
||||
@@ -674,7 +674,7 @@ class BaseQuestionsForm(forms.Form):
|
||||
label=label, required=required,
|
||||
min_value=q.valid_number_min or Decimal('0.00'),
|
||||
max_value=q.valid_number_max,
|
||||
help_text=help_text,
|
||||
help_text=q.help_text,
|
||||
initial=initial.answer if initial else None,
|
||||
)
|
||||
elif q.type == Question.TYPE_STRING:
|
||||
|
||||
@@ -55,7 +55,6 @@ class UserSettingsForm(forms.ModelForm):
|
||||
'pw_current_wrong': _("The current password you entered was not correct."),
|
||||
'pw_mismatch': _("Please enter the same password twice"),
|
||||
'rate_limit': _("For security reasons, please wait 5 minutes before you try again."),
|
||||
'pw_equal': _("Please choose a password different to your current one.")
|
||||
}
|
||||
|
||||
old_pw = forms.CharField(max_length=255,
|
||||
@@ -159,12 +158,6 @@ class UserSettingsForm(forms.ModelForm):
|
||||
code='pw_current'
|
||||
)
|
||||
|
||||
if password1 and password1 == old_pw:
|
||||
raise forms.ValidationError(
|
||||
self.error_messages['pw_equal'],
|
||||
code='pw_equal'
|
||||
)
|
||||
|
||||
if password1:
|
||||
self.instance.set_password(password1)
|
||||
|
||||
|
||||
@@ -395,13 +395,7 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
||||
return txt
|
||||
|
||||
if not self.invoice.event.has_subevents and self.invoice.event.settings.show_dates_on_frontpage:
|
||||
tz = self.invoice.event.timezone
|
||||
show_end_date = (
|
||||
self.invoice.event.settings.show_date_to and
|
||||
self.invoice.event.date_to and
|
||||
self.invoice.event.date_to.astimezone(tz).date() != self.invoice.event.date_from.astimezone(tz).date()
|
||||
)
|
||||
if show_end_date:
|
||||
if self.invoice.event.settings.show_date_to and self.invoice.event.date_to:
|
||||
p_str = (
|
||||
shorten(self.invoice.event.name) + '\n' +
|
||||
pgettext('invoice', '{from_date}\nuntil {to_date}').format(
|
||||
@@ -556,10 +550,7 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
||||
for line in self.invoice.lines.all():
|
||||
if has_taxes:
|
||||
tdata.append((
|
||||
Paragraph(
|
||||
bleach.clean(line.description, tags=['br']).strip().replace('<br>', '<br/>').replace('\n', '<br />\n'),
|
||||
self.stylesheet['Normal']
|
||||
),
|
||||
Paragraph(line.description, self.stylesheet['Normal']),
|
||||
"1",
|
||||
localize(line.tax_rate) + " %",
|
||||
money_filter(line.net_value, self.invoice.event.currency),
|
||||
@@ -567,10 +558,7 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
||||
))
|
||||
else:
|
||||
tdata.append((
|
||||
Paragraph(
|
||||
bleach.clean(line.description, tags=['br']).strip().replace('<br>', '<br/>').replace('\n', '<br />\n'),
|
||||
self.stylesheet['Normal']
|
||||
),
|
||||
Paragraph(line.description, self.stylesheet['Normal']),
|
||||
"1",
|
||||
money_filter(line.gross_value, self.invoice.event.currency),
|
||||
))
|
||||
|
||||
@@ -43,7 +43,7 @@ class Command(BaseCommand):
|
||||
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)
|
||||
), Value(Decimal(0)), output_field=models.DecimalField(decimal_places=2, max_digits=10)
|
||||
),
|
||||
fee_total=Coalesce(
|
||||
Subquery(
|
||||
@@ -51,7 +51,7 @@ class Command(BaseCommand):
|
||||
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)
|
||||
), Value(Decimal(0)), output_field=models.DecimalField(decimal_places=2, max_digits=10)
|
||||
),
|
||||
tx_total=Coalesce(
|
||||
Subquery(
|
||||
@@ -59,12 +59,12 @@ class Command(BaseCommand):
|
||||
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)
|
||||
), 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(0)),
|
||||
then=Value(Decimal(0))),
|
||||
default=F('position_total') + F('fee_total'),
|
||||
output_field=models.DecimalField(decimal_places=2, max_digits=10)
|
||||
),
|
||||
@@ -73,9 +73,6 @@ class Command(BaseCommand):
|
||||
tx_total=F('correct_total')
|
||||
).select_related('event')
|
||||
for o in qs:
|
||||
if abs(o.tx_total - o.correct_total) < Decimal('0.00001') and abs(o.position_total + o.fee_total - o.total) < Decimal('0.00001'):
|
||||
# Ignore SQLite which treats Decimals like floats…
|
||||
continue
|
||||
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}")
|
||||
|
||||
|
||||
@@ -208,7 +208,7 @@ def _parse_csp(header):
|
||||
|
||||
|
||||
def _render_csp(h):
|
||||
return "; ".join(k + ' ' + ' '.join(v) for k, v in h.items() if v)
|
||||
return "; ".join(k + ' ' + ' '.join(v) for k, v in h.items())
|
||||
|
||||
|
||||
def _merge_csp(a, b):
|
||||
|
||||
29
src/pretix/base/migrations/0201_check.py
Normal file
29
src/pretix/base/migrations/0201_check.py
Normal file
@@ -0,0 +1,29 @@
|
||||
# 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),
|
||||
),
|
||||
]
|
||||
@@ -1,18 +0,0 @@
|
||||
# Generated by Django 3.2.4 on 2021-11-03 09:24
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('pretixbase', '0200_transaction'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='invoiceline',
|
||||
name='event_location',
|
||||
field=models.TextField(null=True),
|
||||
),
|
||||
]
|
||||
@@ -1,18 +0,0 @@
|
||||
# Generated by Django 3.2.9 on 2021-11-04 13:05
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('pretixbase', '0201_invoiceline_event_location'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='needs_password_change',
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
]
|
||||
@@ -1,17 +0,0 @@
|
||||
# Generated by Django 3.2.2 on 2021-11-08 07:51
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('pretixbase', '0202_user_needs_password_change'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='orderposition',
|
||||
name='is_bundled',
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
]
|
||||
@@ -1,46 +0,0 @@
|
||||
# Generated by Django 3.2.2 on 2021-11-08 07:51
|
||||
|
||||
from django.db import migrations, models
|
||||
from django.db.models import Count, OuterRef, Subquery
|
||||
from django.db.models.functions import Coalesce
|
||||
|
||||
|
||||
def fill_is_bundled(apps, schema_editor):
|
||||
# We cannot really know if a position was bundled or an add-on, but we can at least guess
|
||||
ItemBundle = apps.get_model("pretixbase", "ItemBundle")
|
||||
OrderPosition = apps.get_model("pretixbase", "OrderPosition")
|
||||
|
||||
for ib in ItemBundle.objects.iterator():
|
||||
OrderPosition.all.alias(
|
||||
pos_earlier=Coalesce(Subquery(
|
||||
OrderPosition.all.filter(
|
||||
canceled=False,
|
||||
addon_to=OuterRef('addon_to'),
|
||||
item=ib.bundled_item,
|
||||
variation=ib.bundled_variation,
|
||||
positionid__lt=OuterRef('positionid'),
|
||||
).values('addon_to').order_by().annotate(c=Count('*')).values('c'),
|
||||
output_field=models.IntegerField()
|
||||
), 0)
|
||||
).filter(
|
||||
canceled=False,
|
||||
addon_to__item=ib.base_item,
|
||||
item=ib.bundled_item,
|
||||
variation=ib.bundled_variation,
|
||||
pos_earlier__lt=ib.count,
|
||||
).update(
|
||||
is_bundled=True
|
||||
)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('pretixbase', '0203_orderposition_is_bundled'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(
|
||||
fill_is_bundled,
|
||||
migrations.RunPython.noop,
|
||||
),
|
||||
]
|
||||
@@ -23,6 +23,7 @@ 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 (
|
||||
|
||||
@@ -36,7 +36,7 @@ from django.db import transaction
|
||||
dirty_transactions = threading.local()
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
fail_loudly = os.getenv('PRETIX_DIRTY_TRANSACTIONS_QUIET', 'false' if settings.DEBUG else 'true') not in ('true', 'True', 'on', '1')
|
||||
fail_loudly = os.getenv('PRETIX_DIRTY_TRANSACTIONS_QUIET', 'false') not in ('true', 'True', 'on', '1')
|
||||
|
||||
|
||||
class DirtyTransactionsForOrderException(Exception):
|
||||
|
||||
@@ -113,8 +113,6 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
|
||||
:type date_joined: datetime
|
||||
:param locale: The user's preferred locale code.
|
||||
:type locale: str
|
||||
:param needs_password_change: Whether this user's password needs to be changed.
|
||||
:type needs_password_change: bool
|
||||
:param timezone: The user's preferred timezone.
|
||||
:type timezone: str
|
||||
"""
|
||||
@@ -132,8 +130,6 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
|
||||
verbose_name=_('Is site admin'))
|
||||
date_joined = models.DateTimeField(auto_now_add=True,
|
||||
verbose_name=_('Date joined'))
|
||||
needs_password_change = models.BooleanField(default=False,
|
||||
verbose_name=_('Force user to select a new password'))
|
||||
locale = models.CharField(max_length=50,
|
||||
choices=settings.LANGUAGES,
|
||||
default=settings.LANGUAGE_CODE,
|
||||
|
||||
54
src/pretix/base/models/checks.py
Normal file
54
src/pretix/base/models/checks.py
Normal file
@@ -0,0 +1,54 @@
|
||||
#
|
||||
# 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()
|
||||
@@ -565,8 +565,6 @@ class Event(EventMixin, LoggedModel):
|
||||
self.settings.ticketoutput_pdf__enabled = True
|
||||
self.settings.ticketoutput_passbook__enabled = True
|
||||
self.settings.event_list_type = 'calendar'
|
||||
self.settings.invoice_email_attachment = True
|
||||
self.settings.name_scheme = 'given_family'
|
||||
|
||||
@property
|
||||
def social_image(self):
|
||||
|
||||
@@ -237,7 +237,7 @@ class Invoice(models.Model):
|
||||
def _get_invoice_number_from_order(self):
|
||||
return '{order}-{count}'.format(
|
||||
order=self.order.code,
|
||||
count=Invoice.objects.filter(event=self.event, prefix=self.prefix, invoice_no__startswith=f"{self.order.code}-", order=self.order).count() + 1,
|
||||
count=Invoice.objects.filter(event=self.event, order=self.order).count() + 1,
|
||||
)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
@@ -264,7 +264,6 @@ class Invoice(models.Model):
|
||||
self.invoice_no = self._get_invoice_number_from_order()
|
||||
try:
|
||||
with transaction.atomic():
|
||||
self.full_invoice_no = self.prefix + self.invoice_no
|
||||
return super().save(*args, **kwargs)
|
||||
except DatabaseError:
|
||||
# Suppress duplicate key errors and try again
|
||||
@@ -329,8 +328,6 @@ class InvoiceLine(models.Model):
|
||||
:type event_date_from: datetime
|
||||
:param event_date_to: Event end date of the (sub)event at the time the invoice was created
|
||||
:type event_date_to: datetime
|
||||
:param event_location: Event location of the (sub)event at the time the invoice was created
|
||||
:type event_location: str
|
||||
:param item: The item this line refers to
|
||||
:type item: Item
|
||||
:param variation: The variation this line refers to
|
||||
@@ -348,7 +345,6 @@ class InvoiceLine(models.Model):
|
||||
subevent = models.ForeignKey('SubEvent', null=True, blank=True, on_delete=models.PROTECT)
|
||||
event_date_from = models.DateTimeField(null=True)
|
||||
event_date_to = models.DateTimeField(null=True)
|
||||
event_location = models.TextField(null=True, blank=True)
|
||||
item = models.ForeignKey('Item', null=True, blank=True, on_delete=models.PROTECT)
|
||||
variation = models.ForeignKey('ItemVariation', null=True, blank=True, on_delete=models.PROTECT)
|
||||
attendee_name = models.TextField(null=True, blank=True)
|
||||
|
||||
@@ -75,7 +75,7 @@ from pretix.base.email import get_email_context
|
||||
from pretix.base.i18n import language
|
||||
from pretix.base.models import Customer, User
|
||||
from pretix.base.reldate import RelativeDateWrapper
|
||||
from pretix.base.services.locking import LOCK_TIMEOUT, NoLockManager
|
||||
from pretix.base.services.locking import NoLockManager
|
||||
from pretix.base.settings import PERSON_NAME_SCHEMES
|
||||
from pretix.base.signals import order_gracefully_delete
|
||||
|
||||
@@ -581,7 +581,6 @@ class Order(LockModel, LoggedModel):
|
||||
Returns whether or not this order can be canceled by the user.
|
||||
"""
|
||||
from .checkin import Checkin
|
||||
from .items import ItemAddOn
|
||||
|
||||
if self.status not in (Order.STATUS_PENDING, Order.STATUS_PAID) or not self.count_positions:
|
||||
return False
|
||||
@@ -607,10 +606,7 @@ class Order(LockModel, LoggedModel):
|
||||
if self.user_change_deadline and now() > self.user_change_deadline:
|
||||
return False
|
||||
|
||||
return (
|
||||
(self.event.settings.change_allow_user_variation and any([op.has_variations for op in positions])) or
|
||||
(self.event.settings.change_allow_user_addons and ItemAddOn.objects.filter(base_item_id__in=[op.item_id for op in positions]).exists())
|
||||
)
|
||||
return self.event.settings.change_allow_user_variation and any([op.has_variations for op in positions])
|
||||
|
||||
@property
|
||||
@scopes_disabled()
|
||||
@@ -1310,7 +1306,6 @@ class AbstractPosition(models.Model):
|
||||
seat = models.ForeignKey(
|
||||
'Seat', null=True, blank=True, on_delete=models.PROTECT
|
||||
)
|
||||
is_bundled = models.BooleanField(default=False)
|
||||
|
||||
company = models.CharField(max_length=255, blank=True, verbose_name=_('Company name'), null=True)
|
||||
street = models.TextField(verbose_name=_('Address'), blank=True, null=True)
|
||||
@@ -1547,7 +1542,7 @@ class OrderPayment(models.Model):
|
||||
return self.order.event.get_payment_providers(cached=True).get(self.provider)
|
||||
|
||||
@transaction.atomic()
|
||||
def _mark_paid_inner(self, force, count_waitinglist, user, auth, ignore_date=False, overpaid=False):
|
||||
def _mark_paid(self, force, count_waitinglist, user, auth, ignore_date=False, overpaid=False):
|
||||
from pretix.base.signals import order_paid
|
||||
can_be_paid = self.order._can_be_paid(count_waitinglist=count_waitinglist, ignore_date=ignore_date, force=force)
|
||||
if can_be_paid is not True:
|
||||
@@ -1623,6 +1618,10 @@ class OrderPayment(models.Model):
|
||||
:type mail_text: str
|
||||
:raises Quota.QuotaExceededException: if the quota is exceeded and ``force`` is ``False``
|
||||
"""
|
||||
from pretix.base.services.invoices import (
|
||||
generate_invoice, invoice_qualified,
|
||||
)
|
||||
|
||||
with transaction.atomic():
|
||||
locked_instance = OrderPayment.objects.select_for_update().get(pk=self.pk)
|
||||
if locked_instance.state == self.PAYMENT_STATE_CONFIRMED:
|
||||
@@ -1666,15 +1665,7 @@ class OrderPayment(models.Model):
|
||||
))
|
||||
return
|
||||
|
||||
self._mark_order_paid(count_waitinglist, send_mail, force, user, auth, mail_text, ignore_date, lock, payment_sum - refund_sum)
|
||||
|
||||
def _mark_order_paid(self, count_waitinglist=True, send_mail=True, force=False, user=None, auth=None, mail_text='',
|
||||
ignore_date=False, lock=True, payment_refund_sum=0):
|
||||
from pretix.base.services.invoices import (
|
||||
generate_invoice, invoice_qualified,
|
||||
)
|
||||
|
||||
if (self.order.status == Order.STATUS_PENDING and self.order.expires > now() + timedelta(seconds=LOCK_TIMEOUT * 2)) or not lock:
|
||||
if (self.order.status == Order.STATUS_PENDING and self.order.expires > now() + timedelta(hours=12)) or not lock:
|
||||
# Performance optimization. In this case, there's really no reason to lock everything and an atomic
|
||||
# database transaction is more than enough.
|
||||
lockfn = NoLockManager
|
||||
@@ -1682,8 +1673,8 @@ class OrderPayment(models.Model):
|
||||
lockfn = self.order.event.lock
|
||||
|
||||
with lockfn():
|
||||
self._mark_paid_inner(force, count_waitinglist, user, auth, overpaid=payment_refund_sum > self.order.total,
|
||||
ignore_date=ignore_date)
|
||||
self._mark_paid(force, count_waitinglist, user, auth, overpaid=payment_sum - refund_sum > self.order.total,
|
||||
ignore_date=ignore_date)
|
||||
|
||||
invoice = None
|
||||
if invoice_qualified(self.order):
|
||||
@@ -2571,6 +2562,7 @@ class CartPosition(AbstractPosition):
|
||||
max_digits=10, decimal_places=2,
|
||||
null=True, blank=True
|
||||
)
|
||||
is_bundled = models.BooleanField(default=False)
|
||||
|
||||
objects = ScopedManager(organizer='event__organizer')
|
||||
|
||||
|
||||
@@ -736,11 +736,7 @@ def process_exit_all(sender, **kwargs):
|
||||
exit_all_at__isnull=False
|
||||
).select_related('event', 'event__organizer')
|
||||
for cl in qs:
|
||||
positions = cl.positions_inside.filter(
|
||||
Q(last_exit__isnull=True) | Q(last_exit__lte=cl.exit_all_at),
|
||||
last_entry__lte=cl.exit_all_at,
|
||||
)
|
||||
for p in positions:
|
||||
for p in cl.positions_inside:
|
||||
with scope(organizer=cl.event.organizer):
|
||||
ci = Checkin.objects.create(
|
||||
position=p, list=cl, auto_checked_in=True, type=Checkin.TYPE_EXIT, datetime=cl.exit_all_at
|
||||
@@ -752,9 +748,6 @@ def process_exit_all(sender, **kwargs):
|
||||
cl.event.settings.delete(f'autocheckin_dst_hack_{cl.pk}')
|
||||
try:
|
||||
cl.exit_all_at = make_aware(datetime.combine(d.date() + timedelta(days=1), d.time()), cl.event.timezone)
|
||||
except pytz.exceptions.AmbiguousTimeError:
|
||||
cl.exit_all_at = make_aware(datetime.combine(d.date() + timedelta(days=1), d.time()), cl.event.timezone,
|
||||
is_dst=False)
|
||||
except pytz.exceptions.NonExistentTimeError:
|
||||
cl.event.settings.set(f'autocheckin_dst_hack_{cl.pk}', True)
|
||||
d += timedelta(hours=1)
|
||||
|
||||
191
src/pretix/base/services/checks.py
Normal file
191
src/pretix/base/services/checks.py
Normal file
@@ -0,0 +1,191 @@
|
||||
#
|
||||
# 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()
|
||||
@@ -69,10 +69,6 @@ from pretix.helpers.models import modelcopy
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _location_oneliner(loc):
|
||||
return ', '.join([l.strip() for l in loc.splitlines() if l and l.strip()])
|
||||
|
||||
|
||||
@transaction.atomic
|
||||
def build_invoice(invoice: Invoice) -> Invoice:
|
||||
invoice.locale = invoice.event.settings.get('invoice_language', invoice.event.settings.locale)
|
||||
@@ -180,38 +176,19 @@ def build_invoice(invoice: Invoice) -> Invoice:
|
||||
reverse_charge = False
|
||||
|
||||
positions.sort(key=lambda p: p.sort_key)
|
||||
fees = list(invoice.order.fees.all())
|
||||
|
||||
locations = {
|
||||
str((p.subevent or invoice.event).location) if (p.subevent or invoice.event).location else None
|
||||
for p in positions
|
||||
}
|
||||
if fees and invoice.event.has_subevents:
|
||||
locations.add(None)
|
||||
|
||||
tax_texts = []
|
||||
|
||||
if invoice.event.settings.invoice_event_location and len(locations) == 1 and list(locations)[0] is not None:
|
||||
tax_texts.append(pgettext("invoice", "Event location: {location}").format(
|
||||
location=_location_oneliner(str(list(locations)[0]))
|
||||
))
|
||||
|
||||
for i, p in enumerate(positions):
|
||||
if not invoice.event.settings.invoice_include_free and p.price == Decimal('0.00') and not p.addon_c:
|
||||
continue
|
||||
|
||||
location = str((p.subevent or invoice.event).location) if (p.subevent or invoice.event).location else None
|
||||
|
||||
desc = str(p.item.name)
|
||||
if p.variation:
|
||||
desc += " - " + str(p.variation.value)
|
||||
if p.addon_to_id:
|
||||
desc = " + " + desc
|
||||
if invoice.event.settings.invoice_attendee_name and p.attendee_name:
|
||||
desc += "<br />" + pgettext("invoice", "Attendee: {name}").format(
|
||||
name=p.attendee_name
|
||||
)
|
||||
|
||||
desc += "<br />" + pgettext("invoice", "Attendee: {name}").format(name=p.attendee_name)
|
||||
for recv, resp in invoice_line_text.send(sender=invoice.event, position=p):
|
||||
if resp:
|
||||
desc += "<br/>" + resp
|
||||
@@ -227,12 +204,6 @@ def build_invoice(invoice: Invoice) -> Invoice:
|
||||
|
||||
if invoice.event.has_subevents:
|
||||
desc += "<br />" + pgettext("subevent", "Date: {}").format(p.subevent)
|
||||
|
||||
if invoice.event.settings.invoice_event_location and location and len(locations) > 1:
|
||||
desc += "<br />" + pgettext("invoice", "Event location: {location}").format(
|
||||
location=_location_oneliner(location)
|
||||
)
|
||||
|
||||
InvoiceLine.objects.create(
|
||||
position=i,
|
||||
invoice=invoice,
|
||||
@@ -245,7 +216,6 @@ def build_invoice(invoice: Invoice) -> Invoice:
|
||||
attendee_name=p.attendee_name if invoice.event.settings.invoice_attendee_name else None,
|
||||
event_date_from=p.subevent.date_from if invoice.event.has_subevents else invoice.event.date_from,
|
||||
event_date_to=p.subevent.date_to if invoice.event.has_subevents else invoice.event.date_to,
|
||||
event_location=location if invoice.event.settings.invoice_event_location else None,
|
||||
tax_rate=p.tax_rate, tax_name=p.tax_rule.name if p.tax_rule else ''
|
||||
)
|
||||
|
||||
@@ -258,7 +228,7 @@ def build_invoice(invoice: Invoice) -> Invoice:
|
||||
tax_texts.append(tax_text)
|
||||
|
||||
offset = len(positions)
|
||||
for i, fee in enumerate(fees):
|
||||
for i, fee in enumerate(invoice.order.fees.all()):
|
||||
if fee.fee_type == OrderFee.FEE_TYPE_OTHER and fee.description:
|
||||
fee_title = fee.description
|
||||
else:
|
||||
@@ -272,12 +242,6 @@ def build_invoice(invoice: Invoice) -> Invoice:
|
||||
gross_value=fee.value,
|
||||
event_date_from=None if invoice.event.has_subevents else invoice.event.date_from,
|
||||
event_date_to=None if invoice.event.has_subevents else invoice.event.date_to,
|
||||
event_location=(
|
||||
None if invoice.event.has_subevents
|
||||
else (str(invoice.event.location)
|
||||
if invoice.event.settings.invoice_event_location and invoice.event.location
|
||||
else None)
|
||||
),
|
||||
tax_value=fee.tax_value,
|
||||
tax_rate=fee.tax_rate,
|
||||
tax_name=fee.tax_rule.name if fee.tax_rule else '',
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
|
||||
import json
|
||||
import logging
|
||||
from collections import Counter, defaultdict, namedtuple
|
||||
from collections import Counter, namedtuple
|
||||
from datetime import datetime, time, timedelta
|
||||
from decimal import Decimal
|
||||
from typing import List, Optional
|
||||
@@ -46,7 +46,7 @@ from django.core.cache import cache
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import transaction
|
||||
from django.db.models import (
|
||||
Count, Exists, F, IntegerField, Max, Min, OuterRef, Q, Sum, Value,
|
||||
Exists, F, IntegerField, Max, Min, OuterRef, Q, Sum, Value,
|
||||
)
|
||||
from django.db.models.functions import Coalesce, Greatest
|
||||
from django.db.transaction import get_connection
|
||||
@@ -73,7 +73,7 @@ from pretix.base.models.orders import (
|
||||
InvoiceAddress, OrderFee, OrderRefund, generate_secret,
|
||||
)
|
||||
from pretix.base.models.organizer import TeamAPIToken
|
||||
from pretix.base.models.tax import TAXED_ZERO, TaxedPrice, TaxRule
|
||||
from pretix.base.models.tax import TaxRule
|
||||
from pretix.base.payment import BasePaymentProvider, PaymentException
|
||||
from pretix.base.reldate import RelativeDateWrapper
|
||||
from pretix.base.secrets import assign_ticket_secret
|
||||
@@ -122,7 +122,8 @@ error_messages = {
|
||||
'from your cart.'),
|
||||
'voucher_invalid_item': _('The voucher code used for one of the items in your cart is not valid for this item. We '
|
||||
'removed this item from your cart.'),
|
||||
'voucher_required': _('You need a valid voucher code to order one of the products.'),
|
||||
'voucher_required': _('You need a valid voucher code to order one of the products in your cart. We removed this '
|
||||
'item from your cart.'),
|
||||
'some_subevent_not_started': _('The presale period for one of the events in your cart has not yet started. The '
|
||||
'affected positions have been removed from your cart.'),
|
||||
'some_subevent_ended': _('The presale period for one of the events in your cart has ended. The affected '
|
||||
@@ -130,13 +131,6 @@ error_messages = {
|
||||
'seat_invalid': _('One of the seats in your order was invalid, we removed the position from your cart.'),
|
||||
'seat_unavailable': _('One of the seats in your order has been taken in the meantime, we removed the position from your cart.'),
|
||||
'country_blocked': _('One of the selected products is not available in the selected country.'),
|
||||
'not_for_sale': _('You selected a product which is not available for sale.'),
|
||||
'addon_invalid_base': _('You can not select an add-on for the selected product.'),
|
||||
'addon_duplicate_item': _('You can not select two variations of the same add-on product.'),
|
||||
'addon_max_count': _('You can select at most %(max)s add-ons from the category %(cat)s for the product %(base)s.'),
|
||||
'addon_min_count': _('You need to select at least %(min)s add-ons from the category %(cat)s for the '
|
||||
'product %(base)s.'),
|
||||
'addon_no_multi': _('You can select every add-ons from the category %(cat)s for the product %(base)s at most once.'),
|
||||
}
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -1267,15 +1261,15 @@ class OrderChangeManager:
|
||||
ItemOperation = namedtuple('ItemOperation', ('position', 'item', 'variation'))
|
||||
SubeventOperation = namedtuple('SubeventOperation', ('position', 'subevent'))
|
||||
SeatOperation = namedtuple('SubeventOperation', ('position', 'seat'))
|
||||
PriceOperation = namedtuple('PriceOperation', ('position', 'price', 'price_diff'))
|
||||
PriceOperation = namedtuple('PriceOperation', ('position', 'price'))
|
||||
TaxRuleOperation = namedtuple('TaxRuleOperation', ('position', 'tax_rule'))
|
||||
MembershipOperation = namedtuple('MembershipOperation', ('position', 'membership'))
|
||||
CancelOperation = namedtuple('CancelOperation', ('position', 'price_diff'))
|
||||
CancelOperation = namedtuple('CancelOperation', ('position',))
|
||||
AddOperation = namedtuple('AddOperation', ('item', 'variation', 'price', 'addon_to', 'subevent', 'seat', 'membership'))
|
||||
SplitOperation = namedtuple('SplitOperation', ('position',))
|
||||
FeeValueOperation = namedtuple('FeeValueOperation', ('fee', 'value', 'price_diff'))
|
||||
AddFeeOperation = namedtuple('AddFeeOperation', ('fee', 'price_diff'))
|
||||
CancelFeeOperation = namedtuple('CancelFeeOperation', ('fee', 'price_diff'))
|
||||
FeeValueOperation = namedtuple('FeeValueOperation', ('fee', 'value'))
|
||||
AddFeeOperation = namedtuple('AddFeeOperation', ('fee',))
|
||||
CancelFeeOperation = namedtuple('CancelFeeOperation', ('fee',))
|
||||
RegenerateSecretOperation = namedtuple('RegenerateSecretOperation', ('position',))
|
||||
|
||||
def __init__(self, order: Order, user=None, auth=None, notify=True, reissue_invoice=True):
|
||||
@@ -1392,7 +1386,7 @@ class OrderChangeManager:
|
||||
if self.order.event.settings.invoice_include_free or price.gross != Decimal('0.00') or position.price != Decimal('0.00'):
|
||||
self._invoice_dirty = True
|
||||
|
||||
self._operations.append(self.PriceOperation(position, price, price.gross - position.price))
|
||||
self._operations.append(self.PriceOperation(position, price))
|
||||
|
||||
def change_tax_rule(self, position_or_fee, tax_rule: TaxRule):
|
||||
self._operations.append(self.TaxRuleOperation(position_or_fee, tax_rule))
|
||||
@@ -1432,28 +1426,28 @@ class OrderChangeManager:
|
||||
new_tax = tax_rule.tax(pos.price, base_price_is='gross', currency=self.event.currency,
|
||||
override_tax_rate=new_rate)
|
||||
self._totaldiff += new_tax.gross - pos.price
|
||||
self._operations.append(self.PriceOperation(pos, new_tax, new_tax.gross - pos.price))
|
||||
self._operations.append(self.PriceOperation(pos, new_tax))
|
||||
|
||||
def cancel_fee(self, fee: OrderFee):
|
||||
self._totaldiff -= fee.value
|
||||
self._operations.append(self.CancelFeeOperation(fee, -fee.value))
|
||||
self._operations.append(self.CancelFeeOperation(fee))
|
||||
self._invoice_dirty = True
|
||||
|
||||
def add_fee(self, fee: OrderFee):
|
||||
self._totaldiff += fee.value
|
||||
self._invoice_dirty = True
|
||||
self._operations.append(self.AddFeeOperation(fee, fee.value))
|
||||
self._operations.append(self.AddFeeOperation(fee))
|
||||
|
||||
def change_fee(self, fee: OrderFee, value: Decimal):
|
||||
value = (fee.tax_rule or TaxRule.zero()).tax(value, base_price_is='gross')
|
||||
self._totaldiff += value.gross - fee.value
|
||||
self._invoice_dirty = True
|
||||
self._operations.append(self.FeeValueOperation(fee, value, value.gross - fee.value))
|
||||
self._operations.append(self.FeeValueOperation(fee, value))
|
||||
|
||||
def cancel(self, position: OrderPosition):
|
||||
self._totaldiff -= position.price
|
||||
self._quotadiff.subtract(position.quotas)
|
||||
self._operations.append(self.CancelOperation(position, -position.price))
|
||||
self._operations.append(self.CancelOperation(position))
|
||||
if position.seat:
|
||||
self._seatdiff.subtract([position.seat])
|
||||
|
||||
@@ -1478,7 +1472,7 @@ class OrderChangeManager:
|
||||
try:
|
||||
if price is None:
|
||||
price = get_price(item, variation, subevent=subevent, invoice_address=self._invoice_address)
|
||||
elif not isinstance(price, TaxedPrice):
|
||||
else:
|
||||
price = item.tax(price, base_price_is='gross', invoice_address=self._invoice_address)
|
||||
except TaxRule.SaleNotAllowed:
|
||||
raise OrderError(self.error_messages['tax_rule_country_blocked'])
|
||||
@@ -1521,190 +1515,6 @@ class OrderChangeManager:
|
||||
|
||||
self._operations.append(self.SplitOperation(position))
|
||||
|
||||
def set_addons(self, addons):
|
||||
if self._operations:
|
||||
raise ValueError("Setting addons should be the first/only operation")
|
||||
|
||||
# Prepare various containers to hold data later
|
||||
current_addons = defaultdict(lambda: defaultdict(list)) # OrderPos -> currently attached add-ons
|
||||
input_addons = defaultdict(Counter) # OrderPos -> final desired set of add-ons
|
||||
selected_addons = defaultdict(Counter) # OrderPos, ItemAddOn -> final desired set of add-ons
|
||||
opcache = {} # OrderPos.pk -> OrderPos
|
||||
quota_diff = Counter() # Quota -> Number of usages
|
||||
available_categories = defaultdict(set) # OrderPos -> Category IDs to choose from
|
||||
price_included = defaultdict(dict) # OrderPos -> CategoryID -> bool(price is included)
|
||||
toplevel_op = self.order.positions.filter(
|
||||
addon_to__isnull=True
|
||||
).prefetch_related(
|
||||
'addons', 'item__addons', 'item__addons__addon_category'
|
||||
).select_related('item', 'variation')
|
||||
|
||||
_items_cache = {
|
||||
i.pk: i
|
||||
for i in self.event.items.select_related('category').prefetch_related(
|
||||
'addons', 'bundles', 'addons__addon_category', 'quotas'
|
||||
).annotate(
|
||||
has_variations=Count('variations'),
|
||||
).filter(
|
||||
id__in=[a['item'] for a in addons]
|
||||
).order_by()
|
||||
}
|
||||
_variations_cache = {
|
||||
v.pk: v
|
||||
for v in ItemVariation.objects.filter(item__event=self.event).prefetch_related(
|
||||
'quotas'
|
||||
).select_related('item', 'item__event').filter(
|
||||
id__in=[a['variation'] for a in addons if a.get('variation')]
|
||||
).order_by()
|
||||
}
|
||||
|
||||
# Prefill some of the cache containers
|
||||
for op in toplevel_op:
|
||||
if op.canceled:
|
||||
continue
|
||||
available_categories[op.pk] = {iao.addon_category_id for iao in op.item.addons.all()}
|
||||
price_included[op.pk] = {iao.addon_category_id: iao.price_included for iao in op.item.addons.all()}
|
||||
opcache[op.pk] = op
|
||||
for a in op.addons.all():
|
||||
if a.canceled:
|
||||
continue
|
||||
|
||||
if not a.is_bundled:
|
||||
current_addons[op][a.item_id, a.variation_id].append(a)
|
||||
|
||||
# Create operations, perform various checks
|
||||
for a in addons:
|
||||
# Check whether the specified items are part of what we just fetched from the database
|
||||
# If they are not, the user supplied item IDs which either do not exist or belong to
|
||||
# a different event
|
||||
if a['item'] not in _items_cache or (a['variation'] and a['variation'] not in _variations_cache):
|
||||
raise OrderError(error_messages['not_for_sale'])
|
||||
|
||||
# Only attach addons to things that are actually in this user's cart
|
||||
if a['addon_to'] not in opcache:
|
||||
raise OrderError(error_messages['addon_invalid_base'])
|
||||
|
||||
op = opcache[a['addon_to']]
|
||||
item = _items_cache[a['item']]
|
||||
variation = _variations_cache[a['variation']] if a['variation'] is not None else None
|
||||
|
||||
if item.category_id not in available_categories[op.pk]:
|
||||
raise OrderError(error_messages['addon_invalid_base'])
|
||||
|
||||
# Fetch all quotas. If there are no quotas, this item is not allowed to be sold.
|
||||
quotas = list(item.quotas.filter(subevent=op.subevent)
|
||||
if variation is None else variation.quotas.filter(subevent=op.subevent))
|
||||
if not quotas:
|
||||
raise OrderError(error_messages['unavailable'])
|
||||
|
||||
if (a['item'], a['variation']) in input_addons[op.id]:
|
||||
raise OrderError(error_messages['addon_duplicate_item'])
|
||||
|
||||
if item.require_voucher or op.item.hide_without_voucher or (op.variation and op.variation.hide_without_voucher):
|
||||
raise OrderError(error_messages['voucher_required'])
|
||||
|
||||
if not item.is_available() or (variation and not variation.is_available()):
|
||||
raise OrderError(error_messages['unavailable'])
|
||||
|
||||
if self.order.sales_channel not in item.sales_channels or (
|
||||
variation and self.order.sales_channel not in variation.sales_channels):
|
||||
raise OrderError(error_messages['unavailable'])
|
||||
|
||||
if op.subevent and item.pk in op.subevent.item_overrides and not op.subevent.item_overrides[op.item.pk].is_available():
|
||||
raise OrderError(error_messages['not_for_sale'])
|
||||
|
||||
if op.subevent and variation and variation.pk in op.subevent.var_overrides and \
|
||||
not op.subevent.var_overrides[variation.pk].is_available():
|
||||
raise OrderError(error_messages['not_for_sale'])
|
||||
|
||||
if item.has_variations and not variation:
|
||||
raise OrderError(error_messages['not_for_sale'])
|
||||
|
||||
if variation and variation.item_id != item.pk:
|
||||
raise OrderError(error_messages['not_for_sale'])
|
||||
|
||||
if op.subevent and op.subevent.presale_start and now() < op.subevent.presale_start:
|
||||
raise OrderError(error_messages['not_started'])
|
||||
|
||||
if (op.subevent and op.subevent.presale_has_ended) or self.event.presale_has_ended:
|
||||
raise OrderError(error_messages['ended'])
|
||||
|
||||
if item.require_bundling:
|
||||
raise OrderError(error_messages['unavailable'])
|
||||
|
||||
input_addons[op.id][a['item'], a['variation']] = a.get('count', 1)
|
||||
selected_addons[op.id, item.category_id][a['item'], a['variation']] = a.get('count', 1)
|
||||
|
||||
if price_included[op.pk].get(item.category_id):
|
||||
price = TAXED_ZERO
|
||||
else:
|
||||
price = get_price(
|
||||
item, variation, voucher=None, custom_price=a.get('price'), subevent=op.subevent,
|
||||
custom_price_is_net=self.event.settings.display_net_prices,
|
||||
invoice_address=self._invoice_address,
|
||||
)
|
||||
|
||||
if a.get('count', 1) > len(current_addons[op][a['item'], a['variation']]):
|
||||
# This add-on is new, add it to the cart
|
||||
for quota in quotas:
|
||||
quota_diff[quota] += a.get('count', 1) - len(current_addons[op][a['item'], a['variation']])
|
||||
|
||||
for i in range(a.get('count', 1) - len(current_addons[op][a['item'], a['variation']])):
|
||||
self.add_position(
|
||||
item=item, variation=variation, price=price,
|
||||
addon_to=op, subevent=op.subevent, seat=None,
|
||||
)
|
||||
|
||||
# Check constraints on the add-on combinations
|
||||
for op in toplevel_op:
|
||||
item = op.item
|
||||
for iao in item.addons.all():
|
||||
selected = selected_addons[op.id, iao.addon_category_id]
|
||||
n_per_i = Counter()
|
||||
for (i, v), c in selected.items():
|
||||
n_per_i[i] += c
|
||||
if sum(selected.values()) > iao.max_count:
|
||||
# TODO: Proper i18n
|
||||
# TODO: Proper pluralization
|
||||
raise OrderError(
|
||||
error_messages['addon_max_count'],
|
||||
{
|
||||
'base': str(item.name),
|
||||
'max': iao.max_count,
|
||||
'cat': str(iao.addon_category.name),
|
||||
}
|
||||
)
|
||||
elif sum(selected.values()) < iao.min_count:
|
||||
# TODO: Proper i18n
|
||||
# TODO: Proper pluralization
|
||||
raise OrderError(
|
||||
error_messages['addon_min_count'],
|
||||
{
|
||||
'base': str(item.name),
|
||||
'min': iao.min_count,
|
||||
'cat': str(iao.addon_category.name),
|
||||
}
|
||||
)
|
||||
elif any(v > 1 for v in n_per_i.values()) and not iao.multi_allowed:
|
||||
raise OrderError(
|
||||
error_messages['addon_no_multi'],
|
||||
{
|
||||
'base': str(item.name),
|
||||
'cat': str(iao.addon_category.name),
|
||||
}
|
||||
)
|
||||
|
||||
# Detect removed add-ons and create RemoveOperations
|
||||
for cp, al in list(current_addons.items()):
|
||||
for k, v in al.items():
|
||||
input_num = input_addons[cp.id].get(k, 0)
|
||||
current_num = len(current_addons[cp].get(k, []))
|
||||
if input_num < current_num:
|
||||
for a in current_addons[cp][k][:current_num - input_num]:
|
||||
if a.canceled:
|
||||
continue
|
||||
self.cancel(a)
|
||||
|
||||
def _check_seats(self):
|
||||
for seat, diff in self._seatdiff.items():
|
||||
if diff <= 0:
|
||||
|
||||
@@ -20,13 +20,11 @@
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
from urllib.error import HTTPError
|
||||
|
||||
import vat_moss.errors
|
||||
import vat_moss.id
|
||||
from django.conf import settings
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from zeep import Client, Transport
|
||||
from zeep.cache import SqliteCache
|
||||
@@ -85,12 +83,14 @@ def _validate_vat_id_CH(vat_id, country_code):
|
||||
|
||||
vat_id = re.sub('[^A-Z0-9]', '', vat_id.replace('HR', '').replace('MWST', ''))
|
||||
try:
|
||||
transport = Transport(cache=SqliteCache(os.path.join(settings.CACHE_DIR, "validate_vat_id_ch_zeep_cache.db")))
|
||||
transport = Transport(cache=SqliteCache())
|
||||
client = Client(
|
||||
'https://www.uid-wse.admin.ch/V5.0/PublicServices.svc?wsdl',
|
||||
'https://www.uid-wse-a.admin.ch/V5.0/PublicServices.svc?wsdl',
|
||||
transport=transport
|
||||
)
|
||||
result = client.service.ValidateUID(uid=vat_id)
|
||||
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.'))
|
||||
@@ -118,10 +118,6 @@ def _validate_vat_id_CH(vat_id, country_code):
|
||||
'need to charge VAT on your invoice. You can get the tax amount '
|
||||
'back via the VAT reimbursement process.'
|
||||
))
|
||||
else:
|
||||
if not result:
|
||||
raise VATIDFinalError(_('This VAT ID is not valid. Please re-check your input.'))
|
||||
return vat_id
|
||||
|
||||
|
||||
def validate_vat_id(vat_id, country_code):
|
||||
|
||||
@@ -310,17 +310,6 @@ DEFAULTS = {
|
||||
label=_("Show attendee names on invoices"),
|
||||
)
|
||||
},
|
||||
'invoice_event_location': {
|
||||
'default': 'False',
|
||||
'type': bool,
|
||||
'form_class': forms.BooleanField,
|
||||
'serializer_class': serializers.BooleanField,
|
||||
'form_kwargs': dict(
|
||||
label=_("Show event location on invoices"),
|
||||
help_text=_("The event location will be shown below the list of products if it is the same for all "
|
||||
"lines. It will be shown on every line if there are different locations.")
|
||||
)
|
||||
},
|
||||
'invoice_eu_currencies': {
|
||||
'default': 'True',
|
||||
'type': bool,
|
||||
@@ -426,7 +415,7 @@ DEFAULTS = {
|
||||
)
|
||||
},
|
||||
'invoice_include_expire_date': {
|
||||
'default': 'False', # default for new events is True
|
||||
'default': 'False',
|
||||
'type': bool,
|
||||
'form_class': forms.BooleanField,
|
||||
'serializer_class': serializers.BooleanField,
|
||||
@@ -482,7 +471,7 @@ DEFAULTS = {
|
||||
)
|
||||
},
|
||||
'invoice_renderer': {
|
||||
'default': 'classic', # default for new events is 'modern1'
|
||||
'default': 'classic',
|
||||
'type': str,
|
||||
},
|
||||
'ticket_secret_generator': {
|
||||
@@ -908,7 +897,7 @@ DEFAULTS = {
|
||||
'type': str
|
||||
},
|
||||
'invoice_email_attachment': {
|
||||
'default': 'False', # default for new events is True
|
||||
'default': 'False',
|
||||
'type': bool,
|
||||
'form_class': forms.BooleanField,
|
||||
'serializer_class': serializers.BooleanField,
|
||||
@@ -1241,7 +1230,7 @@ DEFAULTS = {
|
||||
)
|
||||
},
|
||||
'event_list_type': {
|
||||
'default': 'list', # default for new events is 'calendar'
|
||||
'default': 'list',
|
||||
'type': str,
|
||||
'form_class': forms.ChoiceField,
|
||||
'serializer_class': serializers.ChoiceField,
|
||||
@@ -1301,15 +1290,6 @@ DEFAULTS = {
|
||||
label=_("Customers can change the variation of the products they purchased"),
|
||||
)
|
||||
},
|
||||
'change_allow_user_addons': {
|
||||
'default': 'False',
|
||||
'type': bool,
|
||||
'form_class': forms.BooleanField,
|
||||
'serializer_class': serializers.BooleanField,
|
||||
'form_kwargs': dict(
|
||||
label=_("Customers can change their selected add-on products"),
|
||||
)
|
||||
},
|
||||
'change_allow_user_price': {
|
||||
'default': 'gte',
|
||||
'type': str,
|
||||
@@ -1994,7 +1974,7 @@ Your {organizer} team"""))
|
||||
),
|
||||
},
|
||||
'theme_color_success': {
|
||||
'default': '#50a167',
|
||||
'default': '#50A167',
|
||||
'type': str,
|
||||
'form_class': forms.CharField,
|
||||
'serializer_class': serializers.CharField,
|
||||
@@ -2016,7 +1996,7 @@ Your {organizer} team"""))
|
||||
),
|
||||
},
|
||||
'theme_color_danger': {
|
||||
'default': '#c44f4f',
|
||||
'default': '#C44F4F',
|
||||
'type': str,
|
||||
'form_class': forms.CharField,
|
||||
'serializer_class': serializers.CharField,
|
||||
@@ -2038,7 +2018,7 @@ Your {organizer} team"""))
|
||||
),
|
||||
},
|
||||
'theme_color_background': {
|
||||
'default': '#f5f5f5',
|
||||
'default': '#FFFFFF',
|
||||
'type': str,
|
||||
'form_class': forms.CharField,
|
||||
'serializer_class': serializers.CharField,
|
||||
@@ -2464,7 +2444,7 @@ Your {organizer} team"""))
|
||||
)
|
||||
},
|
||||
'name_scheme': {
|
||||
'default': 'full', # default for new events is 'given_family'
|
||||
'default': 'full',
|
||||
'type': str
|
||||
},
|
||||
'giftcard_length': {
|
||||
|
||||
@@ -14,17 +14,16 @@
|
||||
</o:OfficeDocumentSettings>
|
||||
</xml><![endif]-->
|
||||
<style type="text/css">
|
||||
body, .container {
|
||||
body {
|
||||
background-color: #eee;
|
||||
background-position: top;
|
||||
background-repeat: repeat-x;
|
||||
font-family: "Open Sans", "OpenSans", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 1.4;
|
||||
color: #333;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.container {
|
||||
padding: 20px;
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
table.layout > tr > td,
|
||||
@@ -37,8 +36,7 @@
|
||||
table.layout > tr > td.logo,
|
||||
table.layout > tbody > tr > td.logo,
|
||||
table.layout > thead > tr > td.logo {
|
||||
padding: {% if event.settings.logo_image_large %}0 0 0 0{% else %}20px 0 0 0{% endif %};
|
||||
mso-line-height-rule: at-least;
|
||||
padding: 20px 0 0 0;
|
||||
}
|
||||
|
||||
table.layout > tr > td.header,
|
||||
@@ -114,6 +112,10 @@
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 0 18px;
|
||||
}
|
||||
|
||||
::selection {
|
||||
background: {{ color }};
|
||||
color: #FFF;
|
||||
@@ -147,7 +149,7 @@
|
||||
table.layout > tr > td.containertd,
|
||||
table.layout > tbody > tr > td.containertd,
|
||||
table.layout > thead > tr > td.containertd {
|
||||
padding: 20px;
|
||||
padding: 15px 0;
|
||||
}
|
||||
|
||||
a.button {
|
||||
@@ -165,8 +167,7 @@
|
||||
}
|
||||
|
||||
.order-button {
|
||||
padding-top: 5px;
|
||||
text-align: center;
|
||||
padding-top: 5px
|
||||
}
|
||||
.order-button a.button {
|
||||
font-size: 12px;
|
||||
@@ -213,14 +214,14 @@
|
||||
<![endif]-->
|
||||
</head>
|
||||
<body align="center">
|
||||
<table width="100%"><tr><td align="center" class="container">
|
||||
<!--[if gte mso 9]>
|
||||
<table width="600"><tr><td align="center">
|
||||
<table width="100%"><tr><td align="center">
|
||||
<table width="600"><tr><td align="center"
|
||||
<![endif]-->
|
||||
<table class="layout" style="max-width:600px" border="0" cellspacing="0">
|
||||
{% if event.settings.logo_image %}
|
||||
<tr>
|
||||
<td align="center" class="logo">
|
||||
<td style="line-height: 0; {% if event.settings.logo_image_large %}padding: 0;{% endif %}" align="center" class="logo">
|
||||
{% if event.settings.logo_image_large %}
|
||||
<img src="{% if event.settings.logo_image|thumb:'600_x5000'|first == '/' %}{{ site_url }}{% endif %}{{ event.settings.logo_image|thumb:'600_x5000' }}" alt="{{ event.name }}" style="width:100%" />
|
||||
{% else %}
|
||||
@@ -231,6 +232,9 @@
|
||||
{% endif %}
|
||||
<tr>
|
||||
<td class="header" align="center">
|
||||
<!--[if gte mso 9]>
|
||||
<table cellpadding="20"><tr><td align="center">
|
||||
<![endif]-->
|
||||
{% if event %}
|
||||
<h2><a href="{% abseventurl event "presale:event.index" %}" target="_blank">{{ event.name }}</a>
|
||||
</h2>
|
||||
@@ -243,30 +247,51 @@
|
||||
{% block header %}
|
||||
<h1>{{ subject }}</h1>
|
||||
{% endblock %}
|
||||
<!--[if gte mso 9]>
|
||||
</td></tr></table>
|
||||
<![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="containertd">
|
||||
<!--[if gte mso 9]>
|
||||
<table cellpadding="20"><tr><td>
|
||||
<![endif]-->
|
||||
<div class="content">
|
||||
{{ body|safe }}
|
||||
</div>
|
||||
<!--[if gte mso 9]>
|
||||
</td></tr></table>
|
||||
<![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
{% if order %}
|
||||
<tr>
|
||||
<td class="order containertd">
|
||||
<!--[if gte mso 9]>
|
||||
<table cellpadding="20"><tr><td>
|
||||
<![endif]-->
|
||||
<div class="content">
|
||||
{% include "pretixbase/email/order_details.html" %}
|
||||
</div>
|
||||
<!--[if gte mso 9]>
|
||||
</td></tr></table>
|
||||
<![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if signature %}
|
||||
<tr>
|
||||
<td class="order containertd">
|
||||
<!--[if gte mso 9]>
|
||||
<table cellpadding="20"><tr><td>
|
||||
<![endif]-->
|
||||
<div class="content">
|
||||
{{ signature | safe }}
|
||||
</div>
|
||||
<!--[if gte mso 9]>
|
||||
</td></tr></table>
|
||||
<![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
@@ -278,7 +303,7 @@
|
||||
<br/>
|
||||
<!--[if gte mso 9]>
|
||||
</td></tr></table>
|
||||
<![endif]-->
|
||||
</td></tr></table>
|
||||
<![endif]-->
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
{% 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 %}
|
||||
@@ -1,29 +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 django import template
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.filter
|
||||
def classname(obj):
|
||||
return obj.__class__.__name__
|
||||
@@ -47,7 +47,7 @@ class DownloadView(TemplateView):
|
||||
return HttpResponse('1' if self.object.file else '0')
|
||||
elif self.object.file:
|
||||
resp = ChunkBasedFileResponse(self.object.file.file, content_type=self.object.type)
|
||||
resp['Content-Disposition'] = 'attachment; filename="{}"'.format(self.object.filename).encode('ascii', 'ignore')
|
||||
resp['Content-Disposition'] = 'attachment; filename="{}"'.format(self.object.filename)
|
||||
return resp
|
||||
else:
|
||||
return super().get(request, *args, **kwargs)
|
||||
|
||||
@@ -24,21 +24,6 @@ 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):
|
||||
@@ -47,14 +32,6 @@ 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
|
||||
|
||||
@@ -639,7 +639,6 @@ class CancelSettingsForm(SettingsForm):
|
||||
'change_allow_user_variation',
|
||||
'change_allow_user_price',
|
||||
'change_allow_user_until',
|
||||
'change_allow_user_addons',
|
||||
]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@@ -752,7 +751,6 @@ class InvoiceSettingsForm(SettingsForm):
|
||||
'invoice_reissue_after_modify',
|
||||
'invoice_generate',
|
||||
'invoice_attendee_name',
|
||||
'invoice_event_location',
|
||||
'invoice_include_expire_date',
|
||||
'invoice_numbers_consecutive',
|
||||
'invoice_numbers_prefix',
|
||||
|
||||
@@ -70,7 +70,6 @@ class UserEditForm(forms.ModelForm):
|
||||
'require_2fa',
|
||||
'is_active',
|
||||
'is_staff',
|
||||
'needs_password_change',
|
||||
'last_login'
|
||||
]
|
||||
|
||||
|
||||
@@ -69,11 +69,6 @@ class PermissionMiddleware:
|
||||
"user.settings.notifications.off",
|
||||
)
|
||||
|
||||
EXCEPTIONS_FORCED_PW_CHANGE = (
|
||||
"user.settings",
|
||||
"auth.logout"
|
||||
)
|
||||
|
||||
EXCEPTIONS_2FA = (
|
||||
"user.settings.2fa",
|
||||
"user.settings.2fa.add",
|
||||
@@ -135,9 +130,6 @@ class PermissionMiddleware:
|
||||
if url_name not in ('user.reauth', 'auth.logout'):
|
||||
return redirect(reverse('control:user.reauth') + '?next=' + quote(request.get_full_path()))
|
||||
|
||||
if request.user.needs_password_change and url_name not in self.EXCEPTIONS_FORCED_PW_CHANGE:
|
||||
return redirect(reverse('control:user.settings') + '?next=' + quote(request.get_full_path()))
|
||||
|
||||
if not request.user.require_2fa and settings.PRETIX_OBLIGATORY_2FA \
|
||||
and url_name not in self.EXCEPTIONS_2FA:
|
||||
return redirect(reverse('control:user.settings.2fa'))
|
||||
|
||||
@@ -422,6 +422,11 @@ 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'),
|
||||
},
|
||||
]
|
||||
})
|
||||
|
||||
|
||||
@@ -429,15 +429,6 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if request.user.needs_password_change %}
|
||||
<div class="alert alert-warning">
|
||||
{% blocktrans trimmed %}
|
||||
For security reasons, please change your password before you continue. Afterwards you
|
||||
will be redirected to your original destination.
|
||||
{% endblocktrans %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% block content %}
|
||||
{% endblock %}
|
||||
<footer>
|
||||
|
||||
@@ -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"
|
||||
<span class="fa fa-fw fa-hourglass-end" data-toggle="tooltip_html"
|
||||
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"
|
||||
<span class="fa fa-fw fa-warning" data-toggle="tooltip_html"
|
||||
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"
|
||||
<span class="fa fa-fw fa-magic" data-toggle="tooltip_html"
|
||||
title="{% blocktrans trimmed with date=c.datetime|date:'SHORT_DATETIME_FORMAT' %}Automatically checked in: {{ date }}{% endblocktrans %}"></span>
|
||||
{% endif %}
|
||||
</td>
|
||||
|
||||
@@ -42,37 +42,13 @@
|
||||
<fieldset>
|
||||
<legend>{% trans "Order changes" %}</legend>
|
||||
<div class="alert alert-info">
|
||||
<p>
|
||||
{% blocktrans trimmed %}
|
||||
Allowing customers to change their own orders is a complex process due to the many different options pretix provides. Therefore, this feature currently has the following
|
||||
limitations:
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
<ul>
|
||||
<li>{% trans "It is possible to switch to a different variation of the same product, but not to an entirely different product (except for add-on products)." %}</li>
|
||||
<li>{% trans "Changing the seat or the event date in an event series will become available in the future, but is not possible now." %}</li>
|
||||
<li>{% trans "If a change leads to a price change, there will not be a change to fees such as payment, service, or shipping fees, even though an additional payment might be required." %}</li>
|
||||
<li>{% trans "If an add-on product is newly added, the system currently does not validate if there are required questions or fields that need to be filled out." %}</li>
|
||||
<li>{% trans "Customers currently cannot switch to a product variation or add an add-on product that requires them to use a voucher or membership." %}</li>
|
||||
<li>{% trans "Additional constraints and validation steps added by plugins are not enforced." %}</li>
|
||||
</ul>
|
||||
{% blocktrans trimmed %}
|
||||
Allowing users to change their order is a feature under development. Therefore, currently only specific changes (such as changing the variation of a product) are possible. More options might be added later.
|
||||
{% endblocktrans %}
|
||||
</div>
|
||||
{% bootstrap_field form.change_allow_user_variation layout="control" %}
|
||||
{% bootstrap_field form.change_allow_user_addons layout="control" %}
|
||||
{% bootstrap_field form.change_allow_user_until layout="control" %}
|
||||
{% bootstrap_field form.change_allow_user_price layout="control" %}
|
||||
<div class="alert alert-info">
|
||||
<p>
|
||||
{% blocktrans trimmed %}
|
||||
If the change leads to a price reduction and automatic refunds are enabled for self-service cancellations,
|
||||
the system will try to refund the money automatically.
|
||||
{% endblocktrans %}
|
||||
{% blocktrans trimmed %}
|
||||
Refunds can be issued as a gift card if the respective option is set, but there is no customer choice between
|
||||
gift card and direct refund.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
</div>
|
||||
{% bootstrap_field form.change_allow_user_until layout="control" %}
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="form-group submit-group">
|
||||
|
||||
@@ -48,7 +48,6 @@
|
||||
<legend>{% trans "Invoice customization" %}</legend>
|
||||
{% bootstrap_field form.invoice_renderer layout="control" %}
|
||||
{% bootstrap_field form.invoice_attendee_name layout="control" %}
|
||||
{% bootstrap_field form.invoice_event_location layout="control" %}
|
||||
{% bootstrap_field form.invoice_include_expire_date layout="control" %}
|
||||
{% bootstrap_field form.invoice_introductory_text layout="control" %}
|
||||
{% bootstrap_field form.invoice_additional_text layout="control" %}
|
||||
|
||||
@@ -204,7 +204,7 @@
|
||||
{% bootstrap_field sform.logo_show_title layout="control" %}
|
||||
{% bootstrap_field sform.og_image layout="control" %}
|
||||
{% url "control:organizer.edit" organizer=request.organizer.slug as org_url %}
|
||||
{% propagated request.event org_url "primary_color" "primary_font" "theme_color_success" "theme_color_danger" "theme_color_background" "theme_round_borders" %}
|
||||
{% propagated request.event org_url "primary_color" "primary_font" "theme_color_success" "theme_color_danger" "theme_round_borders" %}
|
||||
{% bootstrap_field sform.primary_color layout="control" %}
|
||||
{% bootstrap_field sform.theme_color_success layout="control" %}
|
||||
{% bootstrap_field sform.theme_color_danger layout="control" %}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<div class="help-block">
|
||||
{% blocktrans trimmed %}
|
||||
This is the address users can buy your tickets at. Should be short, only contain lowercase
|
||||
letters, numbers, dots, and dashes, and must be unique among your events. We recommend some kind of
|
||||
letters and numbers, and must be unique among your events. We recommend some kind of
|
||||
abbreviation or a date with less than 10 characters that can be easily remembered, but you
|
||||
can also choose to use a random value.
|
||||
{% endblocktrans %}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% load i18n %}
|
||||
<div class="quotabox availability" data-toggle="tooltip_html" data-placement="top"
|
||||
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 %}">
|
||||
title="{% trans "Quota:" %} {{ q.name }}<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">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{% load i18n %}
|
||||
<a class="quotabox" data-toggle="tooltip_html" data-placement="top"
|
||||
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 %}"
|
||||
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 %}"
|
||||
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">
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
{% 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 %}
|
||||
@@ -0,0 +1,11 @@
|
||||
{% 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 %}
|
||||
@@ -360,19 +360,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|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>
|
||||
<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>
|
||||
{% 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|force_escape|force_escape }}<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 }}<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|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>
|
||||
<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>
|
||||
{% endif %}
|
||||
{% elif c.forced %}
|
||||
<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>
|
||||
<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>
|
||||
{% elif c.auto_checked_in %}
|
||||
<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>
|
||||
<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>
|
||||
{% else %}
|
||||
<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>
|
||||
<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>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
{% bootstrap_field form.email layout='control' %}
|
||||
{% bootstrap_field form.new_pw layout='control' %}
|
||||
{% bootstrap_field form.new_pw_repeat layout='control' %}
|
||||
{% bootstrap_field form.needs_password_change layout='control' %}
|
||||
</fieldset>
|
||||
<div class="form-group submit-group">
|
||||
<button type="submit" class="btn btn-primary btn-save">
|
||||
|
||||
@@ -45,7 +45,6 @@
|
||||
{% endif %}
|
||||
{% bootstrap_field form.last_login layout='control' %}
|
||||
{% bootstrap_field form.require_2fa layout='control' %}
|
||||
{% bootstrap_field form.needs_password_change layout='control' %}
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>{% trans "Team memberships" %}</legend>
|
||||
|
||||
@@ -55,6 +55,8 @@ 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'),
|
||||
|
||||
@@ -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, TemplateView
|
||||
from django.views.generic import FormView, ListView, TemplateView, DetailView
|
||||
|
||||
from pretix.base.models import LogEntry, OrderPayment, OrderRefund
|
||||
from pretix.base.models import Check, 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,3 +265,15 @@ 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'
|
||||
|
||||
@@ -401,10 +401,10 @@ class QuestionList(ListView):
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
questions = []
|
||||
ctx['questions'] = list(ctx['questions'])
|
||||
|
||||
if self.request.event.settings.attendee_names_asked:
|
||||
questions.append(
|
||||
ctx['questions'].append(
|
||||
FakeQuestion(
|
||||
id='attendee_name_parts',
|
||||
question=_('Attendee name'),
|
||||
@@ -416,7 +416,7 @@ class QuestionList(ListView):
|
||||
)
|
||||
|
||||
if self.request.event.settings.attendee_emails_asked:
|
||||
questions.append(
|
||||
ctx['questions'].append(
|
||||
FakeQuestion(
|
||||
id='attendee_email',
|
||||
question=_('Attendee email'),
|
||||
@@ -427,8 +427,8 @@ class QuestionList(ListView):
|
||||
)
|
||||
)
|
||||
|
||||
if self.request.event.settings.attendee_company_asked:
|
||||
questions.append(
|
||||
if self.request.event.settings.attendee_emails_asked:
|
||||
ctx['questions'].append(
|
||||
FakeQuestion(
|
||||
id='company',
|
||||
question=_('Company'),
|
||||
@@ -440,7 +440,7 @@ class QuestionList(ListView):
|
||||
)
|
||||
|
||||
if self.request.event.settings.attendee_addresses_asked:
|
||||
questions.append(
|
||||
ctx['questions'].append(
|
||||
FakeQuestion(
|
||||
id='street',
|
||||
question=_('Street'),
|
||||
@@ -450,7 +450,7 @@ class QuestionList(ListView):
|
||||
required=self.request.event.settings.attendee_addresses_required,
|
||||
)
|
||||
)
|
||||
questions.append(
|
||||
ctx['questions'].append(
|
||||
FakeQuestion(
|
||||
id='zipcode',
|
||||
question=_('ZIP code'),
|
||||
@@ -460,7 +460,7 @@ class QuestionList(ListView):
|
||||
required=self.request.event.settings.attendee_addresses_required,
|
||||
)
|
||||
)
|
||||
questions.append(
|
||||
ctx['questions'].append(
|
||||
FakeQuestion(
|
||||
id='city',
|
||||
question=_('City'),
|
||||
@@ -470,7 +470,7 @@ class QuestionList(ListView):
|
||||
required=self.request.event.settings.attendee_addresses_required,
|
||||
)
|
||||
)
|
||||
questions.append(
|
||||
ctx['questions'].append(
|
||||
FakeQuestion(
|
||||
id='country',
|
||||
question=_('Country'),
|
||||
@@ -481,9 +481,7 @@ class QuestionList(ListView):
|
||||
)
|
||||
)
|
||||
|
||||
questions += list(ctx['questions'])
|
||||
questions.sort(key=lambda q: q.position)
|
||||
ctx['questions'] = questions
|
||||
ctx['questions'].sort(key=lambda q: q.position)
|
||||
return ctx
|
||||
|
||||
|
||||
|
||||
@@ -1177,19 +1177,6 @@ class OrderTransition(OrderView):
|
||||
to = self.request.POST.get('status', '')
|
||||
if self.order.status in (Order.STATUS_PENDING, Order.STATUS_EXPIRED) and to == 'p' and self.mark_paid_form.is_valid():
|
||||
ps = self.mark_paid_form.cleaned_data['amount']
|
||||
|
||||
if ps == Decimal('0.00') and self.order.pending_sum <= Decimal('0.00'):
|
||||
p = self.order.payments.filter(state=OrderPayment.PAYMENT_STATE_CONFIRMED).last()
|
||||
if p:
|
||||
p._mark_order_paid(
|
||||
user=self.request.user,
|
||||
send_mail=self.mark_paid_form.cleaned_data['send_email'],
|
||||
force=self.mark_paid_form.cleaned_data.get('force', False),
|
||||
payment_refund_sum=self.order.payment_refund_sum,
|
||||
)
|
||||
messages.success(self.request, _('The order has been marked as paid.'))
|
||||
return redirect(self.get_order_url())
|
||||
|
||||
try:
|
||||
p = self.order.payments.get(
|
||||
state__in=(OrderPayment.PAYMENT_STATE_PENDING, OrderPayment.PAYMENT_STATE_CREATED),
|
||||
|
||||
@@ -226,7 +226,6 @@ class UserSettings(UpdateView):
|
||||
msgs = []
|
||||
|
||||
if 'new_pw' in form.changed_data:
|
||||
self.request.user.needs_password_change = False
|
||||
msgs.append(_('Your password has been changed.'))
|
||||
|
||||
if 'email' in form.changed_data:
|
||||
@@ -244,8 +243,6 @@ class UserSettings(UpdateView):
|
||||
return sup
|
||||
|
||||
def get_success_url(self):
|
||||
if "next" in self.request.GET and url_has_allowed_host_and_scheme(self.request.GET.get("next"), allowed_hosts=None):
|
||||
return self.request.GET.get("next")
|
||||
return reverse('control:user.settings')
|
||||
|
||||
|
||||
|
||||
@@ -1,113 +0,0 @@
|
||||
import re
|
||||
from inspect import isgenerator
|
||||
|
||||
from openpyxl import Workbook
|
||||
from openpyxl.cell.cell import (
|
||||
ILLEGAL_CHARACTERS_RE, KNOWN_TYPES, TIME_TYPES, TYPE_FORMULA, TYPE_STRING,
|
||||
Cell,
|
||||
)
|
||||
from openpyxl.compat import NUMERIC_TYPES
|
||||
from openpyxl.utils import column_index_from_string
|
||||
from openpyxl.utils.exceptions import ReadOnlyWorkbookException
|
||||
from openpyxl.worksheet._write_only import WriteOnlyWorksheet
|
||||
from openpyxl.worksheet.worksheet import Worksheet
|
||||
|
||||
SAFE_TYPES = NUMERIC_TYPES + TIME_TYPES + (bool, type(None))
|
||||
|
||||
|
||||
"""
|
||||
This module provides a safer version of openpyxl's `Workbook` class to generate XLSX files from
|
||||
user-generated data using `WriteOnlyWorksheet` and `ws.append()`. We commonly use these methods
|
||||
to output e.g. order data, which contains data from untrusted sources such as attendee names.
|
||||
|
||||
There are mainly two problems this solves:
|
||||
|
||||
- It makes sure strings starting with = are treated as text, not as a formula, as openpyxl will
|
||||
otherwise assume, which can be used for remote code execution.
|
||||
|
||||
- It removes characters considered invalid by Excel to avoid exporter crashes.
|
||||
"""
|
||||
|
||||
|
||||
def remove_invalid_excel_chars(val):
|
||||
if isinstance(val, Cell):
|
||||
return val
|
||||
|
||||
if not isinstance(val, KNOWN_TYPES):
|
||||
val = str(val)
|
||||
|
||||
if isinstance(val, bytes):
|
||||
val = val.decode("utf-8", errors="ignore")
|
||||
|
||||
if isinstance(val, str):
|
||||
val = re.sub(ILLEGAL_CHARACTERS_RE, '', val)
|
||||
|
||||
return val
|
||||
|
||||
|
||||
def SafeCell(*args, value=None, **kwargs):
|
||||
value = remove_invalid_excel_chars(value)
|
||||
c = Cell(*args, value=value, **kwargs)
|
||||
if c.data_type == TYPE_FORMULA:
|
||||
c.data_type = TYPE_STRING
|
||||
return c
|
||||
|
||||
|
||||
class SafeAppendMixin:
|
||||
def append(self, iterable):
|
||||
row_idx = self._current_row + 1
|
||||
|
||||
if isinstance(iterable, (list, tuple, range)) or isgenerator(iterable):
|
||||
for col_idx, content in enumerate(iterable, 1):
|
||||
if isinstance(content, Cell):
|
||||
# compatible with write-only mode
|
||||
cell = content
|
||||
if cell.parent and cell.parent != self:
|
||||
raise ValueError("Cells cannot be copied from other worksheets")
|
||||
cell.parent = self
|
||||
cell.column = col_idx
|
||||
cell.row = row_idx
|
||||
else:
|
||||
cell = SafeCell(self, row=row_idx, column=col_idx, value=remove_invalid_excel_chars(content))
|
||||
self._cells[(row_idx, col_idx)] = cell
|
||||
|
||||
elif isinstance(iterable, dict):
|
||||
for col_idx, content in iterable.items():
|
||||
if isinstance(col_idx, str):
|
||||
col_idx = column_index_from_string(col_idx)
|
||||
cell = SafeCell(self, row=row_idx, column=col_idx, value=content)
|
||||
self._cells[(row_idx, col_idx)] = cell
|
||||
|
||||
else:
|
||||
self._invalid_row(iterable)
|
||||
|
||||
self._current_row = row_idx
|
||||
|
||||
|
||||
class SafeWriteOnlyWorksheet(SafeAppendMixin, WriteOnlyWorksheet):
|
||||
pass
|
||||
|
||||
|
||||
class SafeWorksheet(SafeAppendMixin, Worksheet):
|
||||
pass
|
||||
|
||||
|
||||
class SafeWorkbook(Workbook):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
if self._sheets:
|
||||
# monkeypatch existing sheets
|
||||
for s in self._sheets:
|
||||
s.append = SafeAppendMixin.append
|
||||
|
||||
def create_sheet(self, title=None, index=None):
|
||||
if self.read_only:
|
||||
raise ReadOnlyWorkbookException('Cannot create new sheet in a read-only workbook')
|
||||
|
||||
if self.write_only:
|
||||
new_ws = SafeWriteOnlyWorksheet(parent=self, title=title)
|
||||
else:
|
||||
new_ws = SafeWorksheet(parent=self, title=title)
|
||||
|
||||
self._add_sheet(sheet=new_ws, index=index)
|
||||
return new_ws
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-11-29 08:28+0000\n"
|
||||
"POT-Creation-Date: 2021-10-17 14:57+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,7 +199,8 @@ msgstr "تذاكر سارية المفعول"
|
||||
msgid "Currently inside"
|
||||
msgstr "حاليا بالداخل"
|
||||
|
||||
#: pretix/static/lightbox/js/lightbox.js:96
|
||||
#: pretix/static/lightbox/js/lightbox.js:90
|
||||
#: pretix/static/lightbox/js/lightbox.min.js:13
|
||||
#, fuzzy
|
||||
#| msgctxt "widget"
|
||||
#| msgid "Close"
|
||||
@@ -535,20 +536,20 @@ msgstr "ستسترد %(currency)%(amount)"
|
||||
msgid "Please enter the amount the organizer can keep."
|
||||
msgstr "الرجاء إدخال المبلغ الذي يمكن للمنظم الاحتفاظ به."
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:339
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:335
|
||||
msgid "Please enter a quantity for one of the ticket types."
|
||||
msgstr "الرجاء إدخال عدد لأحد أنواع التذاكر."
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:375
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:371
|
||||
msgid "required"
|
||||
msgstr "مطلوب"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:478
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:497
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:473
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:491
|
||||
msgid "Time zone:"
|
||||
msgstr "المنطقة الزمنية:"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:488
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:482
|
||||
msgid "Your local time:"
|
||||
msgstr "التوقيت المحلي:"
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-11-29 08:28+0000\n"
|
||||
"POT-Creation-Date: 2021-10-17 14:57+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,7 +194,8 @@ msgstr ""
|
||||
msgid "Currently inside"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/lightbox/js/lightbox.js:96
|
||||
#: pretix/static/lightbox/js/lightbox.js:90
|
||||
#: pretix/static/lightbox/js/lightbox.min.js:13
|
||||
msgid "close"
|
||||
msgstr ""
|
||||
|
||||
@@ -510,22 +511,22 @@ msgstr ""
|
||||
msgid "Please enter the amount the organizer can keep."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:339
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:335
|
||||
msgid "Please enter a quantity for one of the ticket types."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:375
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:371
|
||||
#, fuzzy
|
||||
#| msgid "Cart expired"
|
||||
msgid "required"
|
||||
msgstr "Cistella expirada"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:478
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:497
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:473
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:491
|
||||
msgid "Time zone:"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:488
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:482
|
||||
msgid "Your local time:"
|
||||
msgstr ""
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-11-29 08:28+0000\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"
|
||||
"Language-Team: Czech <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
||||
@@ -194,7 +194,8 @@ msgstr "Platné vstupenky"
|
||||
msgid "Currently inside"
|
||||
msgstr "Právě uvnitř"
|
||||
|
||||
#: pretix/static/lightbox/js/lightbox.js:96
|
||||
#: pretix/static/lightbox/js/lightbox.js:90
|
||||
#: pretix/static/lightbox/js/lightbox.min.js:13
|
||||
msgid "close"
|
||||
msgstr "zavřít"
|
||||
|
||||
@@ -509,10 +510,8 @@ 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."
|
||||
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."
|
||||
msgstr[2] ""
|
||||
"Produkty v nákupním košíku jsou pro vás rezervovány na dalších {num} minut."
|
||||
|
||||
@@ -528,20 +527,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:339
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:335
|
||||
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:375
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:371
|
||||
msgid "required"
|
||||
msgstr "povinný"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:478
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:497
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:473
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:491
|
||||
msgid "Time zone:"
|
||||
msgstr "Časové pásmo:"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:488
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:482
|
||||
msgid "Your local time:"
|
||||
msgstr "Místní čas:"
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-11-29 08:28+0000\n"
|
||||
"POT-Creation-Date: 2021-10-17 14:57+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,7 +206,8 @@ msgstr ""
|
||||
msgid "Currently inside"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/lightbox/js/lightbox.js:96
|
||||
#: pretix/static/lightbox/js/lightbox.js:90
|
||||
#: pretix/static/lightbox/js/lightbox.min.js:13
|
||||
#, fuzzy
|
||||
#| msgctxt "widget"
|
||||
#| msgid "Close"
|
||||
@@ -563,22 +564,22 @@ msgstr "fra %(currency)s %(price)s"
|
||||
msgid "Please enter the amount the organizer can keep."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:339
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:335
|
||||
msgid "Please enter a quantity for one of the ticket types."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:375
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:371
|
||||
#, fuzzy
|
||||
#| msgid "Cart expired"
|
||||
msgid "required"
|
||||
msgstr "Kurv udløbet"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:478
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:497
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:473
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:491
|
||||
msgid "Time zone:"
|
||||
msgstr "Tidszone:"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:488
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:482
|
||||
msgid "Your local time:"
|
||||
msgstr "Din lokaltid:"
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-11-29 08:28+0000\n"
|
||||
"POT-Creation-Date: 2021-10-17 14:57+0000\n"
|
||||
"PO-Revision-Date: 2021-10-25 15:40+0000\n"
|
||||
"Last-Translator: Raphael Michel <michel@rami.io>\n"
|
||||
"Language-Team: German <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
||||
@@ -194,7 +194,8 @@ msgstr "Gültige Tickets"
|
||||
msgid "Currently inside"
|
||||
msgstr "Derzeit anwesend"
|
||||
|
||||
#: pretix/static/lightbox/js/lightbox.js:96
|
||||
#: pretix/static/lightbox/js/lightbox.js:90
|
||||
#: pretix/static/lightbox/js/lightbox.min.js:13
|
||||
msgid "close"
|
||||
msgstr "schließen"
|
||||
|
||||
@@ -532,20 +533,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:339
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:335
|
||||
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:375
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:371
|
||||
msgid "required"
|
||||
msgstr "verpflichtend"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:478
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:497
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:473
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:491
|
||||
msgid "Time zone:"
|
||||
msgstr "Zeitzone:"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:488
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:482
|
||||
msgid "Your local time:"
|
||||
msgstr "Deine lokale Zeit:"
|
||||
|
||||
|
||||
@@ -97,7 +97,6 @@ Explorer
|
||||
FA
|
||||
Favicon
|
||||
Footer
|
||||
Galicisch
|
||||
gehostete
|
||||
geht's
|
||||
GENEXAMPLE
|
||||
@@ -262,7 +261,6 @@ txt
|
||||
ÜBERZAHLT
|
||||
überzahlte
|
||||
uhrzeit
|
||||
UID
|
||||
ungespeicherte
|
||||
unkategorisiert
|
||||
Unkategorisierte
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-11-29 08:28+0000\n"
|
||||
"POT-Creation-Date: 2021-10-17 14:57+0000\n"
|
||||
"PO-Revision-Date: 2021-10-25 15:40+0000\n"
|
||||
"Last-Translator: Raphael Michel <michel@rami.io>\n"
|
||||
"Language-Team: German (informal) <https://translate.pretix.eu/projects/"
|
||||
@@ -194,7 +194,8 @@ msgstr "Gültige Tickets"
|
||||
msgid "Currently inside"
|
||||
msgstr "Derzeit anwesend"
|
||||
|
||||
#: pretix/static/lightbox/js/lightbox.js:96
|
||||
#: pretix/static/lightbox/js/lightbox.js:90
|
||||
#: pretix/static/lightbox/js/lightbox.min.js:13
|
||||
msgid "close"
|
||||
msgstr "schließen"
|
||||
|
||||
@@ -531,20 +532,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:339
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:335
|
||||
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:375
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:371
|
||||
msgid "required"
|
||||
msgstr "verpflichtend"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:478
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:497
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:473
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:491
|
||||
msgid "Time zone:"
|
||||
msgstr "Zeitzone:"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:488
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:482
|
||||
msgid "Your local time:"
|
||||
msgstr "Deine lokale Zeit:"
|
||||
|
||||
|
||||
@@ -97,7 +97,6 @@ Explorer
|
||||
FA
|
||||
Favicon
|
||||
Footer
|
||||
Galicisch
|
||||
gehostete
|
||||
geht's
|
||||
GENEXAMPLE
|
||||
@@ -262,7 +261,6 @@ txt
|
||||
ÜBERZAHLT
|
||||
überzahlte
|
||||
uhrzeit
|
||||
UID
|
||||
ungespeicherte
|
||||
unkategorisiert
|
||||
Unkategorisierte
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-11-29 08:28+0000\n"
|
||||
"POT-Creation-Date: 2021-10-17 14:57+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,7 +193,8 @@ msgstr ""
|
||||
msgid "Currently inside"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/lightbox/js/lightbox.js:96
|
||||
#: pretix/static/lightbox/js/lightbox.js:90
|
||||
#: pretix/static/lightbox/js/lightbox.min.js:13
|
||||
msgid "close"
|
||||
msgstr ""
|
||||
|
||||
@@ -505,20 +506,20 @@ msgstr ""
|
||||
msgid "Please enter the amount the organizer can keep."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:339
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:335
|
||||
msgid "Please enter a quantity for one of the ticket types."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:375
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:371
|
||||
msgid "required"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:478
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:497
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:473
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:491
|
||||
msgid "Time zone:"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:488
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:482
|
||||
msgid "Your local time:"
|
||||
msgstr ""
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-11-29 08:28+0000\n"
|
||||
"POT-Creation-Date: 2021-10-17 14:57+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,7 +209,8 @@ msgstr ""
|
||||
msgid "Currently inside"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/lightbox/js/lightbox.js:96
|
||||
#: pretix/static/lightbox/js/lightbox.js:90
|
||||
#: pretix/static/lightbox/js/lightbox.min.js:13
|
||||
#, fuzzy
|
||||
#| msgctxt "widget"
|
||||
#| msgid "Close"
|
||||
@@ -575,22 +576,22 @@ msgstr "απο %(currency)s %(price)s"
|
||||
msgid "Please enter the amount the organizer can keep."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:339
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:335
|
||||
msgid "Please enter a quantity for one of the ticket types."
|
||||
msgstr "Εισαγάγετε μια ποσότητα για έναν από τους τύπους εισιτηρίων."
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:375
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:371
|
||||
#, fuzzy
|
||||
#| msgid "Cart expired"
|
||||
msgid "required"
|
||||
msgstr "Το καλάθι έληξε"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:478
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:497
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:473
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:491
|
||||
msgid "Time zone:"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:488
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:482
|
||||
msgid "Your local time:"
|
||||
msgstr ""
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,9 +7,9 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-11-29 08:28+0000\n"
|
||||
"PO-Revision-Date: 2021-11-25 21:00+0000\n"
|
||||
"Last-Translator: Ismael Menéndez Fernández <ismael.menendez@balidea.com>\n"
|
||||
"POT-Creation-Date: 2021-10-17 14:57+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-"
|
||||
"js/es/>\n"
|
||||
"Language: es\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,7 +194,11 @@ msgstr "Tickets válidos"
|
||||
msgid "Currently inside"
|
||||
msgstr "Actualmente adentro"
|
||||
|
||||
#: pretix/static/lightbox/js/lightbox.js:96
|
||||
#: pretix/static/lightbox/js/lightbox.js:90
|
||||
#: pretix/static/lightbox/js/lightbox.min.js:13
|
||||
#, fuzzy
|
||||
#| msgctxt "widget"
|
||||
#| msgid "Close"
|
||||
msgid "close"
|
||||
msgstr "Cerrar"
|
||||
|
||||
@@ -498,22 +502,26 @@ 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 they’re available."
|
||||
msgstr ""
|
||||
"Los elementos en su carrito de compras ya no se encuentran reservados. "
|
||||
"Puedes seguir añadiendo más productos mientras estén disponibles."
|
||||
msgstr "Los elementos en su carrito de compras ya no se encuentran reservados."
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/cart.js:45
|
||||
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."
|
||||
msgstr[0] ""
|
||||
"Los elementos en su carrito de compras se han reservado por {num} minutos."
|
||||
"Los elementos en su carrito de compras se han reservado por un minuto."
|
||||
msgstr[1] ""
|
||||
"Los elementos en su carrito de compras se han reservado por {num} minutos."
|
||||
|
||||
@@ -529,20 +537,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:339
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:335
|
||||
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:375
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:371
|
||||
msgid "required"
|
||||
msgstr "campo requerido"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:478
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:497
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:473
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:491
|
||||
msgid "Time zone:"
|
||||
msgstr "Zona horaria:"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:488
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:482
|
||||
msgid "Your local time:"
|
||||
msgstr "Su hora local:"
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,8 +7,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-11-29 08:28+0000\n"
|
||||
"PO-Revision-Date: 2021-11-10 05:00+0000\n"
|
||||
"POT-Creation-Date: 2021-10-17 14:57+0000\n"
|
||||
"PO-Revision-Date: 2021-10-04 21: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"
|
||||
@@ -203,7 +203,8 @@ msgstr ""
|
||||
msgid "Currently inside"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/lightbox/js/lightbox.js:96
|
||||
#: pretix/static/lightbox/js/lightbox.js:90
|
||||
#: pretix/static/lightbox/js/lightbox.min.js:13
|
||||
#, fuzzy
|
||||
#| msgctxt "widget"
|
||||
#| msgid "Close"
|
||||
@@ -497,12 +498,12 @@ 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 they’re available."
|
||||
msgstr ""
|
||||
"Ostoskorissasi olevat tuotteet eivät ole enää varattu sinulle. Voit silti "
|
||||
"suorittaa tilauksen loppuun niin kauan kuin tuotteita on saatavilla."
|
||||
msgstr "Ostoskorissasi olevat tuotteet eivät ole enää varattu sinulle."
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/cart.js:45
|
||||
msgid "Cart expired"
|
||||
@@ -528,22 +529,22 @@ msgstr ""
|
||||
msgid "Please enter the amount the organizer can keep."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:339
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:335
|
||||
msgid "Please enter a quantity for one of the ticket types."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:375
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:371
|
||||
#, fuzzy
|
||||
#| msgid "Cart expired"
|
||||
msgid "required"
|
||||
msgstr "Ostoskori on vanhentunut"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:478
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:497
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:473
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:491
|
||||
msgid "Time zone:"
|
||||
msgstr "Aikavyöhyke:"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:488
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:482
|
||||
msgid "Your local time:"
|
||||
msgstr ""
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: French\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-11-29 08:28+0000\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"
|
||||
"Language-Team: French <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
||||
@@ -206,7 +206,8 @@ msgstr ""
|
||||
msgid "Currently inside"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/lightbox/js/lightbox.js:96
|
||||
#: pretix/static/lightbox/js/lightbox.js:90
|
||||
#: pretix/static/lightbox/js/lightbox.min.js:13
|
||||
#, fuzzy
|
||||
#| msgctxt "widget"
|
||||
#| msgid "Close"
|
||||
@@ -567,22 +568,22 @@ msgstr "de %(currency)s %(price)s"
|
||||
msgid "Please enter the amount the organizer can keep."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:339
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:335
|
||||
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:375
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:371
|
||||
#, fuzzy
|
||||
#| msgid "Cart expired"
|
||||
msgid "required"
|
||||
msgstr "Panier expiré"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:478
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:497
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:473
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:491
|
||||
msgid "Time zone:"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:488
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:482
|
||||
msgid "Your local time:"
|
||||
msgstr ""
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,820 +0,0 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-11-29 08:28+0000\n"
|
||||
"PO-Revision-Date: 2021-11-25 21:00+0000\n"
|
||||
"Last-Translator: Ismael Menéndez Fernández <ismael.menendez@balidea.com>\n"
|
||||
"Language-Team: Galician <https://translate.pretix.eu/projects/pretix/pretix-"
|
||||
"js/gl/>\n"
|
||||
"Language: gl\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||
"X-Generator: Weblate 4.8\n"
|
||||
|
||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
|
||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
|
||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:68
|
||||
msgid "Marked as paid"
|
||||
msgstr "Marcado como pagado"
|
||||
|
||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:76
|
||||
msgid "Comment:"
|
||||
msgstr "Comentario:"
|
||||
|
||||
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:15
|
||||
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:39
|
||||
msgid "Placed orders"
|
||||
msgstr "Pedidos enviados"
|
||||
|
||||
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:15
|
||||
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:39
|
||||
msgid "Paid orders"
|
||||
msgstr "Ordes pagadas"
|
||||
|
||||
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:27
|
||||
msgid "Total revenue"
|
||||
msgstr "Ingresos totais"
|
||||
|
||||
#: pretix/plugins/stripe/static/pretixplugins/stripe/pretix-stripe.js:12
|
||||
msgid "Contacting Stripe …"
|
||||
msgstr "Contactando con Stripe…"
|
||||
|
||||
#: pretix/plugins/stripe/static/pretixplugins/stripe/pretix-stripe.js:60
|
||||
msgid "Total"
|
||||
msgstr "Total"
|
||||
|
||||
#: pretix/plugins/stripe/static/pretixplugins/stripe/pretix-stripe.js:152
|
||||
#: pretix/plugins/stripe/static/pretixplugins/stripe/pretix-stripe.js:183
|
||||
msgid "Confirming your payment …"
|
||||
msgstr "Confirmando o pagamento…"
|
||||
|
||||
#: pretix/plugins/stripe/static/pretixplugins/stripe/pretix-stripe.js:159
|
||||
msgid "Contacting your bank …"
|
||||
msgstr "Contactando co banco…"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:30
|
||||
msgid "Select a check-in list"
|
||||
msgstr "Seleccione unha lista de rexistro"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:31
|
||||
msgid "No active check-in lists found."
|
||||
msgstr "Non se atoparon listas de rexistro activas."
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:32
|
||||
msgid "Switch check-in list"
|
||||
msgstr "Cambiar a lista de rexistro"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:33
|
||||
msgid "Search results"
|
||||
msgstr "Resultados da procura"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:34
|
||||
msgid "No tickets found"
|
||||
msgstr "Non se atoparon tíckets"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:35
|
||||
msgid "Result"
|
||||
msgstr "Resultado"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:36
|
||||
msgid "This ticket requires special attention"
|
||||
msgstr "Este tícket require atención especial"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:37
|
||||
msgid "Switch direction"
|
||||
msgstr "Cambiar enderezo"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:38
|
||||
msgid "Entry"
|
||||
msgstr "Acceso"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:39
|
||||
msgid "Exit"
|
||||
msgstr "Saída"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:40
|
||||
msgid "Scan a ticket or search and press return…"
|
||||
msgstr "Escanee o tícket ou busque e presione volver…"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:41
|
||||
msgid "Load more"
|
||||
msgstr "Cargar máis"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:42
|
||||
msgid "Valid"
|
||||
msgstr "Válido"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:43
|
||||
msgid "Unpaid"
|
||||
msgstr "Sen pagar"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:44
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:45
|
||||
msgid "Canceled"
|
||||
msgstr "Cancelado"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:46
|
||||
msgid "Redeemed"
|
||||
msgstr "Trocado"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:47
|
||||
msgid "Cancel"
|
||||
msgstr "Cancelar"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:48
|
||||
msgid "Continue"
|
||||
msgstr "Continuar"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:49
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:58
|
||||
msgid "Ticket not paid"
|
||||
msgstr "Tícket pendente de pago"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:50
|
||||
msgid "This ticket is not yet paid. Do you want to continue anyways?"
|
||||
msgstr "Este tícket aínda non se pagou. Desexa continuar de todos os xeitos?"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:51
|
||||
msgid "Additional information required"
|
||||
msgstr "Requírese información adicional"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:52
|
||||
msgid "Valid ticket"
|
||||
msgstr "Entrada válida"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:53
|
||||
msgid "Exit recorded"
|
||||
msgstr "Saída rexistrada"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:54
|
||||
msgid "Ticket already used"
|
||||
msgstr "Este tícket xa foi utilizado"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:55
|
||||
msgid "Information required"
|
||||
msgstr "Información requirida"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:56
|
||||
msgid "Unknown ticket"
|
||||
msgstr "Tícket descoñecido"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:57
|
||||
msgid "Ticket type not allowed here"
|
||||
msgstr "Tipo de tícket non permitido"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:59
|
||||
msgid "Entry not allowed"
|
||||
msgstr "Entrada non permitida"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:60
|
||||
msgid "Ticket code revoked/changed"
|
||||
msgstr "Código de tícket revogado/cambiado"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:61
|
||||
msgid "Order canceled"
|
||||
msgstr "Pedido cancelado"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:62
|
||||
msgid "Checked-in Tickets"
|
||||
msgstr "Rexistro de código QR"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:63
|
||||
msgid "Valid Tickets"
|
||||
msgstr "Tíckets válidos"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:64
|
||||
msgid "Currently inside"
|
||||
msgstr "Actualmente dentro"
|
||||
|
||||
#: pretix/static/lightbox/js/lightbox.js:96
|
||||
msgid "close"
|
||||
msgstr "cerrar"
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:43
|
||||
#: pretix/static/pretixbase/js/asynctask.js:119
|
||||
msgid ""
|
||||
"Your request is currently being processed. Depending on the size of your "
|
||||
"event, this might take up to a few minutes."
|
||||
msgstr ""
|
||||
"A súa solicitude estase procesando. Isto pode tardar varios minutos, "
|
||||
"dependendo do tamaño do seu evento."
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:48
|
||||
#: pretix/static/pretixbase/js/asynctask.js:124
|
||||
msgid "Your request has been queued on the server and will soon be processed."
|
||||
msgstr "A súa solicitude foi enviada ao servidor e será procesada en breve."
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:54
|
||||
#: pretix/static/pretixbase/js/asynctask.js:130
|
||||
msgid ""
|
||||
"Your request arrived on the server but we still wait for it to be processed. "
|
||||
"If this takes longer than two minutes, please contact us or go back in your "
|
||||
"browser and try again."
|
||||
msgstr ""
|
||||
"A súa solicitude chegou ao servidor pero seguimos esperando a que sexa "
|
||||
"procesada. Se tarda máis de dous minutos, por favor, contacte con nós ou "
|
||||
"volva á páxina anterior no seu navegador e inténteo de novo."
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:89
|
||||
#: pretix/static/pretixbase/js/asynctask.js:175
|
||||
#: pretix/static/pretixbase/js/asynctask.js:180
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:24
|
||||
msgid "An error of type {code} occurred."
|
||||
msgstr "Ocurreu un error de tipo {code}."
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:92
|
||||
msgid ""
|
||||
"We currently cannot reach the server, but we keep trying. Last error code: "
|
||||
"{code}"
|
||||
msgstr ""
|
||||
"Agora mesmo non podemos contactar co servidor, pero seguímolo intentando. O "
|
||||
"último código de erro foi: {code}"
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:144
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:21
|
||||
msgid "The request took too long. Please try again."
|
||||
msgstr "A petición levou demasiado tempo. Inténteo de novo."
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:183
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:26
|
||||
msgid ""
|
||||
"We currently cannot reach the server. Please try again. Error code: {code}"
|
||||
msgstr ""
|
||||
"Agora mesmo non podemos contactar co servidor. Por favor, inténteo de novo. "
|
||||
"Código de erro: {code}"
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:205
|
||||
msgid "We are processing your request …"
|
||||
msgstr "Estamos procesando a súa solicitude…"
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:213
|
||||
msgid ""
|
||||
"We are currently sending your request to the server. If this takes longer "
|
||||
"than one minute, please check your internet connection and then reload this "
|
||||
"page and try again."
|
||||
msgstr ""
|
||||
"Estamos enviando a súa solicitude ao servidor. Se este proceso tarda máis "
|
||||
"dun minuto, por favor, revise a súa conexión a Internet, recargue a páxina e "
|
||||
"inténteo de novo."
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:264
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:34
|
||||
msgid "Close message"
|
||||
msgstr "Cerrar mensaxe"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/clipboard.js:23
|
||||
msgid "Copied!"
|
||||
msgstr "Copiado!"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/clipboard.js:29
|
||||
msgid "Press Ctrl-C to copy!"
|
||||
msgstr "Presione Control+C para copiar!"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:10
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:16
|
||||
msgid "is one of"
|
||||
msgstr "é un de"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:22
|
||||
msgid "is before"
|
||||
msgstr "está antes"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:26
|
||||
msgid "is after"
|
||||
msgstr "está despois"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:59
|
||||
msgid "Product"
|
||||
msgstr "Produto"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:63
|
||||
msgid "Product variation"
|
||||
msgstr "Ver variacións do produto"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:67
|
||||
msgid "Current date and time"
|
||||
msgstr "Data e hora actual"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:71
|
||||
msgid "Number of previous entries"
|
||||
msgstr "Número de entradas previas"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:75
|
||||
msgid "Number of previous entries since midnight"
|
||||
msgstr "Número de entradas previas desde a medianoite"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:79
|
||||
msgid "Number of days with a previous entry"
|
||||
msgstr "Número de días cunha entrada previa"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:97
|
||||
msgid "All of the conditions below (AND)"
|
||||
msgstr "Todas as condicións seguintes (E)"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:98
|
||||
msgid "At least one of the conditions below (OR)"
|
||||
msgstr "Polo menos unha das seguintes condicións (OU)"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:99
|
||||
msgid "Event start"
|
||||
msgstr "Comezo do evento"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:100
|
||||
msgid "Event end"
|
||||
msgstr "Fin do evento"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:101
|
||||
msgid "Event admission"
|
||||
msgstr "Admisión ao evento"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:102
|
||||
msgid "custom date and time"
|
||||
msgstr "Seleccionar data e hora"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:103
|
||||
msgid "custom time"
|
||||
msgstr "Seleccionar hora"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:104
|
||||
msgid "Tolerance (minutes)"
|
||||
msgstr "Tolerancia (en minutos)"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:105
|
||||
msgid "Add condition"
|
||||
msgstr "Engadir condición"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:106
|
||||
msgid "minutes"
|
||||
msgstr "minutos"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:69
|
||||
msgid "Lead Scan QR"
|
||||
msgstr "Escanear QR de clientela potencial"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:71
|
||||
msgid "Check-in QR"
|
||||
msgstr "QR de validación"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:313
|
||||
msgid "The PDF background file could not be loaded for the following reason:"
|
||||
msgstr "O arquivo PDF de fondo non se puido cargar polo motivo seguinte:"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:521
|
||||
msgid "Group of objects"
|
||||
msgstr "Grupo de obxectos"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:527
|
||||
msgid "Text object"
|
||||
msgstr "Obxecto de texto"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:529
|
||||
msgid "Barcode area"
|
||||
msgstr "Área para código de barras"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:531
|
||||
msgid "Image area"
|
||||
msgstr "Área de imaxe"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:533
|
||||
msgid "Powered by pretix"
|
||||
msgstr "Desenvolto por Pretix"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:535
|
||||
msgid "Object"
|
||||
msgstr "Obxecto"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:539
|
||||
msgid "Ticket design"
|
||||
msgstr "Deseño do tícket"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:813
|
||||
msgid "Saving failed."
|
||||
msgstr "O gardado fallou."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:862
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:901
|
||||
msgid "Error while uploading your PDF file, please try again."
|
||||
msgstr ""
|
||||
"Houbo un erro mentres se cargaba o arquivo PDF. Por favor, inténteo de novo."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:886
|
||||
msgid "Do you really want to leave the editor without saving your changes?"
|
||||
msgstr "Realmente desexa saír do editor sen gardar os cambios?"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:19
|
||||
msgid "An error has occurred."
|
||||
msgstr "Houbo un erro."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:54
|
||||
msgid "Generating messages …"
|
||||
msgstr "Xerando mensaxes…"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:70
|
||||
msgid "Unknown error."
|
||||
msgstr "Erro descoñecido."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:271
|
||||
msgid "Your color has great contrast and is very easy to read!"
|
||||
msgstr "A túa cor ten moito contraste e é moi doada de ler!"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:275
|
||||
msgid "Your color has decent contrast and is probably good-enough to read!"
|
||||
msgstr ""
|
||||
"A túa cor ten un contraste axeitado e probablemente sexa suficientemente "
|
||||
"lexible!"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:279
|
||||
msgid ""
|
||||
"Your color has bad contrast for text on white background, please choose a "
|
||||
"darker shade."
|
||||
msgstr ""
|
||||
"A túa cor ten mal contraste para un texto con fondo branco. Por favor, "
|
||||
"escolle un ton máis escuro."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:417
|
||||
msgid "All"
|
||||
msgstr "Todos"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:418
|
||||
msgid "None"
|
||||
msgstr "Ningún"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:419
|
||||
msgid "Search query"
|
||||
msgstr "Consultar unha procura"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:422
|
||||
msgid "Selected only"
|
||||
msgstr "Soamente seleccionados"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:834
|
||||
msgid "Use a different name internally"
|
||||
msgstr "Usar un nome diferente internamente"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:870
|
||||
msgid "Click to close"
|
||||
msgstr "Click para cerrar"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:911
|
||||
msgid "You have unsaved changes!"
|
||||
msgstr "Tes cambios sen gardar!"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/orderchange.js:25
|
||||
msgid "Calculating default price…"
|
||||
msgstr "Calculando o prezo por defecto…"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/question.js:42
|
||||
msgid "Others"
|
||||
msgstr "Outros"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/question.js:82
|
||||
msgid "Count"
|
||||
msgstr "Cantidade"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/question.js:136
|
||||
#: pretix/static/pretixpresale/js/ui/questions.js:269
|
||||
msgid "Yes"
|
||||
msgstr "Si"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/question.js:137
|
||||
#: pretix/static/pretixpresale/js/ui/questions.js:269
|
||||
msgid "No"
|
||||
msgstr "Non"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/subevent.js:111
|
||||
msgid "(one more date)"
|
||||
msgid_plural "({num} more dates)"
|
||||
msgstr[0] "(unha data máis)"
|
||||
msgstr[1] "({num} máis datas)"
|
||||
|
||||
#: 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 they’re available."
|
||||
msgstr ""
|
||||
"Os artigos da túa cesta xa non están reservados para ti. Aínda podes "
|
||||
"completar o teu pedido mentres estean dispoñibles."
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/cart.js:45
|
||||
msgid "Cart expired"
|
||||
msgstr "O carro da compra caducou"
|
||||
|
||||
#: 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] "Os artigos da túa cesta están reservados para ti durante un minuto."
|
||||
msgstr[1] ""
|
||||
"Os artigos da túa cesta están reservados para ti durante {num} minutos."
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:144
|
||||
msgid "The organizer keeps %(currency)s %(amount)s"
|
||||
msgstr "O organizador queda %(currency)s %(price)s"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:152
|
||||
msgid "You get %(currency)s %(amount)s back"
|
||||
msgstr "Obtés %(currency)s %(price)s de volta"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:168
|
||||
msgid "Please enter the amount the organizer can keep."
|
||||
msgstr "Por favor, ingrese a cantidade que pode conservar o organizador."
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:339
|
||||
msgid "Please enter a quantity for one of the ticket types."
|
||||
msgstr "Por favor, introduza un valor para cada tipo de entrada."
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:375
|
||||
msgid "required"
|
||||
msgstr "campo requirido"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:478
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:497
|
||||
msgid "Time zone:"
|
||||
msgstr "Zona horaria:"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:488
|
||||
msgid "Your local time:"
|
||||
msgstr "A súa hora local:"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:17
|
||||
msgctxt "widget"
|
||||
msgid "Sold out"
|
||||
msgstr "Esgotado"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:18
|
||||
msgctxt "widget"
|
||||
msgid "Buy"
|
||||
msgstr "Mercar"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:19
|
||||
msgctxt "widget"
|
||||
msgid "Register"
|
||||
msgstr "Rexistrarse"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:20
|
||||
msgctxt "widget"
|
||||
msgid "Reserved"
|
||||
msgstr "Reservado"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:21
|
||||
msgctxt "widget"
|
||||
msgid "FREE"
|
||||
msgstr "De balde"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:22
|
||||
msgctxt "widget"
|
||||
msgid "from %(currency)s %(price)s"
|
||||
msgstr "dende %(currency)s %(price)s"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:23
|
||||
msgctxt "widget"
|
||||
msgid "incl. %(rate)s% %(taxname)s"
|
||||
msgstr "inclúe %(rate)s% %(taxname)s"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:24
|
||||
msgctxt "widget"
|
||||
msgid "plus %(rate)s% %(taxname)s"
|
||||
msgstr "máis %(rate)s% %(taxname)s"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:25
|
||||
msgctxt "widget"
|
||||
msgid "incl. taxes"
|
||||
msgstr "impostos incluídos"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:26
|
||||
msgctxt "widget"
|
||||
msgid "plus taxes"
|
||||
msgstr "máis impostos"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:27
|
||||
#, javascript-format
|
||||
msgctxt "widget"
|
||||
msgid "currently available: %s"
|
||||
msgstr "dispoñible actualmente: %s"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:28
|
||||
msgctxt "widget"
|
||||
msgid "Only available with a voucher"
|
||||
msgstr "Só dispoñible mediante vale"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:29
|
||||
#, javascript-format
|
||||
msgctxt "widget"
|
||||
msgid "minimum amount to order: %s"
|
||||
msgstr "cantidade mínima de pedido: %s"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:30
|
||||
msgctxt "widget"
|
||||
msgid "Close ticket shop"
|
||||
msgstr "Cerrar a tenda de tíckets"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:31
|
||||
msgctxt "widget"
|
||||
msgid "The ticket shop could not be loaded."
|
||||
msgstr "Non se puido cargar a tenda de tíckets."
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:32
|
||||
msgctxt "widget"
|
||||
msgid ""
|
||||
"There are currently a lot of users in this ticket shop. Please open the shop "
|
||||
"in a new tab to continue."
|
||||
msgstr ""
|
||||
"Actualmente hai moitas persoas usuarias na tenda de tíckets. Por favor, abra "
|
||||
"a tenda nunha nova pestana para continuar."
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:34
|
||||
msgctxt "widget"
|
||||
msgid "Open ticket shop"
|
||||
msgstr "Abrir a tenda de tíckets"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:35
|
||||
msgctxt "widget"
|
||||
msgid "The cart could not be created. Please try again later"
|
||||
msgstr ""
|
||||
"O carro de compras non se puido crear. Por favor, inténteo de novo máis tarde"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:36
|
||||
msgctxt "widget"
|
||||
msgid ""
|
||||
"We could not create your cart, since there are currently too many users in "
|
||||
"this ticket shop. Please click \"Continue\" to retry in a new tab."
|
||||
msgstr ""
|
||||
"Non puidemos crear o seu carro debido a que hai moitas persoas usuarias na "
|
||||
"tenda. Por favor, presione \"Continuar\" para intentalo nunha nova pestana."
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:38
|
||||
msgctxt "widget"
|
||||
msgid "Waiting list"
|
||||
msgstr "Lista de agarda"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:39
|
||||
msgctxt "widget"
|
||||
msgid ""
|
||||
"You currently have an active cart for this event. If you select more "
|
||||
"products, they will be added to your existing cart."
|
||||
msgstr ""
|
||||
"Xa ten un carro de compras activo para este evento. Se selecciona máis "
|
||||
"produtos, estes engadiranse ao carro actual."
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:41
|
||||
msgctxt "widget"
|
||||
msgid "Resume checkout"
|
||||
msgstr "Continuar co pagamento"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:42
|
||||
msgctxt "widget"
|
||||
msgid "Redeem a voucher"
|
||||
msgstr "Trocar un vale"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:43
|
||||
msgctxt "widget"
|
||||
msgid "Redeem"
|
||||
msgstr "Trocar"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:44
|
||||
msgctxt "widget"
|
||||
msgid "Voucher code"
|
||||
msgstr "Código do cupón"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:45
|
||||
msgctxt "widget"
|
||||
msgid "Close"
|
||||
msgstr "Cerrar"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:46
|
||||
msgctxt "widget"
|
||||
msgid "Continue"
|
||||
msgstr "Continuar"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:47
|
||||
msgctxt "widget"
|
||||
msgid "See variations"
|
||||
msgstr "Ver variacións"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:48
|
||||
msgctxt "widget"
|
||||
msgid "Choose a different event"
|
||||
msgstr "Elixir un evento distinto"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:49
|
||||
msgctxt "widget"
|
||||
msgid "Choose a different date"
|
||||
msgstr "Elixir unha data diferente"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:50
|
||||
msgctxt "widget"
|
||||
msgid "Back"
|
||||
msgstr "Atrás"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:51
|
||||
msgctxt "widget"
|
||||
msgid "Next month"
|
||||
msgstr "Mes seguinte"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:52
|
||||
msgctxt "widget"
|
||||
msgid "Previous month"
|
||||
msgstr "Mes anterior"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:53
|
||||
msgctxt "widget"
|
||||
msgid "Next week"
|
||||
msgstr "Semana seguinte"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:54
|
||||
msgctxt "widget"
|
||||
msgid "Previous week"
|
||||
msgstr "Semana anterior"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:55
|
||||
msgctxt "widget"
|
||||
msgid "Open seat selection"
|
||||
msgstr "Abrir selección de asentos"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:56
|
||||
msgctxt "widget"
|
||||
msgid "Load more"
|
||||
msgstr "Cargar máis"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:58
|
||||
msgid "Mo"
|
||||
msgstr "Lu"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:59
|
||||
msgid "Tu"
|
||||
msgstr "Mar"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:60
|
||||
msgid "We"
|
||||
msgstr "Mér"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:61
|
||||
msgid "Th"
|
||||
msgstr "Xo"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:62
|
||||
msgid "Fr"
|
||||
msgstr "Ve"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:63
|
||||
msgid "Sa"
|
||||
msgstr "Sáb"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:64
|
||||
msgid "Su"
|
||||
msgstr "Dom"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:67
|
||||
msgid "January"
|
||||
msgstr "Xaneiro"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:68
|
||||
msgid "February"
|
||||
msgstr "Febreiro"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:69
|
||||
msgid "March"
|
||||
msgstr "Marzo"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:70
|
||||
msgid "April"
|
||||
msgstr "Abril"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:71
|
||||
msgid "May"
|
||||
msgstr "Maio"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:72
|
||||
msgid "June"
|
||||
msgstr "Xuño"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:73
|
||||
msgid "July"
|
||||
msgstr "Xullo"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:74
|
||||
msgid "August"
|
||||
msgstr "Agosto"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:75
|
||||
msgid "September"
|
||||
msgstr "Setembro"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:76
|
||||
msgid "October"
|
||||
msgstr "Outubro"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:77
|
||||
msgid "November"
|
||||
msgstr "Novembro"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:78
|
||||
msgid "December"
|
||||
msgstr "Decembro"
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-11-29 08:28+0000\n"
|
||||
"POT-Creation-Date: 2021-10-17 14:57+0000\n"
|
||||
"PO-Revision-Date: 2021-09-24 13:54+0000\n"
|
||||
"Last-Translator: ofirtro <ofir.tro@gmail.com>\n"
|
||||
"Language-Team: Hebrew <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
||||
@@ -195,7 +195,8 @@ msgstr "כרטיסים תקפים"
|
||||
msgid "Currently inside"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/lightbox/js/lightbox.js:96
|
||||
#: pretix/static/lightbox/js/lightbox.js:90
|
||||
#: pretix/static/lightbox/js/lightbox.min.js:13
|
||||
msgid "close"
|
||||
msgstr ""
|
||||
|
||||
@@ -513,20 +514,20 @@ msgstr ""
|
||||
msgid "Please enter the amount the organizer can keep."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:339
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:335
|
||||
msgid "Please enter a quantity for one of the ticket types."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:375
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:371
|
||||
msgid "required"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:478
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:497
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:473
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:491
|
||||
msgid "Time zone:"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:488
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:482
|
||||
msgid "Your local time:"
|
||||
msgstr ""
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-11-29 08:28+0000\n"
|
||||
"POT-Creation-Date: 2021-10-17 14:57+0000\n"
|
||||
"PO-Revision-Date: 2020-01-24 08:00+0000\n"
|
||||
"Last-Translator: Prokaj Miklós <mixolid0@gmail.com>\n"
|
||||
"Language-Team: Hungarian <https://translate.pretix.eu/projects/pretix/pretix-"
|
||||
@@ -207,7 +207,8 @@ msgstr ""
|
||||
msgid "Currently inside"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/lightbox/js/lightbox.js:96
|
||||
#: pretix/static/lightbox/js/lightbox.js:90
|
||||
#: pretix/static/lightbox/js/lightbox.min.js:13
|
||||
#, fuzzy
|
||||
#| msgctxt "widget"
|
||||
#| msgid "Close"
|
||||
@@ -563,22 +564,22 @@ msgstr "%(currency) %(price)-tól"
|
||||
msgid "Please enter the amount the organizer can keep."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:339
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:335
|
||||
msgid "Please enter a quantity for one of the ticket types."
|
||||
msgstr "Adjon meg egy mennyiséget az egyik jegytípusból."
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:375
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:371
|
||||
#, fuzzy
|
||||
#| msgid "Cart expired"
|
||||
msgid "required"
|
||||
msgstr "A kosár lejárt"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:478
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:497
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:473
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:491
|
||||
msgid "Time zone:"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:488
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:482
|
||||
msgid "Your local time:"
|
||||
msgstr ""
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,9 +7,9 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-11-29 08:28+0000\n"
|
||||
"PO-Revision-Date: 2021-11-20 03:00+0000\n"
|
||||
"Last-Translator: Marco Giacopuzzi <marco.giaco2000@gmail.com>\n"
|
||||
"POT-Creation-Date: 2021-10-17 14:57+0000\n"
|
||||
"PO-Revision-Date: 2021-07-19 11:57+0000\n"
|
||||
"Last-Translator: dedecosta <dedecosta2@live.it>\n"
|
||||
"Language-Team: Italian <https://translate.pretix.eu/projects/pretix/pretix-"
|
||||
"js/it/>\n"
|
||||
"Language: it\n"
|
||||
@@ -17,7 +17,7 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||
"X-Generator: Weblate 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,7 +194,11 @@ msgstr "Biglietti validi"
|
||||
msgid "Currently inside"
|
||||
msgstr "Attualmente all'interno"
|
||||
|
||||
#: pretix/static/lightbox/js/lightbox.js:96
|
||||
#: pretix/static/lightbox/js/lightbox.js:90
|
||||
#: pretix/static/lightbox/js/lightbox.min.js:13
|
||||
#, fuzzy
|
||||
#| msgctxt "widget"
|
||||
#| msgid "Close"
|
||||
msgid "close"
|
||||
msgstr "Chiudi"
|
||||
|
||||
@@ -493,18 +497,22 @@ msgstr[0] "(un'altra data)"
|
||||
msgstr[1] "({num} altre date)"
|
||||
|
||||
#: 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 they’re available."
|
||||
msgstr ""
|
||||
"Gli articoli nel tuo carrello non sono più riservati per te. Puoi ancora "
|
||||
"completare il tuo ordine finché sono disponibili."
|
||||
msgstr "I prodotti nel tuo carrello non sono più disponibili."
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/cart.js:45
|
||||
msgid "Cart expired"
|
||||
msgstr "Carrello scaduto"
|
||||
|
||||
#: 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."
|
||||
msgstr[0] "Gli elementi nel tuo carrello sono riservati per 1 minuto."
|
||||
@@ -522,20 +530,20 @@ msgstr "Ricevi indietro %(currency)s %(amount)s"
|
||||
msgid "Please enter the amount the organizer can keep."
|
||||
msgstr "Inserisci l'importo che l'organizzatore può trattenere."
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:339
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:335
|
||||
msgid "Please enter a quantity for one of the ticket types."
|
||||
msgstr "Inserisci la quantità per una tipologia di biglietto."
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:375
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:371
|
||||
msgid "required"
|
||||
msgstr "richiesto"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:478
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:497
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:473
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:491
|
||||
msgid "Time zone:"
|
||||
msgstr "Fuso orario:"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:488
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:482
|
||||
msgid "Your local time:"
|
||||
msgstr "Ora locale:"
|
||||
|
||||
@@ -735,9 +743,11 @@ msgid "Open seat selection"
|
||||
msgstr "Apri la selezione dei posti"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:56
|
||||
#, fuzzy
|
||||
#| msgid "Load more"
|
||||
msgctxt "widget"
|
||||
msgid "Load more"
|
||||
msgstr "Mostra di più"
|
||||
msgstr "Carica di più"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:58
|
||||
msgid "Mo"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2021-11-29 08:28+0000\n"
|
||||
"POT-Creation-Date: 2021-10-17 14:57+0000\n"
|
||||
"PO-Revision-Date: 2021-10-11 15:56+0000\n"
|
||||
"Last-Translator: DJG Bayern <pretix2021weblate@aruki.de>\n"
|
||||
"Language-Team: Japanese <https://translate.pretix.eu/projects/pretix/pretix-"
|
||||
@@ -194,7 +194,8 @@ msgstr ""
|
||||
msgid "Currently inside"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/lightbox/js/lightbox.js:96
|
||||
#: pretix/static/lightbox/js/lightbox.js:90
|
||||
#: pretix/static/lightbox/js/lightbox.min.js:13
|
||||
msgid "close"
|
||||
msgstr ""
|
||||
|
||||
@@ -504,20 +505,20 @@ msgstr ""
|
||||
msgid "Please enter the amount the organizer can keep."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:339
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:335
|
||||
msgid "Please enter a quantity for one of the ticket types."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:375
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:371
|
||||
msgid "required"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:478
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:497
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:473
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:491
|
||||
msgid "Time zone:"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:488
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:482
|
||||
msgid "Your local time:"
|
||||
msgstr ""
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user