forked from CGM_Public/pretix_original
Compare commits
33
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6ce98de931 | ||
|
|
7fe31634e6 | ||
|
|
f25c233e91 | ||
|
|
5cf28f1b81 | ||
|
|
d08216d8c5 | ||
|
|
4a28689690 | ||
|
|
958f75b109 | ||
|
|
81f58456e5 | ||
|
|
4d9dfa88fe | ||
|
|
ea792e76b2 | ||
|
|
5c448279f0 | ||
|
|
e3fa1aed7f | ||
|
|
b164cf44c0 | ||
|
|
7bc79337c3 | ||
|
|
5d8ba189fa | ||
|
|
1ea1c1789c | ||
|
|
1a6edae769 | ||
|
|
c4950e714d | ||
|
|
407728cc55 | ||
|
|
9fe25544c1 | ||
|
|
8133061fe1 | ||
|
|
288ac50600 | ||
|
|
e7657a3dd3 | ||
|
|
b659534772 | ||
|
|
c11ebb1391 | ||
|
|
b20557a996 | ||
|
|
e4a0bba3bc | ||
|
|
c369ba5f60 | ||
|
|
2c876057bf | ||
|
|
b70c1b02c2 | ||
|
|
01d736361d | ||
|
|
7627e4b548 | ||
|
|
541e7412e1 |
@@ -1,202 +0,0 @@
|
||||
Führe den vollständigen Upstream-Merge-Workflow für pretix_cgo durch. Gehe dabei strikt Schritt für Schritt vor und warte an den markierten Stellen auf Bestätigung.
|
||||
|
||||
---
|
||||
|
||||
## Schritt 1 – Master aktualisieren
|
||||
|
||||
```bash
|
||||
git checkout master
|
||||
git pull
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Schritt 2 – Upstream-Tags holen & neuesten Tag ermitteln
|
||||
|
||||
```bash
|
||||
git fetch upstream --tags
|
||||
```
|
||||
|
||||
Bestimme danach den neuesten upstream-Tag (Format `vYYYY.M.P`, **ohne** das `.0`-Suffix unserer eigenen Tags):
|
||||
|
||||
```bash
|
||||
git tag --sort=-version:refname | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | head -1
|
||||
```
|
||||
|
||||
Merke dir diesen Tag als `UPSTREAM_TAG` (z.B. `v2026.5.0`) und leite daraus den Branch-Namen ab: `upstream/2026.5.0` (ohne führendes `v`).
|
||||
|
||||
---
|
||||
|
||||
## Schritt 3 – Branch erstellen
|
||||
|
||||
```bash
|
||||
git checkout -b upstream/<VERSION> tags/<UPSTREAM_TAG>
|
||||
# Beispiel: git checkout -b upstream/2026.5.0 tags/v2026.5.0
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Schritt 4 – Master mergen (immer, nicht optional)
|
||||
|
||||
```bash
|
||||
git merge master
|
||||
```
|
||||
|
||||
Bei Merge-Konflikten: Konflikte anzeigen und gemeinsam mit dem Nutzer lösen, bevor es weitergeht.
|
||||
|
||||
---
|
||||
|
||||
## Schritt 5 – Branch pushen & MR anlegen
|
||||
|
||||
Push mit Push Option, damit Gitea direkt einen MR anlegt:
|
||||
|
||||
```bash
|
||||
git push --set-upstream origin upstream/<VERSION> -o merge_request.create
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Schritt 6 – Gender-Prüfung in den deutschen Übersetzungen
|
||||
|
||||
**Erst nach dem Merge mit master** die folgenden vier Dateien auf gegenderte Sprache prüfen:
|
||||
|
||||
- `src/pretix/locale/de/LC_MESSAGES/django.po`
|
||||
- `src/pretix/locale/de/LC_MESSAGES/djangojs.po`
|
||||
- `src/pretix/locale/de_Informal/LC_MESSAGES/django.po`
|
||||
- `src/pretix/locale/de_Informal/LC_MESSAGES/djangojs.po`
|
||||
|
||||
Suche in den `msgstr`-Zeilen (und den zugehörigen `msgid`-Zeilen für Kontext) nach folgenden Mustern:
|
||||
|
||||
| Muster | Beispiele |
|
||||
|--------|-----------|
|
||||
| Binneni mit `*` | `Teilnehmer*in`, `Käufer*innen` |
|
||||
| Binneni mit `:` | `Teilnehmer:in`, `Käufer:innen` |
|
||||
| Binneni mit `_` | `Teilnehmer_in`, `Käufer_innen` |
|
||||
| Binneni mit `(in)` | `Teilnehmer(in)`, `Käufer(innen)` |
|
||||
| Partizip-Plural auf `-enden` | `Teilnehmenden`, `Helfenden`, `Organisierenden`, `Bestellenden`, `Buchenden`, `Zahlenden` |
|
||||
|
||||
**Ausgabe der Befunde:** Zeige für jeden Treffer:
|
||||
1. Datei + Zeilennummer
|
||||
2. Den vollständigen `msgstr`-Block (mehrzeilig wenn nötig) als Kontext
|
||||
3. Den konkreten Vorschlag für die Ersetzung (z.B. `Teilnehmer*in` → `Teilnehmer`, `Teilnehmenden` → `Teilnehmer`)
|
||||
|
||||
Warte dann auf Freigabe durch den Nutzer, bevor Änderungen vorgenommen werden.
|
||||
|
||||
Nach Freigabe: Korrekturen direkt in den Dateien vornehmen und committen:
|
||||
|
||||
```bash
|
||||
git add src/pretix/locale/de/ src/pretix/locale/de_Informal/
|
||||
git commit -m "Remove gendered language from de/de_Informal translations"
|
||||
git push
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Schritt 7 – Warten auf Merge-Bestätigung
|
||||
|
||||
**Hier pausieren.** Den Nutzer auffordern zu bestätigen, sobald der MR auf Gitea gemergt wurde.
|
||||
|
||||
---
|
||||
|
||||
## Schritt 8 – Master aktualisieren und neuen Tag vergeben
|
||||
|
||||
Nach Bestätigung:
|
||||
|
||||
```bash
|
||||
git checkout master
|
||||
git pull
|
||||
```
|
||||
|
||||
Den neuen Tag ableiten: `<UPSTREAM_TAG>.0` **ohne führendes `v`** (z.B. `v2026.5.0` → `2026.5.0.0`):
|
||||
|
||||
```bash
|
||||
git tag <VERSION>.0
|
||||
# Beispiel: git tag 2026.5.0.0
|
||||
git push origin <VERSION>.0
|
||||
```
|
||||
|
||||
Ausgabe: Bestätigung mit dem neuen Tag und kurzem Status.
|
||||
|
||||
---
|
||||
|
||||
## Schritt 9 – Warten auf Build-Pipeline
|
||||
|
||||
Nutze den Gitea MCP Server, um den Pipeline-Lauf für den soeben gepushten Tag zu überwachen:
|
||||
|
||||
- **Repo:** `CGM_Public` / `pretix_cgo`
|
||||
- **Methode:** `mcp__gitea__actions_run_read` mit `method: list_runs`
|
||||
- Warte, bis ein Run mit dem passenden Ref (`refs/tags/<NEUER_TAG>`) den Status `success` erreicht.
|
||||
- Prüfe alle ~30 Sekunden erneut und zeige dem Nutzer den aktuellen Status (running / success / failure).
|
||||
- Bei `failure`: sofort stoppen und dem Nutzer melden.
|
||||
|
||||
Erst wenn der Build erfolgreich (`success`) abgeschlossen ist, weitermachen.
|
||||
|
||||
---
|
||||
|
||||
## Schritt 10 – cgm72 GitOps-Update
|
||||
|
||||
Im GitOps-Repo unter `../OrtlerstrasseGitOps/` (liegt immer neben dem pretix_cgo-Repo):
|
||||
|
||||
1. In `pretix-cgm72/pretix-deployment.yaml` den Image-Tag aktualisieren:
|
||||
- Suche: `cr.ortlerstrasse.de/cgo/pretix:<ALTER_TAG>`
|
||||
- Ersetze mit: `cr.ortlerstrasse.de/cgo/pretix:<NEUER_TAG>` (= der in Schritt 8 gesetzte Git-Tag, z.B. `2026.5.0.0`)
|
||||
|
||||
2. Committen und pushen (im OrtlerstrasseGitOps-Verzeichnis):
|
||||
|
||||
```bash
|
||||
git -C ../OrtlerstrasseGitOps add pretix-cgm72/pretix-deployment.yaml
|
||||
git -C ../OrtlerstrasseGitOps commit -m "Update pretix-cgm72 to <NEUER_TAG>"
|
||||
git -C ../OrtlerstrasseGitOps push
|
||||
```
|
||||
|
||||
Der GitOps-Operator wendet das YAML automatisch via `kubectl apply` an und rollt den Container neu aus.
|
||||
|
||||
---
|
||||
|
||||
## Schritt 11 – cgm72: Container-Neustart abwarten und Loginseite prüfen
|
||||
|
||||
Warte, bis der Container neu gestartet ist, und prüfe dann die Loginseite:
|
||||
|
||||
- **URL:** `https://anmeldung.christliche-gemeinde.de/`
|
||||
- **Prüfung:** HTTP 200 und die Seite enthält typische Pretix-Inhalte (z.B. Stichwort „pretix" oder ein Login-Formular im Body)
|
||||
|
||||
Methode: Nutze `mcp__claude_ai_Simon_Fetch__fetch_page` (oder `curl -sI`), um die URL alle ~30 Sekunden abzurufen. Erwarte einen kurzen Ausfall während des Neustarts – warte danach auf eine erfolgreiche Antwort.
|
||||
|
||||
Zeige dem Nutzer das Ergebnis (Status-Code + kurzen Seitenausschnitt als Bestätigung).
|
||||
|
||||
---
|
||||
|
||||
## Schritt 12 – Manuelle Bestätigung für cgm72
|
||||
|
||||
**Hier pausieren.** Den Nutzer auffordern zu bestätigen, dass cgm72 korrekt funktioniert (`https://anmeldung.christliche-gemeinde.de/`), bevor mit sola fortgefahren wird.
|
||||
|
||||
---
|
||||
|
||||
## Schritt 13 – sola GitOps-Update
|
||||
|
||||
Im GitOps-Repo unter `../OrtlerstrasseGitOps/` (liegt immer neben dem pretix_cgo-Repo):
|
||||
|
||||
1. In `pretix-sola/pretix-deployment.yaml` den Image-Tag aktualisieren:
|
||||
- Suche: `cr.ortlerstrasse.de/cgo/pretix:<ALTER_TAG>`
|
||||
- Ersetze mit: `cr.ortlerstrasse.de/cgo/pretix:<NEUER_TAG>`
|
||||
|
||||
2. Committen und pushen:
|
||||
|
||||
```bash
|
||||
git -C ../OrtlerstrasseGitOps add pretix-sola/pretix-deployment.yaml
|
||||
git -C ../OrtlerstrasseGitOps commit -m "Update pretix-sola to <NEUER_TAG>"
|
||||
git -C ../OrtlerstrasseGitOps push
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Schritt 14 – sola: Container-Neustart abwarten und Loginseite prüfen
|
||||
|
||||
Warte, bis der Container neu gestartet ist, und prüfe dann die Loginseite:
|
||||
|
||||
- **URL:** `https://anmeldung.sola-muenchen.de`
|
||||
- **Prüfung:** HTTP 200 und die Seite enthält typische Pretix-Inhalte
|
||||
|
||||
Methode: wie in Schritt 11.
|
||||
|
||||
Ausgabe: Bestätigung mit kurzem Abschluss-Status des gesamten Workflows.
|
||||
@@ -1,31 +0,0 @@
|
||||
name: Build Deploy email notification tool
|
||||
run-name: ${{ gitea.actor }} building new version of the email notification tool
|
||||
on:
|
||||
push: # Baut bei jedem Push (Branches + Tags)
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
Apply-Kubernetes-Resources:
|
||||
runs-on: podman
|
||||
steps:
|
||||
- name: Check out repository code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Login to Docker Registry
|
||||
run: podman login -u ${{ secrets.REGISTRY_USERNAME }} -p ${{ secrets.REGISTRY_TOKEN }} cr.ortlerstrasse.de
|
||||
|
||||
- name: Set Docker Image Tag
|
||||
run: |
|
||||
if [[ "${{ gitea.ref }}" == refs/tags/* ]]; then
|
||||
echo "TAG_NAME=${{ gitea.ref_name }}" >> $GITHUB_ENV
|
||||
else
|
||||
echo "TAG_NAME=latest" >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
- name: Build Docker image
|
||||
run: podman build -t cr.ortlerstrasse.de/cgo/pretix:${{ env.TAG_NAME }} .
|
||||
|
||||
- name: Push Docker image
|
||||
run: |
|
||||
podman push cr.ortlerstrasse.de/cgo/pretix:${{ env.TAG_NAME }}
|
||||
echo "Image pushed successfully: cr.ortlerstrasse.de/cgo/pretix:${{ env.TAG_NAME }}"
|
||||
+1
-2
@@ -61,8 +61,7 @@ RUN pip3 install -U \
|
||||
cd /pretix && \
|
||||
PRETIX_DOCKER_BUILD=TRUE pip3 install \
|
||||
-e ".[memcached]" \
|
||||
gunicorn django-extensions ipython \
|
||||
pretix-oidc && \
|
||||
gunicorn django-extensions ipython && \
|
||||
rm -rf ~/.cache/pip
|
||||
|
||||
RUN chmod +x /usr/local/bin/pretix && \
|
||||
|
||||
@@ -71,6 +71,8 @@ Checking a ticket in
|
||||
:>json object questions: List of questions to be answered for check-in, only set on status ``"incomplete"``.
|
||||
:>json object media_policy: Reusable media policy (see documentation on items), only set on status ``"exchange"``.
|
||||
:>json object media_type: Reusable media type (see documentation on items), only set on status ``"exchange"``.
|
||||
:>json boolean simulate: Do not actually perform the check-in, only simulate the response. The ``position`` response
|
||||
object will not reflect the simulated changes.
|
||||
|
||||
**Example request**:
|
||||
|
||||
|
||||
@@ -2038,7 +2038,7 @@ Manipulating individual positions
|
||||
|
||||
* ``order`` (mandatory, specified as a string mapping to a ``code``)
|
||||
|
||||
* ``addon_to`` (optional, specified as an integer mapping to the ``positionid`` of the parent position)
|
||||
* ``addon_to`` (optional, specified as an integer mapping to ``positionid`` - the number of the position within the order, see :ref:`_order-position-resource` - of the parent position)
|
||||
|
||||
* ``item`` (mandatory)
|
||||
|
||||
@@ -2348,7 +2348,7 @@ otherwise, such as splitting an order or changing fees.
|
||||
"subevent": 562,
|
||||
"seat": "seat-guid-2",
|
||||
"price": "99.99",
|
||||
"addon_to": 12374,
|
||||
"addon_to": 1,
|
||||
"attendee_name": "Peter",
|
||||
}
|
||||
],
|
||||
|
||||
+4
-4
@@ -33,12 +33,12 @@ dependencies = [
|
||||
"bleach==6.4.*",
|
||||
"celery==5.6.*",
|
||||
"chardet==5.2.*",
|
||||
"cryptography>=49.0.0",
|
||||
"cryptography>=50.0.0",
|
||||
"css-inline==0.21.*",
|
||||
"defusedcsv>=3.0.0",
|
||||
"dnspython==2.*",
|
||||
"Django[argon2]==5.2.*",
|
||||
"django-bootstrap3==26.1",
|
||||
"django-bootstrap3==26.2",
|
||||
"django-compressor==4.6.0",
|
||||
"django-countries==9.0.*",
|
||||
"django-filter==26.1",
|
||||
@@ -48,7 +48,7 @@ dependencies = [
|
||||
"django-hijack==3.7.*",
|
||||
"django-i18nfield==1.11.*",
|
||||
"django-libsass==0.9",
|
||||
"django-localflavor==5.0",
|
||||
"django-localflavor==5.1",
|
||||
"django-markup",
|
||||
"django-oauth-toolkit==2.3.*",
|
||||
"django-otp==1.7.*",
|
||||
@@ -67,7 +67,7 @@ dependencies = [
|
||||
"kombu==5.6.*",
|
||||
"libsass==0.23.*",
|
||||
"lxml",
|
||||
"markdown==3.10.2", # 3.3.5 requires importlib-metadata>=4.4, but django-bootstrap3 requires importlib-metadata<3.
|
||||
"markdown==3.10.3", # 3.3.5 requires importlib-metadata>=4.4, but django-bootstrap3 requires importlib-metadata<3.
|
||||
# We can upgrade markdown again once django-bootstrap3 upgrades or once we drop Python 3.6 and 3.7
|
||||
"mt-940==4.30.*",
|
||||
"oauthlib==3.3.*",
|
||||
|
||||
@@ -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__ = "2026.7.0"
|
||||
__version__ = "2026.8.0.dev0"
|
||||
|
||||
@@ -104,6 +104,7 @@ ALL_LANGUAGES = [
|
||||
('gl', _('Galician')),
|
||||
('el', _('Greek')),
|
||||
('he', _('Hebrew')),
|
||||
('hu', _('Hungarian')),
|
||||
('id', _('Indonesian')),
|
||||
('it', _('Italian')),
|
||||
('ja', _('Japanese')),
|
||||
|
||||
@@ -20,8 +20,11 @@
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
|
||||
from django.contrib.auth.models import AnonymousUser
|
||||
from django.db import DatabaseError
|
||||
from django.utils.timezone import now
|
||||
from django_scopes import scopes_disabled
|
||||
from rest_framework import exceptions
|
||||
from rest_framework.authentication import TokenAuthentication
|
||||
@@ -30,6 +33,7 @@ from pretix.api.auth.devicesecurity import (
|
||||
FullAccessSecurityProfile, get_all_security_profiles,
|
||||
)
|
||||
from pretix.base.models import Device
|
||||
from pretix.base.models.devices import DeviceLastSeen
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -42,7 +46,7 @@ class DeviceTokenAuthentication(TokenAuthentication):
|
||||
model = self.get_model()
|
||||
try:
|
||||
with scopes_disabled():
|
||||
device = model.objects.select_related('organizer').get(api_token=key)
|
||||
device = model.objects.select_related('organizer', 'last_seen').get(api_token=key)
|
||||
except model.DoesNotExist:
|
||||
raise exceptions.AuthenticationFailed('Invalid token.')
|
||||
|
||||
@@ -53,6 +57,7 @@ class DeviceTokenAuthentication(TokenAuthentication):
|
||||
logging.warning(f'Connection attempt of revoked device {device.pk}.')
|
||||
raise exceptions.AuthenticationFailed('Device access has been revoked.')
|
||||
|
||||
self._update_last_seen(device)
|
||||
return AnonymousUser(), device
|
||||
|
||||
def authenticate(self, request):
|
||||
@@ -63,3 +68,22 @@ class DeviceTokenAuthentication(TokenAuthentication):
|
||||
if not profile.is_allowed(request):
|
||||
raise exceptions.PermissionDenied('Request denied by device security profile.')
|
||||
return r
|
||||
|
||||
def _update_last_seen(self, device: Device):
|
||||
try:
|
||||
try:
|
||||
last_seen_obj = device.last_seen
|
||||
except DeviceLastSeen.DoesNotExist:
|
||||
# First request from device, create model, ignore result. Use get_or_create to be safe
|
||||
# against concurrent create requests
|
||||
DeviceLastSeen.objects.get_or_create(device=device, last_seen=now())
|
||||
else:
|
||||
if now() - last_seen_obj.last_seen < timedelta(seconds=10):
|
||||
# We don't need to know the last seen info of a device to more precision than this,
|
||||
# so we can avoid some database writes if the device is bursting a lot of requests.
|
||||
return
|
||||
last_seen_obj.last_seen = now()
|
||||
last_seen_obj.save(update_fields=["last_seen"])
|
||||
except DatabaseError:
|
||||
# Do not stop the request from happening
|
||||
logger.exception("Database error while updating last_seen")
|
||||
|
||||
@@ -115,7 +115,7 @@ class PretixScanSecurityProfile(AllowListSecurityProfile):
|
||||
('GET', 'api-v1:event.settings'),
|
||||
('POST', 'api-v1:upload'),
|
||||
('POST', 'api-v1:checkinrpc.redeem'),
|
||||
('POST', 'api-v1:checkinrpc.annull'),
|
||||
('POST', 'api-v1:checkinrpc.annul'),
|
||||
('GET', 'api-v1:checkinrpc.search'),
|
||||
('GET', 'api-v1:reusablemedium-list'),
|
||||
('POST', 'api-v1:reusablemedium-lookup'),
|
||||
@@ -154,7 +154,7 @@ class PretixScanNoSyncNoSearchSecurityProfile(AllowListSecurityProfile):
|
||||
('GET', 'api-v1:event.settings'),
|
||||
('POST', 'api-v1:upload'),
|
||||
('POST', 'api-v1:checkinrpc.redeem'),
|
||||
('POST', 'api-v1:checkinrpc.annull'),
|
||||
('POST', 'api-v1:checkinrpc.annul'),
|
||||
('GET', 'api-v1:checkinrpc.search'),
|
||||
)
|
||||
|
||||
@@ -191,7 +191,7 @@ class PretixScanNoSyncSecurityProfile(AllowListSecurityProfile):
|
||||
('GET', 'api-v1:event.settings'),
|
||||
('POST', 'api-v1:upload'),
|
||||
('POST', 'api-v1:checkinrpc.redeem'),
|
||||
('POST', 'api-v1:checkinrpc.annull'),
|
||||
('POST', 'api-v1:checkinrpc.annul'),
|
||||
('GET', 'api-v1:checkinrpc.search'),
|
||||
)
|
||||
|
||||
|
||||
@@ -90,6 +90,7 @@ class CheckinRPCRedeemInputSerializer(serializers.Serializer):
|
||||
answers = serializers.JSONField(required=False, allow_null=True)
|
||||
exchange_medium_type = serializers.ChoiceField(required=False, choices=MEDIA_TYPES)
|
||||
exchange_medium_identifier = serializers.CharField(required=False)
|
||||
simulate = serializers.BooleanField(default=False, required=False)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
@@ -139,6 +139,7 @@ class CheckinListViewSet(viewsets.ModelViewSet):
|
||||
)
|
||||
return qs
|
||||
|
||||
@transaction.atomic()
|
||||
def perform_create(self, serializer):
|
||||
serializer.save(event=self.request.event)
|
||||
serializer.instance.log_action(
|
||||
@@ -153,6 +154,7 @@ class CheckinListViewSet(viewsets.ModelViewSet):
|
||||
ctx['event'] = self.request.event
|
||||
return ctx
|
||||
|
||||
@transaction.atomic()
|
||||
def perform_update(self, serializer):
|
||||
serializer.save(event=self.request.event)
|
||||
serializer.instance.log_action(
|
||||
@@ -837,6 +839,11 @@ def _redeem_process(*, checkinlists, raw_barcode, answers_data, datetime, force,
|
||||
)
|
||||
|
||||
if exchange_medium_identifier: # other fields are filled, see CheckinRPCRedeemInputSerializer.validate
|
||||
if simulate:
|
||||
raise CheckInError(
|
||||
gettext('You cannot simulate a medium exchange.'),
|
||||
'error'
|
||||
)
|
||||
with transaction.atomic():
|
||||
# Do exchange and check-in atomically, i.e. both succeed or both fail
|
||||
medium = perform_media_exchange(
|
||||
@@ -1064,6 +1071,7 @@ class CheckinRPCRedeemView(views.APIView):
|
||||
legacy_url_support=False,
|
||||
exchange_medium_type=s.validated_data.get('exchange_medium_type'),
|
||||
exchange_medium_identifier=s.validated_data.get('exchange_medium_identifier'),
|
||||
simulate=s.validated_data.get('simulate'),
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations under the License.
|
||||
|
||||
from django.db import transaction
|
||||
from django_filters.rest_framework import DjangoFilterBackend, FilterSet
|
||||
from django_scopes import scopes_disabled
|
||||
from rest_framework import viewsets
|
||||
@@ -64,6 +65,7 @@ class DiscountViewSet(ConditionalListView, viewsets.ModelViewSet):
|
||||
'limit_sales_channels',
|
||||
)
|
||||
|
||||
@transaction.atomic()
|
||||
def perform_create(self, serializer):
|
||||
serializer.save(event=self.request.event)
|
||||
serializer.instance.log_action(
|
||||
@@ -78,6 +80,7 @@ class DiscountViewSet(ConditionalListView, viewsets.ModelViewSet):
|
||||
ctx['event'] = self.request.event
|
||||
return ctx
|
||||
|
||||
@transaction.atomic()
|
||||
def perform_update(self, serializer):
|
||||
serializer.save(event=self.request.event)
|
||||
serializer.instance.log_action(
|
||||
@@ -87,6 +90,7 @@ class DiscountViewSet(ConditionalListView, viewsets.ModelViewSet):
|
||||
data=self.request.data
|
||||
)
|
||||
|
||||
@transaction.atomic()
|
||||
def perform_destroy(self, instance):
|
||||
if not instance.allow_delete():
|
||||
raise PermissionDenied('You cannot delete this discount because it already has '
|
||||
|
||||
@@ -257,6 +257,7 @@ class EventViewSet(viewsets.ModelViewSet):
|
||||
data=self.request.data
|
||||
)
|
||||
|
||||
@transaction.atomic()
|
||||
def perform_create(self, serializer):
|
||||
copy_from = None
|
||||
if 'clone_from' in self.request.GET:
|
||||
@@ -320,6 +321,7 @@ class EventViewSet(viewsets.ModelViewSet):
|
||||
data=self.request.data
|
||||
)
|
||||
|
||||
@transaction.atomic()
|
||||
def perform_destroy(self, instance):
|
||||
if not instance.allow_delete():
|
||||
raise PermissionDenied('The event can not be deleted as it already contains orders. Please set \'live\''
|
||||
@@ -355,6 +357,7 @@ class CloneEventViewSet(viewsets.ModelViewSet):
|
||||
ctx['organizer'] = self.request.organizer
|
||||
return ctx
|
||||
|
||||
@transaction.atomic()
|
||||
def perform_create(self, serializer):
|
||||
# Weird edge case: Requires settings permission on the event (to read) but also on the organizer (two write)
|
||||
perm_holder = (self.request.auth if isinstance(self.request.auth, (Device, TeamAPIToken))
|
||||
@@ -513,6 +516,7 @@ class SubEventViewSet(ConditionalListView, viewsets.ModelViewSet):
|
||||
resp['X-Page-Generated'] = date
|
||||
return resp
|
||||
|
||||
@transaction.atomic()
|
||||
def perform_update(self, serializer):
|
||||
original_data = self.get_serializer(instance=serializer.instance).data
|
||||
super().perform_update(serializer)
|
||||
@@ -529,6 +533,7 @@ class SubEventViewSet(ConditionalListView, viewsets.ModelViewSet):
|
||||
data=self.request.data
|
||||
)
|
||||
|
||||
@transaction.atomic()
|
||||
def perform_create(self, serializer):
|
||||
serializer.save(event=self.request.event)
|
||||
serializer.instance.log_action(
|
||||
@@ -538,6 +543,7 @@ class SubEventViewSet(ConditionalListView, viewsets.ModelViewSet):
|
||||
data=self.request.data
|
||||
)
|
||||
|
||||
@transaction.atomic()
|
||||
def perform_destroy(self, instance):
|
||||
if not instance.allow_delete():
|
||||
raise PermissionDenied('The sub-event can not be deleted as it has already been used in orders. Please set'
|
||||
@@ -566,6 +572,7 @@ class TaxRuleViewSet(ConditionalListView, viewsets.ModelViewSet):
|
||||
def get_queryset(self):
|
||||
return self.request.event.tax_rules.all()
|
||||
|
||||
@transaction.atomic()
|
||||
def perform_update(self, serializer):
|
||||
super().perform_update(serializer)
|
||||
serializer.instance.log_action(
|
||||
@@ -575,6 +582,7 @@ class TaxRuleViewSet(ConditionalListView, viewsets.ModelViewSet):
|
||||
data=self.request.data
|
||||
)
|
||||
|
||||
@transaction.atomic()
|
||||
def perform_create(self, serializer):
|
||||
serializer.save(event=self.request.event)
|
||||
serializer.instance.log_action(
|
||||
@@ -584,6 +592,7 @@ class TaxRuleViewSet(ConditionalListView, viewsets.ModelViewSet):
|
||||
data=self.request.data
|
||||
)
|
||||
|
||||
@transaction.atomic()
|
||||
def perform_destroy(self, instance):
|
||||
if not instance.allow_delete():
|
||||
raise PermissionDenied('This tax rule can not be deleted as it is currently in use.')
|
||||
@@ -757,6 +766,7 @@ class SeatViewSet(ConditionalListView, UpdateModelMixin, viewsets.ReadOnlyModelV
|
||||
}
|
||||
return ctx
|
||||
|
||||
@transaction.atomic()
|
||||
def perform_update(self, serializer):
|
||||
super().perform_update(serializer)
|
||||
serializer.instance.event.log_action(
|
||||
@@ -766,6 +776,7 @@ class SeatViewSet(ConditionalListView, UpdateModelMixin, viewsets.ReadOnlyModelV
|
||||
data={"seats": [serializer.instance.pk]},
|
||||
)
|
||||
|
||||
@transaction.atomic()
|
||||
def bulk_change_blocked(self, blocked):
|
||||
s = SeatBulkBlockInputSerializer(
|
||||
data=self.request.data,
|
||||
|
||||
@@ -23,6 +23,7 @@ from datetime import timedelta
|
||||
|
||||
from celery.result import AsyncResult
|
||||
from django.conf import settings
|
||||
from django.db import transaction
|
||||
from django.http import Http404
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.utils.functional import cached_property
|
||||
@@ -45,7 +46,8 @@ from pretix.base.models import (
|
||||
)
|
||||
from pretix.base.models.organizer import TeamQuerySet
|
||||
from pretix.base.services.export import (
|
||||
export, init_event_exporters, init_organizer_exporters, multiexport,
|
||||
ExportError, export, init_event_exporters, init_organizer_exporters,
|
||||
multiexport,
|
||||
)
|
||||
from pretix.helpers.http import ChunkBasedFileResponse
|
||||
|
||||
@@ -149,8 +151,11 @@ class EventExportersViewSet(ExportersMixin, viewsets.ViewSet):
|
||||
))
|
||||
exporters = []
|
||||
for ex in sorted(raw_exporters, key=lambda ex: str(ex.verbose_name)):
|
||||
ex._serializer = JobRunSerializer(exporter=ex)
|
||||
exporters.append(ex)
|
||||
try:
|
||||
ex._serializer = JobRunSerializer(exporter=ex)
|
||||
exporters.append(ex)
|
||||
except ExportError:
|
||||
pass
|
||||
return exporters
|
||||
|
||||
def do_export(self, cf, instance, data):
|
||||
@@ -180,8 +185,11 @@ class OrganizerExportersViewSet(ExportersMixin, viewsets.ViewSet):
|
||||
))
|
||||
exporters = []
|
||||
for ex in sorted(raw_exporters, key=lambda ex: str(ex.verbose_name)):
|
||||
ex._serializer = JobRunSerializer(exporter=ex)
|
||||
exporters.append(ex)
|
||||
try:
|
||||
ex._serializer = JobRunSerializer(exporter=ex)
|
||||
exporters.append(ex)
|
||||
except ExportError:
|
||||
pass
|
||||
return exporters
|
||||
|
||||
def do_export(self, cf, instance, data):
|
||||
@@ -220,6 +228,7 @@ class ScheduledEventExportViewSet(ScheduledExportersViewSet):
|
||||
qs = self.request.event.scheduled_exports
|
||||
return qs.select_related("owner")
|
||||
|
||||
@transaction.atomic()
|
||||
def perform_create(self, serializer):
|
||||
if not self.request.user.is_authenticated:
|
||||
raise PermissionDenied('Creation of exports requires user-specific API access.')
|
||||
@@ -250,6 +259,7 @@ class ScheduledEventExportViewSet(ScheduledExportersViewSet):
|
||||
))
|
||||
return {e.identifier: e for e in exporters}
|
||||
|
||||
@transaction.atomic()
|
||||
def perform_update(self, serializer):
|
||||
if not self.request.user.is_authenticated or self.request.user != serializer.instance.owner:
|
||||
# This is to prevent a possible privilege escalation where user A creates a scheduled export and
|
||||
@@ -275,6 +285,7 @@ class ScheduledEventExportViewSet(ScheduledExportersViewSet):
|
||||
data=self.request.data
|
||||
)
|
||||
|
||||
@transaction.atomic()
|
||||
def perform_destroy(self, instance):
|
||||
self.request.event.log_action(
|
||||
'pretix.event.export.schedule.deleted',
|
||||
@@ -302,6 +313,7 @@ class ScheduledOrganizerExportViewSet(ScheduledExportersViewSet):
|
||||
qs = self.request.organizer.scheduled_exports
|
||||
return qs.select_related("owner")
|
||||
|
||||
@transaction.atomic()
|
||||
def perform_create(self, serializer):
|
||||
if not self.request.user.is_authenticated:
|
||||
raise PermissionDenied('Creation of exports requires user-specific API access.')
|
||||
@@ -332,6 +344,7 @@ class ScheduledOrganizerExportViewSet(ScheduledExportersViewSet):
|
||||
))
|
||||
return {e.identifier: e for e in exporters}
|
||||
|
||||
@transaction.atomic()
|
||||
def perform_update(self, serializer):
|
||||
if not self.request.user.is_authenticated or self.request.user != serializer.instance.owner:
|
||||
# This is to prevent a possible privilege escalation where user A creates a scheduled export and
|
||||
@@ -382,6 +395,7 @@ class ScheduledOrganizerExportViewSet(ScheduledExportersViewSet):
|
||||
data=self.request.data
|
||||
)
|
||||
|
||||
@transaction.atomic()
|
||||
def perform_destroy(self, instance):
|
||||
self.request.organizer.log_action(
|
||||
'pretix.organizer.export.schedule.deleted',
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
# License for the specific language governing permissions and limitations under the License.
|
||||
|
||||
import django_filters
|
||||
from django.db import transaction
|
||||
from django.db.models import Q
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.utils.functional import cached_property
|
||||
@@ -109,6 +110,7 @@ class ItemViewSet(ConditionalListView, viewsets.ModelViewSet):
|
||||
'limit_sales_channels', 'variations__limit_sales_channels', 'program_times'
|
||||
).all()
|
||||
|
||||
@transaction.atomic()
|
||||
def perform_create(self, serializer):
|
||||
serializer.save(event=self.request.event)
|
||||
serializer.instance.log_action(
|
||||
@@ -123,6 +125,7 @@ class ItemViewSet(ConditionalListView, viewsets.ModelViewSet):
|
||||
ctx['event'] = self.request.event
|
||||
return ctx
|
||||
|
||||
@transaction.atomic()
|
||||
def perform_update(self, serializer):
|
||||
original_data = self.get_serializer(instance=serializer.instance).data
|
||||
|
||||
@@ -139,6 +142,7 @@ class ItemViewSet(ConditionalListView, viewsets.ModelViewSet):
|
||||
data=self.request.data
|
||||
)
|
||||
|
||||
@transaction.atomic()
|
||||
def perform_destroy(self, instance):
|
||||
if not instance.allow_delete():
|
||||
raise PermissionDenied('This item cannot be deleted because it has already been ordered '
|
||||
@@ -183,6 +187,7 @@ class ItemVariationViewSet(viewsets.ModelViewSet):
|
||||
ctx['event'] = self.request.event
|
||||
return ctx
|
||||
|
||||
@transaction.atomic()
|
||||
def perform_create(self, serializer):
|
||||
item = self.item
|
||||
if not item.has_variations:
|
||||
@@ -197,6 +202,7 @@ class ItemVariationViewSet(viewsets.ModelViewSet):
|
||||
{'value': serializer.instance.value})
|
||||
)
|
||||
|
||||
@transaction.atomic()
|
||||
def perform_update(self, serializer):
|
||||
serializer.save(event=self.request.event)
|
||||
serializer.instance.item.log_action(
|
||||
@@ -207,6 +213,7 @@ class ItemVariationViewSet(viewsets.ModelViewSet):
|
||||
{'value': serializer.instance.value})
|
||||
)
|
||||
|
||||
@transaction.atomic()
|
||||
def perform_destroy(self, instance):
|
||||
if not instance.allow_delete():
|
||||
raise PermissionDenied('This variation cannot be deleted because it has already been ordered '
|
||||
@@ -249,6 +256,7 @@ class ItemBundleViewSet(viewsets.ModelViewSet):
|
||||
ctx['item'] = self.item
|
||||
return ctx
|
||||
|
||||
@transaction.atomic()
|
||||
def perform_create(self, serializer):
|
||||
item = get_object_or_404(Item, pk=self.kwargs['item'], event=self.request.event)
|
||||
serializer.save(base_item=item)
|
||||
@@ -259,6 +267,7 @@ class ItemBundleViewSet(viewsets.ModelViewSet):
|
||||
data=merge_dicts(self.request.data, {'id': serializer.instance.pk})
|
||||
)
|
||||
|
||||
@transaction.atomic()
|
||||
def perform_update(self, serializer):
|
||||
serializer.save(event=self.request.event)
|
||||
serializer.instance.base_item.log_action(
|
||||
@@ -268,6 +277,7 @@ class ItemBundleViewSet(viewsets.ModelViewSet):
|
||||
data=merge_dicts(self.request.data, {'id': serializer.instance.pk})
|
||||
)
|
||||
|
||||
@transaction.atomic()
|
||||
def perform_destroy(self, instance):
|
||||
super().perform_destroy(instance)
|
||||
instance.base_item.log_action(
|
||||
@@ -303,6 +313,7 @@ class ItemProgramTimeViewSet(viewsets.ModelViewSet):
|
||||
ctx['item'] = self.item
|
||||
return ctx
|
||||
|
||||
@transaction.atomic()
|
||||
def perform_create(self, serializer):
|
||||
item = get_object_or_404(Item, pk=self.kwargs['item'], event=self.request.event)
|
||||
serializer.save(item=item)
|
||||
@@ -313,6 +324,7 @@ class ItemProgramTimeViewSet(viewsets.ModelViewSet):
|
||||
data=merge_dicts(self.request.data, {'id': serializer.instance.pk})
|
||||
)
|
||||
|
||||
@transaction.atomic()
|
||||
def perform_update(self, serializer):
|
||||
serializer.save(event=self.request.event)
|
||||
serializer.instance.item.log_action(
|
||||
@@ -322,6 +334,7 @@ class ItemProgramTimeViewSet(viewsets.ModelViewSet):
|
||||
data=merge_dicts(self.request.data, {'id': serializer.instance.pk})
|
||||
)
|
||||
|
||||
@transaction.atomic()
|
||||
def perform_destroy(self, instance):
|
||||
super().perform_destroy(instance)
|
||||
instance.item.log_action(
|
||||
@@ -354,6 +367,7 @@ class ItemAddOnViewSet(viewsets.ModelViewSet):
|
||||
ctx['item'] = self.item
|
||||
return ctx
|
||||
|
||||
@transaction.atomic()
|
||||
def perform_create(self, serializer):
|
||||
item = self.item
|
||||
category = get_object_or_404(ItemCategory, pk=self.request.data['addon_category'])
|
||||
@@ -365,6 +379,7 @@ class ItemAddOnViewSet(viewsets.ModelViewSet):
|
||||
data=merge_dicts(self.request.data, {'ORDER': serializer.instance.position}, {'id': serializer.instance.pk})
|
||||
)
|
||||
|
||||
@transaction.atomic()
|
||||
def perform_update(self, serializer):
|
||||
serializer.save(event=self.request.event)
|
||||
serializer.instance.base_item.log_action(
|
||||
@@ -374,6 +389,7 @@ class ItemAddOnViewSet(viewsets.ModelViewSet):
|
||||
data=merge_dicts(self.request.data, {'ORDER': serializer.instance.position}, {'id': serializer.instance.pk})
|
||||
)
|
||||
|
||||
@transaction.atomic()
|
||||
def perform_destroy(self, instance):
|
||||
super().perform_destroy(instance)
|
||||
instance.base_item.log_action(
|
||||
@@ -403,6 +419,7 @@ class ItemCategoryViewSet(ConditionalListView, viewsets.ModelViewSet):
|
||||
def get_queryset(self):
|
||||
return self.request.event.categories.all()
|
||||
|
||||
@transaction.atomic()
|
||||
def perform_create(self, serializer):
|
||||
serializer.save(event=self.request.event)
|
||||
serializer.instance.log_action(
|
||||
@@ -417,6 +434,7 @@ class ItemCategoryViewSet(ConditionalListView, viewsets.ModelViewSet):
|
||||
ctx['event'] = self.request.event
|
||||
return ctx
|
||||
|
||||
@transaction.atomic()
|
||||
def perform_update(self, serializer):
|
||||
serializer.save(event=self.request.event)
|
||||
serializer.instance.log_action(
|
||||
@@ -426,6 +444,7 @@ class ItemCategoryViewSet(ConditionalListView, viewsets.ModelViewSet):
|
||||
data=self.request.data
|
||||
)
|
||||
|
||||
@transaction.atomic()
|
||||
def perform_destroy(self, instance):
|
||||
for item in instance.items.all():
|
||||
item.category = None
|
||||
@@ -458,6 +477,7 @@ class QuestionViewSet(ConditionalListView, viewsets.ModelViewSet):
|
||||
def get_queryset(self):
|
||||
return self.request.event.questions.prefetch_related('options').all()
|
||||
|
||||
@transaction.atomic()
|
||||
def perform_create(self, serializer):
|
||||
serializer.save(event=self.request.event)
|
||||
serializer.instance.log_action(
|
||||
@@ -472,6 +492,7 @@ class QuestionViewSet(ConditionalListView, viewsets.ModelViewSet):
|
||||
ctx['event'] = self.request.event
|
||||
return ctx
|
||||
|
||||
@transaction.atomic()
|
||||
def perform_update(self, serializer):
|
||||
serializer.save(event=self.request.event)
|
||||
serializer.instance.log_action(
|
||||
@@ -481,6 +502,7 @@ class QuestionViewSet(ConditionalListView, viewsets.ModelViewSet):
|
||||
data=self.request.data
|
||||
)
|
||||
|
||||
@transaction.atomic()
|
||||
def perform_destroy(self, instance):
|
||||
instance.log_action(
|
||||
'pretix.event.question.deleted',
|
||||
@@ -509,6 +531,7 @@ class QuestionOptionViewSet(viewsets.ModelViewSet):
|
||||
ctx['question'] = get_object_or_404(Question, pk=self.kwargs['question'], event=self.request.event)
|
||||
return ctx
|
||||
|
||||
@transaction.atomic()
|
||||
def perform_create(self, serializer):
|
||||
q = get_object_or_404(Question, pk=self.kwargs['question'], event=self.request.event)
|
||||
serializer.save(question=q)
|
||||
@@ -519,6 +542,7 @@ class QuestionOptionViewSet(viewsets.ModelViewSet):
|
||||
data=merge_dicts(self.request.data, {'ORDER': serializer.instance.position}, {'id': serializer.instance.pk})
|
||||
)
|
||||
|
||||
@transaction.atomic()
|
||||
def perform_update(self, serializer):
|
||||
serializer.save(event=self.request.event)
|
||||
serializer.instance.question.log_action(
|
||||
@@ -528,6 +552,7 @@ class QuestionOptionViewSet(viewsets.ModelViewSet):
|
||||
data=merge_dicts(self.request.data, {'ORDER': serializer.instance.position}, {'id': serializer.instance.pk})
|
||||
)
|
||||
|
||||
@transaction.atomic()
|
||||
def perform_destroy(self, instance):
|
||||
instance.question.log_action(
|
||||
'pretix.event.question.option.deleted',
|
||||
@@ -586,6 +611,7 @@ class QuotaViewSet(ConditionalListView, viewsets.ModelViewSet):
|
||||
serializer = self.get_serializer(page, many=True)
|
||||
return self.get_paginated_response(serializer.data)
|
||||
|
||||
@transaction.atomic()
|
||||
def perform_create(self, serializer):
|
||||
serializer.save(event=self.request.event)
|
||||
serializer.instance.log_action(
|
||||
@@ -608,6 +634,7 @@ class QuotaViewSet(ConditionalListView, viewsets.ModelViewSet):
|
||||
ctx['request'] = self.request
|
||||
return ctx
|
||||
|
||||
@transaction.atomic()
|
||||
def perform_update(self, serializer):
|
||||
original_data = self.get_serializer(instance=serializer.instance).data
|
||||
|
||||
@@ -663,6 +690,7 @@ class QuotaViewSet(ConditionalListView, viewsets.ModelViewSet):
|
||||
)
|
||||
serializer.instance.rebuild_cache()
|
||||
|
||||
@transaction.atomic()
|
||||
def perform_destroy(self, instance):
|
||||
instance.log_action(
|
||||
'pretix.event.quota.deleted',
|
||||
|
||||
@@ -394,6 +394,7 @@ class TeamViewSet(viewsets.ModelViewSet):
|
||||
)
|
||||
return inst
|
||||
|
||||
@transaction.atomic()
|
||||
def perform_destroy(self, instance):
|
||||
instance.log_action('pretix.team.deleted', user=self.request.user, auth=self.request.auth)
|
||||
instance.delete()
|
||||
@@ -693,6 +694,7 @@ class MembershipTypeViewSet(viewsets.ModelViewSet):
|
||||
ctx['organizer'] = self.request.organizer
|
||||
return ctx
|
||||
|
||||
@transaction.atomic()
|
||||
def perform_destroy(self, instance):
|
||||
if not instance.allow_delete():
|
||||
raise PermissionDenied("Can only be deleted if unused.")
|
||||
@@ -833,6 +835,7 @@ class SalesChannelViewSet(viewsets.ModelViewSet):
|
||||
)
|
||||
return inst
|
||||
|
||||
@transaction.atomic()
|
||||
def perform_destroy(self, instance):
|
||||
if not instance.allow_delete():
|
||||
raise PermissionDenied("Can only be deleted if unused.")
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
import django_filters
|
||||
from django.db import transaction
|
||||
from django_filters.rest_framework import DjangoFilterBackend, FilterSet
|
||||
from django_scopes import scopes_disabled
|
||||
from rest_framework import viewsets
|
||||
@@ -62,6 +63,7 @@ class WaitingListViewSet(viewsets.ModelViewSet):
|
||||
ctx['event'] = self.request.event
|
||||
return ctx
|
||||
|
||||
@transaction.atomic()
|
||||
def perform_create(self, serializer):
|
||||
serializer.save(event=self.request.event)
|
||||
serializer.instance.log_action(
|
||||
@@ -70,6 +72,7 @@ class WaitingListViewSet(viewsets.ModelViewSet):
|
||||
auth=self.request.auth,
|
||||
)
|
||||
|
||||
@transaction.atomic()
|
||||
def perform_update(self, serializer):
|
||||
if serializer.instance.voucher:
|
||||
raise PermissionDenied('This entry can not be changed as it has already been assigned a voucher.')
|
||||
@@ -80,6 +83,7 @@ class WaitingListViewSet(viewsets.ModelViewSet):
|
||||
auth=self.request.auth,
|
||||
)
|
||||
|
||||
@transaction.atomic()
|
||||
def perform_destroy(self, instance):
|
||||
if instance.voucher:
|
||||
raise PermissionDenied('This entry can not be deleted as it has already been assigned a voucher.')
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
import django_filters
|
||||
from django.db import transaction
|
||||
from django_filters.rest_framework import DjangoFilterBackend, FilterSet
|
||||
from rest_framework import viewsets
|
||||
|
||||
@@ -48,6 +49,7 @@ class WebHookViewSet(viewsets.ModelViewSet):
|
||||
ctx['organizer'] = self.request.organizer
|
||||
return ctx
|
||||
|
||||
@transaction.atomic()
|
||||
def perform_create(self, serializer):
|
||||
inst = serializer.save(organizer=self.request.organizer)
|
||||
self.request.organizer.log_action(
|
||||
@@ -57,6 +59,7 @@ class WebHookViewSet(viewsets.ModelViewSet):
|
||||
data=merge_dicts(self.request.data, {'id': inst.pk})
|
||||
)
|
||||
|
||||
@transaction.atomic()
|
||||
def perform_update(self, serializer):
|
||||
inst = serializer.save(organizer=self.request.organizer)
|
||||
self.request.organizer.log_action(
|
||||
@@ -67,6 +70,7 @@ class WebHookViewSet(viewsets.ModelViewSet):
|
||||
)
|
||||
return inst
|
||||
|
||||
@transaction.atomic()
|
||||
def perform_destroy(self, instance):
|
||||
self.request.organizer.log_action(
|
||||
'pretix.webhook.changed',
|
||||
|
||||
@@ -28,6 +28,5 @@ from .items import * # noqa
|
||||
from .json import * # noqa
|
||||
from .mail import * # noqa
|
||||
from .orderlist import * # noqa
|
||||
from .relevant_orderlist import * # noqa
|
||||
from .reusablemedia import * # noqa
|
||||
from .waitinglist import * # noqa
|
||||
|
||||
@@ -89,7 +89,7 @@ class OrderListExporter(MultiSheetListExporter):
|
||||
description = gettext_lazy('Download a spreadsheet of all orders. The spreadsheet will include three sheets, one '
|
||||
'with a line for every order, one with a line for every order position, and one with '
|
||||
'a line for every additional fee charged in an order.')
|
||||
featured = False
|
||||
featured = True
|
||||
repeatable_read = False
|
||||
|
||||
@cached_property
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -314,3 +314,18 @@ class BusinessBooleanRadio(forms.RadioSelect):
|
||||
'False': False,
|
||||
False: False,
|
||||
}.get(value)
|
||||
|
||||
|
||||
class OptionAttrsSelect(forms.Select):
|
||||
def __init__(self, *args, option_attrs=None, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.option_attrs = option_attrs or {}
|
||||
|
||||
def create_option(self, name, value, label, selected, index, subindex=None, attrs=None):
|
||||
option = super().create_option(
|
||||
name, value, label, selected, index, subindex=subindex, attrs=attrs
|
||||
)
|
||||
extra = self.option_attrs.get(str(value))
|
||||
if extra:
|
||||
option["attrs"].update(extra)
|
||||
return option
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
# Generated by Django 5.2.16 on 2026-08-05 08:00
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
import pretix.helpers.database
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("pretixbase", "0306_alter_eventmetaproperty_unique_together"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="DeviceLastSeen",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True, primary_key=True, serialize=False
|
||||
),
|
||||
),
|
||||
("last_seen", models.DateTimeField(auto_now=True)),
|
||||
(
|
||||
"device",
|
||||
models.OneToOneField(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="pretixbase.device",
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name="devicelastseen",
|
||||
index=pretix.helpers.database.BrinIndexIgnoredOnSQLite(
|
||||
models.F("last_seen"),
|
||||
autosummarize=True,
|
||||
name="pretixbase_device_last_seen",
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -20,11 +20,13 @@
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
import string
|
||||
from datetime import timedelta
|
||||
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import models
|
||||
from django.db.models import Max
|
||||
from django.utils.crypto import get_random_string
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django_scopes import ScopedManager, scopes_disabled
|
||||
|
||||
@@ -32,6 +34,7 @@ from pretix.base.models import LoggedModel
|
||||
from pretix.base.permissions import (
|
||||
AnyPermissionOf, assert_valid_event_permission,
|
||||
)
|
||||
from pretix.helpers import BrinIndexIgnoredOnSQLite
|
||||
|
||||
|
||||
@scopes_disabled()
|
||||
@@ -287,3 +290,27 @@ class Device(LoggedModel):
|
||||
return self.get_events_with_any_permission()
|
||||
else:
|
||||
return self.organizer.events.none()
|
||||
|
||||
|
||||
class DeviceLastSeen(models.Model):
|
||||
# This is a separate model since we expect it to get A LOT of writes and PostgreSQL always
|
||||
# writes full rows and then needs to update all indexes on the row, so this is going to save a
|
||||
# lot of write traffic on the databse
|
||||
device = models.OneToOneField("Device", on_delete=models.CASCADE, related_name="last_seen")
|
||||
last_seen = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
indexes = [
|
||||
BrinIndexIgnoredOnSQLite(
|
||||
# BRIN indexes are highly efficient on lots of updates, especially of chronological data
|
||||
# and especially if we later want to query them by range, as we likely want to.
|
||||
"last_seen",
|
||||
name="pretixbase_device_last_seen",
|
||||
autosummarize=True
|
||||
)
|
||||
]
|
||||
|
||||
@property
|
||||
def is_recent(self):
|
||||
# pretixSCAN/pretixPOS sync every 5 minutes, so 7 minutes can be considered "offline"
|
||||
return now() - self.last_seen < timedelta(minutes=7)
|
||||
|
||||
+373
-155
@@ -19,30 +19,245 @@
|
||||
# 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 copy
|
||||
import datetime
|
||||
import os
|
||||
import warnings
|
||||
from collections import namedtuple
|
||||
from typing import Union
|
||||
from dataclasses import dataclass
|
||||
from typing import (
|
||||
TYPE_CHECKING, Iterable, List, Literal, Optional, Tuple, Union, cast,
|
||||
)
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
from dateutil import parser
|
||||
from django import forms
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import models
|
||||
from django.forms.widgets import Widget
|
||||
from django.utils.formats import get_format
|
||||
from django.utils.functional import lazy
|
||||
from django.utils.functional import Promise, lazy
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from rest_framework import serializers
|
||||
|
||||
BASE_CHOICES = (
|
||||
('date_from', _('Event start')),
|
||||
('date_to', _('Event end')),
|
||||
('date_admission', _('Event admission')),
|
||||
('presale_start', _('Presale start')),
|
||||
('presale_end', _('Presale end')),
|
||||
)
|
||||
from pretix.base.forms.widgets import OptionAttrsSelect
|
||||
|
||||
RelativeDate = namedtuple('RelativeDate', ['days', 'minutes', 'time', 'is_after', 'base_date_name'], defaults=(0, None, None, False, 'date_from'))
|
||||
if TYPE_CHECKING:
|
||||
from .models import Event, Order, SubEvent
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class BaseChoice:
|
||||
base: Literal["event", "order"]
|
||||
attribute: str
|
||||
text: Promise
|
||||
supports_before: bool
|
||||
supports_after: bool
|
||||
|
||||
@property
|
||||
def key(self) -> str:
|
||||
key = f"{self.base}__{self.attribute}"
|
||||
return key
|
||||
|
||||
@staticmethod
|
||||
def find(objects: Iterable["BaseChoice"], key: str) -> "BaseChoice":
|
||||
if "__" in key:
|
||||
choice = next((obj for obj in objects if obj.key == key), None)
|
||||
else:
|
||||
# fallback for RelativeDateFields stored, before support for bases other than event was added
|
||||
choice = next((obj for obj in objects if obj.attribute == key and obj.base == "event"), None)
|
||||
|
||||
if choice is None:
|
||||
raise TypeError(f"key {key} must be a valid key in BASE_CHOICES")
|
||||
|
||||
return choice
|
||||
|
||||
|
||||
BASE_CHOICES: List[BaseChoice] = [
|
||||
BaseChoice('event', 'date_from', _('Event start'), True, True),
|
||||
BaseChoice('event', 'date_to', _('Event end'), True, True),
|
||||
BaseChoice('event', 'date_admission', _('Event admission'), True, True),
|
||||
BaseChoice('event', 'presale_start', _('Presale start'), True, True),
|
||||
BaseChoice('event', 'presale_end', _('Presale end'), True, True),
|
||||
BaseChoice('order', 'datetime', _('Order creation'), False, True),
|
||||
BaseChoice('order', 'expires', _('Order expiry'), True, True),
|
||||
]
|
||||
|
||||
EVENT_BASE_CHOICES = [
|
||||
x for x in BASE_CHOICES if x.base == 'event'
|
||||
]
|
||||
|
||||
ORDER_BASE_CHOICES = [
|
||||
x for x in BASE_CHOICES if x.base == 'order'
|
||||
]
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class RelativeDate:
|
||||
"""
|
||||
This contains information on a date that is defined in relation to a fixed base point.
|
||||
This means that the underlying data is a fixed date as the base point and a number of days or a time interval
|
||||
to calculate the date.
|
||||
|
||||
The list of valid base date choices is defined in BASE_CHOICES.
|
||||
If the base_date_name is not set, the date_from attribute of Event is used.
|
||||
"""
|
||||
|
||||
days: int = 0
|
||||
minutes: Optional[int] = None
|
||||
time: Optional[datetime.time] = None
|
||||
is_after: bool = False
|
||||
base_date_name: str = 'event__date_from__'
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if self.is_after and not self._choice.supports_after:
|
||||
raise ValueError(
|
||||
"The selected base date and attribute combination does not support relative dates placed after the base date"
|
||||
)
|
||||
if not self.is_after and not self._choice.supports_before:
|
||||
raise ValueError(
|
||||
"The selected base date and attribute combination does not support relative dates placed before the base date")
|
||||
|
||||
@property
|
||||
def _choice(self):
|
||||
return BaseChoice.find(BASE_CHOICES, self.base_date_name)
|
||||
|
||||
@property
|
||||
def key(self):
|
||||
return self._choice.key
|
||||
|
||||
def __eq__(self, o: object) -> bool:
|
||||
if not isinstance(o, RelativeDate):
|
||||
return False
|
||||
return self.to_string() == o.to_string()
|
||||
|
||||
def _resolve_date(self, base: "Event | Order | SubEvent") -> Tuple[datetime.datetime, ZoneInfo]:
|
||||
"""
|
||||
Resolves the datetime and timezone information of this RelativeDate object in relation to the provided reference.
|
||||
|
||||
:param base: the reference which should be used to resolve the relative date
|
||||
:return:
|
||||
"""
|
||||
from .models import Event, Order, SubEvent
|
||||
|
||||
choice = self._choice
|
||||
|
||||
if choice.base == "order" and isinstance(base, Order):
|
||||
event = base.event
|
||||
base_date = getattr(base, choice.attribute)
|
||||
elif choice.base == "event" and isinstance(base, SubEvent):
|
||||
event = base.event
|
||||
base_date = (getattr(base, choice.attribute) or
|
||||
getattr(base.event, choice.attribute) or
|
||||
base.date_from)
|
||||
elif choice.base == "event" and isinstance(base, Event):
|
||||
event = base
|
||||
base_date = getattr(base, choice.attribute) or event.date_from
|
||||
else:
|
||||
raise TypeError("The base defined by data does not match the passed in base")
|
||||
|
||||
tz = ZoneInfo(event.settings.timezone)
|
||||
return base_date, tz
|
||||
|
||||
def date(self, base: "Event | Order | SubEvent") -> datetime.date:
|
||||
"""
|
||||
Resolves the effective date of this RelativeDate object in relation to the provided reference.
|
||||
|
||||
:param base: the reference which should be used to resolve the date
|
||||
:return: datetime.date
|
||||
"""
|
||||
if self.minutes is not None:
|
||||
raise ValueError('A minute-based relative datetime can not be used as a date')
|
||||
|
||||
base_date, tz = self._resolve_date(base)
|
||||
|
||||
if self.is_after:
|
||||
new_date = base_date.astimezone(tz) + datetime.timedelta(days=self.days)
|
||||
else:
|
||||
new_date = base_date.astimezone(tz) - datetime.timedelta(days=self.days)
|
||||
return new_date.date()
|
||||
|
||||
def datetime(self, base: "Event | Order | SubEvent") -> datetime.datetime:
|
||||
"""
|
||||
Resolves the effective datetime of this RelativeDate object in relation to the provided reference.
|
||||
|
||||
:param base: the reference which should be used to resolve the datetime
|
||||
:return: datetime.datetime
|
||||
"""
|
||||
base_date, tz = self._resolve_date(base)
|
||||
|
||||
if self.minutes is not None:
|
||||
if self.is_after:
|
||||
return base_date.astimezone(tz) + datetime.timedelta(minutes=self.minutes)
|
||||
else:
|
||||
return base_date.astimezone(tz) - datetime.timedelta(minutes=self.minutes)
|
||||
else:
|
||||
if self.is_after:
|
||||
new_date = (base_date.astimezone(tz) + datetime.timedelta(days=self.days)).astimezone(tz)
|
||||
else:
|
||||
new_date = (base_date.astimezone(tz) - datetime.timedelta(days=self.days)).astimezone(tz)
|
||||
if self.time:
|
||||
new_date = new_date.replace(
|
||||
hour=self.time.hour,
|
||||
minute=self.time.minute,
|
||||
second=self.time.second
|
||||
)
|
||||
new_date = new_date.astimezone(tz)
|
||||
return new_date
|
||||
|
||||
def to_string(self) -> str:
|
||||
if self.minutes is not None:
|
||||
return 'RELDATE/minutes/{}/{}/{}'.format( #
|
||||
self.minutes,
|
||||
self._choice.key,
|
||||
'after' if self.is_after else '',
|
||||
)
|
||||
return 'RELDATE/{}/{}/{}/{}'.format( #
|
||||
self.days,
|
||||
self.time.strftime('%H:%M:%S') if self.time else '-',
|
||||
self._choice.key,
|
||||
'after' if self.is_after else '',
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_string(cls, input: str):
|
||||
if not input.startswith('RELDATE/'):
|
||||
raise TypeError("Invalid input for RelativeDate.from_string()")
|
||||
|
||||
parts = input.split('/')
|
||||
if parts[1] == 'minutes':
|
||||
data = RelativeDate(
|
||||
days=0,
|
||||
minutes=int(parts[2]),
|
||||
base_date_name=parts[3],
|
||||
time=None,
|
||||
is_after=len(parts) > 4 and parts[4] == "after",
|
||||
)
|
||||
else:
|
||||
if parts[2] == '-':
|
||||
time = None
|
||||
else:
|
||||
timeparts = parts[2].split(':')
|
||||
time = datetime.time(hour=int(timeparts[0]), minute=int(timeparts[1]), second=int(timeparts[2]))
|
||||
try:
|
||||
data = RelativeDate(
|
||||
days=int(parts[1] or 0),
|
||||
base_date_name=parts[3],
|
||||
time=time,
|
||||
minutes=None,
|
||||
is_after=len(parts) > 4 and parts[4] == "after",
|
||||
)
|
||||
except ValueError:
|
||||
data = RelativeDate(
|
||||
days=0,
|
||||
base_date_name=parts[3],
|
||||
time=time,
|
||||
minutes=None,
|
||||
is_after=len(parts) > 4 and parts[4] == "after",
|
||||
)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
class RelativeDateWrapper:
|
||||
@@ -50,130 +265,53 @@ class RelativeDateWrapper:
|
||||
This contains information on a date that might be relative to an event. This means
|
||||
that the underlying data is either a fixed date or a number of days and a wall clock
|
||||
time to calculate the date based on a base point.
|
||||
|
||||
The base point can be the date_from, date_to, date_admission, presale_start or presale_end
|
||||
attribute of an event or subevent. If the respective attribute is not set, ``date_from``
|
||||
will be used.
|
||||
"""
|
||||
|
||||
def __init__(self, data: Union[datetime.datetime, RelativeDate]):
|
||||
self.data = data
|
||||
|
||||
def date(self, event) -> datetime.date:
|
||||
from .models import SubEvent
|
||||
def date(self, base: "Event | Order | SubEvent") -> datetime.date:
|
||||
"""
|
||||
If the RelativeDateWrapper wraps a RelativeDate object:
|
||||
Resolves the effective date of this object in relation to the provided reference.
|
||||
If the RelativeDateWrapper wraps an absolute date or datetime:
|
||||
Returns the wrapped absolute date.
|
||||
|
||||
:param base: the reference which should be used to resolve the date in case it is a relative date.
|
||||
:return: datetime.date
|
||||
"""
|
||||
if isinstance(self.data, datetime.datetime):
|
||||
return self.data.date()
|
||||
elif isinstance(self.data, datetime.date):
|
||||
return self.data
|
||||
else:
|
||||
if self.data.minutes is not None:
|
||||
raise ValueError('A minute-based relative datetime can not be used as a date')
|
||||
return self.data.date(base)
|
||||
|
||||
tz = ZoneInfo(event.settings.timezone)
|
||||
if isinstance(event, SubEvent):
|
||||
base_date = (
|
||||
getattr(event, self.data.base_date_name)
|
||||
or getattr(event.event, self.data.base_date_name)
|
||||
or event.date_from
|
||||
)
|
||||
else:
|
||||
base_date = getattr(event, self.data.base_date_name) or event.date_from
|
||||
|
||||
if self.data.is_after:
|
||||
new_date = base_date.astimezone(tz) + datetime.timedelta(days=self.data.days)
|
||||
else:
|
||||
new_date = base_date.astimezone(tz) - datetime.timedelta(days=self.data.days)
|
||||
return new_date.date()
|
||||
|
||||
def datetime(self, event) -> datetime.datetime:
|
||||
from .models import SubEvent
|
||||
def datetime(self, base: "Event | Order | SubEvent") -> datetime.datetime:
|
||||
"""
|
||||
If the RelativeDateWrapper wraps a RelativeDate object:
|
||||
Resolves the effective datetime of this object in relation to the provided reference.
|
||||
If the RelativeDateWrapper wraps an absolute date or datetime:
|
||||
Returns the wrapped absolute datetime.
|
||||
|
||||
:param base: the reference which should be used to resolve the datetime in case it is a relative datetime.
|
||||
:return: datetime.datetime
|
||||
"""
|
||||
if isinstance(self.data, (datetime.datetime, datetime.date)):
|
||||
return self.data
|
||||
else:
|
||||
tz = ZoneInfo(event.settings.timezone)
|
||||
if isinstance(event, SubEvent):
|
||||
base_date = (
|
||||
getattr(event, self.data.base_date_name)
|
||||
or getattr(event.event, self.data.base_date_name)
|
||||
or event.date_from
|
||||
)
|
||||
else:
|
||||
base_date = getattr(event, self.data.base_date_name) or event.date_from
|
||||
|
||||
if self.data.minutes is not None:
|
||||
if self.data.is_after:
|
||||
return base_date.astimezone(tz) + datetime.timedelta(minutes=self.data.minutes)
|
||||
else:
|
||||
return base_date.astimezone(tz) - datetime.timedelta(minutes=self.data.minutes)
|
||||
else:
|
||||
if self.data.is_after:
|
||||
new_date = (base_date.astimezone(tz) + datetime.timedelta(days=self.data.days)).astimezone(tz)
|
||||
else:
|
||||
new_date = (base_date.astimezone(tz) - datetime.timedelta(days=self.data.days)).astimezone(tz)
|
||||
if self.data.time:
|
||||
new_date = new_date.replace(
|
||||
hour=self.data.time.hour,
|
||||
minute=self.data.time.minute,
|
||||
second=self.data.time.second
|
||||
)
|
||||
new_date = new_date.astimezone(tz)
|
||||
return new_date
|
||||
return self.data.datetime(base)
|
||||
|
||||
def to_string(self) -> str:
|
||||
if isinstance(self.data, (datetime.datetime, datetime.date)):
|
||||
return self.data.isoformat()
|
||||
else:
|
||||
if self.data.minutes is not None:
|
||||
return 'RELDATE/minutes/{}/{}/{}'.format( #
|
||||
self.data.minutes,
|
||||
self.data.base_date_name,
|
||||
'after' if self.data.is_after else '',
|
||||
)
|
||||
return 'RELDATE/{}/{}/{}/{}'.format( #
|
||||
self.data.days,
|
||||
self.data.time.strftime('%H:%M:%S') if self.data.time else '-',
|
||||
self.data.base_date_name,
|
||||
'after' if self.data.is_after else '',
|
||||
)
|
||||
return self.data.to_string()
|
||||
|
||||
@classmethod
|
||||
def from_string(cls, input: str):
|
||||
if input.startswith('RELDATE/'):
|
||||
parts = input.split('/')
|
||||
if parts[1] == 'minutes':
|
||||
data = RelativeDate(
|
||||
days=0,
|
||||
minutes=int(parts[2]),
|
||||
base_date_name=parts[3],
|
||||
time=None,
|
||||
is_after=len(parts) > 4 and parts[4] == "after",
|
||||
)
|
||||
else:
|
||||
if parts[2] == '-':
|
||||
time = None
|
||||
else:
|
||||
timeparts = parts[2].split(':')
|
||||
time = datetime.time(hour=int(timeparts[0]), minute=int(timeparts[1]), second=int(timeparts[2]))
|
||||
try:
|
||||
data = RelativeDate(
|
||||
days=int(parts[1] or 0),
|
||||
base_date_name=parts[3],
|
||||
time=time,
|
||||
minutes=None,
|
||||
is_after=len(parts) > 4 and parts[4] == "after",
|
||||
)
|
||||
except ValueError:
|
||||
data = RelativeDate(
|
||||
days=0,
|
||||
base_date_name=parts[3],
|
||||
time=time,
|
||||
minutes=None,
|
||||
is_after=len(parts) > 4 and parts[4] == "after",
|
||||
)
|
||||
if data.base_date_name not in [k[0] for k in BASE_CHOICES]:
|
||||
raise ValueError('{} is not a valid base date'.format(data.base_date_name))
|
||||
data = RelativeDate.from_string(input)
|
||||
else:
|
||||
data = parser.parse(input)
|
||||
return RelativeDateWrapper(data)
|
||||
@@ -187,7 +325,6 @@ BEFORE_AFTER_CHOICE = (
|
||||
('after', _('after')),
|
||||
)
|
||||
|
||||
|
||||
reldatetimeparts = namedtuple('reldatetimeparts', (
|
||||
"status", # 0
|
||||
"absolute", # 1
|
||||
@@ -202,6 +339,14 @@ reldatetimeparts = namedtuple('reldatetimeparts', (
|
||||
reldatetimeparts.indizes = reldatetimeparts(*range(9))
|
||||
|
||||
|
||||
def _get_choices(base_choices: List[BaseChoice]) -> List[Tuple[str, Promise]]:
|
||||
return [(c.key, c.text) for c in base_choices]
|
||||
|
||||
|
||||
def _get_choice_validation_obj(choices: List[BaseChoice]):
|
||||
return {c.key: {"data-supports-before": c.supports_before, "data-supports-after": c.supports_after} for c in choices}
|
||||
|
||||
|
||||
class RelativeDateTimeWidget(forms.MultiWidget):
|
||||
template_name = 'pretixbase/forms/widgets/reldatetime.html'
|
||||
parts = reldatetimeparts
|
||||
@@ -209,6 +354,7 @@ class RelativeDateTimeWidget(forms.MultiWidget):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.status_choices = kwargs.pop('status_choices')
|
||||
base_choices = kwargs.pop('base_choices')
|
||||
choices = _get_choices(base_choices)
|
||||
|
||||
def placeholder_datetime_format():
|
||||
df = get_format('DATETIME_INPUT_FORMATS')[0]
|
||||
@@ -220,21 +366,29 @@ class RelativeDateTimeWidget(forms.MultiWidget):
|
||||
tf = get_format('TIME_INPUT_FORMATS')[0]
|
||||
return datetime.time(8, 30, 0).strftime(tf)
|
||||
|
||||
widgets = reldatetimeparts(
|
||||
widgets = cast(dict[str, Widget | type[Widget]], cast(object, reldatetimeparts(
|
||||
status=forms.RadioSelect(choices=self.status_choices),
|
||||
absolute=forms.DateTimeInput(
|
||||
attrs={'placeholder': lazy(placeholder_datetime_format, str), 'class': 'datetimepicker'}
|
||||
),
|
||||
rel_days_number=forms.NumberInput(),
|
||||
rel_mins_relationto=forms.Select(choices=base_choices),
|
||||
rel_mins_relationto=OptionAttrsSelect(
|
||||
attrs={'data-relative-choice': True},
|
||||
choices=choices,
|
||||
option_attrs=_get_choice_validation_obj(base_choices)
|
||||
),
|
||||
rel_days_timeofday=forms.TimeInput(
|
||||
attrs={'placeholder': lazy(placeholder_time_format, str), 'class': 'timepickerfield'}
|
||||
),
|
||||
rel_mins_number=forms.NumberInput(),
|
||||
rel_days_relationto=forms.Select(choices=base_choices),
|
||||
rel_mins_relation=forms.Select(choices=BEFORE_AFTER_CHOICE),
|
||||
rel_days_relation=forms.Select(choices=BEFORE_AFTER_CHOICE),
|
||||
)
|
||||
rel_days_relationto=OptionAttrsSelect(
|
||||
attrs={'data-relative-choice': True},
|
||||
choices=choices,
|
||||
option_attrs=_get_choice_validation_obj(base_choices)
|
||||
),
|
||||
rel_mins_relation=forms.Select(attrs={'data-relation-choice': True}, choices=BEFORE_AFTER_CHOICE),
|
||||
rel_days_relation=forms.Select(attrs={'data-relation-choice': True}, choices=BEFORE_AFTER_CHOICE),
|
||||
)))
|
||||
super().__init__(widgets=widgets, *args, **kwargs)
|
||||
|
||||
def decompress(self, value):
|
||||
@@ -247,10 +401,10 @@ class RelativeDateTimeWidget(forms.MultiWidget):
|
||||
status="unset",
|
||||
absolute=None,
|
||||
rel_days_number=1,
|
||||
rel_mins_relationto="date_from",
|
||||
rel_mins_relationto="event__date_from",
|
||||
rel_days_timeofday=None,
|
||||
rel_mins_number=0,
|
||||
rel_days_relationto="date_from",
|
||||
rel_days_relationto="event__date_from",
|
||||
rel_mins_relation="before",
|
||||
rel_days_relation="before"
|
||||
)
|
||||
@@ -259,10 +413,10 @@ class RelativeDateTimeWidget(forms.MultiWidget):
|
||||
status="absolute",
|
||||
absolute=value.data,
|
||||
rel_days_number=1,
|
||||
rel_mins_relationto="date_from",
|
||||
rel_mins_relationto="event__date_from",
|
||||
rel_days_timeofday=None,
|
||||
rel_mins_number=0,
|
||||
rel_days_relationto="date_from",
|
||||
rel_days_relationto="event__date_from",
|
||||
rel_mins_relation="before",
|
||||
rel_days_relation="before"
|
||||
)
|
||||
@@ -271,10 +425,10 @@ class RelativeDateTimeWidget(forms.MultiWidget):
|
||||
status="relative_minutes",
|
||||
absolute=None,
|
||||
rel_days_number=None,
|
||||
rel_mins_relationto=value.data.base_date_name,
|
||||
rel_mins_relationto=value.data.key,
|
||||
rel_days_timeofday=None,
|
||||
rel_mins_number=value.data.minutes,
|
||||
rel_days_relationto=value.data.base_date_name,
|
||||
rel_days_relationto=value.data.key,
|
||||
rel_mins_relation="after" if value.data.is_after else "before",
|
||||
rel_days_relation="after" if value.data.is_after else "before"
|
||||
)
|
||||
@@ -282,10 +436,10 @@ class RelativeDateTimeWidget(forms.MultiWidget):
|
||||
status="relative",
|
||||
absolute=None,
|
||||
rel_days_number=value.data.days,
|
||||
rel_mins_relationto=value.data.base_date_name,
|
||||
rel_mins_relationto=value.data.key,
|
||||
rel_days_timeofday=value.data.time,
|
||||
rel_mins_number=0,
|
||||
rel_days_relationto=value.data.base_date_name,
|
||||
rel_days_relationto=value.data.key,
|
||||
rel_mins_relation="after" if value.data.is_after else "before",
|
||||
rel_days_relation="after" if value.data.is_after else "before"
|
||||
)
|
||||
@@ -309,17 +463,41 @@ class RelativeDateTimeField(forms.MultiValueField):
|
||||
('relative', _('Relative date:')),
|
||||
('relative_minutes', _('Relative time:')),
|
||||
]
|
||||
self.relative_to_order = kwargs.pop('relative_to_order', False)
|
||||
|
||||
possible_choices = copy.deepcopy(EVENT_BASE_CHOICES)
|
||||
if self.relative_to_order:
|
||||
possible_choices.extend(ORDER_BASE_CHOICES)
|
||||
|
||||
if kwargs.get('limit_choices'):
|
||||
limit = kwargs.pop('limit_choices')
|
||||
choices = [(k, v) for k, v in BASE_CHOICES if k in limit]
|
||||
else:
|
||||
choices = BASE_CHOICES
|
||||
if any(["__" not in l for l in limit]):
|
||||
_warn_skips = (str(os.path.dirname(__file__)),)
|
||||
|
||||
warnings.warn(
|
||||
message="Please prefix limit_choices with the base the attributes refer to, for example event__date_from",
|
||||
category=DeprecationWarning,
|
||||
skip_file_prefixes=_warn_skips
|
||||
)
|
||||
|
||||
possible_choices = [
|
||||
c for c in possible_choices if
|
||||
# new base case as we want limit_choices to be expressed as base__attribute
|
||||
(c.key in limit) or
|
||||
# fallback for old event based entries
|
||||
# if the base is an event, then using only attribute is fine
|
||||
(c.base == "event" and c.attribute in limit)
|
||||
]
|
||||
|
||||
if not kwargs.get('required', True):
|
||||
status_choices.insert(0, ('unset', _('Not set')))
|
||||
|
||||
choices = _get_choices(possible_choices)
|
||||
|
||||
fields = reldatetimeparts(
|
||||
status=forms.ChoiceField(
|
||||
choices=status_choices,
|
||||
required=True
|
||||
required=True,
|
||||
),
|
||||
absolute=forms.DateTimeField(
|
||||
required=False
|
||||
@@ -329,7 +507,7 @@ class RelativeDateTimeField(forms.MultiValueField):
|
||||
),
|
||||
rel_mins_relationto=forms.ChoiceField(
|
||||
choices=choices,
|
||||
required=False
|
||||
required=False,
|
||||
),
|
||||
rel_days_timeofday=forms.TimeField(
|
||||
required=False,
|
||||
@@ -339,7 +517,7 @@ class RelativeDateTimeField(forms.MultiValueField):
|
||||
),
|
||||
rel_days_relationto=forms.ChoiceField(
|
||||
choices=choices,
|
||||
required=False
|
||||
required=False,
|
||||
),
|
||||
rel_mins_relation=forms.ChoiceField(
|
||||
choices=BEFORE_AFTER_CHOICE,
|
||||
@@ -350,8 +528,9 @@ class RelativeDateTimeField(forms.MultiValueField):
|
||||
required=False
|
||||
),
|
||||
)
|
||||
|
||||
if 'widget' not in kwargs:
|
||||
kwargs['widget'] = RelativeDateTimeWidget(status_choices=status_choices, base_choices=choices)
|
||||
kwargs['widget'] = RelativeDateTimeWidget(status_choices=status_choices, base_choices=possible_choices)
|
||||
kwargs.pop('max_length', 0)
|
||||
kwargs.pop('empty_value', 0)
|
||||
super().__init__(
|
||||
@@ -359,21 +538,24 @@ class RelativeDateTimeField(forms.MultiValueField):
|
||||
)
|
||||
|
||||
def set_event(self, event):
|
||||
self.widget.widgets[reldatetimeparts.indizes.rel_days_relationto].choices = [
|
||||
(k, v) for k, v in BASE_CHOICES if getattr(event, k, None)
|
||||
]
|
||||
self.widget.widgets[reldatetimeparts.indizes.rel_mins_relationto].choices = [
|
||||
(k, v) for k, v in BASE_CHOICES if getattr(event, k, None)
|
||||
]
|
||||
possible_choices = copy.deepcopy(EVENT_BASE_CHOICES)
|
||||
if self.relative_to_order:
|
||||
possible_choices.extend(ORDER_BASE_CHOICES)
|
||||
|
||||
possible_choices = possible_choices
|
||||
choices = _get_choices(possible_choices)
|
||||
|
||||
self.widget.widgets[reldatetimeparts.indizes.rel_days_relationto].choices = choices
|
||||
self.widget.widgets[reldatetimeparts.indizes.rel_mins_relationto].choices = choices
|
||||
|
||||
def compress(self, data_list):
|
||||
if not data_list:
|
||||
return None
|
||||
data = reldatetimeparts(*data_list)
|
||||
if data.status == 'absolute':
|
||||
return RelativeDateWrapper(data.absolute)
|
||||
elif data.status == 'unset':
|
||||
if data.status == 'unset':
|
||||
return None
|
||||
elif data.status == 'absolute':
|
||||
return RelativeDateWrapper(data.absolute)
|
||||
elif data.status == 'relative_minutes':
|
||||
return RelativeDateWrapper(RelativeDate(
|
||||
days=0,
|
||||
@@ -404,6 +586,18 @@ class RelativeDateTimeField(forms.MultiValueField):
|
||||
raise ValidationError(self.error_messages['incomplete'])
|
||||
elif data.status == 'relative_minutes' and (data.rel_mins_number is None or not data.rel_mins_relationto):
|
||||
raise ValidationError(self.error_messages['incomplete'])
|
||||
elif data.status == 'relative':
|
||||
choice = BaseChoice.find(BASE_CHOICES, data.rel_days_relationto)
|
||||
if data.rel_days_relation == "before" and not choice.supports_before:
|
||||
raise ValidationError(_('A relative date cannot be expressed as "before" for "{}"'.format(choice.text)))
|
||||
elif data.status == 'relative' and data.rel_days_relation == "after" and not choice.supports_after:
|
||||
raise ValidationError(_('A relative date cannot be expressed as "after" for "{}"'.format(choice.text)))
|
||||
elif data.status == 'relative_minutes':
|
||||
choice = BaseChoice.find(BASE_CHOICES, data.rel_days_relationto)
|
||||
if data.rel_days_relation == "before" and not choice.supports_before:
|
||||
raise ValidationError(_('A relative time cannot be expressed as "before" for "{}"'.format(choice.text)))
|
||||
elif data.rel_days_relation == "after" and not choice.supports_after:
|
||||
raise ValidationError(_('A relative time cannot be expressed as "after" for "{}"'.format(choice.text)))
|
||||
|
||||
return super().clean(value)
|
||||
|
||||
@@ -424,15 +618,20 @@ class RelativeDateWidget(RelativeDateTimeWidget):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.status_choices = kwargs.pop('status_choices')
|
||||
widgets = reldateparts(
|
||||
self.base_choices = kwargs.pop('base_choices')
|
||||
widgets = cast(dict[str, Widget | type[Widget]], cast(object, reldateparts(
|
||||
status=forms.RadioSelect(choices=self.status_choices),
|
||||
absolute=forms.DateInput(
|
||||
attrs={'class': 'datepickerfield'}
|
||||
),
|
||||
rel_days_number=forms.NumberInput(),
|
||||
rel_days_relationto=forms.Select(choices=kwargs.pop('base_choices')),
|
||||
rel_days_relation=forms.Select(choices=BEFORE_AFTER_CHOICE),
|
||||
)
|
||||
rel_days_relationto=OptionAttrsSelect(
|
||||
choices=self.base_choices,
|
||||
option_attrs=_get_choice_validation_obj(self.base_choices),
|
||||
attrs={'data-relative-choice': True},
|
||||
),
|
||||
rel_days_relation=forms.Select(choices=BEFORE_AFTER_CHOICE, attrs={'data-relation-choice': True},),
|
||||
)))
|
||||
forms.MultiWidget.__init__(self, widgets=widgets, *args, **kwargs)
|
||||
|
||||
def decompress(self, value):
|
||||
@@ -460,7 +659,7 @@ class RelativeDateWidget(RelativeDateTimeWidget):
|
||||
status="relative",
|
||||
absolute=None,
|
||||
rel_days_number=value.data.days,
|
||||
rel_days_relationto=value.data.base_date_name,
|
||||
rel_days_relationto=value.data.key,
|
||||
rel_days_relation="after" if value.data.is_after else "before"
|
||||
)
|
||||
|
||||
@@ -474,6 +673,15 @@ class RelativeDateField(RelativeDateTimeField):
|
||||
]
|
||||
if not kwargs.get('required', True):
|
||||
status_choices.insert(0, ('unset', _('Not set')))
|
||||
|
||||
self.relative_to_order = kwargs.pop('relative_to_order', False)
|
||||
|
||||
possible_choices = copy.deepcopy(EVENT_BASE_CHOICES)
|
||||
if self.relative_to_order:
|
||||
possible_choices.extend(ORDER_BASE_CHOICES)
|
||||
|
||||
choices = _get_choices(possible_choices)
|
||||
|
||||
fields = reldateparts(
|
||||
status=forms.ChoiceField(
|
||||
choices=status_choices,
|
||||
@@ -486,33 +694,37 @@ class RelativeDateField(RelativeDateTimeField):
|
||||
required=False
|
||||
),
|
||||
rel_days_relationto=forms.ChoiceField(
|
||||
choices=BASE_CHOICES,
|
||||
required=False
|
||||
choices=choices,
|
||||
required=False,
|
||||
),
|
||||
rel_days_relation=forms.ChoiceField(
|
||||
choices=BEFORE_AFTER_CHOICE,
|
||||
required=False
|
||||
),
|
||||
)
|
||||
|
||||
if 'widget' not in kwargs:
|
||||
kwargs['widget'] = RelativeDateWidget(status_choices=status_choices, base_choices=BASE_CHOICES)
|
||||
kwargs['widget'] = RelativeDateWidget(status_choices=status_choices, base_choices=possible_choices)
|
||||
forms.MultiValueField.__init__(
|
||||
self, fields=fields, require_all_fields=False, *args, **kwargs
|
||||
)
|
||||
|
||||
def set_event(self, event):
|
||||
self.widget.widgets[reldateparts.indizes.rel_days_relationto].choices = [
|
||||
(k, v) for k, v in BASE_CHOICES if getattr(event, k, None)
|
||||
choices = [
|
||||
(c.key, c.text) for c in EVENT_BASE_CHOICES if getattr(event, c.attribute, None)
|
||||
]
|
||||
if self.relative_to_order:
|
||||
choices += [(c.key, c.text) for c in ORDER_BASE_CHOICES]
|
||||
self.widget.widgets[reldateparts.indizes.rel_days_relationto].choices = choices
|
||||
|
||||
def compress(self, data_list):
|
||||
if not data_list:
|
||||
return None
|
||||
data = reldateparts(*data_list)
|
||||
if data.status == 'absolute':
|
||||
return RelativeDateWrapper(data.absolute)
|
||||
elif data.status == 'unset':
|
||||
if data.status == 'unset':
|
||||
return None
|
||||
elif data.status == 'absolute':
|
||||
return RelativeDateWrapper(data.absolute)
|
||||
else:
|
||||
return RelativeDateWrapper(RelativeDate(
|
||||
days=data.rel_days_number,
|
||||
@@ -525,8 +737,14 @@ class RelativeDateField(RelativeDateTimeField):
|
||||
data = reldateparts(*value)
|
||||
if data.status == 'absolute' and not data.absolute:
|
||||
raise ValidationError(self.error_messages['incomplete'])
|
||||
elif data.status == 'relative' and (data.rel_days_number is None or not data.rel_days_relationto):
|
||||
raise ValidationError(self.error_messages['incomplete'])
|
||||
if data.status == 'relative':
|
||||
choice = BaseChoice.find(BASE_CHOICES, data.rel_days_relationto)
|
||||
if data.rel_days_number is None or not data.rel_days_relationto:
|
||||
raise ValidationError(self.error_messages['incomplete'])
|
||||
elif data.rel_days_relation == "before" and not choice.supports_before:
|
||||
raise ValidationError(_("A relative date cannot be expressed as 'before' for '{}'".format(choice.text)))
|
||||
elif data.rel_days_relation == "after" and not choice.supports_after:
|
||||
raise ValidationError(_("A relative date cannot be expressed as 'after' for '{}'".format(choice.text)))
|
||||
|
||||
return forms.MultiValueField.clean(self, value)
|
||||
|
||||
|
||||
@@ -53,6 +53,7 @@ from django.utils.translation import (
|
||||
)
|
||||
from django_scopes import scopes_disabled
|
||||
|
||||
from pretix.base.decimal import round_decimal
|
||||
from pretix.base.i18n import language
|
||||
from pretix.base.media import MEDIA_TYPES
|
||||
from pretix.base.models import (
|
||||
@@ -916,6 +917,8 @@ class CartManager:
|
||||
if custom_price > 99_999_999_999:
|
||||
raise CartError(error_messages['price_too_high'])
|
||||
|
||||
custom_price = round_decimal(custom_price, currency=self.event.currency)
|
||||
|
||||
op = self.AddOperation(
|
||||
count=i['count'],
|
||||
item=item,
|
||||
@@ -1038,6 +1041,8 @@ class CartManager:
|
||||
if custom_price > 99_999_999_999:
|
||||
raise CartError(error_messages['price_too_high'])
|
||||
|
||||
custom_price = round_decimal(custom_price, currency=self.event.currency)
|
||||
|
||||
# Fix positions with wrong price (TODO: happens out-of-cartmanager-transaction and therefore a little hacky)
|
||||
for ca in current_addons[cp][a['item'], a['variation']]:
|
||||
if ca.listed_price != listed_price:
|
||||
|
||||
@@ -40,7 +40,7 @@ import dateutil
|
||||
import dateutil.parser
|
||||
from dateutil.tz import datetime_exists
|
||||
from django.core.files import File
|
||||
from django.db import IntegrityError, transaction
|
||||
from django.db import IntegrityError
|
||||
from django.db.models import (
|
||||
BooleanField, Case, Count, ExpressionWrapper, F, IntegerField, Max, Min,
|
||||
OuterRef, Q, Subquery, TextField, Value, When,
|
||||
@@ -59,6 +59,7 @@ from pretix.base.models import (
|
||||
)
|
||||
from pretix.base.signals import checkin_created, periodic_task
|
||||
from pretix.helpers import OF_SELF
|
||||
from pretix.helpers.database import conditional_atomic
|
||||
from pretix.helpers.jsonlogic import Logic
|
||||
from pretix.helpers.jsonlogic_boolalg import convert_to_dnf
|
||||
from pretix.helpers.jsonlogic_query import (
|
||||
@@ -1043,10 +1044,10 @@ def perform_checkin(op: OrderPosition, clist: CheckinList, given_answers: dict,
|
||||
if not simulate:
|
||||
_save_answers(op, answers, given_answers)
|
||||
|
||||
with transaction.atomic():
|
||||
with conditional_atomic(not simulate):
|
||||
# Lock order positions, if it is an entry. We don't need it for exits, as a race condition wouldn't be problematic
|
||||
opqs = OrderPosition.all.select_related("order", "item")
|
||||
if type != Checkin.TYPE_EXIT:
|
||||
if type != Checkin.TYPE_EXIT and not simulate:
|
||||
opqs = opqs.select_for_update(of=OF_SELF)
|
||||
op = opqs.get(pk=op.pk)
|
||||
|
||||
|
||||
@@ -48,12 +48,12 @@ from celery.exceptions import MaxRetriesExceededError
|
||||
from django.conf import settings
|
||||
from django.core.cache import cache
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import transaction
|
||||
from django.db import models, transaction
|
||||
from django.db.models import (
|
||||
Count, Exists, F, IntegerField, Max, Min, OuterRef, Q, QuerySet, Sum,
|
||||
Value,
|
||||
Count, Exists, F, IntegerField, Max, Min, OuterRef, Q, QuerySet, Subquery,
|
||||
Sum, Value,
|
||||
)
|
||||
from django.db.models.functions import Coalesce, Greatest
|
||||
from django.db.models.functions import Cast, Greatest
|
||||
from django.db.transaction import get_connection
|
||||
from django.dispatch import receiver
|
||||
from django.utils.functional import cached_property
|
||||
@@ -71,7 +71,7 @@ from pretix.base.models import (
|
||||
Membership, Order, OrderPayment, OrderPosition, Quota, Seat,
|
||||
SeatCategoryMapping, User, Voucher,
|
||||
)
|
||||
from pretix.base.models.event import SubEvent
|
||||
from pretix.base.models.event import Event_SettingsStore, SubEvent
|
||||
from pretix.base.models.orders import (
|
||||
BlockedTicketSecret, InvoiceAddress, OrderFee, OrderRefund,
|
||||
generate_secret,
|
||||
@@ -1494,83 +1494,104 @@ def send_expiry_warnings(sender, **kwargs):
|
||||
@scopes_disabled()
|
||||
def send_download_reminders(sender, **kwargs):
|
||||
today = now().replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
qs = Order.objects.annotate(
|
||||
first_date=Coalesce(
|
||||
Min('all_positions__subevent__date_from'),
|
||||
F('event__date_from')
|
||||
|
||||
events = Event.objects.filter(
|
||||
Q(has_subevents=False, date_from__gte=now()) |
|
||||
(Q(has_subevents=True) & Q(Exists(
|
||||
SubEvent.objects.filter(event_id=OuterRef('id'), date_from__gte=now())
|
||||
)))
|
||||
).annotate(
|
||||
reminder_days=Subquery(
|
||||
Event_SettingsStore.objects.filter(
|
||||
object=OuterRef('id'),
|
||||
key='mail_days_download_reminder'
|
||||
).exclude(
|
||||
value="None"
|
||||
).annotate(
|
||||
val=Cast(F("value"), output_field=models.IntegerField()),
|
||||
).values("val")
|
||||
)
|
||||
).filter(
|
||||
download_reminder_sent=False,
|
||||
datetime__lte=now() - timedelta(hours=2),
|
||||
first_date__gte=today,
|
||||
).only(
|
||||
'pk', 'event_id', 'sales_channel', 'datetime',
|
||||
).order_by('event_id')
|
||||
event_id = None
|
||||
days = None
|
||||
event = None
|
||||
reminder_days__isnull=False,
|
||||
).order_by()
|
||||
|
||||
for o in qs:
|
||||
if o.event_id != event_id:
|
||||
days = o.event.settings.get('mail_days_download_reminder', as_type=int)
|
||||
event = o.event
|
||||
event_id = o.event_id
|
||||
for event in events.iterator(chunk_size=10_000):
|
||||
qs = event.orders.filter(
|
||||
download_reminder_sent=False,
|
||||
datetime__lte=now() - timedelta(hours=2),
|
||||
)
|
||||
|
||||
if days is None:
|
||||
continue
|
||||
|
||||
if o.sales_channel.identifier not in event.settings.mail_sales_channel_download_reminder:
|
||||
continue
|
||||
|
||||
reminder_date = (o.first_date - timedelta(days=days)).replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
if now() < reminder_date or o.datetime > reminder_date:
|
||||
continue
|
||||
|
||||
with transaction.atomic():
|
||||
o = Order.objects.select_for_update(of=OF_SELF).get(pk=o.pk)
|
||||
if o.download_reminder_sent:
|
||||
# Race condition
|
||||
continue
|
||||
positions = list(o.positions_with_tickets)
|
||||
if not positions:
|
||||
if event.has_subevents:
|
||||
qs = qs.annotate(
|
||||
first_date=Min('all_positions__subevent__date_from')
|
||||
).filter(
|
||||
Q(first_date__gte=today)
|
||||
)
|
||||
else:
|
||||
event_reminder_date = (event.date_from - timedelta(days=event.reminder_days)).replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
if now() < event_reminder_date:
|
||||
continue
|
||||
|
||||
if not o.ticket_download_available:
|
||||
qs = qs.only(
|
||||
'pk', 'event_id', 'sales_channel', 'datetime',
|
||||
).order_by()
|
||||
|
||||
for o in qs:
|
||||
if o.sales_channel.identifier not in event.settings.mail_sales_channel_download_reminder:
|
||||
continue
|
||||
|
||||
if o.status != Order.STATUS_PAID:
|
||||
if o.status != Order.STATUS_PENDING or o.require_approval or (not o.valid_if_pending and not o.event.settings.ticket_download_pending):
|
||||
if event.has_subevents:
|
||||
reminder_date = ((o.first_date or event.date_from) - timedelta(days=event.reminder_days)).replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
else:
|
||||
reminder_date = event_reminder_date
|
||||
if now() < reminder_date or o.datetime > reminder_date:
|
||||
continue
|
||||
|
||||
with transaction.atomic():
|
||||
o = Order.objects.select_for_update(of=OF_SELF).get(pk=o.pk)
|
||||
if o.download_reminder_sent:
|
||||
# Race condition
|
||||
continue
|
||||
positions = list(o.positions_with_tickets)
|
||||
if not positions:
|
||||
continue
|
||||
|
||||
with language(o.locale, o.event.settings.region):
|
||||
o.download_reminder_sent = True
|
||||
o.save(update_fields=['download_reminder_sent'])
|
||||
email_template = event.settings.mail_text_download_reminder
|
||||
email_subject = event.settings.mail_subject_download_reminder
|
||||
email_context = get_email_context(event=event, order=o)
|
||||
o.send_mail(
|
||||
email_subject, email_template, email_context,
|
||||
'pretix.event.order.email.download_reminder_sent',
|
||||
attach_tickets=True
|
||||
)
|
||||
if not o.ticket_download_available:
|
||||
continue
|
||||
|
||||
if event.settings.mail_send_download_reminder_attendee:
|
||||
for p in positions:
|
||||
if p.subevent_id:
|
||||
reminder_date = (p.subevent.date_from - timedelta(days=days)).replace(
|
||||
hour=0, minute=0, second=0, microsecond=0
|
||||
)
|
||||
if now() < reminder_date:
|
||||
continue
|
||||
if p.addon_to_id is None and p.attendee_email and p.attendee_email != o.email:
|
||||
email_template = event.settings.mail_text_download_reminder_attendee
|
||||
email_subject = event.settings.mail_subject_download_reminder_attendee
|
||||
email_context = get_email_context(event=event, order=o, position=p)
|
||||
o.send_mail(
|
||||
email_subject, email_template, email_context,
|
||||
'pretix.event.order.email.download_reminder_sent',
|
||||
attach_tickets=True, position=p
|
||||
)
|
||||
if o.status != Order.STATUS_PAID:
|
||||
if o.status != Order.STATUS_PENDING or o.require_approval or (not o.valid_if_pending and not o.event.settings.ticket_download_pending):
|
||||
continue
|
||||
|
||||
with language(o.locale, o.event.settings.region):
|
||||
o.download_reminder_sent = True
|
||||
o.save(update_fields=['download_reminder_sent'])
|
||||
email_template = event.settings.mail_text_download_reminder
|
||||
email_subject = event.settings.mail_subject_download_reminder
|
||||
email_context = get_email_context(event=event, order=o)
|
||||
o.send_mail(
|
||||
email_subject, email_template, email_context,
|
||||
'pretix.event.order.email.download_reminder_sent',
|
||||
attach_tickets=True
|
||||
)
|
||||
|
||||
if event.settings.mail_send_download_reminder_attendee:
|
||||
for p in positions:
|
||||
if p.subevent_id:
|
||||
reminder_date = (p.subevent.date_from - timedelta(days=event.reminder_days)).replace(
|
||||
hour=0, minute=0, second=0, microsecond=0
|
||||
)
|
||||
if now() < reminder_date:
|
||||
continue
|
||||
if p.addon_to_id is None and p.attendee_email and p.attendee_email != o.email:
|
||||
email_template = event.settings.mail_text_download_reminder_attendee
|
||||
email_subject = event.settings.mail_subject_download_reminder_attendee
|
||||
email_context = get_email_context(event=event, order=o, position=p)
|
||||
o.send_mail(
|
||||
email_subject, email_template, email_context,
|
||||
'pretix.event.order.email.download_reminder_sent',
|
||||
attach_tickets=True, position=p
|
||||
)
|
||||
|
||||
|
||||
def notify_user_changed_order(order, user=None, auth=None, invoices=[]):
|
||||
|
||||
@@ -801,11 +801,10 @@ def get_available_placeholders(event, base_parameters, rich=False):
|
||||
return params
|
||||
|
||||
|
||||
def get_sample_context(event, context_parameters, rich=True):
|
||||
def prepare_sample_context_for_preview(placeholder_to_sample):
|
||||
context_dict = {}
|
||||
lbl = _('This value will be replaced based on dynamic parameters.')
|
||||
for k, v in get_available_placeholders(event, context_parameters, rich=rich).items():
|
||||
sample = v.render_sample(event)
|
||||
for k, sample in placeholder_to_sample.items():
|
||||
if isinstance(sample, PlainHtmlAlternativeString):
|
||||
context_dict[k] = PlainHtmlAlternativeString(
|
||||
'<{el} class="placeholder" title="{title}">{plain}</{el}>'.format(
|
||||
@@ -830,3 +829,12 @@ def get_sample_context(event, context_parameters, rich=True):
|
||||
escape(sample)
|
||||
))
|
||||
return context_dict
|
||||
|
||||
|
||||
def get_sample_context(event, context_parameters, rich=True):
|
||||
return prepare_sample_context_for_preview(
|
||||
{
|
||||
k: v.render_sample(event)
|
||||
for k, v in get_available_placeholders(event, context_parameters, rich=rich).items()
|
||||
}
|
||||
)
|
||||
|
||||
@@ -2930,7 +2930,7 @@ Your {event} team""")) # noqa: W291
|
||||
},
|
||||
'mail_days_download_reminder': {
|
||||
'type': int,
|
||||
'default': None
|
||||
'default': None # when this default is changed, send_download_reminders needs to change
|
||||
},
|
||||
'mail_send_download_reminder_attendee': {
|
||||
'type': bool,
|
||||
|
||||
@@ -94,14 +94,14 @@ class SubEventBulkForm(SubEventForm):
|
||||
label=_('Start of presale'),
|
||||
help_text=_('Optional. No products will be sold before this date.'),
|
||||
required=False,
|
||||
limit_choices=('date_from', 'date_to'),
|
||||
limit_choices=('event__date_from', 'event__date_to'),
|
||||
)
|
||||
rel_presale_end = RelativeDateTimeField(
|
||||
label=_('End of presale'),
|
||||
help_text=_('Optional. No products will be sold after this date. If you do not set this value, the presale '
|
||||
'will end after the end date of your event.'),
|
||||
required=False,
|
||||
limit_choices=('date_from', 'date_to'),
|
||||
limit_choices=('event__date_from', 'event__date_to'),
|
||||
)
|
||||
skip_if_overlap = forms.BooleanField(
|
||||
label=pgettext_lazy('subevent', 'Skip dates that overlap with any existing date'),
|
||||
@@ -333,12 +333,12 @@ class BulkSubEventItemForm(SubEventItemForm):
|
||||
rel_available_from = RelativeDateTimeField(
|
||||
label=_('Available from'),
|
||||
required=False,
|
||||
limit_choices=('date_from', 'date_to'),
|
||||
limit_choices=('event__date_from', 'event__date_to'),
|
||||
)
|
||||
rel_available_until = RelativeDateTimeField(
|
||||
label=_('Available until'),
|
||||
required=False,
|
||||
limit_choices=('date_from', 'date_to'),
|
||||
limit_choices=('event__date_from', 'event__date_to'),
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@@ -355,12 +355,12 @@ class BulkSubEventItemVariationForm(SubEventItemVariationForm):
|
||||
rel_available_from = RelativeDateTimeField(
|
||||
label=_('Available from'),
|
||||
required=False,
|
||||
limit_choices=('date_from', 'date_to'),
|
||||
limit_choices=('event__date_from', 'event__date_to'),
|
||||
)
|
||||
rel_available_until = RelativeDateTimeField(
|
||||
label=_('Available_until'),
|
||||
required=False,
|
||||
limit_choices=('date_from', 'date_to'),
|
||||
limit_choices=('event__date_from', 'event__date_to'),
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
||||
@@ -58,6 +58,7 @@
|
||||
<script type="text/javascript" src="{% static "leaflet/leaflet.js" %}"></script>
|
||||
<script type="text/javascript" src="{% static "pretixcontrol/js/ui/geo.js" %}"></script>
|
||||
<script type="text/javascript" src="{% static "pretixbase/js/details.js" %}"></script>
|
||||
<script type="text/javascript" src="{% static "pretixbase/js/reldate.js" %}"></script>
|
||||
<script type="text/javascript" src="{% static "pretixbase/js/asynctask.js" %}"></script>
|
||||
<script type="text/javascript" src="{% static "sortable/Sortable.js" %}"></script>
|
||||
<script type="text/javascript" src="{% static "colorpicker/bootstrap-colorpicker.js" %}"></script>
|
||||
|
||||
@@ -118,6 +118,10 @@
|
||||
</td>
|
||||
{% endif %}
|
||||
<td>
|
||||
<span class="fa fa-fw {% if d.last_seen and d.last_seen.is_recent %}fa-check-circle text-success{% else %}fa-circle text-danger{% endif %}"
|
||||
data-toggle="tooltip"
|
||||
title="{% if d.last_seen %}{% blocktrans with time=d.last_seen.last_seen|date:"SHORT_DATETIME_FORMAT" %}Last seen: {{ time }}{% endblocktrans %}{% else %}{% trans "No recent contact" %}{% endif %}"
|
||||
></span>
|
||||
{{ d.device_id }}
|
||||
</td>
|
||||
<td>
|
||||
@@ -125,6 +129,7 @@
|
||||
<del>{% endif %}
|
||||
{{ d.name }}
|
||||
{% if d.revoked %}</del>{% endif %}
|
||||
|
||||
{% if d.gate %}
|
||||
<br>
|
||||
<small class="text-muted">{{ d.gate.name }}</small>
|
||||
|
||||
@@ -269,8 +269,8 @@ class MailSettingsSetupView(TemplateView):
|
||||
if settings.MAIL_CUSTOM_SENDER_DMARC_REQUIRED:
|
||||
dmarc_record = get_dmarc_record(hostname)
|
||||
if not dmarc_record:
|
||||
spf_warning = _(
|
||||
'We did not find DMARC record for your domain. This means that there is a very high chance '
|
||||
dmarc_warning = _(
|
||||
'We did not find a DMARC record for your domain. This means that there is a very high chance '
|
||||
'most of the emails will be rejected or marked as spam. You should update the DNS settings '
|
||||
'of your domain.'
|
||||
)
|
||||
|
||||
@@ -382,7 +382,7 @@ class OrderOverpaidRefundBulkActionView(BaseOrderBulkActionView):
|
||||
'provider': refund.provider,
|
||||
}, user=self.request.user)
|
||||
payment.payment_provider.execute_refund(refund)
|
||||
return True
|
||||
return bool(proposals)
|
||||
except (ValueError, PaymentException):
|
||||
return False
|
||||
|
||||
|
||||
@@ -108,6 +108,9 @@ from pretix.base.services.export import (
|
||||
init_organizer_exporters, multiexport, scheduled_organizer_export,
|
||||
)
|
||||
from pretix.base.services.mail import mail, prefix_subject
|
||||
from pretix.base.services.placeholders import (
|
||||
prepare_sample_context_for_preview,
|
||||
)
|
||||
from pretix.base.templatetags.rich_text import markdown_compile_email
|
||||
from pretix.base.views.tasks import AsyncAction
|
||||
from pretix.control.forms.exports import ScheduledOrganizerExportForm
|
||||
@@ -345,16 +348,11 @@ class MailSettingsPreview(OrganizerPermissionRequiredMixin, View):
|
||||
|
||||
# get all supported placeholders with dummy values
|
||||
def placeholders(self, item):
|
||||
ctx = {}
|
||||
for p, s in MailSettingsForm(obj=self.request.organizer)._get_sample_context(
|
||||
MailSettingsForm.base_context[item]).items():
|
||||
if s.strip().startswith('*'):
|
||||
ctx[p] = s
|
||||
else:
|
||||
ctx[p] = '<span class="placeholder" title="{}">{}</span>'.format(
|
||||
_('This value will be replaced based on dynamic parameters.'),
|
||||
s
|
||||
)
|
||||
ctx = prepare_sample_context_for_preview(
|
||||
MailSettingsForm(obj=self.request.organizer)._get_sample_context(
|
||||
MailSettingsForm.base_context[item]
|
||||
)
|
||||
)
|
||||
return self.SafeDict(ctx)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
@@ -1210,7 +1208,7 @@ class DeviceQueryMixin:
|
||||
def get_queryset(self):
|
||||
qs = self.request.organizer.devices.prefetch_related(
|
||||
'limit_events', 'gate',
|
||||
).order_by('revoked', '-device_id')
|
||||
).select_related('last_seen').order_by('revoked', '-device_id')
|
||||
|
||||
if 'device' in self.request_data and '__ALL' not in self.request_data:
|
||||
qs = qs.filter(
|
||||
|
||||
@@ -431,7 +431,7 @@ class SubEventEditorMixin(MetaDataEditorMixin):
|
||||
days = (self.copy_from.date_from.astimezone(tz).date() - value.astimezone(tz).date()).days
|
||||
return RelativeDateWrapper(RelativeDate(
|
||||
days=abs(days),
|
||||
base_date_name='date_from',
|
||||
base_date_name='event__date_from',
|
||||
time=value.astimezone(tz).time(),
|
||||
minutes=None,
|
||||
is_after=days < 0,
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
import contextlib
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.postgres.indexes import BrinIndex
|
||||
from django.core.exceptions import FieldDoesNotExist, ImproperlyConfigured
|
||||
from django.db import connection, transaction
|
||||
from django.db.models import (
|
||||
@@ -285,3 +286,30 @@ def get_deterministic_ordering(model, ordering):
|
||||
# on the primary key to provide total ordering.
|
||||
ordering.append("-pk")
|
||||
return ordering
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def conditional_atomic(do_atomic, **kwargs):
|
||||
if do_atomic:
|
||||
with transaction.atomic(**kwargs):
|
||||
yield
|
||||
else:
|
||||
yield
|
||||
|
||||
|
||||
class IgnoreOnSQLiteMixin:
|
||||
# Mixin to allow defining PostgreSQL-specific indexes that will just not be created
|
||||
# on SQLite. SQLite is supported for testing only anyways!
|
||||
def create_sql(self, model, schema_editor, *args, **kwargs):
|
||||
if "sqlite" in settings.DATABASES["default"]["ENGINE"]:
|
||||
return ""
|
||||
return super().create_sql(model, schema_editor, *args, **kwargs)
|
||||
|
||||
def remove_sql(self, model, schema_editor, **kwargs):
|
||||
if "sqlite" in settings.DATABASES["default"]["ENGINE"]:
|
||||
return ""
|
||||
return super().remove_sql(model, schema_editor, **kwargs)
|
||||
|
||||
|
||||
class BrinIndexIgnoredOnSQLite(IgnoreOnSQLiteMixin, BrinIndex):
|
||||
pass
|
||||
|
||||
@@ -4,10 +4,10 @@ msgstr ""
|
||||
"Project-Id-Version: 1\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-07-30 13:52+0000\n"
|
||||
"PO-Revision-Date: 2026-07-27 17:00+0000\n"
|
||||
"PO-Revision-Date: 2026-07-31 18:00+0000\n"
|
||||
"Last-Translator: Nikolai <nikolai@lengefeldt.de>\n"
|
||||
"Language-Team: Danish <https://translate.pretix.eu/projects/pretix/pretix/da/"
|
||||
">\n"
|
||||
"Language-Team: Danish <https://translate.pretix.eu/projects/pretix/pretix/"
|
||||
"da/>\n"
|
||||
"Language: da\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
@@ -1381,10 +1381,8 @@ msgid "Membership type"
|
||||
msgstr "Medlemskabstype"
|
||||
|
||||
#: pretix/base/exporters/customers.py
|
||||
#, fuzzy
|
||||
#| msgid "Purchase time"
|
||||
msgid "Purchase ticket"
|
||||
msgstr "Købsdato"
|
||||
msgstr "Køb billet"
|
||||
|
||||
#: pretix/base/exporters/customers.py pretix/base/exporters/orderlist.py
|
||||
#: pretix/base/exporters/waitinglist.py pretix/base/forms/questions.py
|
||||
@@ -1401,10 +1399,8 @@ msgid "Start date"
|
||||
msgstr "Starttidspunkt"
|
||||
|
||||
#: pretix/base/exporters/customers.py
|
||||
#, fuzzy
|
||||
#| msgid "Start time from"
|
||||
msgid "Start time"
|
||||
msgstr "Starttidspunkt fra"
|
||||
msgstr "Starttidspunkt"
|
||||
|
||||
#: pretix/base/exporters/customers.py pretix/base/exporters/orderlist.py
|
||||
#: pretix/base/exporters/waitinglist.py pretix/base/models/memberships.py
|
||||
@@ -1417,10 +1413,8 @@ msgid "End date"
|
||||
msgstr "Sluttidspunkt"
|
||||
|
||||
#: pretix/base/exporters/customers.py
|
||||
#, fuzzy
|
||||
#| msgid "End: %(time)s"
|
||||
msgid "End time"
|
||||
msgstr "Slut: %(time)s"
|
||||
msgstr "Sluttidspunkt"
|
||||
|
||||
#: pretix/base/exporters/dekodi.py pretix/base/exporters/invoices.py
|
||||
msgctxt "export_category"
|
||||
@@ -4641,16 +4635,12 @@ msgid "This event is remote or partially remote."
|
||||
msgstr "Dette arrangement afholdes online eller delvist online."
|
||||
|
||||
#: pretix/base/models/event.py
|
||||
#, fuzzy
|
||||
#| msgid ""
|
||||
#| "This will be used to let users know if the event is in a different "
|
||||
#| "timezone and let’s us calculate users’ local times."
|
||||
msgid ""
|
||||
"This will be used to let users know if the event is in a different timezone, "
|
||||
"and to let us calculate the local time of a user."
|
||||
msgstr ""
|
||||
"Dette vil blive brugt til at informere brugerne om, hvorvidt begivenheden "
|
||||
"finder sted i en anden tidszone, og til at beregne brugernes lokale tid."
|
||||
"finder sted i en anden tidszone, og til at beregne brugerens lokale tid."
|
||||
|
||||
#: pretix/base/models/event.py pretix/base/models/organizer.py
|
||||
#: pretix/control/navigation.py
|
||||
@@ -5472,14 +5462,6 @@ msgid "Reusable media policy"
|
||||
msgstr "Politik for genanvendelige medier"
|
||||
|
||||
#: pretix/base/models/items.py
|
||||
#, fuzzy
|
||||
#| msgid ""
|
||||
#| "If this product should be stored on a re-usable physical medium, you can "
|
||||
#| "attach a physical media policy. This is not required for regular tickets, "
|
||||
#| "which just use a one-time barcode, but only for products like renewable "
|
||||
#| "season tickets or re-chargeable gift card wristbands. This is an advanced "
|
||||
#| "feature that also requires specific configuration of ticketing and "
|
||||
#| "printing settings."
|
||||
msgid ""
|
||||
"If this product should be stored on a reusable physical medium, you can "
|
||||
"attach a physical media policy. This is not required for regular tickets, "
|
||||
@@ -5488,12 +5470,12 @@ msgid ""
|
||||
"feature that also requires specific configuration of ticketing and printing "
|
||||
"settings."
|
||||
msgstr ""
|
||||
"Hvis dette produkt skal opbevares på et genanvendeligt fysisk medie, kan du "
|
||||
"vedhæfte en politik for fysiske medier. Dette er ikke nødvendigt for "
|
||||
"almindelige billetter, som bare bruger en engangs-stregkode, men kun for "
|
||||
"produkter som f.eks. fornyelige sæsonkort eller genopladelige gavekort-"
|
||||
"armbånd. Dette er en avanceret funktion, som også kræver en specifik "
|
||||
"konfiguration af billet- og printindstillinger."
|
||||
"Hvis dette produkt skal opbevares på et genanvendeligt fysisk medium, kan du "
|
||||
"knytte en politik for fysiske medier til det. Dette er ikke påkrævet for "
|
||||
"almindelige billetter, som blot bruger en engangsstregkode, men gælder kun "
|
||||
"for produkter som sæsonkort, der kan fornyes, eller gavekortarmbånd, som kan "
|
||||
"blive genopladet. Dette er en avanceret funktion, der også kræver en "
|
||||
"specifik konfiguration af billet- og udskrivningsindstillingerne."
|
||||
|
||||
#: pretix/base/models/items.py
|
||||
msgid "Reusable media type"
|
||||
@@ -5537,6 +5519,9 @@ msgid ""
|
||||
"prior to their usage. Therefore, the selected media policy does not make "
|
||||
"sense for this media type."
|
||||
msgstr ""
|
||||
"Den valgte medietype kræver, at alle medier registreres i systemet, inden de "
|
||||
"tages i brug. Derfor giver den valgte mediepolitik ikke mening for denne "
|
||||
"medietype."
|
||||
|
||||
#: pretix/base/models/items.py
|
||||
msgid ""
|
||||
@@ -6090,18 +6075,16 @@ msgstr "afvist"
|
||||
#: pretix/base/models/media.py
|
||||
msgctxt "reusable_medium"
|
||||
msgid "Claim token"
|
||||
msgstr ""
|
||||
msgstr "Hent token"
|
||||
|
||||
#: pretix/base/models/media.py
|
||||
msgctxt "reusable_medium"
|
||||
msgid "Label"
|
||||
msgstr ""
|
||||
msgstr "Etiket"
|
||||
|
||||
#: pretix/base/models/media.py
|
||||
#, fuzzy
|
||||
#| msgid "Linked ticket"
|
||||
msgid "Linked tickets"
|
||||
msgstr "Forbundet billet"
|
||||
msgstr "Sammenkædede billetter"
|
||||
|
||||
#: pretix/base/models/media.py
|
||||
msgid ""
|
||||
@@ -6109,6 +6092,9 @@ msgid ""
|
||||
"validity. If multiple tickets are valid at once, this will lead to failed "
|
||||
"check-ins."
|
||||
msgstr ""
|
||||
"Hvis du linker til mere end én billet, skal du sikre dig, at der ikke er "
|
||||
"nogen overlapning i gyldighedsperioden. Hvis flere billetter er gyldige på "
|
||||
"samme tid, vil det medføre, at check-in mislykkes."
|
||||
|
||||
#: pretix/base/models/memberships.py
|
||||
#: pretix/presale/templates/pretixpresale/organizers/customer_memberships.html
|
||||
@@ -7568,6 +7554,8 @@ msgid ""
|
||||
"This payment provider exists for historical purposes only and is no longer "
|
||||
"usable."
|
||||
msgstr ""
|
||||
"Denne betalingsudbyder findes udelukkende af historiske årsager og kan ikke "
|
||||
"længere benyttes."
|
||||
|
||||
#: pretix/base/pdf.py
|
||||
msgid "Ticket code (barcode content)"
|
||||
@@ -7802,15 +7790,12 @@ msgid "Atlantis"
|
||||
msgstr "Eksempelland"
|
||||
|
||||
#: pretix/base/pdf.py
|
||||
#, fuzzy
|
||||
msgid "Invoice custom recipient field"
|
||||
msgstr "Fakturamodtager:"
|
||||
msgstr "Brugerdefineret modtagerfelt på fakturaen"
|
||||
|
||||
#: pretix/base/pdf.py
|
||||
#, fuzzy
|
||||
#| msgid "Custom recipient field label"
|
||||
msgid "Custom recipient field"
|
||||
msgstr "Brugerdefineret etiket til modtagerfelt"
|
||||
msgstr "Brugerdefineret modtagerfelt"
|
||||
|
||||
#: pretix/base/pdf.py
|
||||
msgid "List of Add-Ons"
|
||||
@@ -8252,7 +8237,7 @@ msgstr "Arrangement aflyst"
|
||||
|
||||
#: pretix/base/services/cancelevent.py
|
||||
msgid "Confirm event cancellation and bulk refund"
|
||||
msgstr ""
|
||||
msgstr "Bekræft aflysning af begivenhed og samlet refusion"
|
||||
|
||||
#: pretix/base/services/cart.py pretix/base/services/modelimport.py
|
||||
#: pretix/base/services/orders.py
|
||||
@@ -8781,13 +8766,15 @@ msgstr "Du skal svare på spørgsmål for at gennemføre dette tjek-ind."
|
||||
|
||||
#: pretix/base/services/checkin.py
|
||||
msgid "Ticket needs to be exchanged to a suitable medium."
|
||||
msgstr ""
|
||||
msgstr "Billetten skal ombyttes til et passende medium."
|
||||
|
||||
#: pretix/base/services/checkin.py
|
||||
msgid ""
|
||||
"This ticket has already been exchanged for a reusable medium that now needs "
|
||||
"to be used instead."
|
||||
msgstr ""
|
||||
"Denne billet er allerede blevet ombyttet til et genanvendeligt medium, som "
|
||||
"nu skal bruges i stedet."
|
||||
|
||||
#: pretix/base/services/checkin.py
|
||||
msgid "This ticket has already been redeemed."
|
||||
@@ -8818,9 +8805,8 @@ msgid "Your export did not contain any data."
|
||||
msgstr "Din eksport indeholdt ingen data."
|
||||
|
||||
#: pretix/base/services/export.py
|
||||
#, fuzzy
|
||||
msgid "Scheduled export failed"
|
||||
msgstr "Start eksport"
|
||||
msgstr "Den planlagte eksport mislykkedes"
|
||||
|
||||
#: pretix/base/services/export.py
|
||||
msgid "Permission denied."
|
||||
@@ -8947,60 +8933,44 @@ msgid "You are receiving this email because you placed an order for {event}."
|
||||
msgstr "Du modtager denne e-mail, fordi du har afgivet en ordre til {event}."
|
||||
|
||||
#: pretix/base/services/media.py
|
||||
#, fuzzy
|
||||
#| msgid "Invalid input type."
|
||||
msgid "Invalid medium type."
|
||||
msgstr "Ugyldig indtastning."
|
||||
msgstr "Ugyldig medietype."
|
||||
|
||||
#: pretix/base/services/media.py
|
||||
#, fuzzy
|
||||
#| msgid "The selected media type is not enabled in your organizer settings."
|
||||
msgid "Medium type is not enabled for organizer."
|
||||
msgstr "Den valgte medietype er ikke aktiveret i dine arrangør-indstillinger."
|
||||
msgstr "Medie-typen er ikke aktiveret for arrangøren."
|
||||
|
||||
#: pretix/base/services/media.py
|
||||
msgid "Incorrect medium type for product."
|
||||
msgstr ""
|
||||
msgstr "Forkert medietype for produktet."
|
||||
|
||||
#: pretix/base/services/media.py
|
||||
#, fuzzy
|
||||
#| msgid "This ticket has already been redeemed."
|
||||
msgid "Ticket is already exchanged for reusable medium."
|
||||
msgstr "Denne billet er allerede blevet indløst."
|
||||
msgstr "Billetten er allerede ombyttet til et genanvendeligt medium."
|
||||
|
||||
#: pretix/base/services/media.py
|
||||
#, fuzzy
|
||||
#| msgid "Reusable Medium ID"
|
||||
msgid "Reusable medium not found."
|
||||
msgstr "ID for genanvendeligt medie"
|
||||
msgstr "Genanvendeligt medie blev ikke fundet."
|
||||
|
||||
#: pretix/base/services/media.py
|
||||
#, fuzzy
|
||||
#| msgid "The reusable medium has been created."
|
||||
msgid "Reusable medium is inactive or expired."
|
||||
msgstr "Det genanvendelige medie er blevet oprettet."
|
||||
msgstr "Det genanvendelige medium er inaktivt eller udløbet."
|
||||
|
||||
#: pretix/base/services/media.py
|
||||
#, fuzzy
|
||||
#| msgid "The reusable medium has been created."
|
||||
msgid "Reusable medium not found and could not be created."
|
||||
msgstr "Det genanvendelige medie er blevet oprettet."
|
||||
msgstr "Genanvendeligt medie blev ikke fundet og kunne ikke oprettes."
|
||||
|
||||
#: pretix/base/services/media.py
|
||||
#, fuzzy
|
||||
#| msgid "Reusable media type"
|
||||
msgid "Reusable medium already exists."
|
||||
msgstr "Genanvendelig medietype"
|
||||
msgstr "Det genanvendelige medie findes allerede."
|
||||
|
||||
#: pretix/base/services/media.py
|
||||
#, fuzzy
|
||||
#| msgid "The reusable medium has been created."
|
||||
msgid "Reusable medium could not be created."
|
||||
msgstr "Det genanvendelige medie er blevet oprettet."
|
||||
msgstr "Det var ikke muligt at oprette det genanvendelige medie."
|
||||
|
||||
#: pretix/base/services/media.py
|
||||
msgid "Product does not support medium exchange."
|
||||
msgstr ""
|
||||
msgstr "Produktet understøtter ikke udskiftning af medier."
|
||||
|
||||
#: pretix/base/services/memberships.py
|
||||
#, python-brace-format
|
||||
@@ -9164,6 +9134,7 @@ msgstr "En voucher kan ikke oprettes uden kode."
|
||||
msgid ""
|
||||
"Voucher codes must be unique. Code \"{code}\" already exists in this import."
|
||||
msgstr ""
|
||||
"Kuponkoder skal være unikke. Koden \"{code}\" findes allerede i denne import."
|
||||
|
||||
#: pretix/base/services/modelimport.py
|
||||
#, python-brace-format
|
||||
@@ -9172,13 +9143,19 @@ msgid ""
|
||||
msgid_plural ""
|
||||
"Voucher codes must be unique. Import contains existing voucher codes {code}."
|
||||
msgstr[0] ""
|
||||
"Rabatkoder skal være unikke. Importen indeholder den eksisterende rabatkode "
|
||||
"{code}."
|
||||
msgstr[1] ""
|
||||
"Rabatkoder skal være unikke. Importen indeholder de eksisterende rabatkoder "
|
||||
"{code}."
|
||||
|
||||
#: pretix/base/services/modelimport.py
|
||||
msgid ""
|
||||
"Vouchers could not be imported, probably due to a voucher code already being "
|
||||
"in use."
|
||||
msgstr ""
|
||||
"Rabatkoder kunne ikke importeres, sandsynligvis fordi en kuponkode allerede "
|
||||
"var i brug."
|
||||
|
||||
#: pretix/base/services/orders.py
|
||||
msgid ""
|
||||
@@ -9462,16 +9439,12 @@ msgstr ""
|
||||
"gavekort."
|
||||
|
||||
#: pretix/base/services/orders.py
|
||||
#, fuzzy
|
||||
#| msgid ""
|
||||
#| "You cannot change the price of a position that has been used to issue a "
|
||||
#| "gift card."
|
||||
msgid ""
|
||||
"You cannot change the ticket secret of a position that has been used to "
|
||||
"issue a gift card."
|
||||
msgstr ""
|
||||
"Du kan ikke ændre prisen af en post, som er blevet brugt til at udstede et "
|
||||
"gavekort."
|
||||
"Du kan ikke ændre billetkoden for en position, der er blevet brugt til at "
|
||||
"udstede et gavekort."
|
||||
|
||||
#: pretix/base/services/orders.py
|
||||
#, python-brace-format
|
||||
@@ -9558,10 +9531,9 @@ msgid "Something happened in your event after the export, please try again."
|
||||
msgstr "Der skete noget i din begivenhed efter eksporten, prøv venligst igen."
|
||||
|
||||
#: pretix/base/services/shredder.py
|
||||
#, fuzzy, python-format
|
||||
#| msgid "Data shredding completed"
|
||||
#, python-format
|
||||
msgid "Data shredding completed for %(event)s"
|
||||
msgstr "Makulering af data afsluttet"
|
||||
msgstr "Datadestruktion for %(event)s er afsluttet"
|
||||
|
||||
#: pretix/base/services/stats.py
|
||||
msgid "Uncategorized"
|
||||
@@ -9738,29 +9710,24 @@ msgstr ""
|
||||
"ind under købet."
|
||||
|
||||
#: pretix/base/settings.py
|
||||
#, fuzzy
|
||||
#| msgid "Activate re-usable media"
|
||||
msgid "Activate reusable media"
|
||||
msgstr "Aktiver genanvendelige medier"
|
||||
|
||||
#: pretix/base/settings.py
|
||||
#, fuzzy
|
||||
#| msgid ""
|
||||
#| "The re-usable media feature allows you to connect tickets and gift cards "
|
||||
#| "with physical media such as wristbands or chip cards that may be re-used "
|
||||
#| "for different tickets or gift cards later."
|
||||
msgid ""
|
||||
"The reusable media feature allows you to connect tickets and gift cards with "
|
||||
"physical media such as wristbands or chip cards that may be reused for "
|
||||
"different tickets or gift cards later."
|
||||
msgstr ""
|
||||
"Funktionen \"Genanvendelige medier\" gør det muligt at forbinde billetter og "
|
||||
"voucher med fysiske medier som armbånd eller chipkort, der kan genbruges til "
|
||||
"andre billetter eller voucher senere."
|
||||
"Med funktionen \"Genanvendelige medier\" kan du knytte billetter og gavekort "
|
||||
"til fysiske medier såsom armbånd eller chipkort, som senere kan genbruges "
|
||||
"til andre billetter eller gavekort."
|
||||
|
||||
#: pretix/base/settings.py
|
||||
msgid "Enforce the usage of issued reusable media for check-in"
|
||||
msgstr ""
|
||||
"Gennemtving, at der udelukkende anvendes de udleverede genanvendelige medier "
|
||||
"ved check-in"
|
||||
|
||||
#: pretix/base/settings.py
|
||||
msgid ""
|
||||
@@ -9768,6 +9735,10 @@ msgid ""
|
||||
"medium has been created and linked to a ticket. Keeping this option turned "
|
||||
"off will treat the reusable medium and ticket as equals."
|
||||
msgstr ""
|
||||
"Hvis denne indstilling er aktiveret, accepteres en billetstregkode ikke "
|
||||
"længere, hvis der er oprettet et genanvendeligt medie, som er knyttet til en "
|
||||
"billet. Hvis denne indstilling forbliver deaktiveret, behandles det "
|
||||
"genanvendelige medie og billetten på samme måde."
|
||||
|
||||
#: pretix/base/settings.py
|
||||
msgid "Length of barcodes"
|
||||
@@ -11336,10 +11307,8 @@ msgid "We'll show this publicly to allow attendees to contact you."
|
||||
msgstr "Vi vil vise dette offentligt, så deltagerne kan kontakte dig."
|
||||
|
||||
#: pretix/base/settings.py
|
||||
#, fuzzy
|
||||
#| msgid "Contact"
|
||||
msgid "Contact URL"
|
||||
msgstr "Kontakt"
|
||||
msgstr "Kontakt-URL"
|
||||
|
||||
#: pretix/base/settings.py
|
||||
msgid ""
|
||||
@@ -11347,6 +11316,9 @@ msgid ""
|
||||
"the email address above. Please note that you still need to add a contact "
|
||||
"email address that will be shared with all emails you send."
|
||||
msgstr ""
|
||||
"Hvis du indstiller dette, vil kontaktlinket i sidefoden pege hertil i stedet "
|
||||
"for at bruge ovenstående e-mailadresse. Bemærk, at du stadig skal tilføje en "
|
||||
"kontakt-e-mailadresse, som vil blive angivet i alle de e-mails, du sender."
|
||||
|
||||
#: pretix/base/settings.py pretix/control/forms/event.py
|
||||
msgid "Imprint URL"
|
||||
@@ -13213,30 +13185,19 @@ msgstr ""
|
||||
"os."
|
||||
|
||||
#: pretix/base/templates/pretixbase/email/cancel_confirm.txt
|
||||
#, fuzzy
|
||||
#| msgid ""
|
||||
#| "You have requested us to cancel an event which includes a larger bulk-"
|
||||
#| "refund:"
|
||||
msgid "You requested to cancel an event that involves a large bulk refund:"
|
||||
msgstr ""
|
||||
"Du har bedt os om at aflyse et arrangement, der indebærer en større samlet "
|
||||
"Du har anmodet om at annullere en begivenhed, der medfører en stor samlet "
|
||||
"refusion:"
|
||||
|
||||
#: pretix/base/templates/pretixbase/email/cancel_confirm.txt
|
||||
#, fuzzy
|
||||
#| msgid "Estimated refund amount"
|
||||
msgid "Estimated refund"
|
||||
msgstr "Anslået refusionsbeløb"
|
||||
msgstr "Anslået tilbagebetaling"
|
||||
|
||||
#: pretix/base/templates/pretixbase/email/cancel_confirm.txt
|
||||
#, fuzzy
|
||||
#| msgid ""
|
||||
#| "Please confirm that you want to proceed by coping the following "
|
||||
#| "confirmation code into the cancellation form:"
|
||||
msgid "To confirm, paste the following code into the cancellation form:"
|
||||
msgstr ""
|
||||
"Bekræft venligst, at du ønsker at fortsætte ved at indsætte følgende "
|
||||
"bekræftelseskode i afmeldingsformularen:"
|
||||
"For at bekræfte skal du indsætte følgende kode i afbestillingsformularen:"
|
||||
|
||||
#: pretix/base/templates/pretixbase/email/cancel_confirm.txt
|
||||
#, python-format
|
||||
@@ -13244,6 +13205,8 @@ msgid ""
|
||||
"Don't share this code with anyone. The %(instance)s team will never ask you "
|
||||
"for it."
|
||||
msgstr ""
|
||||
"Del ikke denne kode med nogen. %(instance)s-teamet vil aldrig bede dig om "
|
||||
"den."
|
||||
|
||||
#: pretix/base/templates/pretixbase/email/cancel_confirm.txt
|
||||
#: pretix/base/templates/pretixbase/email/export_failed.txt
|
||||
@@ -13252,6 +13215,8 @@ msgid ""
|
||||
"Thanks, \n"
|
||||
"The %(instance)s Team"
|
||||
msgstr ""
|
||||
"Tak, \n"
|
||||
"%(instance)s-teamet"
|
||||
|
||||
#: pretix/base/templates/pretixbase/email/email_footer.html
|
||||
#, python-format
|
||||
@@ -13259,51 +13224,38 @@ msgid "powered by <a %(a_attr)s>pretix</a>"
|
||||
msgstr "drevet af <a %(a_attr)s>pretix</a>"
|
||||
|
||||
#: pretix/base/templates/pretixbase/email/export_failed.txt
|
||||
#, fuzzy
|
||||
#| msgid "Your export failed."
|
||||
msgid "Your scheduled export failed."
|
||||
msgstr "Din eksport mislykkedes."
|
||||
msgstr "Din planlagte eksport mislykkedes."
|
||||
|
||||
#: pretix/base/templates/pretixbase/email/export_failed.txt
|
||||
#: pretix/control/templates/pretixcontrol/event/tax_edit.html
|
||||
#, fuzzy
|
||||
msgid "Reason"
|
||||
msgstr "Tilbagebetal bestilling"
|
||||
msgstr "Begrundelse"
|
||||
|
||||
#: pretix/base/templates/pretixbase/email/export_failed.txt
|
||||
#, fuzzy
|
||||
#| msgid "If your export fails five times in a row, it will no longer be sent."
|
||||
msgid "If an export fails five times in a row, we'll stop sending it."
|
||||
msgstr ""
|
||||
"Hvis din eksport mislykkes fem gange i træk, vil den ikke længere blive "
|
||||
"sendt."
|
||||
"Hvis en eksport mislykkes fem gange i træk, vil vi stoppe med at sende den."
|
||||
|
||||
#: pretix/base/templates/pretixbase/email/export_failed.txt
|
||||
#, fuzzy
|
||||
#| msgid "You can request to cancel this order."
|
||||
msgid "You can adjust or remove this export here:"
|
||||
msgstr "Du kan anmode om at annullere denne bestilling."
|
||||
msgstr "Du kan ændre eller fjerne denne eksport her:"
|
||||
|
||||
#: pretix/base/templates/pretixbase/email/notification.html
|
||||
#: pretix/base/templates/pretixbase/email/notification.txt
|
||||
#, fuzzy
|
||||
#| msgid "You receive these emails based on your notification settings."
|
||||
msgid "You're receiving this email based on your notification settings."
|
||||
msgstr ""
|
||||
"Du modtager disse e-mails i henhold til dine indstillinger for "
|
||||
"notifikationer."
|
||||
"Du modtager denne e-mail i henhold til dine indstillinger for meddelelser."
|
||||
|
||||
#: pretix/base/templates/pretixbase/email/notification.html
|
||||
#: pretix/base/templates/pretixbase/email/notification.txt
|
||||
#, fuzzy
|
||||
msgid "Manage settings"
|
||||
msgstr "Basisindstillinger"
|
||||
msgstr "Administrer indstillinger"
|
||||
|
||||
#: pretix/base/templates/pretixbase/email/notification.html
|
||||
#: pretix/base/templates/pretixbase/email/notification.txt
|
||||
#, fuzzy
|
||||
msgid "Disable all notifications"
|
||||
msgstr "E-mailnotifikationer"
|
||||
msgstr "Deaktiver alle notifikationer"
|
||||
|
||||
#: pretix/base/templates/pretixbase/email/order_details.html
|
||||
msgid ""
|
||||
@@ -13362,25 +13314,7 @@ msgid "Contact"
|
||||
msgstr "Kontakt"
|
||||
|
||||
#: pretix/base/templates/pretixbase/email/shred_completed.txt
|
||||
#, fuzzy, python-format
|
||||
#| msgid ""
|
||||
#| "Hello,\n"
|
||||
#| "\n"
|
||||
#| "we hereby confirm that the following data shredding job has been "
|
||||
#| "completed:\n"
|
||||
#| "\n"
|
||||
#| "Organizer: %(organizer)s\n"
|
||||
#| "\n"
|
||||
#| "Event: %(event)s\n"
|
||||
#| "\n"
|
||||
#| "Data selection: %(shredders)s\n"
|
||||
#| "\n"
|
||||
#| "Start time: %(start_time)s (new data added after this time might not have "
|
||||
#| "been deleted)\n"
|
||||
#| "\n"
|
||||
#| "Best regards,\n"
|
||||
#| "\n"
|
||||
#| "Your %(instance)s team\n"
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Hello,\n"
|
||||
"\n"
|
||||
@@ -13398,20 +13332,19 @@ msgid ""
|
||||
msgstr ""
|
||||
"Hej,\n"
|
||||
"\n"
|
||||
"vi bekræfter hermed, at følgende datadestruktionsopgave er afsluttet:\n"
|
||||
"Følgende datadestruktionsopgave er afsluttet:\n"
|
||||
"\n"
|
||||
"Arrangør: %(organizer)s\n"
|
||||
"- Arrangør: %(organizer)s\n"
|
||||
"- Begivenhed: %(event)s\n"
|
||||
"- Dataudvælgelse: %(shredders)s\n"
|
||||
"- Starttidspunkt: %(start_time)s\n"
|
||||
"\n"
|
||||
"Begivenhed: %(event)s\n"
|
||||
"Data, der er tilføjet til begivenheden efter starttidspunktet, er muligvis "
|
||||
"ikke blevet slettet.\n"
|
||||
"\n"
|
||||
"Dataudvælgelse: %(shredders)s\n"
|
||||
"Med venlig hilsen, \n"
|
||||
"\n"
|
||||
"Starttidspunkt: %(start_time)s (nye data tilføjet efter dette tidspunkt er "
|
||||
"muligvis ikke blevet slettet)\n"
|
||||
"\n"
|
||||
"Med venlig hilsen,\n"
|
||||
"\n"
|
||||
"Dit %(instance)s-team\n"
|
||||
"%(instance)s-teamet\n"
|
||||
|
||||
#: pretix/base/templates/pretixbase/forms/widgets/checkbox_sales_channel_option.html
|
||||
msgid ""
|
||||
@@ -13443,22 +13376,23 @@ msgstr "%(number)s dage %(relation)s %(relation_to)s på %(time_of_day)s"
|
||||
#: pretix/base/templates/pretixbase/framebreak.html
|
||||
#: pretix/presale/templates/pretixpresale/event/cookies.html
|
||||
msgid "Please continue in a new tab"
|
||||
msgstr ""
|
||||
msgstr "Fortsæt venligst i en ny fane"
|
||||
|
||||
#: pretix/base/templates/pretixbase/framebreak.html
|
||||
msgid "For security reasons, the following step is only possible in a new tab."
|
||||
msgstr ""
|
||||
"Af sikkerhedsmæssige årsager kan det følgende trin kun udføres i en ny fane."
|
||||
|
||||
#: pretix/base/templates/pretixbase/framebreak.html
|
||||
msgid ""
|
||||
"If the new tab did not open automatically, please click the following button:"
|
||||
msgstr ""
|
||||
"Hvis den nye fane ikke åbnede automatisk, skal du klikke på følgende knap:"
|
||||
|
||||
#: pretix/base/templates/pretixbase/framebreak.html
|
||||
#: pretix/presale/templates/pretixpresale/event/cookies.html
|
||||
#, fuzzy
|
||||
msgid "Continue in new tab"
|
||||
msgstr "Opret gruppe"
|
||||
msgstr "Fortsæt i en ny fane"
|
||||
|
||||
#: pretix/base/templates/pretixbase/redirect.html
|
||||
msgid "Redirect"
|
||||
@@ -15184,13 +15118,11 @@ msgstr "Søg efter e-mailadresse eller emne"
|
||||
#: pretix/control/templates/pretixcontrol/order/index.html
|
||||
#: pretix/control/templates/pretixcontrol/orders/refunds.html
|
||||
msgid "Source"
|
||||
msgstr ""
|
||||
msgstr "Kilde"
|
||||
|
||||
#: pretix/control/forms/filter.py
|
||||
#, fuzzy
|
||||
#| msgid "All vouchers"
|
||||
msgid "All sources"
|
||||
msgstr "Alle vouchere"
|
||||
msgstr "Alle kilder"
|
||||
|
||||
#: pretix/control/forms/filter.py
|
||||
msgid "Team actions"
|
||||
@@ -15201,21 +15133,16 @@ msgid "Customer actions"
|
||||
msgstr "Kundehandlinger"
|
||||
|
||||
#: pretix/control/forms/filter.py
|
||||
#, fuzzy
|
||||
#| msgid "Device status"
|
||||
msgid "Device actions"
|
||||
msgstr "Enhedsstatus"
|
||||
msgstr "Enhedshandlinger"
|
||||
|
||||
#: pretix/control/forms/filter.py
|
||||
#, fuzzy
|
||||
#| msgid "Order email"
|
||||
msgid "User email"
|
||||
msgstr "Bestillings-e-mail"
|
||||
msgstr "Brugerens e-mailadresse"
|
||||
|
||||
#: pretix/control/forms/filter.py pretix/control/navigation.py
|
||||
#, fuzzy
|
||||
msgid "All users"
|
||||
msgstr "Alle vouchere"
|
||||
msgstr "Alle brugere"
|
||||
|
||||
#: pretix/control/forms/global_settings.py
|
||||
msgid "Additional footer text"
|
||||
@@ -16724,40 +16651,34 @@ msgid ""
|
||||
"because at least one of the selected vouchers has already been redeemed "
|
||||
"%(max_redeemed)s times."
|
||||
msgstr ""
|
||||
"Du kan ikke reducere det maksimale antal indløsninger til %(max_usages)s, da "
|
||||
"mindst én af de valgte kuponer allerede er blevet indløst %(max_redeemed)s "
|
||||
"gange."
|
||||
|
||||
#: pretix/control/forms/vouchers.py
|
||||
#, fuzzy
|
||||
#| msgid ""
|
||||
#| "You cannot create a voucher that blocks quota as the selected product or "
|
||||
#| "quota is currently sold out or completely reserved."
|
||||
msgid ""
|
||||
"You cannot create a voucher that allows selection of a quota but has no date "
|
||||
"selected."
|
||||
msgstr ""
|
||||
"Du kan ikke oprette en rabatkode der blokerer en kvote idet det valgte "
|
||||
"produkt eller kvote pt. er udsolgt eller fuldt reserveret."
|
||||
"Du kan ikke oprette en kupon, hvor det er muligt at vælge en kvote, men hvor "
|
||||
"der ikke er valgt nogen dato."
|
||||
|
||||
#: pretix/control/forms/vouchers.py
|
||||
#, fuzzy
|
||||
#| msgid "The selected product does not allow to select a seat."
|
||||
msgid "The selected quota does not match the selected subevent."
|
||||
msgstr "Det valgte produkt tillader ikke, at du vælger en plads."
|
||||
msgstr "Den valgte kvote stemmer ikke overens med den valgte underbegivenhed."
|
||||
|
||||
#: pretix/control/forms/vouchers.py
|
||||
#, fuzzy
|
||||
#| msgid ""
|
||||
#| "There is not enough quota available on quota \"{}\" to perform the "
|
||||
#| "operation."
|
||||
msgid "There is no sufficient quota available to perform this change."
|
||||
msgstr ""
|
||||
"Der er ikke enheder nok tilbage på kvoten \"{}\" til at udføre denne "
|
||||
"operation."
|
||||
"Der er ikke tilstrækkelig kvote til rådighed til at gennemføre denne ændring."
|
||||
|
||||
#: pretix/control/forms/vouchers.py
|
||||
msgid ""
|
||||
"Changing the maximum number of usages in bulk is not supported if any of the "
|
||||
"selected vouchers is assigned a seat."
|
||||
msgstr ""
|
||||
"Det er ikke muligt at ændre det maksimale antal anvendelser samlet, hvis en "
|
||||
"af de valgte kuponer er tildelt en plads."
|
||||
|
||||
#: pretix/control/forms/vouchers.py
|
||||
msgctxt "subevent"
|
||||
@@ -16765,18 +16686,24 @@ msgid ""
|
||||
"Changing the date in bulk is not supported if any of the selected vouchers "
|
||||
"is assigned a seat."
|
||||
msgstr ""
|
||||
"Det er ikke muligt at ændre datoen for flere billetter på én gang, hvis en "
|
||||
"af de valgte billetter er tildelt en plads."
|
||||
|
||||
#: pretix/control/forms/vouchers.py
|
||||
msgid ""
|
||||
"Changing the product to a quota is not supported if any of the selected "
|
||||
"vouchers is assigned a seat."
|
||||
msgstr ""
|
||||
"Det er ikke muligt at ændre produktet til en kvote, hvis en af de valgte "
|
||||
"kuponer er tildelt en plads."
|
||||
|
||||
#: pretix/control/forms/vouchers.py
|
||||
msgid ""
|
||||
"This change cannot be completed because not all assigned seats of the "
|
||||
"vouchers are still available"
|
||||
msgstr ""
|
||||
"Denne ændring kan ikke gennemføres, da ikke alle de tildelte pladser på "
|
||||
"billetterne stadig er ledige"
|
||||
|
||||
#: pretix/control/forms/vouchers.py
|
||||
msgid "Codes"
|
||||
@@ -17897,16 +17824,12 @@ msgid "The reusable medium has been changed."
|
||||
msgstr "Det genanvendelige medie er blevet udskiftet."
|
||||
|
||||
#: pretix/control/logdisplay.py
|
||||
#, fuzzy
|
||||
#| msgid "The new member has been added to the team."
|
||||
msgid "A new ticket has been added to the medium."
|
||||
msgstr "Det nye medlem er blevet føjet til gruppen."
|
||||
msgstr "Der er blevet tilføjet en ny billet til mediet."
|
||||
|
||||
#: pretix/control/logdisplay.py
|
||||
#, fuzzy
|
||||
#| msgid "{user} has been removed from the team."
|
||||
msgid "A ticket has been removed from the medium."
|
||||
msgstr "{user} er fjernet fra gruppen."
|
||||
msgstr "En billet er blevet fjernet fra mediet."
|
||||
|
||||
#: pretix/control/logdisplay.py
|
||||
msgid "The medium has been connected to a new ticket."
|
||||
@@ -17918,6 +17841,8 @@ msgid ""
|
||||
"The ticket #{positionid} was exchanged for reusable medium "
|
||||
"{medium_identifier}."
|
||||
msgstr ""
|
||||
"Billetten #{positionid} blev ombyttet til et genanvendeligt medie "
|
||||
"{medium_identifier}."
|
||||
|
||||
#: pretix/control/logdisplay.py
|
||||
msgid "The medium has been connected to a new gift card."
|
||||
@@ -18328,14 +18253,14 @@ msgid "Payment {local_id} has been confirmed."
|
||||
msgstr "Betalingen {local_id} er blevet bekræftet."
|
||||
|
||||
#: pretix/control/logdisplay.py
|
||||
#, fuzzy, python-brace-format
|
||||
#, python-brace-format
|
||||
msgid "Payment {local_id} has been canceled."
|
||||
msgstr "Bestillingen er blevet annulleret."
|
||||
msgstr "Betalingen {local_id} er blevet annulleret."
|
||||
|
||||
#: pretix/control/logdisplay.py
|
||||
#, fuzzy, python-brace-format
|
||||
#, python-brace-format
|
||||
msgid "Canceling payment {local_id} has failed."
|
||||
msgstr "Bestillingen er blevet annulleret."
|
||||
msgstr "Det lykkedes ikke at annullere betalingen {local_id}."
|
||||
|
||||
#: pretix/control/logdisplay.py
|
||||
#, fuzzy, python-brace-format
|
||||
@@ -21795,54 +21720,51 @@ msgstr ""
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/global_sysreport.html
|
||||
msgid "January"
|
||||
msgstr ""
|
||||
msgstr "januar"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/global_sysreport.html
|
||||
msgid "February"
|
||||
msgstr ""
|
||||
msgstr "februar"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/global_sysreport.html
|
||||
#, fuzzy
|
||||
msgid "March"
|
||||
msgstr "Marts"
|
||||
msgstr "marts"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/global_sysreport.html
|
||||
msgid "April"
|
||||
msgstr ""
|
||||
msgstr "april"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/global_sysreport.html
|
||||
#, fuzzy
|
||||
#| msgid "Day"
|
||||
msgid "May"
|
||||
msgstr "Dag"
|
||||
msgstr "maj"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/global_sysreport.html
|
||||
msgid "June"
|
||||
msgstr ""
|
||||
msgstr "juni"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/global_sysreport.html
|
||||
msgid "July"
|
||||
msgstr ""
|
||||
msgstr "juli"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/global_sysreport.html
|
||||
msgid "August"
|
||||
msgstr ""
|
||||
msgstr "august"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/global_sysreport.html
|
||||
msgid "September"
|
||||
msgstr "September"
|
||||
msgstr "september"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/global_sysreport.html
|
||||
msgid "October"
|
||||
msgstr ""
|
||||
msgstr "oktober"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/global_sysreport.html
|
||||
msgid "November"
|
||||
msgstr "November"
|
||||
msgstr "november"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/global_sysreport.html
|
||||
msgid "December"
|
||||
msgstr "December"
|
||||
msgstr "december"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/global_sysreport.html
|
||||
#, fuzzy
|
||||
|
||||
@@ -7,7 +7,7 @@ msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-07-06 15:52+0000\n"
|
||||
"PO-Revision-Date: 2026-04-22 18:00+0000\n"
|
||||
"PO-Revision-Date: 2026-07-31 18:00+0000\n"
|
||||
"Last-Translator: Nikolai <nikolai@lengefeldt.de>\n"
|
||||
"Language-Team: Danish <https://translate.pretix.eu/projects/pretix/pretix-js/"
|
||||
"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 5.17\n"
|
||||
"X-Generator: Weblate 2026.7.1\n"
|
||||
|
||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js
|
||||
msgid "Marked as paid"
|
||||
@@ -1306,62 +1306,62 @@ msgstr ""
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js
|
||||
#: pretix/static/pretixpresale/widget/src/i18n.ts
|
||||
msgid "January"
|
||||
msgstr "Januar"
|
||||
msgstr "januar"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js
|
||||
#: pretix/static/pretixpresale/widget/src/i18n.ts
|
||||
msgid "February"
|
||||
msgstr "Februar"
|
||||
msgstr "februar"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js
|
||||
#: pretix/static/pretixpresale/widget/src/i18n.ts
|
||||
msgid "March"
|
||||
msgstr "Marts"
|
||||
msgstr "marts"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js
|
||||
#: pretix/static/pretixpresale/widget/src/i18n.ts
|
||||
msgid "April"
|
||||
msgstr "April"
|
||||
msgstr "april"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js
|
||||
#: pretix/static/pretixpresale/widget/src/i18n.ts
|
||||
msgid "May"
|
||||
msgstr "Maj"
|
||||
msgstr "maj"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js
|
||||
#: pretix/static/pretixpresale/widget/src/i18n.ts
|
||||
msgid "June"
|
||||
msgstr "Juni"
|
||||
msgstr "juni"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js
|
||||
#: pretix/static/pretixpresale/widget/src/i18n.ts
|
||||
msgid "July"
|
||||
msgstr "Juli"
|
||||
msgstr "juli"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js
|
||||
#: pretix/static/pretixpresale/widget/src/i18n.ts
|
||||
msgid "August"
|
||||
msgstr "August"
|
||||
msgstr "august"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js
|
||||
#: pretix/static/pretixpresale/widget/src/i18n.ts
|
||||
msgid "September"
|
||||
msgstr "September"
|
||||
msgstr "september"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js
|
||||
#: pretix/static/pretixpresale/widget/src/i18n.ts
|
||||
msgid "October"
|
||||
msgstr "Oktober"
|
||||
msgstr "oktober"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js
|
||||
#: pretix/static/pretixpresale/widget/src/i18n.ts
|
||||
msgid "November"
|
||||
msgstr "November"
|
||||
msgstr "november"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js
|
||||
#: pretix/static/pretixpresale/widget/src/i18n.ts
|
||||
msgid "December"
|
||||
msgstr "December"
|
||||
msgstr "december"
|
||||
|
||||
#~ msgid "iDEAL"
|
||||
#~ msgstr "iDEAL"
|
||||
|
||||
@@ -843,7 +843,7 @@ msgstr "Details der Bestellposition"
|
||||
|
||||
#: pretix/base/datasync/sourcefields.py
|
||||
msgid "Attendee details"
|
||||
msgstr "Details Teilnehmer"
|
||||
msgstr "Details Teilnehmer*in"
|
||||
|
||||
#: pretix/base/datasync/sourcefields.py pretix/base/exporters/answers.py
|
||||
#: pretix/base/models/items.py pretix/control/navigation.py
|
||||
@@ -891,11 +891,11 @@ msgstr "Veranstaltungs- oder Termininformationen"
|
||||
#: pretix/presale/templates/pretixpresale/organizers/customer_membership.html
|
||||
#: pretix/presale/templates/pretixpresale/organizers/customer_memberships.html
|
||||
msgid "Attendee name"
|
||||
msgstr "Name Teilnehmer"
|
||||
msgstr "Name Teilnehmer*in"
|
||||
|
||||
#: pretix/base/datasync/sourcefields.py
|
||||
msgid "Attendee"
|
||||
msgstr "Teilnehmer"
|
||||
msgstr "Teilnehmer*in"
|
||||
|
||||
#: pretix/base/datasync/sourcefields.py pretix/base/exporters/orderlist.py
|
||||
#: pretix/base/forms/questions.py pretix/base/models/customers.py
|
||||
@@ -904,11 +904,11 @@ msgstr "Teilnehmer"
|
||||
#: pretix/control/views/item.py
|
||||
#: pretix/presale/templates/pretixpresale/event/fragment_cart.html
|
||||
msgid "Attendee email"
|
||||
msgstr "E-Mail Teilnehmer"
|
||||
msgstr "E-Mail Teilnehmer*in"
|
||||
|
||||
#: pretix/base/datasync/sourcefields.py
|
||||
msgid "Attendee or order email"
|
||||
msgstr "E-Mail Teilnehmer oder Bestellung"
|
||||
msgstr "E-Mail Teilnehmer*in oder Bestellung"
|
||||
|
||||
#: pretix/base/datasync/sourcefields.py pretix/base/exporters/orderlist.py
|
||||
#: pretix/base/pdf.py pretix/control/templates/pretixcontrol/order/index.html
|
||||
@@ -916,23 +916,23 @@ msgstr "E-Mail Teilnehmer oder Bestellung"
|
||||
#: pretix/plugins/ticketoutputpdf/exporters.py
|
||||
#: pretix/presale/templates/pretixpresale/event/fragment_cart.html
|
||||
msgid "Attendee company"
|
||||
msgstr "Teilnehmer-Firma"
|
||||
msgstr "Teilnehmer*innen-Firma"
|
||||
|
||||
#: pretix/base/datasync/sourcefields.py
|
||||
msgid "Attendee address street"
|
||||
msgstr "Teilnehmer-Adresse: Straße"
|
||||
msgstr "Teilnehmer*innen-Adresse: Straße"
|
||||
|
||||
#: pretix/base/datasync/sourcefields.py
|
||||
msgid "Attendee address ZIP code"
|
||||
msgstr "Teilnehmer-Adresse: PLZ"
|
||||
msgstr "Teilnehmer*innen-Adresse: PLZ"
|
||||
|
||||
#: pretix/base/datasync/sourcefields.py
|
||||
msgid "Attendee address city"
|
||||
msgstr "Teilnehmer-Adresse: Stadt"
|
||||
msgstr "Teilnehmer*innen-Adresse: Stadt"
|
||||
|
||||
#: pretix/base/datasync/sourcefields.py
|
||||
msgid "Attendee address country"
|
||||
msgstr "Teilnehmer-Adresse: Land"
|
||||
msgstr "Teilnehmer*innen-Adresse: Land"
|
||||
|
||||
#: pretix/base/datasync/sourcefields.py pretix/base/exporters/orderlist.py
|
||||
#: pretix/base/pdf.py
|
||||
@@ -1166,7 +1166,7 @@ msgid ""
|
||||
"Download a ZIP file including all files that have been uploaded by your "
|
||||
"customers while creating an order."
|
||||
msgstr ""
|
||||
"ZIP-Datei mit allen Dateien, die von Kunden im Bestellprozess als "
|
||||
"ZIP-Datei mit allen Dateien, die von Kund*innen im Bestellprozess als "
|
||||
"Antwort auf eine Frage hochgeladen wurden."
|
||||
|
||||
#: pretix/base/exporters/answers.py pretix/base/exporters/orderlist.py
|
||||
@@ -2020,11 +2020,7 @@ msgstr "Ohne gültige Mitgliedschaft verstecken"
|
||||
msgid "Order data"
|
||||
msgstr "Bestelldaten"
|
||||
|
||||
#: pretix/base/exporters/relevant_orderlist.py:86
|
||||
msgid "Order data (sorted by relevance)"
|
||||
msgstr "Bestelldaten (nach Relevanz sortiert)"
|
||||
|
||||
#: pretix/base/exporters/json.py:53
|
||||
#: pretix/base/exporters/json.py
|
||||
msgid ""
|
||||
"Download a structured JSON representation of all orders. This might be "
|
||||
"useful for the import in third-party systems."
|
||||
@@ -2041,8 +2037,8 @@ msgid ""
|
||||
"Download a text file with all email addresses collected either from buyers "
|
||||
"or from ticket holders."
|
||||
msgstr ""
|
||||
"Textdatei mit allen E-Mail-Adressen, die von Käufern und "
|
||||
"Ticketinhaber eingesammelt wurden."
|
||||
"Textdatei mit allen E-Mail-Adressen, die von Käufer*innen und "
|
||||
"Ticketinhaber*innen eingesammelt wurden."
|
||||
|
||||
#: pretix/base/exporters/mail.py pretix/plugins/reports/exporters.py
|
||||
msgid "Filter by status"
|
||||
@@ -2059,30 +2055,15 @@ msgstr ""
|
||||
"Bestellposition und das dritte eine Zeile für jede zusätzlich erhobene "
|
||||
"Gebühr."
|
||||
|
||||
#: pretix/base/exporters/relevant_orderlist.py:88
|
||||
msgid ""
|
||||
"Download a spreadsheet of all orders. The spreadsheet will include three "
|
||||
"sheets, one with a line for every order, one with a line for every order "
|
||||
"position, and one with a line for every additional fee charged in an "
|
||||
"order. The most relevant data is in the first columns of the tables."
|
||||
msgstr ""
|
||||
"Tabelle (Excel oder CSV) mit allen Bestellungen. Das erste Tabellenblatt "
|
||||
"enthält eine Zeile für jede Bestellung, das zweite eine Zeile für jede "
|
||||
"Bestellposition und das dritte eine Zeile für jede zusätzlich erhobene "
|
||||
"Gebühr. Die relevantesten Daten sind in den ersten Spalten der Tabellen."
|
||||
|
||||
#: pretix/base/exporters/orderlist.py:102 pretix/base/models/orders.py:336
|
||||
#: pretix/base/permissions.py:228 pretix/control/navigation.py:267
|
||||
#: pretix/control/navigation.py:387
|
||||
#: pretix/control/templates/pretixcontrol/orders/index.html:8
|
||||
#: pretix/control/templates/pretixcontrol/orders/index.html:10
|
||||
#: pretix/control/templates/pretixcontrol/organizers/customer.html:205
|
||||
#: pretix/control/templates/pretixcontrol/subevents/detail.html:203
|
||||
#: pretix/plugins/reports/accountingreport.py:673
|
||||
#: pretix/plugins/reports/accountingreport.py:873
|
||||
#: pretix/plugins/reports/exporters.py:673 pretix/plugins/statistics/apps.py:39
|
||||
#: pretix/presale/templates/pretixpresale/organizers/customer_orders.html:13
|
||||
#: pretix/presale/views/customer.py:364
|
||||
#: pretix/base/exporters/orderlist.py pretix/base/models/orders.py
|
||||
#: pretix/base/permissions.py pretix/control/navigation.py
|
||||
#: pretix/control/templates/pretixcontrol/orders/index.html
|
||||
#: pretix/control/templates/pretixcontrol/organizers/customer.html
|
||||
#: pretix/control/templates/pretixcontrol/subevents/detail.html
|
||||
#: pretix/plugins/reports/accountingreport.py
|
||||
#: pretix/plugins/reports/exporters.py pretix/plugins/statistics/apps.py
|
||||
#: pretix/presale/templates/pretixpresale/organizers/customer_orders.html
|
||||
#: pretix/presale/views/customer.py
|
||||
msgid "Orders"
|
||||
msgstr "Bestellungen"
|
||||
|
||||
@@ -3859,13 +3840,13 @@ msgstr "Bitte wählen Sie einen gültigen Staat aus."
|
||||
|
||||
#: pretix/base/modelimport_orders.py pretix/control/forms/filter.py
|
||||
msgid "Attendee email address"
|
||||
msgstr "Teilnehmer-E-Mail-Adresse"
|
||||
msgstr "Teilnehmer*innen-E-Mail-Adresse"
|
||||
|
||||
#: pretix/base/modelimport_orders.py pretix/control/forms/filter.py
|
||||
#: pretix/control/templates/pretixcontrol/order/index.html
|
||||
#: pretix/presale/templates/pretixpresale/event/fragment_cart.html
|
||||
msgid "Attendee address"
|
||||
msgstr "Teilnehmer-Adresse"
|
||||
msgstr "Teilnehmer*innen-Adresse"
|
||||
|
||||
#: pretix/base/modelimport_orders.py
|
||||
msgid "Calculate from product"
|
||||
@@ -5369,8 +5350,8 @@ msgid ""
|
||||
"needs to be checked."
|
||||
msgstr ""
|
||||
"Wenn diese Option gesetzt ist, wird die Check-in-Anwendung melden, dass "
|
||||
"dieses Ticket besondere Behandlung braucht. Das ist z.B. für Studenten "
|
||||
"nützlich, deren Studentenausweis noch geprüft werden soll."
|
||||
"dieses Ticket besondere Behandlung braucht. Das ist z.B. für Studierende "
|
||||
"nützlich, deren Studierendenausweis noch geprüft werden soll."
|
||||
|
||||
#: pretix/base/models/items.py
|
||||
msgid ""
|
||||
@@ -6246,8 +6227,8 @@ msgid ""
|
||||
"cases."
|
||||
msgstr ""
|
||||
"Wenn diese Option gesetzt ist, wird die Check-in-Anwendung melden, dass "
|
||||
"dieses Ticket besondere Behandlung braucht. Das ist z.B. für Studenten "
|
||||
"nützlich, deren Studentenausweis noch geprüft werden soll."
|
||||
"dieses Ticket besondere Behandlung braucht. Das ist z.B. für Studierende "
|
||||
"nützlich, deren Studierendenausweis noch geprüft werden soll."
|
||||
|
||||
#: pretix/base/models/orders.py
|
||||
msgid ""
|
||||
@@ -7723,7 +7704,7 @@ msgstr "Musterfirma GmbH"
|
||||
|
||||
#: pretix/base/pdf.py
|
||||
msgid "Full attendee address"
|
||||
msgstr "Volle Teilnehmer-Adresse"
|
||||
msgstr "Volle Teilnehmer*innen-Adresse"
|
||||
|
||||
#: pretix/base/pdf.py
|
||||
msgid ""
|
||||
@@ -7741,23 +7722,23 @@ msgstr ""
|
||||
|
||||
#: pretix/base/pdf.py
|
||||
msgid "Attendee street"
|
||||
msgstr "Teilnehmer-Straße"
|
||||
msgstr "Teilnehmer*innen-Straße"
|
||||
|
||||
#: pretix/base/pdf.py
|
||||
msgid "Attendee ZIP code"
|
||||
msgstr "Teilnehmer-PLZ"
|
||||
msgstr "Teilnehmer*innen-PLZ"
|
||||
|
||||
#: pretix/base/pdf.py
|
||||
msgid "Attendee city"
|
||||
msgstr "Teilnehmer-Stadt"
|
||||
msgstr "Teilnehmer*innen-Stadt"
|
||||
|
||||
#: pretix/base/pdf.py
|
||||
msgid "Attendee state"
|
||||
msgstr "Teilnehmer-Bundesstaat"
|
||||
msgstr "Teilnehmer*innen-Bundesstaat"
|
||||
|
||||
#: pretix/base/pdf.py
|
||||
msgid "Attendee country"
|
||||
msgstr "Teilnehmer-Land"
|
||||
msgstr "Teilnehmer*innen-Land"
|
||||
|
||||
#: pretix/base/pdf.py
|
||||
msgid "Pseudonymization ID (lead scanning)"
|
||||
@@ -8052,7 +8033,7 @@ msgstr "Herr Mustermann"
|
||||
#: pretix/plugins/ticketoutputpdf/exporters.py
|
||||
#, python-brace-format
|
||||
msgid "Attendee name: {part}"
|
||||
msgstr "Teilnehmername: {part}"
|
||||
msgstr "Teilnehmer*innenname: {part}"
|
||||
|
||||
#: pretix/base/pdf.py
|
||||
msgid "Invoice address name for salutation"
|
||||
@@ -8954,7 +8935,7 @@ msgstr "Veranstaltungsort: {location}"
|
||||
#, python-brace-format
|
||||
msgctxt "invoice"
|
||||
msgid "Attendee: {name}"
|
||||
msgstr "Teilnehmer: {name}"
|
||||
msgstr "Teilnehmer*in: {name}"
|
||||
|
||||
#: pretix/base/services/invoices.py pretix/plugins/reports/exporters.py
|
||||
#, python-brace-format
|
||||
@@ -9911,7 +9892,7 @@ msgstr ""
|
||||
|
||||
#: pretix/base/settings.py
|
||||
msgid "Hide prices on attendee ticket page"
|
||||
msgstr "Preise auf Teilnehmer-Ticket-Seite verstecken"
|
||||
msgstr "Preise auf Teilnehmer*innen-Ticket-Seite verstecken"
|
||||
|
||||
#: pretix/base/settings.py
|
||||
msgid ""
|
||||
@@ -9921,13 +9902,13 @@ msgid ""
|
||||
"price."
|
||||
msgstr ""
|
||||
"Wenn eine Person mehrere Tickets erwirbt und E-Mails an alle "
|
||||
"Teilnehmer verschickt werden, wird mit dieser Option der Ticketpreis "
|
||||
"auf der Ticket-Seite der einzelnen Teilnehmer versteckt. Nur die "
|
||||
"Teilnehmer*innen verschickt werden, wird mit dieser Option der Ticketpreis "
|
||||
"auf der Ticket-Seite der einzelnen Teilnehmer*innen versteckt. Nur die "
|
||||
"Person, welche die Tickets kauft, sieht den Preis."
|
||||
|
||||
#: pretix/base/settings.py
|
||||
msgid "Ask for attendee names"
|
||||
msgstr "Namen der Teilnehmer erfragen"
|
||||
msgstr "Namen der Teilnehmer*innen erfragen"
|
||||
|
||||
#: pretix/base/settings.py
|
||||
msgid "Ask for a name for all personalized tickets."
|
||||
@@ -9936,11 +9917,11 @@ msgstr ""
|
||||
|
||||
#: pretix/base/settings.py
|
||||
msgid "Require attendee names"
|
||||
msgstr "Namen der Teilnehmer erfordern"
|
||||
msgstr "Namen der Teilnehmer*innen erfordern"
|
||||
|
||||
#: pretix/base/settings.py
|
||||
msgid "Require customers to fill in the names of all attendees."
|
||||
msgstr "Erfordere die Eingabe aller Teilnehmer-Namen."
|
||||
msgstr "Erfordere die Eingabe aller Teilnehmer*innen-Namen."
|
||||
|
||||
#: pretix/base/settings.py
|
||||
msgid "Ask for email addresses per ticket"
|
||||
@@ -10470,8 +10451,8 @@ msgid ""
|
||||
"but no indication of missing payment will be visible on the ticket pages of "
|
||||
"attendees who did not buy the ticket themselves."
|
||||
msgstr ""
|
||||
"Die Box mit Zahlungsinstruktionen wird Ticketkäufern weiter angezeigt, "
|
||||
"aber Teilnehmern, die ihr Ticket nicht selbst gekauft haben, werden "
|
||||
"Die Box mit Zahlungsinstruktionen wird Ticketkäufer*innen weiter angezeigt, "
|
||||
"aber Teilnehmer*innen, die ihr Ticket nicht selbst gekauft haben, werden "
|
||||
"keine Anzeichen des fehlenden Zahlungseingangs sehen."
|
||||
|
||||
#: pretix/base/settings.py
|
||||
@@ -10967,7 +10948,7 @@ msgstr ""
|
||||
|
||||
#: pretix/base/settings.py
|
||||
msgid "Show number of check-ins to customer"
|
||||
msgstr "Zeige Anzahl der Check-ins für Kunden an"
|
||||
msgstr "Zeige Anzahl der Check-ins für Kund*innen an"
|
||||
|
||||
#: pretix/base/settings.py
|
||||
msgid ""
|
||||
@@ -10978,12 +10959,12 @@ msgid ""
|
||||
"failed scans will not be counted, and the user will not see the different "
|
||||
"check-in lists."
|
||||
msgstr ""
|
||||
"Wenn diese Option aktiv ist, können Kunden selbst sehen, wie oft sie die "
|
||||
"Wenn diese Option aktiv ist, können Kund*innen selbst sehen, wie oft sie die "
|
||||
"Veranstaltung betreten haben. Das ist normalerweise nicht nötig, aber kann "
|
||||
"nützlich sein, wenn es Tickets gibt, die eine bestimmte Anzahl an Eintritten "
|
||||
"erlauben, sodass Kunden die bisherige Nutzung des Tickets einsehen "
|
||||
"erlauben, sodass Kund*innen die bisherige Nutzung des Tickets einsehen "
|
||||
"können. Ausgänge oder fehlgeschlagene Scans werden nicht angezeigt und die "
|
||||
"Kunden sehen keine Aufschlüsselung verschiedener Check-in-Listen."
|
||||
"Kund*innen sehen keine Aufschlüsselung verschiedener Check-in-Listen."
|
||||
|
||||
#: pretix/base/settings.py
|
||||
msgid "Allow users to download tickets"
|
||||
@@ -11164,7 +11145,7 @@ msgstr ""
|
||||
#: pretix/base/settings.py
|
||||
msgid "Both the attendee and the person who ordered can make changes"
|
||||
msgstr ""
|
||||
"Sowohl Besteller als auch Teilnehmer können Änderungen vornehmen"
|
||||
"Sowohl Besteller*in als auch Teilnehmer*innen können Änderungen vornehmen"
|
||||
|
||||
#: pretix/base/settings.py
|
||||
msgid "Allow customers to modify their information"
|
||||
@@ -11272,7 +11253,7 @@ msgstr ""
|
||||
|
||||
#: pretix/base/settings.py
|
||||
msgid "Allow individual attendees to change their ticket"
|
||||
msgstr "Erlaubt einzelnen Teilnehmern ihr Ticket zu ändern"
|
||||
msgstr "Erlaubt einzelnen Teilnehmer*innen ihr Ticket zu ändern"
|
||||
|
||||
#: pretix/base/settings.py
|
||||
msgid ""
|
||||
@@ -11284,7 +11265,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Standardmäßig kann nur die Person, welche die Tickets gekauft hat, "
|
||||
"Änderungen an der Bestellung vornehmen. Wenn diese Option aktiv ist, können "
|
||||
"auch einzelne Teilnehmer Änderungen vornehmen. Teilnehmer können "
|
||||
"auch einzelne Teilnehmer*innen Änderungen vornehmen. Teilnehmer*innen können "
|
||||
"jedoch immer nur Änderungen vornehmen, welche die Gesamtkosten der "
|
||||
"Bestellung nicht verändern. Solche Änderungen können nur vom Ticketkäufer "
|
||||
"vorgenommen werden."
|
||||
@@ -11593,7 +11574,7 @@ msgid ""
|
||||
"people."
|
||||
msgstr ""
|
||||
"Sie können dieses Feld benutzen, um zusätzliche Informationen mit Ihren "
|
||||
"Teilnehmer zu teilen, wie z.B. Anreise-Informationen oder den Link zu "
|
||||
"Teilnehmer*innen zu teilen, wie z.B. Anreise-Informationen oder den Link zu "
|
||||
"einer digitalen Veranstaltung. Wenn das Feld leer ist, fügen wir automatisch "
|
||||
"einen Link zum Ticketshop, die Einlass-Uhrzeit und den Veranstalter hier "
|
||||
"ein. Es sind keine Platzhalter mit sensiblen personenbezogenen Daten "
|
||||
@@ -11829,8 +11810,8 @@ msgstr ""
|
||||
"Diese Datei wird an die erste E-Mail angehängt, die wir beim Eingang einer "
|
||||
"neuen Bestellung verschicken. Sie kann daher mit den Textvorlagen "
|
||||
"\"Getätigte Bestellung\", \"Kostenlose Bestellung\" oder \"Erhaltene "
|
||||
"Bestellung\" von oben auftreten. Sie wird ggf. sowohl an Besteller als "
|
||||
"auch Teilnehmer verschickt. Nicht geeignet zum Versand nicht-"
|
||||
"Bestellung\" von oben auftreten. Sie wird ggf. sowohl an Besteller*innen als "
|
||||
"auch Teilnehmer*innen verschickt. Nicht geeignet zum Versand nicht-"
|
||||
"öffentlicher Informationen, da die Datei unabhängig davon verschickt wird, "
|
||||
"ob die Bestellung bezahlt oder freigegeben ist. Um zu vermeiden, dass diese "
|
||||
"wichtige E-Mail nicht ankommt, können nur PDF-Dateien mit maximal {size} MB "
|
||||
@@ -15118,7 +15099,7 @@ msgstr "Check-in-Status"
|
||||
|
||||
#: pretix/control/forms/filter.py pretix/plugins/checkinlists/exporters.py
|
||||
msgid "All attendees"
|
||||
msgstr "Alle Teilnehmer"
|
||||
msgstr "Alle Teilnehmer*innen"
|
||||
|
||||
#: pretix/control/forms/filter.py
|
||||
#: pretix/control/templates/pretixcontrol/checkin/index.html
|
||||
@@ -15721,7 +15702,7 @@ msgid ""
|
||||
"people over 65. This ticket includes access to all parts of the event, "
|
||||
"except the VIP area."
|
||||
msgstr ""
|
||||
"z.B. Dieses reduzierte Ticket ist erhältlich für Vollzeitstudent, "
|
||||
"z.B. Dieses reduzierte Ticket ist erhältlich für Vollzeitstudent*innen, "
|
||||
"Arbeitslose und Menschen über 65. Das Ticket enthält Zugang zu allen Teilen "
|
||||
"der Veranstaltung außer des VIP-Bereiches."
|
||||
|
||||
@@ -16032,7 +16013,7 @@ msgstr ""
|
||||
|
||||
#: pretix/control/forms/orders.py
|
||||
msgid "Notify customer by email"
|
||||
msgstr "Kunde per E-Mail benachrichtigen"
|
||||
msgstr "Kund*in per E-Mail benachrichtigen"
|
||||
|
||||
#: pretix/control/forms/orders.py
|
||||
msgid "Keep a cancellation fee of"
|
||||
@@ -17638,7 +17619,7 @@ msgstr "Eine individuelle E-Mail wurde verschickt."
|
||||
|
||||
#: pretix/control/logdisplay.py
|
||||
msgid "A custom email has been sent to an attendee."
|
||||
msgstr "Eine individuelle E-Mail wurde an einen Teilnehmer verschickt."
|
||||
msgstr "Eine individuelle E-Mail wurde an eine Teilnehmer*in verschickt."
|
||||
|
||||
#: pretix/control/logdisplay.py
|
||||
msgid ""
|
||||
@@ -19340,7 +19321,7 @@ msgstr "Terminal-ID"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/boxoffice/payment.html
|
||||
msgid "Card holder"
|
||||
msgstr "Karteninhaber"
|
||||
msgstr "Karteninhaber*in"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/boxoffice/payment.html
|
||||
msgid "Card expiration"
|
||||
@@ -19620,7 +19601,7 @@ msgstr "CSV"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/checkin/index.html
|
||||
msgid "No attendee record was found."
|
||||
msgstr "Keine passenden Teilnehmer gefunden."
|
||||
msgstr "Keine passenden Teilnehmer*innen gefunden."
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/checkin/index.html
|
||||
#: pretix/control/templates/pretixcontrol/datasync/failed_jobs.html
|
||||
@@ -22380,7 +22361,7 @@ msgid ""
|
||||
"Only purchases of such products will be considered \"attendees\" for most "
|
||||
"statistical purposes or within some plugins."
|
||||
msgstr ""
|
||||
"Nur Käufe eines solchen Produkts werden als \"Teilnehmer\" gewertet, "
|
||||
"Nur Käufe eines solchen Produkts werden als \"Teilnehmer*innen\" gewertet, "
|
||||
"z.B. in Statistiken oder in Funktionen von Erweiterungen."
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/item/create.html
|
||||
@@ -22459,7 +22440,7 @@ msgid ""
|
||||
"The system will not ask for a name or other attendee details. This only "
|
||||
"affects system-provided fields, you can still add your own questions."
|
||||
msgstr ""
|
||||
"Das System wird nicht nach einem Namen oder anderen Teilnehmer-Daten "
|
||||
"Das System wird nicht nach einem Namen oder anderen Teilnehmer*innen-Daten "
|
||||
"fragen. Dies betrifft nur vom System bereitgestellte Felder, eigene Fragen "
|
||||
"können trotzdem hinzugefügt werden."
|
||||
|
||||
@@ -24711,7 +24692,7 @@ msgstr "Sonstige Datenexporte"
|
||||
#: pretix/control/templates/pretixcontrol/orders/export.html
|
||||
#: pretix/control/templates/pretixcontrol/organizers/export.html
|
||||
msgid "Recommended for new users"
|
||||
msgstr "Empfohlen für neue Benutzer"
|
||||
msgstr "Empfohlen für neue Benutzer*innen"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/export.html
|
||||
#: pretix/control/templates/pretixcontrol/organizers/export.html
|
||||
@@ -25644,7 +25625,7 @@ msgstr "Domains"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/organizers/edit.html
|
||||
msgid "This dialog is intended for advanced users."
|
||||
msgstr "Dieser Dialog ist für fortgeschrittene Anwender gedacht."
|
||||
msgstr "Dieser Dialog ist für fortgeschrittene Anwender*innen gedacht."
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/organizers/edit.html
|
||||
msgid ""
|
||||
@@ -28024,7 +28005,7 @@ msgid ""
|
||||
"customers. This way, customers will not be able to discover the waiting list."
|
||||
msgstr ""
|
||||
"Entsprechend Ihrer Veranstaltungseinstellungen werden ausverkaufte Produkte "
|
||||
"nicht angezeigt. Dies führt dazu, dass Kunden die Warteliste nicht "
|
||||
"nicht angezeigt. Dies führt dazu, dass Kund*innen die Warteliste nicht "
|
||||
"finden können."
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/waitinglist/index.html
|
||||
@@ -28322,11 +28303,11 @@ msgstr "Das ausgewählte List wurde gelöscht."
|
||||
|
||||
#: pretix/control/views/dashboards.py
|
||||
msgid "Attendees (ordered)"
|
||||
msgstr "Teilnehmer (bestellt)"
|
||||
msgstr "Teilnehmende (bestellt)"
|
||||
|
||||
#: pretix/control/views/dashboards.py
|
||||
msgid "Attendees (paid)"
|
||||
msgstr "Teilnehmer (bezahlt)"
|
||||
msgstr "Teilnehmende (bezahlt)"
|
||||
|
||||
#: pretix/control/views/dashboards.py
|
||||
#, python-brace-format
|
||||
@@ -30389,7 +30370,7 @@ msgstr "PDF-Sammlungen"
|
||||
|
||||
#: pretix/plugins/badges/exporters.py
|
||||
msgid "Download all attendee badges as one large PDF for printing."
|
||||
msgstr "Alle Teilnehmer-Badges in einer großen PDF-Datei für den Druck."
|
||||
msgstr "Alle Teilnehmer*innen-Badges in einer großen PDF-Datei für den Druck."
|
||||
|
||||
#: pretix/plugins/badges/exporters.py
|
||||
#: pretix/plugins/ticketoutputpdf/exporters.py
|
||||
@@ -30640,7 +30621,7 @@ msgstr "Anderes Bankkonto"
|
||||
|
||||
#: pretix/plugins/banktransfer/payment.py
|
||||
msgid "Name of account holder"
|
||||
msgstr "Kontoinhaber"
|
||||
msgstr "Kontoinhaber*in"
|
||||
|
||||
#: pretix/plugins/banktransfer/payment.py
|
||||
msgid ""
|
||||
@@ -30776,7 +30757,7 @@ msgstr "Bitte überweisen Sie den vollen Betrag auf das folgende Bankkonto:"
|
||||
#: pretix/plugins/banktransfer/views.py pretix/plugins/stripe/payment.py
|
||||
#: pretix/plugins/stripe/templates/pretixplugins/stripe/checkout_payment_confirm.html
|
||||
msgid "Account holder"
|
||||
msgstr "Kontoinhaber"
|
||||
msgstr "Kontoinhaber*in"
|
||||
|
||||
#: pretix/plugins/banktransfer/payment.py
|
||||
#: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/checkout_payment_form.html
|
||||
@@ -31354,7 +31335,7 @@ msgid ""
|
||||
"Download a spreadsheet with all attendees that are included in a check-in "
|
||||
"list."
|
||||
msgstr ""
|
||||
"Tabelle (Excel oder CSV) mit allen Teilnehmer, die in einer Check-in-"
|
||||
"Tabelle (Excel oder CSV) mit allen Teilnehmer*innen, die in einer Check-in-"
|
||||
"Liste zutrittsberechtigt sind."
|
||||
|
||||
#: pretix/plugins/checkinlists/exporters.py
|
||||
@@ -32470,7 +32451,7 @@ msgstr "Geplante E-Mails"
|
||||
|
||||
#: pretix/plugins/sendmail/signals.py
|
||||
msgid "Mass email was sent to customers or attendees."
|
||||
msgstr "Rundmail wurde an Kunden oder Teilnehmer verschickt."
|
||||
msgstr "Rundmail wurde an Kunden oder Teilnehmer*innen verschickt."
|
||||
|
||||
#: pretix/plugins/sendmail/signals.py
|
||||
msgid "Mass email was sent to waiting list entries."
|
||||
@@ -32502,7 +32483,7 @@ msgstr "Eine automatisierte E-Mail wurde an den Besteller verschickt"
|
||||
|
||||
#: pretix/plugins/sendmail/signals.py
|
||||
msgid "A scheduled email was sent to a ticket holder"
|
||||
msgstr "Eine automatisierte E-Mail wurde an eine Teilnehmer verschickt."
|
||||
msgstr "Eine automatisierte E-Mail wurde an eine Teilnehmer*in verschickt."
|
||||
|
||||
#: pretix/plugins/sendmail/signals.py
|
||||
msgid "An email rule was deleted"
|
||||
@@ -32535,7 +32516,7 @@ msgstr "Alle nicht eingecheckten Kunden"
|
||||
|
||||
#: pretix/plugins/sendmail/templates/pretixplugins/sendmail/history_fragment_orders.html
|
||||
msgid "Attendee contact addresses"
|
||||
msgstr "Teilnehmer-E-Mail-Adressen"
|
||||
msgstr "Teilnehmer*innen-E-Mail-Adressen"
|
||||
|
||||
#: pretix/plugins/sendmail/templates/pretixplugins/sendmail/history_fragment_orders.html
|
||||
msgid "All contact addresses"
|
||||
@@ -32687,14 +32668,14 @@ msgstr ""
|
||||
|
||||
#: pretix/plugins/sendmail/views.py
|
||||
msgid "Orders or attendees"
|
||||
msgstr "Bestellungen oder Teilnehmer"
|
||||
msgstr "Bestellungen oder Teilnehmer*innen"
|
||||
|
||||
#: pretix/plugins/sendmail/views.py
|
||||
msgid ""
|
||||
"Send an email to every customer, or to every person a ticket has been "
|
||||
"purchased for, or a combination of both."
|
||||
msgstr ""
|
||||
"Senden Sie eine E-Mail an alle Ticketkäufer, alle Ticketinhaber "
|
||||
"Senden Sie eine E-Mail an alle Ticketkäufer*innen, alle Ticketinhaber*innen "
|
||||
"oder eine Kombination aus beiden Gruppen."
|
||||
|
||||
#: pretix/plugins/sendmail/views.py
|
||||
@@ -33257,23 +33238,23 @@ msgstr "SEPA-Lastschrift"
|
||||
|
||||
#: pretix/plugins/stripe/payment.py
|
||||
msgid "Account Holder Name"
|
||||
msgstr "Kontoinhaber"
|
||||
msgstr "Kontoinhaber*in"
|
||||
|
||||
#: pretix/plugins/stripe/payment.py
|
||||
msgid "Account Holder Street"
|
||||
msgstr "Straße (Kontoinhaber)"
|
||||
msgstr "Straße (Kontoinhaber*in)"
|
||||
|
||||
#: pretix/plugins/stripe/payment.py
|
||||
msgid "Account Holder Postal Code"
|
||||
msgstr "PLZ (Kontoinhaber)"
|
||||
msgstr "PLZ (Kontoinhaber*in)"
|
||||
|
||||
#: pretix/plugins/stripe/payment.py
|
||||
msgid "Account Holder City"
|
||||
msgstr "Stadt (Kontoinhaber)"
|
||||
msgstr "Stadt (Kontoinhaber*in)"
|
||||
|
||||
#: pretix/plugins/stripe/payment.py
|
||||
msgid "Account Holder Country"
|
||||
msgstr "Land (Kontoinhaber)"
|
||||
msgstr "Land (Kontoinhaber*in)"
|
||||
|
||||
#: pretix/plugins/stripe/payment.py
|
||||
msgid "Affirm via Stripe"
|
||||
@@ -33713,7 +33694,7 @@ msgstr "MOTO"
|
||||
|
||||
#: pretix/plugins/stripe/templates/pretixplugins/stripe/control.html
|
||||
msgid "Payer name"
|
||||
msgstr "Name des Zahlers"
|
||||
msgstr "Name des Zahlenden"
|
||||
|
||||
#: pretix/plugins/stripe/templates/pretixplugins/stripe/control.html
|
||||
msgid "Payment receipt"
|
||||
@@ -35006,7 +34987,7 @@ msgstr[1] "Das Ticket wurde %(count)s-mal eingelöst."
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/fragment_cart.html
|
||||
msgid "No attendee name provided"
|
||||
msgstr "Name des Teilnehmenrs nicht angegeben"
|
||||
msgstr "Name der teilnehmenden Person nicht angegeben"
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/fragment_cart.html
|
||||
msgid "The image you previously uploaded"
|
||||
@@ -36783,12 +36764,12 @@ msgstr ""
|
||||
#: pretix/presale/templates/pretixpresale/organizers/customer_profiles.html
|
||||
#: pretix/presale/views/customer.py
|
||||
msgid "Attendee profiles"
|
||||
msgstr "Teilnehmer-Profile"
|
||||
msgstr "Teilnehmer*innen-Profile"
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/organizers/customer_profiles.html
|
||||
msgid "You don’t have any attendee profiles in your account yet."
|
||||
msgstr ""
|
||||
"In Ihrem Kundenkonto sind noch keine Teilnehmer-Profile gespeichert."
|
||||
"In Ihrem Kundenkonto sind noch keine Teilnehmer*innen-Profile gespeichert."
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/organizers/customer_registration.html
|
||||
msgid "Registration"
|
||||
@@ -38543,7 +38524,7 @@ msgstr "Kosovo"
|
||||
#~ "This plugin allows you to generate badges or name tags for your attendees."
|
||||
#~ msgstr ""
|
||||
#~ "Diese Erweiterung erlaubt, Namensschilder oder Badges für die "
|
||||
#~ "Teilnehmer zu erstellen."
|
||||
#~ "Teilnehmer*innen zu erstellen."
|
||||
|
||||
#~ msgid "This plugin allows you to receive payments via PayPal"
|
||||
#~ msgstr "Dieses Plugin erlaubt, Zahlungen über PayPal anzunehmen"
|
||||
|
||||
@@ -153,11 +153,11 @@ msgstr "Bezahlte Bestellungen"
|
||||
|
||||
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js
|
||||
msgid "Attendees (ordered)"
|
||||
msgstr "Teilnehmer (bestellt)"
|
||||
msgstr "Teilnehmende (bestellt)"
|
||||
|
||||
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js
|
||||
msgid "Attendees (paid)"
|
||||
msgstr "Teilnehmer (bezahlt)"
|
||||
msgstr "Teilnehmende (bezahlt)"
|
||||
|
||||
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js
|
||||
msgid "Total revenue"
|
||||
|
||||
@@ -845,7 +845,7 @@ msgstr "Details der Bestellposition"
|
||||
|
||||
#: pretix/base/datasync/sourcefields.py
|
||||
msgid "Attendee details"
|
||||
msgstr "Details Teilnehmer"
|
||||
msgstr "Details Teilnehmer*in"
|
||||
|
||||
#: pretix/base/datasync/sourcefields.py pretix/base/exporters/answers.py
|
||||
#: pretix/base/models/items.py pretix/control/navigation.py
|
||||
@@ -893,11 +893,11 @@ msgstr "Veranstaltungs- oder Termininformationen"
|
||||
#: pretix/presale/templates/pretixpresale/organizers/customer_membership.html
|
||||
#: pretix/presale/templates/pretixpresale/organizers/customer_memberships.html
|
||||
msgid "Attendee name"
|
||||
msgstr "Name Teilnehmer"
|
||||
msgstr "Name Teilnehmer*in"
|
||||
|
||||
#: pretix/base/datasync/sourcefields.py
|
||||
msgid "Attendee"
|
||||
msgstr "Teilnehmer"
|
||||
msgstr "Teilnehmer*in"
|
||||
|
||||
#: pretix/base/datasync/sourcefields.py pretix/base/exporters/orderlist.py
|
||||
#: pretix/base/forms/questions.py pretix/base/models/customers.py
|
||||
@@ -906,11 +906,11 @@ msgstr "Teilnehmer"
|
||||
#: pretix/control/views/item.py
|
||||
#: pretix/presale/templates/pretixpresale/event/fragment_cart.html
|
||||
msgid "Attendee email"
|
||||
msgstr "E-Mail Teilnehmer"
|
||||
msgstr "E-Mail Teilnehmer*in"
|
||||
|
||||
#: pretix/base/datasync/sourcefields.py
|
||||
msgid "Attendee or order email"
|
||||
msgstr "E-Mail Teilnehmer oder Bestellung"
|
||||
msgstr "E-Mail Teilnehmer*in oder Bestellung"
|
||||
|
||||
#: pretix/base/datasync/sourcefields.py pretix/base/exporters/orderlist.py
|
||||
#: pretix/base/pdf.py pretix/control/templates/pretixcontrol/order/index.html
|
||||
@@ -918,23 +918,23 @@ msgstr "E-Mail Teilnehmer oder Bestellung"
|
||||
#: pretix/plugins/ticketoutputpdf/exporters.py
|
||||
#: pretix/presale/templates/pretixpresale/event/fragment_cart.html
|
||||
msgid "Attendee company"
|
||||
msgstr "Teilnehmer-Firma"
|
||||
msgstr "Teilnehmer*innen-Firma"
|
||||
|
||||
#: pretix/base/datasync/sourcefields.py
|
||||
msgid "Attendee address street"
|
||||
msgstr "Teilnehmer-Adresse: Straße"
|
||||
msgstr "Teilnehmer*innen-Adresse: Straße"
|
||||
|
||||
#: pretix/base/datasync/sourcefields.py
|
||||
msgid "Attendee address ZIP code"
|
||||
msgstr "Teilnehmer-Adresse: PLZ"
|
||||
msgstr "Teilnehmer*innen-Adresse: PLZ"
|
||||
|
||||
#: pretix/base/datasync/sourcefields.py
|
||||
msgid "Attendee address city"
|
||||
msgstr "Teilnehmer-Adresse: Stadt"
|
||||
msgstr "Teilnehmer*innen-Adresse: Stadt"
|
||||
|
||||
#: pretix/base/datasync/sourcefields.py
|
||||
msgid "Attendee address country"
|
||||
msgstr "Teilnehmer-Adresse: Land"
|
||||
msgstr "Teilnehmer*innen-Adresse: Land"
|
||||
|
||||
#: pretix/base/datasync/sourcefields.py pretix/base/exporters/orderlist.py
|
||||
#: pretix/base/pdf.py
|
||||
@@ -1168,7 +1168,7 @@ msgid ""
|
||||
"Download a ZIP file including all files that have been uploaded by your "
|
||||
"customers while creating an order."
|
||||
msgstr ""
|
||||
"ZIP-Datei mit allen Dateien, die von Kunden im Bestellprozess als "
|
||||
"ZIP-Datei mit allen Dateien, die von Kund*innen im Bestellprozess als "
|
||||
"Antwort auf eine Frage hochgeladen wurden."
|
||||
|
||||
#: pretix/base/exporters/answers.py pretix/base/exporters/orderlist.py
|
||||
@@ -2039,8 +2039,8 @@ msgid ""
|
||||
"Download a text file with all email addresses collected either from buyers "
|
||||
"or from ticket holders."
|
||||
msgstr ""
|
||||
"Textdatei mit allen E-Mail-Adressen, die von Käufer und "
|
||||
"Ticketinhaber eingesammelt wurden."
|
||||
"Textdatei mit allen E-Mail-Adressen, die von Käufer*innen und "
|
||||
"Ticketinhaber*innen eingesammelt wurden."
|
||||
|
||||
#: pretix/base/exporters/mail.py pretix/plugins/reports/exporters.py
|
||||
msgid "Filter by status"
|
||||
@@ -3842,13 +3842,13 @@ msgstr "Bitte wähle einen gültigen Staat aus."
|
||||
|
||||
#: pretix/base/modelimport_orders.py pretix/control/forms/filter.py
|
||||
msgid "Attendee email address"
|
||||
msgstr "Teilnehmer-E-Mail-Adresse"
|
||||
msgstr "Teilnehmer*innen-E-Mail-Adresse"
|
||||
|
||||
#: pretix/base/modelimport_orders.py pretix/control/forms/filter.py
|
||||
#: pretix/control/templates/pretixcontrol/order/index.html
|
||||
#: pretix/presale/templates/pretixpresale/event/fragment_cart.html
|
||||
msgid "Attendee address"
|
||||
msgstr "Teilnehmer-Adresse"
|
||||
msgstr "Teilnehmer*innen-Adresse"
|
||||
|
||||
#: pretix/base/modelimport_orders.py
|
||||
msgid "Calculate from product"
|
||||
@@ -5347,8 +5347,8 @@ msgid ""
|
||||
"needs to be checked."
|
||||
msgstr ""
|
||||
"Wenn diese Option gesetzt ist, wird die Check-in-Anwendung melden, dass "
|
||||
"dieses Ticket besondere Behandlung braucht. Das ist z.B. für Studenten "
|
||||
"nützlich, deren Studentenausweis noch geprüft werden soll."
|
||||
"dieses Ticket besondere Behandlung braucht. Das ist z.B. für Studierende "
|
||||
"nützlich, deren Studierendenausweis noch geprüft werden soll."
|
||||
|
||||
#: pretix/base/models/items.py
|
||||
msgid ""
|
||||
@@ -6224,8 +6224,8 @@ msgid ""
|
||||
"cases."
|
||||
msgstr ""
|
||||
"Wenn diese Option gesetzt ist, wird die Check-in-Anwendung melden, dass "
|
||||
"dieses Ticket besondere Behandlung braucht. Das ist z.B. für Studenten "
|
||||
"nützlich, deren Studentenausweis noch geprüft werden soll."
|
||||
"dieses Ticket besondere Behandlung braucht. Das ist z.B. für Studierende "
|
||||
"nützlich, deren Studierendenausweis noch geprüft werden soll."
|
||||
|
||||
#: pretix/base/models/orders.py
|
||||
msgid ""
|
||||
@@ -7697,7 +7697,7 @@ msgstr "Musterfirma"
|
||||
|
||||
#: pretix/base/pdf.py
|
||||
msgid "Full attendee address"
|
||||
msgstr "Volle Teilnehmer-Adresse"
|
||||
msgstr "Volle Teilnehmer*innen-Adresse"
|
||||
|
||||
#: pretix/base/pdf.py
|
||||
msgid ""
|
||||
@@ -7715,23 +7715,23 @@ msgstr ""
|
||||
|
||||
#: pretix/base/pdf.py
|
||||
msgid "Attendee street"
|
||||
msgstr "Teilnehmer-Straße"
|
||||
msgstr "Teilnehmer*innen-Straße"
|
||||
|
||||
#: pretix/base/pdf.py
|
||||
msgid "Attendee ZIP code"
|
||||
msgstr "Teilnehmer-PLZ"
|
||||
msgstr "Teilnehmer*innen-PLZ"
|
||||
|
||||
#: pretix/base/pdf.py
|
||||
msgid "Attendee city"
|
||||
msgstr "Teilnehmer-Stadt"
|
||||
msgstr "Teilnehmer*innen-Stadt"
|
||||
|
||||
#: pretix/base/pdf.py
|
||||
msgid "Attendee state"
|
||||
msgstr "Teilnehmer-Bundesstaat"
|
||||
msgstr "Teilnehmer*innen-Bundesstaat"
|
||||
|
||||
#: pretix/base/pdf.py
|
||||
msgid "Attendee country"
|
||||
msgstr "Teilnehmer-Land"
|
||||
msgstr "Teilnehmer*innen-Land"
|
||||
|
||||
#: pretix/base/pdf.py
|
||||
msgid "Pseudonymization ID (lead scanning)"
|
||||
@@ -8026,7 +8026,7 @@ msgstr "Herr Mustermann"
|
||||
#: pretix/plugins/ticketoutputpdf/exporters.py
|
||||
#, python-brace-format
|
||||
msgid "Attendee name: {part}"
|
||||
msgstr "Teilnehmer: {part}"
|
||||
msgstr "Teilnehmer*innenname: {part}"
|
||||
|
||||
#: pretix/base/pdf.py
|
||||
msgid "Invoice address name for salutation"
|
||||
@@ -8925,7 +8925,7 @@ msgstr "Veranstaltungsort: {location}"
|
||||
#, python-brace-format
|
||||
msgctxt "invoice"
|
||||
msgid "Attendee: {name}"
|
||||
msgstr "Teilnehmer: {name}"
|
||||
msgstr "Teilnehmer*in: {name}"
|
||||
|
||||
#: pretix/base/services/invoices.py pretix/plugins/reports/exporters.py
|
||||
#, python-brace-format
|
||||
@@ -9880,7 +9880,7 @@ msgstr ""
|
||||
|
||||
#: pretix/base/settings.py
|
||||
msgid "Hide prices on attendee ticket page"
|
||||
msgstr "Preise auf Teilnehmer-Ticket-Seite verstecken"
|
||||
msgstr "Preise auf Teilnehmer*innen-Ticket-Seite verstecken"
|
||||
|
||||
#: pretix/base/settings.py
|
||||
msgid ""
|
||||
@@ -9890,13 +9890,13 @@ msgid ""
|
||||
"price."
|
||||
msgstr ""
|
||||
"Wenn eine Person mehrere Tickets erwirbt und E-Mails an alle "
|
||||
"Teilnehmer verschickt werden, wird mit dieser Option der Ticketpreis "
|
||||
"auf der Ticket-Seite der einzelnen Teilnehmer versteckt. Nur die "
|
||||
"Teilnehmer*innen verschickt werden, wird mit dieser Option der Ticketpreis "
|
||||
"auf der Ticket-Seite der einzelnen Teilnehmer*innen versteckt. Nur die "
|
||||
"Person, welche die Tickets kauft, sieht den Preis."
|
||||
|
||||
#: pretix/base/settings.py
|
||||
msgid "Ask for attendee names"
|
||||
msgstr "Namen der Teilnehmer erfragen"
|
||||
msgstr "Namen der Teilnehmer*innen erfragen"
|
||||
|
||||
#: pretix/base/settings.py
|
||||
msgid "Ask for a name for all personalized tickets."
|
||||
@@ -9905,11 +9905,11 @@ msgstr ""
|
||||
|
||||
#: pretix/base/settings.py
|
||||
msgid "Require attendee names"
|
||||
msgstr "Namen der Teilnehmer erfordern"
|
||||
msgstr "Namen der Teilnehmer*innen erfordern"
|
||||
|
||||
#: pretix/base/settings.py
|
||||
msgid "Require customers to fill in the names of all attendees."
|
||||
msgstr "Erfordere die Eingabe aller Teilnehmer-Namen."
|
||||
msgstr "Erfordere die Eingabe aller Teilnehmer*innen-Namen."
|
||||
|
||||
#: pretix/base/settings.py
|
||||
msgid "Ask for email addresses per ticket"
|
||||
@@ -9931,7 +9931,7 @@ msgstr ""
|
||||
"aktivierst, fragt das System zusätzlich nach einzelnen E-Mail-Adressen für "
|
||||
"jedes personalisierte Ticket in der Bestellung. Dies könnte z.B. nützlich "
|
||||
"sein, wenn du auch im Falle von Gruppenbestellungen individuelle Adressen "
|
||||
"von jedem Teilnehmer benötigst. pretix sendet die Bestellbestätigung "
|
||||
"von jeder Teilnehmer*in benötigst. pretix sendet die Bestellbestätigung "
|
||||
"standardmäßig nach wie vor nur an die primäre Adresse, dies kann jedoch in "
|
||||
"den E-Mail-Einstellungen angepasst werden."
|
||||
|
||||
@@ -10438,8 +10438,8 @@ msgid ""
|
||||
"but no indication of missing payment will be visible on the ticket pages of "
|
||||
"attendees who did not buy the ticket themselves."
|
||||
msgstr ""
|
||||
"Die Box mit Zahlungsinstruktionen wird Ticketkäufern weiter angezeigt, "
|
||||
"aber Teilnehmer, die ihr Ticket nicht selbst gekauft haben, werden "
|
||||
"Die Box mit Zahlungsinstruktionen wird Ticketkäufer*innen weiter angezeigt, "
|
||||
"aber Teilnehmer*innen, die ihr Ticket nicht selbst gekauft haben, werden "
|
||||
"keine Anzeichen des fehlenden Zahlungseingangs sehen."
|
||||
|
||||
#: pretix/base/settings.py
|
||||
@@ -10935,7 +10935,7 @@ msgstr ""
|
||||
|
||||
#: pretix/base/settings.py
|
||||
msgid "Show number of check-ins to customer"
|
||||
msgstr "Zeige Anzahl der Check-ins für Kunden an"
|
||||
msgstr "Zeige Anzahl der Check-ins für Kund*innen an"
|
||||
|
||||
#: pretix/base/settings.py
|
||||
msgid ""
|
||||
@@ -10946,12 +10946,12 @@ msgid ""
|
||||
"failed scans will not be counted, and the user will not see the different "
|
||||
"check-in lists."
|
||||
msgstr ""
|
||||
"Wenn diese Option aktiv ist, können Kunden selbst sehen, wie oft sie die "
|
||||
"Wenn diese Option aktiv ist, können Kund*innen selbst sehen, wie oft sie die "
|
||||
"Veranstaltung betreten haben. Das ist normalerweise nicht nötig, aber kann "
|
||||
"nützlich sein, wenn es Tickets gibt, die eine bestimmte Anzahl an Eintritten "
|
||||
"erlauben, sodass Kunden die bisherige Nutzung des Tickets einsehen "
|
||||
"erlauben, sodass Kund*innen die bisherige Nutzung des Tickets einsehen "
|
||||
"können. Ausgänge oder fehlgeschlagene Scans werden nicht angezeigt und die "
|
||||
"Kunden sehen keine Aufschlüsselung verschiedener Check-in-Listen."
|
||||
"Kund*innen sehen keine Aufschlüsselung verschiedener Check-in-Listen."
|
||||
|
||||
#: pretix/base/settings.py
|
||||
msgid "Allow users to download tickets"
|
||||
@@ -11132,7 +11132,7 @@ msgstr ""
|
||||
#: pretix/base/settings.py
|
||||
msgid "Both the attendee and the person who ordered can make changes"
|
||||
msgstr ""
|
||||
"Sowohl Besteller als auch Teilnehmer können Änderungen vornehmen"
|
||||
"Sowohl Besteller*in als auch Teilnehmer*innen können Änderungen vornehmen"
|
||||
|
||||
#: pretix/base/settings.py
|
||||
msgid "Allow customers to modify their information"
|
||||
@@ -11240,7 +11240,7 @@ msgstr ""
|
||||
|
||||
#: pretix/base/settings.py
|
||||
msgid "Allow individual attendees to change their ticket"
|
||||
msgstr "Erlaubt einzelnen Teilnehmer ihr Ticket zu ändern"
|
||||
msgstr "Erlaubt einzelnen Teilnehmer*innen ihr Ticket zu ändern"
|
||||
|
||||
#: pretix/base/settings.py
|
||||
msgid ""
|
||||
@@ -11252,7 +11252,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Standardmäßig kann nur die Person, welche die Tickets gekauft hat, "
|
||||
"Änderungen an der Bestellung vornehmen. Wenn diese Option aktiv ist, können "
|
||||
"auch einzelne Teilnehmer Änderungen vornehmen. Teilnehmer können "
|
||||
"auch einzelne Teilnehmer*innen Änderungen vornehmen. Teilnehmer*innen können "
|
||||
"jedoch immer nur Änderungen vornehmen, welche die Gesamtkosten der "
|
||||
"Bestellung nicht verändern. Solche Änderungen können nur vom Ticketkäufer "
|
||||
"vorgenommen werden."
|
||||
@@ -11435,7 +11435,7 @@ msgstr "Kontakt-E-Mail"
|
||||
#: pretix/base/settings.py pretix/control/forms/event.py
|
||||
msgid "We'll show this publicly to allow attendees to contact you."
|
||||
msgstr ""
|
||||
"Wir werden diese Adresse veröffentlichen um Teilnehmern zu ermöglichen, "
|
||||
"Wir werden diese Adresse veröffentlichen um Teilnehmer*innen zu ermöglichen, "
|
||||
"dich zu kontaktieren."
|
||||
|
||||
#: pretix/base/settings.py
|
||||
@@ -11560,7 +11560,7 @@ msgid ""
|
||||
"people."
|
||||
msgstr ""
|
||||
"Du kannst dieses Feld benutzen um zusätzliche Informationen mit deinen "
|
||||
"Teilnehmer zu teilen, wie z.B. Anreise-Informationen oder den Link zu "
|
||||
"Teilnehmer*innen zu teilen, wie z.B. Anreise-Informationen oder den Link zu "
|
||||
"einer digitalen Veranstaltung. Wenn das Feld leer ist, fügen wir automatisch "
|
||||
"einen Link zum Ticketshop, die Einlass-Uhrzeit und den Veranstalter hier "
|
||||
"ein. Es sind keine Platzhalter mit sensiblen personenbezogenen Daten "
|
||||
@@ -11795,8 +11795,8 @@ msgstr ""
|
||||
"Diese Datei wird an die erste E-Mail angehängt, die wir beim Eingang einer "
|
||||
"neuen Bestellung verschicken. Sie kann daher mit den Textvorlagen "
|
||||
"\"Getätigte Bestellung\", \"Kostenlose Bestellung\" oder \"Erhaltene "
|
||||
"Bestellung\" von oben auftreten. Sie wird ggf. sowohl an Besteller als "
|
||||
"auch Teilnehmer verschickt. Nicht geeignet zum Versand nicht-"
|
||||
"Bestellung\" von oben auftreten. Sie wird ggf. sowohl an Besteller*innen als "
|
||||
"auch Teilnehmer*innen verschickt. Nicht geeignet zum Versand nicht-"
|
||||
"öffentlicher Informationen, da die Datei unabhängig davon verschickt wird, "
|
||||
"ob die Bestellung bezahlt oder freigegeben ist. Um zu vermeiden, dass diese "
|
||||
"wichtige E-Mail nicht ankommt, können nur PDF-Dateien mit maximal {size} MB "
|
||||
@@ -13039,7 +13039,7 @@ msgstr ""
|
||||
msgid ""
|
||||
"You cannot require specifying attendee names if you do not ask for them."
|
||||
msgstr ""
|
||||
"Du kannst die Angabe von Teilnehmernamen nur erfordern, wenn auch nach "
|
||||
"Du kannst die Angabe von Teilnehmer*innennamen nur erfordern, wenn auch nach "
|
||||
"Namen gefragt wird."
|
||||
|
||||
#: pretix/base/settings.py
|
||||
@@ -14712,11 +14712,11 @@ msgstr "Ticket-Downloads"
|
||||
#: pretix/control/forms/event.py
|
||||
msgid "Your customers will be able to download their tickets in PDF format."
|
||||
msgstr ""
|
||||
"Die Teilnehmer werden ihre Tickets im PDF-Format herunterladen können."
|
||||
"Die Teilnehmer*innen werden ihre Tickets im PDF-Format herunterladen können."
|
||||
|
||||
#: pretix/control/forms/event.py
|
||||
msgid "Require all attendees to fill in their names"
|
||||
msgstr "Erfordere, dass alle Teilnehmer ihre Namen ausfüllen"
|
||||
msgstr "Erfordere, dass alle Teilnehmer*innen ihre Namen ausfüllen"
|
||||
|
||||
#: pretix/control/forms/event.py
|
||||
msgid ""
|
||||
@@ -14752,7 +14752,7 @@ msgid ""
|
||||
"then import your bank statements to process the payments within pretix, or "
|
||||
"mark them as paid manually."
|
||||
msgstr ""
|
||||
"Deine Teilnehmer werden angewiesen, das Geld direkt auf dein Bankkonto "
|
||||
"Deine Teilnehmer*innen werden angewiesen, das Geld direkt auf dein Bankkonto "
|
||||
"zu überweisen. Du kannst dann deinen Kontoauszug in pretix importieren, um "
|
||||
"Zahlungen zuzuweisen, oder die Bestellungen manuell als bezahlt markieren."
|
||||
|
||||
@@ -15075,7 +15075,7 @@ msgstr "Check-in-Status"
|
||||
|
||||
#: pretix/control/forms/filter.py pretix/plugins/checkinlists/exporters.py
|
||||
msgid "All attendees"
|
||||
msgstr "Alle Teilnehmer"
|
||||
msgstr "Alle Teilnehmer*innen"
|
||||
|
||||
#: pretix/control/forms/filter.py
|
||||
#: pretix/control/templates/pretixcontrol/checkin/index.html
|
||||
@@ -15678,7 +15678,7 @@ msgid ""
|
||||
"people over 65. This ticket includes access to all parts of the event, "
|
||||
"except the VIP area."
|
||||
msgstr ""
|
||||
"z.B. Dieses reduzierte Ticket ist erhältlich für Vollzeitstudenten, "
|
||||
"z.B. Dieses reduzierte Ticket ist erhältlich für Vollzeitstudent*innen, "
|
||||
"Arbeitslose und Menschen über 65. Das Ticket enthält Zugang zu allen Teilen "
|
||||
"der Veranstaltung außer des VIP-Bereiches."
|
||||
|
||||
@@ -17595,7 +17595,7 @@ msgstr "Eine individuelle E-Mail wurde verschickt."
|
||||
|
||||
#: pretix/control/logdisplay.py
|
||||
msgid "A custom email has been sent to an attendee."
|
||||
msgstr "Eine individuelle E-Mail wurde an eine Teilnehmer verschickt."
|
||||
msgstr "Eine individuelle E-Mail wurde an eine Teilnehmer*in verschickt."
|
||||
|
||||
#: pretix/control/logdisplay.py
|
||||
msgid ""
|
||||
@@ -19295,7 +19295,7 @@ msgstr "Terminal-ID"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/boxoffice/payment.html
|
||||
msgid "Card holder"
|
||||
msgstr "Karteninhaber"
|
||||
msgstr "Karteninhaber*in"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/boxoffice/payment.html
|
||||
msgid "Card expiration"
|
||||
@@ -19575,7 +19575,7 @@ msgstr "CSV"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/checkin/index.html
|
||||
msgid "No attendee record was found."
|
||||
msgstr "Keine passenden Teilnehmer gefunden."
|
||||
msgstr "Keine passenden Teilnehmer*innen gefunden."
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/checkin/index.html
|
||||
#: pretix/control/templates/pretixcontrol/datasync/failed_jobs.html
|
||||
@@ -21354,7 +21354,7 @@ msgid ""
|
||||
"provide ways for your attendees to contact you:"
|
||||
msgstr ""
|
||||
"Wenn irgendetwas schiefgeht oder unklar ist, empfehlen wir, dass du deinen "
|
||||
"Teilnehmer die Möglichkeit gibst, dich zu benachrichtigen:"
|
||||
"Teilnehmer*innen die Möglichkeit gibst, dich zu benachrichtigen:"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/event/settings.html
|
||||
msgid "Basics"
|
||||
@@ -22331,7 +22331,7 @@ msgid ""
|
||||
"Only purchases of such products will be considered \"attendees\" for most "
|
||||
"statistical purposes or within some plugins."
|
||||
msgstr ""
|
||||
"Nur Käufe eines solchen Produkts werden als \"Teilnehmer\" gewertet, "
|
||||
"Nur Käufe eines solchen Produkts werden als \"Teilnehmer*innen\" gewertet, "
|
||||
"z.B. in Statistiken oder in Funktionen von Erweiterungen."
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/item/create.html
|
||||
@@ -22410,7 +22410,7 @@ msgid ""
|
||||
"The system will not ask for a name or other attendee details. This only "
|
||||
"affects system-provided fields, you can still add your own questions."
|
||||
msgstr ""
|
||||
"Das System wird nicht nach einem Namen oder anderen Teilnehmer-Daten "
|
||||
"Das System wird nicht nach einem Namen oder anderen Teilnehmer*innen-Daten "
|
||||
"fragen. Dies betrifft nur vom System bereitgestellte Felder, eigene Fragen "
|
||||
"können trotzdem hinzugefügt werden."
|
||||
|
||||
@@ -23081,9 +23081,9 @@ msgid ""
|
||||
"ticket. If you provide food, one example might be to ask your users about "
|
||||
"dietary requirements."
|
||||
msgstr ""
|
||||
"Fragen erlauben deinen Besucher, zusätzliche Informationen zu ihrem "
|
||||
"Fragen erlauben deinen Besucher*innen, zusätzliche Informationen zu ihrem "
|
||||
"Ticket auszufüllen. Wenn deine Veranstaltung Verpflegung beinhaltet, "
|
||||
"könntest du z.B. nach Allergien deiner Teilnehmer fragen."
|
||||
"könntest du z.B. nach Allergien deiner Teilnehmer*innen fragen."
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/items/questions.html
|
||||
msgid "Create a new question"
|
||||
@@ -23245,7 +23245,7 @@ msgstr ""
|
||||
"Um deine Produkte verfügbar zu machen, musst du Kontingente anlegen. "
|
||||
"Kontingente definieren, wie oft ein Produkt verkauft werden darf. Auf diese "
|
||||
"Art kannst du konfigurieren, ob deine Veranstaltung unbegrenzt viele "
|
||||
"Teilnehmer aufnehmen kann oder ob die Anzahl begrenzt ist. Du kannst "
|
||||
"Teilnehmer*innen aufnehmen kann oder ob die Anzahl begrenzt ist. Du kannst "
|
||||
"ein Produkt zu mehreren Kontingenten hinzufügen, um komplexere Anforderungen "
|
||||
"abzubilden, z.B. wenn du die Gesamtzahl der Tickets begrenzen willst, aber "
|
||||
"einen speziellen Ticket-Typ noch stärker begrenzen willst."
|
||||
@@ -24659,7 +24659,7 @@ msgstr "Sonstige Datenexporte"
|
||||
#: pretix/control/templates/pretixcontrol/orders/export.html
|
||||
#: pretix/control/templates/pretixcontrol/organizers/export.html
|
||||
msgid "Recommended for new users"
|
||||
msgstr "Empfohlen für neue Benutzer"
|
||||
msgstr "Empfohlen für neue Benutzer*innen"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/orders/export.html
|
||||
#: pretix/control/templates/pretixcontrol/organizers/export.html
|
||||
@@ -25591,7 +25591,7 @@ msgstr "Domains"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/organizers/edit.html
|
||||
msgid "This dialog is intended for advanced users."
|
||||
msgstr "Dieser Dialog ist für fortgeschrittene Anwender gedacht."
|
||||
msgstr "Dieser Dialog ist für fortgeschrittene Anwender*innen gedacht."
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/organizers/edit.html
|
||||
msgid ""
|
||||
@@ -27966,7 +27966,7 @@ msgid ""
|
||||
"customers. This way, customers will not be able to discover the waiting list."
|
||||
msgstr ""
|
||||
"Entsprechend deiner Veranstaltungseinstellungen werden ausverkaufte Produkte "
|
||||
"nicht angezeigt. Dies führt dazu, dass Kunden die Warteliste nicht "
|
||||
"nicht angezeigt. Dies führt dazu, dass Kund*innen die Warteliste nicht "
|
||||
"finden können."
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/waitinglist/index.html
|
||||
@@ -28263,11 +28263,11 @@ msgstr "Das ausgewählte List wurde gelöscht."
|
||||
|
||||
#: pretix/control/views/dashboards.py
|
||||
msgid "Attendees (ordered)"
|
||||
msgstr "Teilnehmer (bestellt)"
|
||||
msgstr "Teilnehmende (bestellt)"
|
||||
|
||||
#: pretix/control/views/dashboards.py
|
||||
msgid "Attendees (paid)"
|
||||
msgstr "Teilnehmer (bezahlt)"
|
||||
msgstr "Teilnehmende (bezahlt)"
|
||||
|
||||
#: pretix/control/views/dashboards.py
|
||||
#, python-brace-format
|
||||
@@ -30326,7 +30326,7 @@ msgstr "PDF-Sammlungen"
|
||||
|
||||
#: pretix/plugins/badges/exporters.py
|
||||
msgid "Download all attendee badges as one large PDF for printing."
|
||||
msgstr "Alle Teilnehmer-Badges in einer großen PDF-Datei für den Druck."
|
||||
msgstr "Alle Teilnehmer*innen-Badges in einer großen PDF-Datei für den Druck."
|
||||
|
||||
#: pretix/plugins/badges/exporters.py
|
||||
#: pretix/plugins/ticketoutputpdf/exporters.py
|
||||
@@ -30576,7 +30576,7 @@ msgstr "Anderes Bankkonto"
|
||||
|
||||
#: pretix/plugins/banktransfer/payment.py
|
||||
msgid "Name of account holder"
|
||||
msgstr "Kontoinhaber"
|
||||
msgstr "Kontoinhaber*in"
|
||||
|
||||
#: pretix/plugins/banktransfer/payment.py
|
||||
msgid ""
|
||||
@@ -30712,7 +30712,7 @@ msgstr "Bitte überweise den vollen Betrag auf das folgende Bankkonto:"
|
||||
#: pretix/plugins/banktransfer/views.py pretix/plugins/stripe/payment.py
|
||||
#: pretix/plugins/stripe/templates/pretixplugins/stripe/checkout_payment_confirm.html
|
||||
msgid "Account holder"
|
||||
msgstr "Kontoinhaber"
|
||||
msgstr "Kontoinhaber*in"
|
||||
|
||||
#: pretix/plugins/banktransfer/payment.py
|
||||
#: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/checkout_payment_form.html
|
||||
@@ -31291,7 +31291,7 @@ msgid ""
|
||||
"Download a spreadsheet with all attendees that are included in a check-in "
|
||||
"list."
|
||||
msgstr ""
|
||||
"Tabelle (Excel oder CSV) mit allen Teilnehmer, die in einer Check-in-"
|
||||
"Tabelle (Excel oder CSV) mit allen Teilnehmer*innen, die in einer Check-in-"
|
||||
"Liste zutrittsberechtigt sind."
|
||||
|
||||
#: pretix/plugins/checkinlists/exporters.py
|
||||
@@ -32404,7 +32404,7 @@ msgstr "Geplante E-Mails"
|
||||
|
||||
#: pretix/plugins/sendmail/signals.py
|
||||
msgid "Mass email was sent to customers or attendees."
|
||||
msgstr "Rundmail wurde an Kunden oder Teilnehmer verschickt."
|
||||
msgstr "Rundmail wurde an Kunden oder Teilnehmer*innen verschickt."
|
||||
|
||||
#: pretix/plugins/sendmail/signals.py
|
||||
msgid "Mass email was sent to waiting list entries."
|
||||
@@ -32436,7 +32436,7 @@ msgstr "Eine automatisierte E-Mail wurde an den Besteller verschickt"
|
||||
|
||||
#: pretix/plugins/sendmail/signals.py
|
||||
msgid "A scheduled email was sent to a ticket holder"
|
||||
msgstr "Eine automatisierte E-Mail wurde an einen Teilnehmer verschickt."
|
||||
msgstr "Eine automatisierte E-Mail wurde an eine Teilnehmer*in verschickt."
|
||||
|
||||
#: pretix/plugins/sendmail/signals.py
|
||||
msgid "An email rule was deleted"
|
||||
@@ -32469,7 +32469,7 @@ msgstr "Alle nicht eingecheckten Kunden"
|
||||
|
||||
#: pretix/plugins/sendmail/templates/pretixplugins/sendmail/history_fragment_orders.html
|
||||
msgid "Attendee contact addresses"
|
||||
msgstr "Teilnehmer-E-Mail-Adressen"
|
||||
msgstr "Teilnehmer*innen-E-Mail-Adressen"
|
||||
|
||||
#: pretix/plugins/sendmail/templates/pretixplugins/sendmail/history_fragment_orders.html
|
||||
msgid "All contact addresses"
|
||||
@@ -32623,14 +32623,14 @@ msgstr ""
|
||||
|
||||
#: pretix/plugins/sendmail/views.py
|
||||
msgid "Orders or attendees"
|
||||
msgstr "Bestellungen oder Teilnehmer"
|
||||
msgstr "Bestellungen oder Teilnehmer*innen"
|
||||
|
||||
#: pretix/plugins/sendmail/views.py
|
||||
msgid ""
|
||||
"Send an email to every customer, or to every person a ticket has been "
|
||||
"purchased for, or a combination of both."
|
||||
msgstr ""
|
||||
"Sende eine E-Mail an alle Ticketkäufer, alle Ticketinhaber oder "
|
||||
"Sende eine E-Mail an alle Ticketkäufer*innen, alle Ticketinhaber*innen oder "
|
||||
"eine Kombination aus beiden Gruppen."
|
||||
|
||||
#: pretix/plugins/sendmail/views.py
|
||||
@@ -33191,23 +33191,23 @@ msgstr "SEPA-Lastschrift"
|
||||
|
||||
#: pretix/plugins/stripe/payment.py
|
||||
msgid "Account Holder Name"
|
||||
msgstr "Kontoinhaber"
|
||||
msgstr "Kontoinhaber*in"
|
||||
|
||||
#: pretix/plugins/stripe/payment.py
|
||||
msgid "Account Holder Street"
|
||||
msgstr "Straße (Kontoinhaber)"
|
||||
msgstr "Straße (Kontoinhaber*in)"
|
||||
|
||||
#: pretix/plugins/stripe/payment.py
|
||||
msgid "Account Holder Postal Code"
|
||||
msgstr "PLZ (Kontoinhaber)"
|
||||
msgstr "PLZ (Kontoinhaber*in)"
|
||||
|
||||
#: pretix/plugins/stripe/payment.py
|
||||
msgid "Account Holder City"
|
||||
msgstr "Stadt (Kontoinhaber)"
|
||||
msgstr "Stadt (Kontoinhaber*in)"
|
||||
|
||||
#: pretix/plugins/stripe/payment.py
|
||||
msgid "Account Holder Country"
|
||||
msgstr "Land (Kontoinhaber)"
|
||||
msgstr "Land (Kontoinhaber*in)"
|
||||
|
||||
#: pretix/plugins/stripe/payment.py
|
||||
msgid "Affirm via Stripe"
|
||||
@@ -33645,7 +33645,7 @@ msgstr "MOTO"
|
||||
|
||||
#: pretix/plugins/stripe/templates/pretixplugins/stripe/control.html
|
||||
msgid "Payer name"
|
||||
msgstr "Name des Zahlers"
|
||||
msgstr "Name des Zahlenden"
|
||||
|
||||
#: pretix/plugins/stripe/templates/pretixplugins/stripe/control.html
|
||||
msgid "Payment receipt"
|
||||
@@ -34933,7 +34933,7 @@ msgstr[1] "Das Ticket wurde %(count)s-mal eingelöst."
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/fragment_cart.html
|
||||
msgid "No attendee name provided"
|
||||
msgstr "Name des Teilnehmenrs nicht angegeben"
|
||||
msgstr "Name der teilnehmenden Person nicht angegeben"
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/fragment_cart.html
|
||||
msgid "The image you previously uploaded"
|
||||
@@ -36708,12 +36708,12 @@ msgstr "Möchtest das folgende Profil wirklich aus deinem Kundenkonto löschen?"
|
||||
#: pretix/presale/templates/pretixpresale/organizers/customer_profiles.html
|
||||
#: pretix/presale/views/customer.py
|
||||
msgid "Attendee profiles"
|
||||
msgstr "Teilnehmer-Adresse"
|
||||
msgstr "Teilnehmer*innen-Adresse"
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/organizers/customer_profiles.html
|
||||
msgid "You don’t have any attendee profiles in your account yet."
|
||||
msgstr ""
|
||||
"In deinem Kundenkonto sind noch keine Teilnehmer-Profile gespeichert."
|
||||
"In deinem Kundenkonto sind noch keine Teilnehmer*innen-Profile gespeichert."
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/organizers/customer_registration.html
|
||||
msgid "Registration"
|
||||
@@ -38413,7 +38413,7 @@ msgstr "Kosovo"
|
||||
#~ "This plugin allows you to generate badges or name tags for your attendees."
|
||||
#~ msgstr ""
|
||||
#~ "Diese Erweiterung erlaubt, Namensschilder oder Badges für die "
|
||||
#~ "Teilnehmer zu erstellen."
|
||||
#~ "Teilnehmer*innen zu erstellen."
|
||||
|
||||
#~ msgid "This plugin allows you to receive payments via PayPal"
|
||||
#~ msgstr "Dieses Plugin erlaubt, Zahlungen über PayPal anzunehmen"
|
||||
@@ -38991,7 +38991,7 @@ msgstr "Kosovo"
|
||||
#~ msgstr "Biete Ticket-Download bereits vor Bezahlung einer Bestellung an"
|
||||
|
||||
#~ msgid "Attendee names"
|
||||
#~ msgstr "Teilnehmername"
|
||||
#~ msgstr "Teilnehmer*innennamen"
|
||||
|
||||
#~ msgid "Enable output"
|
||||
#~ msgstr "Aktivieren"
|
||||
@@ -39268,7 +39268,7 @@ msgstr "Kosovo"
|
||||
#~ "If checked, users can cancel orders by themselves as long as they are not "
|
||||
#~ "yet paid."
|
||||
#~ msgstr ""
|
||||
#~ "Wenn diese Option aktiviert ist, können Teilnehmer selbstständig "
|
||||
#~ "Wenn diese Option aktiviert ist, können Teilnehmer*innen selbstständig "
|
||||
#~ "Bestellungen stornieren solange sie nicht bezahlt wurden."
|
||||
|
||||
#~ msgid "Sales overview"
|
||||
|
||||
@@ -153,11 +153,11 @@ msgstr "Bezahlte Bestellungen"
|
||||
|
||||
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js
|
||||
msgid "Attendees (ordered)"
|
||||
msgstr "Teilnehmer (bestellt)"
|
||||
msgstr "Teilnehmende (bestellt)"
|
||||
|
||||
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js
|
||||
msgid "Attendees (paid)"
|
||||
msgstr "Teilnehmer (bezahlt)"
|
||||
msgstr "Teilnehmende (bezahlt)"
|
||||
|
||||
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js
|
||||
msgid "Total revenue"
|
||||
|
||||
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-07-30 13:52+0000\n"
|
||||
"PO-Revision-Date: 2026-07-23 15:35+0000\n"
|
||||
"PO-Revision-Date: 2026-08-01 21:00+0000\n"
|
||||
"Last-Translator: szurofkamarciidfbe08444ef04788 <szurofkamarcii@gmail.com>\n"
|
||||
"Language-Team: Hungarian <https://translate.pretix.eu/projects/pretix/pretix/"
|
||||
"hu/>\n"
|
||||
@@ -1377,10 +1377,8 @@ msgid "Membership type"
|
||||
msgstr "Tagságtípus"
|
||||
|
||||
#: pretix/base/exporters/customers.py
|
||||
#, fuzzy
|
||||
#| msgid "Purchase time"
|
||||
msgid "Purchase ticket"
|
||||
msgstr "Vásárlás időpontja"
|
||||
msgstr "Jegy vásárlása"
|
||||
|
||||
#: pretix/base/exporters/customers.py pretix/base/exporters/orderlist.py
|
||||
#: pretix/base/exporters/waitinglist.py pretix/base/forms/questions.py
|
||||
@@ -1397,10 +1395,8 @@ msgid "Start date"
|
||||
msgstr "Kezdő dátum"
|
||||
|
||||
#: pretix/base/exporters/customers.py
|
||||
#, fuzzy
|
||||
#| msgid "Start time from"
|
||||
msgid "Start time"
|
||||
msgstr "Kezdési időpont ettől"
|
||||
msgstr "Kezdési időpont"
|
||||
|
||||
#: pretix/base/exporters/customers.py pretix/base/exporters/orderlist.py
|
||||
#: pretix/base/exporters/waitinglist.py pretix/base/models/memberships.py
|
||||
@@ -1413,10 +1409,8 @@ msgid "End date"
|
||||
msgstr "Záró dátum"
|
||||
|
||||
#: pretix/base/exporters/customers.py
|
||||
#, fuzzy
|
||||
#| msgid "End: %(time)s"
|
||||
msgid "End time"
|
||||
msgstr "Vége: %(time)s"
|
||||
msgstr "Záró időpont"
|
||||
|
||||
#: pretix/base/exporters/dekodi.py pretix/base/exporters/invoices.py
|
||||
msgctxt "export_category"
|
||||
@@ -4641,10 +4635,6 @@ msgid "This event is remote or partially remote."
|
||||
msgstr "Ez az rendezvény távoli vagy részben távoli."
|
||||
|
||||
#: pretix/base/models/event.py
|
||||
#, fuzzy
|
||||
#| msgid ""
|
||||
#| "This will be used to let users know if the event is in a different "
|
||||
#| "timezone and let’s us calculate users’ local times."
|
||||
msgid ""
|
||||
"This will be used to let users know if the event is in a different timezone, "
|
||||
"and to let us calculate the local time of a user."
|
||||
@@ -7578,15 +7568,11 @@ msgid "This gift card was used in the meantime. Please try again."
|
||||
msgstr "Ezt az ajándékkártyát időközben felhasználták. Kérjük, próbálja újra."
|
||||
|
||||
#: pretix/base/payment.py
|
||||
#, fuzzy
|
||||
#| msgid ""
|
||||
#| "This payment provider does not exist or the respective plugin is disabled."
|
||||
msgid ""
|
||||
"This payment provider exists for historical purposes only and is no longer "
|
||||
"usable."
|
||||
msgstr ""
|
||||
"Ez a fizetési szolgáltató nem létezik, vagy a hozzá tartozó bővítmény le van "
|
||||
"tiltva."
|
||||
"Ez a fizetési szolgáltató egy korábbi funkció volt, már nem használható."
|
||||
|
||||
#: pretix/base/pdf.py
|
||||
msgid "Ticket code (barcode content)"
|
||||
|
||||
@@ -8,8 +8,8 @@ msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-07-30 13:52+0000\n"
|
||||
"PO-Revision-Date: 2026-05-12 04:00+0000\n"
|
||||
"Last-Translator: Stefano Campus <stefano.campus@regione.piemonte.it>\n"
|
||||
"PO-Revision-Date: 2026-08-03 23:00+0000\n"
|
||||
"Last-Translator: \"Luca Sorace \\\"Stranck\\\"\" <strdjn@gmail.com>\n"
|
||||
"Language-Team: Italian <https://translate.pretix.eu/projects/pretix/pretix/"
|
||||
"it/>\n"
|
||||
"Language: it\n"
|
||||
@@ -17,7 +17,7 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||
"X-Generator: Weblate 5.17.1\n"
|
||||
"X-Generator: Weblate 2026.7.1\n"
|
||||
|
||||
#: pretix/_base_settings.py
|
||||
msgid "English"
|
||||
@@ -149,7 +149,7 @@ msgstr "Spagnolo (America Latina)"
|
||||
|
||||
#: pretix/_base_settings.py
|
||||
msgid "Thai"
|
||||
msgstr ""
|
||||
msgstr "Tailandese"
|
||||
|
||||
#: pretix/_base_settings.py
|
||||
msgid "Turkish"
|
||||
@@ -413,7 +413,7 @@ msgstr ""
|
||||
#: pretix/api/serializers/organizer.py pretix/control/views/organizer.py
|
||||
#, python-format
|
||||
msgid "You've been invited to join %(organizer)s"
|
||||
msgstr ""
|
||||
msgstr "Sei stato invitato ad entrare in %(organizer)s"
|
||||
|
||||
#: pretix/api/serializers/organizer.py pretix/control/views/organizer.py
|
||||
msgid "This user already has been invited for this team."
|
||||
@@ -605,6 +605,9 @@ msgid ""
|
||||
"This includes related events like creation, deletion, opening or closing of "
|
||||
"quotas. No webhook is sent for changes to the resulting availability."
|
||||
msgstr ""
|
||||
"Questo include gli eventi relativi quali la creazione, cancellazione, "
|
||||
"apertura o chiusura delle quote. Nessun webhook verrà inviato per modifiche "
|
||||
"alla disponibilità."
|
||||
|
||||
#: pretix/api/webhooks.py
|
||||
msgid "Shop taken live"
|
||||
@@ -1234,8 +1237,9 @@ msgstr "Indirizzi Email (file di testo)"
|
||||
#: pretix/control/templates/pretixcontrol/organizers/customer.html
|
||||
#: pretix/presale/templates/pretixpresale/organizers/customer_memberships.html
|
||||
#: pretix/presale/views/customer.py
|
||||
#, fuzzy
|
||||
msgid "Memberships"
|
||||
msgstr ""
|
||||
msgstr "Membership"
|
||||
|
||||
#: pretix/base/exporters/customers.py pretix/base/models/customers.py
|
||||
#: pretix/control/templates/pretixcontrol/organizers/customer.html
|
||||
@@ -3030,11 +3034,12 @@ msgid ""
|
||||
"The field \"%(label)s\" may not contain special characters such as "
|
||||
"\"%(chars)s\"."
|
||||
msgstr ""
|
||||
"Il campo \"%(label)s\" non può contenere i caratteri speciali \"%(chars)s\"."
|
||||
|
||||
#: pretix/base/forms/questions.py
|
||||
#, python-format
|
||||
msgid "The field \"%(label)s\" may not contain an URL (%(url)s)."
|
||||
msgstr ""
|
||||
msgstr "Il campo \"%(label)s\" non può contenere un URL (%(url)s)."
|
||||
|
||||
#: pretix/base/forms/questions.py
|
||||
msgctxt "phonenumber"
|
||||
@@ -4272,8 +4277,9 @@ msgid "Ticket type not allowed here"
|
||||
msgstr "Biglietto non consentito qui"
|
||||
|
||||
#: pretix/base/models/checkin.py
|
||||
#, fuzzy
|
||||
msgid "Ticket code is ambiguous on list"
|
||||
msgstr "Codice biglietto ambiguo nella lista"
|
||||
msgstr "Il codice del biglietto è ambiguo sulla lista"
|
||||
|
||||
#: pretix/base/models/checkin.py
|
||||
msgid "Server error"
|
||||
@@ -6167,14 +6173,15 @@ msgid "bounced"
|
||||
msgstr "rimbalzato"
|
||||
|
||||
#: pretix/base/models/media.py
|
||||
#, fuzzy
|
||||
msgctxt "reusable_medium"
|
||||
msgid "Claim token"
|
||||
msgstr ""
|
||||
msgstr "Applica token"
|
||||
|
||||
#: pretix/base/models/media.py
|
||||
msgctxt "reusable_medium"
|
||||
msgid "Label"
|
||||
msgstr ""
|
||||
msgstr "Etichetta"
|
||||
|
||||
#: pretix/base/models/media.py
|
||||
#, fuzzy
|
||||
@@ -6188,6 +6195,9 @@ msgid ""
|
||||
"validity. If multiple tickets are valid at once, this will lead to failed "
|
||||
"check-ins."
|
||||
msgstr ""
|
||||
"Se colleghi più di un biglietto, assicurati che la loro validità non si "
|
||||
"sovrapponga. Se più biglietti sono validi contemporaneamente i check-in "
|
||||
"andranno in errore."
|
||||
|
||||
#: pretix/base/models/memberships.py
|
||||
#: pretix/presale/templates/pretixpresale/organizers/customer_memberships.html
|
||||
@@ -7676,6 +7686,8 @@ msgid ""
|
||||
"This payment provider exists for historical purposes only and is no longer "
|
||||
"usable."
|
||||
msgstr ""
|
||||
"Questo provider di pagamenti esiste per ragioni storiche e non ne è più "
|
||||
"possibile l'utilizzo."
|
||||
|
||||
#: pretix/base/pdf.py
|
||||
msgid "Ticket code (barcode content)"
|
||||
@@ -7843,7 +7855,7 @@ msgstr "Giorno di inizio evento"
|
||||
#: pretix/base/pdf.py pretix/base/services/checkin.py
|
||||
#: pretix/control/forms/filter.py
|
||||
msgid "Friday"
|
||||
msgstr "venerdì"
|
||||
msgstr "Venerdì"
|
||||
|
||||
#: pretix/base/pdf.py
|
||||
msgid "Event end date and time"
|
||||
@@ -8387,7 +8399,7 @@ msgstr "Evento annullato"
|
||||
|
||||
#: pretix/base/services/cancelevent.py
|
||||
msgid "Confirm event cancellation and bulk refund"
|
||||
msgstr ""
|
||||
msgstr "Conferma la cancellazione dell'evento e i rimborsi di tutti gli ordini"
|
||||
|
||||
#: pretix/base/services/cart.py pretix/base/services/modelimport.py
|
||||
#: pretix/base/services/orders.py
|
||||
@@ -9333,6 +9345,8 @@ msgstr "Non è possibile creare un buono senza un codice."
|
||||
msgid ""
|
||||
"Voucher codes must be unique. Code \"{code}\" already exists in this import."
|
||||
msgstr ""
|
||||
"Il codice del voucher deve essere univoco. \"{code}\" esiste già in questo "
|
||||
"import."
|
||||
|
||||
#: pretix/base/services/modelimport.py
|
||||
#, python-brace-format
|
||||
@@ -9341,13 +9355,19 @@ msgid ""
|
||||
msgid_plural ""
|
||||
"Voucher codes must be unique. Import contains existing voucher codes {code}."
|
||||
msgstr[0] ""
|
||||
"Il codice del voucher deve essere univoco. \"{code}\" esiste già in questo "
|
||||
"import."
|
||||
msgstr[1] ""
|
||||
"Il codice dei voucher deve essere univoco. \"{code}\" esiste già in questo "
|
||||
"import."
|
||||
|
||||
#: pretix/base/services/modelimport.py
|
||||
msgid ""
|
||||
"Vouchers could not be imported, probably due to a voucher code already being "
|
||||
"in use."
|
||||
msgstr ""
|
||||
"Non è stato possibile importare i voucher, probabilmente perché il codice di "
|
||||
"un voucher è già in uso."
|
||||
|
||||
#: pretix/base/services/orders.py
|
||||
msgid ""
|
||||
@@ -10267,6 +10287,10 @@ msgid ""
|
||||
"ID in all countries. VAT ID will be required for all business addresses in "
|
||||
"the selected countries."
|
||||
msgstr ""
|
||||
"La partita IVA è opzionale di default, perché non tutte le aziende la "
|
||||
"posseggono in tutte le nazioni. Il codice della partita IVA sarà "
|
||||
"obbligatorio per tutti gli indirizzi aziendali locati nelle nazioni "
|
||||
"selezionate."
|
||||
|
||||
#: pretix/base/settings.py
|
||||
msgid "Invoice address explanation"
|
||||
@@ -11316,50 +11340,60 @@ msgstr ""
|
||||
|
||||
#: pretix/base/settings.py
|
||||
msgid "Customers can change the variation of the products they purchased"
|
||||
msgstr ""
|
||||
msgstr "I clienti potranno cambiare la variation dei prodotti già acquistati"
|
||||
|
||||
#: pretix/base/settings.py
|
||||
#, fuzzy
|
||||
msgid "Customers can change their selected add-on products"
|
||||
msgstr "I clienti non possono più modificare i loro ordini"
|
||||
msgstr "I clienti non potranno più modificare i gli add-on selezionati"
|
||||
|
||||
#: pretix/base/settings.py
|
||||
msgid ""
|
||||
"Only allow changes if the resulting price is higher or equal than the "
|
||||
"previous price."
|
||||
msgstr ""
|
||||
"Permetti le modifiche solamente se il prezzo finale è maggiore o uguale al "
|
||||
"prezzo precedente."
|
||||
|
||||
#: pretix/base/settings.py
|
||||
msgid ""
|
||||
"Only allow changes if the resulting price is higher than the previous price."
|
||||
msgstr ""
|
||||
"Permetti le modifiche solamente se il prezzo finale è maggiore del prezzo "
|
||||
"precedente."
|
||||
|
||||
#: pretix/base/settings.py
|
||||
msgid ""
|
||||
"Only allow changes if the resulting price is equal to the previous price."
|
||||
msgstr ""
|
||||
"Permetti le modifiche solamente se il prezzo finale è uguale al prezzo "
|
||||
"precedente."
|
||||
|
||||
#: pretix/base/settings.py
|
||||
msgid ""
|
||||
"Allow changes regardless of price, as long as no refund is required (i.e. "
|
||||
"the resulting price is not lower than what has already been paid)."
|
||||
msgstr ""
|
||||
"Permetti le modifiche a prescindere del prezzo, a meno che non sia "
|
||||
"necessario un rimborso (Il prezzo finale non è inferiore a quanto il cliente "
|
||||
"ha già pagato)."
|
||||
|
||||
#: pretix/base/settings.py
|
||||
msgid "Allow changes regardless of price, even if this results in a refund."
|
||||
msgstr ""
|
||||
"Permetti le modifiche a prescindere del prezzo, anche se portano ad un "
|
||||
"rimborso."
|
||||
|
||||
#: pretix/base/settings.py
|
||||
msgid "Requirement for changed prices"
|
||||
msgstr ""
|
||||
msgstr "Requisiti per il cambiamento di prezzi"
|
||||
|
||||
#: pretix/base/settings.py
|
||||
msgid "Do not allow changes after"
|
||||
msgstr ""
|
||||
msgstr "Non permettere modifiche dopo il"
|
||||
|
||||
#: pretix/base/settings.py
|
||||
msgid "Allow change even though the ticket has already been checked in"
|
||||
msgstr ""
|
||||
msgstr "Permetti modifiche anche se il biglietto ha già effettuato il check-in"
|
||||
|
||||
#: pretix/base/settings.py
|
||||
msgid ""
|
||||
@@ -11369,10 +11403,16 @@ msgid ""
|
||||
"in individually. Use with care, and preferably only in combination with a "
|
||||
"limitation on price changes above."
|
||||
msgstr ""
|
||||
"Di default, le modifiche degli ordini sono disabilitate dopo che è stato "
|
||||
"effettuato il check-in con qualsiasi biglietto contenuto nell'ordine. "
|
||||
"Abilitando questa opzione, disabiliterai questo controllo. Non sarà comunque "
|
||||
"possibile rimuovere un add-on con cui è già stato effettuato un check-in. "
|
||||
"Usa questa opzione con attenzione e, preferibilmente, solamente in "
|
||||
"combinazione con una delle limitazioni sul prezzo elencate sopra."
|
||||
|
||||
#: pretix/base/settings.py
|
||||
msgid "Allow individual attendees to change their ticket"
|
||||
msgstr ""
|
||||
msgstr "Permetti ai singoli partecipanti di modificare il proprio biglietto"
|
||||
|
||||
#: pretix/base/settings.py
|
||||
msgid ""
|
||||
@@ -11382,15 +11422,20 @@ msgid ""
|
||||
"total price of the order. Such changes can always only be made by the main "
|
||||
"customer."
|
||||
msgstr ""
|
||||
"Di default, solo la persona che ha effettuato l'ordine di questo biglietto "
|
||||
"può eseguire modifiche. Selezionando questa opzione, anche i singoli "
|
||||
"partecipanti potranno fare cambiamenti. In ogni caso, i singoli partecipanti "
|
||||
"non potranno effettuare operazioni che cambiano il costo dell'ordine. Queste "
|
||||
"operazioni sono possibili solamente per chi originariamente ha creato "
|
||||
"l'ordine."
|
||||
|
||||
#: pretix/base/settings.py
|
||||
msgid "Customers can cancel their unpaid orders"
|
||||
msgstr ""
|
||||
msgstr "I clienti possono annullare i loro ordini non pagati"
|
||||
|
||||
#: pretix/base/settings.py
|
||||
#, fuzzy
|
||||
msgid "Charge a fixed cancellation fee"
|
||||
msgstr "Cancellazione di"
|
||||
msgstr "Addebita una quota di cancellazione fissa"
|
||||
|
||||
#: pretix/base/settings.py
|
||||
msgid ""
|
||||
@@ -11398,50 +11443,58 @@ msgid ""
|
||||
"never charged. Note that it will be your responsibility to claim the "
|
||||
"cancellation fee from the user."
|
||||
msgstr ""
|
||||
"Interessa solamente gli ordini con pagamenti in attesa, la quota di "
|
||||
"cancellazione non viene mai addebitata sugli ordini gratuiti. Nota: è tua "
|
||||
"responsabilità collezionare la quota di cancellazione dall'utente."
|
||||
|
||||
#: pretix/base/settings.py
|
||||
msgid "Charge payment, shipping and service fees"
|
||||
msgstr ""
|
||||
msgstr "Addebita la quota di spedizione, pagamento e servizio"
|
||||
|
||||
#: pretix/base/settings.py
|
||||
msgid "Charge a percentual cancellation fee"
|
||||
msgstr ""
|
||||
msgstr "Addebita una quota di quota di cancellazione in percentuale"
|
||||
|
||||
#: pretix/base/settings.py
|
||||
msgid "Do not allow cancellations after"
|
||||
msgstr ""
|
||||
msgstr "Non permettere di annullare gli ordine dopo il"
|
||||
|
||||
#: pretix/base/settings.py
|
||||
msgid "Customers can cancel their paid orders"
|
||||
msgstr ""
|
||||
msgstr "I clienti possono annullare i loro ordini già pagati"
|
||||
|
||||
#: pretix/base/settings.py
|
||||
msgid ""
|
||||
"Paid money will be automatically paid back if the payment method allows it. "
|
||||
"Otherwise, a manual refund will be created for you to process manually."
|
||||
msgstr ""
|
||||
"Il denaro già incassato sarà automaticamente restituito se il metodo di "
|
||||
"pagamento lo permette. Altrimenti, un rimborso manuale verrà creato così che "
|
||||
"tu possa processarlo manualmente."
|
||||
|
||||
#: pretix/base/settings.py pretix/control/forms/orders.py
|
||||
msgid "Keep a fixed cancellation fee"
|
||||
msgstr ""
|
||||
msgstr "Trattieni una quota di cancellazione fissa"
|
||||
|
||||
#: pretix/base/settings.py
|
||||
msgid "Keep payment, shipping and service fees"
|
||||
msgstr ""
|
||||
msgstr "Trattieni la quota di spedizione, pagamento e servizio"
|
||||
|
||||
#: pretix/base/settings.py pretix/control/forms/orders.py
|
||||
msgid "Keep a percentual cancellation fee"
|
||||
msgstr ""
|
||||
msgstr "Trattieni una quota di cancellazione in percentuale"
|
||||
|
||||
#: pretix/base/settings.py
|
||||
msgid "Allow customers to voluntarily choose a lower refund"
|
||||
msgstr ""
|
||||
msgstr "Permetti si clienti di scegliere volontariamente un rimborso minore"
|
||||
|
||||
#: pretix/base/settings.py
|
||||
msgid ""
|
||||
"With this option enabled, your customers can choose to get a smaller refund "
|
||||
"to support you."
|
||||
msgstr ""
|
||||
"Con questa opzione attiva, i tuoi clienti potranno scegliere di ottenere un "
|
||||
"rimborso minore al dovuto, per effettuare una donazione nei vostri confronti."
|
||||
|
||||
#: pretix/base/settings.py
|
||||
msgid ""
|
||||
@@ -20421,7 +20474,7 @@ msgstr "Abilita webhook"
|
||||
#: pretix/control/templates/pretixcontrol/organizers/device_logs.html
|
||||
#: pretix/control/templates/pretixcontrol/organizers/logs.html
|
||||
msgid "No results"
|
||||
msgstr ""
|
||||
msgstr "Nessun risultato"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/event/mail.html
|
||||
#: pretix/control/templates/pretixcontrol/organizers/mail.html
|
||||
@@ -20462,7 +20515,7 @@ msgstr ""
|
||||
#: pretix/control/templates/pretixcontrol/vouchers/bulk.html
|
||||
#: pretix/plugins/sendmail/templates/pretixplugins/sendmail/send_form.html
|
||||
msgid "Edit"
|
||||
msgstr ""
|
||||
msgstr "Modifica"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/event/mail.html
|
||||
#, fuzzy
|
||||
@@ -25479,7 +25532,7 @@ msgstr ""
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/pdf/index.html
|
||||
msgid "Text box"
|
||||
msgstr ""
|
||||
msgstr "Riquadro testo"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/pdf/index.html
|
||||
#, fuzzy
|
||||
@@ -25520,7 +25573,7 @@ msgstr ""
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/pdf/index.html
|
||||
msgid "Duplicate"
|
||||
msgstr ""
|
||||
msgstr "Duplicato"
|
||||
|
||||
#: pretix/control/templates/pretixcontrol/pdf/index.html
|
||||
msgid "Undo"
|
||||
@@ -27145,11 +27198,11 @@ msgstr ""
|
||||
|
||||
#: pretix/control/views/dashboards.py
|
||||
msgid "Attendees (ordered)"
|
||||
msgstr ""
|
||||
msgstr "Partecipanti (ordini effettuati)"
|
||||
|
||||
#: pretix/control/views/dashboards.py
|
||||
msgid "Attendees (paid)"
|
||||
msgstr ""
|
||||
msgstr "Partecipanti (ordini pagati)"
|
||||
|
||||
#: pretix/control/views/dashboards.py
|
||||
#, python-brace-format
|
||||
@@ -31650,11 +31703,11 @@ msgstr ""
|
||||
|
||||
#: pretix/plugins/stripe/payment.py
|
||||
msgid "Bancontact"
|
||||
msgstr ""
|
||||
msgstr "Bancontact"
|
||||
|
||||
#: pretix/plugins/stripe/payment.py
|
||||
msgid "SEPA Direct Debit"
|
||||
msgstr ""
|
||||
msgstr "Addebito diretto SEPA"
|
||||
|
||||
#: pretix/plugins/stripe/payment.py
|
||||
msgid ""
|
||||
@@ -31684,7 +31737,7 @@ msgstr ""
|
||||
|
||||
#: pretix/plugins/stripe/payment.py
|
||||
msgid "Przelewy24"
|
||||
msgstr ""
|
||||
msgstr "Przelewy24"
|
||||
|
||||
#: pretix/plugins/stripe/payment.py
|
||||
#, fuzzy
|
||||
@@ -31700,7 +31753,7 @@ msgstr ""
|
||||
|
||||
#: pretix/plugins/stripe/payment.py
|
||||
msgid "WeChat Pay"
|
||||
msgstr ""
|
||||
msgstr "WeChat Pay"
|
||||
|
||||
#: pretix/plugins/stripe/payment.py
|
||||
msgid "Swish"
|
||||
@@ -31853,7 +31906,7 @@ msgstr ""
|
||||
|
||||
#: pretix/plugins/stripe/payment.py
|
||||
msgid "giropay"
|
||||
msgstr ""
|
||||
msgstr "giropay"
|
||||
|
||||
#: pretix/plugins/stripe/payment.py
|
||||
msgid ""
|
||||
@@ -31877,7 +31930,7 @@ msgstr ""
|
||||
|
||||
#: pretix/plugins/stripe/payment.py
|
||||
msgid "iDEAL | Wero"
|
||||
msgstr ""
|
||||
msgstr "iDEAL | Wero"
|
||||
|
||||
#: pretix/plugins/stripe/payment.py
|
||||
msgid ""
|
||||
@@ -33621,9 +33674,8 @@ msgstr ""
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/fragment_cart.html
|
||||
#: pretix/presale/templates/pretixpresale/fragment_modals.html
|
||||
#, fuzzy
|
||||
msgid "Renew reservation"
|
||||
msgstr "Descrizione"
|
||||
msgstr "Rinnova scadenza"
|
||||
|
||||
#: pretix/presale/templates/pretixpresale/event/fragment_cart.html
|
||||
#, fuzzy
|
||||
|
||||
@@ -8,8 +8,8 @@ msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2026-07-06 15:52+0000\n"
|
||||
"PO-Revision-Date: 2026-03-25 14:14+0000\n"
|
||||
"Last-Translator: Pietro Isotti <isottipietro@gmail.com>\n"
|
||||
"PO-Revision-Date: 2026-08-02 22:00+0000\n"
|
||||
"Last-Translator: \"Luca Sorace \\\"Stranck\\\"\" <strdjn@gmail.com>\n"
|
||||
"Language-Team: Italian <https://translate.pretix.eu/projects/pretix/pretix-"
|
||||
"js/it/>\n"
|
||||
"Language: it\n"
|
||||
@@ -17,7 +17,7 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
||||
"X-Generator: Weblate 5.16.2\n"
|
||||
"X-Generator: Weblate 2026.7.1\n"
|
||||
|
||||
#: pretix/plugins/banktransfer/static/pretixplugins/banktransfer/ui.js
|
||||
msgid "Marked as paid"
|
||||
@@ -42,93 +42,91 @@ msgstr "Apple Pay"
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js
|
||||
msgid "Itaú"
|
||||
msgstr ""
|
||||
msgstr "Ita"
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js
|
||||
#, fuzzy
|
||||
msgid "PayPal Credit"
|
||||
msgstr "PayPal"
|
||||
msgstr "PayPal (Pagamento a rate)"
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js
|
||||
msgid "Credit Card"
|
||||
msgstr ""
|
||||
msgstr "Carta di credito"
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js
|
||||
msgid "PayPal Pay Later"
|
||||
msgstr ""
|
||||
msgstr "PayPal (Acquista ora, paga dopo)"
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js
|
||||
msgid "iDEAL | Wero"
|
||||
msgstr ""
|
||||
msgstr "iDEAL | Wero"
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js
|
||||
msgid "SEPA Direct Debit"
|
||||
msgstr ""
|
||||
msgstr "Addebito diretto SEPA"
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js
|
||||
msgid "Bancontact"
|
||||
msgstr ""
|
||||
msgstr "Bancontact"
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js
|
||||
msgid "giropay"
|
||||
msgstr ""
|
||||
msgstr "giropay"
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js
|
||||
msgid "SOFORT"
|
||||
msgstr ""
|
||||
msgstr "SOFORT"
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js
|
||||
#, fuzzy
|
||||
msgid "eps"
|
||||
msgstr "Si"
|
||||
msgstr "eps"
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js
|
||||
msgid "MyBank"
|
||||
msgstr ""
|
||||
msgstr "MyBank"
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js
|
||||
msgid "Przelewy24"
|
||||
msgstr ""
|
||||
msgstr "Przelewy24"
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js
|
||||
msgid "Verkkopankki"
|
||||
msgstr ""
|
||||
msgstr "Verkkopankki"
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js
|
||||
msgid "PayU"
|
||||
msgstr ""
|
||||
msgstr "PayU"
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js
|
||||
msgid "BLIK"
|
||||
msgstr ""
|
||||
msgstr "BLIK"
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js
|
||||
msgid "Trustly"
|
||||
msgstr ""
|
||||
msgstr "Trustly"
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js
|
||||
msgid "Zimpler"
|
||||
msgstr ""
|
||||
msgstr "Zimpler"
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js
|
||||
msgid "Maxima"
|
||||
msgstr ""
|
||||
msgstr "Maxima"
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js
|
||||
msgid "OXXO"
|
||||
msgstr ""
|
||||
msgstr "OXXO"
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js
|
||||
msgid "Boleto"
|
||||
msgstr ""
|
||||
msgstr "Boleto"
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js
|
||||
msgid "WeChat Pay"
|
||||
msgstr ""
|
||||
msgstr "WeChat Pay"
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js
|
||||
msgid "Mercado Pago"
|
||||
msgstr ""
|
||||
msgstr "Mercado Pago"
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/i18n.ts
|
||||
@@ -143,7 +141,7 @@ msgstr "Stiamo processando il tuo pagamento …"
|
||||
|
||||
#: pretix/plugins/paypal2/static/pretixplugins/paypal2/pretix-paypal.js
|
||||
msgid "Payment method unavailable"
|
||||
msgstr ""
|
||||
msgstr "Metodo di pagamento non disponibile"
|
||||
|
||||
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js
|
||||
msgid "Placed orders"
|
||||
@@ -155,11 +153,11 @@ msgstr "Ordini pagati"
|
||||
|
||||
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js
|
||||
msgid "Attendees (ordered)"
|
||||
msgstr ""
|
||||
msgstr "Partecipanti (ordini effettuati)"
|
||||
|
||||
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js
|
||||
msgid "Attendees (paid)"
|
||||
msgstr ""
|
||||
msgstr "Partecipanti (ordini pagati)"
|
||||
|
||||
#: pretix/plugins/statistics/static/pretixplugins/statistics/statistics.js
|
||||
msgid "Total revenue"
|
||||
@@ -239,11 +237,11 @@ msgstr "Eliminato"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/i18n.ts
|
||||
msgid "Confirmed"
|
||||
msgstr ""
|
||||
msgstr "Confermato"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/i18n.ts
|
||||
msgid "Approval pending"
|
||||
msgstr ""
|
||||
msgstr "In attesa di approvazione"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/i18n.ts
|
||||
msgid "Redeemed"
|
||||
@@ -310,12 +308,13 @@ msgid "Order canceled"
|
||||
msgstr "Ordine cancellato"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/i18n.ts
|
||||
#, fuzzy
|
||||
msgid "Ticket code is ambiguous on list"
|
||||
msgstr ""
|
||||
msgstr "Il codice del biglietto è ambiguo sulla lista"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/i18n.ts
|
||||
msgid "Order not approved"
|
||||
msgstr ""
|
||||
msgstr "Ordine non approvato"
|
||||
|
||||
#: pretix/plugins/webcheckin/static/pretixplugins/webcheckin/i18n.ts
|
||||
msgid "Checked-in Tickets"
|
||||
@@ -430,11 +429,11 @@ msgstr "Usa i tasti Ctrl-C per copiare!"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules/App.vue
|
||||
msgid "Edit"
|
||||
msgstr ""
|
||||
msgstr "Modifica"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules/App.vue
|
||||
msgid "Visualize"
|
||||
msgstr ""
|
||||
msgstr "Visualizza"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules/App.vue
|
||||
msgid ""
|
||||
@@ -442,10 +441,13 @@ msgid ""
|
||||
"or variations are not contained in any of your rule parts so people with "
|
||||
"these tickets will not get in:"
|
||||
msgstr ""
|
||||
"La tua regola filtra sempre per prodotto o variation, ma i seguenti prodotti "
|
||||
"o variation non sono presenti in nessuna parte delle tue regole. Questo "
|
||||
"impedirà alle persone con questi biglietti di poter accedere all'evento:"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules/App.vue
|
||||
msgid "Please double-check if this was intentional."
|
||||
msgstr ""
|
||||
msgstr "Per favore, controlla se è stato intenzionale."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules/constants.ts
|
||||
msgid "All of the conditions below (AND)"
|
||||
@@ -489,17 +491,17 @@ msgstr "minuti"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules/constants.ts
|
||||
msgid "Duplicate"
|
||||
msgstr ""
|
||||
msgstr "Duplicato"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules/constants.ts
|
||||
msgctxt "entry_status"
|
||||
msgid "present"
|
||||
msgstr ""
|
||||
msgstr "Presente"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules/constants.ts
|
||||
msgctxt "entry_status"
|
||||
msgid "absent"
|
||||
msgstr ""
|
||||
msgstr "Assente"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules/constants.ts
|
||||
msgid "is one of"
|
||||
@@ -515,7 +517,7 @@ msgstr "è dopo"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules/constants.ts
|
||||
msgid "="
|
||||
msgstr ""
|
||||
msgstr "="
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules/constants.ts
|
||||
msgid "Product"
|
||||
@@ -535,63 +537,55 @@ msgstr "Data e orario corrente"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules/constants.ts
|
||||
msgid "Current day of the week (1 = Monday, 7 = Sunday)"
|
||||
msgstr ""
|
||||
msgstr "Giorno corrente della settimana (1 = Lunedì, 7 = Domenica)"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules/constants.ts
|
||||
msgid "Current entry status"
|
||||
msgstr ""
|
||||
msgstr "Stato dell'ingresso corrente"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules/constants.ts
|
||||
msgid "Number of previous entries"
|
||||
msgstr "Numero di inserimenti precedenti"
|
||||
msgstr "Numero di precedenti ingressi"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules/constants.ts
|
||||
msgid "Number of previous entries since midnight"
|
||||
msgstr "Numero di inserimenti precedenti fino a mezzanotte"
|
||||
msgstr "Numero di precedenti ingressi fino a mezzanotte"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules/constants.ts
|
||||
#, fuzzy
|
||||
#| msgid "Number of previous entries"
|
||||
msgid "Number of previous entries since"
|
||||
msgstr "Numero di inserimenti precedenti"
|
||||
msgstr "Numero di precedenti ingressi dal"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules/constants.ts
|
||||
#, fuzzy
|
||||
#| msgid "Number of previous entries"
|
||||
msgid "Number of previous entries before"
|
||||
msgstr "Numero di inserimenti precedenti"
|
||||
msgstr "Numero di precedenti ingressi prima del"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules/constants.ts
|
||||
msgid "Number of days with a previous entry"
|
||||
msgstr "Nunmero di giorni con un inserimento precedente"
|
||||
msgstr "Numero di giorni con un precedente ingresso"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules/constants.ts
|
||||
#, fuzzy
|
||||
#| msgid "Number of days with a previous entry"
|
||||
msgid "Number of days with a previous entry since"
|
||||
msgstr "Nunmero di giorni con un inserimento precedente"
|
||||
msgstr "Numero di giorni con un precedente ingresso dal"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules/constants.ts
|
||||
#, fuzzy
|
||||
#| msgid "Number of days with a previous entry"
|
||||
msgid "Number of days with a previous entry before"
|
||||
msgstr "Nunmero di giorni con un inserimento precedente"
|
||||
msgstr "Numero di giorni con un precedente ingresso prima del"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules/constants.ts
|
||||
msgid "Minutes since last entry (-1 on first entry)"
|
||||
msgstr ""
|
||||
msgstr "Minuti dall'ultimo ingresso (-1 al primo ingresso)"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules/constants.ts
|
||||
msgid "Minutes since first entry (-1 on first entry)"
|
||||
msgstr ""
|
||||
msgstr "Minuti dal primo ingresso (-1 al primo ingresso)"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules/django-interop.ts
|
||||
msgid "Error: Product not found!"
|
||||
msgstr ""
|
||||
msgstr "Errore: Prodotto non trovato!"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/checkinrules/django-interop.ts
|
||||
msgid "Error: Variation not found!"
|
||||
msgstr ""
|
||||
msgstr "Errore: Variation non trovata!"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js
|
||||
msgid "Check-in QR"
|
||||
@@ -606,16 +600,12 @@ msgid "Group of objects"
|
||||
msgstr "Gruppo di oggetti"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js
|
||||
#, fuzzy
|
||||
#| msgid "Text object"
|
||||
msgid "Text object (deprecated)"
|
||||
msgstr "Oggetto testo"
|
||||
msgstr "Oggetto testo (deprecato)"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js
|
||||
#, fuzzy
|
||||
#| msgid "Text object"
|
||||
msgid "Text box"
|
||||
msgstr "Oggetto testo"
|
||||
msgstr "Riquadro testo"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/editor.js
|
||||
msgid "Barcode area"
|
||||
@@ -662,25 +652,24 @@ msgid "Unknown error."
|
||||
msgstr "Errore sconosciuto."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js
|
||||
#, fuzzy
|
||||
#| msgid "Your color has great contrast and is very easy to read!"
|
||||
msgid "Your color has great contrast and will provide excellent accessibility."
|
||||
msgstr "Il colore scelto ha un ottimo contrasto ed è molto leggibile!"
|
||||
msgstr "Il colore scelto ha un ottimo contrasto ed è molto leggibile."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js
|
||||
#, fuzzy
|
||||
#| msgid "Your color has decent contrast and is probably good-enough to read!"
|
||||
msgid ""
|
||||
"Your color has decent contrast and is sufficient for minimum accessibility "
|
||||
"requirements."
|
||||
msgstr ""
|
||||
"Il colore scelto ha un buon contrasto e probabilmente è abbastanza leggibile!"
|
||||
"Il colore scelto ha un buon contrasto ed è sufficiente per i requisiti di "
|
||||
"accessibilità."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js
|
||||
msgid ""
|
||||
"Your color has insufficient contrast to white. Accessibility of your site "
|
||||
"will be impacted."
|
||||
msgstr ""
|
||||
"Il colore scelto ha un contrasto insufficiente rispetto al bianco. "
|
||||
"L'accessibilità nel tuo sito sarà ridotta."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js
|
||||
msgid "Search query"
|
||||
@@ -700,11 +689,11 @@ msgstr "Solo i selezionati"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js
|
||||
msgid "Enter page number between 1 and %(max)s."
|
||||
msgstr ""
|
||||
msgstr "Inserisci il numero di pagina tra 1 e %(max)s."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js
|
||||
msgid "Invalid page number."
|
||||
msgstr ""
|
||||
msgstr "Numero di pagina invalido."
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/main.js
|
||||
msgid "Use a different name internally"
|
||||
@@ -723,10 +712,8 @@ msgid "Calculating default price…"
|
||||
msgstr "Calcolando il prezzo di default…"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/plugins.js
|
||||
#, fuzzy
|
||||
#| msgid "Search results"
|
||||
msgid "No results"
|
||||
msgstr "Risultati ricerca"
|
||||
msgstr "Nessun risultato"
|
||||
|
||||
#: pretix/static/pretixcontrol/js/ui/question.js
|
||||
msgid "Others"
|
||||
@@ -756,39 +743,33 @@ msgstr "Carrello scaduto"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/cart.js
|
||||
msgid "Your cart is about to expire."
|
||||
msgstr ""
|
||||
msgstr "La selezione nel tuo carrello sta per scadere."
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/cart.js
|
||||
msgid "The items in your cart are reserved for you for one minute."
|
||||
msgid_plural "The items in your cart are reserved for you for {num} minutes."
|
||||
msgstr[0] "Gli elementi nel tuo carrello sono riservati per 1 minuto."
|
||||
msgstr[0] "Gli elementi nel tuo carrello sono riservati per un minuto."
|
||||
msgstr[1] "Gli elementi nel tuo carrello sono riservati per {num} minuti."
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/cart.js
|
||||
#, fuzzy
|
||||
#| msgid "Cart expired"
|
||||
msgid "Your cart has expired."
|
||||
msgstr "Carrello scaduto"
|
||||
msgstr "Il tuo carrello è scaduto."
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/cart.js
|
||||
#, fuzzy
|
||||
#| msgid ""
|
||||
#| "The items in your cart are no longer reserved for you. You can still "
|
||||
#| "complete your order as long as they’re available."
|
||||
msgid ""
|
||||
"The items in your cart are no longer reserved for you. You can still "
|
||||
"complete your order as long as they're available."
|
||||
msgstr ""
|
||||
"Gli articoli nel tuo carrello non sono più riservati per te. Puoi ancora "
|
||||
"Gli articoli nel tuo carrello non sono più riservati a te. Puoi ancora "
|
||||
"completare il tuo ordine finché sono disponibili."
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/cart.js
|
||||
msgid "Do you want to renew the reservation period?"
|
||||
msgstr ""
|
||||
msgstr "Vuoi rinnovare la scadenza del tuo carrello?"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/cart.js
|
||||
msgid "Renew reservation"
|
||||
msgstr ""
|
||||
msgstr "Rinnova scadenza"
|
||||
|
||||
#: pretix/static/pretixpresale/js/ui/main.js
|
||||
msgid "The organizer keeps %(currency)s %(amount)s"
|
||||
@@ -807,69 +788,64 @@ msgid "Your local time:"
|
||||
msgstr "Ora locale:"
|
||||
|
||||
#: pretix/static/pretixpresale/js/walletdetection.js
|
||||
#, fuzzy
|
||||
#| msgid "Apple Pay"
|
||||
msgid "Google Pay"
|
||||
msgstr "Apple Pay"
|
||||
msgstr "Google Pay"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js
|
||||
#: pretix/static/pretixpresale/widget/src/i18n.ts
|
||||
msgctxt "widget"
|
||||
msgid "Quantity"
|
||||
msgstr ""
|
||||
msgstr "Quantità"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js
|
||||
#: pretix/static/pretixpresale/widget/src/i18n.ts
|
||||
msgctxt "widget"
|
||||
msgid "Decrease quantity"
|
||||
msgstr ""
|
||||
msgstr "Diminuisci quantità"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js
|
||||
#: pretix/static/pretixpresale/widget/src/i18n.ts
|
||||
msgctxt "widget"
|
||||
msgid "Increase quantity"
|
||||
msgstr ""
|
||||
msgstr "Aumenta quantità"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js
|
||||
#: pretix/static/pretixpresale/widget/src/i18n.ts
|
||||
msgctxt "widget"
|
||||
msgid "Filter events by"
|
||||
msgstr ""
|
||||
msgstr "Filtra eventi per"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js
|
||||
#: pretix/static/pretixpresale/widget/src/i18n.ts
|
||||
msgctxt "widget"
|
||||
msgid "Filter"
|
||||
msgstr ""
|
||||
msgstr "Filtra"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js
|
||||
#: pretix/static/pretixpresale/widget/src/i18n.ts
|
||||
msgctxt "widget"
|
||||
msgid "Price"
|
||||
msgstr ""
|
||||
msgstr "Prezzo"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js
|
||||
#: pretix/static/pretixpresale/widget/src/i18n.ts
|
||||
#, javascript-format
|
||||
msgctxt "widget"
|
||||
msgid "Original price: %s"
|
||||
msgstr ""
|
||||
msgstr "Prezzo originale: %s"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js
|
||||
#: pretix/static/pretixpresale/widget/src/i18n.ts
|
||||
#, javascript-format
|
||||
msgctxt "widget"
|
||||
msgid "New price: %s"
|
||||
msgstr ""
|
||||
msgstr "Nuovo prezzo: %s"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js
|
||||
#: pretix/static/pretixpresale/widget/src/i18n.ts
|
||||
#, fuzzy
|
||||
#| msgctxt "widget"
|
||||
#| msgid "Select %s"
|
||||
msgctxt "widget"
|
||||
msgid "Select"
|
||||
msgstr "Seleziona %s"
|
||||
msgstr "Seleziona"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js
|
||||
#: pretix/static/pretixpresale/widget/src/i18n.ts
|
||||
@@ -926,7 +902,7 @@ msgstr "da %(currency)s %(price)s"
|
||||
#, javascript-format
|
||||
msgctxt "widget"
|
||||
msgid "Image of %s"
|
||||
msgstr ""
|
||||
msgstr "Immagine di %s"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js
|
||||
#: pretix/static/pretixpresale/widget/src/i18n.ts
|
||||
@@ -967,27 +943,21 @@ msgstr "Disponibile solo con voucher"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js
|
||||
#: pretix/static/pretixpresale/widget/src/i18n.ts
|
||||
#, fuzzy
|
||||
#| msgctxt "widget"
|
||||
#| msgid "currently available: %s"
|
||||
msgctxt "widget"
|
||||
msgid "Not yet available"
|
||||
msgstr "attualmente disponibile: %s"
|
||||
msgstr "Ancora non disponibile"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js
|
||||
#: pretix/static/pretixpresale/widget/src/i18n.ts
|
||||
msgctxt "widget"
|
||||
msgid "Not available anymore"
|
||||
msgstr ""
|
||||
msgstr "Non più disponibile"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js
|
||||
#: pretix/static/pretixpresale/widget/src/i18n.ts
|
||||
#, fuzzy
|
||||
#| msgctxt "widget"
|
||||
#| msgid "currently available: %s"
|
||||
msgctxt "widget"
|
||||
msgid "Currently not available"
|
||||
msgstr "attualmente disponibile: %s"
|
||||
msgstr "Attualmente non disponibile"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js
|
||||
#: pretix/static/pretixpresale/widget/src/i18n.ts
|
||||
@@ -1095,18 +1065,17 @@ msgstr "Chiudi"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js
|
||||
#: pretix/static/pretixpresale/widget/src/i18n.ts
|
||||
#, fuzzy
|
||||
#| msgctxt "widget"
|
||||
#| msgid "Resume checkout"
|
||||
msgctxt "widget"
|
||||
msgid "Close checkout"
|
||||
msgstr "Ricarica checkout"
|
||||
msgstr "Finisci checkout"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js
|
||||
#: pretix/static/pretixpresale/widget/src/i18n.ts
|
||||
msgctxt "widget"
|
||||
msgid "You cannot cancel this operation. Please wait for loading to finish."
|
||||
msgstr ""
|
||||
"Non puoi annullare questa operazione. Per favore, aspetta che il caricamento "
|
||||
"finisca."
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js
|
||||
#: pretix/static/pretixpresale/widget/src/i18n.ts
|
||||
@@ -1116,21 +1085,15 @@ msgstr "Continua"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js
|
||||
#: pretix/static/pretixpresale/widget/src/i18n.ts
|
||||
#, fuzzy
|
||||
#| msgctxt "widget"
|
||||
#| msgid "Select variant %s"
|
||||
msgctxt "widget"
|
||||
msgid "Show variants"
|
||||
msgstr "Seleziona variante %s"
|
||||
msgstr "Mostra varianti"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js
|
||||
#: pretix/static/pretixpresale/widget/src/i18n.ts
|
||||
#, fuzzy
|
||||
#| msgctxt "widget"
|
||||
#| msgid "Select variant %s"
|
||||
msgctxt "widget"
|
||||
msgid "Hide variants"
|
||||
msgstr "Seleziona variante %s"
|
||||
msgstr "Nascondi varianti"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js
|
||||
#: pretix/static/pretixpresale/widget/src/i18n.ts
|
||||
@@ -1236,37 +1199,37 @@ msgstr "Do"
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js
|
||||
#: pretix/static/pretixpresale/widget/src/i18n.ts
|
||||
msgid "Monday"
|
||||
msgstr ""
|
||||
msgstr "Lunedì"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js
|
||||
#: pretix/static/pretixpresale/widget/src/i18n.ts
|
||||
msgid "Tuesday"
|
||||
msgstr ""
|
||||
msgstr "Martedì"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js
|
||||
#: pretix/static/pretixpresale/widget/src/i18n.ts
|
||||
msgid "Wednesday"
|
||||
msgstr ""
|
||||
msgstr "Mercoledì"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js
|
||||
#: pretix/static/pretixpresale/widget/src/i18n.ts
|
||||
msgid "Thursday"
|
||||
msgstr ""
|
||||
msgstr "Giovedì"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js
|
||||
#: pretix/static/pretixpresale/widget/src/i18n.ts
|
||||
msgid "Friday"
|
||||
msgstr ""
|
||||
msgstr "Venerdì"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js
|
||||
#: pretix/static/pretixpresale/widget/src/i18n.ts
|
||||
msgid "Saturday"
|
||||
msgstr ""
|
||||
msgstr "Sabato"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js
|
||||
#: pretix/static/pretixpresale/widget/src/i18n.ts
|
||||
msgid "Sunday"
|
||||
msgstr ""
|
||||
msgstr "Domenica"
|
||||
|
||||
#: pretix/static/pretixpresale/js/widget/widget.js
|
||||
#: pretix/static/pretixpresale/widget/src/i18n.ts
|
||||
|
||||
@@ -23,7 +23,7 @@ import json
|
||||
import logging
|
||||
import urllib.parse
|
||||
from collections import OrderedDict
|
||||
from datetime import timedelta
|
||||
from datetime import datetime, timedelta
|
||||
from decimal import Decimal
|
||||
|
||||
from django import forms
|
||||
@@ -645,7 +645,7 @@ class PaypalMethod(BasePaymentProvider):
|
||||
def _execute_payment(self, request: HttpRequest, payment: OrderPayment):
|
||||
payment = OrderPayment.objects.select_for_update(of=OF_SELF).get(pk=payment.pk)
|
||||
if payment.state == OrderPayment.PAYMENT_STATE_CONFIRMED:
|
||||
logger.warning('payment is already confirmed; possible return-view/webhook race-condition')
|
||||
# payment is already confirmed; possible return-view/webhook race-condition
|
||||
return
|
||||
|
||||
try:
|
||||
@@ -677,6 +677,8 @@ class PaypalMethod(BasePaymentProvider):
|
||||
raise PaymentException(_('We had trouble communicating with PayPal'))
|
||||
else:
|
||||
pp_captured_order = response.result
|
||||
payment.info = json.dumps(pp_captured_order.dict())
|
||||
payment.save()
|
||||
|
||||
try:
|
||||
ReferencedPayPalObject.objects.get_or_create(order=payment.order, payment=payment, reference=pp_captured_order.id)
|
||||
@@ -830,6 +832,7 @@ class PaypalMethod(BasePaymentProvider):
|
||||
payment.info = json.dumps(pp_captured_order.dict())
|
||||
payment.save(update_fields=['info'])
|
||||
payment.confirm()
|
||||
self.log_payment_duration(payment)
|
||||
except Quota.QuotaExceededException as e:
|
||||
raise PaymentException(str(e))
|
||||
# Payment has not any captures yet - so it's probably in created status
|
||||
@@ -839,6 +842,20 @@ class PaypalMethod(BasePaymentProvider):
|
||||
if 'payment_paypal_oid' in request.session:
|
||||
del request.session['payment_paypal_oid']
|
||||
|
||||
@staticmethod
|
||||
def log_payment_duration(payment: OrderPayment):
|
||||
try:
|
||||
capture = payment.info_data["purchase_units"][0]["payments"]["captures"][0]
|
||||
create_time: str | None = capture["create_time"]
|
||||
update_time: str | None = capture["update_time"]
|
||||
except (KeyError, IndexError, TypeError):
|
||||
create_time = None
|
||||
update_time = None
|
||||
|
||||
if create_time is not None and update_time is not None:
|
||||
duration = datetime.fromisoformat(update_time) - datetime.fromisoformat(create_time)
|
||||
logger.info('{}: {} - paypal payment processing time'.format(str(payment.global_id), str(duration)))
|
||||
|
||||
def payment_pending_render(self, request, payment) -> str:
|
||||
retry = True
|
||||
try:
|
||||
|
||||
@@ -357,14 +357,13 @@ def webhook(request, *args, **kwargs):
|
||||
if 'resource_type' not in event_json:
|
||||
return HttpResponse("Invalid body, no resource_type given", status=400)
|
||||
|
||||
if event_json['resource_type'] not in ["checkout-order", "refund", "capture"]:
|
||||
return HttpResponse("Not interested in this resource type", status=200)
|
||||
|
||||
# Retrieve the Charge ID of the refunded payment
|
||||
if event_json['resource_type'] == 'refund':
|
||||
if event_json['resource_type'] == 'checkout-order':
|
||||
payloadid = event_json['resource']['id']
|
||||
elif event_json['resource_type'] == 'refund' or event_json['resource_type'] == 'capture':
|
||||
payloadid = get_link(event_json['resource']['links'], 'up')['href'].split('/')[-1]
|
||||
else:
|
||||
payloadid = event_json['resource']['id']
|
||||
return HttpResponse("Not interested in this resource type", status=200)
|
||||
|
||||
refs = [payloadid]
|
||||
if event_json['resource'].get('supplementary_data', {}).get('related_ids', {}).get('order_id'):
|
||||
@@ -424,6 +423,8 @@ def webhook(request, *args, **kwargs):
|
||||
**event_json,
|
||||
'_order_state': sale.dict(),
|
||||
})
|
||||
payment.info = json.dumps(sale.dict())
|
||||
payment.save()
|
||||
|
||||
if payment.state == OrderPayment.PAYMENT_STATE_CONFIRMED and sale['status'] in ('PARTIALLY_REFUNDED', 'REFUNDED', 'COMPLETED'):
|
||||
if event_json['resource_type'] == 'refund':
|
||||
@@ -489,6 +490,7 @@ def webhook(request, *args, **kwargs):
|
||||
payment.info = json.dumps(sale.dict())
|
||||
payment.save(update_fields=['info'])
|
||||
payment.confirm()
|
||||
prov.log_payment_duration(payment)
|
||||
except Quota.QuotaExceededException:
|
||||
pass
|
||||
elif sale['status'] == 'APPROVED':
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import transaction
|
||||
from django_filters.rest_framework import DjangoFilterBackend, FilterSet
|
||||
from django_scopes import scopes_disabled
|
||||
from rest_framework import viewsets
|
||||
@@ -118,6 +119,7 @@ class RuleViewSet(viewsets.ModelViewSet):
|
||||
def get_queryset(self):
|
||||
return Rule.objects.filter(event=self.request.event)
|
||||
|
||||
@transaction.atomic()
|
||||
def perform_create(self, serializer):
|
||||
super().perform_create(serializer)
|
||||
serializer.instance.log_action(
|
||||
@@ -128,6 +130,7 @@ class RuleViewSet(viewsets.ModelViewSet):
|
||||
|
||||
)
|
||||
|
||||
@transaction.atomic()
|
||||
def perform_update(self, serializer):
|
||||
super().perform_update(serializer)
|
||||
serializer.instance.log_action(
|
||||
@@ -137,6 +140,7 @@ class RuleViewSet(viewsets.ModelViewSet):
|
||||
data=self.request.data
|
||||
)
|
||||
|
||||
@transaction.atomic()
|
||||
def perform_destroy(self, instance):
|
||||
instance.log_action(
|
||||
'pretix.plugins.sendmail.rule.deleted',
|
||||
|
||||
@@ -19,19 +19,9 @@
|
||||
# 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 hashlib
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.cache import cache
|
||||
from django.core.files.base import ContentFile, File
|
||||
from django.core.files.storage import default_storage
|
||||
from django.core.management.base import BaseCommand
|
||||
from django_scopes import scopes_disabled
|
||||
|
||||
from pretix.base.settings import GlobalSettingsObject
|
||||
from pretix.presale.views.widget import (
|
||||
generate_widget_js, version_max, version_min,
|
||||
)
|
||||
from pretix.presale.views.widget import regenerate_all_widget_js
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
@@ -41,26 +31,5 @@ class Command(BaseCommand):
|
||||
parser.add_argument('--organizer', action='store', type=str)
|
||||
parser.add_argument('--event', action='store', type=str)
|
||||
|
||||
@scopes_disabled()
|
||||
def handle(self, *args, **options):
|
||||
gs = GlobalSettingsObject()
|
||||
for lc, ll in settings.LANGUAGES:
|
||||
for version in range(version_min, version_max + 1):
|
||||
data = generate_widget_js(version, lc).encode()
|
||||
checksum = hashlib.sha1(data).hexdigest()
|
||||
settings_file_key = 'widget_file_v{}_{}'.format(version, lc)
|
||||
settings_checksum_key = 'widget_checksum_v{}_{}'.format(version, lc)
|
||||
fname = gs.settings.get(settings_file_key)
|
||||
if not fname or gs.settings.get(settings_checksum_key, '') != checksum:
|
||||
newname = default_storage.save(
|
||||
'pub/widget/widget.v{}.{}.{}.js'.format(version, lc, checksum),
|
||||
ContentFile(data)
|
||||
)
|
||||
gs.settings.set(settings_file_key, 'file://' + newname)
|
||||
gs.settings.set(settings_checksum_key, checksum)
|
||||
cache.delete('widget_js_data_v{}_{}'.format(version, lc))
|
||||
if fname:
|
||||
if isinstance(fname, File):
|
||||
default_storage.delete(fname.name)
|
||||
else:
|
||||
default_storage.delete(fname)
|
||||
regenerate_all_widget_js()
|
||||
|
||||
@@ -147,7 +147,7 @@ def get_font_stylesheet(font_name, organizer: Organizer = None, event: Event = N
|
||||
stylesheet = []
|
||||
font = get_fonts(event)[font_name]
|
||||
for sty, formats in font.items():
|
||||
if sty == 'sample':
|
||||
if sty in ['sample', 'pdf_only']:
|
||||
continue
|
||||
stylesheet.append('@font-face { ')
|
||||
stylesheet.append('font-family: "{}";'.format(font_name))
|
||||
|
||||
@@ -229,6 +229,60 @@ def generate_widget_js(version, lang, use_vite=False):
|
||||
return f"/* v{version} */\n" + code
|
||||
|
||||
|
||||
def get_widget_js(version, lang, use_vite, force_regenerate=False):
|
||||
variant = 'vite' if use_vite else 'legacy'
|
||||
cache_prefix = 'widget_js_data_v{}_{}_{}'.format(version, lang, variant)
|
||||
settings_key = 'widget_file_v{}_{}_{}'.format(version, lang, variant)
|
||||
checksum_key = 'widget_checksum_v{}_{}_{}'.format(version, lang, variant)
|
||||
gs = GlobalSettingsObject()
|
||||
|
||||
if not force_regenerate:
|
||||
cached_js = cache.get(cache_prefix)
|
||||
if cached_js and not settings.DEBUG:
|
||||
return cached_js
|
||||
|
||||
fname = gs.settings.get(settings_key)
|
||||
if fname and not settings.DEBUG:
|
||||
if isinstance(fname, File):
|
||||
fname = fname.name
|
||||
try:
|
||||
data = default_storage.open(fname).read()
|
||||
cache.set(cache_prefix, data, 3600 * 4)
|
||||
return data
|
||||
except:
|
||||
logger.exception('Failed to open widget.js')
|
||||
else:
|
||||
fname = gs.settings.get(settings_key)
|
||||
|
||||
data = generate_widget_js(version, lang, use_vite=use_vite).encode()
|
||||
checksum = hashlib.sha1(data).hexdigest()
|
||||
should_save = (
|
||||
not fname
|
||||
or gs.settings.get(checksum_key, '') != checksum
|
||||
)
|
||||
if should_save and not settings.DEBUG:
|
||||
newname = default_storage.save(
|
||||
'widget/widget.{}.{}.{}.{}.js'.format(version, lang, variant, checksum),
|
||||
ContentFile(data)
|
||||
)
|
||||
gs.settings.set(settings_key, 'file://' + newname)
|
||||
gs.settings.set(checksum_key, checksum)
|
||||
cache.set(cache_prefix, data, 3600 * 4)
|
||||
if fname:
|
||||
if isinstance(fname, File):
|
||||
default_storage.delete(fname.name)
|
||||
else:
|
||||
default_storage.delete(fname)
|
||||
return data
|
||||
|
||||
|
||||
def regenerate_all_widget_js():
|
||||
for lc, ll in settings.LANGUAGES:
|
||||
for version in range(version_min, version_max + 1):
|
||||
for use_vite in [True, False]:
|
||||
get_widget_js(version, lc, use_vite, force_regenerate=True)
|
||||
|
||||
|
||||
@gzip_page
|
||||
@condition(etag_func=widget_js_etag)
|
||||
def widget_js(request, version, lang, **kwargs):
|
||||
@@ -239,43 +293,9 @@ def widget_js(request, version, lang, **kwargs):
|
||||
version = version_min
|
||||
|
||||
use_vite = _use_vite(request)
|
||||
variant = 'vite' if use_vite else 'legacy'
|
||||
cache_prefix = 'widget_js_data_v{}_{}_{}'.format(version, lang, variant)
|
||||
data = get_widget_js(version, lang, use_vite)
|
||||
|
||||
cached_js = cache.get(cache_prefix)
|
||||
if cached_js and not settings.DEBUG:
|
||||
resp = HttpResponse(cached_js, content_type='text/javascript')
|
||||
resp['Access-Control-Allow-Origin'] = '*'
|
||||
return resp
|
||||
|
||||
settings_key = 'widget_file_v{}_{}_{}'.format(version, lang, variant)
|
||||
checksum_key = 'widget_checksum_v{}_{}_{}'.format(version, lang, variant)
|
||||
|
||||
gs = GlobalSettingsObject()
|
||||
fname = gs.settings.get(settings_key)
|
||||
resp = None
|
||||
if fname and not settings.DEBUG:
|
||||
if isinstance(fname, File):
|
||||
fname = fname.name
|
||||
try:
|
||||
data = default_storage.open(fname).read()
|
||||
resp = HttpResponse(data, content_type='text/javascript')
|
||||
cache.set(cache_prefix, data, 3600 * 4)
|
||||
except:
|
||||
logger.exception('Failed to open widget.js')
|
||||
|
||||
if not resp:
|
||||
data = generate_widget_js(version, lang, use_vite=use_vite).encode()
|
||||
checksum = hashlib.sha1(data).hexdigest()
|
||||
if not settings.DEBUG:
|
||||
newname = default_storage.save(
|
||||
'widget/widget.{}.{}.{}.{}.js'.format(version, lang, variant, checksum),
|
||||
ContentFile(data)
|
||||
)
|
||||
gs.settings.set(settings_key, 'file://' + newname)
|
||||
gs.settings.set(checksum_key, checksum)
|
||||
cache.set(cache_prefix, data, 3600 * 4)
|
||||
resp = HttpResponse(data, content_type='text/javascript')
|
||||
resp = HttpResponse(data, content_type='text/javascript')
|
||||
resp['Access-Control-Allow-Origin'] = '*'
|
||||
return resp
|
||||
|
||||
@@ -795,6 +815,7 @@ class WidgetAPIProductList(EventListMixin, View):
|
||||
'target_url': eventreverse_absolute(request.event, 'presale:event.index'),
|
||||
'subevent': self.subevent.pk if self.subevent else None,
|
||||
'currency': request.event.currency,
|
||||
'currency_places': settings.CURRENCY_PLACES.get(request.event.currency, 2),
|
||||
'display_net_prices': request.event.settings.display_net_prices,
|
||||
'use_native_spinners': request.event.settings.widget_use_native_spinners,
|
||||
'show_variations_expanded': request.event.settings.show_variations_expanded,
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
$(document).on('pretix:bind-forms', () => {
|
||||
document.querySelectorAll('.reldatetime, .reldate').forEach(container => {
|
||||
const groups = container.querySelectorAll('.radio')
|
||||
|
||||
groups.forEach(group => {
|
||||
const referenceSelect = group.querySelector('select[data-relative-choice]')
|
||||
const beforeAfterSelect = group.querySelector('select[data-relation-choice]')
|
||||
if (!referenceSelect || !beforeAfterSelect) return
|
||||
|
||||
const beforeOption = beforeAfterSelect.querySelector('option[value="before"]')
|
||||
const afterOption = beforeAfterSelect.querySelector('option[value="after"]')
|
||||
|
||||
const updateBeforeOption = () => {
|
||||
let supportsBefore = referenceSelect.selectedOptions[0].hasAttribute('data-supports-before')
|
||||
if (beforeOption) {
|
||||
beforeOption.disabled = !beforeOption.disabled && !supportsBefore
|
||||
}
|
||||
|
||||
let supportsAfter = referenceSelect.selectedOptions[0].hasAttribute('data-supports-after')
|
||||
if (afterOption) {
|
||||
afterOption.disabled = !afterOption.disabled && !supportsAfter
|
||||
}
|
||||
|
||||
let dirty = false
|
||||
if (beforeOption.disabled && beforeAfterSelect.value === 'before') {
|
||||
beforeAfterSelect.value = 'after'
|
||||
dirty = true
|
||||
}
|
||||
if (afterOption.disabled && beforeAfterSelect.value === 'after') {
|
||||
beforeAfterSelect.value = 'before'
|
||||
dirty = true
|
||||
}
|
||||
|
||||
if (dirty) {
|
||||
beforeAfterSelect.dispatchEvent(new Event('change', {bubbles: true}))
|
||||
}
|
||||
}
|
||||
referenceSelect.addEventListener('change', updateBeforeOption)
|
||||
updateBeforeOption()
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -380,16 +380,16 @@ Vue.component('pricebox', {
|
||||
},
|
||||
display_price: function () {
|
||||
if (this.$root.display_net_prices) {
|
||||
return floatformat(parseFloat(this.price.net), 2);
|
||||
return floatformat(this.price.net, this.$root.currency_places);
|
||||
} else {
|
||||
return floatformat(parseFloat(this.price.gross), 2);
|
||||
return floatformat(this.price.gross, this.$root.currency_places);
|
||||
}
|
||||
},
|
||||
display_price_nonlocalized: function () {
|
||||
if (this.$root.display_net_prices) {
|
||||
return parseFloat(this.price.net).toFixed(2);
|
||||
return parseFloat(this.price.net).toFixed(this.$root.currency_places);
|
||||
} else {
|
||||
return parseFloat(this.price.gross).toFixed(2);
|
||||
return parseFloat(this.price.gross).toFixed(this.$root.currency_places);
|
||||
}
|
||||
},
|
||||
suggested_price_nonlocalized: function () {
|
||||
@@ -398,9 +398,9 @@ Vue.component('pricebox', {
|
||||
price = this.price;
|
||||
}
|
||||
if (this.$root.display_net_prices) {
|
||||
return parseFloat(price.net).toFixed(2);
|
||||
return parseFloat(price.net).toFixed(this.$root.currency_places);
|
||||
} else {
|
||||
return parseFloat(price.gross).toFixed(2);
|
||||
return parseFloat(price.gross).toFixed(this.$root.currency_places);
|
||||
}
|
||||
},
|
||||
original_price_aria_label: function () {
|
||||
@@ -410,7 +410,7 @@ Vue.component('pricebox', {
|
||||
return django.interpolate(strings.new_price, [this.stripHTML(this.priceline)]);
|
||||
},
|
||||
original_line: function () {
|
||||
return '<span class="pretix-widget-pricebox-currency">' + this.$root.currency + "</span> " + floatformat(parseFloat(this.original_price), 2);
|
||||
return '<span class="pretix-widget-pricebox-currency">' + this.$root.currency + "</span> " + floatformat(this.original_price, this.$root.currency_places);
|
||||
},
|
||||
priceline: function () {
|
||||
if (this.price.gross === "0.00") {
|
||||
@@ -645,19 +645,19 @@ Vue.component('item', {
|
||||
if (this.item.free_price) {
|
||||
return django.interpolate(strings.price_from, {
|
||||
'currency': this.$root.currency,
|
||||
'price': floatformat(this.item.min_price, 2)
|
||||
'price': floatformat(this.item.min_price, this.$root.currency_places)
|
||||
}, true).replace(this.$root.currency, '<span class="pretix-widget-pricebox-currency">' + this.$root.currency + '</span>');
|
||||
} else if (this.item.min_price !== this.item.max_price) {
|
||||
return '<span class="pretix-widget-pricebox-currency">' + this.$root.currency + "</span> "
|
||||
+ floatformat(this.item.min_price, 2) + " – "
|
||||
+ floatformat(this.item.max_price, 2);
|
||||
return '<span class="pretix-widget-pricebox-currency">' + this.$root.currency + "</span> "
|
||||
+ floatformat(this.item.min_price, this.$root.currency_places) + " – "
|
||||
+ floatformat(this.item.max_price, this.$root.currency_places);
|
||||
} else if (this.item.min_price === "0.00" && this.item.max_price === "0.00") {
|
||||
if (this.item.mandatory_priced_addons) {
|
||||
return "\xA0"; // nbsp, because an empty string would cause the HTML element to collapse
|
||||
}
|
||||
return strings.free;
|
||||
} else {
|
||||
return '<span class="pretix-widget-pricebox-currency">' + this.$root.currency + "</span> " + floatformat(this.item.min_price, 2);
|
||||
return '<span class="pretix-widget-pricebox-currency">' + this.$root.currency + "</span> " + floatformat(this.item.min_price, this.$root.currency_places);
|
||||
}
|
||||
},
|
||||
variationsToggleLabel: function () {
|
||||
@@ -1949,6 +1949,7 @@ var shared_root_methods = {
|
||||
root.location = data.location;
|
||||
root.categories = data.items_by_category;
|
||||
root.currency = data.currency;
|
||||
root.currency_places = data.currency_places;
|
||||
root.display_net_prices = data.display_net_prices;
|
||||
root.voucher_explanation_text = data.voucher_explanation_text;
|
||||
root.error = data.error;
|
||||
@@ -1983,6 +1984,7 @@ var shared_root_methods = {
|
||||
}, function (error) {
|
||||
root.categories = [];
|
||||
root.currency = '';
|
||||
root.currency_places = 2;
|
||||
if (error.status === 429) {
|
||||
root.error = strings['loading_error_429'];
|
||||
root.connection_error = true;
|
||||
@@ -2339,6 +2341,7 @@ var create_widget = function (element, html_id=null) {
|
||||
is_button: false,
|
||||
categories: null,
|
||||
currency: null,
|
||||
currency_places: 2,
|
||||
name: null,
|
||||
date_range: null,
|
||||
location: null,
|
||||
|
||||
@@ -22,6 +22,7 @@ export interface ProductListResponse {
|
||||
location?: string
|
||||
items_by_category?: Category[]
|
||||
currency?: string
|
||||
currency_places?: number
|
||||
display_net_prices?: boolean
|
||||
voucher_explanation_text?: string
|
||||
error?: string
|
||||
|
||||
@@ -72,7 +72,7 @@ const pricerange = computed(() => {
|
||||
STRINGS.price_from,
|
||||
{
|
||||
currency: store.currency,
|
||||
price: floatformat(props.item.min_price || '0', 2),
|
||||
price: floatformat(props.item.min_price || '0', store.currency_places),
|
||||
},
|
||||
true
|
||||
).replace(
|
||||
@@ -80,14 +80,14 @@ const pricerange = computed(() => {
|
||||
`<span class="pretix-widget-pricebox-currency">${store.currency}</span>`
|
||||
)
|
||||
} else if (props.item.min_price !== props.item.max_price) {
|
||||
return `<span class="pretix-widget-pricebox-currency">${store.currency}</span> ${floatformat(props.item.min_price || '0', 2)} – ${floatformat(props.item.max_price || '0', 2)}`
|
||||
return `<span class="pretix-widget-pricebox-currency">${store.currency}</span> ${floatformat(props.item.min_price || '0', store.currency_places)} – ${floatformat(props.item.max_price || '0', store.currency_places)}`
|
||||
} else if (props.item.min_price === '0.00' && props.item.max_price === '0.00') {
|
||||
if (props.item.mandatory_priced_addons) {
|
||||
return '\xA0' // nbsp, because an empty string would cause the HTML element to collapse
|
||||
}
|
||||
return STRINGS.free
|
||||
} else {
|
||||
return `<span class="pretix-widget-pricebox-currency">${store.currency}</span> ${floatformat(props.item.min_price || '0', 2)}`
|
||||
return `<span class="pretix-widget-pricebox-currency">${store.currency}</span> ${floatformat(props.item.min_price || '0', store.currency_places)}`
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -25,9 +25,9 @@ const ariaLabelledby = computed(() => `${store.htmlId}-item-label-${props.itemId
|
||||
|
||||
const displayPrice = computed(() => {
|
||||
if (store.displayNetPrices) {
|
||||
return floatformat(parseFloat(props.price.net), 2)
|
||||
return floatformat(props.price.net, store.currency_places)
|
||||
}
|
||||
return floatformat(parseFloat(props.price.gross), 2)
|
||||
return floatformat(props.price.gross, store.currency_places)
|
||||
})
|
||||
|
||||
const displayPriceNonlocalized = computed(() => {
|
||||
@@ -40,15 +40,15 @@ const displayPriceNonlocalized = computed(() => {
|
||||
const suggestedPriceNonlocalized = computed(() => {
|
||||
const price = props.suggestedPrice ?? props.price
|
||||
if (store.displayNetPrices) {
|
||||
return parseFloat(price.net).toFixed(2)
|
||||
return parseFloat(price.net).toFixed(store.currency_places)
|
||||
}
|
||||
return parseFloat(price.gross).toFixed(2)
|
||||
return parseFloat(price.gross).toFixed(store.currency_places)
|
||||
})
|
||||
|
||||
// TODO BAD
|
||||
const originalLine = computed(() => {
|
||||
if (!props.originalPrice) return ''
|
||||
return `<span class="pretix-widget-pricebox-currency">${store.currency}</span> ${floatformat(parseFloat(props.originalPrice), 2)}`
|
||||
return `<span class="pretix-widget-pricebox-currency">${store.currency}</span> ${floatformat(props.originalPrice, store.currency_places)}`
|
||||
})
|
||||
|
||||
// TODO BAD
|
||||
|
||||
@@ -71,6 +71,7 @@ export function createWidgetStore (config: {
|
||||
frontpageText: null as string | null,
|
||||
categories: [] as Category[],
|
||||
currency: '',
|
||||
currency_places: 2,
|
||||
displayNetPrices: false,
|
||||
voucherExplanationText: null as string | null,
|
||||
displayAddToCart: false,
|
||||
@@ -299,6 +300,7 @@ export function createWidgetStore (config: {
|
||||
this.location = data.location ?? null
|
||||
this.categories = data.items_by_category ?? []
|
||||
this.currency = data.currency ?? ''
|
||||
this.currency_places = data.currency_places ?? 2
|
||||
this.displayNetPrices = data.display_net_prices ?? false
|
||||
this.voucherExplanationText = data.voucher_explanation_text ?? null
|
||||
this.error = data.error ?? null
|
||||
@@ -338,6 +340,7 @@ export function createWidgetStore (config: {
|
||||
} catch (e) {
|
||||
this.categories = []
|
||||
this.currency = ''
|
||||
this.currency_places = 2
|
||||
if (e instanceof ApiError && e.status === 429) {
|
||||
this.error = STRINGS.loading_error_429
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-today pretix 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/>.
|
||||
#
|
||||
def readonly_db(execute, sql, params, many, context):
|
||||
if not sql.lower().startswith("select"):
|
||||
raise Exception(f"Should not write anything to the database, but detected query: {sql}")
|
||||
return execute(sql, params, many, context)
|
||||
@@ -25,6 +25,7 @@ from unittest import mock
|
||||
|
||||
import pytest
|
||||
from django.core.files.base import ContentFile
|
||||
from django.db import connection
|
||||
from django.utils.timezone import now
|
||||
from django_countries.fields import Country
|
||||
from django_scopes import scopes_disabled
|
||||
@@ -36,6 +37,7 @@ from pretix.api.serializers.item import QuestionSerializer
|
||||
from pretix.base.models import (
|
||||
Checkin, InvoiceAddress, Item, Order, OrderPosition, ReusableMedium,
|
||||
)
|
||||
from pretix.testutils.db import readonly_db
|
||||
|
||||
# Lots of this code is overlapping with test_checkin.py, and some of it is arguably redundant since it's triggering
|
||||
# the same backend code paths (for now). However, this is SUCH a critical part of pretix that we don't want to take
|
||||
@@ -1739,3 +1741,41 @@ def test_exchange_create_gift_card(token_client, organizer, clist, event, order,
|
||||
with scopes_disabled():
|
||||
rm = ReusableMedium.objects.get(identifier="0412345")
|
||||
assert rm.linked_giftcard.currency == "EUR"
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_simulate(token_client, organizer, clist, event, order):
|
||||
with scopes_disabled():
|
||||
p = order.positions.first()
|
||||
with connection.execute_wrapper(readonly_db):
|
||||
resp = _redeem(token_client, organizer, clist, p.secret, {"simulate": True})
|
||||
assert resp.status_code == 201
|
||||
assert resp.data['status'] == 'ok'
|
||||
with scopes_disabled():
|
||||
assert not p.checkins.exists()
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_simulate_no_exchange(token_client, organizer, clist, event, order, item):
|
||||
organizer.settings.reusable_media_type_nfc_uid = True
|
||||
item.media_type = "nfc_uid"
|
||||
item.media_policy = Item.MEDIA_POLICY_NEW
|
||||
item.save()
|
||||
with scopes_disabled():
|
||||
rm = ReusableMedium.objects.create(
|
||||
type="nfc_uid",
|
||||
identifier="12345678",
|
||||
organizer=organizer,
|
||||
)
|
||||
with connection.execute_wrapper(readonly_db):
|
||||
resp = _redeem(token_client, organizer, clist, "z3fsn8jyufm5kpk768q69gkbyr5f4h6w", {
|
||||
"source_type": "barcode",
|
||||
"exchange_medium_type": "nfc_uid",
|
||||
"exchange_medium_identifier": "12345678",
|
||||
"simulate": True,
|
||||
})
|
||||
assert resp.status_code == 400
|
||||
assert resp.data['status'] == 'error'
|
||||
assert resp.data['reason'] == 'error'
|
||||
with scopes_disabled():
|
||||
assert not rm.linked_orderpositions.exists()
|
||||
|
||||
@@ -20,13 +20,16 @@
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
import base64
|
||||
from datetime import datetime, timezone
|
||||
|
||||
import pytest
|
||||
from cryptography.hazmat.primitives.asymmetric import padding
|
||||
from cryptography.hazmat.primitives.serialization import load_pem_private_key
|
||||
from django_scopes import scopes_disabled
|
||||
from freezegun import freeze_time
|
||||
|
||||
from pretix.base.models import Device
|
||||
from pretix.base.models.devices import DeviceLastSeen
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -386,3 +389,26 @@ def test_device_info_key_sets(device_client, device: Device):
|
||||
base64.b64decode(ks['diversification_key']),
|
||||
padding.PKCS1v15()
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_update_last_seen(device_client, device: Device):
|
||||
assert not DeviceLastSeen.objects.exists()
|
||||
|
||||
with freeze_time("2020-01-10T14:30:00+00:00"):
|
||||
resp = device_client.get('/api/v1/device/info')
|
||||
assert resp.status_code == 200
|
||||
assert device.last_seen.last_seen == datetime(2020, 1, 10, 14, 30, tzinfo=timezone.utc)
|
||||
|
||||
with freeze_time("2020-01-10T14:30:05+00:00"):
|
||||
resp = device_client.get('/api/v1/device/info')
|
||||
assert resp.status_code == 200
|
||||
# No update, interal too short
|
||||
device.last_seen.refresh_from_db()
|
||||
assert device.last_seen.last_seen == datetime(2020, 1, 10, 14, 30, tzinfo=timezone.utc)
|
||||
|
||||
with freeze_time("2020-01-10T14:30:30+00:00"):
|
||||
resp = device_client.get('/api/v1/device/info')
|
||||
assert resp.status_code == 200
|
||||
device.last_seen.refresh_from_db()
|
||||
assert device.last_seen.last_seen == datetime(2020, 1, 10, 14, 30, 30, tzinfo=timezone.utc)
|
||||
|
||||
@@ -473,7 +473,7 @@ class QuotaTestCase(BaseQuotaTestCase):
|
||||
@classscope(attr='o')
|
||||
def test_waitinglist_auto_disable(self):
|
||||
self.event.settings.waiting_list_auto_disable = RelativeDateWrapper(
|
||||
RelativeDate(days=0, time=None, base_date_name='date_from', minutes=20, is_after=True)
|
||||
RelativeDate(days=0, time=None, base_date_name='event__date_from', minutes=20, is_after=True)
|
||||
)
|
||||
self.quota.items.add(self.item1)
|
||||
self.quota.size = 1
|
||||
@@ -484,7 +484,7 @@ class QuotaTestCase(BaseQuotaTestCase):
|
||||
self.assertEqual(self.item1.check_quotas(), (Quota.AVAILABILITY_ORDERED, 0))
|
||||
self.assertEqual(self.item1.check_quotas(count_waitinglist=False), (Quota.AVAILABILITY_OK, 1))
|
||||
self.event.settings.waiting_list_auto_disable = RelativeDateWrapper(
|
||||
RelativeDate(days=0, time=None, base_date_name='date_from', minutes=20, is_after=False)
|
||||
RelativeDate(days=0, time=None, base_date_name='event__date_from', minutes=20, is_after=False)
|
||||
)
|
||||
self.assertEqual(self.item1.check_quotas(), (Quota.AVAILABILITY_OK, 1))
|
||||
self.assertEqual(self.item1.check_quotas(count_waitinglist=False), (Quota.AVAILABILITY_OK, 1))
|
||||
@@ -1192,7 +1192,7 @@ class OrderTestCase(BaseQuotaTestCase):
|
||||
self.op2.subevent = se2
|
||||
self.op2.save()
|
||||
self.event.settings.set('payment_term_last', RelativeDateWrapper(
|
||||
RelativeDate(days=2, time=None, base_date_name='date_from', minutes=None)
|
||||
RelativeDate(days=2, time=None, base_date_name='event__date_from', minutes=None)
|
||||
))
|
||||
|
||||
self.order.status = Order.STATUS_EXPIRED
|
||||
@@ -1347,7 +1347,7 @@ class OrderTestCase(BaseQuotaTestCase):
|
||||
self.op2.subevent = se2
|
||||
self.op2.save()
|
||||
self.event.settings.set('last_order_modification_date', RelativeDateWrapper(
|
||||
RelativeDate(days=2, time=None, base_date_name='date_from', minutes=None)
|
||||
RelativeDate(days=2, time=None, base_date_name='event__date_from', minutes=None)
|
||||
))
|
||||
assert self.order.can_modify_answers
|
||||
self.op2.subevent = se3
|
||||
@@ -1363,7 +1363,7 @@ class OrderTestCase(BaseQuotaTestCase):
|
||||
self.event.date_from = datetime.datetime(2017, 5, 3, 12, 0, 0, tzinfo=datetime.timezone.utc)
|
||||
self.event.save()
|
||||
self.event.settings.set('payment_term_last', RelativeDateWrapper(
|
||||
RelativeDate(days=2, time=None, base_date_name='date_from', minutes=None)
|
||||
RelativeDate(days=2, time=None, base_date_name='event__date_from', minutes=None)
|
||||
))
|
||||
assert self.order.payment_term_last == datetime.datetime(2017, 5, 1, 23, 59, 59, tzinfo=datetime.timezone.utc)
|
||||
|
||||
@@ -1379,7 +1379,7 @@ class OrderTestCase(BaseQuotaTestCase):
|
||||
self.op2.subevent = se2
|
||||
self.op2.save()
|
||||
self.event.settings.set('payment_term_last', RelativeDateWrapper(
|
||||
RelativeDate(days=2, time=None, base_date_name='date_from', minutes=None)
|
||||
RelativeDate(days=2, time=None, base_date_name='event__date_from', minutes=None)
|
||||
))
|
||||
assert self.order.payment_term_last > now()
|
||||
self.op2.subevent = se3
|
||||
@@ -1395,7 +1395,7 @@ class OrderTestCase(BaseQuotaTestCase):
|
||||
self.event.date_from = datetime.datetime(2017, 5, 3, 12, 0, 0, tzinfo=datetime.timezone.utc)
|
||||
self.event.save()
|
||||
self.event.settings.set('ticket_download_date', RelativeDateWrapper(
|
||||
RelativeDate(days=2, time=None, base_date_name='date_from', minutes=None)
|
||||
RelativeDate(days=2, time=None, base_date_name='event__date_from', minutes=None)
|
||||
))
|
||||
assert self.order.ticket_download_date == datetime.datetime(2017, 5, 1, 12, 0, 0, tzinfo=datetime.timezone.utc)
|
||||
|
||||
@@ -1411,7 +1411,7 @@ class OrderTestCase(BaseQuotaTestCase):
|
||||
self.op2.subevent = se2
|
||||
self.op2.save()
|
||||
self.event.settings.set('ticket_download_date', RelativeDateWrapper(
|
||||
RelativeDate(days=2, time=None, base_date_name='date_from', minutes=None)
|
||||
RelativeDate(days=2, time=None, base_date_name='event__date_from', minutes=None)
|
||||
))
|
||||
assert self.order.ticket_download_date > now()
|
||||
self.op2.subevent = se3
|
||||
@@ -1582,13 +1582,13 @@ class OrderTestCase(BaseQuotaTestCase):
|
||||
|
||||
assert self.order.user_cancel_deadline is None
|
||||
self.event.settings.set('cancel_allow_user_until', RelativeDateWrapper(
|
||||
RelativeDate(days=2, time=datetime.time(14, 0, 0), base_date_name='date_from', minutes=None)
|
||||
RelativeDate(days=2, time=datetime.time(14, 0, 0), base_date_name='event__date_from', minutes=None)
|
||||
))
|
||||
self.order = Order.objects.get(pk=self.order.pk)
|
||||
assert self.order.user_cancel_deadline > now()
|
||||
assert self.order.user_cancel_allowed
|
||||
self.event.settings.set('cancel_allow_user_until', RelativeDateWrapper(
|
||||
RelativeDate(days=4, time=datetime.time(14, 0, 0), base_date_name='date_from', minutes=None)
|
||||
RelativeDate(days=4, time=datetime.time(14, 0, 0), base_date_name='event__date_from', minutes=None)
|
||||
))
|
||||
self.order = Order.objects.get(pk=self.order.pk)
|
||||
assert self.order.user_cancel_deadline < now()
|
||||
@@ -1607,7 +1607,7 @@ class OrderTestCase(BaseQuotaTestCase):
|
||||
self.op2.save()
|
||||
|
||||
self.event.settings.set('cancel_allow_user_until', RelativeDateWrapper(
|
||||
RelativeDate(days=2, time=datetime.time(14, 0, 0), base_date_name='date_from', minutes=None)
|
||||
RelativeDate(days=2, time=datetime.time(14, 0, 0), base_date_name='event__date_from', minutes=None)
|
||||
))
|
||||
self.order = Order.objects.get(pk=self.order.pk)
|
||||
assert self.order.user_cancel_deadline < now()
|
||||
@@ -2018,13 +2018,13 @@ class OrderTestCase(BaseQuotaTestCase):
|
||||
|
||||
assert self.order.user_change_deadline is None
|
||||
self.event.settings.set('change_allow_user_until', RelativeDateWrapper(
|
||||
RelativeDate(days=2, time=datetime.time(14, 0, 0), base_date_name='date_from', minutes=None)
|
||||
RelativeDate(days=2, time=datetime.time(14, 0, 0), base_date_name='event__date_from', minutes=None)
|
||||
))
|
||||
self.order = Order.objects.get(pk=self.order.pk)
|
||||
assert self.order.user_change_deadline > now()
|
||||
assert self.order.user_change_allowed
|
||||
self.event.settings.set('change_allow_user_until', RelativeDateWrapper(
|
||||
RelativeDate(days=4, time=datetime.time(14, 0, 0), base_date_name='date_from', minutes=None)
|
||||
RelativeDate(days=4, time=datetime.time(14, 0, 0), base_date_name='event__date_from', minutes=None)
|
||||
))
|
||||
self.order = Order.objects.get(pk=self.order.pk)
|
||||
assert self.order.user_change_deadline < now()
|
||||
@@ -2046,7 +2046,7 @@ class OrderTestCase(BaseQuotaTestCase):
|
||||
self.op2.save()
|
||||
|
||||
self.event.settings.set('change_allow_user_until', RelativeDateWrapper(
|
||||
RelativeDate(days=2, time=datetime.time(14, 0, 0), base_date_name='date_from', minutes=None)
|
||||
RelativeDate(days=2, time=datetime.time(14, 0, 0), base_date_name='event__date_from', minutes=None)
|
||||
))
|
||||
self.order = Order.objects.get(pk=self.order.pk)
|
||||
assert self.order.user_change_deadline < now()
|
||||
|
||||
@@ -204,7 +204,7 @@ def test_expiry_last_relative(event):
|
||||
event.date_from = now() + timedelta(days=5)
|
||||
event.save()
|
||||
event.settings.set('payment_term_last', RelativeDateWrapper(
|
||||
RelativeDate(days=2, time=None, base_date_name='date_from', minutes=None)
|
||||
RelativeDate(days=2, time=None, base_date_name='event__date_from', minutes=None)
|
||||
))
|
||||
order = _create_order(event, email='dummy@example.org', positions=[],
|
||||
now_dt=today,
|
||||
@@ -245,7 +245,7 @@ def test_expiry_last_relative_subevents(event):
|
||||
)
|
||||
|
||||
event.settings.set('payment_term_last', RelativeDateWrapper(
|
||||
RelativeDate(days=2, time=None, base_date_name='date_from', minutes=None)
|
||||
RelativeDate(days=2, time=None, base_date_name='event__date_from', minutes=None)
|
||||
))
|
||||
order = _create_order(event, email='dummy@example.org', positions=[cp1, cp2],
|
||||
now_dt=today,
|
||||
|
||||
@@ -133,7 +133,7 @@ def test_availability_date_relative(event):
|
||||
event.save()
|
||||
prov = DummyPaymentProvider(event)
|
||||
prov.settings.set('_availability_date', RelativeDateWrapper(
|
||||
RelativeDate(days=2, time=None, base_date_name='date_from', minutes=None)
|
||||
RelativeDate(days=2, time=None, base_date_name='event__date_from', minutes=None)
|
||||
))
|
||||
|
||||
utc = datetime.timezone.utc
|
||||
@@ -150,7 +150,7 @@ def test_availability_start_relative(event):
|
||||
event.save()
|
||||
prov = DummyPaymentProvider(event)
|
||||
prov.settings.set('_availability_start', RelativeDateWrapper(
|
||||
RelativeDate(days=2, time=datetime.time(12, 0), base_date_name='date_from', minutes=None)
|
||||
RelativeDate(days=2, time=datetime.time(12, 0), base_date_name='event__date_from', minutes=None)
|
||||
))
|
||||
|
||||
utc = datetime.timezone.utc
|
||||
@@ -193,12 +193,12 @@ def test_availability_date_cart_relative_subevents(event):
|
||||
|
||||
prov = DummyPaymentProvider(event)
|
||||
prov.settings.set('_availability_date', RelativeDateWrapper(
|
||||
RelativeDate(days=3, time=None, base_date_name='date_from', minutes=None)
|
||||
RelativeDate(days=3, time=None, base_date_name='event__date_from', minutes=None)
|
||||
))
|
||||
assert prov._is_available_by_time(cart_id="123")
|
||||
|
||||
prov.settings.set('_availability_date', RelativeDateWrapper(
|
||||
RelativeDate(days=4, time=None, base_date_name='date_from', minutes=None)
|
||||
RelativeDate(days=4, time=None, base_date_name='event__date_from', minutes=None)
|
||||
))
|
||||
assert not prov._is_available_by_time(cart_id="123")
|
||||
|
||||
@@ -233,11 +233,11 @@ def test_availability_date_order_relative_subevents(event):
|
||||
|
||||
prov = DummyPaymentProvider(event)
|
||||
prov.settings.set('_availability_date', RelativeDateWrapper(
|
||||
RelativeDate(days=3, time=None, base_date_name='date_from', minutes=None)
|
||||
RelativeDate(days=3, time=None, base_date_name='event__date_from', minutes=None)
|
||||
))
|
||||
assert prov._is_available_by_time(order=order)
|
||||
|
||||
prov.settings.set('_availability_date', RelativeDateWrapper(
|
||||
RelativeDate(days=4, time=None, base_date_name='date_from', minutes=None)
|
||||
RelativeDate(days=4, time=None, base_date_name='event__date_from', minutes=None)
|
||||
))
|
||||
assert not prov._is_available_by_time(order=order)
|
||||
|
||||
@@ -19,13 +19,13 @@
|
||||
# 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 datetime import datetime, time
|
||||
from datetime import datetime, time, timedelta
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
import pytest
|
||||
from django_scopes import scope
|
||||
|
||||
from pretix.base.models import Event, Organizer
|
||||
from pretix.base.models import Event, Order, Organizer
|
||||
from pretix.base.reldate import RelativeDate, RelativeDateWrapper
|
||||
|
||||
TOKYO = ZoneInfo('Asia/Tokyo')
|
||||
@@ -40,7 +40,6 @@ def event():
|
||||
date_from=datetime(2017, 12, 27, 5, 0, 0, tzinfo=TOKYO),
|
||||
presale_start=datetime(2017, 12, 1, 5, 0, 0, tzinfo=TOKYO),
|
||||
plugins='pretix.plugins.banktransfer'
|
||||
|
||||
)
|
||||
event.settings.timezone = "Asia/Tokyo"
|
||||
return event
|
||||
@@ -56,57 +55,57 @@ def test_absolute_date(event):
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_relative_date_without_time(event):
|
||||
rdw = RelativeDateWrapper(RelativeDate(days=1, time=None, base_date_name='date_from', minutes=None))
|
||||
rdw = RelativeDateWrapper(RelativeDate(days=1, time=None, base_date_name='event__date_from', minutes=None))
|
||||
assert rdw.datetime(event).astimezone(TOKYO) == datetime(2017, 12, 26, 5, 0, 0, tzinfo=TOKYO)
|
||||
assert rdw.to_string() == 'RELDATE/1/-/date_from/'
|
||||
rdw = RelativeDateWrapper(RelativeDate(days=1, time=None, base_date_name='date_from', minutes=None, is_after=True))
|
||||
assert rdw.to_string() == 'RELDATE/1/-/event__date_from/'
|
||||
rdw = RelativeDateWrapper(RelativeDate(days=1, time=None, base_date_name='event__date_from', minutes=None, is_after=True))
|
||||
assert rdw.datetime(event).astimezone(TOKYO) == datetime(2017, 12, 28, 5, 0, 0, tzinfo=TOKYO)
|
||||
assert rdw.to_string() == 'RELDATE/1/-/date_from/after'
|
||||
assert rdw.to_string() == 'RELDATE/1/-/event__date_from/after'
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_relative_date_other_base_point(event):
|
||||
with scope(organizer=event.organizer):
|
||||
rdw = RelativeDateWrapper(RelativeDate(days=1, time=None, base_date_name='presale_start', minutes=None))
|
||||
rdw = RelativeDateWrapper(RelativeDate(days=1, time=None, base_date_name='event__presale_start', minutes=None))
|
||||
assert rdw.datetime(event) == datetime(2017, 11, 30, 5, 0, 0, tzinfo=TOKYO)
|
||||
assert rdw.to_string() == 'RELDATE/1/-/presale_start/'
|
||||
assert rdw.to_string() == 'RELDATE/1/-/event__presale_start/'
|
||||
|
||||
# presale_end is unset, defaults to date_from
|
||||
rdw = RelativeDateWrapper(RelativeDate(days=1, time=None, base_date_name='presale_end', minutes=None))
|
||||
rdw = RelativeDateWrapper(RelativeDate(days=1, time=None, base_date_name='event__presale_end', minutes=None))
|
||||
assert rdw.datetime(event) == datetime(2017, 12, 26, 5, 0, 0, tzinfo=TOKYO)
|
||||
assert rdw.to_string() == 'RELDATE/1/-/presale_end/'
|
||||
assert rdw.to_string() == 'RELDATE/1/-/event__presale_end/'
|
||||
|
||||
# subevent base
|
||||
se = event.subevents.create(name="SE1", date_from=datetime(2017, 11, 27, 5, 0, 0, tzinfo=TOKYO))
|
||||
rdw = RelativeDateWrapper(RelativeDate(days=1, time=None, base_date_name='date_from', minutes=None))
|
||||
rdw = RelativeDateWrapper(RelativeDate(days=1, time=None, base_date_name='event__date_from', minutes=None))
|
||||
assert rdw.datetime(se) == datetime(2017, 11, 26, 5, 0, 0, tzinfo=TOKYO)
|
||||
|
||||
# presale_start is unset on subevent, default to event
|
||||
rdw = RelativeDateWrapper(RelativeDate(days=1, time=None, base_date_name='presale_start', minutes=None))
|
||||
rdw = RelativeDateWrapper(RelativeDate(days=1, time=None, base_date_name='event__presale_start', minutes=None))
|
||||
assert rdw.datetime(se) == datetime(2017, 11, 30, 5, 0, 0, tzinfo=TOKYO)
|
||||
|
||||
# presale_end is unset on all, default to date_from of subevent
|
||||
rdw = RelativeDateWrapper(RelativeDate(days=1, time=None, base_date_name='presale_end', minutes=None))
|
||||
rdw = RelativeDateWrapper(RelativeDate(days=1, time=None, base_date_name='event__presale_end', minutes=None))
|
||||
assert rdw.datetime(se) == datetime(2017, 11, 26, 5, 0, 0, tzinfo=TOKYO)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_relative_date_in_minutes(event):
|
||||
rdw = RelativeDateWrapper(RelativeDate(days=0, time=None, base_date_name='date_from', minutes=60))
|
||||
assert rdw.to_string() == 'RELDATE/minutes/60/date_from/'
|
||||
rdw = RelativeDateWrapper(RelativeDate(days=0, time=None, base_date_name='event__date_from', minutes=60))
|
||||
assert rdw.to_string() == 'RELDATE/minutes/60/event__date_from/'
|
||||
assert rdw.datetime(event) == datetime(2017, 12, 27, 4, 0, 0, tzinfo=TOKYO)
|
||||
rdw = RelativeDateWrapper(RelativeDate(days=0, time=None, base_date_name='date_from', minutes=60, is_after=True))
|
||||
assert rdw.to_string() == 'RELDATE/minutes/60/date_from/after'
|
||||
rdw = RelativeDateWrapper(RelativeDate(days=0, time=None, base_date_name='event__date_from', minutes=60, is_after=True))
|
||||
assert rdw.to_string() == 'RELDATE/minutes/60/event__date_from/after'
|
||||
assert rdw.datetime(event) == datetime(2017, 12, 27, 6, 0, 0, tzinfo=TOKYO)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_relative_date_with_time(event):
|
||||
rdw = RelativeDateWrapper(RelativeDate(days=1, time=time(8, 5, 13), base_date_name='date_from', minutes=None))
|
||||
assert rdw.to_string() == 'RELDATE/1/08:05:13/date_from/'
|
||||
rdw = RelativeDateWrapper(RelativeDate(days=1, time=time(8, 5, 13), base_date_name='event__date_from', minutes=None))
|
||||
assert rdw.to_string() == 'RELDATE/1/08:05:13/event__date_from/'
|
||||
assert rdw.datetime(event) == datetime(2017, 12, 26, 8, 5, 13, tzinfo=TOKYO)
|
||||
rdw = RelativeDateWrapper(RelativeDate(days=1, time=time(8, 5, 13), base_date_name='date_from', minutes=None, is_after=True))
|
||||
assert rdw.to_string() == 'RELDATE/1/08:05:13/date_from/after'
|
||||
rdw = RelativeDateWrapper(RelativeDate(days=1, time=time(8, 5, 13), base_date_name='event__date_from', minutes=None, is_after=True))
|
||||
assert rdw.to_string() == 'RELDATE/1/08:05:13/event__date_from/after'
|
||||
assert rdw.datetime(event) == datetime(2017, 12, 28, 8, 5, 13, tzinfo=TOKYO)
|
||||
|
||||
|
||||
@@ -115,35 +114,94 @@ def test_relative_date_with_time_around_dst(event):
|
||||
event.settings.timezone = "Europe/Berlin"
|
||||
event.date_from = datetime(2020, 3, 29, 18, 0, 0, tzinfo=BERLIN)
|
||||
|
||||
rdw = RelativeDateWrapper(RelativeDate(days=1, time=time(18, 0, 0), base_date_name='date_from', minutes=None))
|
||||
assert rdw.to_string() == 'RELDATE/1/18:00:00/date_from/'
|
||||
rdw = RelativeDateWrapper(RelativeDate(days=1, time=time(18, 0, 0), base_date_name='event__date_from', minutes=None))
|
||||
assert rdw.to_string() == 'RELDATE/1/18:00:00/event__date_from/'
|
||||
assert rdw.datetime(event) == datetime(2020, 3, 28, 18, 0, 0, tzinfo=BERLIN)
|
||||
|
||||
rdw = RelativeDateWrapper(RelativeDate(days=0, time=time(2, 30, 0), base_date_name='date_from', minutes=None))
|
||||
assert rdw.to_string() == 'RELDATE/0/02:30:00/date_from/'
|
||||
rdw = RelativeDateWrapper(RelativeDate(days=0, time=time(2, 30, 0), base_date_name='event__date_from', minutes=None))
|
||||
assert rdw.to_string() == 'RELDATE/0/02:30:00/event__date_from/'
|
||||
assert rdw.datetime(event) == datetime(2020, 3, 29, 2, 30, 0, tzinfo=BERLIN)
|
||||
|
||||
event.date_from = datetime(2020, 10, 25, 18, 0, 0, tzinfo=BERLIN)
|
||||
|
||||
rdw = RelativeDateWrapper(RelativeDate(days=1, time=time(18, 0, 0), base_date_name='date_from', minutes=None))
|
||||
assert rdw.to_string() == 'RELDATE/1/18:00:00/date_from/'
|
||||
rdw = RelativeDateWrapper(RelativeDate(days=1, time=time(18, 0, 0), base_date_name='event__date_from', minutes=None))
|
||||
assert rdw.to_string() == 'RELDATE/1/18:00:00/event__date_from/'
|
||||
assert rdw.datetime(event) == datetime(2020, 10, 24, 18, 0, 0, tzinfo=BERLIN)
|
||||
|
||||
rdw = RelativeDateWrapper(RelativeDate(days=0, time=time(2, 30, 0), base_date_name='date_from', minutes=None))
|
||||
assert rdw.to_string() == 'RELDATE/0/02:30:00/date_from/'
|
||||
rdw = RelativeDateWrapper(RelativeDate(days=0, time=time(2, 30, 0), base_date_name='event__date_from', minutes=None))
|
||||
assert rdw.to_string() == 'RELDATE/0/02:30:00/event__date_from/'
|
||||
assert rdw.datetime(event) == datetime(2020, 10, 25, 2, 30, 0, tzinfo=BERLIN)
|
||||
|
||||
|
||||
def test_unserialize():
|
||||
def test_unserialize_backwards_compatibility():
|
||||
d = datetime(2017, 12, 25, 10, 0, 0, tzinfo=TOKYO)
|
||||
rdw = RelativeDateWrapper.from_string(d.isoformat())
|
||||
assert rdw.data == d
|
||||
|
||||
# keeping the test for the old from_string_format to ensure that we don't break anything
|
||||
rdw = RelativeDateWrapper.from_string('RELDATE/1/-/date_from/')
|
||||
assert rdw.data == RelativeDate(days=1, time=None, base_date_name='date_from', minutes=None)
|
||||
|
||||
# keeping the test for the old from_string_format to ensure that we don't break anything
|
||||
rdw = RelativeDateWrapper.from_string('RELDATE/1/18:05:13/date_from/')
|
||||
assert rdw.data == RelativeDate(days=1, time=time(18, 5, 13), base_date_name='date_from', minutes=None)
|
||||
|
||||
# keeping the test for the old from_string_format to ensure that we don't break anything
|
||||
rdw = RelativeDateWrapper.from_string('RELDATE/minutes/60/date_from/')
|
||||
assert rdw.data == RelativeDate(days=0, time=None, base_date_name='date_from', minutes=60)
|
||||
|
||||
|
||||
def test_backwards_compatibility():
|
||||
# the data model of RelativeDate had to be extended to support other models as relation target
|
||||
# previously only:
|
||||
# - date_from
|
||||
# - date_to
|
||||
# - date_admission
|
||||
# - presale_start
|
||||
# - presale_end
|
||||
# where valid values for the fourth slot (base_date_names) of the serialized form.
|
||||
# the relationship in this case always pointed at event
|
||||
# so any preexisting base_date_names without __ should continue to work and upgrade to event__{old_base_date_name}
|
||||
d = datetime(2017, 12, 25, 10, 0, 0, tzinfo=TOKYO)
|
||||
rdw = RelativeDateWrapper.from_string(d.isoformat())
|
||||
assert rdw.data == d
|
||||
|
||||
rdw = RelativeDateWrapper.from_string('RELDATE/1/-/date_from/')
|
||||
assert rdw.data == RelativeDate(days=1, time=None, base_date_name='date_from', minutes=None)
|
||||
assert rdw.to_string() == 'RELDATE/1/-/event__date_from/'
|
||||
|
||||
rdw = RelativeDateWrapper.from_string('RELDATE/1/18:05:13/date_from/')
|
||||
assert rdw.data == RelativeDate(days=1, time=time(18, 5, 13), base_date_name='date_from', minutes=None)
|
||||
rdw = RelativeDateWrapper.from_string('RELDATE/1/-/date_to/')
|
||||
assert rdw.to_string() == 'RELDATE/1/-/event__date_to/'
|
||||
|
||||
rdw = RelativeDateWrapper.from_string('RELDATE/minutes/60/date_from/')
|
||||
assert rdw.data == RelativeDate(days=0, time=None, base_date_name='date_from', minutes=60)
|
||||
rdw = RelativeDateWrapper.from_string('RELDATE/1/-/date_admission/')
|
||||
assert rdw.to_string() == 'RELDATE/1/-/event__date_admission/'
|
||||
|
||||
rdw = RelativeDateWrapper.from_string('RELDATE/1/-/presale_start/')
|
||||
assert rdw.to_string() == 'RELDATE/1/-/event__presale_start/'
|
||||
|
||||
rdw = RelativeDateWrapper.from_string('RELDATE/1/-/presale_end/')
|
||||
assert rdw.to_string() == 'RELDATE/1/-/event__presale_end/'
|
||||
|
||||
# new order base_date_names should not work without __
|
||||
with pytest.raises(TypeError):
|
||||
RelativeDateWrapper.from_string('RELDATE/1/-/datetime/')
|
||||
with pytest.raises(TypeError):
|
||||
RelativeDateWrapper.from_string('RELDATE/1/-/expires/')
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_relative_to_order(event):
|
||||
with scope(organizer=event.organizer):
|
||||
order_moment = datetime(2020, 3, 29, 18, 0, 0, tzinfo=TOKYO)
|
||||
|
||||
order = Order.objects.create(
|
||||
code='FOO', event=event, email='dummy@dummy.test',
|
||||
status=Order.STATUS_PENDING, secret="k24fiuwvu8kxz3y1",
|
||||
datetime=order_moment,
|
||||
expires=order_moment + timedelta(days=10),
|
||||
sales_channel=event.organizer.sales_channels.get(identifier="web"),
|
||||
total=23, locale='en'
|
||||
)
|
||||
|
||||
rdw = RelativeDateWrapper(RelativeDate(days=1, time=None, base_date_name='order__datetime', minutes=None, is_after=True))
|
||||
assert rdw.datetime(order).astimezone(TOKYO) == datetime(2020, 3, 30, 18, 0, 0, tzinfo=TOKYO)
|
||||
assert rdw.to_string() == 'RELDATE/1/-/order__datetime/after'
|
||||
|
||||
@@ -235,7 +235,7 @@ class WaitingListTestCase(TestCase):
|
||||
self.event.settings.set('waiting_list_enabled', True)
|
||||
self.event.settings.set('waiting_list_auto', True)
|
||||
self.event.settings.waiting_list_auto_disable = RelativeDateWrapper(
|
||||
RelativeDate(days=0, time=None, base_date_name='date_from', minutes=20, is_after=False)
|
||||
RelativeDate(days=0, time=None, base_date_name='event__date_from', minutes=20, is_after=False)
|
||||
)
|
||||
self.event.save()
|
||||
with scope(organizer=self.o):
|
||||
|
||||
@@ -481,7 +481,7 @@ class EventsTest(SoupTest):
|
||||
'payment_term_last_0': 'absolute',
|
||||
'payment_term_last_1': (self.event1.presale_end - datetime.timedelta(1)).strftime('%Y-%m-%d'),
|
||||
'payment_term_last_2': '0',
|
||||
'payment_term_last_3': 'date_from',
|
||||
'payment_term_last_3': 'event__date_from',
|
||||
'tax_rule_payment': 'default',
|
||||
})
|
||||
assert doc.select('.alert-danger')
|
||||
@@ -496,7 +496,7 @@ class EventsTest(SoupTest):
|
||||
'payment_term_last_0': 'relative',
|
||||
'payment_term_last_1': '',
|
||||
'payment_term_last_2': '10',
|
||||
'payment_term_last_3': 'date_from',
|
||||
'payment_term_last_3': 'event__date_from',
|
||||
'tax_rule_payment': 'default',
|
||||
})
|
||||
assert doc.select('.alert-danger')
|
||||
|
||||
@@ -230,19 +230,19 @@ class SubEventsTest(SoupTest):
|
||||
'rel_presale_start_0': 'unset',
|
||||
'rel_presale_start_1': '',
|
||||
'rel_presale_start_2': '1',
|
||||
'rel_presale_start_3': 'date_from',
|
||||
'rel_presale_start_3': 'event__date_from',
|
||||
'rel_presale_start_4': '',
|
||||
'rel_presale_start_5': '',
|
||||
'rel_presale_start_6': 'date_from',
|
||||
'rel_presale_start_6': 'event__date_from',
|
||||
'rel_presale_start_7': 'before',
|
||||
'rel_presale_start_8': 'before',
|
||||
'rel_presale_end_1': '',
|
||||
'rel_presale_end_0': 'relative',
|
||||
'rel_presale_end_2': '1',
|
||||
'rel_presale_end_3': 'date_from',
|
||||
'rel_presale_end_3': 'event__date_from',
|
||||
'rel_presale_end_4': '13:29:31',
|
||||
'rel_presale_end_5': '',
|
||||
'rel_presale_end_6': 'date_from',
|
||||
'rel_presale_end_6': 'event__date_from',
|
||||
'rel_presale_end_7': 'before',
|
||||
'rel_presale_end_8': 'before',
|
||||
'quotas-TOTAL_FORMS': '1',
|
||||
@@ -328,19 +328,19 @@ class SubEventsTest(SoupTest):
|
||||
'rel_presale_start_0': 'unset',
|
||||
'rel_presale_start_1': '',
|
||||
'rel_presale_start_2': '1',
|
||||
'rel_presale_start_3': 'date_from',
|
||||
'rel_presale_start_3': 'event__date_from',
|
||||
'rel_presale_start_4': '',
|
||||
'rel_presale_start_5': '',
|
||||
'rel_presale_start_6': 'date_from',
|
||||
'rel_presale_start_6': 'event__date_from',
|
||||
'rel_presale_start_7': 'before',
|
||||
'rel_presale_start_8': 'before',
|
||||
'rel_presale_end_1': '',
|
||||
'rel_presale_end_0': 'relative',
|
||||
'rel_presale_end_2': '1',
|
||||
'rel_presale_end_3': 'date_from',
|
||||
'rel_presale_end_3': 'event__date_from',
|
||||
'rel_presale_end_4': '13:29:31',
|
||||
'rel_presale_end_5': '',
|
||||
'rel_presale_end_6': 'date_from',
|
||||
'rel_presale_end_6': 'event__date_from',
|
||||
'rel_presale_end_7': 'before',
|
||||
'rel_presale_end_8': 'before',
|
||||
'quotas-TOTAL_FORMS': '1',
|
||||
@@ -403,19 +403,19 @@ class SubEventsTest(SoupTest):
|
||||
'rel_presale_start_0': 'unset',
|
||||
'rel_presale_start_1': '',
|
||||
'rel_presale_start_2': '1',
|
||||
'rel_presale_start_3': 'date_from',
|
||||
'rel_presale_start_3': 'event__date_from',
|
||||
'rel_presale_start_4': '',
|
||||
'rel_presale_start_5': '',
|
||||
'rel_presale_start_6': 'date_from',
|
||||
'rel_presale_start_6': 'event__date_from',
|
||||
'rel_presale_start_7': 'before',
|
||||
'rel_presale_start_8': 'before',
|
||||
'rel_presale_end_1': '',
|
||||
'rel_presale_end_0': 'relative',
|
||||
'rel_presale_end_2': '1',
|
||||
'rel_presale_end_3': 'date_from',
|
||||
'rel_presale_end_3': 'event__date_from',
|
||||
'rel_presale_end_4': '13:29:31',
|
||||
'rel_presale_end_5': '',
|
||||
'rel_presale_end_6': 'date_from',
|
||||
'rel_presale_end_6': 'event__date_from',
|
||||
'rel_presale_end_7': 'before',
|
||||
'rel_presale_end_8': 'before',
|
||||
'quotas-TOTAL_FORMS': '1',
|
||||
@@ -479,19 +479,19 @@ class SubEventsTest(SoupTest):
|
||||
'rel_presale_start_0': 'unset',
|
||||
'rel_presale_start_1': '',
|
||||
'rel_presale_start_2': '1',
|
||||
'rel_presale_start_3': 'date_from',
|
||||
'rel_presale_start_3': 'event__date_from',
|
||||
'rel_presale_start_4': '',
|
||||
'rel_presale_start_5': '',
|
||||
'rel_presale_start_6': 'date_from',
|
||||
'rel_presale_start_6': 'event__date_from',
|
||||
'rel_presale_start_7': 'before',
|
||||
'rel_presale_start_8': 'before',
|
||||
'rel_presale_end_1': '',
|
||||
'rel_presale_end_0': 'relative',
|
||||
'rel_presale_end_2': '1',
|
||||
'rel_presale_end_3': 'date_from',
|
||||
'rel_presale_end_3': 'event__date_from',
|
||||
'rel_presale_end_4': '02:30:00',
|
||||
'rel_presale_end_5': '',
|
||||
'rel_presale_end_6': 'date_from',
|
||||
'rel_presale_end_6': 'event__date_from',
|
||||
'rel_presale_end_7': 'before',
|
||||
'rel_presale_end_8': 'before',
|
||||
'quotas-TOTAL_FORMS': '1',
|
||||
@@ -567,19 +567,19 @@ class SubEventsTest(SoupTest):
|
||||
'rel_presale_start_0': 'unset',
|
||||
'rel_presale_start_1': '',
|
||||
'rel_presale_start_2': '1',
|
||||
'rel_presale_start_3': 'date_from',
|
||||
'rel_presale_start_3': 'event__date_from',
|
||||
'rel_presale_start_4': '',
|
||||
'rel_presale_start_5': '',
|
||||
'rel_presale_start_6': 'date_from',
|
||||
'rel_presale_start_6': 'event__date_from',
|
||||
'rel_presale_start_7': 'before',
|
||||
'rel_presale_start_8': 'before',
|
||||
'rel_presale_end_1': '',
|
||||
'rel_presale_end_0': 'relative',
|
||||
'rel_presale_end_2': '1',
|
||||
'rel_presale_end_3': 'date_from',
|
||||
'rel_presale_end_3': 'event__date_from',
|
||||
'rel_presale_end_4': '13:29:31',
|
||||
'rel_presale_end_5': '',
|
||||
'rel_presale_end_6': 'date_from',
|
||||
'rel_presale_end_6': 'event__date_from',
|
||||
'rel_presale_end_7': 'before',
|
||||
'rel_presale_end_8': 'before',
|
||||
'quotas-TOTAL_FORMS': '1',
|
||||
@@ -639,19 +639,19 @@ class SubEventsTest(SoupTest):
|
||||
'rel_presale_start_0': 'unset',
|
||||
'rel_presale_start_1': '',
|
||||
'rel_presale_start_2': '1',
|
||||
'rel_presale_start_3': 'date_from',
|
||||
'rel_presale_start_3': 'event__date_from',
|
||||
'rel_presale_start_4': '',
|
||||
'rel_presale_start_5': '',
|
||||
'rel_presale_start_6': 'date_from',
|
||||
'rel_presale_start_6': 'event__date_from',
|
||||
'rel_presale_start_7': 'before',
|
||||
'rel_presale_start_8': 'before',
|
||||
'rel_presale_end_0': 'unset',
|
||||
'rel_presale_end_1': '',
|
||||
'rel_presale_end_2': '1',
|
||||
'rel_presale_end_3': 'date_from',
|
||||
'rel_presale_end_3': 'event__date_from',
|
||||
'rel_presale_end_4': '13:29:31',
|
||||
'rel_presale_end_5': '',
|
||||
'rel_presale_end_6': 'date_from',
|
||||
'rel_presale_end_6': 'event__date_from',
|
||||
'rel_presale_end_7': 'before',
|
||||
'rel_presale_end_8': 'before',
|
||||
'quotas-TOTAL_FORMS': '1',
|
||||
@@ -711,19 +711,19 @@ class SubEventsTest(SoupTest):
|
||||
'rel_presale_start_0': 'unset',
|
||||
'rel_presale_start_1': '',
|
||||
'rel_presale_start_2': '1',
|
||||
'rel_presale_start_3': 'date_from',
|
||||
'rel_presale_start_3': 'event__date_from',
|
||||
'rel_presale_start_4': '',
|
||||
'rel_presale_start_5': '',
|
||||
'rel_presale_start_6': 'date_from',
|
||||
'rel_presale_start_6': 'event__date_from',
|
||||
'rel_presale_start_7': 'before',
|
||||
'rel_presale_start_8': 'before',
|
||||
'rel_presale_end_0': 'unset',
|
||||
'rel_presale_end_1': '',
|
||||
'rel_presale_end_2': '1',
|
||||
'rel_presale_end_3': 'date_from',
|
||||
'rel_presale_end_3': 'event__date_from',
|
||||
'rel_presale_end_4': '13:29:31',
|
||||
'rel_presale_end_5': '',
|
||||
'rel_presale_end_6': 'date_from',
|
||||
'rel_presale_end_6': 'event__date_from',
|
||||
'rel_presale_end_7': 'before',
|
||||
'rel_presale_end_8': 'before',
|
||||
'quotas-TOTAL_FORMS': '1',
|
||||
|
||||
@@ -224,3 +224,28 @@ def test_one_view(logged_in_client, url, expected, event, item, item_category, o
|
||||
)
|
||||
response = logged_in_client.get(url)
|
||||
assert response.status_code == expected
|
||||
|
||||
# Do not reintroduce any CSP nonces into control responses, as discussed in PR #6387
|
||||
if response['Content-Type'] != 'application/json':
|
||||
assert 'script-src' in response['Content-Security-Policy']
|
||||
assert 'nonce-' not in response['Content-Security-Policy']
|
||||
|
||||
|
||||
@pytest.mark.parametrize('url', [
|
||||
'/control/login',
|
||||
'/',
|
||||
'/{orga}/{event}/',
|
||||
])
|
||||
@pytest.mark.django_db
|
||||
def test_csp_header_unauthenticated(client, url, event):
|
||||
# Do not reintroduce any CSP nonces into most presale responses, as discussed in PR #6387
|
||||
with scope(organizer=event.organizer):
|
||||
url = url.format(
|
||||
event=event.slug, orga=event.organizer.slug,
|
||||
)
|
||||
event.live = True
|
||||
event.save()
|
||||
response = client.get(url)
|
||||
assert response.status_code == 200
|
||||
assert 'script-src' in response['Content-Security-Policy']
|
||||
assert 'nonce-' not in response['Content-Security-Policy']
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
import json
|
||||
from datetime import timedelta
|
||||
from decimal import Decimal
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
from django.utils.timezone import now
|
||||
@@ -409,7 +410,8 @@ def test_webhook_mark_paid(env, client, monkeypatch):
|
||||
order.payments.update(state=OrderPayment.PAYMENT_STATE_PENDING)
|
||||
|
||||
pp_order = Result(get_test_order())
|
||||
monkeypatch.setattr("paypalcheckoutsdk.orders.OrdersGetRequest", lambda *args: pp_order)
|
||||
mock_orders_get_request = MagicMock(return_value=pp_order)
|
||||
monkeypatch.setattr("paypalcheckoutsdk.orders.OrdersGetRequest", mock_orders_get_request)
|
||||
monkeypatch.setattr("pretix.plugins.paypal2.payment.PaypalMethod.init_api", init_api)
|
||||
with scopes_disabled():
|
||||
ReferencedPayPalObject.objects.create(order=order, payment=order.payments.first(),
|
||||
@@ -497,7 +499,7 @@ def test_webhook_mark_paid(env, client, monkeypatch):
|
||||
"resource_version": "2.0"
|
||||
}
|
||||
), content_type='application_json')
|
||||
|
||||
mock_orders_get_request.assert_called_once_with('806440346Y391300T')
|
||||
order.refresh_from_db()
|
||||
assert order.status == Order.STATUS_PAID
|
||||
|
||||
|
||||
@@ -697,6 +697,42 @@ class CartTest(CartTestMixin, TestCase):
|
||||
self.assertIsNone(objs[0].variation)
|
||||
self.assertEqual(objs[0].price, 23)
|
||||
|
||||
def test_free_price_rounding(self):
|
||||
self.ticket.free_price = True
|
||||
self.ticket.save()
|
||||
|
||||
response = self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), {
|
||||
'item_%d' % self.ticket.id: '1',
|
||||
'price_%d' % self.ticket.id: '40.1234',
|
||||
}, follow=True)
|
||||
self.assertRedirects(response, '/%s/%s/?require_cookie=true' % (self.orga.slug, self.event.slug),
|
||||
target_status_code=200)
|
||||
|
||||
with scopes_disabled():
|
||||
cr1 = CartPosition.objects.get()
|
||||
assert cr1.listed_price == Decimal('23.00')
|
||||
assert cr1.custom_price_input == Decimal('40.12')
|
||||
assert cr1.price == Decimal('40.12')
|
||||
|
||||
def test_free_price_rounding_jpy(self):
|
||||
self.event.currency = "JPY"
|
||||
self.event.save()
|
||||
self.ticket.free_price = True
|
||||
self.ticket.save()
|
||||
|
||||
response = self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), {
|
||||
'item_%d' % self.ticket.id: '1',
|
||||
'price_%d' % self.ticket.id: '40.1234',
|
||||
}, follow=True)
|
||||
self.assertRedirects(response, '/%s/%s/?require_cookie=true' % (self.orga.slug, self.event.slug),
|
||||
target_status_code=200)
|
||||
|
||||
with scopes_disabled():
|
||||
cr1 = CartPosition.objects.get()
|
||||
assert cr1.listed_price == Decimal('23.00')
|
||||
assert cr1.custom_price_input == Decimal('40.00')
|
||||
assert cr1.price == Decimal('40.00')
|
||||
|
||||
def test_variation_inactive(self):
|
||||
self.shirt_red.active = False
|
||||
self.shirt_red.save()
|
||||
@@ -3060,6 +3096,31 @@ class CartAddonTest(CartTestMixin, TestCase):
|
||||
assert cp1.addons.count() == 3
|
||||
assert all(a.price == Decimal('12.00') for a in cp1.addons.all())
|
||||
|
||||
@classscope(attr='orga')
|
||||
def test_free_price_rounding(self):
|
||||
self.event.settings.locales = ['de']
|
||||
self.event.settings.locale = 'de'
|
||||
self.event.currency = "JPY"
|
||||
self.event.save()
|
||||
|
||||
self.workshop1.free_price = True
|
||||
self.workshop1.save()
|
||||
cp1 = CartPosition.objects.create(
|
||||
event=self.event, cart_id=self.session_key, item=self.ticket,
|
||||
price=23, expires=now() - timedelta(minutes=10)
|
||||
)
|
||||
|
||||
response = self.client.post('/%s/%s/checkout/addons/' % (self.orga.slug, self.event.slug), {
|
||||
'cp_{}_item_{}'.format(cp1.pk, self.workshop1.pk): '1',
|
||||
'cp_{}_item_{}_price'.format(cp1.pk, self.workshop1.pk): '99,99',
|
||||
}, follow=True)
|
||||
self.assertRedirects(response, '/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug),
|
||||
target_status_code=200)
|
||||
with scopes_disabled():
|
||||
assert cp1.addons.count() == 1
|
||||
assert cp1.addons.first().item == self.workshop1
|
||||
assert cp1.addons.first().price == Decimal('100')
|
||||
|
||||
@classscope(attr='orga')
|
||||
def test_change_number(self):
|
||||
cp1 = CartPosition.objects.create(
|
||||
|
||||
@@ -1132,7 +1132,7 @@ class WaitingListTest(EventTestMixin, SoupTest):
|
||||
def test_auto_disable(self):
|
||||
self.event.settings.set('waiting_list_enabled', True)
|
||||
self.event.settings.waiting_list_auto_disable = RelativeDateWrapper(
|
||||
RelativeDate(days=900, time=datetime.time(9, 0, 0), base_date_name='date_from', minutes=None, is_after=False)
|
||||
RelativeDate(days=900, time=datetime.time(9, 0, 0), base_date_name='event__date_from', minutes=None, is_after=False)
|
||||
)
|
||||
response = self.client.get(
|
||||
'/%s/%s/' % (self.orga.slug, self.event.slug)
|
||||
|
||||
@@ -1527,7 +1527,7 @@ class OrderChangeAddonsTest(BaseOrdersTest):
|
||||
self.order.save()
|
||||
self._subevent_setup()
|
||||
self.event.settings.set('payment_term_last', RelativeDateWrapper(
|
||||
RelativeDate(days=2, time=None, base_date_name='date_from', minutes=None)
|
||||
RelativeDate(days=2, time=None, base_date_name='event__date_from', minutes=None)
|
||||
))
|
||||
|
||||
response = self.client.get(
|
||||
|
||||
@@ -1076,7 +1076,7 @@ class OrdersTest(BaseOrdersTest):
|
||||
self.event.date_from = now() + datetime.timedelta(days=3)
|
||||
self.event.save()
|
||||
self.event.settings.set('ticket_download_date', RelativeDateWrapper(RelativeDate(
|
||||
base_date_name='date_from', days=2, time=None, minutes=None
|
||||
base_date_name='event__date_from', days=2, time=None, minutes=None
|
||||
)))
|
||||
response = self.client.post(
|
||||
'/%s/%s/order/%s/%s/download/%d/testdummy' % (self.orga.slug, self.event.slug, self.order.code,
|
||||
|
||||
@@ -173,6 +173,7 @@ class WidgetCartTest(CartTestMixin, TestCase):
|
||||
"frontpage_text": "",
|
||||
"location": "",
|
||||
"currency": "EUR",
|
||||
"currency_places": 2,
|
||||
"show_variations_expanded": False,
|
||||
"display_net_prices": False,
|
||||
"use_native_spinners": False,
|
||||
@@ -379,6 +380,7 @@ class WidgetCartTest(CartTestMixin, TestCase):
|
||||
"frontpage_text": "",
|
||||
"location": "",
|
||||
"currency": "EUR",
|
||||
"currency_places": 2,
|
||||
"show_variations_expanded": False,
|
||||
"display_net_prices": False,
|
||||
"use_native_spinners": False,
|
||||
@@ -439,6 +441,7 @@ class WidgetCartTest(CartTestMixin, TestCase):
|
||||
"frontpage_text": "",
|
||||
"location": "",
|
||||
"currency": "EUR",
|
||||
"currency_places": 2,
|
||||
"show_variations_expanded": False,
|
||||
"display_net_prices": False,
|
||||
"use_native_spinners": False,
|
||||
@@ -524,6 +527,7 @@ class WidgetCartTest(CartTestMixin, TestCase):
|
||||
"frontpage_text": "",
|
||||
"location": "",
|
||||
"currency": "EUR",
|
||||
"currency_places": 2,
|
||||
'poweredby': '<a href="https://pretix.eu" target="_blank" rel="noopener">ticketing powered by pretix</a>',
|
||||
"show_variations_expanded": False,
|
||||
"display_net_prices": False,
|
||||
|
||||
Reference in New Issue
Block a user