forked from CGM_Public/pretix_original
Compare commits
36
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2b40c9f44c | ||
|
|
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 |
@@ -23,10 +23,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
|
||||
|
||||
@@ -21,10 +21,10 @@ jobs:
|
||||
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 +47,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
|
||||
|
||||
@@ -21,10 +21,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
|
||||
@@ -42,10 +42,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 +63,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
|
||||
|
||||
@@ -60,7 +60,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
|
||||
|
||||
@@ -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'] = {}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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'])
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:'):
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
@@ -85,6 +86,7 @@ INVALID_ADDRESS = 'invalid-pretix-mail-address'
|
||||
|
||||
|
||||
class TolerantDict(dict):
|
||||
# kept for backwards compatibility with plugins
|
||||
|
||||
def __missing__(self, key):
|
||||
return key
|
||||
@@ -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)
|
||||
|
||||
@@ -177,7 +179,7 @@ def mail(email: Union[str, Sequence[str]], subject: str, template: Union[str, La
|
||||
})
|
||||
renderer = ClassicMailRenderer(None, organizer)
|
||||
content_plain = body_plain = render_mail(template, context)
|
||||
subject = str(subject).format_map(TolerantDict(context))
|
||||
subject = format_map(str(subject), context)
|
||||
sender = (
|
||||
sender or
|
||||
(event.settings.get('mail_from') if event else None) or
|
||||
@@ -608,7 +610,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)
|
||||
|
||||
@@ -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
|
||||
@@ -1029,6 +1029,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')
|
||||
@@ -1042,25 +1045,52 @@ def _perform_order(event: Event, payment_requests: List[dict], position_ids: Lis
|
||||
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)
|
||||
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 +1130,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:
|
||||
|
||||
@@ -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'))
|
||||
|
||||
@@ -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" %}
|
||||
|
||||
@@ -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())
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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])
|
||||
)
|
||||
]
|
||||
})
|
||||
|
||||
@@ -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)
|
||||
@@ -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):
|
||||
|
||||
@@ -4,8 +4,8 @@ msgstr ""
|
||||
"Project-Id-Version: 1\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2022-11-29 20:30+0000\n"
|
||||
"PO-Revision-Date: 2022-09-23 22:00+0000\n"
|
||||
"Last-Translator: Anna-itk <abc@aarhus.dk>\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/da/"
|
||||
">\n"
|
||||
"Language: da\n"
|
||||
@@ -13,7 +13,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.14.1\n"
|
||||
"X-Generator: Weblate 4.14.2\n"
|
||||
|
||||
#: htmlcov/pretix_control_views_dashboards_py.html:963
|
||||
#: pretix/control/templates/pretixcontrol/events/index.html:140
|
||||
@@ -66,9 +66,8 @@ msgid "pretixSCAN (online only, no order sync)"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/api/auth/devicesecurity.py:146
|
||||
#, fuzzy
|
||||
msgid "pretixPOS"
|
||||
msgstr "Standard"
|
||||
msgstr "pretixPOS"
|
||||
|
||||
#: pretix/api/models.py:39 pretix/base/models/customers.py:388
|
||||
msgid "Application name"
|
||||
@@ -290,7 +289,7 @@ msgstr "Ny bestilling afgivet"
|
||||
|
||||
#: pretix/api/webhooks.py:209 pretix/base/notifications.py:239
|
||||
msgid "New order requires approval"
|
||||
msgstr "Ny ordre kræver godkendelse"
|
||||
msgstr "Ny bestilling kræver godkendelse"
|
||||
|
||||
#: pretix/api/webhooks.py:213 pretix/base/notifications.py:245
|
||||
msgid "Order marked as paid"
|
||||
@@ -304,23 +303,20 @@ msgid "Order canceled"
|
||||
msgstr "Bestilling annulleret"
|
||||
|
||||
#: pretix/api/webhooks.py:221 pretix/base/notifications.py:257
|
||||
#, fuzzy
|
||||
msgid "Order reactivated"
|
||||
msgstr "Bestiiling udløbet"
|
||||
msgstr "Bestilling genaktiveret"
|
||||
|
||||
#: pretix/api/webhooks.py:225 pretix/base/notifications.py:263
|
||||
msgid "Order expired"
|
||||
msgstr "Bestiiling udløbet"
|
||||
msgstr "Bestilling udløbet"
|
||||
|
||||
#: pretix/api/webhooks.py:229
|
||||
#, fuzzy
|
||||
#| msgid "The order's expiry date has been changed."
|
||||
msgid "Order expiry date changed"
|
||||
msgstr "Bestillingens udløbsdato er blevet ændret."
|
||||
msgstr "Bestillingens udløbsdato ændret"
|
||||
|
||||
#: pretix/api/webhooks.py:233 pretix/base/notifications.py:269
|
||||
msgid "Order information changed"
|
||||
msgstr ""
|
||||
msgstr "Bestillingsoplysninger ændret"
|
||||
|
||||
#: pretix/api/webhooks.py:237 pretix/base/notifications.py:275
|
||||
msgid "Order contact address changed"
|
||||
@@ -332,46 +328,36 @@ msgid "Order changed"
|
||||
msgstr "Bestilling ændret"
|
||||
|
||||
#: pretix/api/webhooks.py:245
|
||||
#, fuzzy
|
||||
msgid "Refund of payment created"
|
||||
msgstr "Aktiver betalingsmetode"
|
||||
msgstr "Tilbagebetaling oprettet"
|
||||
|
||||
#: pretix/api/webhooks.py:249 pretix/base/notifications.py:293
|
||||
#, fuzzy
|
||||
msgid "External refund of payment"
|
||||
msgstr "Udvid betalingsfrist"
|
||||
msgstr "Ekstern tilbagebetaling"
|
||||
|
||||
#: pretix/api/webhooks.py:253
|
||||
#, fuzzy
|
||||
#| msgid "Text (requested by user)"
|
||||
msgid "Refund of payment requested by customer"
|
||||
msgstr "Tekst (forespurgt af bruger)"
|
||||
msgstr "Tilbagebetaling anmodet af kunden"
|
||||
|
||||
#: pretix/api/webhooks.py:257
|
||||
#, fuzzy
|
||||
#| msgid "Payment completed."
|
||||
msgid "Refund of payment completed"
|
||||
msgstr "Betaling gennemført."
|
||||
msgstr "Tilbagebetaling gennemført"
|
||||
|
||||
#: pretix/api/webhooks.py:261
|
||||
#, fuzzy
|
||||
msgid "Refund of payment canceled"
|
||||
msgstr "Bestillingen er blevet annulleret."
|
||||
msgstr "Tilbagebetaling annulleret"
|
||||
|
||||
#: pretix/api/webhooks.py:265
|
||||
#, fuzzy
|
||||
msgid "Refund of payment failed"
|
||||
msgstr "Tilbagebetal bestilling"
|
||||
msgstr "Tilbagebetaling mislykkedes"
|
||||
|
||||
#: pretix/api/webhooks.py:269
|
||||
#, fuzzy
|
||||
msgid "Order approved"
|
||||
msgstr "Bestiiling udløbet"
|
||||
msgstr "Bestilling godkendt"
|
||||
|
||||
#: pretix/api/webhooks.py:273
|
||||
#, fuzzy
|
||||
msgid "Order denied"
|
||||
msgstr "Bestiiling udløbet"
|
||||
msgstr "Bestilling afvist"
|
||||
|
||||
#: pretix/api/webhooks.py:277
|
||||
#, fuzzy
|
||||
@@ -383,25 +369,21 @@ msgid "Ticket check-in reverted"
|
||||
msgstr "Billet-check-in annulleret"
|
||||
|
||||
#: pretix/api/webhooks.py:285
|
||||
#, fuzzy
|
||||
msgid "Event created"
|
||||
msgstr "Arrangementsdato"
|
||||
msgstr "Arrangement oprettet"
|
||||
|
||||
#: pretix/api/webhooks.py:289
|
||||
#, fuzzy
|
||||
msgid "Event details changed"
|
||||
msgstr "Arrangementsdatoer"
|
||||
msgstr "Arrangementsoplysninger ændret"
|
||||
|
||||
#: pretix/api/webhooks.py:293
|
||||
#, fuzzy
|
||||
msgid "Event deleted"
|
||||
msgstr "Arrangementsdato"
|
||||
msgstr "Arrangement slettet"
|
||||
|
||||
#: pretix/api/webhooks.py:297
|
||||
#, fuzzy
|
||||
msgctxt "subevent"
|
||||
msgid "Event series date added"
|
||||
msgstr "Arrangements startdato og -tidspunkt"
|
||||
msgstr "Dato for arrangementsserien tilføjet"
|
||||
|
||||
#: pretix/api/webhooks.py:301
|
||||
#, fuzzy
|
||||
@@ -416,10 +398,8 @@ msgid "Event series date deleted"
|
||||
msgstr "Arrangements startdato og -tidspunkt"
|
||||
|
||||
#: pretix/api/webhooks.py:309
|
||||
#, fuzzy
|
||||
#| msgid "Shop not live"
|
||||
msgid "Shop taken live"
|
||||
msgstr "Butik ikke i luften"
|
||||
msgstr "Butik gået i luften"
|
||||
|
||||
#: pretix/api/webhooks.py:313
|
||||
#, fuzzy
|
||||
@@ -428,19 +408,17 @@ msgid "Shop taken offline"
|
||||
msgstr "Butik ikke i luften"
|
||||
|
||||
#: pretix/api/webhooks.py:317
|
||||
#, fuzzy
|
||||
msgid "Test-Mode of shop has been activated"
|
||||
msgstr "Bestilling oprettet."
|
||||
msgstr "Butikkens testtilstand er blevet aktiveret"
|
||||
|
||||
#: pretix/api/webhooks.py:321
|
||||
#, fuzzy
|
||||
msgid "Test-Mode of shop has been deactivated"
|
||||
msgstr "Bestilling oprettet."
|
||||
msgstr "Butikkens testtilstand er blevet deaktiveret"
|
||||
|
||||
#: pretix/base/auth.py:143
|
||||
#, python-brace-format
|
||||
msgid "{system} User"
|
||||
msgstr ""
|
||||
msgstr "{system} Bruger"
|
||||
|
||||
#: pretix/base/auth.py:152 pretix/base/exporters/customers.py:65
|
||||
#: pretix/base/forms/auth.py:246 pretix/base/models/auth.py:246
|
||||
@@ -457,7 +435,7 @@ msgstr ""
|
||||
#: pretix/presale/templates/pretixpresale/event/order.html:306
|
||||
#: pretix/presale/templates/pretixpresale/organizers/customer_profile.html:26
|
||||
msgid "E-mail"
|
||||
msgstr "E-mailadresse"
|
||||
msgstr "E-mail"
|
||||
|
||||
#: pretix/base/auth.py:154 pretix/base/forms/auth.py:153
|
||||
#: pretix/base/forms/auth.py:207 pretix/base/models/customers.py:95
|
||||
@@ -468,7 +446,7 @@ msgstr "Adgangskode"
|
||||
|
||||
#: pretix/base/channels.py:119
|
||||
msgid "Online shop"
|
||||
msgstr ""
|
||||
msgstr "Netbutik"
|
||||
|
||||
#: pretix/base/context.py:45
|
||||
#, python-brace-format
|
||||
@@ -550,7 +528,7 @@ msgstr "Standard"
|
||||
|
||||
#: pretix/base/email.py:207
|
||||
msgid "Simple with logo"
|
||||
msgstr ""
|
||||
msgstr "Enkelt med logo"
|
||||
|
||||
#: pretix/base/email.py:536 pretix/base/email.py:618 pretix/base/email.py:634
|
||||
#: pretix/base/email.py:639 pretix/base/pdf.py:164 pretix/base/pdf.py:322
|
||||
@@ -729,9 +707,8 @@ msgstr "E-mailadresse"
|
||||
|
||||
#: pretix/base/exporters/customers.py:77 pretix/base/models/customers.py:100
|
||||
#: pretix/control/templates/pretixcontrol/organizers/customer.html:67
|
||||
#, fuzzy
|
||||
msgid "Last login"
|
||||
msgstr "Login"
|
||||
msgstr "Seneste login"
|
||||
|
||||
#: pretix/base/exporters/customers.py:78 pretix/base/models/customers.py:101
|
||||
#: pretix/control/templates/pretixcontrol/organizers/customer.html:65
|
||||
@@ -1720,9 +1697,8 @@ msgstr "Fakturanumre"
|
||||
#: pretix/base/exporters/orderlist.py:629 pretix/base/orderimport.py:651
|
||||
#: pretix/control/forms/filter.py:538
|
||||
#: pretix/control/templates/pretixcontrol/order/index.html:181
|
||||
#, fuzzy
|
||||
msgid "Sales channel"
|
||||
msgstr "Gem ændringer"
|
||||
msgstr "Salgskanal"
|
||||
|
||||
#: pretix/base/exporters/orderlist.py:295
|
||||
#: pretix/base/exporters/orderlist.py:821 pretix/base/models/orders.py:219
|
||||
@@ -2005,7 +1981,7 @@ msgstr "Tjek-ind-status"
|
||||
#: pretix/plugins/checkinlists/exporters.py:332
|
||||
#: pretix/presale/templates/pretixpresale/event/order.html:66
|
||||
msgid "Order"
|
||||
msgstr "Rækkefølge"
|
||||
msgstr "Bestilling"
|
||||
|
||||
#: pretix/base/exporters/orderlist.py:820
|
||||
#: pretix/base/exporters/orderlist.py:997
|
||||
@@ -2936,13 +2912,12 @@ msgid ""
|
||||
msgstr ""
|
||||
|
||||
#: pretix/base/models/checkin.py:255
|
||||
#, fuzzy
|
||||
msgid "Entry"
|
||||
msgstr "Land"
|
||||
msgstr "Indgang"
|
||||
|
||||
#: pretix/base/models/checkin.py:256
|
||||
msgid "Exit"
|
||||
msgstr ""
|
||||
msgstr "Udgang"
|
||||
|
||||
#: pretix/base/models/checkin.py:271
|
||||
msgid "Unknown ticket"
|
||||
@@ -2974,7 +2949,7 @@ msgstr "Standardtidszone"
|
||||
|
||||
#: pretix/base/models/checkin.py:277
|
||||
msgid "Ticket type not allowed here"
|
||||
msgstr "Billettype ikke tilladt her"
|
||||
msgstr "Billettype er ikke tilladt her"
|
||||
|
||||
#: pretix/base/models/checkin.py:278
|
||||
msgid "Ticket code is ambiguous on list"
|
||||
@@ -3816,7 +3791,7 @@ msgstr ""
|
||||
#: pretix/base/models/items.py:863 pretix/base/models/vouchers.py:262
|
||||
#: pretix/base/models/waitinglist.py:96 pretix/base/orderimport.py:284
|
||||
msgid "Product variation"
|
||||
msgstr "Produktvariant"
|
||||
msgstr "Produktvariation"
|
||||
|
||||
#: pretix/base/models/items.py:864
|
||||
#: pretix/control/templates/pretixcontrol/item/create.html:64
|
||||
@@ -5524,26 +5499,26 @@ msgstr "Gratis"
|
||||
|
||||
#: pretix/base/payment.py:993
|
||||
msgid "Box office"
|
||||
msgstr ""
|
||||
msgstr "Box office"
|
||||
|
||||
#: pretix/base/payment.py:1041 pretix/base/payment.py:1060
|
||||
#: pretix/plugins/manualpayment/apps.py:30
|
||||
#: pretix/plugins/manualpayment/apps.py:33
|
||||
#, fuzzy
|
||||
msgid "Manual payment"
|
||||
msgstr "Ugyldige betalinger"
|
||||
msgstr "Manuel betaling"
|
||||
|
||||
#: pretix/base/payment.py:1046 pretix/plugins/banktransfer/payment.py:188
|
||||
msgid ""
|
||||
"In test mode, you can just manually mark this order as paid in the backend "
|
||||
"after it has been created."
|
||||
msgstr ""
|
||||
"I testtilstand kan du blot manuelt markere denne ordre som betalt i backend, "
|
||||
"efter den er oprettet."
|
||||
|
||||
#: pretix/base/payment.py:1067 pretix/base/payment.py:1200
|
||||
#: pretix/plugins/banktransfer/payment.py:147
|
||||
#, fuzzy
|
||||
msgid "Payment method name"
|
||||
msgstr "Betalingsmetode"
|
||||
msgstr "Navn på betalingsmetode"
|
||||
|
||||
#: pretix/base/payment.py:1071
|
||||
msgid "Payment process description during checkout"
|
||||
@@ -5581,14 +5556,12 @@ msgid ""
|
||||
msgstr ""
|
||||
|
||||
#: pretix/base/payment.py:1135
|
||||
#, fuzzy
|
||||
msgid "Offsetting"
|
||||
msgstr "Indstillinger"
|
||||
msgstr "Forskydning"
|
||||
|
||||
#: pretix/base/payment.py:1149 pretix/control/views/orders.py:967
|
||||
#, fuzzy
|
||||
msgid "You entered an order that could not be found."
|
||||
msgstr "Dine ændringer kunne ikke gemmes."
|
||||
msgstr "Du har indtastet en ordre, der ikke kunne findes."
|
||||
|
||||
#: pretix/base/payment.py:1178
|
||||
#, python-format
|
||||
@@ -12576,7 +12549,7 @@ msgstr "Den valgte dato er blevet slettet."
|
||||
|
||||
#: pretix/control/logdisplay.py:355
|
||||
msgid "The order has been created."
|
||||
msgstr "Bestilling oprettet."
|
||||
msgstr "Bestillingen er oprettet"
|
||||
|
||||
#: pretix/control/logdisplay.py:356
|
||||
msgid "The order requires approval before it can continue to be processed."
|
||||
@@ -13730,6 +13703,7 @@ msgstr ""
|
||||
#: pretix/presale/templates/pretixpresale/event/order_change.html:81
|
||||
#: pretix/presale/templates/pretixpresale/event/order_pay.html:27
|
||||
#: pretix/presale/templates/pretixpresale/event/order_pay_change.html:60
|
||||
#, fuzzy
|
||||
msgid "Continue"
|
||||
msgstr "Fortsæt"
|
||||
|
||||
@@ -22683,22 +22657,21 @@ msgstr ""
|
||||
#: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/import_base_organizer.html:4
|
||||
#: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/import_base_organizer.html:7
|
||||
msgid "Import bank data"
|
||||
msgstr "Importer bankdata"
|
||||
msgstr "Importér bankdata"
|
||||
|
||||
#: pretix/plugins/banktransfer/signals.py:61
|
||||
#: pretix/plugins/banktransfer/signals.py:97
|
||||
#, fuzzy
|
||||
msgid "Export refunds"
|
||||
msgstr "Udfør ændringer"
|
||||
msgstr "Eksportér refunderinger"
|
||||
|
||||
#: pretix/plugins/banktransfer/tasks.py:69
|
||||
#, fuzzy, python-format
|
||||
#, python-format
|
||||
msgid "Your order received an incomplete payment: %(code)s"
|
||||
msgstr "Din bestilling afventer betaling: %(code)s"
|
||||
msgstr "Din ordre modtog en ufuldstændig betaling: %(code)s"
|
||||
|
||||
#: pretix/plugins/banktransfer/tasks.py:170
|
||||
msgid "The order has already been canceled."
|
||||
msgstr "Denne bestilling er allerede blevet annulleret."
|
||||
msgstr "Ordren er allerede blevet annulleret."
|
||||
|
||||
#: pretix/plugins/banktransfer/tasks.py:180
|
||||
msgid "Automatic split to multiple orders not possible."
|
||||
@@ -22722,14 +22695,12 @@ msgid ""
|
||||
msgstr ""
|
||||
|
||||
#: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/control.html:8
|
||||
#, fuzzy
|
||||
msgid "Account"
|
||||
msgstr "Beløb"
|
||||
msgstr "Konto"
|
||||
|
||||
#: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/control.html:22
|
||||
#, fuzzy
|
||||
msgid "Transfer amount"
|
||||
msgstr "Navn"
|
||||
msgstr "Overførselsbeløb"
|
||||
|
||||
#: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/control.html:32
|
||||
msgid "Reference code"
|
||||
@@ -23256,7 +23227,7 @@ msgstr ""
|
||||
#: pretix/plugins/paypal2/payment.py:134 pretix/plugins/paypal2/payment.py:984
|
||||
#: pretix/plugins/paypal2/payment.py:985
|
||||
msgid "PayPal"
|
||||
msgstr ""
|
||||
msgstr "PayPal"
|
||||
|
||||
#: pretix/plugins/paypal/apps.py:53
|
||||
msgid ""
|
||||
@@ -24556,7 +24527,7 @@ msgstr ""
|
||||
|
||||
#: pretix/plugins/stripe/payment.py:243 pretix/plugins/stripe/payment.py:990
|
||||
msgid "giropay"
|
||||
msgstr ""
|
||||
msgstr "giropay"
|
||||
|
||||
#: pretix/plugins/stripe/payment.py:245 pretix/plugins/stripe/payment.py:252
|
||||
#: pretix/plugins/stripe/payment.py:259 pretix/plugins/stripe/payment.py:266
|
||||
@@ -24568,7 +24539,7 @@ msgstr ""
|
||||
|
||||
#: pretix/plugins/stripe/payment.py:250 pretix/plugins/stripe/payment.py:1062
|
||||
msgid "iDEAL"
|
||||
msgstr ""
|
||||
msgstr "iDEAL"
|
||||
|
||||
#: pretix/plugins/stripe/payment.py:257 pretix/plugins/stripe/payment.py:1114
|
||||
msgid "Alipay"
|
||||
@@ -24576,11 +24547,11 @@ msgstr ""
|
||||
|
||||
#: pretix/plugins/stripe/payment.py:264 pretix/plugins/stripe/payment.py:1157
|
||||
msgid "Bancontact"
|
||||
msgstr ""
|
||||
msgstr "Bancontact"
|
||||
|
||||
#: pretix/plugins/stripe/payment.py:271 pretix/plugins/stripe/payment.py:1229
|
||||
msgid "SOFORT"
|
||||
msgstr ""
|
||||
msgstr "SOFORT"
|
||||
|
||||
#: pretix/plugins/stripe/payment.py:276
|
||||
msgid ""
|
||||
@@ -24600,7 +24571,7 @@ msgstr ""
|
||||
|
||||
#: pretix/plugins/stripe/payment.py:299 pretix/plugins/stripe/payment.py:1428
|
||||
msgid "Przelewy24"
|
||||
msgstr ""
|
||||
msgstr "Przelewy24"
|
||||
|
||||
#: pretix/plugins/stripe/payment.py:306 pretix/plugins/stripe/payment.py:1484
|
||||
msgid "WeChat Pay"
|
||||
|
||||
@@ -7,8 +7,8 @@ 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"
|
||||
"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"
|
||||
@@ -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
@@ -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-11-29 20:31+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:858
|
||||
msgid "Use a different name internally"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:894
|
||||
msgid "Click to close"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js:966
|
||||
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:388
|
||||
msgid "Please enter a quantity for one of the ticket types."
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:424
|
||||
msgid "required"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:527
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:546
|
||||
msgid "Time zone:"
|
||||
msgstr ""
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js:537
|
||||
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 ""
|
||||
@@ -797,7 +797,7 @@ class SepaXMLExportForm(forms.Form):
|
||||
bic = BICFormField(label="BIC")
|
||||
|
||||
def set_initial_from_event(self, event: Event):
|
||||
banktransfer = event.get_payment_providers(cached=True)[BankTransfer.identifier]
|
||||
banktransfer = BankTransfer(event)
|
||||
self.initial["account_holder"] = banktransfer.settings.get("bank_details_sepa_name")
|
||||
self.initial["iban"] = banktransfer.settings.get("bank_details_sepa_iban")
|
||||
self.initial["bic"] = banktransfer.settings.get("bank_details_sepa_bic")
|
||||
|
||||
@@ -611,7 +611,7 @@ class PaypalMethod(BasePaymentProvider):
|
||||
pass
|
||||
if str(pp_captured_order.purchase_units[0].amount.value) != str(payment.amount) or \
|
||||
pp_captured_order.purchase_units[0].amount.currency_code != self.event.currency:
|
||||
logger.error('Value mismatch: Payment %s vs paypal trans %s' % (payment.id, str(pp_captured_order)))
|
||||
logger.error('Value mismatch: Payment %s vs paypal trans %s' % (payment.id, str(pp_captured_order.dict())))
|
||||
raise PaymentException(_('We were unable to process your payment. See below for details on how to '
|
||||
'proceed.'))
|
||||
|
||||
|
||||
@@ -149,19 +149,27 @@ class XHRView(View):
|
||||
else:
|
||||
cart_total = get_cart_total(request)
|
||||
cart_payments = cart_session(request).get('payments', [])
|
||||
for fee in get_fees(request.event, request, cart_total, None, cart_payments, get_cart(request)):
|
||||
multi_use_cart_payments = [p for p in cart_payments if p.get('multi_use_supported')]
|
||||
simulated_payments = multi_use_cart_payments + [{
|
||||
'provider': 'paypal',
|
||||
'multi_use_supported': False,
|
||||
'min_value': None,
|
||||
'max_value': None,
|
||||
'info_data': {},
|
||||
}]
|
||||
|
||||
for fee in get_fees(request.event, request, cart_total, None, simulated_payments, get_cart(request)):
|
||||
cart_total += fee.value
|
||||
|
||||
total_remaining = cart_total
|
||||
for p in cart_session(request).get('payments', []):
|
||||
if p['provider'] != 'paypal':
|
||||
if p.get('min_value') and total_remaining < Decimal(p['min_value']):
|
||||
continue
|
||||
for p in multi_use_cart_payments:
|
||||
if p.get('min_value') and total_remaining < Decimal(p['min_value']):
|
||||
continue
|
||||
|
||||
to_pay = total_remaining
|
||||
if p.get('max_value') and to_pay > Decimal(p['max_value']):
|
||||
to_pay = min(to_pay, Decimal(p['max_value']))
|
||||
total_remaining -= to_pay
|
||||
to_pay = total_remaining
|
||||
if p.get('max_value') and to_pay > Decimal(p['max_value']):
|
||||
to_pay = min(to_pay, Decimal(p['max_value']))
|
||||
total_remaining -= to_pay
|
||||
|
||||
cart_total = total_remaining
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ from pretix.base.models import Checkin, Event, InvoiceAddress, Order, User
|
||||
from pretix.base.services.mail import SendMailException, mail
|
||||
from pretix.base.services.tasks import ProfiledEventTask
|
||||
from pretix.celery_app import app
|
||||
from pretix.helpers.format import format_map
|
||||
|
||||
|
||||
@app.task(base=ProfiledEventTask, acks_late=True)
|
||||
@@ -116,8 +117,8 @@ def send_mails(event: Event, user: int, subject: dict, message: dict, orders: li
|
||||
user=user,
|
||||
data={
|
||||
'position': p.positionid,
|
||||
'subject': subject.localize(o.locale).format_map(email_context),
|
||||
'message': message.localize(o.locale).format_map(email_context),
|
||||
'subject': format_map(subject.localize(o.locale), email_context),
|
||||
'message': format_map(message.localize(o.locale), email_context),
|
||||
'recipient': p.attendee_email
|
||||
}
|
||||
)
|
||||
@@ -143,8 +144,8 @@ def send_mails(event: Event, user: int, subject: dict, message: dict, orders: li
|
||||
'pretix.plugins.sendmail.order.email.sent',
|
||||
user=user,
|
||||
data={
|
||||
'subject': subject.localize(o.locale).format_map(email_context),
|
||||
'message': message.localize(o.locale).format_map(email_context),
|
||||
'subject': format_map(subject.localize(o.locale), email_context),
|
||||
'message': format_map(message.localize(o.locale), email_context),
|
||||
'recipient': o.email
|
||||
}
|
||||
)
|
||||
|
||||
@@ -52,12 +52,12 @@ from pretix.base.email import get_available_placeholders
|
||||
from pretix.base.i18n import LazyI18nString, language
|
||||
from pretix.base.models import Checkin, LogEntry, Order, OrderPosition
|
||||
from pretix.base.models.event import SubEvent
|
||||
from pretix.base.services.mail import TolerantDict
|
||||
from pretix.base.templatetags.rich_text import markdown_compile_email
|
||||
from pretix.control.permissions import EventPermissionRequiredMixin
|
||||
from pretix.control.views import CreateView, PaginationMixin, UpdateView
|
||||
from pretix.plugins.sendmail.tasks import send_mails
|
||||
|
||||
from ...helpers.format import format_map
|
||||
from . import forms
|
||||
from .models import Rule, ScheduledMail
|
||||
|
||||
@@ -203,7 +203,7 @@ class SenderView(EventPermissionRequiredMixin, FormView):
|
||||
if self.request.POST.get("action") != "send":
|
||||
for l in self.request.event.settings.locales:
|
||||
with language(l, self.request.event.settings.region):
|
||||
context_dict = TolerantDict()
|
||||
context_dict = {}
|
||||
for k, v in get_available_placeholders(self.request.event, ['event', 'order',
|
||||
'position_or_address']).items():
|
||||
context_dict[k] = '<span class="placeholder" title="{}">{}</span>'.format(
|
||||
@@ -212,9 +212,9 @@ class SenderView(EventPermissionRequiredMixin, FormView):
|
||||
)
|
||||
|
||||
subject = bleach.clean(form.cleaned_data['subject'].localize(l), tags=[])
|
||||
preview_subject = subject.format_map(context_dict)
|
||||
preview_subject = format_map(subject, context_dict)
|
||||
message = form.cleaned_data['message'].localize(l)
|
||||
preview_text = markdown_compile_email(message.format_map(context_dict))
|
||||
preview_text = markdown_compile_email(format_map(message, context_dict))
|
||||
|
||||
self.output[l] = {
|
||||
'subject': _('Subject: {subject}').format(subject=preview_subject),
|
||||
@@ -350,7 +350,7 @@ class CreateRule(EventPermissionRequiredMixin, CreateView):
|
||||
if self.request.POST.get("action") == "preview":
|
||||
for l in self.request.event.settings.locales:
|
||||
with language(l, self.request.event.settings.region):
|
||||
context_dict = TolerantDict()
|
||||
context_dict = {}
|
||||
for k, v in get_available_placeholders(self.request.event, ['event', 'order',
|
||||
'position_or_address']).items():
|
||||
context_dict[k] = '<span class="placeholder" title="{}">{}</span>'.format(
|
||||
@@ -359,9 +359,9 @@ class CreateRule(EventPermissionRequiredMixin, CreateView):
|
||||
)
|
||||
|
||||
subject = bleach.clean(form.cleaned_data['subject'].localize(l), tags=[])
|
||||
preview_subject = subject.format_map(context_dict)
|
||||
preview_subject = format_map(subject, context_dict)
|
||||
template = form.cleaned_data['template'].localize(l)
|
||||
preview_text = markdown_compile_email(template.format_map(context_dict))
|
||||
preview_text = markdown_compile_email(format_map(template, context_dict))
|
||||
|
||||
self.output[l] = {
|
||||
'subject': _('Subject: {subject}').format(subject=preview_subject),
|
||||
@@ -427,7 +427,7 @@ class UpdateRule(EventPermissionRequiredMixin, UpdateView):
|
||||
|
||||
for lang in self.request.event.settings.locales:
|
||||
with language(lang, self.request.event.settings.region):
|
||||
placeholders = TolerantDict()
|
||||
placeholders = {}
|
||||
for k, v in get_available_placeholders(self.request.event, ['event', 'order', 'position_or_address']).items():
|
||||
placeholders[k] = '<span class="placeholder" title="{}">{}</span>'.format(
|
||||
_('This value will be replaced based on dynamic parameters.'),
|
||||
@@ -435,9 +435,9 @@ class UpdateRule(EventPermissionRequiredMixin, UpdateView):
|
||||
)
|
||||
|
||||
subject = bleach.clean(self.object.subject.localize(lang), tags=[])
|
||||
preview_subject = subject.format_map(placeholders)
|
||||
preview_subject = format_map(subject, placeholders)
|
||||
template = self.object.template.localize(lang)
|
||||
preview_text = markdown_compile_email(template.format_map(placeholders))
|
||||
preview_text = markdown_compile_email(format_map(template, placeholders))
|
||||
|
||||
o[lang] = {
|
||||
'subject': _('Subject: {subject}'.format(subject=preview_subject)),
|
||||
|
||||
@@ -113,6 +113,7 @@ class ContactForm(forms.Form):
|
||||
class InvoiceAddressForm(BaseInvoiceAddressForm):
|
||||
required_css_class = 'required'
|
||||
vat_warning = True
|
||||
address_validation = True
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
allow_save = kwargs.pop('allow_save', False)
|
||||
|
||||
@@ -30,6 +30,7 @@ from django.utils.translation import gettext as _
|
||||
|
||||
from pretix.base.email import get_email_context
|
||||
from pretix.base.models import Event
|
||||
from pretix.helpers.format import format_map
|
||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||
|
||||
|
||||
@@ -112,9 +113,6 @@ def get_private_icals(event, positions):
|
||||
- It would be pretty hard to implement it in a way that doesn't require us to use distinct
|
||||
settings fields for emails to customers and to attendees, which feels like an overcomplication.
|
||||
"""
|
||||
|
||||
from pretix.base.services.mail import TolerantDict
|
||||
|
||||
tz = pytz.timezone(event.settings.timezone)
|
||||
|
||||
creation_time = datetime.datetime.now(pytz.utc)
|
||||
@@ -131,7 +129,7 @@ def get_private_icals(event, positions):
|
||||
|
||||
if event.settings.mail_attach_ical_description:
|
||||
ctx = get_email_context(event=event, event_or_subevent=ev)
|
||||
description = str(event.settings.mail_attach_ical_description).format_map(TolerantDict(ctx))
|
||||
description = format_map(str(event.settings.mail_attach_ical_description), ctx)
|
||||
else:
|
||||
# Default description
|
||||
descr = []
|
||||
|
||||
@@ -377,6 +377,7 @@ INSTALLED_APPS = [
|
||||
'django_countries',
|
||||
'hijack',
|
||||
'oauth2_provider',
|
||||
'localflavor',
|
||||
'phonenumber_field'
|
||||
]
|
||||
|
||||
@@ -860,6 +861,7 @@ AUTH_PASSWORD_VALIDATORS = [
|
||||
OAUTH2_PROVIDER_APPLICATION_MODEL = 'pretixapi.OAuthApplication'
|
||||
OAUTH2_PROVIDER_GRANT_MODEL = 'pretixapi.OAuthGrant'
|
||||
OAUTH2_PROVIDER_ACCESS_TOKEN_MODEL = 'pretixapi.OAuthAccessToken'
|
||||
OAUTH2_PROVIDER_ID_TOKEN_MODEL = 'pretixapi.OAuthIDToken'
|
||||
OAUTH2_PROVIDER_REFRESH_TOKEN_MODEL = 'pretixapi.OAuthRefreshToken'
|
||||
OAUTH2_PROVIDER = {
|
||||
'SCOPES': {
|
||||
@@ -871,7 +873,8 @@ OAUTH2_PROVIDER = {
|
||||
'ALLOWED_REDIRECT_URI_SCHEMES': ['https'] if not DEBUG else ['http', 'https'],
|
||||
'ACCESS_TOKEN_EXPIRE_SECONDS': 3600 * 24,
|
||||
'ROTATE_REFRESH_TOKEN': False,
|
||||
|
||||
'PKCE_REQUIRED': False,
|
||||
'OIDC_RESPONSE_TYPES_SUPPORTED': ["code"], # We don't support proper OIDC for now
|
||||
}
|
||||
|
||||
COUNTRIES_OVERRIDE = {
|
||||
|
||||
+124
-124
@@ -8,14 +8,14 @@
|
||||
"name": "pretix",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.19.6",
|
||||
"@babel/core": "^7.20.5",
|
||||
"@babel/preset-env": "^7.20.2",
|
||||
"@rollup/plugin-babel": "^6.0.2",
|
||||
"@rollup/plugin-babel": "^6.0.3",
|
||||
"@rollup/plugin-node-resolve": "^15.0.1",
|
||||
"rollup": "^2.79.1",
|
||||
"rollup-plugin-vue": "^5.0.1",
|
||||
"vue": "^2.7.13",
|
||||
"vue-template-compiler": "^2.7.13"
|
||||
"vue": "^2.7.14",
|
||||
"vue-template-compiler": "^2.7.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@ampproject/remapping": {
|
||||
@@ -50,20 +50,20 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/core": {
|
||||
"version": "7.19.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.19.6.tgz",
|
||||
"integrity": "sha512-D2Ue4KHpc6Ys2+AxpIx1BZ8+UegLLLE2p3KJEuJRKmokHOtl49jQ5ny1773KsGLZs8MQvBidAF6yWUJxRqtKtg==",
|
||||
"version": "7.20.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.5.tgz",
|
||||
"integrity": "sha512-UdOWmk4pNWTm/4DlPUl/Pt4Gz4rcEMb7CY0Y3eJl5Yz1vI8ZJGmHWaVE55LoxRjdpx0z259GE9U5STA9atUinQ==",
|
||||
"dependencies": {
|
||||
"@ampproject/remapping": "^2.1.0",
|
||||
"@babel/code-frame": "^7.18.6",
|
||||
"@babel/generator": "^7.19.6",
|
||||
"@babel/helper-compilation-targets": "^7.19.3",
|
||||
"@babel/helper-module-transforms": "^7.19.6",
|
||||
"@babel/helpers": "^7.19.4",
|
||||
"@babel/parser": "^7.19.6",
|
||||
"@babel/generator": "^7.20.5",
|
||||
"@babel/helper-compilation-targets": "^7.20.0",
|
||||
"@babel/helper-module-transforms": "^7.20.2",
|
||||
"@babel/helpers": "^7.20.5",
|
||||
"@babel/parser": "^7.20.5",
|
||||
"@babel/template": "^7.18.10",
|
||||
"@babel/traverse": "^7.19.6",
|
||||
"@babel/types": "^7.19.4",
|
||||
"@babel/traverse": "^7.20.5",
|
||||
"@babel/types": "^7.20.5",
|
||||
"convert-source-map": "^1.7.0",
|
||||
"debug": "^4.1.0",
|
||||
"gensync": "^1.0.0-beta.2",
|
||||
@@ -98,11 +98,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/generator": {
|
||||
"version": "7.20.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.1.tgz",
|
||||
"integrity": "sha512-u1dMdBUmA7Z0rBB97xh8pIhviK7oItYOkjbsCxTWMknyvbQRBwX7/gn4JXurRdirWMFh+ZtYARqkA6ydogVZpg==",
|
||||
"version": "7.20.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.5.tgz",
|
||||
"integrity": "sha512-jl7JY2Ykn9S0yj4DQP82sYvPU+T3g0HFcWTqDLqiuA9tGRNIj9VfbtXGAYTTkyNEnQk1jkMGOdYka8aG/lulCA==",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.20.0",
|
||||
"@babel/types": "^7.20.5",
|
||||
"@jridgewell/gen-mapping": "^0.3.2",
|
||||
"jsesc": "^2.5.1"
|
||||
},
|
||||
@@ -295,18 +295,18 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-module-transforms": {
|
||||
"version": "7.19.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.19.6.tgz",
|
||||
"integrity": "sha512-fCmcfQo/KYr/VXXDIyd3CBGZ6AFhPFy1TfSEJ+PilGVlQT6jcbqtHAM4C1EciRqMza7/TpOUZliuSH+U6HAhJw==",
|
||||
"version": "7.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.20.2.tgz",
|
||||
"integrity": "sha512-zvBKyJXRbmK07XhMuujYoJ48B5yvvmM6+wcpv6Ivj4Yg6qO7NOZOSnvZN9CRl1zz1Z4cKf8YejmCMh8clOoOeA==",
|
||||
"dependencies": {
|
||||
"@babel/helper-environment-visitor": "^7.18.9",
|
||||
"@babel/helper-module-imports": "^7.18.6",
|
||||
"@babel/helper-simple-access": "^7.19.4",
|
||||
"@babel/helper-simple-access": "^7.20.2",
|
||||
"@babel/helper-split-export-declaration": "^7.18.6",
|
||||
"@babel/helper-validator-identifier": "^7.19.1",
|
||||
"@babel/template": "^7.18.10",
|
||||
"@babel/traverse": "^7.19.6",
|
||||
"@babel/types": "^7.19.4"
|
||||
"@babel/traverse": "^7.20.1",
|
||||
"@babel/types": "^7.20.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -364,11 +364,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-simple-access": {
|
||||
"version": "7.19.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.19.4.tgz",
|
||||
"integrity": "sha512-f9Xq6WqBFqaDfbCzn2w85hwklswz5qsKlh7f08w4Y9yhJHpnNC0QemtSkK5YyOY8kPGvyiwdzZksGUhnGdaUIg==",
|
||||
"version": "7.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz",
|
||||
"integrity": "sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.19.4"
|
||||
"@babel/types": "^7.20.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -435,13 +435,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helpers": {
|
||||
"version": "7.20.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.1.tgz",
|
||||
"integrity": "sha512-J77mUVaDTUJFZ5BpP6mMn6OIl3rEWymk2ZxDBQJUG3P+PbmyMcF3bYWvz0ma69Af1oobDqT/iAsvzhB58xhQUg==",
|
||||
"version": "7.20.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.6.tgz",
|
||||
"integrity": "sha512-Pf/OjgfgFRW5bApskEz5pvidpim7tEDPlFtKcNRXWmfHGn9IEI2W2flqRQXTFb7gIPTyK++N6rVHuwKut4XK6w==",
|
||||
"dependencies": {
|
||||
"@babel/template": "^7.18.10",
|
||||
"@babel/traverse": "^7.20.1",
|
||||
"@babel/types": "^7.20.0"
|
||||
"@babel/traverse": "^7.20.5",
|
||||
"@babel/types": "^7.20.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@@ -461,9 +461,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/parser": {
|
||||
"version": "7.20.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.1.tgz",
|
||||
"integrity": "sha512-hp0AYxaZJhxULfM1zyp7Wgr+pSUKBcP3M+PHnSzWGdXOzg/kHWIgiUWARvubhUKGOEw3xqY4x+lyZ9ytBVcELw==",
|
||||
"version": "7.20.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.5.tgz",
|
||||
"integrity": "sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA==",
|
||||
"bin": {
|
||||
"parser": "bin/babel-parser.js"
|
||||
},
|
||||
@@ -1527,18 +1527,18 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/traverse": {
|
||||
"version": "7.20.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.1.tgz",
|
||||
"integrity": "sha512-d3tN8fkVJwFLkHkBN479SOsw4DMZnz8cdbL/gvuDuzy3TS6Nfw80HuQqhw1pITbIruHyh7d1fMA47kWzmcUEGA==",
|
||||
"version": "7.20.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.5.tgz",
|
||||
"integrity": "sha512-WM5ZNN3JITQIq9tFZaw1ojLU3WgWdtkxnhM1AegMS+PvHjkM5IXjmYEGY7yukz5XS4sJyEf2VzWjI8uAavhxBQ==",
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.18.6",
|
||||
"@babel/generator": "^7.20.1",
|
||||
"@babel/generator": "^7.20.5",
|
||||
"@babel/helper-environment-visitor": "^7.18.9",
|
||||
"@babel/helper-function-name": "^7.19.0",
|
||||
"@babel/helper-hoist-variables": "^7.18.6",
|
||||
"@babel/helper-split-export-declaration": "^7.18.6",
|
||||
"@babel/parser": "^7.20.1",
|
||||
"@babel/types": "^7.20.0",
|
||||
"@babel/parser": "^7.20.5",
|
||||
"@babel/types": "^7.20.5",
|
||||
"debug": "^4.1.0",
|
||||
"globals": "^11.1.0"
|
||||
},
|
||||
@@ -1547,9 +1547,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/types": {
|
||||
"version": "7.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.2.tgz",
|
||||
"integrity": "sha512-FnnvsNWgZCr232sqtXggapvlkk/tuwR/qhGzcmxI0GXLCjmPYQPzio2FbdlWuY6y1sHFfQKk+rRbUZ9VStQMog==",
|
||||
"version": "7.20.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.5.tgz",
|
||||
"integrity": "sha512-c9fst/h2/dcF7H+MJKZ2T0KjEQ8hY/BNnDk/H3XY8C4Aw/eWQXWn/lWntHF9ooUBnGmEvbfGrTgLWc+um0YDUg==",
|
||||
"dependencies": {
|
||||
"@babel/helper-string-parser": "^7.19.4",
|
||||
"@babel/helper-validator-identifier": "^7.19.1",
|
||||
@@ -1610,9 +1610,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/plugin-babel": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-6.0.2.tgz",
|
||||
"integrity": "sha512-Vnt8XIWYwCf3MD7qhBWYlP9pjSZvcE++nlPXhQYw6YNehl5742AzFbrV6h4BHb20VAOVUlIksVLymQCTwVCGDg==",
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-6.0.3.tgz",
|
||||
"integrity": "sha512-fKImZKppa1A/gX73eg4JGo+8kQr/q1HBQaCGKECZ0v4YBBv3lFqi14+7xyApECzvkLTHCifx+7ntcrvtBIRcpg==",
|
||||
"dependencies": {
|
||||
"@babel/helper-module-imports": "^7.18.6",
|
||||
"@rollup/pluginutils": "^5.0.1"
|
||||
@@ -1710,9 +1710,9 @@
|
||||
"integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q=="
|
||||
},
|
||||
"node_modules/@vue/compiler-sfc": {
|
||||
"version": "2.7.13",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-2.7.13.tgz",
|
||||
"integrity": "sha512-zzu2rLRZlgIU+OT3Atbr7Y6PG+LW4wVQpPfNRrGDH3dM9PsrcVfa+1pKb8bW467bGM3aDOvAnsYLWVpYIv3GRg==",
|
||||
"version": "2.7.14",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-2.7.14.tgz",
|
||||
"integrity": "sha512-aNmNHyLPsw+sVvlQFQ2/8sjNuLtK54TC6cuKnVzAY93ks4ZBrvwQSnkkIh7bsbNhum5hJBS00wSDipQ937f5DA==",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.18.4",
|
||||
"postcss": "^8.4.14",
|
||||
@@ -1720,9 +1720,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-sfc/node_modules/postcss": {
|
||||
"version": "8.4.18",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.18.tgz",
|
||||
"integrity": "sha512-Wi8mWhncLJm11GATDaQKobXSNEYGUHeQLiQqDFG1qQ5UTDPTEvKw0Xt5NsTpktGTwLps3ByrWsBrG0rB8YQ9oA==",
|
||||
"version": "8.4.19",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.19.tgz",
|
||||
"integrity": "sha512-h+pbPsyhlYj6N2ozBmHhHrs9DzGmbaarbLvWipMRO7RLS+v4onj26MPFXA5OBYFxyqYhUJK456SwDcY9H2/zsA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
@@ -2358,9 +2358,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/decode-uri-component": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
|
||||
"integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=",
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz",
|
||||
"integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=0.10"
|
||||
@@ -3934,11 +3934,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vue": {
|
||||
"version": "2.7.13",
|
||||
"resolved": "https://registry.npmjs.org/vue/-/vue-2.7.13.tgz",
|
||||
"integrity": "sha512-QnM6ULTNnPmn71eUO+4hdjfBIA3H0GLsBnchnI/kS678tjI45GOUZhXd0oP/gX9isikXz1PAzSnkPspp9EUNfQ==",
|
||||
"version": "2.7.14",
|
||||
"resolved": "https://registry.npmjs.org/vue/-/vue-2.7.14.tgz",
|
||||
"integrity": "sha512-b2qkFyOM0kwqWFuQmgd4o+uHGU7T+2z3T+WQp8UBjADfEv2n4FEMffzBmCKNP0IGzOEEfYjvtcC62xaSKeQDrQ==",
|
||||
"dependencies": {
|
||||
"@vue/compiler-sfc": "2.7.13",
|
||||
"@vue/compiler-sfc": "2.7.14",
|
||||
"csstype": "^3.1.0"
|
||||
}
|
||||
},
|
||||
@@ -3948,9 +3948,9 @@
|
||||
"integrity": "sha512-pZfGp+PW/IXEOyETE09xQHR1CKkR9HfHZdnMD/FVLUNI+HxYTa82evx5WrF6Kz4s82qtqHvMZ8MZpbk2zT2E1Q=="
|
||||
},
|
||||
"node_modules/vue-template-compiler": {
|
||||
"version": "2.7.13",
|
||||
"resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.13.tgz",
|
||||
"integrity": "sha512-jYM6TClwDS9YqP48gYrtAtaOhRKkbYmbzE+Q51gX5YDr777n7tNI/IZk4QV4l/PjQPNh/FVa/E92sh/RqKMrog==",
|
||||
"version": "2.7.14",
|
||||
"resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.14.tgz",
|
||||
"integrity": "sha512-zyA5Y3ArvVG0NacJDkkzJuPQDF8RFeRlzV2vLeSnhSpieO6LK2OVbdLPi5MPPs09Ii+gMO8nY4S3iKQxBxDmWQ==",
|
||||
"dependencies": {
|
||||
"de-indent": "^1.0.2",
|
||||
"he": "^1.2.0"
|
||||
@@ -4049,20 +4049,20 @@
|
||||
"integrity": "sha512-EWZ4mE2diW3QALKvDMiXnbZpRvlj+nayZ112nK93SnhqOtpdsbVD4W+2tEoT3YNBAG9RBR0ISY758ZkOgsn6pQ=="
|
||||
},
|
||||
"@babel/core": {
|
||||
"version": "7.19.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.19.6.tgz",
|
||||
"integrity": "sha512-D2Ue4KHpc6Ys2+AxpIx1BZ8+UegLLLE2p3KJEuJRKmokHOtl49jQ5ny1773KsGLZs8MQvBidAF6yWUJxRqtKtg==",
|
||||
"version": "7.20.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.5.tgz",
|
||||
"integrity": "sha512-UdOWmk4pNWTm/4DlPUl/Pt4Gz4rcEMb7CY0Y3eJl5Yz1vI8ZJGmHWaVE55LoxRjdpx0z259GE9U5STA9atUinQ==",
|
||||
"requires": {
|
||||
"@ampproject/remapping": "^2.1.0",
|
||||
"@babel/code-frame": "^7.18.6",
|
||||
"@babel/generator": "^7.19.6",
|
||||
"@babel/helper-compilation-targets": "^7.19.3",
|
||||
"@babel/helper-module-transforms": "^7.19.6",
|
||||
"@babel/helpers": "^7.19.4",
|
||||
"@babel/parser": "^7.19.6",
|
||||
"@babel/generator": "^7.20.5",
|
||||
"@babel/helper-compilation-targets": "^7.20.0",
|
||||
"@babel/helper-module-transforms": "^7.20.2",
|
||||
"@babel/helpers": "^7.20.5",
|
||||
"@babel/parser": "^7.20.5",
|
||||
"@babel/template": "^7.18.10",
|
||||
"@babel/traverse": "^7.19.6",
|
||||
"@babel/types": "^7.19.4",
|
||||
"@babel/traverse": "^7.20.5",
|
||||
"@babel/types": "^7.20.5",
|
||||
"convert-source-map": "^1.7.0",
|
||||
"debug": "^4.1.0",
|
||||
"gensync": "^1.0.0-beta.2",
|
||||
@@ -4083,11 +4083,11 @@
|
||||
}
|
||||
},
|
||||
"@babel/generator": {
|
||||
"version": "7.20.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.1.tgz",
|
||||
"integrity": "sha512-u1dMdBUmA7Z0rBB97xh8pIhviK7oItYOkjbsCxTWMknyvbQRBwX7/gn4JXurRdirWMFh+ZtYARqkA6ydogVZpg==",
|
||||
"version": "7.20.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.5.tgz",
|
||||
"integrity": "sha512-jl7JY2Ykn9S0yj4DQP82sYvPU+T3g0HFcWTqDLqiuA9tGRNIj9VfbtXGAYTTkyNEnQk1jkMGOdYka8aG/lulCA==",
|
||||
"requires": {
|
||||
"@babel/types": "^7.20.0",
|
||||
"@babel/types": "^7.20.5",
|
||||
"@jridgewell/gen-mapping": "^0.3.2",
|
||||
"jsesc": "^2.5.1"
|
||||
},
|
||||
@@ -4229,18 +4229,18 @@
|
||||
}
|
||||
},
|
||||
"@babel/helper-module-transforms": {
|
||||
"version": "7.19.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.19.6.tgz",
|
||||
"integrity": "sha512-fCmcfQo/KYr/VXXDIyd3CBGZ6AFhPFy1TfSEJ+PilGVlQT6jcbqtHAM4C1EciRqMza7/TpOUZliuSH+U6HAhJw==",
|
||||
"version": "7.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.20.2.tgz",
|
||||
"integrity": "sha512-zvBKyJXRbmK07XhMuujYoJ48B5yvvmM6+wcpv6Ivj4Yg6qO7NOZOSnvZN9CRl1zz1Z4cKf8YejmCMh8clOoOeA==",
|
||||
"requires": {
|
||||
"@babel/helper-environment-visitor": "^7.18.9",
|
||||
"@babel/helper-module-imports": "^7.18.6",
|
||||
"@babel/helper-simple-access": "^7.19.4",
|
||||
"@babel/helper-simple-access": "^7.20.2",
|
||||
"@babel/helper-split-export-declaration": "^7.18.6",
|
||||
"@babel/helper-validator-identifier": "^7.19.1",
|
||||
"@babel/template": "^7.18.10",
|
||||
"@babel/traverse": "^7.19.6",
|
||||
"@babel/types": "^7.19.4"
|
||||
"@babel/traverse": "^7.20.1",
|
||||
"@babel/types": "^7.20.2"
|
||||
}
|
||||
},
|
||||
"@babel/helper-optimise-call-expression": {
|
||||
@@ -4280,11 +4280,11 @@
|
||||
}
|
||||
},
|
||||
"@babel/helper-simple-access": {
|
||||
"version": "7.19.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.19.4.tgz",
|
||||
"integrity": "sha512-f9Xq6WqBFqaDfbCzn2w85hwklswz5qsKlh7f08w4Y9yhJHpnNC0QemtSkK5YyOY8kPGvyiwdzZksGUhnGdaUIg==",
|
||||
"version": "7.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz",
|
||||
"integrity": "sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==",
|
||||
"requires": {
|
||||
"@babel/types": "^7.19.4"
|
||||
"@babel/types": "^7.20.2"
|
||||
}
|
||||
},
|
||||
"@babel/helper-skip-transparent-expression-wrappers": {
|
||||
@@ -4330,13 +4330,13 @@
|
||||
}
|
||||
},
|
||||
"@babel/helpers": {
|
||||
"version": "7.20.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.1.tgz",
|
||||
"integrity": "sha512-J77mUVaDTUJFZ5BpP6mMn6OIl3rEWymk2ZxDBQJUG3P+PbmyMcF3bYWvz0ma69Af1oobDqT/iAsvzhB58xhQUg==",
|
||||
"version": "7.20.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.6.tgz",
|
||||
"integrity": "sha512-Pf/OjgfgFRW5bApskEz5pvidpim7tEDPlFtKcNRXWmfHGn9IEI2W2flqRQXTFb7gIPTyK++N6rVHuwKut4XK6w==",
|
||||
"requires": {
|
||||
"@babel/template": "^7.18.10",
|
||||
"@babel/traverse": "^7.20.1",
|
||||
"@babel/types": "^7.20.0"
|
||||
"@babel/traverse": "^7.20.5",
|
||||
"@babel/types": "^7.20.5"
|
||||
}
|
||||
},
|
||||
"@babel/highlight": {
|
||||
@@ -4350,9 +4350,9 @@
|
||||
}
|
||||
},
|
||||
"@babel/parser": {
|
||||
"version": "7.20.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.1.tgz",
|
||||
"integrity": "sha512-hp0AYxaZJhxULfM1zyp7Wgr+pSUKBcP3M+PHnSzWGdXOzg/kHWIgiUWARvubhUKGOEw3xqY4x+lyZ9ytBVcELw=="
|
||||
"version": "7.20.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.5.tgz",
|
||||
"integrity": "sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA=="
|
||||
},
|
||||
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": {
|
||||
"version": "7.18.6",
|
||||
@@ -5045,26 +5045,26 @@
|
||||
}
|
||||
},
|
||||
"@babel/traverse": {
|
||||
"version": "7.20.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.1.tgz",
|
||||
"integrity": "sha512-d3tN8fkVJwFLkHkBN479SOsw4DMZnz8cdbL/gvuDuzy3TS6Nfw80HuQqhw1pITbIruHyh7d1fMA47kWzmcUEGA==",
|
||||
"version": "7.20.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.5.tgz",
|
||||
"integrity": "sha512-WM5ZNN3JITQIq9tFZaw1ojLU3WgWdtkxnhM1AegMS+PvHjkM5IXjmYEGY7yukz5XS4sJyEf2VzWjI8uAavhxBQ==",
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.18.6",
|
||||
"@babel/generator": "^7.20.1",
|
||||
"@babel/generator": "^7.20.5",
|
||||
"@babel/helper-environment-visitor": "^7.18.9",
|
||||
"@babel/helper-function-name": "^7.19.0",
|
||||
"@babel/helper-hoist-variables": "^7.18.6",
|
||||
"@babel/helper-split-export-declaration": "^7.18.6",
|
||||
"@babel/parser": "^7.20.1",
|
||||
"@babel/types": "^7.20.0",
|
||||
"@babel/parser": "^7.20.5",
|
||||
"@babel/types": "^7.20.5",
|
||||
"debug": "^4.1.0",
|
||||
"globals": "^11.1.0"
|
||||
}
|
||||
},
|
||||
"@babel/types": {
|
||||
"version": "7.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.2.tgz",
|
||||
"integrity": "sha512-FnnvsNWgZCr232sqtXggapvlkk/tuwR/qhGzcmxI0GXLCjmPYQPzio2FbdlWuY6y1sHFfQKk+rRbUZ9VStQMog==",
|
||||
"version": "7.20.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.5.tgz",
|
||||
"integrity": "sha512-c9fst/h2/dcF7H+MJKZ2T0KjEQ8hY/BNnDk/H3XY8C4Aw/eWQXWn/lWntHF9ooUBnGmEvbfGrTgLWc+um0YDUg==",
|
||||
"requires": {
|
||||
"@babel/helper-string-parser": "^7.19.4",
|
||||
"@babel/helper-validator-identifier": "^7.19.1",
|
||||
@@ -5112,9 +5112,9 @@
|
||||
}
|
||||
},
|
||||
"@rollup/plugin-babel": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-6.0.2.tgz",
|
||||
"integrity": "sha512-Vnt8XIWYwCf3MD7qhBWYlP9pjSZvcE++nlPXhQYw6YNehl5742AzFbrV6h4BHb20VAOVUlIksVLymQCTwVCGDg==",
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-6.0.3.tgz",
|
||||
"integrity": "sha512-fKImZKppa1A/gX73eg4JGo+8kQr/q1HBQaCGKECZ0v4YBBv3lFqi14+7xyApECzvkLTHCifx+7ntcrvtBIRcpg==",
|
||||
"requires": {
|
||||
"@babel/helper-module-imports": "^7.18.6",
|
||||
"@rollup/pluginutils": "^5.0.1"
|
||||
@@ -5176,9 +5176,9 @@
|
||||
"integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q=="
|
||||
},
|
||||
"@vue/compiler-sfc": {
|
||||
"version": "2.7.13",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-2.7.13.tgz",
|
||||
"integrity": "sha512-zzu2rLRZlgIU+OT3Atbr7Y6PG+LW4wVQpPfNRrGDH3dM9PsrcVfa+1pKb8bW467bGM3aDOvAnsYLWVpYIv3GRg==",
|
||||
"version": "2.7.14",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-2.7.14.tgz",
|
||||
"integrity": "sha512-aNmNHyLPsw+sVvlQFQ2/8sjNuLtK54TC6cuKnVzAY93ks4ZBrvwQSnkkIh7bsbNhum5hJBS00wSDipQ937f5DA==",
|
||||
"requires": {
|
||||
"@babel/parser": "^7.18.4",
|
||||
"postcss": "^8.4.14",
|
||||
@@ -5186,9 +5186,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"postcss": {
|
||||
"version": "8.4.18",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.18.tgz",
|
||||
"integrity": "sha512-Wi8mWhncLJm11GATDaQKobXSNEYGUHeQLiQqDFG1qQ5UTDPTEvKw0Xt5NsTpktGTwLps3ByrWsBrG0rB8YQ9oA==",
|
||||
"version": "8.4.19",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.19.tgz",
|
||||
"integrity": "sha512-h+pbPsyhlYj6N2ozBmHhHrs9DzGmbaarbLvWipMRO7RLS+v4onj26MPFXA5OBYFxyqYhUJK456SwDcY9H2/zsA==",
|
||||
"requires": {
|
||||
"nanoid": "^3.3.4",
|
||||
"picocolors": "^1.0.0",
|
||||
@@ -5677,9 +5677,9 @@
|
||||
"optional": true
|
||||
},
|
||||
"decode-uri-component": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
|
||||
"integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=",
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz",
|
||||
"integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==",
|
||||
"optional": true
|
||||
},
|
||||
"deepmerge": {
|
||||
@@ -6909,11 +6909,11 @@
|
||||
"optional": true
|
||||
},
|
||||
"vue": {
|
||||
"version": "2.7.13",
|
||||
"resolved": "https://registry.npmjs.org/vue/-/vue-2.7.13.tgz",
|
||||
"integrity": "sha512-QnM6ULTNnPmn71eUO+4hdjfBIA3H0GLsBnchnI/kS678tjI45GOUZhXd0oP/gX9isikXz1PAzSnkPspp9EUNfQ==",
|
||||
"version": "2.7.14",
|
||||
"resolved": "https://registry.npmjs.org/vue/-/vue-2.7.14.tgz",
|
||||
"integrity": "sha512-b2qkFyOM0kwqWFuQmgd4o+uHGU7T+2z3T+WQp8UBjADfEv2n4FEMffzBmCKNP0IGzOEEfYjvtcC62xaSKeQDrQ==",
|
||||
"requires": {
|
||||
"@vue/compiler-sfc": "2.7.13",
|
||||
"@vue/compiler-sfc": "2.7.14",
|
||||
"csstype": "^3.1.0"
|
||||
}
|
||||
},
|
||||
@@ -6923,9 +6923,9 @@
|
||||
"integrity": "sha512-pZfGp+PW/IXEOyETE09xQHR1CKkR9HfHZdnMD/FVLUNI+HxYTa82evx5WrF6Kz4s82qtqHvMZ8MZpbk2zT2E1Q=="
|
||||
},
|
||||
"vue-template-compiler": {
|
||||
"version": "2.7.13",
|
||||
"resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.13.tgz",
|
||||
"integrity": "sha512-jYM6TClwDS9YqP48gYrtAtaOhRKkbYmbzE+Q51gX5YDr777n7tNI/IZk4QV4l/PjQPNh/FVa/E92sh/RqKMrog==",
|
||||
"version": "2.7.14",
|
||||
"resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.14.tgz",
|
||||
"integrity": "sha512-zyA5Y3ArvVG0NacJDkkzJuPQDF8RFeRlzV2vLeSnhSpieO6LK2OVbdLPi5MPPs09Ii+gMO8nY4S3iKQxBxDmWQ==",
|
||||
"requires": {
|
||||
"de-indent": "^1.0.2",
|
||||
"he": "^1.2.0"
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
"private": true,
|
||||
"scripts": {},
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.19.6",
|
||||
"@babel/core": "^7.20.5",
|
||||
"@babel/preset-env": "^7.20.2",
|
||||
"@rollup/plugin-babel": "^6.0.2",
|
||||
"@rollup/plugin-babel": "^6.0.3",
|
||||
"@rollup/plugin-node-resolve": "^15.0.1",
|
||||
"vue": "^2.7.13",
|
||||
"vue": "^2.7.14",
|
||||
"rollup": "^2.79.1",
|
||||
"rollup-plugin-vue": "^5.0.1",
|
||||
"vue-template-compiler": "^2.7.13"
|
||||
"vue-template-compiler": "^2.7.14"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -499,6 +499,10 @@ var form_handlers = function (el) {
|
||||
|
||||
el.find('input[data-typeahead-url]').each(function () {
|
||||
var $inp = $(this);
|
||||
if ($inp.data("ttTypeahead") || $inp.hasClass("tt-hint")) {
|
||||
// Already initialized on this element
|
||||
return;
|
||||
}
|
||||
$inp.typeahead(null, {
|
||||
minLength: 1,
|
||||
highlight: true,
|
||||
|
||||
@@ -187,6 +187,11 @@ function setup_basics(el) {
|
||||
$($(this).attr("data-parent")).find(".collapse.in").collapse('hide');
|
||||
$($(this).attr("data-target")).collapse('show');
|
||||
});
|
||||
el.find("input[data-toggle=radiocollapse]:checked").each(function () {
|
||||
if (!$($(this).attr("data-parent")).find(".collapse.in").length) {
|
||||
$($(this).attr("data-target")).collapse('show');
|
||||
}
|
||||
});
|
||||
el.find(".js-only").removeClass("js-only");
|
||||
el.find(".js-hidden").hide();
|
||||
|
||||
@@ -287,14 +292,14 @@ $(function () {
|
||||
$(".js-copy-answers").click(function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
let idx = $(this).data('id');
|
||||
const addonDivs = $('div[data-idx="' + idx +'"]')
|
||||
var idx = $(this).data('id');
|
||||
var addonDivs = $('div[data-idx="' + idx +'"]');
|
||||
addonDivs.each(function (index) {
|
||||
const elements = $(this).find('input, select, textarea');
|
||||
var elements = $(this).find('input, select, textarea');
|
||||
|
||||
const addonIdx = $(this).attr("data-addonidx");
|
||||
const answersDiv = $('div[data-idx="0"][data-addonidx="' + addonIdx + '"]');
|
||||
const answers = answersDiv.find('input, select, textarea');
|
||||
var addonIdx = $(this).attr("data-addonidx");
|
||||
var answersDiv = $('div[data-idx="' + (idx - 1) + '"][data-addonidx="' + addonIdx + '"]');
|
||||
var answers = answersDiv.find('input, select, textarea');
|
||||
|
||||
copy_answers(elements, answers);
|
||||
})
|
||||
@@ -303,11 +308,11 @@ $(function () {
|
||||
$(".js-copy-answers-addon").click(function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const id = $(this).data('id');
|
||||
const addonId = $(this).data('addonid');
|
||||
const addonDiv = $('div[data-idx="' + id +'"][data-addonidx="' + addonId + '"]');
|
||||
const elements = addonDiv.find('input, select, textarea');
|
||||
const answers = $('*[data-idx="' + id + '"] input, *[data-idx="' + id + '"] select, *[data-idx="' + id + '"] textarea');
|
||||
var id = $(this).data('id');
|
||||
var addonId = $(this).data('addonid');
|
||||
var addonDiv = $('div[data-idx="' + id +'"][data-addonidx="' + addonId + '"]');
|
||||
var elements = addonDiv.find('input, select, textarea');
|
||||
var answers = $('[data-idx="' + id + '"][data-addonidx="' + (addonId - 1) + '"] input, [data-idx="' + id + '"][data-addonidx="' + (addonId - 1) + '"] select, [data-idx="' + id + '"][data-addonidx="' + (addonId - 1) + '"] textarea').reverse();
|
||||
copy_answers(elements, answers);
|
||||
return false;
|
||||
});
|
||||
@@ -711,6 +716,8 @@ function copy_answers(elements, answers) {
|
||||
input.prop("checked", answers.filter("[name$=" + suffix + "]").prop("checked"));
|
||||
}
|
||||
break;
|
||||
case "file":
|
||||
break
|
||||
default:
|
||||
input.val(answers.filter("[name$=" + suffix + "]").val());
|
||||
}
|
||||
|
||||
+8
-8
@@ -163,7 +163,7 @@ setup(
|
||||
'BeautifulSoup4==4.11.*',
|
||||
'bleach==5.0.*',
|
||||
'celery==4.4.*',
|
||||
'chardet==5.0.*',
|
||||
'chardet==5.1.*',
|
||||
'cryptography>=3.4.2',
|
||||
'css-inline==0.8.*',
|
||||
'defusedcsv>=1.1.0',
|
||||
@@ -182,7 +182,7 @@ setup(
|
||||
'django-localflavor==3.1',
|
||||
'django-markup',
|
||||
'django-mysql',
|
||||
'django-oauth-toolkit==1.2.*',
|
||||
'django-oauth-toolkit==2.2.*',
|
||||
'django-otp==1.1.*',
|
||||
'django-phonenumber-field==7.0.*',
|
||||
'django-redis==5.2.*',
|
||||
@@ -190,7 +190,7 @@ setup(
|
||||
'django-statici18n==2.3.*',
|
||||
'djangorestframework==3.14.*',
|
||||
'dnspython<2.0', # do not upgrade, causes issues with eventlet / gunicorn 19 and we cannot upgrade gunicorn right now
|
||||
'drf_ujson2==1.6.*',
|
||||
'drf_ujson2==1.7.*',
|
||||
'isoweek',
|
||||
'jsonschema',
|
||||
'kombu==4.6.*',
|
||||
@@ -239,20 +239,20 @@ setup(
|
||||
'coverage',
|
||||
'coveralls',
|
||||
'django-debug-toolbar==3.7.*',
|
||||
'flake8==5.0.*',
|
||||
'flake8==6.0.*',
|
||||
'freezegun',
|
||||
'isort==5.10.*',
|
||||
'pep8-naming==0.12.*',
|
||||
'potypo',
|
||||
'pycodestyle==2.9.*',
|
||||
'pyflakes==2.5.*',
|
||||
'pycodestyle==2.10.*',
|
||||
'pyflakes==3.0.*',
|
||||
'pytest-cache',
|
||||
'pytest-cov',
|
||||
'pytest-django==4.*',
|
||||
'pytest-mock==3.10.*',
|
||||
'pytest-rerunfailures==10.2',
|
||||
'pytest-rerunfailures==10.*',
|
||||
'pytest-sugar',
|
||||
'pytest-xdist==3.0.*',
|
||||
'pytest-xdist==3.1.*',
|
||||
'pytest==7.2.*',
|
||||
'responses',
|
||||
],
|
||||
|
||||
@@ -384,7 +384,8 @@ def test_item_detail_variations(token_client, organizer, event, team, item):
|
||||
"available_from": None,
|
||||
"available_until": None,
|
||||
"hide_without_voucher": False,
|
||||
"original_price": None
|
||||
"original_price": None,
|
||||
"meta_data": {}
|
||||
}]
|
||||
res["has_variations"] = True
|
||||
resp = token_client.get('/api/v1/organizers/{}/events/{}/items/{}/'.format(organizer.slug, event.slug,
|
||||
@@ -551,7 +552,10 @@ def test_item_create_with_variation(token_client, organizer, event, item, catego
|
||||
"description": None,
|
||||
"position": 0,
|
||||
"default_price": None,
|
||||
"price": "23.00"
|
||||
"price": "23.00",
|
||||
"meta_data": {
|
||||
"day": "Wednesday",
|
||||
},
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -564,6 +568,7 @@ def test_item_create_with_variation(token_client, organizer, event, item, catego
|
||||
assert new_item.variations.first().value.localize('en') == "Comment"
|
||||
assert new_item.variations.first().require_approval is True
|
||||
assert set(new_item.variations.first().sales_channels) == set(get_all_sales_channels().keys())
|
||||
assert new_item.variations.first().meta_data == {"day": "Wednesday"}
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@@ -1258,7 +1263,8 @@ TEST_VARIATIONS_RES = {
|
||||
"available_from": None,
|
||||
"available_until": None,
|
||||
"hide_without_voucher": False,
|
||||
"original_price": None
|
||||
"original_price": None,
|
||||
"meta_data": {}
|
||||
}
|
||||
|
||||
TEST_VARIATIONS_UPDATE = {
|
||||
@@ -1277,7 +1283,8 @@ TEST_VARIATIONS_UPDATE = {
|
||||
"available_from": None,
|
||||
"available_until": None,
|
||||
"hide_without_voucher": False,
|
||||
"original_price": None
|
||||
"original_price": None,
|
||||
"meta_data": {}
|
||||
}
|
||||
|
||||
|
||||
@@ -1314,7 +1321,10 @@ def test_variations_create(token_client, organizer, event, item, variation):
|
||||
"position": 1,
|
||||
"default_price": None,
|
||||
"original_price": "23.42",
|
||||
"price": 23.0
|
||||
"price": 23.0,
|
||||
"meta_data": {
|
||||
"day": "Wednesday",
|
||||
},
|
||||
},
|
||||
format='json'
|
||||
)
|
||||
@@ -1324,6 +1334,7 @@ def test_variations_create(token_client, organizer, event, item, variation):
|
||||
assert var.position == 1
|
||||
assert var.price == 23.0
|
||||
assert set(var.sales_channels) == set(get_all_sales_channels().keys())
|
||||
assert var.meta_data == {"day": "Wednesday"}
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@@ -1355,6 +1366,7 @@ def test_variations_update(token_client, organizer, event, item, item3, variatio
|
||||
res["price"] = "20.00"
|
||||
res["default_price"] = "20.00"
|
||||
res["original_price"] = "50.00"
|
||||
res["meta_data"] = {"day": "Thursday"}
|
||||
resp = token_client.patch(
|
||||
'/api/v1/organizers/{}/events/{}/items/{}/variations/{}/'.format(organizer.slug, event.slug, item.pk, variation.pk),
|
||||
{
|
||||
@@ -1364,7 +1376,10 @@ def test_variations_update(token_client, organizer, event, item, item3, variatio
|
||||
"position": 1,
|
||||
"sales_channels": ["web"],
|
||||
"default_price": "20.00",
|
||||
"original_price": "50.00"
|
||||
"original_price": "50.00",
|
||||
"meta_data": {
|
||||
"day": "Thursday",
|
||||
},
|
||||
},
|
||||
format='json'
|
||||
)
|
||||
|
||||
+38
-17
@@ -37,6 +37,7 @@ import json
|
||||
from urllib.parse import quote
|
||||
|
||||
import pytest
|
||||
from django.utils.crypto import get_random_string
|
||||
|
||||
from pretix.api.models import (
|
||||
OAuthAccessToken, OAuthApplication, OAuthGrant, OAuthRefreshToken,
|
||||
@@ -64,12 +65,17 @@ def admin_user(admin_team):
|
||||
|
||||
@pytest.fixture
|
||||
def application():
|
||||
return OAuthApplication.objects.create(
|
||||
secret = get_random_string(32)
|
||||
a = OAuthApplication.objects.create(
|
||||
name="pretalx",
|
||||
redirect_uris="https://pretalx.com",
|
||||
client_type='confidential',
|
||||
client_secret=secret,
|
||||
authorization_grant_type='authorization-code'
|
||||
)
|
||||
a._cached_secret = secret
|
||||
a.save()
|
||||
return a
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@@ -267,7 +273,7 @@ def test_token_from_code(client, admin_user, organizer, application: OAuthApplic
|
||||
'redirect_uri': application.redirect_uris,
|
||||
'grant_type': 'authorization_code',
|
||||
}, HTTP_AUTHORIZATION='Basic ' + base64.b64encode(
|
||||
('%s:%s' % (application.client_id, application.client_secret)).encode()).decode())
|
||||
('%s:%s' % (application.client_id, application._cached_secret)).encode()).decode())
|
||||
assert resp.status_code == 200
|
||||
data = json.loads(resp.content.decode())
|
||||
assert data['expires_in'] == 86400
|
||||
@@ -306,7 +312,7 @@ def test_use_token_for_access_one_organizer(client, admin_user, organizer, appli
|
||||
'redirect_uri': application.redirect_uris,
|
||||
'grant_type': 'authorization_code',
|
||||
}, HTTP_AUTHORIZATION='Basic ' + base64.b64encode(
|
||||
('%s:%s' % (application.client_id, application.client_secret)).encode()).decode())
|
||||
('%s:%s' % (application.client_id, application._cached_secret)).encode()).decode())
|
||||
assert resp.status_code == 200
|
||||
data = json.loads(resp.content.decode())
|
||||
access_token = data['access_token']
|
||||
@@ -348,7 +354,7 @@ def test_use_token_for_access_two_organizers(client, admin_user, organizer, appl
|
||||
'redirect_uri': application.redirect_uris,
|
||||
'grant_type': 'authorization_code',
|
||||
}, HTTP_AUTHORIZATION='Basic ' + base64.b64encode(
|
||||
('%s:%s' % (application.client_id, application.client_secret)).encode()).decode())
|
||||
('%s:%s' % (application.client_id, application._cached_secret)).encode()).decode())
|
||||
assert resp.status_code == 200
|
||||
data = json.loads(resp.content.decode())
|
||||
access_token = data['access_token']
|
||||
@@ -389,7 +395,7 @@ def test_token_refresh(client, admin_user, organizer, application: OAuthApplicat
|
||||
'redirect_uri': application.redirect_uris,
|
||||
'grant_type': 'authorization_code',
|
||||
}, HTTP_AUTHORIZATION='Basic ' + base64.b64encode(
|
||||
('%s:%s' % (application.client_id, application.client_secret)).encode()).decode())
|
||||
('%s:%s' % (application.client_id, application._cached_secret)).encode()).decode())
|
||||
assert resp.status_code == 200
|
||||
data = json.loads(resp.content.decode())
|
||||
refresh_token = data['refresh_token']
|
||||
@@ -398,7 +404,7 @@ def test_token_refresh(client, admin_user, organizer, application: OAuthApplicat
|
||||
'refresh_token': refresh_token,
|
||||
'grant_type': 'refresh_token',
|
||||
}, HTTP_AUTHORIZATION='Basic ' + base64.b64encode(
|
||||
('%s:%s' % (application.client_id, application.client_secret)).encode()).decode())
|
||||
('%s:%s' % (application.client_id, application._cached_secret)).encode()).decode())
|
||||
assert resp.status_code == 200
|
||||
assert not OAuthAccessToken.objects.filter(token=access_token).exists() # old token revoked
|
||||
data = json.loads(resp.content.decode())
|
||||
@@ -431,7 +437,7 @@ def test_allow_write(client, admin_user, organizer, application: OAuthApplicatio
|
||||
'redirect_uri': application.redirect_uris,
|
||||
'grant_type': 'authorization_code',
|
||||
}, HTTP_AUTHORIZATION='Basic ' + base64.b64encode(
|
||||
('%s:%s' % (application.client_id, application.client_secret)).encode()).decode())
|
||||
('%s:%s' % (application.client_id, application._cached_secret)).encode()).decode())
|
||||
assert resp.status_code == 200
|
||||
data = json.loads(resp.content.decode())
|
||||
access_token = data['access_token']
|
||||
@@ -463,7 +469,7 @@ def test_allow_read_only(client, admin_user, organizer, application: OAuthApplic
|
||||
'redirect_uri': application.redirect_uris,
|
||||
'grant_type': 'authorization_code',
|
||||
}, HTTP_AUTHORIZATION='Basic ' + base64.b64encode(
|
||||
('%s:%s' % (application.client_id, application.client_secret)).encode()).decode())
|
||||
('%s:%s' % (application.client_id, application._cached_secret)).encode()).decode())
|
||||
assert resp.status_code == 200
|
||||
data = json.loads(resp.content.decode())
|
||||
access_token = data['access_token']
|
||||
@@ -495,7 +501,7 @@ def test_token_revoke_refresh_token(client, admin_user, organizer, application:
|
||||
'redirect_uri': application.redirect_uris,
|
||||
'grant_type': 'authorization_code',
|
||||
}, HTTP_AUTHORIZATION='Basic ' + base64.b64encode(
|
||||
('%s:%s' % (application.client_id, application.client_secret)).encode()).decode())
|
||||
('%s:%s' % (application.client_id, application._cached_secret)).encode()).decode())
|
||||
assert resp.status_code == 200
|
||||
data = json.loads(resp.content.decode())
|
||||
refresh_token = data['refresh_token']
|
||||
@@ -503,7 +509,7 @@ def test_token_revoke_refresh_token(client, admin_user, organizer, application:
|
||||
resp = client.post('/api/v1/oauth/revoke_token', data={
|
||||
'token': refresh_token,
|
||||
}, HTTP_AUTHORIZATION='Basic ' + base64.b64encode(
|
||||
('%s:%s' % (application.client_id, application.client_secret)).encode()).decode())
|
||||
('%s:%s' % (application.client_id, application._cached_secret)).encode()).decode())
|
||||
assert resp.status_code == 200
|
||||
assert not OAuthAccessToken.objects.get(token=access_token).is_valid()
|
||||
assert not OAuthRefreshToken.objects.filter(token=refresh_token, revoked__isnull=True).exists()
|
||||
@@ -511,7 +517,7 @@ def test_token_revoke_refresh_token(client, admin_user, organizer, application:
|
||||
'refresh_token': refresh_token,
|
||||
'grant_type': 'refresh_token',
|
||||
}, HTTP_AUTHORIZATION='Basic ' + base64.b64encode(
|
||||
('%s:%s' % (application.client_id, application.client_secret)).encode()).decode())
|
||||
('%s:%s' % (application.client_id, application._cached_secret)).encode()).decode())
|
||||
assert resp.status_code == 400
|
||||
|
||||
|
||||
@@ -539,7 +545,7 @@ def test_token_revoke_access_token(client, admin_user, organizer, application: O
|
||||
'redirect_uri': application.redirect_uris,
|
||||
'grant_type': 'authorization_code',
|
||||
}, HTTP_AUTHORIZATION='Basic ' + base64.b64encode(
|
||||
('%s:%s' % (application.client_id, application.client_secret)).encode()).decode())
|
||||
('%s:%s' % (application.client_id, application._cached_secret)).encode()).decode())
|
||||
assert resp.status_code == 200
|
||||
data = json.loads(resp.content.decode())
|
||||
refresh_token = data['refresh_token']
|
||||
@@ -547,7 +553,7 @@ def test_token_revoke_access_token(client, admin_user, organizer, application: O
|
||||
resp = client.post('/api/v1/oauth/revoke_token', data={
|
||||
'token': access_token,
|
||||
}, HTTP_AUTHORIZATION='Basic ' + base64.b64encode(
|
||||
('%s:%s' % (application.client_id, application.client_secret)).encode()).decode())
|
||||
('%s:%s' % (application.client_id, application._cached_secret)).encode()).decode())
|
||||
assert resp.status_code == 200
|
||||
assert not OAuthAccessToken.objects.get(token=access_token).is_valid() # old token revoked
|
||||
|
||||
@@ -555,7 +561,7 @@ def test_token_revoke_access_token(client, admin_user, organizer, application: O
|
||||
'refresh_token': refresh_token,
|
||||
'grant_type': 'refresh_token',
|
||||
}, HTTP_AUTHORIZATION='Basic ' + base64.b64encode(
|
||||
('%s:%s' % (application.client_id, application.client_secret)).encode()).decode())
|
||||
('%s:%s' % (application.client_id, application._cached_secret)).encode()).decode())
|
||||
assert resp.status_code == 200
|
||||
data = json.loads(resp.content.decode())
|
||||
access_token = data['access_token']
|
||||
@@ -587,7 +593,7 @@ def test_user_revoke(client, admin_user, organizer, application: OAuthApplicatio
|
||||
'redirect_uri': application.redirect_uris,
|
||||
'grant_type': 'authorization_code',
|
||||
}, HTTP_AUTHORIZATION='Basic ' + base64.b64encode(
|
||||
('%s:%s' % (application.client_id, application.client_secret)).encode()).decode())
|
||||
('%s:%s' % (application.client_id, application._cached_secret)).encode()).decode())
|
||||
assert resp.status_code == 200
|
||||
data = json.loads(resp.content.decode())
|
||||
refresh_token = data['refresh_token']
|
||||
@@ -606,7 +612,7 @@ def test_user_revoke(client, admin_user, organizer, application: OAuthApplicatio
|
||||
'refresh_token': refresh_token,
|
||||
'grant_type': 'refresh_token',
|
||||
}, HTTP_AUTHORIZATION='Basic ' + base64.b64encode(
|
||||
('%s:%s' % (application.client_id, application.client_secret)).encode()).decode())
|
||||
('%s:%s' % (application.client_id, application._cached_secret)).encode()).decode())
|
||||
assert resp.status_code == 400
|
||||
|
||||
|
||||
@@ -634,7 +640,7 @@ def test_allow_profile_only(client, admin_user, organizer, application: OAuthApp
|
||||
'redirect_uri': application.redirect_uris,
|
||||
'grant_type': 'authorization_code',
|
||||
}, HTTP_AUTHORIZATION='Basic ' + base64.b64encode(
|
||||
('%s:%s' % (application.client_id, application.client_secret)).encode()).decode())
|
||||
('%s:%s' % (application.client_id, application._cached_secret)).encode()).decode())
|
||||
assert resp.status_code == 200
|
||||
data = json.loads(resp.content.decode())
|
||||
access_token = data['access_token']
|
||||
@@ -642,3 +648,18 @@ def test_allow_profile_only(client, admin_user, organizer, application: OAuthApp
|
||||
assert resp.status_code == 403
|
||||
resp = client.get('/api/v1/me', HTTP_AUTHORIZATION='Bearer %s' % access_token)
|
||||
assert resp.status_code == 200
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_reject_other_response_types(client, admin_user, organizer, application: OAuthApplication):
|
||||
client.login(email='dummy@dummy.dummy', password='dummy')
|
||||
resp = client.get('/api/v1/oauth/authorize?client_id=%s&redirect_uri=%s&response_type=code+id_token' % (
|
||||
application.client_id, quote(application.redirect_uris)
|
||||
))
|
||||
assert resp.status_code == 302
|
||||
assert 'error=unauthorized_client' in resp['Location']
|
||||
resp = client.get('/api/v1/oauth/authorize?client_id=%s&redirect_uri=%s&response_type=id_token' % (
|
||||
application.client_id, quote(application.redirect_uris)
|
||||
))
|
||||
assert resp.status_code == 302
|
||||
assert 'error=unsupported_response_type' in resp['Location']
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
#
|
||||
# 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 pytest
|
||||
from django.core.exceptions import ValidationError
|
||||
|
||||
from pretix.base.addressvalidation import validate_address
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"input,output,all_optional",
|
||||
[
|
||||
# No address is allowed
|
||||
({"name": "Peter"}, {"name": "Peter"}, False),
|
||||
# Country must be given if any part of the address is filled
|
||||
({"street": "Main Street"}, {"country": ["This field is required."]}, False),
|
||||
# Country without any semantic validation
|
||||
(
|
||||
{"street": "Main Street", "country": "CR"},
|
||||
{"street": "Main Street", "country": "CR"},
|
||||
False,
|
||||
),
|
||||
# Country that requires all fields except state to be filled
|
||||
(
|
||||
{"street": "Main Street", "country": "DE"},
|
||||
{"zipcode": ["This field is required."]},
|
||||
False,
|
||||
),
|
||||
(
|
||||
{"street": "Main Street", "country": "DE", "zipcode": "12345"},
|
||||
{"city": ["This field is required."]},
|
||||
False,
|
||||
),
|
||||
(
|
||||
{"city": "Heidelberg", "country": "DE", "zipcode": "12345"},
|
||||
{"street": ["This field is required."]},
|
||||
False,
|
||||
),
|
||||
# All-optional flag works
|
||||
(
|
||||
{"street": "Main Street", "country": "DE"},
|
||||
{"street": "Main Street", "country": "DE"},
|
||||
True,
|
||||
),
|
||||
(
|
||||
{"street": "Main Street", "country": "DE", "zipcode": "12345"},
|
||||
{"street": "Main Street", "country": "DE", "zipcode": "12345"},
|
||||
True,
|
||||
),
|
||||
(
|
||||
{"city": "Heidelberg", "country": "DE", "zipcode": "12345"},
|
||||
{"city": "Heidelberg", "country": "DE", "zipcode": "12345"},
|
||||
True,
|
||||
),
|
||||
(
|
||||
{
|
||||
"street": "Main Street",
|
||||
"city": "Heidelberg",
|
||||
"country": "DE",
|
||||
"zipcode": "12345",
|
||||
},
|
||||
True,
|
||||
False,
|
||||
),
|
||||
# Country that requires state to be filled
|
||||
(
|
||||
{
|
||||
"street": "Main street",
|
||||
"city": "Heidelberg",
|
||||
"country": "US",
|
||||
"zipcode": "12345",
|
||||
},
|
||||
{"state": ["This field is required."]},
|
||||
False,
|
||||
),
|
||||
# Country with zip code validation inherited from django-localflavor
|
||||
(
|
||||
{
|
||||
"street": "Main street",
|
||||
"city": "Heidelberg",
|
||||
"country": "DE",
|
||||
"zipcode": "ABCDE",
|
||||
},
|
||||
{"zipcode": ["Enter a zip code in the format XXXXX."]},
|
||||
False,
|
||||
),
|
||||
# Country with zip code validation implemented directly
|
||||
(
|
||||
{
|
||||
"street": "Main street",
|
||||
"city": "Heidelberg",
|
||||
"country": "IS",
|
||||
"zipcode": "ABCDE",
|
||||
},
|
||||
{"zipcode": ["Enter a postal code in the format XXX."]},
|
||||
False,
|
||||
),
|
||||
# Country with zip code normalization inherited from django-localflavor
|
||||
(
|
||||
{
|
||||
"street": "Main street",
|
||||
"city": "London",
|
||||
"country": "GB",
|
||||
"zipcode": "se19de",
|
||||
},
|
||||
{
|
||||
"street": "Main street",
|
||||
"city": "London",
|
||||
"country": "GB",
|
||||
"zipcode": "SE1 9DE",
|
||||
},
|
||||
False,
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_validate_address(input, output, all_optional):
|
||||
try:
|
||||
actual_output = validate_address(input, all_optional)
|
||||
except ValidationError as e:
|
||||
assert {
|
||||
k: ["".join(s for s in e) for e in v] for k, v in e.error_dict.items()
|
||||
} == output
|
||||
else:
|
||||
if output is True:
|
||||
assert actual_output == input
|
||||
else:
|
||||
assert output == actual_output
|
||||
@@ -73,6 +73,7 @@ def test_full_clone_same_organizer():
|
||||
assert item1.meta_data
|
||||
item2 = event.items.create(category=category, tax_rule=tax_rule, name="T-shirt", default_price=15)
|
||||
item2v = item2.variations.create(value="red", default_price=15)
|
||||
item2v.meta_values.create(property=item_meta, value="Bar")
|
||||
item2.require_membership_types.add(membership_type)
|
||||
ItemAddOn.objects.create(base_item=item1, addon_category=category)
|
||||
ItemBundle.objects.create(base_item=item1, bundled_item=item2, bundled_variation=item2v)
|
||||
@@ -147,6 +148,7 @@ def test_full_clone_same_organizer():
|
||||
assert copied_item1.tax_rule == copied_event.tax_rules.get()
|
||||
assert copied_item1.category == copied_event.categories.get()
|
||||
assert copied_item1.meta_data == item1.meta_data
|
||||
assert copied_item2.variations.get().meta_data == item2v.meta_data
|
||||
assert copied_item1.hidden_if_available == copied_q2
|
||||
assert copied_item1.grant_membership_type == membership_type
|
||||
assert copied_item2.variations.count() == 1
|
||||
|
||||
@@ -1962,6 +1962,32 @@ class ItemTest(TestCase):
|
||||
assert not Item.objects.filter_available().exists()
|
||||
assert Item.objects.filter_available(voucher=v).exists()
|
||||
|
||||
@classscope(attr='o')
|
||||
def test_meta_data_inheritance(self):
|
||||
prop = self.event.item_meta_properties.create(name="day", default="Monday")
|
||||
i = Item.objects.create(
|
||||
event=self.event, name="Ticket", default_price=23,
|
||||
active=True, available_until=now() + timedelta(days=1),
|
||||
)
|
||||
v = i.variations.create(value="Day 1")
|
||||
|
||||
assert i.meta_data == {"day": "Monday"}
|
||||
assert v.meta_data == {"day": "Monday"}
|
||||
|
||||
i.meta_values.create(property=prop, value="Tuesday")
|
||||
i = Item.objects.get(pk=i.pk)
|
||||
v = ItemVariation.objects.get(pk=v.pk)
|
||||
|
||||
assert i.meta_data == {"day": "Tuesday"}
|
||||
assert v.meta_data == {"day": "Tuesday"}
|
||||
|
||||
v.meta_values.create(property=prop, value="Wednesday")
|
||||
i = Item.objects.get(pk=i.pk)
|
||||
v = ItemVariation.objects.get(pk=v.pk)
|
||||
|
||||
assert i.meta_data == {"day": "Tuesday"}
|
||||
assert v.meta_data == {"day": "Wednesday"}
|
||||
|
||||
|
||||
class EventTest(TestCase):
|
||||
@classmethod
|
||||
|
||||
@@ -637,6 +637,8 @@ class ItemsTest(ItemFormTest):
|
||||
q = Question.objects.create(event=self.event1, question="Size", type="N")
|
||||
q.items.add(self.item2)
|
||||
self.item2.sales_channels = ["web", "bar"]
|
||||
prop = self.event1.item_meta_properties.create(name="Foo")
|
||||
self.item2.meta_values.create(property=prop, value="Bar")
|
||||
|
||||
self.client.post('/control/event/%s/%s/items/add' % (self.orga1.slug, self.event1.slug), {
|
||||
'name_0': 'Intermediate',
|
||||
@@ -657,6 +659,7 @@ class ItemsTest(ItemFormTest):
|
||||
assert i_new.hide_without_voucher == i_old.hide_without_voucher
|
||||
assert i_new.allow_cancel == i_old.allow_cancel
|
||||
assert i_new.sales_channels == i_old.sales_channels
|
||||
assert i_new.meta_data == i_old.meta_data == {"Foo": "Bar"}
|
||||
assert set(i_new.questions.all()) == set(i_old.questions.all())
|
||||
assert set([str(v.value) for v in i_new.variations.all()]) == set([str(v.value) for v in i_old.variations.all()])
|
||||
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
#
|
||||
# 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 pretix.helpers.format import format_map
|
||||
|
||||
|
||||
def test_format_map():
|
||||
assert format_map("Foo {bar}", {"bar": 3}) == "Foo 3"
|
||||
assert format_map("Foo {baz}", {"bar": 3}) == "Foo {baz}"
|
||||
assert format_map("Foo {bar.__module__}", {"bar": 3}) == "Foo {bar.__module__}"
|
||||
assert format_map("Foo {bar!s}", {"bar": 3}) == "Foo 3"
|
||||
assert format_map("Foo {bar:<20}", {"bar": 3}) == "Foo 3"
|
||||
@@ -24,11 +24,13 @@ from openpyxl.cell.cell import TYPE_STRING
|
||||
from pretix.helpers.safe_openpyxl import SafeWorkbook
|
||||
|
||||
|
||||
def test_nullbyte_removed():
|
||||
def test_invalid_byte_removed():
|
||||
wb = SafeWorkbook()
|
||||
ws = wb.create_sheet()
|
||||
ws.append(["foo\u0000bar"])
|
||||
assert ws.cell(1, 1).value == "foobar"
|
||||
ws.append(["foo\uffffbaz"])
|
||||
assert ws.cell(2, 1).value == "foobaz"
|
||||
|
||||
|
||||
def test_no_formulas():
|
||||
|
||||
@@ -249,7 +249,7 @@ EXPECTED = [
|
||||
],
|
||||
[
|
||||
{'amount': '5000.00', 'date': '2002-03-17',
|
||||
'reference': '68790452\nVerwendungszweck 1\nVerwendungszweck 2\nEmpfänger\nBuchungstext\n12345\n1000\n4711'},
|
||||
'reference': '68790452\nVerwendungszweck 1\nVerwendungszweck 2\nEmpf√§nger\nBuchungstext\n12345\n1000\n4711'},
|
||||
{'amount': '20000.00', 'date': '2002-03-22', 'reference': ''},
|
||||
{'amount': '20000.00', 'date': '2002-03-22', 'reference': ''},
|
||||
{'amount': '20000.00', 'date': '2002-03-22', 'reference': ''},
|
||||
|
||||
@@ -170,7 +170,7 @@ class CheckoutTestCase(BaseCheckoutTestCase, TestCase):
|
||||
'company': 'Foo',
|
||||
'name': 'Bar',
|
||||
'street': 'Baz',
|
||||
'zipcode': '12345',
|
||||
'zipcode': '1234',
|
||||
'city': 'Here',
|
||||
'country': 'AT',
|
||||
'vat_id': 'AT123456',
|
||||
@@ -193,7 +193,7 @@ class CheckoutTestCase(BaseCheckoutTestCase, TestCase):
|
||||
'is_business': 'individual',
|
||||
'name': 'Bar',
|
||||
'street': 'Baz',
|
||||
'zipcode': '12345',
|
||||
'zipcode': '1234',
|
||||
'city': 'Here',
|
||||
'country': 'AT',
|
||||
'vat_id': '',
|
||||
@@ -229,7 +229,7 @@ class CheckoutTestCase(BaseCheckoutTestCase, TestCase):
|
||||
'company': 'Foo',
|
||||
'name': 'Bar',
|
||||
'street': 'Baz',
|
||||
'zipcode': '12345',
|
||||
'zipcode': '1234',
|
||||
'city': 'Here',
|
||||
'country': 'AT',
|
||||
'vat_id': 'AT123456',
|
||||
@@ -259,7 +259,7 @@ class CheckoutTestCase(BaseCheckoutTestCase, TestCase):
|
||||
'company': 'Foo',
|
||||
'name': 'Bar',
|
||||
'street': 'Baz',
|
||||
'zipcode': '12345',
|
||||
'zipcode': '3000',
|
||||
'city': 'Here',
|
||||
'country': 'AU',
|
||||
'state': 'QLD',
|
||||
@@ -293,7 +293,7 @@ class CheckoutTestCase(BaseCheckoutTestCase, TestCase):
|
||||
'company': 'Foo',
|
||||
'name': 'Bar',
|
||||
'street': 'Baz',
|
||||
'zipcode': '12345',
|
||||
'zipcode': '1234',
|
||||
'city': 'Here',
|
||||
'country': 'AT',
|
||||
'vat_id': 'AT123456',
|
||||
@@ -324,7 +324,7 @@ class CheckoutTestCase(BaseCheckoutTestCase, TestCase):
|
||||
'company': 'Foo',
|
||||
'name': 'Bar',
|
||||
'street': 'Baz',
|
||||
'zipcode': '12345',
|
||||
'zipcode': '1234',
|
||||
'city': 'Here',
|
||||
'country': 'FR',
|
||||
'vat_id': 'AT123456',
|
||||
@@ -357,7 +357,7 @@ class CheckoutTestCase(BaseCheckoutTestCase, TestCase):
|
||||
'company': 'Foo',
|
||||
'name': 'Bar',
|
||||
'street': 'Baz',
|
||||
'zipcode': '12345',
|
||||
'zipcode': '1234',
|
||||
'city': 'Here',
|
||||
'country': 'AT',
|
||||
'vat_id': 'AT123456',
|
||||
@@ -391,7 +391,7 @@ class CheckoutTestCase(BaseCheckoutTestCase, TestCase):
|
||||
'company': 'Foo',
|
||||
'name': 'Bar',
|
||||
'street': 'Baz',
|
||||
'zipcode': '12345',
|
||||
'zipcode': '1234',
|
||||
'city': 'Here',
|
||||
'country': 'AT',
|
||||
'vat_id': 'AT123456',
|
||||
@@ -428,7 +428,7 @@ class CheckoutTestCase(BaseCheckoutTestCase, TestCase):
|
||||
'company': 'Foo',
|
||||
'name': 'Bar',
|
||||
'street': 'Baz',
|
||||
'zipcode': '12345',
|
||||
'zipcode': '1234',
|
||||
'city': 'Here',
|
||||
'country': 'AT',
|
||||
'vat_id': 'AT123456',
|
||||
@@ -492,7 +492,7 @@ class CheckoutTestCase(BaseCheckoutTestCase, TestCase):
|
||||
'company': 'Foo',
|
||||
'name': 'Bar',
|
||||
'street': 'Baz',
|
||||
'zipcode': '12345',
|
||||
'zipcode': '1234',
|
||||
'city': 'Here',
|
||||
'country': 'AT',
|
||||
'vat_id': 'AT123456',
|
||||
@@ -551,7 +551,7 @@ class CheckoutTestCase(BaseCheckoutTestCase, TestCase):
|
||||
'is_business': 'individual',
|
||||
'name': 'Bar',
|
||||
'street': 'Baz',
|
||||
'zipcode': '12345',
|
||||
'zipcode': '1234',
|
||||
'city': 'Here',
|
||||
'country': 'AT',
|
||||
'email': 'admin@localhost'
|
||||
@@ -603,7 +603,7 @@ class CheckoutTestCase(BaseCheckoutTestCase, TestCase):
|
||||
'is_business': 'individual',
|
||||
'name': 'Bar',
|
||||
'street': 'Baz',
|
||||
'zipcode': '12345',
|
||||
'zipcode': '1234',
|
||||
'city': 'Here',
|
||||
'country': 'AT',
|
||||
'email': 'admin@localhost'
|
||||
@@ -631,7 +631,7 @@ class CheckoutTestCase(BaseCheckoutTestCase, TestCase):
|
||||
'is_business': 'individual',
|
||||
'name': 'Bar',
|
||||
'street': 'Baz',
|
||||
'zipcode': '12345',
|
||||
'zipcode': '99501',
|
||||
'city': 'Here',
|
||||
'country': 'US',
|
||||
'state': 'CA',
|
||||
@@ -684,7 +684,7 @@ class CheckoutTestCase(BaseCheckoutTestCase, TestCase):
|
||||
'company': 'Foo',
|
||||
'name': 'Bar',
|
||||
'street': 'Baz',
|
||||
'zipcode': '12345',
|
||||
'zipcode': '1345',
|
||||
'city': 'Here',
|
||||
'country': 'AT',
|
||||
'vat_id': 'AT123456',
|
||||
@@ -748,7 +748,7 @@ class CheckoutTestCase(BaseCheckoutTestCase, TestCase):
|
||||
'company': 'Foo',
|
||||
'name': 'Bar',
|
||||
'street': 'Baz',
|
||||
'zipcode': '12345',
|
||||
'zipcode': '1234',
|
||||
'city': 'Here',
|
||||
'country': 'AT',
|
||||
'vat_id': 'AT123456',
|
||||
@@ -1102,6 +1102,57 @@ class CheckoutTestCase(BaseCheckoutTestCase, TestCase):
|
||||
}
|
||||
assert ia.name_cached == 'Mr John Kennedy'
|
||||
|
||||
def test_invoice_address_validated(self):
|
||||
self.event.settings.invoice_address_asked = True
|
||||
self.event.settings.invoice_address_required = True
|
||||
self.event.settings.invoice_address_not_asked_free = True
|
||||
self.event.settings.set('name_scheme', 'title_given_middle_family')
|
||||
|
||||
with scopes_disabled():
|
||||
CartPosition.objects.create(
|
||||
event=self.event, cart_id=self.session_key, item=self.ticket,
|
||||
price=23, expires=now() + timedelta(minutes=10)
|
||||
)
|
||||
response = self.client.get('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), follow=True)
|
||||
doc = BeautifulSoup(response.content.decode(), "lxml")
|
||||
self.assertEqual(len(doc.select('input[name="city"]')), 1)
|
||||
|
||||
# Not all required fields filled out correctly, expect failure
|
||||
response = self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
|
||||
'is_business': 'business',
|
||||
'company': 'Foo',
|
||||
'name_parts_0': 'Mr',
|
||||
'name_parts_1': 'John',
|
||||
'name_parts_2': '',
|
||||
'name_parts_3': 'Kennedy',
|
||||
'street': 'Baz',
|
||||
'zipcode': '123456',
|
||||
'city': 'Here',
|
||||
'country': 'DE',
|
||||
'vat_id': 'DE123456',
|
||||
'email': 'admin@localhost'
|
||||
}, follow=True)
|
||||
doc = BeautifulSoup(response.content.decode(), "lxml")
|
||||
self.assertGreaterEqual(len(doc.select('.has-error')), 1)
|
||||
|
||||
# Corrected request
|
||||
response = self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
|
||||
'is_business': 'business',
|
||||
'company': 'Foo',
|
||||
'name_parts_0': 'Mr',
|
||||
'name_parts_1': 'John',
|
||||
'name_parts_2': '',
|
||||
'name_parts_3': 'Kennedy',
|
||||
'street': 'Baz',
|
||||
'zipcode': '12345',
|
||||
'city': 'Here',
|
||||
'country': 'DE',
|
||||
'vat_id': 'DE123456',
|
||||
'email': 'admin@localhost'
|
||||
}, follow=True)
|
||||
self.assertRedirects(response, '/%s/%s/checkout/payment/' % (self.orga.slug, self.event.slug),
|
||||
target_status_code=200)
|
||||
|
||||
def test_invoice_address_hidden_for_free(self):
|
||||
self.event.settings.invoice_address_asked = True
|
||||
self.event.settings.invoice_address_required = True
|
||||
@@ -1136,10 +1187,19 @@ class CheckoutTestCase(BaseCheckoutTestCase, TestCase):
|
||||
doc = BeautifulSoup(response.content.decode(), "lxml")
|
||||
self.assertEqual(len(doc.select('input[name="city"]')), 1)
|
||||
|
||||
# Not all required fields filled out, expect failure
|
||||
# Partial address is not allowed
|
||||
response = self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
|
||||
'is_business': 'business',
|
||||
'country': 'DE',
|
||||
'city': 'Musterstadt',
|
||||
'email': 'admin@localhost'
|
||||
}, follow=True)
|
||||
doc = BeautifulSoup(response.content.decode(), "lxml")
|
||||
self.assertGreaterEqual(len(doc.select('.has-error')), 1)
|
||||
|
||||
# No address works
|
||||
response = self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), {
|
||||
'is_business': 'business',
|
||||
'city': 'Here',
|
||||
'country': 'DE',
|
||||
'vat_id': 'DE123456',
|
||||
'email': 'admin@localhost'
|
||||
|
||||
@@ -345,6 +345,35 @@ class OrdersTest(BaseOrdersTest):
|
||||
with scopes_disabled():
|
||||
assert self.ticket_pos.answers.get(question=self.question).answer == 'ABC'
|
||||
|
||||
def test_modify_invoice_address_validated(self):
|
||||
self.event.settings.set('invoice_reissue_after_modify', True)
|
||||
self.event.settings.set('invoice_address_asked', True)
|
||||
with scopes_disabled():
|
||||
generate_invoice(self.order)
|
||||
|
||||
response = self.client.post(
|
||||
'/%s/%s/order/%s/%s/modify' % (self.orga.slug, self.event.slug, self.order.code, self.order.secret), {
|
||||
'%s-question_%s' % (self.ticket_pos.id, self.question.id): 'ABC',
|
||||
}, follow=True)
|
||||
self.assertRedirects(response,
|
||||
'/%s/%s/order/%s/%s/' % (self.orga.slug, self.event.slug, self.order.code,
|
||||
self.order.secret),
|
||||
target_status_code=200)
|
||||
# Only questions changed
|
||||
with scopes_disabled():
|
||||
assert self.order.invoices.count() == 1
|
||||
|
||||
response = self.client.post(
|
||||
'/%s/%s/order/%s/%s/modify' % (self.orga.slug, self.event.slug, self.order.code, self.order.secret), {
|
||||
'%s-question_%s' % (self.ticket_pos.id, self.question.id): 'ABC',
|
||||
'zipcode': 'XXINVALIDXX',
|
||||
'street': 'Main Street',
|
||||
'city': 'Heidelberg',
|
||||
'country': 'DE',
|
||||
}, follow=True)
|
||||
doc = BeautifulSoup(response.content.decode(), "lxml")
|
||||
self.assertGreaterEqual(len(doc.select('.has-error')), 1)
|
||||
|
||||
def test_modify_invoice_regenerate(self):
|
||||
self.event.settings.set('invoice_reissue_after_modify', True)
|
||||
self.event.settings.set('invoice_address_asked', True)
|
||||
@@ -366,7 +395,10 @@ class OrdersTest(BaseOrdersTest):
|
||||
response = self.client.post(
|
||||
'/%s/%s/order/%s/%s/modify' % (self.orga.slug, self.event.slug, self.order.code, self.order.secret), {
|
||||
'%s-question_%s' % (self.ticket_pos.id, self.question.id): 'ABC',
|
||||
'zipcode': '1234',
|
||||
'zipcode': '12345',
|
||||
'street': 'Main Street',
|
||||
'city': 'Heidelberg',
|
||||
'country': 'DE',
|
||||
}, follow=True)
|
||||
self.assertRedirects(response,
|
||||
'/%s/%s/order/%s/%s/' % (self.orga.slug, self.event.slug, self.order.code,
|
||||
@@ -381,6 +413,9 @@ class OrdersTest(BaseOrdersTest):
|
||||
'/%s/%s/order/%s/%s/modify' % (self.orga.slug, self.event.slug, self.order.code, self.order.secret), {
|
||||
'%s-question_%s' % (self.ticket_pos.id, self.question.id): 'ABC',
|
||||
'zipcode': '54321',
|
||||
'street': 'Main Street',
|
||||
'city': 'Heidelberg',
|
||||
'country': 'DE',
|
||||
}, follow=True)
|
||||
self.assertRedirects(response,
|
||||
'/%s/%s/order/%s/%s/' % (self.orga.slug, self.event.slug, self.order.code,
|
||||
|
||||
Reference in New Issue
Block a user