mirror of
https://github.com/pretix/pretix.git
synced 2026-07-26 08:25:08 +00:00
Compare commits
83
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
48c110b32b | ||
|
|
5587aebcd8 | ||
|
|
0b708067de | ||
|
|
e75dc74661 | ||
|
|
f0c5e54e34 | ||
|
|
eeb6e11934 | ||
|
|
1238165e7a | ||
|
|
bcf65603e4 | ||
|
|
c4aed04a18 | ||
|
|
8be09ee937 | ||
|
|
a1ec45daf6 | ||
|
|
9b0b8e2061 | ||
|
|
b6e65e7356 | ||
|
|
5d82305e18 | ||
|
|
c8983ca863 | ||
|
|
52f6b7c971 | ||
|
|
809177397a | ||
|
|
b83cb7d8c4 | ||
|
|
bfd980fc30 | ||
|
|
5bc3503d04 | ||
|
|
a582db3280 | ||
|
|
bd4ea5d8f8 | ||
|
|
5dec94606b | ||
|
|
ab97082c85 | ||
|
|
0723ff92ee | ||
|
|
15272cc3e6 | ||
|
|
60554dad9a | ||
|
|
b288ea1e96 | ||
|
|
6a4b792501 | ||
|
|
8dd83e5a35 | ||
|
|
bd5c9a4cb5 | ||
|
|
0cd8bbf9a9 | ||
|
|
d46989473b | ||
|
|
b31b2d34c0 | ||
|
|
5e963d87d9 | ||
|
|
a8e0eea69a | ||
|
|
efa9f6dfe5 | ||
|
|
857377d16c | ||
|
|
229b6fed4a | ||
|
|
b2e4fb6db3 | ||
|
|
16b15057fd | ||
|
|
e4168ff06a | ||
|
|
b208db32c7 | ||
|
|
ce177227c7 | ||
|
|
2cd70ef434 | ||
|
|
633755ab13 | ||
|
|
6ade32d7cb | ||
|
|
cea6c340be | ||
|
|
ad1dab3b7f | ||
|
|
930abe0cc5 | ||
|
|
ba2cc56c82 | ||
|
|
cb1f63bf80 | ||
|
|
aab7042cda | ||
|
|
495a21c683 | ||
|
|
86b5ba6937 | ||
|
|
3d9679a144 | ||
|
|
5f899ed5c5 | ||
|
|
47dabc1fe7 | ||
|
|
2d7c4a3d42 | ||
|
|
51ef98f736 | ||
|
|
2d7d2b1a90 | ||
|
|
cede7ba3aa | ||
|
|
4fd8726b05 | ||
|
|
b344ce90ba | ||
|
|
69dc7f56e5 | ||
|
|
247a61489f | ||
|
|
979d23e997 | ||
|
|
28e529995d | ||
|
|
a982cbf6b6 | ||
|
|
f1c2ae5b6b | ||
|
|
5b27ac66f9 | ||
|
|
c71ac2141f | ||
|
|
e59498d65d | ||
|
|
dfe3454915 | ||
|
|
b64c5735a8 | ||
|
|
11eecd739d | ||
|
|
07a6d4898a | ||
|
|
a759e23504 | ||
|
|
3eaf05502a | ||
|
|
04df1c2032 | ||
|
|
6a8df75a9f | ||
|
|
547cfdffd6 | ||
|
|
f72a0b4c09 |
@@ -9,6 +9,7 @@ updates:
|
||||
directory: "/src"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
versioning-strategy: increase
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/src/pretix/static/npm_dir"
|
||||
schedule:
|
||||
|
||||
@@ -17,16 +17,19 @@ on:
|
||||
permissions:
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
|
||||
env:
|
||||
FORCE_COLOR: 1
|
||||
|
||||
jobs:
|
||||
spelling:
|
||||
name: Spellcheck
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python 3.8
|
||||
- name: Set up Python 3.9
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: 3.8
|
||||
python-version: 3.9
|
||||
- uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
|
||||
@@ -15,16 +15,19 @@ on:
|
||||
permissions:
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
|
||||
env:
|
||||
FORCE_COLOR: 1
|
||||
|
||||
jobs:
|
||||
compile:
|
||||
runs-on: ubuntu-22.04
|
||||
name: Check gettext syntax
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python 3.8
|
||||
- name: Set up Python 3.9
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: 3.8
|
||||
python-version: 3.9
|
||||
- uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
@@ -47,10 +50,10 @@ jobs:
|
||||
name: Spellcheck
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python 3.8
|
||||
- name: Set up Python 3.9
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: 3.8
|
||||
python-version: 3.9
|
||||
- uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
|
||||
@@ -15,16 +15,19 @@ on:
|
||||
permissions:
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
|
||||
env:
|
||||
FORCE_COLOR: 1
|
||||
|
||||
jobs:
|
||||
isort:
|
||||
name: isort
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python 3.8
|
||||
- name: Set up Python 3.9
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: 3.8
|
||||
python-version: 3.9
|
||||
- uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
@@ -42,10 +45,10 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python 3.8
|
||||
- name: Set up Python 3.9
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: 3.8
|
||||
python-version: 3.9
|
||||
- uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
@@ -63,10 +66,10 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python 3.8
|
||||
- name: Set up Python 3.9
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: 3.8
|
||||
python-version: 3.9
|
||||
- name: Install Dependencies
|
||||
run: pip3 install licenseheaders
|
||||
- name: Run licenseheaders
|
||||
|
||||
@@ -15,6 +15,9 @@ on:
|
||||
permissions:
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
|
||||
env:
|
||||
FORCE_COLOR: 1
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-22.04
|
||||
@@ -60,7 +63,7 @@ jobs:
|
||||
- name: Install system dependencies
|
||||
run: sudo apt update && sudo apt install gettext mariadb-client
|
||||
- name: Install Python dependencies
|
||||
run: pip3 install -e ".[dev]" mysqlclient psycopg2-binary
|
||||
run: pip3 install --ignore-requires-python -e ".[dev]" mysqlclient psycopg2-binary # We ignore that flake8 needs newer python as we don't run flake8 during tests
|
||||
working-directory: ./src
|
||||
- name: Run checks
|
||||
run: python manage.py check
|
||||
|
||||
@@ -318,6 +318,27 @@ example::
|
||||
(venv)$ python -m pretix rebuild
|
||||
# systemctl restart pretix-web pretix-worker
|
||||
|
||||
System updates
|
||||
--------------
|
||||
|
||||
After system updates, such as updates to a new Ubuntu or Debian release, you might be using a new Python version.
|
||||
That's great, but requires some adjustments. First, adjust any old version paths in your nginx configuration file.
|
||||
Then, re-create your Python environment::
|
||||
|
||||
$ source /var/pretix/venv/bin/activate
|
||||
(venv)$ pip3 freeze > /tmp/pip-backup.txt
|
||||
$ rm -rf /var/pretix/venv
|
||||
$ python3 -m venv /var/pretix/venv
|
||||
$ source /var/pretix/venv/bin/activate
|
||||
(venv)$ pip3 install -U pip wheel setuptools
|
||||
(venv)$ pip3 install -r /tmp/pip-backup.txt
|
||||
|
||||
Then, proceed like after any plugin installation::
|
||||
|
||||
(venv)$ python -m pretix migrate
|
||||
(venv)$ python -m pretix rebuild
|
||||
(venv)$ python -m pretix updatestyles
|
||||
# systemctl restart pretix-web pretix-worker
|
||||
|
||||
.. _Postfix: https://www.digitalocean.com/community/tutorials/how-to-install-and-configure-postfix-as-a-send-only-smtp-server-on-ubuntu-16-04
|
||||
.. _nginx: https://botleg.com/stories/https-with-lets-encrypt-and-nginx/
|
||||
|
||||
@@ -48,10 +48,11 @@ Possible permissions are:
|
||||
Compatibility
|
||||
-------------
|
||||
|
||||
We currently see pretix' API as a beta-stage feature. We therefore do not give any guarantees
|
||||
for compatibility between feature releases of pretix (such as 1.5 and 1.6). However, as always,
|
||||
we try not to break things when we don't need to. Any backwards-incompatible changes will be
|
||||
prominently noted in the release notes.
|
||||
We try to avoid any breaking changes to our API to avoid hassle on your end. If possible, we'll
|
||||
build new features in a way that keeps all pre-existing API usage unchanged. In some cases,
|
||||
this might not be possible or only possible with restrictions. In these case, any
|
||||
backwards-incompatible changes will be prominently noted in the "Changes to the REST API"
|
||||
section of our release notes. If possible, we will announce them multiple releases in advance.
|
||||
|
||||
We treat the following types of changes as *backwards-compatible* so we ask you to make sure
|
||||
that your clients can deal with them properly:
|
||||
@@ -60,6 +61,7 @@ that your clients can deal with them properly:
|
||||
* Support of new HTTP methods for a given API endpoint
|
||||
* Support of new query parameters for a given API endpoint
|
||||
* New fields contained in API responses
|
||||
* New possible values of enumeration-like fields
|
||||
* Response body structure or message texts on failed requests (``4xx``, ``5xx`` response codes)
|
||||
|
||||
We treat the following types of changes as *backwards-incompatible*:
|
||||
|
||||
@@ -43,8 +43,13 @@ available_until datetime The last date t
|
||||
hide_without_voucher boolean If ``true``, this variation is only shown during the voucher
|
||||
redemption process, but not in the normal shop
|
||||
frontend.
|
||||
meta_data object Values set for event-specific meta data parameters.
|
||||
===================================== ========================== =======================================================
|
||||
|
||||
.. versionchanged:: 4.16
|
||||
|
||||
The ``meta_data`` attribute has been added.
|
||||
|
||||
Endpoints
|
||||
---------
|
||||
|
||||
@@ -94,6 +99,7 @@ Endpoints
|
||||
"default_price": "223.00",
|
||||
"price": 223.0,
|
||||
"original_price": null,
|
||||
"meta_data": {}
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
@@ -108,7 +114,8 @@ Endpoints
|
||||
"description": {},
|
||||
"position": 1,
|
||||
"default_price": null,
|
||||
"price": 15.0
|
||||
"price": 15.0,
|
||||
"meta_data": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -161,7 +168,8 @@ Endpoints
|
||||
"available_until": null,
|
||||
"hide_without_voucher": false,
|
||||
"description": null,
|
||||
"position": 0
|
||||
"position": 0,
|
||||
"meta_data": {}
|
||||
}
|
||||
|
||||
:param organizer: The ``slug`` field of the organizer to fetch
|
||||
@@ -198,7 +206,8 @@ Endpoints
|
||||
"available_until": null,
|
||||
"hide_without_voucher": false,
|
||||
"description": null,
|
||||
"position": 0
|
||||
"position": 0,
|
||||
"meta_data": {}
|
||||
}
|
||||
|
||||
**Example response**:
|
||||
@@ -225,7 +234,8 @@ Endpoints
|
||||
"available_until": null,
|
||||
"hide_without_voucher": false,
|
||||
"description": null,
|
||||
"position": 0
|
||||
"position": 0,
|
||||
"meta_data": {}
|
||||
}
|
||||
|
||||
:param organizer: The ``slug`` field of the organizer of the event/item to create a variation for
|
||||
@@ -283,7 +293,8 @@ Endpoints
|
||||
"available_until": null,
|
||||
"hide_without_voucher": false,
|
||||
"description": null,
|
||||
"position": 1
|
||||
"position": 1,
|
||||
"meta_data": {}
|
||||
}
|
||||
|
||||
:param organizer: The ``slug`` field of the organizer to modify
|
||||
|
||||
@@ -123,6 +123,7 @@ variations list of objects A list with one
|
||||
├ hide_without_voucher boolean If ``true``, this variation is only shown during the voucher
|
||||
redemption process, but not in the normal shop
|
||||
frontend.
|
||||
├ meta_data object Values set for event-specific meta data parameters.
|
||||
└ position integer An integer, used for sorting
|
||||
addons list of objects Definition of add-ons that can be chosen for this item.
|
||||
Only writable during creation,
|
||||
@@ -155,6 +156,10 @@ meta_data object Values set for
|
||||
|
||||
The attributes ``require_membership_hidden`` attribute has been added.
|
||||
|
||||
.. versionchanged:: 4.16
|
||||
|
||||
The ``variations[x].meta_data`` attribute has been added.
|
||||
|
||||
Notes
|
||||
-----
|
||||
|
||||
@@ -247,6 +252,7 @@ Endpoints
|
||||
"available_until": null,
|
||||
"hide_without_voucher": false,
|
||||
"description": null,
|
||||
"meta_data": {},
|
||||
"position": 0
|
||||
},
|
||||
{
|
||||
@@ -262,6 +268,7 @@ Endpoints
|
||||
"available_until": null,
|
||||
"hide_without_voucher": false,
|
||||
"description": null,
|
||||
"meta_data": {},
|
||||
"position": 1
|
||||
}
|
||||
],
|
||||
@@ -361,6 +368,7 @@ Endpoints
|
||||
"available_from": null,
|
||||
"available_until": null,
|
||||
"hide_without_voucher": false,
|
||||
"meta_data": {},
|
||||
"position": 0
|
||||
},
|
||||
{
|
||||
@@ -376,6 +384,7 @@ Endpoints
|
||||
"available_until": null,
|
||||
"hide_without_voucher": false,
|
||||
"description": null,
|
||||
"meta_data": {},
|
||||
"position": 1
|
||||
}
|
||||
],
|
||||
@@ -455,6 +464,7 @@ Endpoints
|
||||
"available_until": null,
|
||||
"hide_without_voucher": false,
|
||||
"description": null,
|
||||
"meta_data": {},
|
||||
"position": 0
|
||||
},
|
||||
{
|
||||
@@ -470,6 +480,7 @@ Endpoints
|
||||
"available_until": null,
|
||||
"hide_without_voucher": false,
|
||||
"description": null,
|
||||
"meta_data": {},
|
||||
"position": 1
|
||||
}
|
||||
],
|
||||
@@ -538,6 +549,7 @@ Endpoints
|
||||
"available_until": null,
|
||||
"hide_without_voucher": false,
|
||||
"description": null,
|
||||
"meta_data": {},
|
||||
"position": 0
|
||||
},
|
||||
{
|
||||
@@ -553,6 +565,7 @@ Endpoints
|
||||
"available_until": null,
|
||||
"hide_without_voucher": false,
|
||||
"description": null,
|
||||
"meta_data": {},
|
||||
"position": 1
|
||||
}
|
||||
],
|
||||
@@ -652,6 +665,7 @@ Endpoints
|
||||
"available_until": null,
|
||||
"hide_without_voucher": false,
|
||||
"description": null,
|
||||
"meta_data": {},
|
||||
"position": 0
|
||||
},
|
||||
{
|
||||
@@ -667,6 +681,7 @@ Endpoints
|
||||
"available_until": null,
|
||||
"hide_without_voucher": false,
|
||||
"description": null,
|
||||
"meta_data": {},
|
||||
"position": 1
|
||||
}
|
||||
],
|
||||
|
||||
@@ -91,6 +91,7 @@ Field Type Description
|
||||
===================================== ========================== =======================================================
|
||||
id integer Internal content ID
|
||||
title multi-lingual string The content title (required)
|
||||
internal_name string An optional name that is only used in the backend
|
||||
content_type string The type of content, valid values are ``webinar``, ``video``, ``livestream``, ``link``, ``file``
|
||||
url string The location of the digital content
|
||||
file file A downloadable file. Either ``url`` or ``file`` must be ``null``.
|
||||
|
||||
@@ -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.15.0"
|
||||
__version__ = "4.16.0.dev0"
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
# Generated by Django 3.2.16 on 2022-12-17 18:47
|
||||
|
||||
import uuid
|
||||
|
||||
import django.db.models.deletion
|
||||
import oauth2_provider.generators
|
||||
import oauth2_provider.models
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('pretixbase', '0226_itemvariationmetavalue'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('pretixapi', '0008_webhookcallretry'),
|
||||
]
|
||||
run_before = [
|
||||
('oauth2_provider', '0002_auto_20190406_1805'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='oauthapplication',
|
||||
name='algorithm',
|
||||
field=models.CharField(default='', max_length=5),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='oauthgrant',
|
||||
name='claims',
|
||||
field=models.TextField(default=''),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='oauthgrant',
|
||||
name='code_challenge',
|
||||
field=models.CharField(default='', max_length=128),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='oauthgrant',
|
||||
name='code_challenge_method',
|
||||
field=models.CharField(default='', max_length=10),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='oauthgrant',
|
||||
name='nonce',
|
||||
field=models.CharField(default='', max_length=255),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='oauthapplication',
|
||||
name='client_secret',
|
||||
field=oauth2_provider.models.ClientSecretField(db_index=True, default=oauth2_provider.generators.generate_client_secret, max_length=255),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='OAuthIDToken',
|
||||
fields=[
|
||||
('id', models.BigAutoField(primary_key=True, serialize=False)),
|
||||
('jti', models.UUIDField(default=uuid.uuid4, unique=True)),
|
||||
('expires', models.DateTimeField()),
|
||||
('scope', models.TextField()),
|
||||
('created', models.DateTimeField(auto_now_add=True)),
|
||||
('updated', models.DateTimeField(auto_now=True)),
|
||||
('application', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.OAUTH2_PROVIDER_APPLICATION_MODEL)),
|
||||
('organizers', models.ManyToManyField(to='pretixbase.Organizer')),
|
||||
('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='pretixapi_oauthidtoken', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='oauthaccesstoken',
|
||||
name='id_token',
|
||||
field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='access_token', to='pretixapi.oauthidtoken'),
|
||||
),
|
||||
]
|
||||
@@ -29,8 +29,8 @@ from oauth2_provider.generators import (
|
||||
generate_client_id, generate_client_secret,
|
||||
)
|
||||
from oauth2_provider.models import (
|
||||
AbstractAccessToken, AbstractApplication, AbstractGrant,
|
||||
AbstractRefreshToken,
|
||||
AbstractAccessToken, AbstractApplication, AbstractGrant, AbstractIDToken,
|
||||
AbstractRefreshToken, ClientSecretField,
|
||||
)
|
||||
from oauth2_provider.validators import URIValidator
|
||||
|
||||
@@ -46,7 +46,7 @@ class OAuthApplication(AbstractApplication):
|
||||
verbose_name=_("Client ID"),
|
||||
max_length=100, unique=True, default=generate_client_id, db_index=True
|
||||
)
|
||||
client_secret = models.CharField(
|
||||
client_secret = ClientSecretField(
|
||||
verbose_name=_("Client secret"),
|
||||
max_length=255, blank=False, default=generate_client_secret, db_index=True
|
||||
)
|
||||
@@ -67,12 +67,26 @@ class OAuthGrant(AbstractGrant):
|
||||
redirect_uri = models.CharField(max_length=2500) # Only 255 in AbstractGrant, which caused problems
|
||||
|
||||
|
||||
class OAuthIDToken(AbstractIDToken):
|
||||
application = models.ForeignKey(
|
||||
OAuthApplication, on_delete=models.CASCADE,
|
||||
)
|
||||
organizers = models.ManyToManyField('pretixbase.Organizer')
|
||||
|
||||
|
||||
class OAuthAccessToken(AbstractAccessToken):
|
||||
source_refresh_token = models.OneToOneField(
|
||||
# unique=True implied by the OneToOneField
|
||||
'OAuthRefreshToken', on_delete=models.SET_NULL, blank=True, null=True,
|
||||
related_name="refreshed_access_token"
|
||||
)
|
||||
id_token = models.OneToOneField(
|
||||
OAuthIDToken,
|
||||
on_delete=models.CASCADE,
|
||||
blank=True,
|
||||
null=True,
|
||||
related_name="access_token",
|
||||
)
|
||||
application = models.ForeignKey(
|
||||
OAuthApplication, on_delete=models.CASCADE, blank=True, null=True,
|
||||
)
|
||||
|
||||
@@ -47,13 +47,14 @@ from pretix.api.serializers.fields import UploadedFileField
|
||||
from pretix.api.serializers.i18n import I18nAwareModelSerializer
|
||||
from pretix.base.models import (
|
||||
Item, ItemAddOn, ItemBundle, ItemCategory, ItemMetaValue, ItemVariation,
|
||||
Question, QuestionOption, Quota,
|
||||
ItemVariationMetaValue, Question, QuestionOption, Quota,
|
||||
)
|
||||
|
||||
|
||||
class InlineItemVariationSerializer(I18nAwareModelSerializer):
|
||||
price = serializers.DecimalField(read_only=True, decimal_places=2, max_digits=10,
|
||||
coerce_to_string=True)
|
||||
meta_data = MetaDataField(required=False, source='*')
|
||||
|
||||
class Meta:
|
||||
model = ItemVariation
|
||||
@@ -61,16 +62,23 @@ class InlineItemVariationSerializer(I18nAwareModelSerializer):
|
||||
'position', 'default_price', 'price', 'original_price', 'require_approval',
|
||||
'require_membership', 'require_membership_types',
|
||||
'require_membership_hidden', 'available_from', 'available_until',
|
||||
'sales_channels', 'hide_without_voucher',)
|
||||
'sales_channels', 'hide_without_voucher', 'meta_data')
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['require_membership_types'].queryset = lazy(lambda: self.context['event'].organizer.membership_types.all(), QuerySet)
|
||||
|
||||
def validate_meta_data(self, value):
|
||||
for key in value['meta_data'].keys():
|
||||
if key not in self.parent.parent.item_meta_properties:
|
||||
raise ValidationError(_('Item meta data property \'{name}\' does not exist.').format(name=key))
|
||||
return value
|
||||
|
||||
|
||||
class ItemVariationSerializer(I18nAwareModelSerializer):
|
||||
price = serializers.DecimalField(read_only=True, decimal_places=2, max_digits=10,
|
||||
coerce_to_string=True)
|
||||
meta_data = MetaDataField(required=False, source='*')
|
||||
|
||||
class Meta:
|
||||
model = ItemVariation
|
||||
@@ -78,12 +86,63 @@ class ItemVariationSerializer(I18nAwareModelSerializer):
|
||||
'position', 'default_price', 'price', 'original_price', 'require_approval',
|
||||
'require_membership', 'require_membership_types',
|
||||
'require_membership_hidden', 'available_from', 'available_until',
|
||||
'sales_channels', 'hide_without_voucher',)
|
||||
'sales_channels', 'hide_without_voucher', 'meta_data')
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['require_membership_types'].queryset = self.context['event'].organizer.membership_types.all()
|
||||
|
||||
@transaction.atomic
|
||||
def create(self, validated_data):
|
||||
meta_data = validated_data.pop('meta_data', None)
|
||||
variation = ItemVariation.objects.create(**validated_data)
|
||||
|
||||
# Meta data
|
||||
if meta_data is not None:
|
||||
for key, value in meta_data.items():
|
||||
ItemVariationMetaValue.objects.create(
|
||||
property=self.item_meta_properties.get(key),
|
||||
value=value,
|
||||
variation=variation
|
||||
)
|
||||
return variation
|
||||
|
||||
@cached_property
|
||||
def item_meta_properties(self):
|
||||
return {
|
||||
p.name: p for p in self.context['request'].event.item_meta_properties.all()
|
||||
}
|
||||
|
||||
def validate_meta_data(self, value):
|
||||
for key in value['meta_data'].keys():
|
||||
if key not in self.item_meta_properties:
|
||||
raise ValidationError(_('Item meta data property \'{name}\' does not exist.').format(name=key))
|
||||
return value
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
meta_data = validated_data.pop('meta_data', None)
|
||||
variation = super().update(instance, validated_data)
|
||||
|
||||
# Meta data
|
||||
if meta_data is not None:
|
||||
current = {mv.property: mv for mv in variation.meta_values.select_related('property')}
|
||||
for key, value in meta_data.items():
|
||||
prop = self.item_meta_properties.get(key)
|
||||
if prop in current:
|
||||
current[prop].value = value
|
||||
current[prop].save()
|
||||
else:
|
||||
variation.meta_values.create(
|
||||
property=self.item_meta_properties.get(key),
|
||||
value=value
|
||||
)
|
||||
|
||||
for prop, current_object in current.items():
|
||||
if prop.name not in meta_data:
|
||||
current_object.delete()
|
||||
|
||||
return variation
|
||||
|
||||
|
||||
class InlineItemBundleSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
@@ -263,9 +322,19 @@ class ItemSerializer(I18nAwareModelSerializer):
|
||||
|
||||
for variation_data in variations_data:
|
||||
require_membership_types = variation_data.pop('require_membership_types', [])
|
||||
var_meta_data = variation_data.pop('meta_data', {})
|
||||
v = ItemVariation.objects.create(item=item, **variation_data)
|
||||
if require_membership_types:
|
||||
v.require_membership_types.add(*require_membership_types)
|
||||
|
||||
if var_meta_data is not None:
|
||||
for key, value in var_meta_data.items():
|
||||
ItemVariationMetaValue.objects.create(
|
||||
property=self.item_meta_properties.get(key),
|
||||
value=value,
|
||||
variation=v
|
||||
)
|
||||
|
||||
for addon_data in addons_data:
|
||||
ItemAddOn.objects.create(base_item=item, **addon_data)
|
||||
for bundle_data in bundles_data:
|
||||
|
||||
@@ -371,10 +371,19 @@ class PdfDataSerializer(serializers.Field):
|
||||
for k, v in ev._cached_meta_data.items():
|
||||
res['meta:' + k] = v
|
||||
|
||||
if not hasattr(instance.item, '_cached_meta_data'):
|
||||
instance.item._cached_meta_data = instance.item.meta_data
|
||||
for k, v in instance.item._cached_meta_data.items():
|
||||
res['itemmeta:' + k] = v
|
||||
if instance.variation_id:
|
||||
print(instance, instance.variation, instance.variation_id, instance.item)
|
||||
if not hasattr(instance.variation, '_cached_meta_data'):
|
||||
instance.variation.item = instance.item # saves some database lookups
|
||||
instance.variation._cached_meta_data = instance.variation.meta_data
|
||||
print(instance.variation._cached_meta_data.items())
|
||||
for k, v in instance.variation._cached_meta_data.items():
|
||||
res['itemmeta:' + k] = v
|
||||
else:
|
||||
if not hasattr(instance.item, '_cached_meta_data'):
|
||||
instance.item._cached_meta_data = instance.item.meta_data
|
||||
for k, v in instance.item._cached_meta_data.items():
|
||||
res['itemmeta:' + k] = v
|
||||
|
||||
res['images'] = {}
|
||||
|
||||
@@ -627,7 +636,7 @@ class OrderSerializer(I18nAwareModelSerializer):
|
||||
for fname, field in list(self.fields.items()):
|
||||
if fname in includes:
|
||||
continue
|
||||
elif hasattr(field, 'child'):
|
||||
elif hasattr(field, 'child'): # Nested list serializers
|
||||
found_any = False
|
||||
for childfname, childfield in list(field.child.fields.items()):
|
||||
if f'{fname}.{childfname}' not in includes:
|
||||
@@ -636,6 +645,15 @@ class OrderSerializer(I18nAwareModelSerializer):
|
||||
found_any = True
|
||||
if not found_any:
|
||||
self.fields.pop(fname)
|
||||
elif isinstance(field, serializers.Serializer): # Nested serializers
|
||||
found_any = False
|
||||
for childfname, childfield in list(field.fields.items()):
|
||||
if f'{fname}.{childfname}' not in includes:
|
||||
field.fields.pop(childfname)
|
||||
else:
|
||||
found_any = True
|
||||
if not found_any:
|
||||
self.fields.pop(fname)
|
||||
else:
|
||||
self.fields.pop(fname)
|
||||
|
||||
|
||||
@@ -35,7 +35,8 @@
|
||||
import importlib
|
||||
|
||||
from django.apps import apps
|
||||
from django.conf.urls import include, re_path
|
||||
from django.conf.urls import re_path
|
||||
from django.urls import include
|
||||
from rest_framework import routers
|
||||
|
||||
from pretix.api.views import cart
|
||||
|
||||
@@ -84,7 +84,9 @@ class ItemViewSet(ConditionalListView, viewsets.ModelViewSet):
|
||||
|
||||
def get_queryset(self):
|
||||
return self.request.event.items.select_related('tax_rule').prefetch_related(
|
||||
'variations', 'addons', 'bundles', 'meta_values'
|
||||
'variations', 'addons', 'bundles', 'meta_values', 'meta_values__property',
|
||||
'variations__meta_values', 'variations__meta_values__property',
|
||||
'require_membership_types', 'variations__require_membership_types',
|
||||
).all()
|
||||
|
||||
def perform_create(self, serializer):
|
||||
@@ -147,7 +149,11 @@ class ItemVariationViewSet(viewsets.ModelViewSet):
|
||||
return get_object_or_404(Item, pk=self.kwargs['item'], event=self.request.event)
|
||||
|
||||
def get_queryset(self):
|
||||
return self.item.variations.all()
|
||||
return self.item.variations.all().prefetch_related(
|
||||
'meta_values',
|
||||
'meta_values__property',
|
||||
'require_membership_types'
|
||||
)
|
||||
|
||||
def get_serializer_context(self):
|
||||
ctx = super().get_serializer_context()
|
||||
|
||||
@@ -65,9 +65,10 @@ from pretix.api.views import RichOrderingFilter
|
||||
from pretix.base.i18n import language
|
||||
from pretix.base.models import (
|
||||
CachedCombinedTicket, CachedTicket, Checkin, Device, EventMetaValue,
|
||||
Invoice, InvoiceAddress, ItemMetaValue, Order, OrderFee, OrderPayment,
|
||||
OrderPosition, OrderRefund, Quota, SubEvent, SubEventMetaValue, TaxRule,
|
||||
TeamAPIToken, generate_secret,
|
||||
Invoice, InvoiceAddress, ItemMetaValue, ItemVariation,
|
||||
ItemVariationMetaValue, Order, OrderFee, OrderPayment, OrderPosition,
|
||||
OrderRefund, Quota, SubEvent, SubEventMetaValue, TaxRule, TeamAPIToken,
|
||||
generate_secret,
|
||||
)
|
||||
from pretix.base.models.orders import QuestionAnswer, RevokedTicketSecret
|
||||
from pretix.base.payment import PaymentException
|
||||
@@ -232,7 +233,9 @@ class OrderViewSet(viewsets.ModelViewSet):
|
||||
Prefetch('item', queryset=self.request.event.items.prefetch_related(
|
||||
Prefetch('meta_values', ItemMetaValue.objects.select_related('property'), to_attr='meta_values_cached')
|
||||
)),
|
||||
'variation',
|
||||
Prefetch('variation', queryset=ItemVariation.objects.prefetch_related(
|
||||
Prefetch('meta_values', ItemVariationMetaValue.objects.select_related('property'), to_attr='meta_values_cached')
|
||||
)),
|
||||
'answers', 'answers__options', 'answers__question',
|
||||
'item__category',
|
||||
'addon_to__answers', 'addon_to__answers__options', 'addon_to__answers__question',
|
||||
@@ -999,7 +1002,11 @@ class OrderPositionViewSet(viewsets.ModelViewSet):
|
||||
Prefetch('meta_values', ItemMetaValue.objects.select_related('property'),
|
||||
to_attr='meta_values_cached')
|
||||
)),
|
||||
'variation', 'answers', 'answers__options', 'answers__question',
|
||||
Prefetch('variation', queryset=self.request.event.items.prefetch_related(
|
||||
Prefetch('meta_values', ItemVariationMetaValue.objects.select_related('property'),
|
||||
to_attr='meta_values_cached')
|
||||
)),
|
||||
'answers', 'answers__options', 'answers__question',
|
||||
'item__category',
|
||||
Prefetch('subevent', queryset=self.request.event.subevents.prefetch_related(
|
||||
Prefetch('meta_values', to_attr='meta_values_cached',
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
#
|
||||
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
|
||||
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
|
||||
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
|
||||
# this file, see <https://pretix.eu/about/en/license>.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
from collections import defaultdict
|
||||
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from localflavor.ar.forms import ARPostalCodeField
|
||||
from localflavor.at.forms import ATZipCodeField
|
||||
from localflavor.au.forms import AUPostCodeField
|
||||
from localflavor.be.forms import BEPostalCodeField
|
||||
from localflavor.br.forms import BRZipCodeField
|
||||
from localflavor.ca.forms import CAPostalCodeField
|
||||
from localflavor.ch.forms import CHZipCodeField
|
||||
from localflavor.cn.forms import CNPostCodeField
|
||||
from localflavor.cu.forms import CUPostalCodeField
|
||||
from localflavor.cz.forms import CZPostalCodeField
|
||||
from localflavor.de.forms import DEZipCodeField
|
||||
from localflavor.dk.forms import DKPostalCodeField
|
||||
from localflavor.ee.forms import EEZipCodeField
|
||||
from localflavor.es.forms import ESPostalCodeField
|
||||
from localflavor.fi.forms import FIZipCodeField
|
||||
from localflavor.fr.forms import FRZipCodeField
|
||||
from localflavor.gb.forms import GBPostcodeField
|
||||
from localflavor.gr.forms import GRPostalCodeField
|
||||
from localflavor.hr.forms import HRPostalCodeField
|
||||
from localflavor.id_.forms import IDPostCodeField
|
||||
from localflavor.ie.forms import EircodeField
|
||||
from localflavor.il.forms import ILPostalCodeField
|
||||
from localflavor.in_.forms import INZipCodeField
|
||||
from localflavor.ir.forms import IRPostalCodeField
|
||||
from localflavor.is_.is_postalcodes import IS_POSTALCODES
|
||||
from localflavor.it.forms import ITZipCodeField
|
||||
from localflavor.jp.forms import JPPostalCodeField
|
||||
from localflavor.lt.forms import LTPostalCodeField
|
||||
from localflavor.lv.forms import LVPostalCodeField
|
||||
from localflavor.ma.forms import MAPostalCodeField
|
||||
from localflavor.mt.forms import MTPostalCodeField
|
||||
from localflavor.mx.forms import MXZipCodeField
|
||||
from localflavor.nl.forms import NLZipCodeField
|
||||
from localflavor.no.forms import NOZipCodeField
|
||||
from localflavor.nz.forms import NZPostCodeField
|
||||
from localflavor.pk.forms import PKPostCodeField
|
||||
from localflavor.pl.forms import PLPostalCodeField
|
||||
from localflavor.pt.forms import PTZipCodeField
|
||||
from localflavor.ro.forms import ROPostalCodeField
|
||||
from localflavor.ru.forms import RUPostalCodeField
|
||||
from localflavor.se.forms import SEPostalCodeField
|
||||
from localflavor.sg.forms import SGPostCodeField
|
||||
from localflavor.si.si_postalcodes import SI_POSTALCODES
|
||||
from localflavor.sk.forms import SKPostalCodeField
|
||||
from localflavor.tr.forms import TRPostalCodeField
|
||||
from localflavor.ua.forms import UAPostalCodeField
|
||||
from localflavor.us.forms import USZipCodeField
|
||||
from localflavor.za.forms import ZAPostCodeField
|
||||
|
||||
from pretix.base.settings import COUNTRIES_WITH_STATE_IN_ADDRESS
|
||||
|
||||
_validator_classes = defaultdict(list)
|
||||
|
||||
COUNTRIES_WITH_STREET_ZIPCODE_AND_CITY_REQUIRED = {
|
||||
# We don't presume this for countries we don't have knowledge about, there are countries in the
|
||||
# world e.g. without zipcodes
|
||||
'AR', 'AT', 'AU', 'BE', 'BR', 'CA', 'CH', 'CN', 'CU', 'CZ', 'DE', 'DK', 'EE', 'ES', 'FI', 'FR',
|
||||
'GB', 'GR', 'HR', 'ID', 'IE', 'IL', 'IN', 'IR', 'IS', 'IT', 'JP', 'LT', 'LV', 'MA', 'MT', 'MX',
|
||||
'NL', 'NO', 'NZ', 'PK', 'PL', 'PT', 'RO', 'RU', 'SE', 'SG', 'SI', 'SK', 'TR', 'UA', 'US', 'ZA',
|
||||
}
|
||||
|
||||
|
||||
def validate_address(address: dict, all_optional=False):
|
||||
"""
|
||||
:param address: A dictionary with at least the entries ``street``, ``zipcode``, ``city``, ``country``,
|
||||
``state``
|
||||
:return: The dictionary, possibly with changes
|
||||
"""
|
||||
if not address.get('street') and not address.get('zipcode') and not address.get('city'):
|
||||
# Consider the actual address part to be empty, no further validation necessary, if the
|
||||
# address should be required, it's the callers job to validate that at least one of these
|
||||
# fields is filled
|
||||
return address
|
||||
|
||||
if not address.get('country'):
|
||||
raise ValidationError({'country': [_('This field is required.')]})
|
||||
|
||||
if str(address['country']) in COUNTRIES_WITH_STATE_IN_ADDRESS and not address.get('state') and not all_optional:
|
||||
raise ValidationError({'state': [_('This field is required.')]})
|
||||
|
||||
if str(address['country']) in COUNTRIES_WITH_STREET_ZIPCODE_AND_CITY_REQUIRED and not all_optional:
|
||||
for f in ('street', 'zipcode', 'city'):
|
||||
if not address.get(f):
|
||||
raise ValidationError({f: [_('This field is required.')]})
|
||||
|
||||
for klass in _validator_classes[str(address['country'])]:
|
||||
validator = klass()
|
||||
try:
|
||||
if address.get('zipcode'):
|
||||
address['zipcode'] = validator.validate_zipcode(address['zipcode'])
|
||||
except ValidationError as e:
|
||||
raise ValidationError({'zipcode': list(e)})
|
||||
|
||||
return address
|
||||
|
||||
|
||||
def register_validator_for(country):
|
||||
def inner(klass):
|
||||
_validator_classes[country].append(klass)
|
||||
return klass
|
||||
|
||||
return inner
|
||||
|
||||
|
||||
class BaseValidator:
|
||||
required_fields = []
|
||||
|
||||
def validate_zipcode(self, value):
|
||||
return value
|
||||
|
||||
|
||||
"""
|
||||
Currently, mostly have validators that are auto-generated from django-localflavor
|
||||
but custom ones can be added like this:
|
||||
|
||||
@register_validator_for('DE')
|
||||
class DEValidator(BaseValidator):
|
||||
def validate_zipcode(value):
|
||||
return value
|
||||
|
||||
In the future, we can also add additional methods to validate that e.g. a city
|
||||
is plausible for a given zip code.
|
||||
"""
|
||||
|
||||
_zip_code_fields = {
|
||||
'AR': ARPostalCodeField,
|
||||
'AT': ATZipCodeField,
|
||||
'AU': AUPostCodeField,
|
||||
'BE': BEPostalCodeField,
|
||||
'BR': BRZipCodeField,
|
||||
'CA': CAPostalCodeField,
|
||||
'CH': CHZipCodeField,
|
||||
'CN': CNPostCodeField,
|
||||
'CU': CUPostalCodeField,
|
||||
'CZ': CZPostalCodeField,
|
||||
'DE': DEZipCodeField,
|
||||
'DK': DKPostalCodeField,
|
||||
'EE': EEZipCodeField,
|
||||
'ES': ESPostalCodeField,
|
||||
'FI': FIZipCodeField,
|
||||
'FR': FRZipCodeField,
|
||||
'GB': GBPostcodeField,
|
||||
'GR': GRPostalCodeField,
|
||||
'HR': HRPostalCodeField,
|
||||
'ID': IDPostCodeField,
|
||||
'IE': EircodeField,
|
||||
'IL': ILPostalCodeField,
|
||||
'IN': INZipCodeField,
|
||||
'IR': IRPostalCodeField,
|
||||
'IT': ITZipCodeField,
|
||||
'JP': JPPostalCodeField,
|
||||
'LT': LTPostalCodeField,
|
||||
'LV': LVPostalCodeField,
|
||||
'MA': MAPostalCodeField,
|
||||
'MT': MTPostalCodeField,
|
||||
'MX': MXZipCodeField,
|
||||
'NL': NLZipCodeField,
|
||||
'NO': NOZipCodeField,
|
||||
'NZ': NZPostCodeField,
|
||||
'PK': PKPostCodeField,
|
||||
'PL': PLPostalCodeField,
|
||||
'PT': PTZipCodeField,
|
||||
'RO': ROPostalCodeField,
|
||||
'RU': RUPostalCodeField,
|
||||
'SE': SEPostalCodeField,
|
||||
'SG': SGPostCodeField,
|
||||
'SK': SKPostalCodeField,
|
||||
'TR': TRPostalCodeField,
|
||||
'UA': UAPostalCodeField,
|
||||
'US': USZipCodeField,
|
||||
'ZA': ZAPostCodeField,
|
||||
}
|
||||
|
||||
|
||||
def _generate_class_from_zipcode_field(field_class):
|
||||
class _GeneratedValidator(BaseValidator):
|
||||
def validate_zipcode(self, value):
|
||||
return field_class().clean(value)
|
||||
return _GeneratedValidator
|
||||
|
||||
|
||||
for cc, field_class in _zip_code_fields.items():
|
||||
register_validator_for(cc)(_generate_class_from_zipcode_field(field_class))
|
||||
|
||||
|
||||
@register_validator_for('IS')
|
||||
class ISValidator(BaseValidator):
|
||||
def validate_zipcode(self, value):
|
||||
if value not in [entry[0] for entry in IS_POSTALCODES]:
|
||||
raise ValidationError(_('Enter a postal code in the format XXX.'), code='invalid')
|
||||
return value
|
||||
|
||||
|
||||
@register_validator_for('SI')
|
||||
class SIValidator(BaseValidator):
|
||||
def validate_zipcode(self, value):
|
||||
try:
|
||||
if int(value) not in [entry[0] for entry in SI_POSTALCODES]:
|
||||
raise ValidationError(_('Enter a postal code in the format XXXX.'), code='invalid')
|
||||
except ValueError:
|
||||
raise ValidationError(_('Enter a postal code in the format XXXX.'), code='invalid')
|
||||
return value
|
||||
@@ -381,6 +381,14 @@ def base_placeholders(sender, **kwargs):
|
||||
SimpleFunctionalMailTextPlaceholder(
|
||||
'currency', ['event'], lambda event: event.currency, lambda event: event.currency
|
||||
),
|
||||
SimpleFunctionalMailTextPlaceholder(
|
||||
'order_email', ['order'], lambda order: order.email, 'john@example.org'
|
||||
),
|
||||
SimpleFunctionalMailTextPlaceholder(
|
||||
'invoice_number', ['invoice'],
|
||||
lambda invoice: invoice.full_invoice_no,
|
||||
f'{sender.settings.invoice_numbers_prefix or (sender.slug.upper() + "-")}00000'
|
||||
),
|
||||
SimpleFunctionalMailTextPlaceholder(
|
||||
'refund_amount', ['event_or_subevent', 'refund_amount'],
|
||||
lambda event_or_subevent, refund_amount: LazyCurrencyNumber(refund_amount, event_or_subevent.currency),
|
||||
|
||||
@@ -29,7 +29,7 @@ from openpyxl.utils import get_column_letter
|
||||
from ...helpers.safe_openpyxl import SafeCell
|
||||
from ..channels import get_all_sales_channels
|
||||
from ..exporter import ListExporter
|
||||
from ..models import ItemMetaValue
|
||||
from ..models import ItemMetaValue, ItemVariation, ItemVariationMetaValue
|
||||
from ..signals import register_data_exporters
|
||||
|
||||
|
||||
@@ -106,18 +106,27 @@ class ItemDataExporter(ListExporter):
|
||||
yield row
|
||||
|
||||
for i in self.event.items.prefetch_related(
|
||||
'variations',
|
||||
Prefetch(
|
||||
'meta_values',
|
||||
ItemMetaValue.objects.select_related('property'),
|
||||
to_attr='meta_values_cached'
|
||||
)
|
||||
),
|
||||
Prefetch(
|
||||
'variations',
|
||||
queryset=ItemVariation.objects.prefetch_related(
|
||||
Prefetch(
|
||||
'meta_values',
|
||||
ItemVariationMetaValue.objects.select_related('property'),
|
||||
to_attr='meta_values_cached'
|
||||
),
|
||||
),
|
||||
),
|
||||
).select_related('category', 'tax_rule'):
|
||||
m = i.meta_data
|
||||
vars = list(i.variations.all())
|
||||
|
||||
if vars:
|
||||
for v in vars:
|
||||
m = v.meta_data
|
||||
row = [
|
||||
i.pk,
|
||||
v.pk,
|
||||
@@ -160,6 +169,7 @@ class ItemDataExporter(ListExporter):
|
||||
yield row
|
||||
|
||||
else:
|
||||
m = i.meta_data
|
||||
row = [
|
||||
i.pk,
|
||||
"",
|
||||
|
||||
@@ -36,9 +36,11 @@ import json
|
||||
from decimal import Decimal
|
||||
|
||||
from django.core.serializers.json import DjangoJSONEncoder
|
||||
from django.db.models import Prefetch
|
||||
from django.dispatch import receiver
|
||||
|
||||
from ..exporter import BaseExporter
|
||||
from ..models import ItemMetaValue, ItemVariation, ItemVariationMetaValue
|
||||
from ..signals import register_data_exporters
|
||||
|
||||
|
||||
@@ -106,9 +108,26 @@ class JSONExporter(BaseExporter):
|
||||
'available_from': variation.available_from,
|
||||
'available_until': variation.available_until,
|
||||
'hide_without_voucher': variation.hide_without_voucher,
|
||||
'meta_data': variation.meta_data,
|
||||
} for variation in item.variations.all()
|
||||
]
|
||||
} for item in self.event.items.select_related('tax_rule').prefetch_related('variations')
|
||||
} for item in self.event.items.select_related('tax_rule').prefetch_related(
|
||||
Prefetch(
|
||||
'meta_values',
|
||||
ItemMetaValue.objects.select_related('property'),
|
||||
to_attr='meta_values_cached'
|
||||
),
|
||||
Prefetch(
|
||||
'variations',
|
||||
queryset=ItemVariation.objects.prefetch_related(
|
||||
Prefetch(
|
||||
'meta_values',
|
||||
ItemVariationMetaValue.objects.select_related('property'),
|
||||
to_attr='meta_values_cached'
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
'questions': [
|
||||
{
|
||||
|
||||
@@ -135,6 +135,10 @@ class NamePartsWidget(forms.MultiWidget):
|
||||
data.append(value.get(fname, ""))
|
||||
if '_legacy' in value and not data[-1]:
|
||||
data[-1] = value.get('_legacy', '')
|
||||
elif not any(d for d in data) and '_scheme' in value:
|
||||
scheme = PERSON_NAME_SCHEMES[value['_scheme']]
|
||||
data[-1] = scheme['concatenation'](value).strip()
|
||||
|
||||
return data
|
||||
|
||||
def render(self, name: str, value, attrs=None, renderer=None) -> str:
|
||||
@@ -915,6 +919,7 @@ class BaseQuestionsForm(forms.Form):
|
||||
|
||||
class BaseInvoiceAddressForm(forms.ModelForm):
|
||||
vat_warning = False
|
||||
address_validation = False
|
||||
|
||||
class Meta:
|
||||
model = InvoiceAddress
|
||||
@@ -1050,6 +1055,9 @@ class BaseInvoiceAddressForm(forms.ModelForm):
|
||||
v.widget.attrs['autocomplete'] = 'section-invoice billing ' + v.widget.attrs.get('autocomplete', '')
|
||||
|
||||
def clean(self):
|
||||
from pretix.base.addressvalidation import \
|
||||
validate_address # local import to prevent impact on startup time
|
||||
|
||||
data = self.cleaned_data
|
||||
if not data.get('is_business'):
|
||||
data['company'] = ''
|
||||
@@ -1065,9 +1073,8 @@ class BaseInvoiceAddressForm(forms.ModelForm):
|
||||
if 'vat_id' in self.changed_data or not data.get('vat_id'):
|
||||
self.instance.vat_id_validated = False
|
||||
|
||||
if data.get('city') and data.get('country') and str(data['country']) in COUNTRIES_WITH_STATE_IN_ADDRESS:
|
||||
if not data.get('state'):
|
||||
self.add_error('state', _('This field is required.'))
|
||||
if self.address_validation:
|
||||
self.cleaned_data = data = validate_address(data, self.all_optional)
|
||||
|
||||
self.instance.name_parts = data.get('name_parts')
|
||||
|
||||
|
||||
+18
-12
@@ -242,6 +242,12 @@ class BaseReportlabInvoiceRenderer(BaseInvoiceRenderer):
|
||||
buffer.seek(0)
|
||||
return 'invoice.pdf', 'application/pdf', buffer.read()
|
||||
|
||||
def _clean_text(self, text, tags=None):
|
||||
return bleach.clean(
|
||||
text,
|
||||
tags=tags or []
|
||||
).strip().replace('<br>', '<br />').replace('\n', '<br />\n')
|
||||
|
||||
|
||||
class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
||||
identifier = 'classic'
|
||||
@@ -266,7 +272,7 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
||||
invoice_to_top = 52 * mm
|
||||
|
||||
def _draw_invoice_to(self, canvas):
|
||||
p = Paragraph(bleach.clean(self.invoice.address_invoice_to, tags=[]).strip().replace('\n', '<br />\n'),
|
||||
p = Paragraph(self._clean_text(self.invoice.address_invoice_to),
|
||||
style=self.stylesheet['Normal'])
|
||||
p.wrapOn(canvas, self.invoice_to_width, self.invoice_to_height)
|
||||
p_size = p.wrap(self.invoice_to_width, self.invoice_to_height)
|
||||
@@ -279,7 +285,7 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
||||
|
||||
def _draw_invoice_from(self, canvas):
|
||||
p = Paragraph(
|
||||
bleach.clean(self.invoice.full_invoice_from, tags=[]).strip().replace('\n', '<br />\n'),
|
||||
self._clean_text(self.invoice.full_invoice_from),
|
||||
style=self.stylesheet['InvoiceFrom']
|
||||
)
|
||||
p.wrapOn(canvas, self.invoice_from_width, self.invoice_from_height)
|
||||
@@ -474,8 +480,8 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
||||
if self.invoice.custom_field:
|
||||
story.append(Paragraph(
|
||||
'{}: {}'.format(
|
||||
bleach.clean(str(self.invoice.event.settings.invoice_address_custom_field), tags=[]).strip().replace('\n', '<br />\n'),
|
||||
bleach.clean(self.invoice.custom_field, tags=[]).strip().replace('\n', '<br />\n'),
|
||||
self._clean_text(str(self.invoice.event.settings.invoice_address_custom_field)),
|
||||
self._clean_text(self.invoice.custom_field),
|
||||
),
|
||||
self.stylesheet['Normal']
|
||||
))
|
||||
@@ -483,7 +489,7 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
||||
if self.invoice.internal_reference:
|
||||
story.append(Paragraph(
|
||||
pgettext('invoice', 'Customer reference: {reference}').format(
|
||||
reference=bleach.clean(self.invoice.internal_reference, tags=[]).strip().replace('\n', '<br />\n'),
|
||||
reference=self._clean_text(self.invoice.internal_reference),
|
||||
),
|
||||
self.stylesheet['Normal']
|
||||
))
|
||||
@@ -491,20 +497,20 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
||||
if self.invoice.invoice_to_vat_id:
|
||||
story.append(Paragraph(
|
||||
pgettext('invoice', 'Customer VAT ID') + ': ' +
|
||||
bleach.clean(self.invoice.invoice_to_vat_id, tags=[]).replace("\n", "<br />\n"),
|
||||
self._clean_text(self.invoice.invoice_to_vat_id),
|
||||
self.stylesheet['Normal']
|
||||
))
|
||||
|
||||
if self.invoice.invoice_to_beneficiary:
|
||||
story.append(Paragraph(
|
||||
pgettext('invoice', 'Beneficiary') + ':<br />' +
|
||||
bleach.clean(self.invoice.invoice_to_beneficiary, tags=[]).replace("\n", "<br />\n"),
|
||||
self._clean_text(self.invoice.invoice_to_beneficiary),
|
||||
self.stylesheet['Normal']
|
||||
))
|
||||
|
||||
if self.invoice.introductory_text:
|
||||
story.append(Paragraph(
|
||||
self.invoice.introductory_text,
|
||||
self._clean_text(self.invoice.introductory_text, tags=['br']),
|
||||
self.stylesheet['Normal']
|
||||
))
|
||||
story.append(Spacer(1, 10 * mm))
|
||||
@@ -571,7 +577,7 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
||||
description = description + "\n" + single_price_line
|
||||
tdata.append((
|
||||
Paragraph(
|
||||
bleach.clean(description, tags=['br']).strip().replace('<br>', '<br/>').replace('\n', '<br />\n'),
|
||||
self._clean_text(description, tags=['br']),
|
||||
self.stylesheet['Normal']
|
||||
),
|
||||
str(len(lines)),
|
||||
@@ -587,7 +593,7 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
||||
description = description + "\n" + single_price_line
|
||||
tdata.append((
|
||||
Paragraph(
|
||||
bleach.clean(description, tags=['br']).strip().replace('<br>', '<br/>').replace('\n', '<br />\n'),
|
||||
self._clean_text(description, tags=['br']),
|
||||
self.stylesheet['Normal']
|
||||
),
|
||||
str(len(lines)),
|
||||
@@ -657,7 +663,7 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
||||
|
||||
if self.invoice.additional_text:
|
||||
story.append(Paragraph(
|
||||
self.invoice.additional_text,
|
||||
self._clean_text(self.invoice.additional_text, tags=['br']),
|
||||
self.stylesheet['Normal']
|
||||
))
|
||||
story.append(Spacer(1, 5 * mm))
|
||||
@@ -794,7 +800,7 @@ class Modern1Renderer(ClassicInvoiceRenderer):
|
||||
if not self.invoice.invoice_from:
|
||||
return
|
||||
c = [
|
||||
bleach.clean(l, tags=[]).strip().replace('\n', '<br />\n')
|
||||
self._clean_text(l)
|
||||
for l in self.invoice.address_invoice_from.strip().split('\n')
|
||||
]
|
||||
p = Paragraph(' · '.join(c), style=self.stylesheet['Sender'])
|
||||
|
||||
@@ -224,6 +224,11 @@ def _merge_csp(a, b):
|
||||
if k not in a:
|
||||
a[k] = b[k]
|
||||
|
||||
for k, v in a.items():
|
||||
if "'unsafe-inline'" in v:
|
||||
# If we need unsafe-inline, drop any hashes or nonce as they will be ignored otherwise
|
||||
a[k] = [i for i in v if not i.startswith("'nonce-") and not i.startswith("'sha-")]
|
||||
|
||||
|
||||
class SecurityMiddleware(MiddlewareMixin):
|
||||
CSP_EXEMPT = (
|
||||
@@ -301,7 +306,7 @@ class SecurityMiddleware(MiddlewareMixin):
|
||||
resp['Content-Security-Policy'] = _render_csp(h).format(static=staticdomain, dynamic=dynamicdomain,
|
||||
media=mediadomain)
|
||||
for k, v in h.items():
|
||||
h[k] = ' '.join(v).format(static=staticdomain, dynamic=dynamicdomain, media=mediadomain).split(' ')
|
||||
h[k] = sorted(set(' '.join(v).format(static=staticdomain, dynamic=dynamicdomain, media=mediadomain).split(' ')))
|
||||
resp['Content-Security-Policy'] = _render_csp(h)
|
||||
elif 'Content-Security-Policy' in resp:
|
||||
del resp['Content-Security-Policy']
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
# Generated by Django 3.2.16 on 2022-12-09 10:06
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
import pretix.base.models.base
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('pretixbase', '0225_orderpayment_process_initiated'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='ItemVariationMetaValue',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False)),
|
||||
('value', models.TextField()),
|
||||
('property', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='variation_values', to='pretixbase.itemmetaproperty')),
|
||||
('variation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='meta_values', to='pretixbase.itemvariation')),
|
||||
],
|
||||
options={
|
||||
'unique_together': {('variation', 'property')},
|
||||
},
|
||||
bases=(models.Model, pretix.base.models.base.LoggingMixin),
|
||||
),
|
||||
]
|
||||
@@ -34,8 +34,8 @@ from .giftcards import GiftCard, GiftCardAcceptance, GiftCardTransaction
|
||||
from .invoices import Invoice, InvoiceLine, invoice_filename
|
||||
from .items import (
|
||||
Item, ItemAddOn, ItemBundle, ItemCategory, ItemMetaProperty, ItemMetaValue,
|
||||
ItemVariation, Question, QuestionOption, Quota, SubEventItem,
|
||||
SubEventItemVariation, itempicture_upload_to,
|
||||
ItemVariation, ItemVariationMetaValue, Question, QuestionOption, Quota,
|
||||
SubEventItem, SubEventItemVariation, itempicture_upload_to,
|
||||
)
|
||||
from .log import LogEntry
|
||||
from .memberships import Membership, MembershipType
|
||||
|
||||
@@ -28,6 +28,7 @@ from typing import Dict, Optional, Tuple
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.validators import MinValueValidator
|
||||
from django.db import models
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import gettext_lazy as _, pgettext_lazy
|
||||
from django_scopes import ScopedManager
|
||||
|
||||
@@ -198,6 +199,14 @@ class Discount(LoggedModel):
|
||||
'subevent_mode': self.subevent_mode,
|
||||
})
|
||||
|
||||
def is_available_by_time(self, now_dt=None) -> bool:
|
||||
now_dt = now_dt or now()
|
||||
if self.available_from and self.available_from > now_dt:
|
||||
return False
|
||||
if self.available_until and self.available_until < now_dt:
|
||||
return False
|
||||
return True
|
||||
|
||||
def _apply_min_value(self, positions, idx_group, result):
|
||||
if self.condition_min_value and sum(positions[idx][2] for idx in idx_group) < self.condition_min_value:
|
||||
return
|
||||
|
||||
@@ -728,7 +728,7 @@ class Event(EventMixin, LoggedModel):
|
||||
from ..signals import event_copy_data
|
||||
from . import (
|
||||
Discount, Item, ItemAddOn, ItemBundle, ItemCategory, ItemMetaValue,
|
||||
Question, Quota,
|
||||
ItemVariationMetaValue, Question, Quota,
|
||||
)
|
||||
|
||||
# Note: avoid self.set_active_plugins(), it causes trouble e.g. for the badges plugin.
|
||||
@@ -804,12 +804,18 @@ class Event(EventMixin, LoggedModel):
|
||||
v.item = i
|
||||
v.save(force_insert=True)
|
||||
|
||||
for imv in ItemMetaValue.objects.filter(item__event=other).prefetch_related('item', 'property'):
|
||||
for imv in ItemMetaValue.objects.filter(item__event=other):
|
||||
imv.pk = None
|
||||
imv.property = item_meta_properties_map[imv.property.pk]
|
||||
imv.property = item_meta_properties_map[imv.property_id]
|
||||
imv.item = item_map[imv.item.pk]
|
||||
imv.save(force_insert=True)
|
||||
|
||||
for imv in ItemVariationMetaValue.objects.filter(variation__item__event=other):
|
||||
imv.pk = None
|
||||
imv.property = item_meta_properties_map[imv.property_id]
|
||||
imv.variation = variation_map[imv.variation_id]
|
||||
imv.save(force_insert=True)
|
||||
|
||||
for ia in ItemAddOn.objects.filter(base_item__event=other).prefetch_related('base_item', 'addon_category'):
|
||||
ia.pk = None
|
||||
ia.base_item = item_map[ia.base_item.pk]
|
||||
|
||||
@@ -1008,6 +1008,16 @@ class ItemVariation(models.Model):
|
||||
return False
|
||||
return True
|
||||
|
||||
@property
|
||||
def meta_data(self):
|
||||
data = self.item.meta_data
|
||||
if hasattr(self, 'meta_values_cached'):
|
||||
data.update({v.property.name: v.value for v in self.meta_values_cached})
|
||||
else:
|
||||
data.update({v.property.name: v.value for v in self.meta_values.select_related('property').all()})
|
||||
|
||||
return OrderedDict((k, v) for k, v in sorted(data.items(), key=lambda k: k[0]))
|
||||
|
||||
|
||||
class ItemAddOn(models.Model):
|
||||
"""
|
||||
@@ -1787,8 +1797,21 @@ class ItemMetaValue(LoggedModel):
|
||||
class Meta:
|
||||
unique_together = ('item', 'property')
|
||||
|
||||
def delete(self, *args, **kwargs):
|
||||
super().delete(*args, **kwargs)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
super().save(*args, **kwargs)
|
||||
class ItemVariationMetaValue(LoggedModel):
|
||||
"""
|
||||
A meta-data value assigned to an item variation, overriding the value on the item.
|
||||
|
||||
:param variation: The variation this metadata is valid for
|
||||
:type variation: ItemVariation
|
||||
:param property: The property this value belongs to
|
||||
:type property: ItemMetaProperty
|
||||
:param value: The actual value
|
||||
:type value: str
|
||||
"""
|
||||
variation = models.ForeignKey('ItemVariation', on_delete=models.CASCADE, related_name='meta_values')
|
||||
property = models.ForeignKey('ItemMetaProperty', on_delete=models.CASCADE, related_name='variation_values')
|
||||
value = models.TextField()
|
||||
|
||||
class Meta:
|
||||
unique_together = ('variation', 'property')
|
||||
|
||||
@@ -80,6 +80,7 @@ from pretix.base.settings import PERSON_NAME_SCHEMES
|
||||
from pretix.base.signals import order_gracefully_delete
|
||||
|
||||
from ...helpers.countries import CachedCountries, FastCountryField
|
||||
from ...helpers.format import format_map
|
||||
from ._transactions import (
|
||||
_fail, _transactions_mark_order_clean, _transactions_mark_order_dirty,
|
||||
)
|
||||
@@ -996,7 +997,7 @@ class Order(LockModel, LoggedModel):
|
||||
position and the attendee email will be used if available.
|
||||
"""
|
||||
from pretix.base.services.mail import (
|
||||
SendMailException, TolerantDict, mail, render_mail,
|
||||
SendMailException, mail, render_mail,
|
||||
)
|
||||
|
||||
if not self.email and not (position and position.attendee_email):
|
||||
@@ -1012,7 +1013,7 @@ class Order(LockModel, LoggedModel):
|
||||
|
||||
try:
|
||||
email_content = render_mail(template, context)
|
||||
subject = str(subject).format_map(TolerantDict(context))
|
||||
subject = format_map(subject, context)
|
||||
mail(
|
||||
recipient, subject, template, context,
|
||||
self.event, self.locale, self, headers=headers, sender=sender,
|
||||
@@ -1651,7 +1652,7 @@ class OrderPayment(models.Model):
|
||||
}, user=user, auth=auth)
|
||||
|
||||
def confirm(self, count_waitinglist=True, send_mail=True, force=False, user=None, auth=None, mail_text='',
|
||||
ignore_date=False, lock=True, payment_date=None):
|
||||
ignore_date=False, lock=True, payment_date=None, generate_invoice=True):
|
||||
"""
|
||||
Marks the payment as complete. If possible, this also marks the order as paid if no further
|
||||
payment is required
|
||||
@@ -1714,10 +1715,11 @@ 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)
|
||||
self._mark_order_paid(count_waitinglist, send_mail, force, user, auth, mail_text, ignore_date, lock, payment_sum - refund_sum,
|
||||
generate_invoice)
|
||||
|
||||
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):
|
||||
ignore_date=False, lock=True, payment_refund_sum=0, allow_generate_invoice=True):
|
||||
from pretix.base.services.invoices import (
|
||||
generate_invoice, invoice_qualified,
|
||||
)
|
||||
@@ -1734,7 +1736,7 @@ class OrderPayment(models.Model):
|
||||
ignore_date=ignore_date)
|
||||
|
||||
invoice = None
|
||||
if invoice_qualified(self.order):
|
||||
if invoice_qualified(self.order) and allow_generate_invoice:
|
||||
invoices = self.order.invoices.filter(is_cancellation=False).count()
|
||||
cancellations = self.order.invoices.filter(is_cancellation=True).count()
|
||||
gen_invoice = (
|
||||
@@ -2413,7 +2415,7 @@ class OrderPosition(AbstractPosition):
|
||||
:param attach_ical: Attach relevant ICS files
|
||||
"""
|
||||
from pretix.base.services.mail import (
|
||||
SendMailException, TolerantDict, mail, render_mail,
|
||||
SendMailException, mail, render_mail,
|
||||
)
|
||||
|
||||
if not self.attendee_email:
|
||||
@@ -2426,7 +2428,7 @@ class OrderPosition(AbstractPosition):
|
||||
recipient = self.attendee_email
|
||||
try:
|
||||
email_content = render_mail(template, context)
|
||||
subject = str(subject).format_map(TolerantDict(context))
|
||||
subject = format_map(subject, context)
|
||||
mail(
|
||||
recipient, subject, template, context,
|
||||
self.event, self.order.locale, order=self.order, headers=headers, sender=sender,
|
||||
|
||||
+18
-15
@@ -68,6 +68,7 @@ from pretix.base.signals import register_payment_providers
|
||||
from pretix.base.templatetags.money import money_filter
|
||||
from pretix.base.templatetags.rich_text import rich_text
|
||||
from pretix.helpers.countries import CachedCountries
|
||||
from pretix.helpers.format import format_map
|
||||
from pretix.helpers.money import DecimalTextInput
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||
from pretix.presale.views import get_cart, get_cart_total
|
||||
@@ -175,7 +176,9 @@ class BasePaymentProvider:
|
||||
"""
|
||||
Set this to ``True`` if your ``execute_payment`` function needs to be triggered by a user request, i.e. either
|
||||
needs the ``request`` object or might require a browser redirect. If this is ``False``, you will not receive
|
||||
a ``request`` and may not redirect since execute_payment might be called server-side.
|
||||
a ``request`` and may not redirect since execute_payment might be called server-side. You should ensure that
|
||||
your ``execute_payment`` method has a limited execution time (i.e. by using ``timeout`` for all external calls)
|
||||
and handles all error cases appropriately.
|
||||
"""
|
||||
return True
|
||||
|
||||
@@ -322,16 +325,6 @@ class BasePaymentProvider:
|
||||
help_text=_('Users will not be able to choose this payment provider after the given date.'),
|
||||
required=False,
|
||||
)),
|
||||
('_invoice_text',
|
||||
I18nFormField(
|
||||
label=_('Text on invoices'),
|
||||
help_text=_('Will be printed just below the payment figures and above the closing text on invoices. '
|
||||
'This will only be used if the invoice is generated before the order is paid. If the '
|
||||
'invoice is generated later, it will show a text stating that it has already been paid.'),
|
||||
required=False,
|
||||
widget=I18nTextarea,
|
||||
widget_kwargs={'attrs': {'rows': '2'}}
|
||||
)),
|
||||
('_total_min',
|
||||
forms.DecimalField(
|
||||
label=_('Minimum order total'),
|
||||
@@ -379,6 +372,16 @@ class BasePaymentProvider:
|
||||
'above!').format(docs_url='https://docs.pretix.eu/en/latest/user/payments/fees.html'),
|
||||
required=False
|
||||
)),
|
||||
('_invoice_text',
|
||||
I18nFormField(
|
||||
label=_('Text on invoices'),
|
||||
help_text=_('Will be printed just below the payment figures and above the closing text on invoices. '
|
||||
'This will only be used if the invoice is generated before the order is paid. If the '
|
||||
'invoice is generated later, it will show a text stating that it has already been paid.'),
|
||||
required=False,
|
||||
widget=I18nTextarea,
|
||||
widget_kwargs={'attrs': {'rows': '2'}}
|
||||
)),
|
||||
('_restricted_countries',
|
||||
forms.MultipleChoiceField(
|
||||
label=_('Restrict to countries'),
|
||||
@@ -1120,12 +1123,12 @@ class ManualPayment(BasePaymentProvider):
|
||||
}
|
||||
|
||||
def order_pending_mail_render(self, order, payment) -> str:
|
||||
msg = str(self.settings.get('email_instructions', as_type=LazyI18nString)).format_map(self.format_map(order, payment))
|
||||
msg = format_map(self.settings.get('email_instructions', as_type=LazyI18nString), self.format_map(order, payment))
|
||||
return msg
|
||||
|
||||
def payment_pending_render(self, request, payment) -> str:
|
||||
return rich_text(
|
||||
str(self.settings.get('pending_description', as_type=LazyI18nString)).format_map(self.format_map(payment.order, payment))
|
||||
format_map(self.settings.get('pending_description', as_type=LazyI18nString), self.format_map(payment.order, payment))
|
||||
)
|
||||
|
||||
|
||||
@@ -1385,7 +1388,7 @@ class GiftCardPayment(BasePaymentProvider):
|
||||
except GiftCard.MultipleObjectsReturned:
|
||||
messages.error(request, _("This gift card can not be redeemed since its code is not unique. Please contact the organizer of this event."))
|
||||
|
||||
def execute_payment(self, request: HttpRequest, payment: OrderPayment) -> str:
|
||||
def execute_payment(self, request: HttpRequest, payment: OrderPayment, is_early_special_case=False) -> str:
|
||||
for p in payment.order.positions.all():
|
||||
if p.item.issue_giftcard:
|
||||
raise PaymentException(_("You cannot pay with gift cards when buying a gift card."))
|
||||
@@ -1421,7 +1424,7 @@ class GiftCardPayment(BasePaymentProvider):
|
||||
'gift_card': gc.pk,
|
||||
'transaction_id': trans.pk,
|
||||
}
|
||||
payment.confirm()
|
||||
payment.confirm(send_mail=not is_early_special_case, generate_invoice=not is_early_special_case)
|
||||
except PaymentException as e:
|
||||
payment.fail(info={'error': str(e)})
|
||||
raise e
|
||||
|
||||
@@ -746,6 +746,8 @@ class Renderer:
|
||||
|
||||
def replace(x):
|
||||
if x.group(1).startswith('itemmeta:'):
|
||||
if op.variation_id:
|
||||
return op.variation.meta_data.get(x.group(1)[9:]) or ''
|
||||
return op.item.meta_data.get(x.group(1)[9:]) or ''
|
||||
elif x.group(1).startswith('meta:'):
|
||||
return ev.meta_data.get(x.group(1)[5:]) or ''
|
||||
@@ -766,6 +768,8 @@ class Renderer:
|
||||
return re.sub(r'\{([a-zA-Z0-9:_]+)\}', replace, text)
|
||||
|
||||
elif o['content'].startswith('itemmeta:'):
|
||||
if op.variation_id:
|
||||
return op.variation.meta_data.get(o['content'][9:]) or ''
|
||||
return op.item.meta_data.get(o['content'][9:]) or ''
|
||||
|
||||
elif o['content'].startswith('meta:'):
|
||||
|
||||
@@ -65,7 +65,14 @@ def get_all_plugins(event=None) -> List[type]:
|
||||
)
|
||||
|
||||
|
||||
class PluginConfig(AppConfig):
|
||||
class PluginConfigMeta(type):
|
||||
def __getattribute__(cls, item):
|
||||
if item == "default" and cls is PluginConfig:
|
||||
return False
|
||||
return super().__getattribute__(item)
|
||||
|
||||
|
||||
class PluginConfig(AppConfig, metaclass=PluginConfigMeta):
|
||||
IGNORE = False
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
||||
@@ -35,12 +35,13 @@ from pretix.base.models import (
|
||||
SubEvent, User, WaitingListEntry,
|
||||
)
|
||||
from pretix.base.services.locking import LockTimeoutException
|
||||
from pretix.base.services.mail import SendMailException, TolerantDict, mail
|
||||
from pretix.base.services.mail import SendMailException, mail
|
||||
from pretix.base.services.orders import (
|
||||
OrderChangeManager, OrderError, _cancel_order, _try_auto_refund,
|
||||
)
|
||||
from pretix.base.services.tasks import ProfiledEventTask
|
||||
from pretix.celery_app import app
|
||||
from pretix.helpers.format import format_map
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -51,7 +52,7 @@ def _send_wle_mail(wle: WaitingListEntry, subject: LazyI18nString, message: Lazy
|
||||
try:
|
||||
mail(
|
||||
wle.email,
|
||||
str(subject).format_map(TolerantDict(email_context)),
|
||||
format_map(subject, email_context),
|
||||
message,
|
||||
email_context,
|
||||
wle.event,
|
||||
@@ -71,7 +72,7 @@ def _send_mail(order: Order, subject: LazyI18nString, message: LazyI18nString, s
|
||||
|
||||
email_context = get_email_context(event_or_subevent=subevent or order.event, refund_amount=refund_amount,
|
||||
order=order, position_or_address=ia, event=order.event)
|
||||
real_subject = str(subject).format_map(TolerantDict(email_context))
|
||||
real_subject = format_map(subject, email_context)
|
||||
try:
|
||||
order.send_mail(
|
||||
real_subject, message, email_context,
|
||||
@@ -86,7 +87,7 @@ def _send_mail(order: Order, subject: LazyI18nString, message: LazyI18nString, s
|
||||
continue
|
||||
|
||||
if p.addon_to_id is None and p.attendee_email and p.attendee_email != order.email:
|
||||
real_subject = str(subject).format_map(TolerantDict(email_context))
|
||||
real_subject = format_map(subject, email_context)
|
||||
email_context = get_email_context(event_or_subevent=p.subevent or order.event,
|
||||
event=order.event,
|
||||
refund_amount=refund_amount,
|
||||
|
||||
@@ -140,7 +140,7 @@ error_messages = {
|
||||
'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.'),
|
||||
'addon_only': _('One of the products you selected can only be bought as an add-on to another project.'),
|
||||
'addon_only': _('One of the products you selected can only be bought as an add-on to another product.'),
|
||||
'bundled_only': _('One of the products you selected can only be bought part of a bundle.'),
|
||||
'seat_required': _('You need to select a specific seat.'),
|
||||
'seat_invalid': _('Please select a valid seat.'),
|
||||
|
||||
@@ -452,7 +452,7 @@ def build_preview_invoice_pdf(event):
|
||||
|
||||
if event.tax_rules.exists():
|
||||
for i, tr in enumerate(event.tax_rules.all()):
|
||||
for j in range(150):
|
||||
for j in range(5):
|
||||
tax = tr.tax(Decimal('100.00'), base_price_is='gross')
|
||||
InvoiceLine.objects.create(
|
||||
invoice=invoice, description=_("Sample product {}").format(i + 1),
|
||||
@@ -460,7 +460,7 @@ def build_preview_invoice_pdf(event):
|
||||
tax_rate=tax.rate
|
||||
)
|
||||
else:
|
||||
for i in range(150):
|
||||
for i in range(5):
|
||||
InvoiceLine.objects.create(
|
||||
invoice=invoice, description=_("Sample product A"),
|
||||
gross_value=100, tax_value=0, tax_rate=0
|
||||
|
||||
@@ -76,6 +76,7 @@ from pretix.base.services.tasks import TransactionAwareTask
|
||||
from pretix.base.services.tickets import get_tickets_for_order
|
||||
from pretix.base.signals import email_filter, global_email_filter
|
||||
from pretix.celery_app import app
|
||||
from pretix.helpers.format import format_map
|
||||
from pretix.helpers.hierarkey import clean_filename
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||
from pretix.presale.ical import get_private_icals
|
||||
@@ -98,7 +99,8 @@ def mail(email: Union[str, Sequence[str]], subject: str, template: Union[str, La
|
||||
context: Dict[str, Any] = None, event: Event = None, locale: str = None, order: Order = None,
|
||||
position: OrderPosition = None, *, headers: dict = None, sender: str = None, organizer: Organizer = None,
|
||||
customer: Customer = None, invoices: Sequence = None, attach_tickets=False, auto_email=True, user=None,
|
||||
attach_ical=False, attach_cached_files: Sequence = None, attach_other_files: list=None):
|
||||
attach_ical=False, attach_cached_files: Sequence = None, attach_other_files: list=None,
|
||||
plain_text_only=False, no_order_links=False):
|
||||
"""
|
||||
Sends out an email to a user. The mail will be sent synchronously or asynchronously depending on the installation.
|
||||
|
||||
@@ -109,7 +111,7 @@ def mail(email: Union[str, Sequence[str]], subject: str, template: Union[str, La
|
||||
|
||||
:param template: The filename of a template to be used. It will be rendered with the locale given in the locale
|
||||
argument and the context given in the next argument. Alternatively, you can pass a LazyI18nString and
|
||||
``context`` will be used as the argument to a Python ``.format_map()`` call on the template.
|
||||
``context`` will be used as the argument to a ``pretix.helpers.format.format_map(template, context)`` call on the template.
|
||||
|
||||
:param context: The context for rendering the template (see ``template`` parameter)
|
||||
|
||||
@@ -148,12 +150,21 @@ def mail(email: Union[str, Sequence[str]], subject: str, template: Union[str, La
|
||||
|
||||
:param attach_other_files: A list of file paths on our storage to attach.
|
||||
|
||||
:param plain_text_only: If set to ``True``, rendering a HTML version will be skipped.
|
||||
|
||||
:param no_order_links: If set to ``True``, no link to the order confirmation page will be auto-appended. Currently
|
||||
only allowed to use together with ``plain_text_only`` since HTML renderers add their own
|
||||
links.
|
||||
|
||||
:raises MailOrderException: on obvious, immediate failures. Not raising an exception does not necessarily mean
|
||||
that the email has been sent, just that it has been queued by the email backend.
|
||||
"""
|
||||
if email == INVALID_ADDRESS:
|
||||
return
|
||||
|
||||
if no_order_links and not plain_text_only:
|
||||
raise ValueError('If you set no_order_links, you also need to set plain_text_only.')
|
||||
|
||||
headers = headers or {}
|
||||
if auto_email:
|
||||
headers['X-Auto-Response-Suppress'] = 'OOF, NRN, AutoReply, RN'
|
||||
@@ -242,7 +253,7 @@ def mail(email: Union[str, Sequence[str]], subject: str, template: Union[str, La
|
||||
if order and order.testmode:
|
||||
subject = "[TESTMODE] " + subject
|
||||
|
||||
if order and position:
|
||||
if order and position and not no_order_links:
|
||||
body_plain += _(
|
||||
"You are receiving this email because someone placed an order for {event} for you."
|
||||
).format(event=event.name)
|
||||
@@ -258,7 +269,7 @@ def mail(email: Union[str, Sequence[str]], subject: str, template: Union[str, La
|
||||
}
|
||||
)
|
||||
)
|
||||
elif order:
|
||||
elif order and not no_order_links:
|
||||
body_plain += _(
|
||||
"You are receiving this email because you placed an order for {event}."
|
||||
).format(event=event.name)
|
||||
@@ -278,7 +289,9 @@ def mail(email: Union[str, Sequence[str]], subject: str, template: Union[str, La
|
||||
|
||||
with override(timezone):
|
||||
try:
|
||||
if 'position' in inspect.signature(renderer.render).parameters:
|
||||
if plain_text_only:
|
||||
body_html = None
|
||||
elif 'position' in inspect.signature(renderer.render).parameters:
|
||||
body_html = renderer.render(content_plain, signature, raw_subject, order, position)
|
||||
else:
|
||||
# Backwards compatibility
|
||||
@@ -608,7 +621,7 @@ def render_mail(template, context):
|
||||
if isinstance(template, LazyI18nString):
|
||||
body = str(template)
|
||||
if context:
|
||||
body = body.format_map(TolerantDict(context))
|
||||
body = format_map(body, context)
|
||||
else:
|
||||
tpl = get_template(template)
|
||||
body = tpl.render(context)
|
||||
|
||||
@@ -64,9 +64,9 @@ from pretix.base.i18n import (
|
||||
LazyLocaleException, get_language_without_region, language,
|
||||
)
|
||||
from pretix.base.models import (
|
||||
CartPosition, Device, Event, GiftCard, Item, ItemVariation, Membership,
|
||||
Order, OrderPayment, OrderPosition, Quota, Seat, SeatCategoryMapping, User,
|
||||
Voucher,
|
||||
CartPosition, Customer, Device, Event, GiftCard, Item, ItemVariation,
|
||||
Membership, Order, OrderPayment, OrderPosition, Quota, Seat,
|
||||
SeatCategoryMapping, User, Voucher,
|
||||
)
|
||||
from pretix.base.models.event import SubEvent
|
||||
from pretix.base.models.orders import (
|
||||
@@ -74,7 +74,7 @@ from pretix.base.models.orders import (
|
||||
)
|
||||
from pretix.base.models.organizer import TeamAPIToken
|
||||
from pretix.base.models.tax import TAXED_ZERO, TaxedPrice, TaxRule
|
||||
from pretix.base.payment import PaymentException
|
||||
from pretix.base.payment import GiftCardPayment, PaymentException
|
||||
from pretix.base.reldate import RelativeDateWrapper
|
||||
from pretix.base.secrets import assign_ticket_secret
|
||||
from pretix.base.services import tickets
|
||||
@@ -849,7 +849,7 @@ def _get_fees(positions: List[CartPosition], payment_requests: List[dict], addre
|
||||
def _create_order(event: Event, email: str, positions: List[CartPosition], now_dt: datetime,
|
||||
payment_requests: List[dict], locale: str=None, address: InvoiceAddress=None,
|
||||
meta_info: dict=None, sales_channel: str='web', shown_total=None,
|
||||
customer=None):
|
||||
customer=None, customer_attached=False):
|
||||
payments = []
|
||||
sales_channel = get_all_sales_channels()[sales_channel]
|
||||
|
||||
@@ -931,6 +931,8 @@ def _create_order(event: Event, email: str, positions: List[CartPosition], now_d
|
||||
if meta_info:
|
||||
for msg in meta_info.get('confirm_messages', []):
|
||||
order.log_action('pretix.event.order.consent', data={'msg': msg})
|
||||
if customer and customer_attached:
|
||||
customer.log_action('pretix.customer.order.attached', data={'order': order.code})
|
||||
|
||||
order_placed.send(event, order=order)
|
||||
return order, payments
|
||||
@@ -981,9 +983,6 @@ def _perform_order(event: Event, payment_requests: List[dict], position_ids: Lis
|
||||
if not p['pprov']:
|
||||
raise OrderError(error_messages['internal'])
|
||||
|
||||
if customer:
|
||||
customer = event.organizer.customers.get(pk=customer)
|
||||
|
||||
if email == settings.PRETIX_EMAIL_NONE_VALUE:
|
||||
email = None
|
||||
|
||||
@@ -995,6 +994,30 @@ def _perform_order(event: Event, payment_requests: List[dict], position_ids: Lis
|
||||
except InvoiceAddress.DoesNotExist:
|
||||
pass
|
||||
|
||||
customer_attached = False
|
||||
if customer:
|
||||
customer = event.organizer.customers.get(pk=customer)
|
||||
elif event.settings.customer_accounts_link_by_email in ('attach', 'create') and email:
|
||||
try:
|
||||
customer = event.organizer.customers.get(email__iexact=email)
|
||||
customer_attached = True
|
||||
except Customer.MultipleObjectsReturned:
|
||||
logger.warning(f'Multiple customer accounts found for {email}, not attaching.')
|
||||
except Customer.DoesNotExist:
|
||||
if event.settings.customer_accounts_link_by_email == 'create':
|
||||
customer = event.organizer.customers.create(
|
||||
email=email,
|
||||
name_parts=addr.name_parts if addr else None,
|
||||
phone=(meta_info or {}).get('contact_form_data', {}).get('phone'),
|
||||
is_active=True,
|
||||
is_verified=False,
|
||||
locale=locale,
|
||||
)
|
||||
customer.set_unusable_password()
|
||||
customer.save()
|
||||
customer.log_action('pretix.customer.created.auto', {})
|
||||
customer_attached = True
|
||||
|
||||
requires_seat = Exists(
|
||||
SeatCategoryMapping.objects.filter(
|
||||
Q(product=OuterRef('item'))
|
||||
@@ -1018,7 +1041,7 @@ def _perform_order(event: Event, payment_requests: List[dict], position_ids: Lis
|
||||
locale=locale,
|
||||
invoice_address=addr,
|
||||
meta_info=meta_info,
|
||||
customer=customer,
|
||||
customer=customer if not customer_attached else None,
|
||||
)
|
||||
|
||||
lockfn = NoLockManager
|
||||
@@ -1029,6 +1052,9 @@ def _perform_order(event: Event, payment_requests: List[dict], position_ids: Lis
|
||||
locked = True
|
||||
lockfn = event.lock
|
||||
|
||||
warnings = []
|
||||
any_payment_failed = False
|
||||
|
||||
with lockfn() as now_dt:
|
||||
positions = list(
|
||||
positions.select_related('item', 'variation', 'subevent', 'seat', 'addon_to').prefetch_related('addons')
|
||||
@@ -1038,29 +1064,57 @@ def _perform_order(event: Event, payment_requests: List[dict], position_ids: Lis
|
||||
raise OrderError(error_messages['empty'])
|
||||
if len(position_ids) != len(positions):
|
||||
raise OrderError(error_messages['internal'])
|
||||
_check_positions(event, now_dt, positions, address=addr, sales_channel=sales_channel, customer=customer)
|
||||
_check_positions(event, now_dt, positions, address=addr, sales_channel=sales_channel,
|
||||
customer=customer if not customer_attached else None)
|
||||
order, payment_objs = _create_order(event, email, positions, now_dt, payment_requests,
|
||||
locale=locale, address=addr, meta_info=meta_info, sales_channel=sales_channel,
|
||||
shown_total=shown_total, customer=customer)
|
||||
shown_total=shown_total, customer=customer, customer_attached=customer_attached)
|
||||
try:
|
||||
for p in payment_objs:
|
||||
if p.provider == 'free':
|
||||
p.confirm(send_mail=False, lock=not locked, generate_invoice=False)
|
||||
except Quota.QuotaExceededException:
|
||||
pass
|
||||
|
||||
free_order_flow = (
|
||||
payment_objs and
|
||||
any(p['provider'] == 'free' for p in payment_requests) and
|
||||
order.pending_sum == Decimal('0.00') and
|
||||
not order.require_approval
|
||||
)
|
||||
if free_order_flow:
|
||||
# We give special treatment to GiftCardPayment here because our invoice renderer expects gift cards to already be
|
||||
# processed, and because we historically treat gift card orders like free orders with regards to email texts.
|
||||
# It would be great to give external gift card plugins the same special treatment, but it feels to risky for now, as
|
||||
# (a) there would be no email at all if the plugin fails in a weird way and (b) we'd be able to run into
|
||||
# contradictions when a plugin set both execute_payment_needs_user=False as well as requires_invoice_immediately=True
|
||||
for p in payment_objs:
|
||||
if isinstance(p.payment_provider, GiftCardPayment):
|
||||
try:
|
||||
for p in payment_objs:
|
||||
if p.provider == 'free':
|
||||
p.confirm(send_mail=False, lock=not locked)
|
||||
except Quota.QuotaExceededException:
|
||||
pass
|
||||
p.process_initiated = True
|
||||
p.save(update_fields=['process_initiated'])
|
||||
p.payment_provider.execute_payment(None, p, is_early_special_case=True)
|
||||
except PaymentException as e:
|
||||
warnings.append(str(e))
|
||||
any_payment_failed = True
|
||||
except Exception:
|
||||
logger.exception('Error during payment attempt')
|
||||
|
||||
pending_sum = order.pending_sum
|
||||
free_order_flow = (
|
||||
payment_objs and
|
||||
(
|
||||
any(p['provider'] == 'free' for p in payment_requests) or
|
||||
all(p['provider'] == 'giftcard' for p in payment_requests)
|
||||
) and
|
||||
pending_sum == Decimal('0.00') and
|
||||
not order.require_approval
|
||||
)
|
||||
|
||||
invoice = order.invoices.last() # Might be generated by plugin already
|
||||
if not invoice and invoice_qualified(order):
|
||||
if event.settings.get('invoice_generate') == 'True' or (
|
||||
event.settings.get('invoice_generate') == 'paid' and any(p['pprov'].requires_invoice_immediately for p in payment_requests)):
|
||||
invoice_required = (
|
||||
event.settings.get('invoice_generate') == 'True' or (
|
||||
event.settings.get('invoice_generate') == 'paid' and (
|
||||
any(p['pprov'].requires_invoice_immediately for p in payment_requests) or
|
||||
pending_sum <= Decimal('0.00')
|
||||
)
|
||||
)
|
||||
)
|
||||
if invoice_required:
|
||||
invoice = generate_invoice(
|
||||
order,
|
||||
trigger_pdf=not event.settings.invoice_email_attachment or not order.email
|
||||
@@ -1100,23 +1154,22 @@ def _perform_order(event: Event, payment_requests: List[dict], position_ids: Lis
|
||||
_order_placed_email_attendee(event, order, p, email_attendees_template, subject_attendees_template, log_entry,
|
||||
is_free=free_order_flow)
|
||||
|
||||
warnings = []
|
||||
any_failed = False
|
||||
for p in payment_objs:
|
||||
if not p.payment_provider.execute_payment_needs_user:
|
||||
try:
|
||||
p.process_initiated = True
|
||||
p.save(update_fields=['process_initiated'])
|
||||
resp = p.payment_provider.execute_payment(None, p)
|
||||
if isinstance(resp, str):
|
||||
logger.warning('Payment provider returned URL from execute_payment even though execute_payment_needs_user is not set')
|
||||
except PaymentException as e:
|
||||
warnings.append(str(e))
|
||||
any_failed = True
|
||||
except Exception:
|
||||
logger.exception('Error during payment attempt')
|
||||
if not any_payment_failed:
|
||||
for p in payment_objs:
|
||||
if not p.payment_provider.execute_payment_needs_user and not p.process_initiated:
|
||||
try:
|
||||
p.process_initiated = True
|
||||
p.save(update_fields=['process_initiated'])
|
||||
resp = p.payment_provider.execute_payment(None, p)
|
||||
if isinstance(resp, str):
|
||||
logger.warning('Payment provider returned URL from execute_payment even though execute_payment_needs_user is not set')
|
||||
except PaymentException as e:
|
||||
warnings.append(str(e))
|
||||
any_payment_failed = True
|
||||
except Exception:
|
||||
logger.exception('Error during payment attempt')
|
||||
|
||||
if any_failed:
|
||||
if any_payment_failed:
|
||||
# Cancel all other payments because their amount might be wrong now.
|
||||
for p in payment_objs:
|
||||
if p.state == OrderPayment.PAYMENT_STATE_CREATED:
|
||||
|
||||
@@ -159,13 +159,37 @@ DEFAULTS = {
|
||||
},
|
||||
'customer_accounts_link_by_email': {
|
||||
'default': 'False',
|
||||
'type': bool,
|
||||
'form_class': forms.BooleanField,
|
||||
'serializer_class': serializers.BooleanField,
|
||||
'type': str,
|
||||
'form_class': forms.ChoiceField,
|
||||
'serializer_class': serializers.ChoiceField,
|
||||
'serializer_kwargs': dict(
|
||||
choices=(
|
||||
('False', _('Keep guest orders separate and do not link them to customer accounts.')),
|
||||
('True', _('Do not attach guest orders to customer accounts, but show them in customer '
|
||||
'accounts with a verified matching email address.')),
|
||||
('attach', _('Attach guest orders to existing customer accounts with a matching email address if such '
|
||||
'an account exists.')),
|
||||
('create', _('Attach guest orders to existing customer accounts with a matching email and '
|
||||
'automatically create a customer account for all others.')),
|
||||
('forbidden', _('Do not allow guest orders.')),
|
||||
),
|
||||
),
|
||||
'form_kwargs': dict(
|
||||
label=_("Match orders based on email address"),
|
||||
help_text=_("This will allow registered customers to access orders made with the same email address, even if the customer "
|
||||
"was not logged in during the purchase.")
|
||||
label=_("Guest order handling"),
|
||||
widget=forms.RadioSelect,
|
||||
choices=(
|
||||
('False', _('Keep guest orders separate and do not link them to customer accounts.')),
|
||||
('True', _('Do not attach guest orders to customer accounts, but show them in customer '
|
||||
'accounts with a verified matching email address.')),
|
||||
('attach', _('Attach guest orders to existing customer accounts with a matching email address if '
|
||||
'such an account exists.')),
|
||||
('create', _('Attach guest orders to existing customer accounts with a matching email address and '
|
||||
'automatically create a customer account for all others.')),
|
||||
('forbidden', _('Do not allow guest orders.')),
|
||||
),
|
||||
help_text=_('Please be aware that creating customer accounts without a users consent might not be a '
|
||||
'good idea for privacy reasons in some jurisdictions or situations. We always recommend to '
|
||||
'let the user choose if they want an account.'),
|
||||
)
|
||||
},
|
||||
'max_items_per_order': {
|
||||
|
||||
@@ -28,7 +28,7 @@ from django.urls import reverse
|
||||
|
||||
|
||||
def _is_samesite_referer(request):
|
||||
referer = request.META.get('HTTP_REFERER')
|
||||
referer = request.headers.get('referer')
|
||||
if referer is None:
|
||||
return False
|
||||
|
||||
|
||||
@@ -77,6 +77,31 @@ def get_all_payment_providers():
|
||||
if PAYMENT_PROVIDERS:
|
||||
return PAYMENT_PROVIDERS
|
||||
|
||||
class FakeSettings:
|
||||
def __init__(self, orig_settings):
|
||||
self.orig_settings = orig_settings
|
||||
|
||||
def set(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def __getattr__(self, item):
|
||||
return getattr(self.orig_settings, item)
|
||||
|
||||
class FakeEvent:
|
||||
def __init__(self, orig_event):
|
||||
self.orig_event = orig_event
|
||||
|
||||
@property
|
||||
def settings(self):
|
||||
return FakeSettings(self.orig_event.settings)
|
||||
|
||||
def __getattr__(self, item):
|
||||
return getattr(self.orig_event, item)
|
||||
|
||||
@property
|
||||
def __class__(self): # hackhack
|
||||
return Event
|
||||
|
||||
with rolledback_transaction():
|
||||
event = Event.objects.create(
|
||||
plugins=",".join([app.name for app in apps.get_app_configs()]),
|
||||
@@ -84,6 +109,7 @@ def get_all_payment_providers():
|
||||
date_from=now(),
|
||||
organizer=Organizer.objects.create(name="INTERNAL")
|
||||
)
|
||||
event = FakeEvent(event)
|
||||
provs = register_payment_providers.send(
|
||||
sender=event
|
||||
)
|
||||
@@ -766,6 +792,12 @@ class OrderSearchFilterForm(OrderFilterForm):
|
||||
)
|
||||
)
|
||||
|
||||
def use_query_hack(self):
|
||||
return (
|
||||
self.cleaned_data.get('query') or
|
||||
self.cleaned_data.get('status') in ('overpaid', 'partially_paid', 'underpaid', 'pendingpaid')
|
||||
)
|
||||
|
||||
def filter_qs(self, qs):
|
||||
fdata = self.cleaned_data
|
||||
qs = super().filter_qs(qs)
|
||||
|
||||
@@ -44,6 +44,7 @@ from django.core.files.uploadedfile import UploadedFile
|
||||
from django.db.models import Max
|
||||
from django.forms.formsets import DELETION_FIELD_NAME
|
||||
from django.urls import reverse
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.html import escape
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.translation import (
|
||||
@@ -436,12 +437,21 @@ class ItemCreateForm(I18nModelForm):
|
||||
v.pk = None
|
||||
v.item = instance
|
||||
v.save()
|
||||
for mv in variation.meta_values.all():
|
||||
mv.pk = None
|
||||
mv.variation = v
|
||||
mv.save(force_insert=True)
|
||||
else:
|
||||
ItemVariation.objects.create(
|
||||
item=instance, value=__('Standard')
|
||||
)
|
||||
|
||||
if self.cleaned_data.get('copy_from'):
|
||||
for mv in self.cleaned_data['copy_from'].meta_values.all():
|
||||
mv.pk = None
|
||||
mv.item = instance
|
||||
mv.save(force_insert=True)
|
||||
|
||||
for question in self.cleaned_data['copy_from'].questions.all():
|
||||
question.items.add(instance)
|
||||
question.log_action('pretix.event.question.changed', user=self.user, data={
|
||||
@@ -727,6 +737,31 @@ class ItemVariationForm(I18nModelForm):
|
||||
del self.fields['require_membership']
|
||||
del self.fields['require_membership_types']
|
||||
|
||||
self.meta_fields = []
|
||||
meta_defaults = {}
|
||||
if self.instance.pk:
|
||||
for mv in self.instance.meta_values.all():
|
||||
meta_defaults[mv.property_id] = mv.value
|
||||
for p in self.meta_properties:
|
||||
self.initial[f'meta_{p.name}'] = meta_defaults.get(p.pk)
|
||||
self.fields[f'meta_{p.name}'] = forms.CharField(
|
||||
label=p.name,
|
||||
widget=forms.TextInput(
|
||||
attrs={
|
||||
'placeholder': _('Use value from product'),
|
||||
'data-typeahead-url': reverse('control:event.items.meta.typeahead', kwargs={
|
||||
'organizer': self.event.organizer.slug,
|
||||
'event': self.event.slug
|
||||
}) + '?' + urlencode({
|
||||
'property': p.name,
|
||||
}),
|
||||
},
|
||||
),
|
||||
required=False,
|
||||
|
||||
)
|
||||
self.meta_fields.append(f'meta_{p.name}')
|
||||
|
||||
class Meta:
|
||||
model = ItemVariation
|
||||
localized_fields = '__all__'
|
||||
@@ -757,6 +792,26 @@ class ItemVariationForm(I18nModelForm):
|
||||
}),
|
||||
}
|
||||
|
||||
def save(self, commit=True):
|
||||
instance = super().save(commit)
|
||||
self.meta_fields = []
|
||||
current_values = {v.property_id: v for v in instance.meta_values.all()}
|
||||
for p in self.meta_properties:
|
||||
if self.cleaned_data[f'meta_{p.name}']:
|
||||
if p.pk in current_values:
|
||||
current_values[p.pk].value = self.cleaned_data[f'meta_{p.name}']
|
||||
current_values[p.pk].save()
|
||||
else:
|
||||
instance.meta_values.create(property=p, value=self.cleaned_data[f'meta_{p.name}'])
|
||||
elif p.pk in current_values:
|
||||
current_values[p.pk].delete()
|
||||
|
||||
@property
|
||||
def meta_properties(self):
|
||||
if not hasattr(self.event, '_cached_item_meta_properties'):
|
||||
self.event._cached_item_meta_properties = self.event.item_meta_properties.all()
|
||||
return self.event._cached_item_meta_properties
|
||||
|
||||
|
||||
class ItemAddOnsFormSet(I18nFormSet):
|
||||
title = _('Add-ons')
|
||||
@@ -845,6 +900,7 @@ class ItemBundleFormSet(I18nFormSet):
|
||||
def _construct_form(self, i, **kwargs):
|
||||
kwargs['event'] = self.event
|
||||
kwargs['item'] = self.item
|
||||
kwargs['item_qs'] = self.item_qs
|
||||
return super()._construct_form(i, **kwargs)
|
||||
|
||||
@property
|
||||
@@ -856,12 +912,17 @@ class ItemBundleFormSet(I18nFormSet):
|
||||
empty_permitted=True,
|
||||
use_required_attribute=False,
|
||||
locales=self.locales,
|
||||
item_qs=self.item_qs,
|
||||
item=self.item,
|
||||
event=self.event
|
||||
)
|
||||
self.add_fields(form, None)
|
||||
return form
|
||||
|
||||
@cached_property
|
||||
def item_qs(self):
|
||||
return self.event.items.prefetch_related('variations').all()
|
||||
|
||||
def clean(self):
|
||||
super().clean()
|
||||
ivs = set()
|
||||
@@ -889,6 +950,7 @@ class ItemBundleForm(I18nModelForm):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.item = kwargs.pop('item')
|
||||
self.item_qs = kwargs.pop('item_qs')
|
||||
super().__init__(*args, **kwargs)
|
||||
instance = kwargs.get('instance', None)
|
||||
initial = kwargs.get('initial', {})
|
||||
@@ -906,7 +968,7 @@ class ItemBundleForm(I18nModelForm):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
choices = []
|
||||
for i in self.event.items.prefetch_related('variations').all():
|
||||
for i in self.item_qs:
|
||||
pname = str(i)
|
||||
if not i.is_available():
|
||||
pname += ' ({})'.format(_('inactive'))
|
||||
|
||||
@@ -331,7 +331,10 @@ def pretixcontrol_logentry_display(sender: Event, logentry: LogEntry, **kwargs):
|
||||
'pretix.membershiptype.changed': _('The membership type has been changed.'),
|
||||
'pretix.membershiptype.deleted': _('The membership type has been deleted.'),
|
||||
'pretix.customer.created': _('The account has been created.'),
|
||||
'pretix.customer.created.auto': _('The account has been automatically created during an order.'),
|
||||
'pretix.customer.claimed': _('The automatically created account has been claimed by the customer.'),
|
||||
'pretix.customer.changed': _('The account has been changed.'),
|
||||
'pretix.customer.order.attached': _('An order was automatically attached to the customer.'),
|
||||
'pretix.customer.membership.created': _('A membership for this account has been added.'),
|
||||
'pretix.customer.membership.changed': _('A membership of this account has been changed.'),
|
||||
'pretix.customer.membership.deleted': _('A membership of this account has been deleted.'),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{% load i18n %}
|
||||
{% load bootstrap3 %}
|
||||
{% load formset_tags %}
|
||||
{% load getitem %}
|
||||
<div class="formset" data-formset data-formset-prefix="{{ formset.prefix }}" id="item_variations">
|
||||
{{ formset.management_form }}
|
||||
{% bootstrap_formset_errors formset %}
|
||||
@@ -29,16 +30,20 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col-md-2 col-xs-6">
|
||||
<span class="fa fa-clock-o fa-fw text-muted variation-timeframe variation-icon-hidden" data-toggle="tooltip" title="{% trans "Only available in a limited timeframe" %}"></span>
|
||||
<span class="fa fa-tags fa-fw text-muted variation-voucher variation-icon-hidden" data-toggle="tooltip"
|
||||
title="{% trans "Only visible with a voucher" %}"></span>
|
||||
<span class="fa fa-id-badge fa-fw text-muted variation-membership variation-icon-hidden" data-toggle="tooltip"
|
||||
title="{% trans "Require a valid membership" %}"></span>
|
||||
<span class="fa fa-clock-o fa-fw text-muted variation-timeframe variation-icon-hidden"
|
||||
data-toggle="tooltip"
|
||||
title="{% trans "Only available in a limited timeframe" %}"></span>
|
||||
<span class="fa fa-tags fa-fw text-muted variation-voucher variation-icon-hidden"
|
||||
data-toggle="tooltip"
|
||||
title="{% trans "Only visible with a voucher" %}"></span>
|
||||
<span class="fa fa-id-badge fa-fw text-muted variation-membership variation-icon-hidden"
|
||||
data-toggle="tooltip"
|
||||
title="{% trans "Require a valid membership" %}"></span>
|
||||
</div>
|
||||
<div class="col-md-2 col-xs-6">
|
||||
{% for k, c in sales_channels.items %}
|
||||
<span class="fa fa-fw fa-{{ c.icon }} text-muted variation-channel-{{ k }} variation-icon-hidden"
|
||||
data-toggle="tooltip" title="{% trans c.verbose_name %}"></span>
|
||||
data-toggle="tooltip" title="{% trans c.verbose_name %}"></span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="col-md-1 col-xs-6 text-right flip variation-price">
|
||||
@@ -69,6 +74,27 @@
|
||||
{% bootstrap_field form.default_price addon_after=request.event.currency layout="control" %}
|
||||
{% bootstrap_field form.original_price addon_after=request.event.currency layout="control" %}
|
||||
{% bootstrap_field form.description layout="control" %}
|
||||
{% if form.meta_fields %}
|
||||
<div class="form-group metadata-group">
|
||||
<label class="col-md-3 control-label">{% trans "Meta data" %}</label>
|
||||
<div class="col-md-9">
|
||||
{% for fname in form.meta_fields %}
|
||||
{% with form|getitem:fname as field %}
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<label for="{{ field.id_for_label }}">
|
||||
{{ field.label }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
{% bootstrap_field field layout="inline" %}
|
||||
</div>
|
||||
</div>
|
||||
{% endwith %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% bootstrap_field form.available_from layout="control" %}
|
||||
{% bootstrap_field form.available_until layout="control" %}
|
||||
{% bootstrap_field form.sales_channels layout="control" %}
|
||||
@@ -110,16 +136,20 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col-md-2 col-xs-6">
|
||||
<span class="fa fa-clock-o fa-fw text-muted variation-timeframe variation-icon-hidden" data-toggle="tooltip" title="{% trans "Only available in a limited timeframe" %}"></span>
|
||||
<span class="fa fa-tags fa-fw text-muted variation-voucher variation-icon-hidden" data-toggle="tooltip"
|
||||
title="{% trans "Only visible with a voucher" %}"></span>
|
||||
<span class="fa fa-id-badge fa-fw text-muted variation-membership variation-icon-hidden" data-toggle="tooltip"
|
||||
title="{% trans "Require a valid membership" %}"></span>
|
||||
<span class="fa fa-clock-o fa-fw text-muted variation-timeframe variation-icon-hidden"
|
||||
data-toggle="tooltip"
|
||||
title="{% trans "Only available in a limited timeframe" %}"></span>
|
||||
<span class="fa fa-tags fa-fw text-muted variation-voucher variation-icon-hidden"
|
||||
data-toggle="tooltip"
|
||||
title="{% trans "Only visible with a voucher" %}"></span>
|
||||
<span class="fa fa-id-badge fa-fw text-muted variation-membership variation-icon-hidden"
|
||||
data-toggle="tooltip"
|
||||
title="{% trans "Require a valid membership" %}"></span>
|
||||
</div>
|
||||
<div class="col-md-2 col-xs-6">
|
||||
{% for k, c in sales_channels.items %}
|
||||
<span class="fa fa-fw fa-{{ c.icon }} text-muted variation-channel-{{ k }} variation-icon-hidden"
|
||||
data-toggle="tooltip" title="{% trans c.verbose_name %}"></span>
|
||||
data-toggle="tooltip" title="{% trans c.verbose_name %}"></span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="col-md-1 col-xs-6 text-right flip variation-price">
|
||||
@@ -141,6 +171,27 @@
|
||||
{% bootstrap_field formset.empty_form.default_price addon_after=request.event.currency layout="control" %}
|
||||
{% bootstrap_field formset.empty_form.original_price addon_after=request.event.currency layout="control" %}
|
||||
{% bootstrap_field formset.empty_form.description layout="control" %}
|
||||
{% if formset.empty_form.meta_fields %}
|
||||
<div class="form-group metadata-group">
|
||||
<label class="col-md-3 control-label">{% trans "Meta data" %}</label>
|
||||
<div class="col-md-9">
|
||||
{% for fname in formset.empty_form.meta_fields %}
|
||||
{% with formset.empty_form|getitem:fname as field %}
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<label for="{{ field.id_for_label }}">
|
||||
{{ field.label }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
{% bootstrap_field field layout="inline" %}
|
||||
</div>
|
||||
</div>
|
||||
{% endwith %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% bootstrap_field formset.empty_form.available_from layout="control" %}
|
||||
{% bootstrap_field formset.empty_form.available_until layout="control" %}
|
||||
{% bootstrap_field formset.empty_form.sales_channels layout="control" %}
|
||||
|
||||
@@ -22,54 +22,72 @@
|
||||
</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<form action="" method="post">
|
||||
<form action="" method="post" class="row">
|
||||
{% csrf_token %}
|
||||
<dl class="dl-horizontal">
|
||||
<dt>{% trans "Customer ID" %}</dt>
|
||||
<dd>#{{ customer.identifier }}</dd>
|
||||
{% if customer.provider %}
|
||||
<dt>{% trans "SSO provider" %}</dt>
|
||||
<dd>{{ customer.provider.name }}</dd>
|
||||
{% endif %}
|
||||
{% if customer.external_identifier %}
|
||||
<dt>{% trans "External identifier" %}</dt>
|
||||
<dd>{{ customer.external_identifier }}</dd>
|
||||
{% endif %}
|
||||
<dt>{% trans "Status" %}</dt>
|
||||
<dd>
|
||||
{% if not customer.is_active %}
|
||||
{% trans "disabled" %}
|
||||
{% elif not customer.is_verified %}
|
||||
{% trans "not yet activated" %}
|
||||
{% else %}
|
||||
{% trans "active" %}
|
||||
<dl class="dl-horizontal col-lg-6 col-sm-12">
|
||||
<dt>{% trans "Customer ID" %}</dt>
|
||||
<dd>#{{ customer.identifier }}</dd>
|
||||
{% if customer.provider %}
|
||||
<dt>{% trans "SSO provider" %}</dt>
|
||||
<dd>{{ customer.provider.name }}</dd>
|
||||
{% endif %}
|
||||
</dd>
|
||||
<dt>{% trans "E-mail" %}</dt>
|
||||
<dd>
|
||||
{{ customer.email|default_if_none:"" }}
|
||||
{% if customer.email and not customer.provider %}
|
||||
<button type="submit" name="action" value="pwreset" class="btn btn-xs btn-default">
|
||||
{% trans "Send password reset link" %}
|
||||
</button>
|
||||
{% if customer.external_identifier %}
|
||||
<dt>{% trans "External identifier" %}</dt>
|
||||
<dd>{{ customer.external_identifier }}</dd>
|
||||
{% endif %}
|
||||
</dd>
|
||||
<dt>{% trans "Name" %}</dt>
|
||||
<dd>{{ customer.name }}</dd>
|
||||
{% if customer.phone %}
|
||||
<dt>{% trans "Phone" %}</dt>
|
||||
<dd>{{ customer.phone }}</dd>
|
||||
{% endif %}
|
||||
<dt>{% trans "Locale" %}</dt>
|
||||
<dd>{{ display_locale }}</dd>
|
||||
<dt>{% trans "Registration date" %}</dt>
|
||||
<dd>{{ customer.date_joined|date:"SHORT_DATETIME_FORMAT" }}</dd>
|
||||
<dt>{% trans "Last login" %}</dt>
|
||||
<dd>{% if customer.last_login %}{{ customer.last_login|date:"SHORT_DATETIME_FORMAT" }}{% else %}
|
||||
–{% endif %}</dd>
|
||||
{% if customer.notes %}
|
||||
<dt>{% trans "Notes" %}</dt>
|
||||
<dd>{{ customer.notes|linebreaks }}</dd>
|
||||
<dt>{% trans "Status" %}</dt>
|
||||
<dd>
|
||||
{% if not customer.is_active %}
|
||||
{% trans "disabled" %}
|
||||
{% elif not customer.is_verified %}
|
||||
{% trans "not yet activated" %}
|
||||
{% else %}
|
||||
{% trans "active" %}
|
||||
{% endif %}
|
||||
</dd>
|
||||
<dt>{% trans "E-mail" %}</dt>
|
||||
<dd>
|
||||
{{ customer.email|default_if_none:"" }}
|
||||
{% if customer.email and not customer.provider %}
|
||||
<button type="submit" name="action" value="pwreset" class="btn btn-xs btn-default">
|
||||
{% trans "Send password reset link" %}
|
||||
</button>
|
||||
{% endif %}
|
||||
</dd>
|
||||
<dt>{% trans "Name" %}</dt>
|
||||
<dd>{{ customer.name }}</dd>
|
||||
{% if customer.phone %}
|
||||
<dt>{% trans "Phone" %}</dt>
|
||||
<dd>{{ customer.phone }}</dd>
|
||||
{% endif %}
|
||||
<dt>{% trans "Locale" %}</dt>
|
||||
<dd>{{ display_locale }}</dd>
|
||||
<dt>{% trans "Registration date" %}</dt>
|
||||
<dd>{{ customer.date_joined|date:"SHORT_DATETIME_FORMAT" }}</dd>
|
||||
<dt>{% trans "Last login" %}</dt>
|
||||
<dd>{% if customer.last_login %}{{ customer.last_login|date:"SHORT_DATETIME_FORMAT" }}{% else %}
|
||||
–{% endif %}</dd>
|
||||
{% if customer.notes %}
|
||||
<dt>{% trans "Notes" %}</dt>
|
||||
<dd>{{ customer.notes|linebreaks }}</dd>
|
||||
{% endif %}
|
||||
</dl>
|
||||
<dl class="col-lg-6 col-sm-12 text-right">
|
||||
<dt class="text-muted"
|
||||
data-toggle="tooltip"
|
||||
title="{% trans "This includes all paid orders by this customer across all your events." %}">
|
||||
{% trans "Lifetime spending" %}
|
||||
</dt>
|
||||
{% if lifetime_spending %}
|
||||
{% for s in lifetime_spending %}
|
||||
{% if s.spending >= 0 %}
|
||||
<dd class="text-success text-h3">{{ s.spending|money:s.currency }}</dd>
|
||||
{% elif s.spending < 0 %}
|
||||
<dd class="text-error text-h3">{{ s.spending|money:s.currency }}</dd>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<dd class="text-muted text-h3">{{ 0|floatformat:2 }}</dd>
|
||||
{% endif %}
|
||||
</dl>
|
||||
</form>
|
||||
|
||||
@@ -79,9 +79,9 @@
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
{% if not c.is_verified %}<strike>{% endif %}
|
||||
{% if not c.is_verified %}<span class="text-muted">{% endif %}
|
||||
{{ c.email|default_if_none:"" }}
|
||||
{% if not c.is_verified %}</strike>{% endif %}
|
||||
{% if not c.is_verified %}</span>{% endif %}
|
||||
</td>
|
||||
<td>{{ c.name }}</td>
|
||||
<td>{% if c.external_identifier %}{{ c.external_identifier }}{% endif %}</td>
|
||||
|
||||
@@ -33,7 +33,8 @@
|
||||
# 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.conf.urls import include, re_path
|
||||
from django.conf.urls import re_path
|
||||
from django.urls import include
|
||||
from django.views.generic.base import RedirectView
|
||||
|
||||
from pretix.control.views import (
|
||||
|
||||
@@ -353,6 +353,12 @@ class Recover(TemplateView):
|
||||
user.save()
|
||||
messages.success(request, _('You can now login using your new password.'))
|
||||
user.log_action('pretix.control.auth.user.forgot_password.recovered')
|
||||
|
||||
has_redis = settings.HAS_REDIS
|
||||
if has_redis:
|
||||
from django_redis import get_redis_connection
|
||||
rc = get_redis_connection("redis")
|
||||
rc.delete('pretix_pwreset_%s' % user.id)
|
||||
return redirect('control:auth.login')
|
||||
else:
|
||||
return self.get(request, *args, **kwargs)
|
||||
|
||||
@@ -51,7 +51,7 @@ from django.utils import formats
|
||||
from django.utils.formats import date_format
|
||||
from django.utils.html import escape
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import gettext_lazy as _, pgettext, ungettext
|
||||
from django.utils.translation import gettext_lazy as _, ngettext, pgettext
|
||||
|
||||
from pretix.base.decimal import round_decimal
|
||||
from pretix.base.models import (
|
||||
@@ -555,7 +555,7 @@ def widgets_for_event_qs(request, qs, user, nmax, lazy=False):
|
||||
'event': event.slug,
|
||||
'organizer': event.organizer.slug
|
||||
}),
|
||||
orders_text=ungettext('{num} order', '{num} orders', event.order_count or 0).format(
|
||||
orders_text=ngettext('{num} order', '{num} orders', event.order_count or 0).format(
|
||||
num=event.order_count or 0
|
||||
)
|
||||
) if user.has_active_staff_session(request.session.session_key) or event.pk in events_with_orders else ''
|
||||
|
||||
@@ -93,8 +93,8 @@ from ...base.i18n import language
|
||||
from ...base.models.items import (
|
||||
Item, ItemCategory, ItemMetaProperty, Question, Quota,
|
||||
)
|
||||
from ...base.services.mail import TolerantDict
|
||||
from ...base.settings import SETTINGS_AFFECTING_CSS, LazyI18nStringList
|
||||
from ...helpers.format import format_map
|
||||
from ..logdisplay import OVERVIEW_BANLIST
|
||||
from . import CreateView, PaginationMixin, UpdateView
|
||||
|
||||
@@ -734,10 +734,10 @@ class MailSettingsPreview(EventPermissionRequiredMixin, View):
|
||||
if idx in self.supported_locale:
|
||||
with language(self.supported_locale[idx], self.request.event.settings.region):
|
||||
if k.startswith('mail_subject_'):
|
||||
msgs[self.supported_locale[idx]] = bleach.clean(v).format_map(self.placeholders(preview_item))
|
||||
msgs[self.supported_locale[idx]] = format_map(bleach.clean(v), self.placeholders(preview_item))
|
||||
else:
|
||||
msgs[self.supported_locale[idx]] = markdown_compile_email(
|
||||
v.format_map(self.placeholders(preview_item))
|
||||
format_map(v, self.placeholders(preview_item))
|
||||
)
|
||||
|
||||
return JsonResponse({
|
||||
@@ -761,7 +761,7 @@ class MailSettingsRendererPreview(MailSettingsPreview):
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
v = str(request.event.settings.mail_text_order_placed)
|
||||
v = v.format_map(TolerantDict(self.placeholders('mail_text_order_placed')))
|
||||
v = format_map(v, self.placeholders('mail_text_order_placed'))
|
||||
renderers = request.event.get_html_mail_renderers()
|
||||
if request.GET.get('renderer') in renderers:
|
||||
with rolledback_transaction():
|
||||
|
||||
@@ -1426,7 +1426,7 @@ class ItemUpdateGeneral(ItemDetailMixin, EventPermissionRequiredMixin, MetaDataE
|
||||
can_order=True, can_delete=True, extra=0
|
||||
)(
|
||||
self.request.POST if self.request.method == "POST" else None,
|
||||
queryset=ItemVariation.objects.filter(item=self.get_object()),
|
||||
queryset=ItemVariation.objects.filter(item=self.get_object()).prefetch_related('meta_values', 'require_membership_types'),
|
||||
event=self.request.event, prefix="variations"
|
||||
)),
|
||||
('addons', inlineformset_factory(
|
||||
|
||||
@@ -65,6 +65,10 @@ class OAuthApplicationRegistrationView(ApplicationRegistration):
|
||||
def form_valid(self, form):
|
||||
form.instance.client_type = 'confidential'
|
||||
form.instance.authorization_grant_type = 'authorization-code'
|
||||
secret = generate_client_secret()
|
||||
messages.success(self.request, _('Your application has been created and an application secret has been generated. '
|
||||
'Please copy and save it right now as it will not be shown again: {secret}').format(secret=secret))
|
||||
form.instance.client_secret = secret
|
||||
oauth_application_registered.send(
|
||||
sender=self.request, user=self.request.user, application=form.instance
|
||||
)
|
||||
@@ -74,18 +78,14 @@ class OAuthApplicationRegistrationView(ApplicationRegistration):
|
||||
class ApplicationUpdateForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = OAuthApplication
|
||||
fields = ("name", "client_id", "client_secret", "redirect_uris")
|
||||
fields = ("name", "client_id", "redirect_uris")
|
||||
|
||||
def clean_client_id(self):
|
||||
return self.instance.client_id
|
||||
|
||||
def clean_client_secret(self):
|
||||
return self.instance.client_secret
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['client_id'].widget.attrs['readonly'] = True
|
||||
self.fields['client_secret'].widget.attrs['readonly'] = True
|
||||
|
||||
|
||||
class OAuthApplicationUpdateView(ApplicationUpdate):
|
||||
@@ -103,8 +103,10 @@ class OAuthApplicationRollView(ApplicationDetail):
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
self.object = self.get_object()
|
||||
messages.success(request, _('A new client secret has been generated and is now effective.'))
|
||||
self.object.client_secret = generate_client_secret()
|
||||
secret = generate_client_secret()
|
||||
messages.success(request, _('A new client secret has been generated. Please copy and save it right now as '
|
||||
'it will not be shown again: {secret}').format(secret=secret))
|
||||
self.object.client_secret = secret
|
||||
self.object.save()
|
||||
return HttpResponseRedirect(self.object.get_absolute_url())
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ from django.urls import reverse
|
||||
from django.utils import formats
|
||||
from django.utils.formats import date_format, get_format
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.http import is_safe_url
|
||||
from django.utils.http import url_has_allowed_host_and_scheme
|
||||
from django.utils.timezone import make_aware, now
|
||||
from django.utils.translation import gettext, gettext_lazy as _, ngettext
|
||||
from django.views.generic import (
|
||||
@@ -93,9 +93,7 @@ from pretix.base.services.invoices import (
|
||||
invoice_qualified, regenerate_invoice,
|
||||
)
|
||||
from pretix.base.services.locking import LockTimeoutException
|
||||
from pretix.base.services.mail import (
|
||||
SendMailException, TolerantDict, render_mail,
|
||||
)
|
||||
from pretix.base.services.mail import SendMailException, render_mail
|
||||
from pretix.base.services.orders import (
|
||||
OrderChangeManager, OrderError, approve_order, cancel_order, deny_order,
|
||||
extend_order, mark_order_expired, mark_order_refunded,
|
||||
@@ -127,6 +125,7 @@ from pretix.control.forms.orders import (
|
||||
from pretix.control.permissions import EventPermissionRequiredMixin
|
||||
from pretix.control.signals import order_search_forms
|
||||
from pretix.control.views import PaginationMixin
|
||||
from pretix.helpers.format import format_map
|
||||
from pretix.helpers.safedownload import check_token
|
||||
from pretix.presale.signals import question_form_fields
|
||||
|
||||
@@ -681,7 +680,7 @@ class OrderRefundCancel(OrderView):
|
||||
messages.success(self.request, _('The refund has been canceled.'))
|
||||
else:
|
||||
messages.error(self.request, _('This refund can not be canceled at the moment.'))
|
||||
if "next" in self.request.GET and is_safe_url(self.request.GET.get("next"), allowed_hosts=None):
|
||||
if "next" in self.request.GET and url_has_allowed_host_and_scheme(self.request.GET.get("next"), allowed_hosts=None):
|
||||
return redirect(self.request.GET.get("next"))
|
||||
return redirect(self.get_order_url())
|
||||
|
||||
@@ -717,7 +716,7 @@ class OrderRefundProcess(OrderView):
|
||||
messages.success(self.request, _('The refund has been processed.'))
|
||||
else:
|
||||
messages.error(self.request, _('This refund can not be processed at the moment.'))
|
||||
if "next" in self.request.GET and is_safe_url(self.request.GET.get("next"), allowed_hosts=None):
|
||||
if "next" in self.request.GET and url_has_allowed_host_and_scheme(self.request.GET.get("next"), allowed_hosts=None):
|
||||
return redirect(self.request.GET.get("next"))
|
||||
return redirect(self.get_order_url())
|
||||
|
||||
@@ -743,7 +742,7 @@ class OrderRefundDone(OrderView):
|
||||
messages.success(self.request, _('The refund has been marked as done.'))
|
||||
else:
|
||||
messages.error(self.request, _('This refund can not be processed at the moment.'))
|
||||
if "next" in self.request.GET and is_safe_url(self.request.GET.get("next"), allowed_hosts=None):
|
||||
if "next" in self.request.GET and url_has_allowed_host_and_scheme(self.request.GET.get("next"), allowed_hosts=None):
|
||||
return redirect(self.request.GET.get("next"))
|
||||
return redirect(self.get_order_url())
|
||||
|
||||
@@ -2032,7 +2031,7 @@ class OrderSendMail(EventPermissionRequiredMixin, OrderViewMixin, FormView):
|
||||
with language(order.locale, self.request.event.settings.region):
|
||||
email_context = get_email_context(event=order.event, order=order)
|
||||
email_template = LazyI18nString(form.cleaned_data['message'])
|
||||
email_subject = str(form.cleaned_data['subject']).format_map(TolerantDict(email_context))
|
||||
email_subject = format_map(str(form.cleaned_data['subject']), email_context)
|
||||
email_content = render_mail(email_template, email_context)
|
||||
if self.request.POST.get('action') == 'preview':
|
||||
self.preview_output = {
|
||||
@@ -2097,7 +2096,7 @@ class OrderPositionSendMail(OrderSendMail):
|
||||
with language(position.order.locale, self.request.event.settings.region):
|
||||
email_context = get_email_context(event=position.order.event, order=position.order, position=position)
|
||||
email_template = LazyI18nString(form.cleaned_data['message'])
|
||||
email_subject = str(form.cleaned_data['subject']).format_map(TolerantDict(email_context))
|
||||
email_subject = format_map(str(form.cleaned_data['subject']), email_context)
|
||||
email_content = render_mail(email_template, email_context)
|
||||
if self.request.POST.get('action') == 'preview':
|
||||
self.preview_output = {
|
||||
|
||||
@@ -45,8 +45,8 @@ from django.core.exceptions import PermissionDenied, ValidationError
|
||||
from django.core.files import File
|
||||
from django.db import connections, transaction
|
||||
from django.db.models import (
|
||||
Count, Exists, IntegerField, Max, Min, OuterRef, Prefetch, ProtectedError,
|
||||
Q, Subquery, Sum,
|
||||
Count, Exists, F, IntegerField, Max, Min, OuterRef, Prefetch,
|
||||
ProtectedError, Q, Subquery, Sum,
|
||||
)
|
||||
from django.db.models.functions import Coalesce, Greatest
|
||||
from django.forms import DecimalField
|
||||
@@ -109,6 +109,7 @@ from pretix.control.views import PaginationMixin
|
||||
from pretix.control.views.mailsetup import MailSettingsSetupView
|
||||
from pretix.helpers import GroupConcat
|
||||
from pretix.helpers.dicts import merge_dicts
|
||||
from pretix.helpers.format import format_map
|
||||
from pretix.helpers.urls import build_absolute_uri as build_global_uri
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||
from pretix.presale.forms.customer import TokenGenerator
|
||||
@@ -335,10 +336,10 @@ class MailSettingsPreview(OrganizerPermissionRequiredMixin, View):
|
||||
if idx in self.supported_locale:
|
||||
with language(self.supported_locale[idx], self.request.organizer.settings.region):
|
||||
if k.startswith('mail_subject_'):
|
||||
msgs[self.supported_locale[idx]] = bleach.clean(v).format_map(self.placeholders(preview_item))
|
||||
msgs[self.supported_locale[idx]] = format_map(bleach.clean(v), self.placeholders(preview_item))
|
||||
else:
|
||||
msgs[self.supported_locale[idx]] = markdown_compile_email(
|
||||
v.format_map(self.placeholders(preview_item))
|
||||
format_map(v, self.placeholders(preview_item))
|
||||
)
|
||||
|
||||
return JsonResponse({
|
||||
@@ -1403,7 +1404,7 @@ class GiftCardDetailView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMi
|
||||
messages.error(request, _('The transaction could not be reversed.'))
|
||||
else:
|
||||
messages.success(request, _('The transaction has been reversed.'))
|
||||
elif 'value' in request.POST:
|
||||
elif request.POST.get('value'):
|
||||
try:
|
||||
value = DecimalField(localize=True).to_python(request.POST.get('value'))
|
||||
except ValidationError:
|
||||
@@ -2214,9 +2215,9 @@ class CustomerDetailView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMi
|
||||
|
||||
def get_queryset(self):
|
||||
q = Q(customer=self.customer)
|
||||
if self.request.organizer.settings.customer_accounts_link_by_email and self.customer.email:
|
||||
if self.request.organizer.settings.customer_accounts_link_by_email == 'True' and self.customer.email:
|
||||
# This is safe because we only let customers with verified emails log in
|
||||
q |= Q(email__iexact=self.customer.email)
|
||||
q |= Q(customer__isnull=True, email__iexact=self.customer.email)
|
||||
qs = Order.objects.filter(
|
||||
q
|
||||
).select_related('event').order_by('-datetime')
|
||||
@@ -2311,6 +2312,14 @@ class CustomerDetailView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMi
|
||||
o.icnt = annotated.get(o.pk)['icnt']
|
||||
o.sales_channel_obj = scs[o.sales_channel]
|
||||
|
||||
ctx["lifetime_spending"] = (
|
||||
self.get_queryset()
|
||||
.filter(status=Order.STATUS_PAID)
|
||||
.values(currency=F("event__currency"))
|
||||
.order_by("currency")
|
||||
.annotate(spending=Sum("total"))
|
||||
)
|
||||
|
||||
return ctx
|
||||
|
||||
|
||||
|
||||
@@ -156,8 +156,8 @@ class BaseEditorView(EventPermissionRequiredMixin, TemplateView):
|
||||
p = PdfWriter()
|
||||
try:
|
||||
p.add_blank_page(
|
||||
width=float(request.POST.get('width')) * mm,
|
||||
height=float(request.POST.get('height')) * mm,
|
||||
width=Decimal('%.5f' % (float(request.POST.get('width')) * mm)),
|
||||
height=Decimal('%.5f' % (float(request.POST.get('height')) * mm)),
|
||||
)
|
||||
except ValueError:
|
||||
return JsonResponse({
|
||||
|
||||
@@ -49,7 +49,7 @@ class OrderSearch(PaginationMixin, ListView):
|
||||
self.filter_form[k] for k in self.filter_form.fields if k.startswith('meta_')
|
||||
]
|
||||
|
||||
# Only compute this annotations for this page (query optimization)
|
||||
# Only compute these annotations for this page (query optimization)
|
||||
s = OrderPosition.objects.filter(
|
||||
order=OuterRef('pk')
|
||||
).order_by().values('order').annotate(k=Count('id')).values('k')
|
||||
@@ -91,7 +91,7 @@ class OrderSearch(PaginationMixin, ListView):
|
||||
if self.filter_form.is_valid():
|
||||
qs = self.filter_form.filter_qs(qs)
|
||||
|
||||
if self.filter_form.cleaned_data.get('query'):
|
||||
if self.filter_form.use_query_hack():
|
||||
"""
|
||||
We need to work around a bug in PostgreSQL's (and likely MySQL's) query plan optimizer here.
|
||||
The database lacks statistical data to predict how common our search filter is and therefore
|
||||
@@ -100,8 +100,14 @@ class OrderSearch(PaginationMixin, ListView):
|
||||
look for something rare (such as an email address used once within hundreds of thousands of
|
||||
orders, this ends up to be pathologically slow.
|
||||
|
||||
Generally, PostgreSQL tries to make these decisions on statistical data and generally, they *can*
|
||||
only be made on statistical data, so it's a little bit of a stretch that we try to do it better
|
||||
than PostgreSQL here. However, experience suggests applying this tricks works specifically in the
|
||||
cases where the WHERE part of the statement is very hard to compute, e.g. uses a complicated
|
||||
condition that can't utilize indices well.
|
||||
|
||||
For some search queries on pretix.eu, we see search times of >30s, just due to the ORDER BY and
|
||||
LIMIT clause. Without them. the query runs in roughly 0.6s. This heuristical approach tries to
|
||||
LIMIT clause. Without them. the query runs in roughly 0.6s. This heuristic approach tries to
|
||||
detect these cases and rewrite the query as a nested subquery that strongly suggests sorting
|
||||
before filtering. However, since even that fails in some cases because PostgreSQL thinks it knows
|
||||
better, we literally force it by evaluating the subquery explicitly. We only do this for n<=200,
|
||||
@@ -111,15 +117,8 @@ class OrderSearch(PaginationMixin, ListView):
|
||||
|
||||
Phew.
|
||||
"""
|
||||
|
||||
page = self.kwargs.get(self.page_kwarg) or self.request.GET.get(self.page_kwarg) or 1
|
||||
limit = self.get_paginate_by(None)
|
||||
try:
|
||||
offset = (int(page) - 1) * limit
|
||||
except ValueError:
|
||||
offset = 0
|
||||
resultids = list(qs.order_by().values_list('id', flat=True)[:201])
|
||||
if len(resultids) <= 200 and len(resultids) <= offset + limit:
|
||||
if len(resultids) <= 200:
|
||||
qs = Order.objects.using(settings.DATABASE_REPLICA).filter(
|
||||
id__in=resultids
|
||||
)
|
||||
@@ -134,7 +133,7 @@ class OrderSearch(PaginationMixin, ListView):
|
||||
"""
|
||||
return qs.only(
|
||||
'id', 'invoice_address__name_cached', 'invoice_address__name_parts', 'code', 'event', 'email',
|
||||
'datetime', 'total', 'status', 'require_approval', 'testmode'
|
||||
'datetime', 'total', 'status', 'require_approval', 'testmode', 'custom_followup_at', 'expires'
|
||||
).prefetch_related(
|
||||
'event', 'event__organizer'
|
||||
).select_related('invoice_address')
|
||||
|
||||
@@ -48,7 +48,7 @@ from django.utils.translation import gettext as _, pgettext
|
||||
|
||||
from pretix.base.models import (
|
||||
EventMetaProperty, EventMetaValue, ItemMetaProperty, ItemMetaValue,
|
||||
ItemVariation, Order, Organizer, User, Voucher,
|
||||
ItemVariation, ItemVariationMetaValue, Order, Organizer, User, Voucher,
|
||||
)
|
||||
from pretix.control.forms.event import EventWizardCopyForm
|
||||
from pretix.control.permissions import (
|
||||
@@ -747,6 +747,10 @@ def item_meta_values(request, organizer, event):
|
||||
value__icontains=q,
|
||||
property__name=propname
|
||||
)
|
||||
var_matches = ItemVariationMetaValue.objects.filter(
|
||||
value__icontains=q,
|
||||
property__name=propname
|
||||
)
|
||||
defaults = ItemMetaProperty.objects.filter(
|
||||
name=propname,
|
||||
default__icontains=q
|
||||
@@ -758,6 +762,7 @@ def item_meta_values(request, organizer, event):
|
||||
|
||||
defaults = defaults.filter(event__organizer_id=organizer.pk)
|
||||
matches = matches.filter(item__event__organizer_id=organizer.pk)
|
||||
var_matches = var_matches.filter(variation__item__event__organizer_id=organizer.pk)
|
||||
all_access = (
|
||||
request.user.has_active_staff_session(request.session.session_key)
|
||||
or request.user.teams.filter(all_events=True, organizer=organizer, can_change_items=True).exists()
|
||||
@@ -773,10 +778,19 @@ def item_meta_values(request, organizer, event):
|
||||
'limit_events__id', flat=True
|
||||
)
|
||||
)
|
||||
var_matches = matches.filter(
|
||||
variation__item__event__id__in=request.user.teams.filter(can_change_items=True).values_list(
|
||||
'limit_events__id', flat=True
|
||||
)
|
||||
)
|
||||
|
||||
return JsonResponse({
|
||||
'results': [
|
||||
{'name': v, 'id': v}
|
||||
for v in sorted(set(defaults.values_list('default', flat=True)[:10]) | set(matches.values_list('value', flat=True)[:10]))
|
||||
for v in sorted(
|
||||
set(defaults.values_list('default', flat=True)[:10]) |
|
||||
set(matches.values_list('value', flat=True)[:10]) |
|
||||
set(var_matches.values_list('value', flat=True)[:10])
|
||||
)
|
||||
]
|
||||
})
|
||||
|
||||
@@ -43,7 +43,7 @@ from django.http import Http404, HttpResponse, HttpResponseRedirect
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
from django.urls import reverse
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.http import is_safe_url
|
||||
from django.utils.http import url_has_allowed_host_and_scheme
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import gettext_lazy as _, pgettext
|
||||
from django.views import View
|
||||
@@ -144,7 +144,7 @@ class WaitingListActionView(EventPermissionRequiredMixin, WaitingListQuerySetMix
|
||||
permission = 'can_change_orders'
|
||||
|
||||
def _redirect_back(self):
|
||||
if "next" in self.request.GET and is_safe_url(self.request.GET.get("next"), allowed_hosts=None):
|
||||
if "next" in self.request.GET and url_has_allowed_host_and_scheme(self.request.GET.get("next"), allowed_hosts=None):
|
||||
return redirect(self.request.GET.get("next"))
|
||||
return redirect(reverse('control:event.orders.waitinglist', kwargs={
|
||||
'event': self.request.event.slug,
|
||||
@@ -360,7 +360,7 @@ class EntryDelete(EventPermissionRequiredMixin, DeleteView):
|
||||
self.object.log_action('pretix.event.orders.waitinglist.deleted', user=self.request.user)
|
||||
self.object.delete()
|
||||
messages.success(self.request, _('The selected entry has been deleted.'))
|
||||
if "next" in self.request.GET and is_safe_url(self.request.GET.get("next"), allowed_hosts=None):
|
||||
if "next" in self.request.GET and url_has_allowed_host_and_scheme(self.request.GET.get("next"), allowed_hosts=None):
|
||||
return redirect(self.request.GET.get("next"))
|
||||
return HttpResponseRedirect(success_url)
|
||||
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
#
|
||||
# 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 string import Formatter
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SafeFormatter(Formatter):
|
||||
"""
|
||||
Customized version of ``str.format`` that (a) behaves just like ``str.format_map`` and
|
||||
(b) does not allow any unwanted shenanigans like attribute access or format specifiers.
|
||||
"""
|
||||
def __init__(self, context):
|
||||
self.context = context
|
||||
|
||||
def get_field(self, field_name, args, kwargs):
|
||||
if '.' in field_name or '[' in field_name:
|
||||
logger.warning(f'Ignored invalid field name "{field_name}"')
|
||||
return ('{' + str(field_name) + '}', field_name)
|
||||
return super().get_field(field_name, args, kwargs)
|
||||
|
||||
def get_value(self, key, args, kwargs):
|
||||
if key not in self.context:
|
||||
return '{' + str(key) + '}'
|
||||
return self.context[key]
|
||||
|
||||
def format_field(self, value, format_spec):
|
||||
# Ignore format _spec
|
||||
return super().format_field(value, '')
|
||||
|
||||
|
||||
def format_map(template, context):
|
||||
if not isinstance(template, str):
|
||||
template = str(template)
|
||||
return SafeFormatter(context).format(template)
|
||||
@@ -38,7 +38,7 @@ class ChunkBasedFileResponse(StreamingHttpResponse):
|
||||
def get_client_ip(request):
|
||||
ip = request.META.get('REMOTE_ADDR')
|
||||
if settings.TRUST_X_FORWARDED_FOR:
|
||||
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
|
||||
x_forwarded_for = request.headers.get('x-forwarded-for')
|
||||
if x_forwarded_for:
|
||||
ip = x_forwarded_for.split(',')[0]
|
||||
return ip
|
||||
|
||||
@@ -24,8 +24,7 @@ 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,
|
||||
KNOWN_TYPES, TIME_TYPES, TYPE_FORMULA, TYPE_STRING, Cell,
|
||||
)
|
||||
from openpyxl.compat import NUMERIC_TYPES
|
||||
from openpyxl.utils import column_index_from_string
|
||||
@@ -49,6 +48,12 @@ There are mainly two problems this solves:
|
||||
- It removes characters considered invalid by Excel to avoid exporter crashes.
|
||||
"""
|
||||
|
||||
ILLEGAL_CHARACTERS_RE = re.compile(
|
||||
# From the XML specification
|
||||
# Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]
|
||||
r'[^\u0020-\uD7FF\u0009\u000A\u000D\uE000-\uFFFD\U00010000-\U0010FFFF]'
|
||||
)
|
||||
|
||||
|
||||
def remove_invalid_excel_chars(val):
|
||||
if isinstance(val, Cell):
|
||||
|
||||
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: 2022-11-29 20:31+0000\n"
|
||||
"POT-Creation-Date: 2022-12-14 13:09+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/"
|
||||
@@ -573,15 +573,15 @@ msgstr "البحث في الاستفسارات"
|
||||
msgid "Selected only"
|
||||
msgstr "المختارة فقط"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:858
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:862
|
||||
msgid "Use a different name internally"
|
||||
msgstr "قم باستخدم اسم مختلف داخليا"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:894
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:898
|
||||
msgid "Click to close"
|
||||
msgstr "اضغط لاغلاق الصفحة"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:966
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:970
|
||||
msgid "You have unsaved changes!"
|
||||
msgstr "لم تقم بحفظ التعديلات!"
|
||||
|
||||
@@ -655,20 +655,20 @@ msgstr "ستسترد %(currency)%(amount)"
|
||||
msgid "Please enter the amount the organizer can keep."
|
||||
msgstr "الرجاء إدخال المبلغ الذي يمكن للمنظم الاحتفاظ به."
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:388
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:393
|
||||
msgid "Please enter a quantity for one of the ticket types."
|
||||
msgstr "الرجاء إدخال عدد لأحد أنواع التذاكر."
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:424
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:429
|
||||
msgid "required"
|
||||
msgstr "مطلوب"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:527
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:546
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:532
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:551
|
||||
msgid "Time zone:"
|
||||
msgstr "المنطقة الزمنية:"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:537
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:542
|
||||
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: 2022-11-29 20:31+0000\n"
|
||||
"POT-Creation-Date: 2022-12-14 13:09+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-"
|
||||
@@ -556,15 +556,15 @@ msgstr ""
|
||||
msgid "Selected only"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:858
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:862
|
||||
msgid "Use a different name internally"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:894
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:898
|
||||
msgid "Click to close"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:966
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:970
|
||||
msgid "You have unsaved changes!"
|
||||
msgstr ""
|
||||
|
||||
@@ -628,22 +628,22 @@ msgstr ""
|
||||
msgid "Please enter the amount the organizer can keep."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:388
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:393
|
||||
msgid "Please enter a quantity for one of the ticket types."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:424
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:429
|
||||
#, fuzzy
|
||||
#| msgid "Cart expired"
|
||||
msgid "required"
|
||||
msgstr "Cistella expirada"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:527
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:546
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:532
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:551
|
||||
msgid "Time zone:"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:537
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:542
|
||||
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: 2022-11-29 20:31+0000\n"
|
||||
"POT-Creation-Date: 2022-12-14 13:09+0000\n"
|
||||
"PO-Revision-Date: 2021-12-06 23:00+0000\n"
|
||||
"Last-Translator: Ondřej Sokol <osokol@treesoft.cz>\n"
|
||||
"Language-Team: Czech <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
||||
@@ -573,15 +573,15 @@ msgstr "Hledaný výraz"
|
||||
msgid "Selected only"
|
||||
msgstr "Pouze vybrané"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:858
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:862
|
||||
msgid "Use a different name internally"
|
||||
msgstr "Interně používat jiný název"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:894
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:898
|
||||
msgid "Click to close"
|
||||
msgstr "Kliknutím zavřete"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:966
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:970
|
||||
msgid "You have unsaved changes!"
|
||||
msgstr "Máte neuložené změny!"
|
||||
|
||||
@@ -648,20 +648,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:388
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:393
|
||||
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:424
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:429
|
||||
msgid "required"
|
||||
msgstr "povinný"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:527
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:546
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:532
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:551
|
||||
msgid "Time zone:"
|
||||
msgstr "Časové pásmo:"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:537
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:542
|
||||
msgid "Your local time:"
|
||||
msgstr "Místní čas:"
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,9 +6,9 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2022-11-29 20:31+0000\n"
|
||||
"PO-Revision-Date: 2022-04-01 13:36+0000\n"
|
||||
"Last-Translator: Anna-itk <abc@aarhus.dk>\n"
|
||||
"POT-Creation-Date: 2022-12-14 13:09+0000\n"
|
||||
"PO-Revision-Date: 2022-12-01 17:00+0000\n"
|
||||
"Last-Translator: Mie Frydensbjerg <mif@aarhus.dk>\n"
|
||||
"Language-Team: Danish <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
||||
"da/>\n"
|
||||
"Language: da\n"
|
||||
@@ -16,7 +16,7 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||
"X-Generator: Weblate 4.11.2\n"
|
||||
"X-Generator: Weblate 4.14.2\n"
|
||||
|
||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:56
|
||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:62
|
||||
@@ -30,51 +30,51 @@ msgstr "Kommentar:"
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:34
|
||||
msgid "PayPal"
|
||||
msgstr ""
|
||||
msgstr "PayPal"
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:35
|
||||
msgid "Venmo"
|
||||
msgstr ""
|
||||
msgstr "Venmo"
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:36
|
||||
msgid "Apple Pay"
|
||||
msgstr ""
|
||||
msgstr "Apple Pay"
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:37
|
||||
msgid "Itaú"
|
||||
msgstr ""
|
||||
msgstr "Itaú"
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:38
|
||||
msgid "PayPal Credit"
|
||||
msgstr ""
|
||||
msgstr "PayPal Credit"
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:39
|
||||
msgid "Credit Card"
|
||||
msgstr ""
|
||||
msgstr "Kreditkort"
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:40
|
||||
msgid "PayPal Pay Later"
|
||||
msgstr ""
|
||||
msgstr "PayPal Pay Later"
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:41
|
||||
msgid "iDEAL"
|
||||
msgstr ""
|
||||
msgstr "iDEAL"
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:42
|
||||
msgid "SEPA Direct Debit"
|
||||
msgstr ""
|
||||
msgstr "SEPA Direct Debit"
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:43
|
||||
msgid "Bancontact"
|
||||
msgstr ""
|
||||
msgstr "Bancontact"
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:44
|
||||
msgid "giropay"
|
||||
msgstr ""
|
||||
msgstr "giropay"
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:45
|
||||
msgid "SOFORT"
|
||||
msgstr ""
|
||||
msgstr "SOFORT"
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:46
|
||||
#, fuzzy
|
||||
@@ -84,11 +84,11 @@ msgstr "Ja"
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:47
|
||||
msgid "MyBank"
|
||||
msgstr ""
|
||||
msgstr "MyBank"
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:48
|
||||
msgid "Przelewy24"
|
||||
msgstr ""
|
||||
msgstr "Przelewy24"
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:49
|
||||
msgid "Verkkopankki"
|
||||
@@ -146,7 +146,7 @@ msgstr "Bekræfter din betaling …"
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:246
|
||||
msgid "Payment method unavailable"
|
||||
msgstr ""
|
||||
msgstr "Betalingsmetode er ikke tilgængelig"
|
||||
|
||||
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:15
|
||||
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:39
|
||||
@@ -176,67 +176,64 @@ msgstr "Kontakter din bank …"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:30
|
||||
msgid "Select a check-in list"
|
||||
msgstr ""
|
||||
msgstr "Vælg en check-in liste"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:31
|
||||
msgid "No active check-in lists found."
|
||||
msgstr ""
|
||||
msgstr "Der blev ikke fundet nogen aktive check-in lister."
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:32
|
||||
msgid "Switch check-in list"
|
||||
msgstr ""
|
||||
msgstr "Skift check-in liste"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:33
|
||||
msgid "Search results"
|
||||
msgstr ""
|
||||
msgstr "Søgeresultater"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:34
|
||||
#, fuzzy
|
||||
#| msgctxt "widget"
|
||||
#| msgid "Close ticket shop"
|
||||
msgid "No tickets found"
|
||||
msgstr "Luk billetbutik"
|
||||
msgstr "Ingen billetter fundet"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:35
|
||||
msgid "Result"
|
||||
msgstr ""
|
||||
msgstr "Resultat"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:36
|
||||
msgid "This ticket requires special attention"
|
||||
msgstr ""
|
||||
msgstr "Denne billet kræver særlig opmærksomhed"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:37
|
||||
msgid "Switch direction"
|
||||
msgstr ""
|
||||
msgstr "Skift retning"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:38
|
||||
msgid "Entry"
|
||||
msgstr ""
|
||||
msgstr "Indgang"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:39
|
||||
msgid "Exit"
|
||||
msgstr ""
|
||||
msgstr "Udgang"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:40
|
||||
msgid "Scan a ticket or search and press return…"
|
||||
msgstr ""
|
||||
msgstr "Scan en billet eller søg og tryk på retur…"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:41
|
||||
msgid "Load more"
|
||||
msgstr ""
|
||||
msgstr "Hent flere"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:42
|
||||
msgid "Valid"
|
||||
msgstr ""
|
||||
msgstr "Gyldig"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:43
|
||||
msgid "Unpaid"
|
||||
msgstr ""
|
||||
msgstr "Ubetalt"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:44
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:45
|
||||
msgid "Canceled"
|
||||
msgstr ""
|
||||
msgstr "Annulleret"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:46
|
||||
#, fuzzy
|
||||
@@ -264,7 +261,7 @@ msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:52
|
||||
msgid "Valid ticket"
|
||||
msgstr ""
|
||||
msgstr "Gyldig billet"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:53
|
||||
msgid "Exit recorded"
|
||||
@@ -279,18 +276,16 @@ msgid "Information required"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:56
|
||||
#, fuzzy
|
||||
#| msgid "Unknown error."
|
||||
msgid "Unknown ticket"
|
||||
msgstr "Ukendt fejl."
|
||||
msgstr "Ukendt billet"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:57
|
||||
msgid "Ticket type not allowed here"
|
||||
msgstr ""
|
||||
msgstr "Billettype er ikke tilladt her"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:59
|
||||
msgid "Entry not allowed"
|
||||
msgstr ""
|
||||
msgstr "Adgang ikke tilladt"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:60
|
||||
msgid "Ticket code revoked/changed"
|
||||
@@ -298,7 +293,7 @@ msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:61
|
||||
msgid "Order canceled"
|
||||
msgstr ""
|
||||
msgstr "Bestilling annulleret"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:62
|
||||
msgid "Ticket code is ambiguous on list"
|
||||
@@ -312,11 +307,11 @@ msgstr "Check-in QR"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:64
|
||||
msgid "Valid Tickets"
|
||||
msgstr ""
|
||||
msgstr "Gyldige billetter"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:65
|
||||
msgid "Currently inside"
|
||||
msgstr ""
|
||||
msgstr "Inde i øjeblikket"
|
||||
|
||||
#: pretix/static/lightbox/js/lightbox.js:96
|
||||
#, fuzzy
|
||||
@@ -376,17 +371,15 @@ msgstr ""
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:145
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:21
|
||||
#, fuzzy
|
||||
#| msgid "The request took to long. Please try again."
|
||||
msgid "The request took too long. Please try again."
|
||||
msgstr "Forespørgselen tog for lang tid. Prøv venligst igen."
|
||||
msgstr "Forespørgslen tog for lang tid. Prøv igen."
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:186
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:26
|
||||
msgid ""
|
||||
"We currently cannot reach the server. Please try again. Error code: {code}"
|
||||
msgstr ""
|
||||
"Vi kan ikke komme i kontakt med serveren. Prøv venligst igen. Fejlkode: "
|
||||
"Vi kan i øjeblikket ikke komme i kontakt med serveren. Prøv igen. Fejlkode: "
|
||||
"{code}"
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:208
|
||||
@@ -433,23 +426,20 @@ msgid "Product"
|
||||
msgstr "Produkt"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:63
|
||||
#, fuzzy
|
||||
#| msgctxt "widget"
|
||||
#| msgid "See variations"
|
||||
msgid "Product variation"
|
||||
msgstr "Vis varianter"
|
||||
msgstr "Produktvariation"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:67
|
||||
msgid "Current date and time"
|
||||
msgstr ""
|
||||
msgstr "Aktuel dato og klokkeslæt"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:71
|
||||
msgid "Current day of the week (1 = Monday, 7 = Sunday)"
|
||||
msgstr ""
|
||||
msgstr "Aktuel ugedag (1 = mandag, 7 = søndag)"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:75
|
||||
msgid "Number of previous entries"
|
||||
msgstr ""
|
||||
msgstr "Antal tidligere poster"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:79
|
||||
msgid "Number of previous entries since midnight"
|
||||
@@ -601,15 +591,15 @@ msgstr ""
|
||||
msgid "Selected only"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:858
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:862
|
||||
msgid "Use a different name internally"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:894
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:898
|
||||
msgid "Click to close"
|
||||
msgstr "Klik for at lukke"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:966
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:970
|
||||
msgid "You have unsaved changes!"
|
||||
msgstr "Du har ændringer, der ikke er gemt!"
|
||||
|
||||
@@ -683,22 +673,22 @@ msgstr "fra %(currency)s %(price)s"
|
||||
msgid "Please enter the amount the organizer can keep."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:388
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:393
|
||||
msgid "Please enter a quantity for one of the ticket types."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:424
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:429
|
||||
#, fuzzy
|
||||
#| msgid "Cart expired"
|
||||
msgid "required"
|
||||
msgstr "Kurv udløbet"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:527
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:546
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:532
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:551
|
||||
msgid "Time zone:"
|
||||
msgstr "Tidszone:"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:537
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:542
|
||||
msgid "Your local time:"
|
||||
msgstr "Din lokaltid:"
|
||||
|
||||
@@ -901,7 +891,7 @@ msgstr "Åbn sædevalg"
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:56
|
||||
msgctxt "widget"
|
||||
msgid "Load more"
|
||||
msgstr ""
|
||||
msgstr "Hent flere"
|
||||
|
||||
#: 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: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2022-11-29 20:31+0000\n"
|
||||
"POT-Creation-Date: 2022-12-14 13:09+0000\n"
|
||||
"PO-Revision-Date: 2022-07-26 08:58+0000\n"
|
||||
"Last-Translator: Raphael Michel <michel@rami.io>\n"
|
||||
"Language-Team: German <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
||||
@@ -577,15 +577,15 @@ msgstr "Suchbegriff"
|
||||
msgid "Selected only"
|
||||
msgstr "Nur ausgewählte"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:858
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:862
|
||||
msgid "Use a different name internally"
|
||||
msgstr "Intern einen anderen Namen verwenden"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:894
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:898
|
||||
msgid "Click to close"
|
||||
msgstr "Klicken zum Schließen"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:966
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:970
|
||||
msgid "You have unsaved changes!"
|
||||
msgstr "Sie haben ungespeicherte Änderungen!"
|
||||
|
||||
@@ -650,20 +650,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:388
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:393
|
||||
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:424
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:429
|
||||
msgid "required"
|
||||
msgstr "verpflichtend"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:527
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:546
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:532
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:551
|
||||
msgid "Time zone:"
|
||||
msgstr "Zeitzone:"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:537
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:542
|
||||
msgid "Your local time:"
|
||||
msgstr "Deine lokale Zeit:"
|
||||
|
||||
|
||||
@@ -337,6 +337,9 @@ WeChat
|
||||
WhatsApp
|
||||
Widget
|
||||
xlsx
|
||||
XXX
|
||||
XXXX
|
||||
XXXXX
|
||||
Yubikey
|
||||
Zahlungs
|
||||
Zahlungsbestätigungs
|
||||
|
||||
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: 2022-11-29 20:31+0000\n"
|
||||
"POT-Creation-Date: 2022-12-14 13:09+0000\n"
|
||||
"PO-Revision-Date: 2022-07-26 08:58+0000\n"
|
||||
"Last-Translator: Raphael Michel <michel@rami.io>\n"
|
||||
"Language-Team: German (informal) <https://translate.pretix.eu/projects/"
|
||||
@@ -576,15 +576,15 @@ msgstr "Suchbegriff"
|
||||
msgid "Selected only"
|
||||
msgstr "Nur ausgewählte"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:858
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:862
|
||||
msgid "Use a different name internally"
|
||||
msgstr "Intern einen anderen Namen verwenden"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:894
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:898
|
||||
msgid "Click to close"
|
||||
msgstr "Klicken zum Schließen"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:966
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:970
|
||||
msgid "You have unsaved changes!"
|
||||
msgstr "Du hast ungespeicherte Änderungen!"
|
||||
|
||||
@@ -649,20 +649,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:388
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:393
|
||||
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:424
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:429
|
||||
msgid "required"
|
||||
msgstr "verpflichtend"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:527
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:546
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:532
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:551
|
||||
msgid "Time zone:"
|
||||
msgstr "Zeitzone:"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:537
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:542
|
||||
msgid "Your local time:"
|
||||
msgstr "Deine lokale Zeit:"
|
||||
|
||||
|
||||
@@ -337,6 +337,9 @@ WeChat
|
||||
WhatsApp
|
||||
Widget
|
||||
xlsx
|
||||
XXX
|
||||
XXXX
|
||||
XXXXX
|
||||
Yubikey
|
||||
Zahlungs
|
||||
Zahlungsbestätigungs
|
||||
|
||||
+934
-846
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: 2022-11-29 20:31+0000\n"
|
||||
"POT-Creation-Date: 2022-12-14 13:09+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"
|
||||
@@ -555,15 +555,15 @@ msgstr ""
|
||||
msgid "Selected only"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:858
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:862
|
||||
msgid "Use a different name internally"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:894
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:898
|
||||
msgid "Click to close"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:966
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:970
|
||||
msgid "You have unsaved changes!"
|
||||
msgstr ""
|
||||
|
||||
@@ -623,20 +623,20 @@ msgstr ""
|
||||
msgid "Please enter the amount the organizer can keep."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:388
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:393
|
||||
msgid "Please enter a quantity for one of the ticket types."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:424
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:429
|
||||
msgid "required"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:527
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:546
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:532
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:551
|
||||
msgid "Time zone:"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:537
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:542
|
||||
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: 2022-11-29 20:31+0000\n"
|
||||
"POT-Creation-Date: 2022-12-14 13:09+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/"
|
||||
@@ -615,15 +615,15 @@ msgstr ""
|
||||
msgid "Selected only"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:858
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:862
|
||||
msgid "Use a different name internally"
|
||||
msgstr "Χρησιμοποιήστε διαφορετικό όνομα εσωτερικά"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:894
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:898
|
||||
msgid "Click to close"
|
||||
msgstr "Κάντε κλικ για να κλείσετε"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:966
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:970
|
||||
msgid "You have unsaved changes!"
|
||||
msgstr ""
|
||||
|
||||
@@ -695,22 +695,22 @@ msgstr "απο %(currency)s %(price)s"
|
||||
msgid "Please enter the amount the organizer can keep."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:388
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:393
|
||||
msgid "Please enter a quantity for one of the ticket types."
|
||||
msgstr "Εισαγάγετε μια ποσότητα για έναν από τους τύπους εισιτηρίων."
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:424
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:429
|
||||
#, fuzzy
|
||||
#| msgid "Cart expired"
|
||||
msgid "required"
|
||||
msgstr "Το καλάθι έληξε"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:527
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:546
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:532
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:551
|
||||
msgid "Time zone:"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:537
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:542
|
||||
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: 2022-11-29 20:31+0000\n"
|
||||
"POT-Creation-Date: 2022-12-14 13:09+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: Spanish <https://translate.pretix.eu/projects/pretix/pretix-"
|
||||
@@ -577,15 +577,15 @@ msgstr "Consultar búsqueda"
|
||||
msgid "Selected only"
|
||||
msgstr "Solamente seleccionados"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:858
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:862
|
||||
msgid "Use a different name internally"
|
||||
msgstr "Usar un nombre diferente internamente"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:894
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:898
|
||||
msgid "Click to close"
|
||||
msgstr "Click para cerrar"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:966
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:970
|
||||
msgid "You have unsaved changes!"
|
||||
msgstr "¡Tienes cambios sin guardar!"
|
||||
|
||||
@@ -649,20 +649,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:388
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:393
|
||||
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:424
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:429
|
||||
msgid "required"
|
||||
msgstr "campo requerido"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:527
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:546
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:532
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:551
|
||||
msgid "Time zone:"
|
||||
msgstr "Zona horaria:"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:537
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:542
|
||||
msgid "Your local time:"
|
||||
msgstr "Su hora local:"
|
||||
|
||||
|
||||
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: 2022-11-29 20:31+0000\n"
|
||||
"POT-Creation-Date: 2022-12-14 13:09+0000\n"
|
||||
"PO-Revision-Date: 2021-11-10 05:00+0000\n"
|
||||
"Last-Translator: Jaakko Rinta-Filppula <jaakko@r-f.fi>\n"
|
||||
"Language-Team: Finnish <https://translate.pretix.eu/projects/pretix/pretix-"
|
||||
@@ -576,15 +576,15 @@ msgstr ""
|
||||
msgid "Selected only"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:858
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:862
|
||||
msgid "Use a different name internally"
|
||||
msgstr "Käytä toista nimeä sisäisesti"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:894
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:898
|
||||
msgid "Click to close"
|
||||
msgstr "Sulje klikkaamalla"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:966
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:970
|
||||
msgid "You have unsaved changes!"
|
||||
msgstr "Sinulla on tallentamattomia muutoksia!"
|
||||
|
||||
@@ -648,22 +648,22 @@ msgstr ""
|
||||
msgid "Please enter the amount the organizer can keep."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:388
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:393
|
||||
msgid "Please enter a quantity for one of the ticket types."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:424
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:429
|
||||
#, fuzzy
|
||||
#| msgid "Cart expired"
|
||||
msgid "required"
|
||||
msgstr "Ostoskori on vanhentunut"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:527
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:546
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:532
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:551
|
||||
msgid "Time zone:"
|
||||
msgstr "Aikavyöhyke:"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:537
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:542
|
||||
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: 2022-11-29 20:31+0000\n"
|
||||
"POT-Creation-Date: 2022-12-14 13:09+0000\n"
|
||||
"PO-Revision-Date: 2022-04-07 10:40+0000\n"
|
||||
"Last-Translator: Eva-Maria Obermann <obermann@rami.io>\n"
|
||||
"Language-Team: French <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
||||
@@ -607,15 +607,15 @@ msgstr ""
|
||||
msgid "Selected only"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:858
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:862
|
||||
msgid "Use a different name internally"
|
||||
msgstr "Utiliser un nom différent en interne"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:894
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:898
|
||||
msgid "Click to close"
|
||||
msgstr "Cliquez pour fermer"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:966
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:970
|
||||
msgid "You have unsaved changes!"
|
||||
msgstr "Vous avez des modifications non sauvegardées !"
|
||||
|
||||
@@ -683,22 +683,22 @@ msgstr "de %(currency)s %(price)s"
|
||||
msgid "Please enter the amount the organizer can keep."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:388
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:393
|
||||
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:424
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:429
|
||||
#, fuzzy
|
||||
#| msgid "Cart expired"
|
||||
msgid "required"
|
||||
msgstr "Panier expiré"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:527
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:546
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:532
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:551
|
||||
msgid "Time zone:"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:537
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:542
|
||||
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: 2022-11-29 20:31+0000\n"
|
||||
"POT-Creation-Date: 2022-12-14 13:09+0000\n"
|
||||
"PO-Revision-Date: 2022-02-22 22: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-"
|
||||
@@ -575,15 +575,15 @@ msgstr "Consultar unha procura"
|
||||
msgid "Selected only"
|
||||
msgstr "Soamente seleccionados"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:858
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:862
|
||||
msgid "Use a different name internally"
|
||||
msgstr "Usar un nome diferente internamente"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:894
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:898
|
||||
msgid "Click to close"
|
||||
msgstr "Click para cerrar"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:966
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:970
|
||||
msgid "You have unsaved changes!"
|
||||
msgstr "Tes cambios sen gardar!"
|
||||
|
||||
@@ -646,20 +646,20 @@ msgstr "Obtés %(currency)s %(price)s de volta"
|
||||
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:388
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:393
|
||||
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:424
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:429
|
||||
msgid "required"
|
||||
msgstr "campo requirido"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:527
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:546
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:532
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:551
|
||||
msgid "Time zone:"
|
||||
msgstr "Zona horaria:"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:537
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:542
|
||||
msgid "Your local time:"
|
||||
msgstr "A súa hora local:"
|
||||
|
||||
|
||||
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: 2022-11-29 20:31+0000\n"
|
||||
"POT-Creation-Date: 2022-12-14 13:09+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/"
|
||||
@@ -559,15 +559,15 @@ msgstr ""
|
||||
msgid "Selected only"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:858
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:862
|
||||
msgid "Use a different name internally"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:894
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:898
|
||||
msgid "Click to close"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:966
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:970
|
||||
msgid "You have unsaved changes!"
|
||||
msgstr ""
|
||||
|
||||
@@ -631,20 +631,20 @@ msgstr ""
|
||||
msgid "Please enter the amount the organizer can keep."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:388
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:393
|
||||
msgid "Please enter a quantity for one of the ticket types."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:424
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:429
|
||||
msgid "required"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:527
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:546
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:532
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:551
|
||||
msgid "Time zone:"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:537
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:542
|
||||
msgid "Your local time:"
|
||||
msgstr ""
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,910 @@
|
||||
# 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: 2022-12-14 13:09+0000\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: Automatically generated\n"
|
||||
"Language-Team: none\n"
|
||||
"Language: hr\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
|
||||
"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\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 ""
|
||||
|
||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js:76
|
||||
msgid "Comment:"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:34
|
||||
msgid "PayPal"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:35
|
||||
msgid "Venmo"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:36
|
||||
msgid "Apple Pay"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:37
|
||||
msgid "Itaú"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:38
|
||||
msgid "PayPal Credit"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:39
|
||||
msgid "Credit Card"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:40
|
||||
msgid "PayPal Pay Later"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:41
|
||||
msgid "iDEAL"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:42
|
||||
msgid "SEPA Direct Debit"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:43
|
||||
msgid "Bancontact"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:44
|
||||
msgid "giropay"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:45
|
||||
msgid "SOFORT"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:46
|
||||
msgid "eps"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:47
|
||||
msgid "MyBank"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:48
|
||||
msgid "Przelewy24"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:49
|
||||
msgid "Verkkopankki"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:50
|
||||
msgid "PayU"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:51
|
||||
msgid "BLIK"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:52
|
||||
msgid "Trustly"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:53
|
||||
msgid "Zimpler"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:54
|
||||
msgid "Maxima"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:55
|
||||
msgid "OXXO"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:56
|
||||
msgid "Boleto"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:57
|
||||
msgid "WeChat Pay"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:58
|
||||
msgid "Mercado Pago"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:163
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:48
|
||||
msgid "Continue"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:221
|
||||
#: 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 ""
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js:246
|
||||
msgid "Payment method unavailable"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:15
|
||||
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:39
|
||||
msgid "Placed orders"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:15
|
||||
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:39
|
||||
msgid "Paid orders"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js:27
|
||||
msgid "Total revenue"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/stripe/static/pretixplugins/stripe/pretix-stripe.js:12
|
||||
msgid "Contacting Stripe …"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/stripe/static/pretixplugins/stripe/pretix-stripe.js:60
|
||||
msgid "Total"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/stripe/static/pretixplugins/stripe/pretix-stripe.js:159
|
||||
msgid "Contacting your bank …"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:30
|
||||
msgid "Select a check-in list"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:31
|
||||
msgid "No active check-in lists found."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:32
|
||||
msgid "Switch check-in list"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:33
|
||||
msgid "Search results"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:34
|
||||
msgid "No tickets found"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:35
|
||||
msgid "Result"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:36
|
||||
msgid "This ticket requires special attention"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:37
|
||||
msgid "Switch direction"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:38
|
||||
msgid "Entry"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:39
|
||||
msgid "Exit"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:40
|
||||
msgid "Scan a ticket or search and press return…"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:41
|
||||
msgid "Load more"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:42
|
||||
msgid "Valid"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:43
|
||||
msgid "Unpaid"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:44
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:45
|
||||
msgid "Canceled"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:46
|
||||
msgid "Redeemed"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:47
|
||||
msgid "Cancel"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:49
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:58
|
||||
msgid "Ticket not paid"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:50
|
||||
msgid "This ticket is not yet paid. Do you want to continue anyways?"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:51
|
||||
msgid "Additional information required"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:52
|
||||
msgid "Valid ticket"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:53
|
||||
msgid "Exit recorded"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:54
|
||||
msgid "Ticket already used"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:55
|
||||
msgid "Information required"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:56
|
||||
msgid "Unknown ticket"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:57
|
||||
msgid "Ticket type not allowed here"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:59
|
||||
msgid "Entry not allowed"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:60
|
||||
msgid "Ticket code revoked/changed"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:61
|
||||
msgid "Order canceled"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:62
|
||||
msgid "Ticket code is ambiguous on list"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:63
|
||||
msgid "Checked-in Tickets"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:64
|
||||
msgid "Valid Tickets"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/main.js:65
|
||||
msgid "Currently inside"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/lightbox/js/lightbox.js:96
|
||||
msgid "close"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:43
|
||||
#: pretix/static/pretixbase/js/asynctask.js:120
|
||||
msgid ""
|
||||
"Your request is currently being processed. Depending on the size of your "
|
||||
"event, this might take up to a few minutes."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:48
|
||||
#: pretix/static/pretixbase/js/asynctask.js:125
|
||||
msgid "Your request has been queued on the server and will soon be processed."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:54
|
||||
#: pretix/static/pretixbase/js/asynctask.js:131
|
||||
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 ""
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:90
|
||||
#: pretix/static/pretixbase/js/asynctask.js:178
|
||||
#: pretix/static/pretixbase/js/asynctask.js:183
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:24
|
||||
msgid "An error of type {code} occurred."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:93
|
||||
msgid ""
|
||||
"We currently cannot reach the server, but we keep trying. Last error code: "
|
||||
"{code}"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:145
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:21
|
||||
msgid "The request took too long. Please try again."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:186
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:26
|
||||
msgid ""
|
||||
"We currently cannot reach the server. Please try again. Error code: {code}"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:208
|
||||
msgid "We are processing your request …"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:216
|
||||
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 ""
|
||||
|
||||
#: pretix/static/pretixbase/js/asynctask.js:276
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:71
|
||||
msgid "Close message"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/clipboard.js:23
|
||||
msgid "Copied!"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/clipboard.js:29
|
||||
msgid "Press Ctrl-C to copy!"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:10
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:16
|
||||
msgid "is one of"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:22
|
||||
msgid "is before"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:26
|
||||
msgid "is after"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:59
|
||||
msgid "Product"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:63
|
||||
msgid "Product variation"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:67
|
||||
msgid "Current date and time"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:71
|
||||
msgid "Current day of the week (1 = Monday, 7 = Sunday)"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:75
|
||||
msgid "Number of previous entries"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:79
|
||||
msgid "Number of previous entries since midnight"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:83
|
||||
msgid "Number of days with a previous entry"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:87
|
||||
msgid "Minutes since last entry (-1 on first entry)"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:91
|
||||
msgid "Minutes since first entry (-1 on first entry)"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:112
|
||||
msgid "All of the conditions below (AND)"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:113
|
||||
msgid "At least one of the conditions below (OR)"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:114
|
||||
msgid "Event start"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:115
|
||||
msgid "Event end"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:116
|
||||
msgid "Event admission"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:117
|
||||
msgid "custom date and time"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:118
|
||||
msgid "custom time"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:119
|
||||
msgid "Tolerance (minutes)"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:120
|
||||
msgid "Add condition"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:121
|
||||
msgid "minutes"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:71
|
||||
msgid "Check-in QR"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:384
|
||||
msgid "The PDF background file could not be loaded for the following reason:"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:653
|
||||
msgid "Group of objects"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:659
|
||||
msgid "Text object"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:661
|
||||
msgid "Barcode area"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:663
|
||||
msgid "Image area"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:665
|
||||
msgid "Powered by pretix"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:667
|
||||
msgid "Object"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:671
|
||||
msgid "Ticket design"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:961
|
||||
msgid "Saving failed."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1011
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1050
|
||||
msgid "Error while uploading your PDF file, please try again."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js:1035
|
||||
msgid "Do you really want to leave the editor without saving your changes?"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:19
|
||||
msgid "An error has occurred."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/mail.js:54
|
||||
msgid "Generating messages …"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:107
|
||||
msgid "Unknown error."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:308
|
||||
msgid "Your color has great contrast and is very easy to read!"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:312
|
||||
msgid "Your color has decent contrast and is probably good-enough to read!"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:316
|
||||
msgid ""
|
||||
"Your color has bad contrast for text on white background, please choose a "
|
||||
"darker shade."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:454
|
||||
msgid "All"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:455
|
||||
msgid "None"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:456
|
||||
msgid "Search query"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:459
|
||||
msgid "Selected only"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:862
|
||||
msgid "Use a different name internally"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:898
|
||||
msgid "Click to close"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:970
|
||||
msgid "You have unsaved changes!"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/orderchange.js:25
|
||||
msgid "Calculating default price…"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/question.js:42
|
||||
msgid "Others"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/question.js:82
|
||||
msgid "Count"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/question.js:136
|
||||
#: pretix/static/pretixpresale/js/ui/questions.js:269
|
||||
msgid "Yes"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/question.js:137
|
||||
#: pretix/static/pretixpresale/js/ui/questions.js:269
|
||||
msgid "No"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/subevent.js:111
|
||||
msgid "(one more date)"
|
||||
msgid_plural "({num} more dates)"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/cart.js:43
|
||||
msgid ""
|
||||
"The items in your cart are no longer reserved for you. You can still "
|
||||
"complete your order as long as they’re available."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/cart.js:45
|
||||
msgid "Cart expired"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/cart.js:50
|
||||
msgid "The items in your cart are reserved for you for one minute."
|
||||
msgid_plural "The items in your cart are reserved for you for {num} minutes."
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:152
|
||||
msgid "The organizer keeps %(currency)s %(amount)s"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:160
|
||||
msgid "You get %(currency)s %(amount)s back"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:176
|
||||
msgid "Please enter the amount the organizer can keep."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:393
|
||||
msgid "Please enter a quantity for one of the ticket types."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:429
|
||||
msgid "required"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:532
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:551
|
||||
msgid "Time zone:"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:542
|
||||
msgid "Your local time:"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:17
|
||||
msgctxt "widget"
|
||||
msgid "Sold out"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:18
|
||||
msgctxt "widget"
|
||||
msgid "Buy"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:19
|
||||
msgctxt "widget"
|
||||
msgid "Register"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:20
|
||||
msgctxt "widget"
|
||||
msgid "Reserved"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:21
|
||||
msgctxt "widget"
|
||||
msgid "FREE"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:22
|
||||
msgctxt "widget"
|
||||
msgid "from %(currency)s %(price)s"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:23
|
||||
msgctxt "widget"
|
||||
msgid "incl. %(rate)s% %(taxname)s"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:24
|
||||
msgctxt "widget"
|
||||
msgid "plus %(rate)s% %(taxname)s"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:25
|
||||
msgctxt "widget"
|
||||
msgid "incl. taxes"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:26
|
||||
msgctxt "widget"
|
||||
msgid "plus taxes"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:27
|
||||
#, javascript-format
|
||||
msgctxt "widget"
|
||||
msgid "currently available: %s"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:28
|
||||
msgctxt "widget"
|
||||
msgid "Only available with a voucher"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:29
|
||||
#, javascript-format
|
||||
msgctxt "widget"
|
||||
msgid "minimum amount to order: %s"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:30
|
||||
msgctxt "widget"
|
||||
msgid "Close ticket shop"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:31
|
||||
msgctxt "widget"
|
||||
msgid "The ticket shop could not be loaded."
|
||||
msgstr ""
|
||||
|
||||
#: 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 ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:34
|
||||
msgctxt "widget"
|
||||
msgid "Open ticket shop"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:35
|
||||
msgctxt "widget"
|
||||
msgid "The cart could not be created. Please try again later"
|
||||
msgstr ""
|
||||
|
||||
#: 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 ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:38
|
||||
msgctxt "widget"
|
||||
msgid "Waiting list"
|
||||
msgstr ""
|
||||
|
||||
#: 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 ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:41
|
||||
msgctxt "widget"
|
||||
msgid "Resume checkout"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:42
|
||||
msgctxt "widget"
|
||||
msgid "Redeem a voucher"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:43
|
||||
msgctxt "widget"
|
||||
msgid "Redeem"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:44
|
||||
msgctxt "widget"
|
||||
msgid "Voucher code"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:45
|
||||
msgctxt "widget"
|
||||
msgid "Close"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:46
|
||||
msgctxt "widget"
|
||||
msgid "Continue"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:47
|
||||
msgctxt "widget"
|
||||
msgid "See variations"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:48
|
||||
msgctxt "widget"
|
||||
msgid "Choose a different event"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:49
|
||||
msgctxt "widget"
|
||||
msgid "Choose a different date"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:50
|
||||
msgctxt "widget"
|
||||
msgid "Back"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:51
|
||||
msgctxt "widget"
|
||||
msgid "Next month"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:52
|
||||
msgctxt "widget"
|
||||
msgid "Previous month"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:53
|
||||
msgctxt "widget"
|
||||
msgid "Next week"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:54
|
||||
msgctxt "widget"
|
||||
msgid "Previous week"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:55
|
||||
msgctxt "widget"
|
||||
msgid "Open seat selection"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:56
|
||||
msgctxt "widget"
|
||||
msgid "Load more"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:58
|
||||
msgid "Mo"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:59
|
||||
msgid "Tu"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:60
|
||||
msgid "We"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:61
|
||||
msgid "Th"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:62
|
||||
msgid "Fr"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:63
|
||||
msgid "Sa"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:64
|
||||
msgid "Su"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:67
|
||||
msgid "January"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:68
|
||||
msgid "February"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:69
|
||||
msgid "March"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:70
|
||||
msgid "April"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:71
|
||||
msgid "May"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:72
|
||||
msgid "June"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:73
|
||||
msgid "July"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:74
|
||||
msgid "August"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:75
|
||||
msgid "September"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:76
|
||||
msgid "October"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:77
|
||||
msgid "November"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js:78
|
||||
msgid "December"
|
||||
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: 2022-11-29 20:31+0000\n"
|
||||
"POT-Creation-Date: 2022-12-14 13:09+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-"
|
||||
@@ -603,15 +603,15 @@ msgstr ""
|
||||
msgid "Selected only"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:858
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:862
|
||||
msgid "Use a different name internally"
|
||||
msgstr "Használj másik nevet"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:894
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:898
|
||||
msgid "Click to close"
|
||||
msgstr "Bezárásért kattints"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:966
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:970
|
||||
msgid "You have unsaved changes!"
|
||||
msgstr "Mentetlen változtatások!"
|
||||
|
||||
@@ -683,22 +683,22 @@ msgstr "%(currency) %(price)-tól"
|
||||
msgid "Please enter the amount the organizer can keep."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:388
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:393
|
||||
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:424
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:429
|
||||
#, fuzzy
|
||||
#| msgid "Cart expired"
|
||||
msgid "required"
|
||||
msgstr "A kosár lejárt"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:527
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:546
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:532
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:551
|
||||
msgid "Time zone:"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:537
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:542
|
||||
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: 2022-11-29 20:31+0000\n"
|
||||
"POT-Creation-Date: 2022-12-14 13:09+0000\n"
|
||||
"PO-Revision-Date: 2022-05-08 19:00+0000\n"
|
||||
"Last-Translator: Emanuele Signoretta <signorettae@gmail.com>\n"
|
||||
"Language-Team: Italian <https://translate.pretix.eu/projects/pretix/pretix-"
|
||||
@@ -574,15 +574,15 @@ msgstr "Chiave di ricerca"
|
||||
msgid "Selected only"
|
||||
msgstr "Solo i selezionati"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:858
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:862
|
||||
msgid "Use a different name internally"
|
||||
msgstr "Utilizza un nome diverso internamente"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:894
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:898
|
||||
msgid "Click to close"
|
||||
msgstr "Clicca per chiudere"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:966
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:970
|
||||
msgid "You have unsaved changes!"
|
||||
msgstr "Hai cambiamenti non salvati!"
|
||||
|
||||
@@ -644,20 +644,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:388
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:393
|
||||
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:424
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:429
|
||||
msgid "required"
|
||||
msgstr "richiesto"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:527
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:546
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:532
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:551
|
||||
msgid "Time zone:"
|
||||
msgstr "Fuso orario:"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:537
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:542
|
||||
msgid "Your local time:"
|
||||
msgstr "Ora locale:"
|
||||
|
||||
|
||||
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: 2022-11-29 20:31+0000\n"
|
||||
"POT-Creation-Date: 2022-12-14 13:09+0000\n"
|
||||
"PO-Revision-Date: 2022-03-15 00:00+0000\n"
|
||||
"Last-Translator: Yuriko Matsunami <y.matsunami@enobyte.com>\n"
|
||||
"Language-Team: Japanese <https://translate.pretix.eu/projects/pretix/pretix-"
|
||||
@@ -571,15 +571,15 @@ msgstr "検索ワード"
|
||||
msgid "Selected only"
|
||||
msgstr "選択したもののみ"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:858
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:862
|
||||
msgid "Use a different name internally"
|
||||
msgstr "内部で別の名前を使用してください"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:894
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:898
|
||||
msgid "Click to close"
|
||||
msgstr "クリックして閉じる"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:966
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:970
|
||||
msgid "You have unsaved changes!"
|
||||
msgstr "保存されていない変更があります!"
|
||||
|
||||
@@ -639,20 +639,20 @@ msgstr "%(currency)s %(amount)s が払い戻されます"
|
||||
msgid "Please enter the amount the organizer can keep."
|
||||
msgstr "イベント開催者が受け取る料金を入力してください。"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:388
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:393
|
||||
msgid "Please enter a quantity for one of the ticket types."
|
||||
msgstr "商品の総数を入力してください。"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:424
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:429
|
||||
msgid "required"
|
||||
msgstr "必須"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:527
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:546
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:532
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:551
|
||||
msgid "Time zone:"
|
||||
msgstr "タイムゾーン:"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:537
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:542
|
||||
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: 2022-11-29 20:31+0000\n"
|
||||
"POT-Creation-Date: 2022-12-14 13:09+0000\n"
|
||||
"PO-Revision-Date: 2022-04-06 03:00+0000\n"
|
||||
"Last-Translator: Liga V <lerning_by_dreaming@gmx.de>\n"
|
||||
"Language-Team: Latvian <https://translate.pretix.eu/projects/pretix/pretix-"
|
||||
@@ -578,15 +578,15 @@ msgstr "Meklēšanas pieprasījums"
|
||||
msgid "Selected only"
|
||||
msgstr "Tikai atzīmētos"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:858
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:862
|
||||
msgid "Use a different name internally"
|
||||
msgstr "Izmantojiet citu nosaukumu iekšēji"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:894
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:898
|
||||
msgid "Click to close"
|
||||
msgstr "Noklikšķiniet, lai aizvērtu"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:966
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:970
|
||||
msgid "You have unsaved changes!"
|
||||
msgstr "Jums ir nesaglabātas izmaiņas!"
|
||||
|
||||
@@ -650,20 +650,20 @@ msgstr "Jūs saņemsiet %(valūta)s %(cena)s atpakaļ"
|
||||
msgid "Please enter the amount the organizer can keep."
|
||||
msgstr "Lūdzu ievadiet skaitu (summu), ko pasākuma organizators var paturēt."
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:388
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:393
|
||||
msgid "Please enter a quantity for one of the ticket types."
|
||||
msgstr "Lūdzu, ievadiet nepieciešamo daudzumu izvēlētajam biļešu veidam."
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:424
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:429
|
||||
msgid "required"
|
||||
msgstr "obligāts"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:527
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:546
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:532
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:551
|
||||
msgid "Time zone:"
|
||||
msgstr "Laika zona:"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:537
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:542
|
||||
msgid "Your local time:"
|
||||
msgstr "Vietējais laiks:"
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user