/',
+ 'speakers//',
speaker.SpeakerView.as_view(),
name='speaker',
),
path(
- 'speaker//og-image',
+ 'speakers//og-image',
speaker.SpeakerSocialMediaCard.as_view(),
name='speaker-social',
),
path(
- 'speaker//talks.ics',
+ 'speakers//talks.ics',
speaker.SpeakerTalksIcalView.as_view(),
name='speaker.talks.ical',
),
diff --git a/app/eventyay/agenda/views/schedule.py b/app/eventyay/agenda/views/schedule.py
index 4ffd53272b..23c4c17ddf 100644
--- a/app/eventyay/agenda/views/schedule.py
+++ b/app/eventyay/agenda/views/schedule.py
@@ -171,7 +171,7 @@ def my_exporters(self):
@context
def show_talk_list(self):
- return self.request.path.endswith('/talk/') or self.request.event.display_settings['schedule'] == 'list'
+ return self.request.path.endswith('/sessions/') or self.request.event.display_settings['schedule'] == 'list'
@cache_page(60 * 60 * 24)
diff --git a/app/eventyay/agenda/views/talk.py b/app/eventyay/agenda/views/talk.py
index c7eac14f0d..e915b1a44a 100644
--- a/app/eventyay/agenda/views/talk.py
+++ b/app/eventyay/agenda/views/talk.py
@@ -360,15 +360,13 @@ def check_user_owning_ticket(user: User, event: Event) -> TicketCheckResult:
# NOTE: It doesn't work with the Docker setup for development, because we use fake domain then,
and inside the container, the fake domain points to the container itself, not the host.
"""
- if 'ticket_link' not in event.display_settings:
- logger.info('display_settings[ticket_link] is missing.')
- return TicketCheckResult.MISCONFIGURED
- base_url, organizer_slug, event_slug = extract_event_info_from_url(
- event.display_settings['ticket_link']
- )
- if not organizer_slug or not event_slug or not base_url:
- logger.info('display_settings[ticket_link] is not valid.')
- return TicketCheckResult.MISCONFIGURED
+ # Use unified ticket base path and event slugs; no manual URL needed
+ base_url = settings.EVENTYAY_TICKET_BASE_PATH
+ # Normalize base URL to keep urljoin from dropping path segments
+ if not base_url.endswith('/'):
+ base_url = f'{base_url}/'
+ organizer_slug = event.organizer.slug
+ event_slug = event.slug
check_payload = {'user_email': user.email}
# call to ticket to check if user order ticket yet or not
api_url = urljoin(base_url, f'api/v1/{organizer_slug}/{event_slug}/ticket-check')
diff --git a/talk/src/pretalx/api/documentation.py b/app/eventyay/api/documentation.py
similarity index 94%
rename from talk/src/pretalx/api/documentation.py
rename to app/eventyay/api/documentation.py
index 8e1bd2ae0c..7c4d008dbc 100644
--- a/talk/src/pretalx/api/documentation.py
+++ b/app/eventyay/api/documentation.py
@@ -1,7 +1,7 @@
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import OpenApiParameter
-from pretalx.mail.models import MailTemplateRoles
+from eventyay.base.models.mail import MailTemplateRoles
def build_expand_docs(*params):
@@ -58,7 +58,7 @@ def postprocess_schema(result, generator, request, public):
{
"name": "teams",
"description": (
- "Access permissions for events are organised in teams within an organiser. "
+ "Access permissions for events are organised in teams within an organizer. "
"This is the only API endpoint that does not use the event setting on your access token, as teams exist outside the event structure."
),
},
@@ -78,14 +78,14 @@ def postprocess_schema(result, generator, request, public):
},
{
"name": "speakers",
- "description": "Speakers can currently only updated, not created or deleted, as a speaker refers to a user object, and users can only be deleted by administrators. Organisers will see additional fields in the API, in line with the response to the update actions.",
+ "description": "Speakers can currently only updated, not created or deleted, as a speaker refers to a user object, and users can only be deleted by administrators. organizers will see additional fields in the API, in line with the response to the update actions.",
},
{
"name": "schedules",
"description": (
"In pretalx, an event’s schedule is versioned, and each version is a schedule object in the API. "
"In addition to the normal ID based routing, you can use the `latest` shortcut to see the current public schedule, "
- "and as organiser, the `wip` shortcut will show the current unpublished working copy. "
+ "and as organizer, the `wip` shortcut will show the current unpublished working copy. "
"As retrieving the fully expanded endpoint is expensive on the pretalx side, "
"consider using the redirect offered at ``by-version/?version=latest`` to check for a new release."
),
@@ -100,7 +100,7 @@ def postprocess_schema(result, generator, request, public):
},
{
"name": "rooms",
- "description": "Rooms are part of conference schedules. Only once the conference schedule is public will the rooms API be available to unauthenticated users. Authenticated organisers will see additional fields in the API, in line with the create and update actions.",
+ "description": "Rooms are part of conference schedules. Only once the conference schedule is public will the rooms API be available to unauthenticated users. Authenticated organizers will see additional fields in the API, in line with the create and update actions.",
},
{
"name": "submission-types",
@@ -112,7 +112,7 @@ def postprocess_schema(result, generator, request, public):
},
{
"name": "tags",
- "description": "Tags are currently only used in the organiser backend and not publicly. As such, all tag endpoints require authentication.",
+ "description": "Tags are currently only used in the organizer backend and not publicly. As such, all tag endpoints require authentication.",
"externalDocs": {
"url": "https://docs.pretalx.org/user/sessions/#tags",
"description": "User documentation",
@@ -129,7 +129,7 @@ def postprocess_schema(result, generator, request, public):
{
"name": "questions",
"description": (
- "The questions resource represents all fields created by organisers via the flexible “custom fields” model, "
+ "The questions resource represents all fields created by organizers via the flexible “custom fields” model, "
"with the answers available under the ``/answers/`` endpoint."
),
},
@@ -142,7 +142,7 @@ def postprocess_schema(result, generator, request, public):
{
"name": "answers",
"description": (
- "The answers resource represents all data collected by organisers via the flexible “custom fields” model, which "
+ "The answers resource represents all data collected by organizers via the flexible “custom fields” model, which "
"allows for nearly arbitrary data collection from speakers or reviewers. "
),
},
diff --git a/talk/src/pretalx/api/exceptions.py b/app/eventyay/api/exceptions.py
similarity index 100%
rename from talk/src/pretalx/api/exceptions.py
rename to app/eventyay/api/exceptions.py
diff --git a/talk/src/pretalx/api/filters/review.py b/app/eventyay/api/filters/review.py
similarity index 91%
rename from talk/src/pretalx/api/filters/review.py
rename to app/eventyay/api/filters/review.py
index fccb7b89f3..1fea190a76 100644
--- a/talk/src/pretalx/api/filters/review.py
+++ b/app/eventyay/api/filters/review.py
@@ -1,8 +1,9 @@
import django_filters
from django_scopes import scopes_disabled
-from pretalx.person.models import User
-from pretalx.submission.models import Review, Submission
+from eventyay.base.models.auth import User
+from eventyay.base.models.submission import Submission
+from eventyay.base.models.review import Review
with scopes_disabled():
diff --git a/talk/src/pretalx/api/filters/schedule.py b/app/eventyay/api/filters/schedule.py
similarity index 87%
rename from talk/src/pretalx/api/filters/schedule.py
rename to app/eventyay/api/filters/schedule.py
index 8f32e5ad34..3cdc3c812f 100644
--- a/talk/src/pretalx/api/filters/schedule.py
+++ b/app/eventyay/api/filters/schedule.py
@@ -1,9 +1,11 @@
import django_filters
from django_scopes import scopes_disabled
-from pretalx.person.models import User
-from pretalx.schedule.models import Room, Schedule, TalkSlot
-from pretalx.submission.models import Submission
+from eventyay.base.models.auth import User
+from eventyay.base.models.schedule import Schedule
+from eventyay.base.models.slot import TalkSlot
+from eventyay.base.models.room import Room
+from eventyay.base.models.submission import Submission
with scopes_disabled():
diff --git a/talk/src/pretalx/api/mixins.py b/app/eventyay/api/mixins.py
similarity index 98%
rename from talk/src/pretalx/api/mixins.py
rename to app/eventyay/api/mixins.py
index 43d33f9344..60600d4ed9 100644
--- a/talk/src/pretalx/api/mixins.py
+++ b/app/eventyay/api/mixins.py
@@ -7,7 +7,7 @@
from rest_framework import exceptions
from rest_framework.serializers import ModelSerializer
-from pretalx.api.versions import get_api_version_from_request, get_serializer_by_version
+from eventyay.api.versions import get_api_version_from_request, get_serializer_by_version
class ApiVersionException(exceptions.APIException):
diff --git a/talk/src/pretalx/api/pagination.py b/app/eventyay/api/pagination.py
similarity index 100%
rename from talk/src/pretalx/api/pagination.py
rename to app/eventyay/api/pagination.py
diff --git a/talk/src/pretalx/api/permissions.py b/app/eventyay/api/permissions.py
similarity index 98%
rename from talk/src/pretalx/api/permissions.py
rename to app/eventyay/api/permissions.py
index a6c6d25f46..8e5eacdbda 100644
--- a/talk/src/pretalx/api/permissions.py
+++ b/app/eventyay/api/permissions.py
@@ -1,6 +1,6 @@
from rest_framework.permissions import BasePermission
-from pretalx.person.rules import is_only_reviewer
+from eventyay.talk_rules.person import is_only_reviewer
MODEL_PERMISSION_MAP = {
"list": "list",
@@ -22,7 +22,7 @@
class ApiPermission(BasePermission):
def get_permission_object(self, view, obj, request, detail=False):
- return obj or getattr(request, "event", None) or request.organiser
+ return obj or getattr(request, "event", None) or request.organizer
def has_permission(self, request, view):
return self._has_permission(view, None, request)
diff --git a/talk/src/pretalx/api/serializers/access_code.py b/app/eventyay/api/serializers/access_code.py
similarity index 78%
rename from talk/src/pretalx/api/serializers/access_code.py
rename to app/eventyay/api/serializers/access_code.py
index ca475de53d..b9bd91c1cf 100644
--- a/talk/src/pretalx/api/serializers/access_code.py
+++ b/app/eventyay/api/serializers/access_code.py
@@ -1,8 +1,8 @@
from rest_flex_fields.serializers import FlexFieldsSerializerMixin
-from pretalx.api.mixins import PretalxSerializer
-from pretalx.api.versions import CURRENT_VERSIONS, register_serializer
-from pretalx.submission.models import SubmitterAccessCode
+from eventyay.api.mixins import PretalxSerializer
+from eventyay.api.versions import CURRENT_VERSIONS, register_serializer
+from eventyay.base.models.access_code import SubmitterAccessCode
@register_serializer(versions=CURRENT_VERSIONS)
@@ -20,11 +20,11 @@ class Meta:
)
expandable_fields = {
"track": (
- "pretalx.api.serializers.submission.TrackSerializer",
+ "eventyay.api.serializers.submission.TrackSerializer",
{"read_only": True},
),
"submission_type": (
- "pretalx.api.serializers.submission.SubmissionTypeSerializer",
+ "eventyay.api.serializers.submission.SubmissionTypeSerializer",
{"read_only": True},
),
}
diff --git a/talk/src/pretalx/api/serializers/availability.py b/app/eventyay/api/serializers/availability.py
similarity index 94%
rename from talk/src/pretalx/api/serializers/availability.py
rename to app/eventyay/api/serializers/availability.py
index 4b5eb7d5d4..83dc7d1335 100644
--- a/talk/src/pretalx/api/serializers/availability.py
+++ b/app/eventyay/api/serializers/availability.py
@@ -1,7 +1,7 @@
from django.db import transaction
from rest_framework.serializers import BooleanField, ModelSerializer
-from pretalx.schedule.models import Availability
+from eventyay.base.models.availability import Availability
class AvailabilitySerializer(ModelSerializer):
diff --git a/app/eventyay/api/serializers/event.py b/app/eventyay/api/serializers/event.py
index 7fc9a3869c..584be70992 100644
--- a/app/eventyay/api/serializers/event.py
+++ b/app/eventyay/api/serializers/event.py
@@ -71,7 +71,7 @@ def to_internal_value(self, data):
class PluginsField(Field):
def to_representation(self, obj):
- from pretix.base.plugins import get_all_plugins
+ from eventyay.base.plugins import get_all_plugins
return sorted(
[
@@ -222,7 +222,7 @@ def validate_seat_category_mapping(self, value):
return {'seat_category_mapping': result}
def validate_plugins(self, value):
- from pretix.base.plugins import get_all_plugins
+ from eventyay.base.plugins import get_all_plugins
plugins_available = {
p.module
diff --git a/talk/src/pretalx/api/serializers/legacy.py b/app/eventyay/api/serializers/legacy.py
similarity index 92%
rename from talk/src/pretalx/api/serializers/legacy.py
rename to app/eventyay/api/serializers/legacy.py
index d1bc2f6458..c587c6ff40 100644
--- a/talk/src/pretalx/api/serializers/legacy.py
+++ b/app/eventyay/api/serializers/legacy.py
@@ -8,23 +8,22 @@
SlugRelatedField,
)
-from pretalx.api.mixins import PretalxSerializer
-from pretalx.api.serializers.question import AnswerSerializer
-from pretalx.api.serializers.room import AvailabilitySerializer
-from pretalx.api.serializers.submission import ResourceSerializer
-from pretalx.api.versions import LEGACY, register_serializer
-from pretalx.person.models import SpeakerProfile, User
-from pretalx.schedule.models import Availability, Room, Schedule, TalkSlot
-from pretalx.submission.models import (
- Answer,
- AnswerOption,
- Question,
- Resource,
- Review,
- Submission,
- SubmissionStates,
- Tag,
-)
+from eventyay.api.mixins import PretalxSerializer
+from eventyay.api.serializers.question import AnswerSerializer
+from eventyay.api.serializers.room import AvailabilitySerializer
+from eventyay.api.serializers.submission import ResourceSerializer
+from eventyay.api.versions import LEGACY, register_serializer
+from eventyay.base.models.profile import SpeakerProfile
+from eventyay.base.models.auth import User
+from eventyay.base.models.availability import Availability
+from eventyay.base.models.question import Answer, AnswerOption, TalkQuestion
+from eventyay.base.models.room import Room
+from eventyay.base.models.schedule import Schedule
+from eventyay.base.models.slot import TalkSlot
+from eventyay.base.models.resource import Resource
+from eventyay.base.models.review import Review
+from eventyay.base.models.submission import Submission, SubmissionStates
+from eventyay.base.models.tag import Tag
class LegacySubmitterSerializer(ModelSerializer):
@@ -342,7 +341,7 @@ class LegacyQuestionSerializer(ModelSerializer):
options = LegacyAnswerOptionSerializer(many=True, required=False)
class Meta:
- model = Question
+ model = TalkQuestion
fields = (
"id",
"variant",
@@ -377,7 +376,7 @@ class LegacyAnswerSerializer(ModelSerializer):
required=False,
)
options = LegacyAnswerOptionSerializer(many=True, required=False)
- question = LegacyQuestionSerializer(Question.objects.none())
+ question = LegacyQuestionSerializer(TalkQuestion.objects.none())
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
diff --git a/talk/src/pretalx/api/serializers/mail.py b/app/eventyay/api/serializers/mail.py
similarity index 85%
rename from talk/src/pretalx/api/serializers/mail.py
rename to app/eventyay/api/serializers/mail.py
index ddf89ab897..05f7002d33 100644
--- a/talk/src/pretalx/api/serializers/mail.py
+++ b/app/eventyay/api/serializers/mail.py
@@ -1,9 +1,9 @@
from rest_framework import exceptions
-from pretalx.api.mixins import PretalxSerializer
-from pretalx.api.versions import CURRENT_VERSIONS, register_serializer
-from pretalx.mail.context import get_invalid_placeholders
-from pretalx.mail.models import MailTemplate
+from eventyay.api.mixins import PretalxSerializer
+from eventyay.api.versions import CURRENT_VERSIONS, register_serializer
+from eventyay.mail.context import get_invalid_placeholders
+from eventyay.base.models.mail import MailTemplate
@register_serializer(versions=CURRENT_VERSIONS)
diff --git a/talk/src/pretalx/api/serializers/question.py b/app/eventyay/api/serializers/question.py
similarity index 83%
rename from talk/src/pretalx/api/serializers/question.py
rename to app/eventyay/api/serializers/question.py
index 9f548bd2c6..877ae3c6c5 100644
--- a/talk/src/pretalx/api/serializers/question.py
+++ b/app/eventyay/api/serializers/question.py
@@ -3,22 +3,22 @@
from rest_framework import exceptions
from rest_framework.serializers import PrimaryKeyRelatedField, SlugRelatedField
-from pretalx.api.mixins import PretalxSerializer
-from pretalx.api.serializers.fields import UploadedFileField
-from pretalx.api.versions import CURRENT_VERSIONS, register_serializer
-from pretalx.person.models import User
-from pretalx.submission.models import (
+from eventyay.api.mixins import PretalxSerializer
+from eventyay.api.serializers.fields import UploadedFileField
+from eventyay.api.versions import CURRENT_VERSIONS, register_serializer
+from eventyay.base.models import User
+from eventyay.base.models.question import (
Answer,
AnswerOption,
- Question,
- QuestionTarget,
- QuestionVariant,
- Review,
- Submission,
- SubmissionType,
- Track,
+ TalkQuestion,
+ TalkQuestionTarget,
+ TalkQuestionVariant,
)
-from pretalx.submission.rules import questions_for_user
+from eventyay.base.models.review import Review
+from eventyay.base.models.submission import Submission
+from eventyay.base.models.track import Track
+from eventyay.base.models.type import SubmissionType
+from eventyay.talk_rules.submission import questions_for_user
@register_serializer(versions=CURRENT_VERSIONS)
@@ -30,7 +30,7 @@ class Meta:
fields = ("id", "question", "answer", "position")
expandable_fields = {
"question": (
- "pretalx.api.serializers.question.QuestionSerializer",
+ "eventyay.api.serializers.question.QuestionSerializer",
{"read_only": True, "omit": ["options"]},
)
}
@@ -42,7 +42,7 @@ class Meta:
# we might as well use it to isolate the create action fully.
@register_serializer(versions=CURRENT_VERSIONS)
class AnswerOptionCreateSerializer(AnswerOptionSerializer):
- question = PrimaryKeyRelatedField(read_only=False, queryset=Question.objects.none())
+ question = PrimaryKeyRelatedField(read_only=False, queryset=TalkQuestion.objects.none())
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -52,12 +52,12 @@ def __init__(self, *args, **kwargs):
manager="all_objects"
).filter(
variant__in=[
- QuestionVariant.CHOICES,
- QuestionVariant.MULTIPLE,
+ TalkQuestionVariant.CHOICES,
+ TalkQuestionVariant.MULTIPLE,
]
)
else:
- self.fields["question"].queryset = Question.objects.none()
+ self.fields["question"].queryset = TalkQuestion.objects.none()
class Meta(AnswerOptionSerializer.Meta):
expandable_fields = None
@@ -66,7 +66,7 @@ class Meta(AnswerOptionSerializer.Meta):
@register_serializer(versions=CURRENT_VERSIONS)
class QuestionSerializer(FlexFieldsSerializerMixin, PretalxSerializer):
class Meta:
- model = Question
+ model = TalkQuestion
fields = (
"id",
"question",
@@ -92,7 +92,7 @@ class Meta:
)
expandable_fields = {
"options": (
- "pretalx.api.serializers.question.AnswerOptionSerializer",
+ "eventyay.api.serializers.question.AnswerOptionSerializer",
{
"many": True,
"read_only": True,
@@ -100,11 +100,11 @@ class Meta:
},
),
"tracks": (
- "pretalx.api.serializers.submission.TrackSerializer",
+ "eventyay.api.serializers.submission.TrackSerializer",
{"many": True, "read_only": True},
),
"submission_types": (
- "pretalx.api.serializers.submission.SubmissionTypeSerializer",
+ "eventyay.api.serializers.submission.SubmissionTypeSerializer",
{"many": True, "read_only": True},
),
}
@@ -195,15 +195,15 @@ class Meta:
)
expandable_fields = {
"question": (
- "pretalx.api.serializers.question.QuestionSerializer",
+ "eventyay.api.serializers.question.QuestionSerializer",
{"read_only": True, "omit": ("options",)},
),
"options": (
- "pretalx.api.serializers.question.AnswerOptionSerializer",
+ "eventyay.api.serializers.question.AnswerOptionSerializer",
{"many": True, "read_only": True, "omit": ("question",)},
),
"person": (
- "pretalx.api.serializers.speaker.SpeakerSerializer",
+ "eventyay.api.serializers.speaker.SpeakerSerializer",
{"read_only": True, "omit": ("answers",)},
),
# submissions and reviews are currently not expandable due to permissions
@@ -215,7 +215,7 @@ class Meta:
@register_serializer(versions=CURRENT_VERSIONS)
class AnswerCreateSerializer(AnswerSerializer):
- question = PrimaryKeyRelatedField(queryset=Question.objects.none())
+ question = PrimaryKeyRelatedField(queryset=TalkQuestion.objects.none())
submission = SlugRelatedField(
queryset=Submission.objects.none(),
slug_field="code",
@@ -256,7 +256,7 @@ def __init__(self, *args, **kwargs):
def validate(self, data):
question = self.get_with_fallback(data, "question")
- if question.variant in (QuestionVariant.CHOICES, QuestionVariant.MULTIPLE):
+ if question.variant in (TalkQuestionVariant.CHOICES, TalkQuestionVariant.MULTIPLE):
options = self.get_with_fallback(data, "options")
if not options:
raise exceptions.ValidationError(
@@ -276,29 +276,29 @@ def validate(self, data):
submission = self.get_with_fallback(data, "submission")
review = self.get_with_fallback(data, "review")
person = self.get_with_fallback(data, "person")
- if target == QuestionTarget.SUBMISSION and not submission:
+ if target == TalkQuestionTarget.SUBMISSION and not submission:
raise exceptions.ValidationError(
{"submission": "This field is required for submission questions."}
)
- if target == QuestionTarget.REVIEWER and not review:
+ if target == TalkQuestionTarget.REVIEWER and not review:
raise exceptions.ValidationError(
{"review": "This field is required for reviewer questions."}
)
- if target == QuestionTarget.SPEAKER and not person:
+ if target == TalkQuestionTarget.SPEAKER and not person:
raise exceptions.ValidationError(
{"person": "This field is required for speaker questions."}
)
# Only allow the field matching the question target
- if target == QuestionTarget.SUBMISSION and review:
+ if target == TalkQuestionTarget.SUBMISSION and review:
raise exceptions.ValidationError(
{"review": "Cannot set review for submission question."}
)
- if target == QuestionTarget.REVIEWER and submission:
+ if target == TalkQuestionTarget.REVIEWER and submission:
raise exceptions.ValidationError(
{"submission": "Cannot set submission for reviewer question."}
)
- if target == QuestionTarget.SPEAKER and submission:
+ if target == TalkQuestionTarget.SPEAKER and submission:
raise exceptions.ValidationError(
{"submission": "Cannot set submission for speaker question."}
)
diff --git a/talk/src/pretalx/api/serializers/review.py b/app/eventyay/api/serializers/review.py
similarity index 84%
rename from talk/src/pretalx/api/serializers/review.py
rename to app/eventyay/api/serializers/review.py
index 37780090f2..f5c3998b68 100644
--- a/talk/src/pretalx/api/serializers/review.py
+++ b/app/eventyay/api/serializers/review.py
@@ -2,16 +2,16 @@
from rest_framework.exceptions import ValidationError
from rest_framework.serializers import SlugRelatedField
-from pretalx.api.mixins import PretalxSerializer
-from pretalx.api.serializers.question import AnswerSerializer
-from pretalx.api.versions import CURRENT_VERSIONS, register_serializer
-from pretalx.person.models import User
-from pretalx.submission.models import (
+from eventyay.api.mixins import PretalxSerializer
+from eventyay.api.serializers.question import AnswerSerializer
+from eventyay.api.versions import CURRENT_VERSIONS, register_serializer
+from eventyay.base.models.auth import User
+from eventyay.base.models.review import (
Review,
ReviewScore,
ReviewScoreCategory,
- Submission,
)
+from eventyay.base.models.submission import Submission
@register_serializer(versions=CURRENT_VERSIONS)
@@ -29,7 +29,7 @@ class Meta:
)
expandable_fields = {
"limit_tracks": (
- "pretalx.api.serializers.submission.TrackSerializer",
+ "eventyay.api.serializers.submission.TrackSerializer",
{"read_only": True, "many": True},
),
}
@@ -42,7 +42,7 @@ class Meta:
fields = ("id", "category", "value", "label")
expandable_fields = {
"category": (
- "pretalx.api.serializers.review.ReviewScoreCategorySerializer",
+ "eventyay.api.serializers.review.ReviewScoreCategorySerializer",
{"read_only": True},
),
}
@@ -87,19 +87,19 @@ class Meta:
read_only_fields = ("submission",)
expandable_fields = {
"submission": (
- "pretalx.api.serializers.submission.SubmissionSerializer",
+ "eventyay.api.serializers.submission.SubmissionSerializer",
{"read_only": True, "omit": ("slots",)},
),
"answers": (
- "pretalx.api.serializers.question.AnswerSerializer",
+ "eventyay.api.serializers.question.AnswerSerializer",
{"read_only": True, "many": True},
),
"user": (
- "pretalx.api.serializers.review.ReviewerSerializer",
+ "eventyay.api.serializers.review.ReviewerSerializer",
{"read_only": True},
),
"scores": (
- "pretalx.api.serializers.review.ReviewScoreSerializer",
+ "eventyay.api.serializers.review.ReviewScoreSerializer",
{"read_only": True, "many": True},
),
}
diff --git a/talk/src/pretalx/api/serializers/room.py b/app/eventyay/api/serializers/room.py
similarity index 88%
rename from talk/src/pretalx/api/serializers/room.py
rename to app/eventyay/api/serializers/room.py
index 063b6422e1..4e5066c91f 100644
--- a/talk/src/pretalx/api/serializers/room.py
+++ b/app/eventyay/api/serializers/room.py
@@ -1,12 +1,12 @@
from rest_framework.serializers import UUIDField
-from pretalx.api.mixins import PretalxSerializer
-from pretalx.api.serializers.availability import (
+from eventyay.api.mixins import PretalxSerializer
+from eventyay.api.serializers.availability import (
AvailabilitiesMixin,
AvailabilitySerializer,
)
-from pretalx.api.versions import CURRENT_VERSIONS, register_serializer
-from pretalx.schedule.models import Room
+from eventyay.api.versions import CURRENT_VERSIONS, register_serializer
+from eventyay.base.models.room import Room
@register_serializer(versions=CURRENT_VERSIONS)
diff --git a/app/eventyay/api/serializers/rooms.py b/app/eventyay/api/serializers/rooms.py
index f121760a8f..9cf770e2e3 100644
--- a/app/eventyay/api/serializers/rooms.py
+++ b/app/eventyay/api/serializers/rooms.py
@@ -23,6 +23,9 @@ class Meta:
"sorting_priority",
"pretalx_id",
"schedule_data",
+ "setup_complete",
+ "hidden",
+ "sidebar_hidden",
# TODO: picture
]
diff --git a/talk/src/pretalx/api/serializers/schedule.py b/app/eventyay/api/serializers/schedule.py
similarity index 88%
rename from talk/src/pretalx/api/serializers/schedule.py
rename to app/eventyay/api/serializers/schedule.py
index 2fcd0c70da..4d3c052fec 100644
--- a/talk/src/pretalx/api/serializers/schedule.py
+++ b/app/eventyay/api/serializers/schedule.py
@@ -9,9 +9,10 @@
SlugRelatedField,
)
-from pretalx.api.mixins import PretalxSerializer
-from pretalx.api.versions import CURRENT_VERSIONS, register_serializer
-from pretalx.schedule.models import Schedule, TalkSlot
+from eventyay.api.mixins import PretalxSerializer
+from eventyay.api.versions import CURRENT_VERSIONS, register_serializer
+from eventyay.base.models.schedule import Schedule
+from eventyay.base.models.slot import TalkSlot
@register_serializer()
@@ -44,7 +45,7 @@ class Meta(ScheduleListSerializer.Meta):
fields = ScheduleListSerializer.Meta.fields + ["comment", "slots"]
extra_expandable_fields = {
"slots": (
- "pretalx.api.serializers.schedule.TalkSlotSerializer",
+ "eventyay.api.serializers.schedule.TalkSlotSerializer",
{"read_only": True, "many": True, "omit": ("schedule",)},
)
}
@@ -88,15 +89,15 @@ class Meta:
read_only_fields = ["submission", "schedule"]
expandable_fields = {
"submission": (
- "pretalx.api.serializers.submission.SubmissionSerializer",
+ "eventyay.api.serializers.submission.SubmissionSerializer",
{"read_only": True, "omit": ("slots",)},
),
"schedule": (
- "pretalx.api.serializers.schedule.ScheduleSerializer",
+ "eventyay.api.serializers.schedule.ScheduleSerializer",
{"read_only": True, "omit": ("slots", "speakers")},
),
"room": (
- "pretalx.api.serializers.room.RoomSerializer",
+ "eventyay.api.serializers.room.RoomSerializer",
{"read_only": True},
),
}
diff --git a/talk/src/pretalx/api/serializers/speaker.py b/app/eventyay/api/serializers/speaker.py
similarity index 87%
rename from talk/src/pretalx/api/serializers/speaker.py
rename to app/eventyay/api/serializers/speaker.py
index b857442f4d..48adaddcfb 100644
--- a/talk/src/pretalx/api/serializers/speaker.py
+++ b/app/eventyay/api/serializers/speaker.py
@@ -8,18 +8,18 @@
EmailField,
SerializerMethodField,
URLField,
- ModelSerializer,
)
-from pretalx.api.mixins import PretalxSerializer
-from pretalx.api.serializers.availability import (
+from eventyay.api.mixins import PretalxSerializer
+from eventyay.api.serializers.availability import (
AvailabilitiesMixin,
AvailabilitySerializer,
)
-from pretalx.api.serializers.fields import UploadedFileField
-from pretalx.api.versions import CURRENT_VERSIONS, register_serializer
-from pretalx.person.models import SpeakerProfile, User
-from pretalx.submission.models import QuestionTarget
+from eventyay.api.serializers.fields import UploadedFileField
+from eventyay.api.versions import CURRENT_VERSIONS, register_serializer
+from eventyay.base.models.auth import User
+from eventyay.base.models.profile import SpeakerProfile
+from eventyay.base.models.question import TalkQuestionTarget
@register_serializer(versions=CURRENT_VERSIONS)
@@ -63,7 +63,7 @@ def get_answers(self, obj):
qs = obj.answers.filter(
question__in=questions,
question__event=self.event,
- question__target=QuestionTarget.SPEAKER,
+ question__target=TalkQuestionTarget.SPEAKER,
)
if serializer := self.get_extra_flex_field("answers", qs):
return serializer.data
@@ -81,20 +81,20 @@ class Meta:
fields = ("code", "name", "biography", "submissions", "avatar_url", "avatar_source", "avatar_license", "answers")
expandable_fields = {
"submissions": (
- "pretalx.api.serializers.submission.SubmissionSerializer",
+ "eventyay.api.serializers.submission.SubmissionSerializer",
{"read_only": True, "many": True},
),
}
extra_expandable_fields = {
"answers": (
- "pretalx.api.serializers.question.AnswerSerializer",
+ "eventyay.api.serializers.question.AnswerSerializer",
{
"many": True,
"read_only": True,
},
),
"submissions": (
- "pretalx.api.serializers.submission.SubmissionSerializer",
+ "eventyay.api.serializers.submission.SubmissionSerializer",
{
"many": True,
"read_only": True,
@@ -131,7 +131,7 @@ class Meta(SpeakerSerializer.Meta):
)
expandable_fields = {
"submissions": (
- "pretalx.api.serializers.submission.SubmissionSerializer",
+ "eventyay.api.serializers.submission.SubmissionSerializer",
{"read_only": True, "many": True},
),
}
diff --git a/talk/src/pretalx/api/serializers/speaker_information.py b/app/eventyay/api/serializers/speaker_information.py
similarity index 78%
rename from talk/src/pretalx/api/serializers/speaker_information.py
rename to app/eventyay/api/serializers/speaker_information.py
index d47b7e9d81..7471570678 100644
--- a/talk/src/pretalx/api/serializers/speaker_information.py
+++ b/app/eventyay/api/serializers/speaker_information.py
@@ -2,11 +2,12 @@
from rest_flex_fields.serializers import FlexFieldsSerializerMixin
-from pretalx.api.mixins import PretalxSerializer
-from pretalx.api.serializers.fields import UploadedFileField
-from pretalx.api.versions import CURRENT_VERSIONS, register_serializer
-from pretalx.person.models import SpeakerInformation
-from pretalx.submission.models import SubmissionType, Track
+from eventyay.api.mixins import PretalxSerializer
+from eventyay.api.serializers.fields import UploadedFileField
+from eventyay.api.versions import CURRENT_VERSIONS, register_serializer
+from eventyay.base.models.information import SpeakerInformation
+from eventyay.base.models.track import Track
+from eventyay.base.models.type import SubmissionType
@register_serializer(versions=CURRENT_VERSIONS)
@@ -26,11 +27,11 @@ class Meta:
)
expandable_fields = {
"limit_tracks": (
- "pretalx.api.serializers.submission.TrackSerializer",
+ "eventyay.api.serializers.submission.TrackSerializer",
{"many": True, "read_only": True},
),
"limit_types": (
- "pretalx.api.serializers.submission.SubmissionTypeSerializer",
+ "eventyay.api.serializers.submission.SubmissionTypeSerializer",
{"many": True, "read_only": True},
),
}
diff --git a/talk/src/pretalx/api/serializers/submission.py b/app/eventyay/api/serializers/submission.py
similarity index 91%
rename from talk/src/pretalx/api/serializers/submission.py
rename to app/eventyay/api/serializers/submission.py
index 537579c39a..f57e5a0554 100644
--- a/talk/src/pretalx/api/serializers/submission.py
+++ b/app/eventyay/api/serializers/submission.py
@@ -5,18 +5,17 @@
from rest_framework import exceptions, serializers
from rest_framework.serializers import ModelSerializer, SerializerMethodField
-from pretalx.api.mixins import PretalxSerializer
-from pretalx.api.serializers.fields import UploadedFileField
-from pretalx.api.versions import CURRENT_VERSIONS, register_serializer
-from pretalx.person.models import SpeakerProfile, User
-from pretalx.submission.models import (
- QuestionTarget,
- Resource,
- Submission,
- SubmissionType,
- Tag,
- Track,
-)
+from eventyay.api.mixins import PretalxSerializer
+from eventyay.api.serializers.fields import UploadedFileField
+from eventyay.api.versions import CURRENT_VERSIONS, register_serializer
+from eventyay.base.models.auth import User
+from eventyay.base.models.profile import SpeakerProfile
+from eventyay.base.models.question import TalkQuestionTarget
+from eventyay.base.models.resource import Resource
+from eventyay.base.models.submission import Submission
+from eventyay.base.models.tag import Tag
+from eventyay.base.models.track import Track
+from eventyay.base.models.type import SubmissionType
@register_serializer()
@@ -190,7 +189,7 @@ def get_answers(self, obj):
qs = obj.answers.filter(
question__in=questions,
question__event=self.event,
- question__target=QuestionTarget.SUBMISSION,
+ question__target=TalkQuestionTarget.SUBMISSION,
)
if serializer := self.get_extra_flex_field("answers", qs):
return serializer.data
@@ -233,33 +232,33 @@ class Meta:
read_only_fields = ("code", "state")
expandable_fields = {
"submission_type": (
- "pretalx.api.serializers.submission.SubmissionTypeSerializer",
+ "eventyay.api.serializers.submission.SubmissionTypeSerializer",
{"read_only": True},
),
"tags": (
- "pretalx.api.serializers.submission.TagSerializer",
+ "eventyay.api.serializers.submission.TagSerializer",
{"many": True, "read_only": True},
),
"track": (
- "pretalx.api.serializers.submission.TrackSerializer",
+ "eventyay.api.serializers.submission.TrackSerializer",
{"read_only": True},
),
"resources": (
- "pretalx.api.serializers.submission.ResourceSerializer",
+ "eventyay.api.serializers.submission.ResourceSerializer",
{"many": True, "read_only": True},
),
}
extra_expandable_fields = {
"slots": (
- "pretalx.api.serializers.schedule.TalkSlotSerializer",
+ "eventyay.api.serializers.schedule.TalkSlotSerializer",
{"many": True, "read_only": True, "omit": ("submission", "schedule")},
),
"answers": (
- "pretalx.api.serializers.question.AnswerSerializer",
+ "eventyay.api.serializers.question.AnswerSerializer",
{"many": True, "read_only": True},
),
"speakers": (
- "pretalx.api.serializers.speaker.SpeakerSerializer",
+ "eventyay.api.serializers.speaker.SpeakerSerializer",
{"many": True, "read_only": True},
),
}
diff --git a/talk/src/pretalx/api/serializers/team.py b/app/eventyay/api/serializers/team.py
similarity index 91%
rename from talk/src/pretalx/api/serializers/team.py
rename to app/eventyay/api/serializers/team.py
index 1ccbd39fde..ea29314307 100644
--- a/talk/src/pretalx/api/serializers/team.py
+++ b/app/eventyay/api/serializers/team.py
@@ -1,11 +1,12 @@
from rest_flex_fields.serializers import FlexFieldsSerializerMixin
from rest_framework import exceptions, serializers
-from pretalx.api.mixins import PretalxSerializer
-from pretalx.api.versions import CURRENT_VERSIONS, register_serializer
-from pretalx.event.models import Event, Team, TeamInvite
-from pretalx.person.models import User
-from pretalx.submission.models import Track
+from eventyay.api.mixins import PretalxSerializer
+from eventyay.api.versions import CURRENT_VERSIONS, register_serializer
+from eventyay.base.models.event import Event, Team
+from eventyay.base.models.organizer import Team, TeamInvite
+from eventyay.base.models.auth import User
+from eventyay.base.models.track import Track
@register_serializer(versions=CURRENT_VERSIONS)
@@ -65,7 +66,7 @@ class Meta:
)
expandable_fields = {
"limit_tracks": (
- "pretalx.api.serializers.submission.TrackSerializer",
+ "eventyay.api.serializers.submission.TrackSerializer",
{"many": True},
),
"members": (TeamMemberSerializer, {"many": True, "required": False}),
diff --git a/talk/src/pretalx/api/templates/rest_framework/api.html b/app/eventyay/api/templates/rest_framework/api.html
similarity index 100%
rename from talk/src/pretalx/api/templates/rest_framework/api.html
rename to app/eventyay/api/templates/rest_framework/api.html
diff --git a/app/eventyay/api/urls.py b/app/eventyay/api/urls.py
index 370b442f52..1f498e3f81 100644
--- a/app/eventyay/api/urls.py
+++ b/app/eventyay/api/urls.py
@@ -1,7 +1,8 @@
import importlib
from django.apps import apps
-from django.urls import include
+from django.http import HttpResponsePermanentRedirect
+from django.urls import include, path
from django.urls import re_path as url
from rest_framework import routers
@@ -9,14 +10,23 @@
from ..eventyay_common.views.billing import BillingInvoicePreview
from .views import (
+ access_code,
checkin,
device,
event,
exporters,
- product,
+ mail,
oauth,
order,
organizer,
+ product,
+ question,
+ review,
+ room,
+ schedule,
+ speaker,
+ speaker_information,
+ submission,
upload,
user,
version,
@@ -26,6 +36,20 @@
)
from .views.stripe import stripe_webhook_view
+
+def talks_to_submissions_redirect(request, event, subpath):
+ """
+ Redirects requests from /events/.../talks/... to /events/.../submissions/...
+ preserving the subpath and query parameters.
+ """
+ new_path = request.path.replace("/talks/", "/submissions/", 1)
+
+ query_string = request.META.get('QUERY_STRING', '')
+ if query_string:
+ new_path += '?' + query_string
+
+ return HttpResponsePermanentRedirect(new_path)
+
router = routers.DefaultRouter()
router.register(r'organizers', organizer.OrganizerViewSet)
@@ -61,6 +85,21 @@
event_router.register(r'checkinlists', checkin.CheckinListViewSet)
event_router.register(r'cartpositions', cart.CartPositionViewSet)
event_router.register(r'exporters', exporters.EventExportersViewSet, basename='exporters')
+event_router.register('submissions', submission.SubmissionViewSet, basename='submission')
+event_router.register('schedules', schedule.ScheduleViewSet, basename='schedule')
+event_router.register('slots', schedule.TalkSlotViewSet, basename='slots')
+event_router.register('tags', submission.TagViewSet, basename='tag')
+event_router.register('submission-types', submission.SubmissionTypeViewSet, basename='submission_type')
+event_router.register('tracks', submission.TrackViewSet, basename='track')
+event_router.register('mail-templates', mail.MailTemplateViewSet, basename='mail_template')
+event_router.register('access-codes', access_code.SubmitterAccessCodeViewSet, basename='access_code')
+event_router.register('speakers', speaker.SpeakerViewSet, basename='speaker')
+event_router.register('reviews', review.ReviewViewSet, basename='review')
+event_router.register('rooms', room.RoomViewSet, basename='room')
+event_router.register('talkquestions', question.QuestionViewSet, basename='talkquestion')
+event_router.register('answers', question.AnswerViewSet, basename='answer')
+event_router.register('question-options', question.AnswerOptionViewSet, basename='question_option')
+event_router.register('speaker-information', speaker_information.SpeakerInformationViewSet, basename='speaker_information',)
checkinlist_router = routers.DefaultRouter()
checkinlist_router.register(r'positions', checkin.CheckinListPositionViewSet, basename='checkinlistpos')
@@ -167,4 +206,29 @@
event.CustomerOrderCheckView.as_view(),
name='event.ticket-check',
),
+
+ # We redirect the old pre-filtered /talks/ endpoint to /submissions/
+ path(
+ 'events//talks/',
+ talks_to_submissions_redirect,
+ name='event_talks_redirect',
+ ),
+ # The favourites endpoints are separate, as they are functions, not viewsets.
+ # They need to be separate from the viewset in order to permit session
+ # authentication.
+ path(
+ 'events//submissions/favourites/',
+ submission.favourites_view,
+ name='submission.favourites',
+ ),
+ path(
+ 'events//submissions//favourite/',
+ submission.favourite_view,
+ name='submission.favourite',
+ ),
+ path('events//', include(event_router.urls)),
+ path(
+ 'events//favourite-talk/',
+ submission.SubmissionFavouriteDeprecatedView.as_view(),
+ ),
]
diff --git a/talk/src/pretalx/api/views/access_code.py b/app/eventyay/api/views/access_code.py
similarity index 86%
rename from talk/src/pretalx/api/views/access_code.py
rename to app/eventyay/api/views/access_code.py
index 960b0c3366..f205880942 100644
--- a/talk/src/pretalx/api/views/access_code.py
+++ b/app/eventyay/api/views/access_code.py
@@ -3,10 +3,10 @@
from drf_spectacular.utils import extend_schema, extend_schema_view
from rest_framework import exceptions, viewsets
-from pretalx.api.documentation import build_expand_docs, build_search_docs
-from pretalx.api.mixins import PretalxViewSetMixin
-from pretalx.api.serializers.access_code import SubmitterAccessCodeSerializer
-from pretalx.submission.models import SubmitterAccessCode
+from eventyay.api.documentation import build_expand_docs, build_search_docs
+from eventyay.api.mixins import PretalxViewSetMixin
+from eventyay.api.serializers.access_code import SubmitterAccessCodeSerializer
+from eventyay.base.models.access_code import SubmitterAccessCode
@extend_schema_view(
diff --git a/app/eventyay/api/views/event.py b/app/eventyay/api/views/event.py
index c7071e2f84..00165ca3ed 100644
--- a/app/eventyay/api/views/event.py
+++ b/app/eventyay/api/views/event.py
@@ -569,4 +569,4 @@ def delete_user(request, **kwargs):
return Response(status=404)
user.soft_delete()
- return Response(status=204)
\ No newline at end of file
+ return Response(status=204)
diff --git a/talk/src/pretalx/api/views/mail.py b/app/eventyay/api/views/mail.py
similarity index 80%
rename from talk/src/pretalx/api/views/mail.py
rename to app/eventyay/api/views/mail.py
index 8326f91978..b94ff8d035 100644
--- a/talk/src/pretalx/api/views/mail.py
+++ b/app/eventyay/api/views/mail.py
@@ -1,10 +1,10 @@
from drf_spectacular.utils import extend_schema, extend_schema_view
from rest_framework import viewsets
-from pretalx.api.documentation import build_search_docs
-from pretalx.api.mixins import PretalxViewSetMixin
-from pretalx.api.serializers.mail import MailTemplateSerializer
-from pretalx.mail.models import MailTemplate
+from eventyay.api.documentation import build_search_docs
+from eventyay.api.mixins import PretalxViewSetMixin
+from eventyay.api.serializers.mail import MailTemplateSerializer
+from eventyay.base.models.mail import MailTemplate
@extend_schema_view(
diff --git a/app/eventyay/api/views/order.py b/app/eventyay/api/views/order.py
index 86e66c9f67..ed2491d49e 100644
--- a/app/eventyay/api/views/order.py
+++ b/app/eventyay/api/views/order.py
@@ -296,10 +296,9 @@ def download(self, request, output, **kwargs):
return resp
else:
resp = FileResponse(ct.file.file, content_type=ct.type)
- resp['Content-Disposition'] = 'attachment; filename="{}-{}-{}{}"'.format(
+ resp['Content-Disposition'] = 'attachment; filename="{}-{}{}"'.format(
self.request.event.slug.upper(),
order.code,
- provider.identifier,
ct.extension,
)
return resp
@@ -1094,11 +1093,10 @@ def download(self, request, output, **kwargs):
return resp
else:
resp = FileResponse(ct.file.file, content_type=ct.type)
- resp['Content-Disposition'] = 'attachment; filename="{}-{}-{}-{}{}"'.format(
+ resp['Content-Disposition'] = 'attachment; filename="{}-{}-{}{}"'.format(
self.request.event.slug.upper(),
pos.order.code,
pos.positionid,
- provider.identifier,
ct.extension,
)
return resp
diff --git a/talk/src/pretalx/api/views/question.py b/app/eventyay/api/views/question.py
similarity index 94%
rename from talk/src/pretalx/api/views/question.py
rename to app/eventyay/api/views/question.py
index e1d5d7a6fa..5a1eff5ae6 100644
--- a/talk/src/pretalx/api/views/question.py
+++ b/app/eventyay/api/views/question.py
@@ -5,9 +5,9 @@
from rest_framework import exceptions, viewsets
from rest_framework.permissions import SAFE_METHODS
-from pretalx.api.documentation import build_expand_docs, build_search_docs
-from pretalx.api.mixins import PretalxViewSetMixin
-from pretalx.api.serializers.question import (
+from eventyay.api.documentation import build_expand_docs, build_search_docs
+from eventyay.api.mixins import PretalxViewSetMixin
+from eventyay.api.serializers.question import (
AnswerCreateSerializer,
AnswerOptionCreateSerializer,
AnswerOptionSerializer,
@@ -15,8 +15,8 @@
QuestionOrgaSerializer,
QuestionSerializer,
)
-from pretalx.submission.models import Answer, AnswerOption, Question, QuestionVariant
-from pretalx.submission.rules import questions_for_user
+from eventyay.base.models.question import Answer, AnswerOption, TalkQuestion, TalkQuestionVariant
+from eventyay.talk_rules.submission import questions_for_user
OPTIONS_HELP = (
"Please note that any update to the options field will delete the "
@@ -45,7 +45,7 @@
destroy=extend_schema(summary="Delete Question"),
)
class QuestionViewSet(PretalxViewSetMixin, viewsets.ModelViewSet):
- queryset = Question.objects.none()
+ queryset = TalkQuestion.objects.none()
serializer_class = QuestionSerializer
filterset_fields = ("is_public", "is_visible_to_reviewers", "target", "variant")
search_fields = ("question",)
@@ -115,7 +115,7 @@ def get_queryset(self):
questions = questions_for_user(self.request, self.event, self.request.user)
queryset = AnswerOption.objects.filter(
question__in=questions,
- question__variant__in=[QuestionVariant.CHOICES, QuestionVariant.MULTIPLE],
+ question__variant__in=[TalkQuestionVariant.CHOICES, TalkQuestionVariant.MULTIPLE],
).select_related("question", "question__event")
for field in self.check_expanded_fields(
"question.tracks", "question.submission_types"
diff --git a/talk/src/pretalx/api/views/review.py b/app/eventyay/api/views/review.py
similarity index 89%
rename from talk/src/pretalx/api/views/review.py
rename to app/eventyay/api/views/review.py
index e03386b1ca..0397c4b617 100644
--- a/talk/src/pretalx/api/views/review.py
+++ b/app/eventyay/api/views/review.py
@@ -3,12 +3,13 @@
from rest_framework import viewsets
from rest_framework.permissions import SAFE_METHODS
-from pretalx.api.documentation import build_expand_docs, build_search_docs
-from pretalx.api.filters.review import ReviewFilter
-from pretalx.api.mixins import PretalxViewSetMixin
-from pretalx.api.serializers.review import ReviewSerializer, ReviewWriteSerializer
-from pretalx.submission.models import Review, Submission
-from pretalx.submission.rules import get_reviewable_submissions
+from eventyay.api.documentation import build_expand_docs, build_search_docs
+from eventyay.api.filters.review import ReviewFilter
+from eventyay.api.mixins import PretalxViewSetMixin
+from eventyay.api.serializers.review import ReviewSerializer, ReviewWriteSerializer
+from eventyay.base.models.review import Review
+from eventyay.base.models.submission import Submission
+from eventyay.talk_rules.submission import get_reviewable_submissions
@extend_schema_view(
diff --git a/talk/src/pretalx/api/views/room.py b/app/eventyay/api/views/room.py
similarity index 89%
rename from talk/src/pretalx/api/views/room.py
rename to app/eventyay/api/views/room.py
index 88cda9bb3d..38abb62934 100644
--- a/talk/src/pretalx/api/views/room.py
+++ b/app/eventyay/api/views/room.py
@@ -4,10 +4,10 @@
from rest_framework import exceptions, pagination, viewsets
from rest_framework.permissions import SAFE_METHODS
-from pretalx.api.documentation import build_search_docs
-from pretalx.api.mixins import PretalxViewSetMixin
-from pretalx.api.serializers.room import RoomOrgaSerializer, RoomSerializer
-from pretalx.schedule.models import Room
+from eventyay.api.documentation import build_search_docs
+from eventyay.api.mixins import PretalxViewSetMixin
+from eventyay.api.serializers.room import RoomOrgaSerializer, RoomSerializer
+from eventyay.base.models.room import Room
class RoomPagination(pagination.LimitOffsetPagination):
diff --git a/app/eventyay/api/views/rooms.py b/app/eventyay/api/views/rooms.py
index d216958fa7..0de0233ab9 100644
--- a/app/eventyay/api/views/rooms.py
+++ b/app/eventyay/api/views/rooms.py
@@ -51,4 +51,4 @@ def perform_destroy(self, instance):
Channel.objects.filter(room=instance, event=self.request.event).delete()
transaction.on_commit( # pragma: no cover
lambda: async_to_sync(notify_event_change)(self.request.event.id)
- )
\ No newline at end of file
+ )
diff --git a/talk/src/pretalx/api/views/schedule.py b/app/eventyay/api/views/schedule.py
similarity index 95%
rename from talk/src/pretalx/api/views/schedule.py
rename to app/eventyay/api/views/schedule.py
index 638c4ff5b8..8463b5a141 100644
--- a/talk/src/pretalx/api/views/schedule.py
+++ b/app/eventyay/api/views/schedule.py
@@ -12,19 +12,20 @@
from rest_framework.decorators import action
from rest_framework.response import Response
-from pretalx.agenda.views.utils import get_schedule_exporter_content
-from pretalx.api.documentation import build_expand_docs, build_search_docs
-from pretalx.api.filters.schedule import TalkSlotFilter
-from pretalx.api.mixins import PretalxViewSetMixin
-from pretalx.api.serializers.legacy import LegacyScheduleSerializer
-from pretalx.api.serializers.schedule import (
+from eventyay.agenda.views.utils import get_schedule_exporter_content
+from eventyay.api.documentation import build_expand_docs, build_search_docs
+from eventyay.api.filters.schedule import TalkSlotFilter
+from eventyay.api.mixins import PretalxViewSetMixin
+from eventyay.api.serializers.legacy import LegacyScheduleSerializer
+from eventyay.api.serializers.schedule import (
ScheduleListSerializer,
ScheduleReleaseSerializer,
ScheduleSerializer,
TalkSlotOrgaSerializer,
TalkSlotSerializer,
)
-from pretalx.schedule.models import Schedule, TalkSlot
+from eventyay.base.models.schedule import Schedule
+from eventyay.base.models.slot import TalkSlot
@extend_schema_view(
@@ -268,7 +269,7 @@ class TalkSlotViewSet(
@cached_property
def is_orga(self):
return self.event and self.request.user.has_perm(
- "schedule.orga_view_schedule", self.event
+ "base.orga_view_schedule", self.event
)
def get_unversioned_serializer_class(self):
diff --git a/talk/src/pretalx/api/views/speaker.py b/app/eventyay/api/views/speaker.py
similarity index 87%
rename from talk/src/pretalx/api/views/speaker.py
rename to app/eventyay/api/views/speaker.py
index 03e51036cc..8343058cf6 100644
--- a/talk/src/pretalx/api/views/speaker.py
+++ b/app/eventyay/api/views/speaker.py
@@ -4,21 +4,21 @@
from rest_framework import filters, mixins, viewsets
from rest_framework.permissions import SAFE_METHODS
-from pretalx.api.documentation import build_expand_docs, build_search_docs
-from pretalx.api.mixins import PretalxViewSetMixin
-from pretalx.api.serializers.legacy import (
+from eventyay.api.documentation import build_expand_docs, build_search_docs
+from eventyay.api.mixins import PretalxViewSetMixin
+from eventyay.api.serializers.legacy import (
LegacySpeakerOrgaSerializer,
LegacySpeakerReviewerSerializer,
LegacySpeakerSerializer,
)
-from pretalx.api.serializers.speaker import (
+from eventyay.api.serializers.speaker import (
SpeakerOrgaSerializer,
SpeakerSerializer,
SpeakerUpdateSerializer,
)
-from pretalx.api.versions import LEGACY
-from pretalx.person.models import SpeakerProfile
-from pretalx.submission.rules import (
+from eventyay.api.versions import LEGACY
+from eventyay.base.models.profile import SpeakerProfile
+from eventyay.talk_rules.submission import (
questions_for_user,
speaker_profiles_for_user,
submissions_for_user,
@@ -38,7 +38,7 @@ def get_search_fields(self, view, request):
parameters=[
build_search_docs(
"name",
- extra_description="Organiser search also includes email addresses.",
+ extra_description="Organizer search also includes email addresses.",
),
build_expand_docs("submissions", "answers", "answers.question"),
],
@@ -75,14 +75,14 @@ class SpeakerViewSet(
filter_backends = (SpeakerSearchFilter, DjangoFilterBackend)
def get_legacy_serializer_class(self): # pragma: no cover
- if self.request.user.has_perm("submission.orga_update_submission", self.event):
+ if self.request.user.has_perm("base.orga_update_submission", self.event):
return LegacySpeakerOrgaSerializer
- if self.request.user.has_perm("person.orga_list_speakerprofile", self.event):
+ if self.request.user.has_perm("base.orga_list_speakerprofile", self.event):
return LegacySpeakerReviewerSerializer
return LegacySpeakerSerializer
def get_legacy_queryset(self): # pragma: no cover
- if self.request.user.has_perm("person.orga_list_speakerprofile", self.event):
+ if self.request.user.has_perm("base.orga_list_speakerprofile", self.event):
return SpeakerProfile.objects.filter(event=self.event, user__isnull=False)
if self.event.current_schedule and self.event.get_feature_flag("show_schedule"):
return SpeakerProfile.objects.filter(
@@ -103,7 +103,7 @@ def get_serializer(self, *args, **kwargs):
@cached_property
def is_orga(self):
return self.event and self.request.user.has_perm(
- "submission.orga_list_submission", self.event
+ "base.orga_list_submission", self.event
)
def get_unversioned_serializer_class(self):
diff --git a/talk/src/pretalx/api/views/speaker_information.py b/app/eventyay/api/views/speaker_information.py
similarity index 83%
rename from talk/src/pretalx/api/views/speaker_information.py
rename to app/eventyay/api/views/speaker_information.py
index d3d22994fc..3731f8b7bf 100644
--- a/talk/src/pretalx/api/views/speaker_information.py
+++ b/app/eventyay/api/views/speaker_information.py
@@ -1,10 +1,10 @@
from drf_spectacular.utils import extend_schema, extend_schema_view
from rest_framework import viewsets
-from pretalx.api.documentation import build_expand_docs, build_search_docs
-from pretalx.api.mixins import PretalxViewSetMixin
-from pretalx.api.serializers.speaker_information import SpeakerInformationSerializer
-from pretalx.person.models import SpeakerInformation
+from eventyay.api.documentation import build_expand_docs, build_search_docs
+from eventyay.api.mixins import PretalxViewSetMixin
+from eventyay.api.serializers.speaker_information import SpeakerInformationSerializer
+from eventyay.base.models.information import SpeakerInformation
@extend_schema_view(
diff --git a/talk/src/pretalx/api/views/submission.py b/app/eventyay/api/views/submission.py
similarity index 94%
rename from talk/src/pretalx/api/views/submission.py
rename to app/eventyay/api/views/submission.py
index b1f40d4b5f..8602202a43 100644
--- a/talk/src/pretalx/api/views/submission.py
+++ b/app/eventyay/api/views/submission.py
@@ -27,38 +27,38 @@
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
-from pretalx.api.documentation import build_expand_docs, build_search_docs
-from pretalx.api.mixins import PretalxViewSetMixin
-from pretalx.api.serializers.legacy import (
+from eventyay.api.documentation import build_expand_docs, build_search_docs
+from eventyay.api.mixins import PretalxViewSetMixin
+from eventyay.api.serializers.legacy import (
LegacySubmissionOrgaSerializer,
LegacySubmissionReviewerSerializer,
LegacySubmissionSerializer,
)
-from pretalx.api.serializers.submission import (
+from eventyay.api.serializers.submission import (
SubmissionOrgaSerializer,
SubmissionSerializer,
SubmissionTypeSerializer,
TagSerializer,
TrackSerializer,
)
-from pretalx.api.versions import LEGACY
-from pretalx.common import exceptions
-from pretalx.person.models import User
-from pretalx.common.auth import TokenAuthentication
-from pretalx.common.exceptions import SubmissionError
-from pretalx.submission.models import (
+from eventyay.api.versions import LEGACY
+from eventyay.common import exceptions
+from eventyay.base.models.auth import User
+from eventyay.common.auth import TokenAuthentication
+from eventyay.common.exceptions import SubmissionError
+from eventyay.base.models.submission import (
Submission,
SubmissionStates,
- SubmissionType,
- Tag,
- Track,
)
-from pretalx.submission.rules import (
+from eventyay.base.models.tag import Tag
+from eventyay.base.models.track import Track
+from eventyay.base.models.type import SubmissionType
+from eventyay.talk_rules.submission import (
questions_for_user,
speaker_profiles_for_user,
submissions_for_user,
)
-from pretalx.submission.models.submission import (
+from eventyay.base.models.submission import (
SubmissionFavouriteDeprecated,
SubmissionFavouriteDeprecatedSerializer,
)
@@ -173,10 +173,10 @@ class SubmissionViewSet(PretalxViewSetMixin, viewsets.ModelViewSet):
def get_legacy_queryset(self): # pragma: no cover
base_qs = self.event.submissions.all().order_by("code")
if not self.request.user.has_perm(
- "submission.orga_list_submission", self.event
+ "base.orga_list_submission", self.event
):
if (
- not self.request.user.has_perm("schedule.list_schedule", self.event)
+ not self.request.user.has_perm("base.list_schedule", self.event)
or not self.event.current_schedule
):
return Submission.objects.none()
@@ -188,9 +188,9 @@ def get_legacy_queryset(self): # pragma: no cover
return base_qs
def get_legacy_serializer_class(self): # pragma: no cover
- if self.request.user.has_perm("submission.orga_update_submission", self.event):
+ if self.request.user.has_perm("base.orga_update_submission", self.event):
return LegacySubmissionOrgaSerializer
- if self.request.user.has_perm("submission.orga_list_submission", self.event):
+ if self.request.user.has_perm("base.orga_list_submission", self.event):
return LegacySubmissionReviewerSerializer
return LegacySubmissionSerializer
@@ -200,7 +200,7 @@ def get_legacy_serializer(self, *args, **kwargs): # pragma: no cover
)
can_view_speakers = self.request.user.has_perm(
"schedule.list_schedule", self.event
- ) or self.request.user.has_perm("person.orga_list_speakerprofile", self.event)
+ ) or self.request.user.has_perm("base.orga_list_speakerprofile", self.event)
if self.request.query_params.get("anon"):
can_view_speakers = False
return super().get_serializer(
@@ -224,7 +224,7 @@ def get_unversioned_serializer_class(self):
@cached_property
def is_orga(self):
return self.event and self.request.user.has_perm(
- "submission.orga_list_submission", self.event
+ "base.orga_list_submission", self.event
)
def get_serializer(self, *args, **kwargs):
@@ -381,7 +381,7 @@ def remove_speaker(self, request, **kwargs):
@permission_classes([IsAuthenticated])
@authentication_classes((SessionAuthentication, TokenAuthentication))
def favourites_view(request, event):
- if not request.user.has_perm("schedule.list_schedule", request.event):
+ if not request.user.has_perm("base.list_schedule", request.event):
raise PermissionDenied()
return Response(
[
@@ -406,7 +406,7 @@ def favourites_view(request, event):
@permission_classes([IsAuthenticated])
@authentication_classes((SessionAuthentication, TokenAuthentication))
def favourite_view(request, event, code):
- if not request.user.has_perm("schedule.list_schedule", request.event):
+ if not request.user.has_perm("base.list_schedule", request.event):
raise PermissionDenied()
submission = (
submissions_for_user(request.event, request.user)
diff --git a/talk/src/pretalx/api/views/team.py b/app/eventyay/api/views/team.py
similarity index 88%
rename from talk/src/pretalx/api/views/team.py
rename to app/eventyay/api/views/team.py
index 0060106060..2f93de232b 100644
--- a/talk/src/pretalx/api/views/team.py
+++ b/app/eventyay/api/views/team.py
@@ -12,12 +12,12 @@
from rest_framework.decorators import action
from rest_framework.response import Response
-from pretalx.api.documentation import build_expand_docs, build_search_docs
-from pretalx.api.mixins import PretalxViewSetMixin
-from pretalx.api.serializers.team import TeamInviteSerializer, TeamSerializer
-from pretalx.event.models import Team, TeamInvite
-from pretalx.event.models.organiser import check_access_permissions
-from pretalx.person.models import User
+from eventyay.api.documentation import build_expand_docs, build_search_docs
+from eventyay.api.mixins import PretalxViewSetMixin
+from eventyay.api.serializers.team import TeamInviteSerializer, TeamSerializer
+from eventyay.base.models.organizer import Team, TeamInvite
+from eventyay.base.models.organizer import check_access_permissions
+from eventyay.base.models.auth import User
class TeamInviteCreateSerializer(serializers.Serializer):
@@ -57,8 +57,8 @@ class TeamViewSet(PretalxViewSetMixin, viewsets.ModelViewSet):
def get_queryset(self):
queryset = (
- self.request.organiser.teams.all()
- .select_related("organiser")
+ self.request.organizer.teams.all()
+ .select_related("organizer")
.order_by("pk")
)
if fields := self.check_expanded_fields("members", "limit_tracks", "invites"):
@@ -73,17 +73,17 @@ def perform_update(self, serializer):
try:
with transaction.atomic():
super().perform_update(serializer)
- check_access_permissions(self.request.organiser)
+ check_access_permissions(self.request.organizer)
except Exception as e:
raise exceptions.ValidationError(str(e))
def perform_destroy(self, instance):
try:
with transaction.atomic():
- organiser = instance.organiser
+ organizer = instance.organizer
instance.logged_actions().delete()
super().perform_destroy(instance)
- check_access_permissions(organiser)
+ check_access_permissions(organizer)
except Exception as e:
raise exceptions.ValidationError(str(e))
@@ -131,7 +131,7 @@ def delete_invite(self, request, invite_id, *args, **kwargs):
email = invite.email
invite.delete()
team.log_action(
- "pretalx.team.invite.orga.retract",
+ "eventyay.team.invite.orga.retract",
person=request.user,
orga=True,
data={"email": email},
@@ -167,9 +167,9 @@ def remove_member(self, request, *args, **kwargs):
try:
with transaction.atomic():
team.members.remove(user_to_remove)
- check_access_permissions(self.request.organiser)
+ check_access_permissions(self.request.organizer)
team.log_action(
- "pretalx.team.remove_member",
+ "eventyay.team.remove_member",
person=request.user,
orga=True,
data={
diff --git a/app/eventyay/api/webhooks.py b/app/eventyay/api/webhooks.py
index bb7afe64d1..5bff9af0ad 100644
--- a/app/eventyay/api/webhooks.py
+++ b/app/eventyay/api/webhooks.py
@@ -175,51 +175,51 @@ def build_payload(self, logentry: LogEntry):
def register_default_webhook_events(sender, **kwargs):
return (
ParametrizedOrderWebhookEvent(
- 'pretix.event.order.placed',
+ 'eventyay.event.order.placed',
_('New order placed'),
),
ParametrizedOrderWebhookEvent(
- 'pretix.event.order.placed.require_approval',
+ 'eventyay.event.order.placed.require_approval',
_('New order requires approval'),
),
ParametrizedOrderWebhookEvent(
- 'pretix.event.order.paid',
+ 'eventyay.event.order.paid',
_('Order marked as paid'),
),
ParametrizedOrderWebhookEvent(
- 'pretix.event.order.canceled',
+ 'eventyay.event.order.canceled',
_('Order canceled'),
),
ParametrizedOrderWebhookEvent(
- 'pretix.event.order.reactivated',
+ 'eventyay.event.order.reactivated',
_('Order reactivated'),
),
ParametrizedOrderWebhookEvent(
- 'pretix.event.order.expired',
+ 'eventyay.event.order.expired',
_('Order expired'),
),
ParametrizedOrderWebhookEvent(
- 'pretix.event.order.modified',
+ 'eventyay.event.order.modified',
_('Order information changed'),
),
ParametrizedOrderWebhookEvent(
- 'pretix.event.order.contact.changed',
+ 'eventyay.event.order.contact.changed',
_('Order contact address changed'),
),
ParametrizedOrderWebhookEvent(
- 'pretix.event.order.changed.*',
+ 'eventyay.event.order.changed.*',
_('Order changed'),
),
ParametrizedOrderWebhookEvent(
- 'pretix.event.order.refund.created.externally',
+ 'eventyay.event.order.refund.created.externally',
_('External refund of payment'),
),
ParametrizedOrderWebhookEvent(
- 'pretix.event.order.approved',
+ 'eventyay.event.order.approved',
_('Order approved'),
),
ParametrizedOrderWebhookEvent(
- 'pretix.event.order.denied',
+ 'eventyay.event.order.denied',
_('Order denied'),
),
ParametrizedOrderPositionWebhookEvent(
@@ -243,15 +243,15 @@ def register_default_webhook_events(sender, **kwargs):
_('Event details changed'),
),
ParametrizedSubEventWebhookEvent(
- 'pretix.subevent.added',
+ 'eventyay.subevent.added',
pgettext_lazy('subevent', 'Event series date added'),
),
ParametrizedSubEventWebhookEvent(
- 'pretix.subevent.changed',
+ 'eventyay.subevent.changed',
pgettext_lazy('subevent', 'Event series date changed'),
),
ParametrizedSubEventWebhookEvent(
- 'pretix.subevent.deleted',
+ 'eventyay.subevent.deleted',
pgettext_lazy('subevent', 'Event series date deleted'),
),
)
diff --git a/app/eventyay/base/configurations/default_setting.py b/app/eventyay/base/configurations/default_setting.py
index c3cb1be23e..43c47d0b57 100644
--- a/app/eventyay/base/configurations/default_setting.py
+++ b/app/eventyay/base/configurations/default_setting.py
@@ -197,6 +197,15 @@ def primary_font_kwargs():
help_text=_('Require attendees to enter their primary email address twice to help prevent errors.'),
),
},
+ 'include_wikimedia_username': {
+ 'default': 'False',
+ 'type': bool,
+ 'form_class': forms.BooleanField,
+ 'serializer_class': serializers.BooleanField,
+ 'form_kwargs': dict(
+ label=_('Add the Wikimedia ID for users authenticated via Wikimedia'),
+ ),
+ },
'order_phone_asked': {
'default': 'False',
'type': bool,
@@ -849,7 +858,24 @@ def primary_font_kwargs():
choices=settings.LANGUAGES,
widget=MultipleLanguagesWidget,
required=True,
- label=_('Available languages'),
+ label=_('Active languages'),
+ ),
+ },
+ 'content_locales': {
+ 'default': json.dumps([settings.LANGUAGE_CODE]),
+ 'type': list,
+ 'serializer_class': ListMultipleChoiceField,
+ 'serializer_kwargs': dict(
+ choices=settings.LANGUAGES,
+ required=True,
+ ),
+ 'form_class': forms.MultipleChoiceField,
+ 'form_kwargs': dict(
+ choices=settings.LANGUAGES,
+ widget=MultipleLanguagesWidget,
+ required=True,
+ label=_('Content languages'),
+ help_text=_('Languages that speakers can select for their submissions. Content languages should be a subset of active languages.'),
),
},
'locale': {
diff --git a/app/eventyay/base/exporter.py b/app/eventyay/base/exporter.py
index 76412480f1..774afa4a5e 100644
--- a/app/eventyay/base/exporter.py
+++ b/app/eventyay/base/exporter.py
@@ -345,10 +345,11 @@ def _render_xlsx(self, form_data, output_file=None):
)
def render(self, form_data: dict, output_file=None) -> Tuple[str, str, bytes]:
- if form_data.get('_format') == 'xlsx':
+ format_value = form_data.get('_format')
+ if format_value == 'xlsx':
return self._render_xlsx(form_data, output_file=output_file)
- elif ':' in form_data.get('_format'):
- sheet, f = form_data.get('_format').split(':')
+ elif format_value and ':' in format_value:
+ sheet, f = format_value.split(':')
if f == 'default':
return self._render_sheet_csv(
form_data,
diff --git a/app/eventyay/base/exporters/dekodi.py b/app/eventyay/base/exporters/dekodi.py
index 8e88715dc5..b8e44561ff 100644
--- a/app/eventyay/base/exporters/dekodi.py
+++ b/app/eventyay/base/exporters/dekodi.py
@@ -23,7 +23,7 @@ class DekodiNREIExporter(BaseExporter):
def _encode_invoice(self, invoice: Invoice):
p_last = invoice.order.payments.filter(
- state=[
+ state__in=[
OrderPayment.PAYMENT_STATE_CONFIRMED,
OrderPayment.PAYMENT_STATE_REFUNDED,
]
diff --git a/app/eventyay/base/exporters/json.py b/app/eventyay/base/exporters/json.py
index 926a4f9115..f399845ac5 100644
--- a/app/eventyay/base/exporters/json.py
+++ b/app/eventyay/base/exporters/json.py
@@ -1,6 +1,7 @@
import json
from decimal import Decimal
+import pytz
from django.core.serializers.json import DjangoJSONEncoder
from django.dispatch import receiver
@@ -13,6 +14,7 @@ class JSONExporter(BaseExporter):
verbose_name = 'Order data (JSON)'
def render(self, form_data):
+ event_tz = pytz.timezone(self.event.settings.timezone)
jo = {
'event': {
'name': str(self.event.name),
@@ -29,30 +31,31 @@ def render(self, form_data):
}
for category in self.event.categories.all()
],
- 'items': [
+ 'products': [
{
- 'id': item.id,
- 'name': str(item.name),
- 'internal_name': str(item.internal_name),
- 'category': item.category_id,
- 'price': item.default_price,
- 'tax_rate': item.tax_rule.rate if item.tax_rule else Decimal('0.00'),
- 'tax_name': str(item.tax_rule.name) if item.tax_rule else None,
- 'admission': item.admission,
- 'active': item.active,
+ 'id': product.id,
+ 'name': str(product.name),
+ 'internal_name': str(product.internal_name),
+ 'category': product.category_id,
+ 'price': product.default_price,
+ 'tax_rate': product.tax_rule.rate if product.tax_rule else Decimal('0.00'),
+ 'tax_name': str(product.tax_rule.name) if product.tax_rule else None,
+ 'admission': product.admission,
+ 'active': product.active,
'variations': [
{
'id': variation.id,
'active': variation.active,
'price': variation.default_price
if variation.default_price is not None
- else item.default_price,
+ else product.default_price,
'name': str(variation),
}
- for variation in item.variations.all()
+ for variation in product.variations.all()
],
}
- for item in self.event.items.select_related('tax_rule').prefetch_related('variations')
+
+ for product in self.event.products.select_related('tax_rule').prefetch_related('variations')
],
'questions': [
{
@@ -62,12 +65,13 @@ def render(self, form_data):
}
for question in self.event.questions.all()
],
+ 'timezone': str(event_tz),
'orders': [
{
'code': order.code,
'status': order.status,
'user': order.email,
- 'datetime': order.datetime,
+ 'datetime': order.datetime.astimezone(event_tz).isoformat(),
'fees': [
{
'type': fee.fee_type,
@@ -80,7 +84,7 @@ def render(self, form_data):
'positions': [
{
'id': position.id,
- 'item': position.item_id,
+ 'product': position.product_id,
'variation': position.variation_id,
'price': position.price,
'attendee_name': position.attendee_name,
@@ -104,10 +108,10 @@ def render(self, form_data):
{
'id': quota.id,
'size': quota.size,
- 'items': [item.id for item in quota.items.all()],
+ 'products': [product.id for product in quota.products.all()],
'variations': [variation.id for variation in quota.variations.all()],
}
- for quota in self.event.quotas.all().prefetch_related('items', 'variations')
+ for quota in self.event.quotas.all().prefetch_related('products', 'variations')
],
}
}
diff --git a/app/eventyay/base/exporters/orderlist.py b/app/eventyay/base/exporters/orderlist.py
index ed6d9bac44..46bb7f719a 100644
--- a/app/eventyay/base/exporters/orderlist.py
+++ b/app/eventyay/base/exporters/orderlist.py
@@ -34,6 +34,7 @@
Order,
OrderPosition,
Question,
+ User,
)
from eventyay.base.models.orders import OrderFee, OrderPayment, OrderRefund
from eventyay.base.services.quotas import QuotaAvailability
@@ -277,6 +278,12 @@ def iterate_orders(self, form_data: dict):
.annotate(k=Count('id'))
.values('k')
)
+
+ # Always load wikimedia_username (lightweight data)
+ wikimedia_query = User.objects.filter(
+ email=OuterRef('email')
+ ).values('wikimedia_username')[:1]
+
qs = (
Order.objects.filter(event__in=self.events)
.annotate(
@@ -284,28 +291,43 @@ def iterate_orders(self, form_data: dict):
payment_providers=Subquery(p_providers, output_field=CharField()),
invoice_numbers=Subquery(i_numbers, output_field=CharField()),
pcnt=Subquery(s, output_field=IntegerField()),
+ wikimedia_username=Subquery(wikimedia_query, output_field=CharField()),
)
.select_related('invoice_address')
)
qs = self._date_filter(qs, form_data, rel='')
- if form_data['paid_only']:
+ if form_data.get('paid_only', True):
qs = qs.filter(status=Order.STATUS_PAID)
tax_rates = self._get_all_tax_rates(qs)
+ # Check if we need to include wikimedia_username in the export
+ should_include_wikimedia = any(
+ self.event_object_cache[event_id].settings.get('include_wikimedia_username', False)
+ for event_id in self.event_object_cache.keys()
+ )
+
headers = [
_('Event slug'),
_('Order code'),
_('Order total'),
_('Status'),
_('Email'),
+ ]
+
+ # Add wikimedia_username header if setting is enabled
+ if should_include_wikimedia:
+ headers.append(_('Wikimedia username'))
+
+ headers += [
_('Phone number'),
_('Order date'),
_('Order time'),
_('Company'),
_('Name'),
]
+
name_scheme = PERSON_NAME_SCHEMES[self.event.settings.name_scheme] if not self.is_multievent else None
if name_scheme and len(name_scheme['fields']) > 1:
for k, label, w in name_scheme['fields']:
@@ -388,17 +410,25 @@ def iterate_orders(self, form_data: dict):
yield self.ProgressSetTotal(total=qs.count())
for order in qs.order_by('datetime').iterator():
tz = pytz.timezone(self.event_object_cache[order.event_id].settings.timezone)
-
row = [
self.event_object_cache[order.event_id].slug,
order.code,
order.total,
order.get_status_display(),
order.email,
+ ]
+
+ # Add wikimedia_username if setting is enabled (insert before phone number)
+ if should_include_wikimedia:
+ wikimedia_username = getattr(order, 'wikimedia_username', '') or ''
+ row.append(wikimedia_username)
+
+ row += [
str(order.phone) if order.phone else '',
order.datetime.astimezone(tz).strftime('%Y-%m-%d'),
- order.datetime.astimezone(tz).strftime('%H:%M:%S'),
+ order.datetime.astimezone(tz).strftime('%H:%M:%S %Z'),
]
+
try:
row += [
order.invoice_address.company,
@@ -424,7 +454,7 @@ def iterate_orders(self, form_data: dict):
)
row += [
- order.payment_date.astimezone(tz).strftime('%Y-%m-%d') if order.payment_date else '',
+ order.payment_date.astimezone(tz).strftime('%Y-%m-%d %H:%M:%S %Z') if order.payment_date else '',
full_fee_sum_cache.get(order.id) or Decimal('0.00'),
order.locale,
]
@@ -499,7 +529,7 @@ def iterate_fees(self, form_data: dict):
)
.select_related('order', 'order__invoice_address', 'tax_rule')
)
- if form_data['paid_only']:
+ if form_data.get('paid_only', True):
qs = qs.filter(order__status=Order.STATUS_PAID)
qs = self._date_filter(qs, form_data, rel='order__')
@@ -548,7 +578,7 @@ def iterate_fees(self, form_data: dict):
order.email,
str(order.phone) if order.phone else '',
order.datetime.astimezone(tz).strftime('%Y-%m-%d'),
- order.datetime.astimezone(tz).strftime('%H:%M:%S'),
+ order.datetime.astimezone(tz).strftime('%H:%M:%S %Z'),
op.get_fee_type_display(),
op.description,
op.value,
@@ -615,14 +645,14 @@ def iterate_positions(self, form_data: dict):
.select_related(
'order',
'order__invoice_address',
- 'item',
+ 'product',
'variation',
'voucher',
'tax_rule',
)
.prefetch_related('answers', 'answers__question', 'answers__options')
)
- if form_data['paid_only']:
+ if form_data.get('paid_only', True):
qs = qs.filter(order__status=Order.STATUS_PAID)
qs = self._date_filter(qs, form_data, rel='order__')
@@ -679,7 +709,7 @@ def iterate_positions(self, form_data: dict):
for q in questions:
if q.type == Question.TYPE_CHOICE_MULTIPLE:
options[q.pk] = []
- if form_data['group_multiple_choice']:
+ if form_data.get('group_multiple_choice', False):
for o in q.options.all():
options[q.pk].append(o)
headers.append(str(q.question))
@@ -728,21 +758,21 @@ def iterate_positions(self, form_data: dict):
order.email,
str(order.phone) if order.phone else '',
order.datetime.astimezone(tz).strftime('%Y-%m-%d'),
- order.datetime.astimezone(tz).strftime('%H:%M:%S'),
+ order.datetime.astimezone(tz).strftime('%H:%M:%S %Z'),
]
if has_subevents:
if op.subevent:
row.append(op.subevent.name)
row.append(
op.subevent.date_from.astimezone(self.event_object_cache[order.event_id].timezone).strftime(
- '%Y-%m-%d %H:%M:%S'
+ '%Y-%m-%d %H:%M:%S %Z'
)
)
if op.subevent.date_to:
row.append(
op.subevent.date_to.astimezone(
self.event_object_cache[order.event_id].timezone
- ).strftime('%Y-%m-%d %H:%M:%S')
+ ).strftime('%Y-%m-%d %H:%M:%S %Z')
)
else:
row.append('')
@@ -751,7 +781,7 @@ def iterate_positions(self, form_data: dict):
row.append('')
row.append('')
row += [
- str(op.item),
+ str(op.product),
str(op.variation) if op.variation else '',
op.price,
op.tax_rate,
@@ -798,7 +828,7 @@ def iterate_positions(self, form_data: dict):
acache[a.question_id] = str(a)
for q in questions:
if q.type == Question.TYPE_CHOICE_MULTIPLE:
- if form_data['group_multiple_choice']:
+ if form_data.get('group_multiple_choice', False):
row.append(
', '.join(str(o.answer) for o in options[q.pk] if o.pk in acache.get(q.pk, set()))
)
@@ -920,16 +950,16 @@ def iterate_list(self, form_data):
for obj in objs:
tz = pytz.timezone(obj.order.event.settings.timezone)
if isinstance(obj, OrderPayment) and obj.payment_date:
- d2 = obj.payment_date.astimezone(tz).date().strftime('%Y-%m-%d')
+ d2 = obj.payment_date.astimezone(tz).strftime('%Y-%m-%d %H:%M:%S %Z')
elif isinstance(obj, OrderRefund) and obj.execution_date:
- d2 = obj.execution_date.astimezone(tz).date().strftime('%Y-%m-%d')
+ d2 = obj.execution_date.astimezone(tz).strftime('%Y-%m-%d %H:%M:%S %Z')
else:
d2 = ''
row = [
obj.order.event.slug,
obj.order.code,
obj.full_id,
- obj.created.astimezone(tz).date().strftime('%Y-%m-%d'),
+ obj.created.astimezone(tz).strftime('%Y-%m-%d %H:%M:%S %Z'),
d2,
obj.get_state_display(),
obj.state,
@@ -990,9 +1020,9 @@ def iterate_list(self, form_data):
if has_subevents:
if quota.subevent:
row.append(quota.subevent.name)
- row.append(quota.subevent.date_from.astimezone(self.event.timezone).strftime('%Y-%m-%d %H:%M:%S'))
+ row.append(quota.subevent.date_from.astimezone(self.event.timezone).strftime('%Y-%m-%d %H:%M:%S %Z'))
if quota.subevent.date_to:
- row.append(quota.subevent.date_to.astimezone(self.event.timezone).strftime('%Y-%m-%d %H:%M:%S'))
+ row.append(quota.subevent.date_to.astimezone(self.event.timezone).strftime('%Y-%m-%d %H:%M:%S %Z'))
else:
row.append('')
else:
@@ -1044,7 +1074,7 @@ def iterate_list(self, form_data):
obj.order.event.slug,
obj.order.code,
obj.full_id,
- obj.created.astimezone(tz).date().strftime('%Y-%m-%d'),
+ obj.created.astimezone(tz).strftime('%Y-%m-%d %H:%M:%S %Z'),
gc.secret,
obj.amount * (-1 if isinstance(obj, OrderRefund) else 1),
gc.issuer,
@@ -1169,8 +1199,8 @@ def iterate_list(self, form_data):
row = [
obj.secret,
_('Yes') if obj.testmode else _('No'),
- obj.issuance.astimezone(tz).date().strftime('%Y-%m-%d'),
- obj.expires.astimezone(tz).date().strftime('%Y-%m-%d') if obj.expires else '',
+ obj.issuance.astimezone(tz).strftime('%Y-%m-%d %H:%M:%S %Z'),
+ obj.expires.astimezone(tz).strftime('%Y-%m-%d %H:%M:%S %Z') if obj.expires else '',
obj.conditions or '',
obj.currency,
obj.cached_value,
diff --git a/app/eventyay/base/exporters/waitinglist.py b/app/eventyay/base/exporters/waitinglist.py
index 9ae2818df7..13a05f794f 100644
--- a/app/eventyay/base/exporters/waitinglist.py
+++ b/app/eventyay/base/exporters/waitinglist.py
@@ -71,7 +71,7 @@ def iterate_list(self, form_data):
WaitingListEntry.objects.filter(
event__in=self.events,
)
- .select_related('item', 'variation', 'voucher', 'subevent')
+ .select_related('product', 'variation', 'voucher', 'subevent')
.order_by('created')
)
@@ -115,14 +115,14 @@ def iterate_list(self, form_data):
# which event should be used to output dates in columns "Start date" and "End date"
event_for_date_columns = entry.subevent if entry.subevent else entry.event
tz = pytz.timezone(entry.event.settings.timezone)
- datetime_format = '%Y-%m-%d %H:%M:%S'
+ datetime_format = '%Y-%m-%d %H:%M:%S %Z'
row = [
entry.created.astimezone(tz).strftime(datetime_format), # alternative: .isoformat(),
entry.name,
entry.email,
entry.phone,
- str(entry.item) if entry.item else '',
+ str(entry.product) if entry.product else '',
str(entry.variation) if entry.variation else '',
entry.event.slug,
entry.event.name,
diff --git a/app/eventyay/base/forms/user.py b/app/eventyay/base/forms/user.py
index 5d1e79c761..ae30a44ba1 100644
--- a/app/eventyay/base/forms/user.py
+++ b/app/eventyay/base/forms/user.py
@@ -54,16 +54,13 @@ class UserSettingsForm(forms.ModelForm):
class Meta:
model = User
fields = ['fullname', 'wikimedia_username', 'locale', 'timezone', 'email']
- labels = {
- 'wikimedia_username': 'Nick name',
- }
widgets = {'locale': SingleLanguageWidget}
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user')
super().__init__(*args, **kwargs)
self.fields['email'].required = True
- self.fields['wikimedia_username'].widget.attrs['readonly'] = True
+ self.fields['wikimedia_username'].disabled = True
if self.user.auth_backend != 'native':
del self.fields['old_pw']
del self.fields['new_pw']
diff --git a/app/eventyay/base/migrations/0002_room_hidden_room_setup_complete_room_sidebar_hidden_and_more.py b/app/eventyay/base/migrations/0002_room_hidden_room_setup_complete_room_sidebar_hidden_and_more.py
new file mode 100644
index 0000000000..915c3c1b87
--- /dev/null
+++ b/app/eventyay/base/migrations/0002_room_hidden_room_setup_complete_room_sidebar_hidden_and_more.py
@@ -0,0 +1,49 @@
+# Generated by Django 5.2.5 on 2025-11-21 10:53
+
+import eventyay.base.models.event
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('base', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='room',
+ name='hidden',
+ field=models.BooleanField(default=False),
+ ),
+ migrations.AddField(
+ model_name='room',
+ name='setup_complete',
+ field=models.BooleanField(default=False),
+ ),
+ migrations.AddField(
+ model_name='room',
+ name='sidebar_hidden',
+ field=models.BooleanField(default=True),
+ ),
+ migrations.AlterField(
+ model_name='event',
+ name='header_image',
+ field=models.ImageField(blank=True, help_text='This image appears at the top of all event pages, replacing the default color or pattern. It is center-aligned and not stretched, ensuring the middle part remains visible on smaller screens. We recommend an image at least 1170 px wide and 120 px in height for best results.', null=True, upload_to=eventyay.base.models.event.event_logo_path, verbose_name='Header image'),
+ ),
+ migrations.AlterField(
+ model_name='event',
+ name='locale',
+ field=models.CharField(choices=[('en', 'English'), ('de', 'German'), ('de-formal', 'German (formal)'), ('ar', 'Arabic'), ('cs', 'Czech'), ('el', 'Greek'), ('es', 'Spanish'), ('fa-ir', 'Persian'), ('fr', 'French'), ('it', 'Italian'), ('id', 'Indonesian'), ('ja-jp', 'Japanese'), ('ko', 'Korean'), ('nl', 'Dutch'), ('pt-br', 'Brasilian Portuguese'), ('pt-pt', 'Portuguese'), ('ru', 'Russian'), ('sw', 'Swahili'), ('ua', 'Ukrainian'), ('zh-hant', 'Traditional Chinese (Taiwan)'), ('zh-hans', 'Simplified Chinese')], default='en', max_length=32, verbose_name='Default language'),
+ ),
+ migrations.AlterField(
+ model_name='event',
+ name='logo',
+ field=models.ImageField(blank=True, help_text='When you upload a logo, the event name and date will not appear in the header. The logo scales to 140 px in height while maintaining aspect ratio.', null=True, upload_to=eventyay.base.models.event.event_logo_path, verbose_name='Logo'),
+ ),
+ migrations.AlterField(
+ model_name='user',
+ name='locale',
+ field=models.CharField(choices=[('en', 'English'), ('de', 'German'), ('de-formal', 'German (formal)'), ('ar', 'Arabic'), ('cs', 'Czech'), ('el', 'Greek'), ('es', 'Spanish'), ('fa-ir', 'Persian'), ('fr', 'French'), ('it', 'Italian'), ('id', 'Indonesian'), ('ja-jp', 'Japanese'), ('ko', 'Korean'), ('nl', 'Dutch'), ('pt-br', 'Brasilian Portuguese'), ('pt-pt', 'Portuguese'), ('ru', 'Russian'), ('sw', 'Swahili'), ('ua', 'Ukrainian'), ('zh-hant', 'Traditional Chinese (Taiwan)'), ('zh-hans', 'Simplified Chinese')], default='en', max_length=50, verbose_name='Language'),
+ ),
+ ]
diff --git a/app/eventyay/base/models/access_code.py b/app/eventyay/base/models/access_code.py
index 32866188e1..10c05b5203 100644
--- a/app/eventyay/base/models/access_code.py
+++ b/app/eventyay/base/models/access_code.py
@@ -61,7 +61,7 @@ class SubmitterAccessCode(GenerateCode, PretalxModel):
_code_length = 32
- log_prefix = 'pretalx.access_code'
+ log_prefix = 'eventyay.access_code'
class Meta:
unique_together = (('event', 'code'),)
diff --git a/app/eventyay/base/models/event.py b/app/eventyay/base/models/event.py
index 58d13bde06..208b0d80c7 100644
--- a/app/eventyay/base/models/event.py
+++ b/app/eventyay/base/models/event.py
@@ -71,7 +71,6 @@
from .organizer import Organizer, OrganizerBillingModel, Team
TALK_HOSTNAME = settings.TALK_HOSTNAME
-LANGUAGE_NAMES = {code: name for code, name in settings.LANGUAGES}
def event_css_path(instance, filename):
return path_with_hash(filename, base_path=f"{instance.slug}/css/")
@@ -187,6 +186,7 @@ def default_feature_flags():
"present_multiple_times": False,
"submission_public_review": True,
"chat-moderation": True,
+ "polls": True,
}
def default_display_settings():
@@ -824,8 +824,8 @@ class urls(EventUrls):
schedule = '{base}schedule/'
schedule_nojs = '{schedule}nojs'
featured = '{base}featured/'
- talks = '{base}talk/'
- speakers = '{base}speaker/'
+ talks = '{base}sessions/'
+ speakers = '{base}speakers/'
changelog = '{schedule}changelog/'
feed = '{schedule}feed.xml'
export = '{schedule}export/'
@@ -1889,18 +1889,15 @@ def has_paid_things(self):
@property
def talk_schedule_url(self):
- url = urljoin(TALK_HOSTNAME, f'{self.slug}/schedule')
- return url
+ return self.urls.schedule.full
@property
def talk_session_url(self):
- url = urljoin(TALK_HOSTNAME, f'{self.slug}/talk')
- return url
+ return self.urls.talks.full
@property
def talk_speaker_url(self):
- url = urljoin(TALK_HOSTNAME, f'{self.slug}/speaker')
- return url
+ return self.urls.speakers.full
@property
def talk_dashboard_url(self):
@@ -2080,12 +2077,53 @@ def clean_presale(presale_start, presale_end):
@cached_property
def locales(self) -> list[str]:
"""Is a list of active event locales."""
- return self.locale_array.split(',')
+ if hasattr(self, 'settings') and 'locales' in self.settings._cache():
+ if locales := self.settings.get('locales', as_type=list):
+ return locales
+ return [code for code in self.locale_array.split(',') if code]
@cached_property
def content_locales(self) -> list[str]:
"""Is a list of active content locales."""
- return self.content_locale_array.split(',')
+ if hasattr(self, 'settings') and 'content_locales' in self.settings._cache():
+ if locales := self.settings.get('content_locales', as_type=list):
+ return locales
+ fallback = [code for code in self.content_locale_array.split(',') if code]
+ return fallback or self.locales
+
+ def _clear_language_caches(self):
+ for attr in [
+ 'locales',
+ 'content_locales',
+ 'is_multilingual',
+ 'named_locales',
+ 'available_content_locales',
+ 'named_content_locales',
+ 'named_plugin_locales',
+ 'plugin_locales',
+ ]:
+ self.__dict__.pop(attr, None)
+
+ def update_language_configuration(
+ self,
+ *,
+ locales: list[str] | None = None,
+ content_locales: list[str] | None = None,
+ default_locale: str | None = None,
+ ) -> None:
+ locales_list = list(locales or [])
+ if content_locales is None:
+ content_locales_list = locales_list
+ else:
+ content_locales_list = list(content_locales)
+ if locales_list:
+ self.locale_array = ','.join(locales_list)
+ if content_locales_list:
+ self.content_locale_array = ','.join(content_locales_list)
+ if default_locale:
+ self.locale = default_locale
+ if locales_list or content_locales_list or default_locale:
+ self._clear_language_caches()
@cached_property
def is_multilingual(self) -> bool:
@@ -2107,7 +2145,7 @@ def available_content_locales(self) -> list:
# Content locales can be anything eventyay knows as a language, merged with
# this event's plugin locales.
- locale_names = dict(default_django_settings.LANGUAGES)
+ locale_names = dict(settings.LANGUAGES)
locale_names.update(self.named_plugin_locales)
return sorted([(key, value) for key, value in locale_names.items()])
@@ -2115,7 +2153,8 @@ def available_content_locales(self) -> list:
def named_content_locales(self) -> list:
locale_names = dict(self.available_content_locales)
# locale_names['en-us'] = locale_names['en']
- return [(code, locale_names[code]) for code in self.content_locales]
+ locale_names |= LANGUAGE_NAMES
+ return [(code, locale_names.get(code, code)) for code in self.content_locales]
@cached_property
def named_plugin_locales(self) -> list:
diff --git a/app/eventyay/base/models/information.py b/app/eventyay/base/models/information.py
index d065457673..67f0cf873b 100644
--- a/app/eventyay/base/models/information.py
+++ b/app/eventyay/base/models/information.py
@@ -52,7 +52,7 @@ class SpeakerInformation(PretalxModel):
upload_to=resource_path,
)
- log_prefix = 'speaker_information'
+ log_prefix = 'eventyay.speaker_information'
@property
def log_parent(self):
diff --git a/app/eventyay/base/models/mail.py b/app/eventyay/base/models/mail.py
index 68c64fe8e2..f544ad8459 100644
--- a/app/eventyay/base/models/mail.py
+++ b/app/eventyay/base/models/mail.py
@@ -62,7 +62,7 @@ class MailTemplate(PretalxModel):
special cases, for now.
"""
- log_prefix = 'pretalx.mail_template'
+ log_prefix = 'eventyay.mail_template'
event = models.ForeignKey(
to='Event',
diff --git a/app/eventyay/base/models/profile.py b/app/eventyay/base/models/profile.py
index bb3afa56c8..49d0629e67 100644
--- a/app/eventyay/base/models/profile.py
+++ b/app/eventyay/base/models/profile.py
@@ -60,7 +60,7 @@ class Meta:
class urls(EventUrls):
"""URL patterns for public speaker profile views."""
- public = '{self.event.urls.base}speaker/{self.user.code}/'
+ public = '{self.event.urls.base}speakers/{self.user.code}/'
social_image = '{public}og-image'
talks_ical = '{self.urls.public}talks.ics'
diff --git a/app/eventyay/base/models/question.py b/app/eventyay/base/models/question.py
index ff270b581a..46038ea3b9 100644
--- a/app/eventyay/base/models/question.py
+++ b/app/eventyay/base/models/question.py
@@ -247,7 +247,7 @@ class TalkQuestion(OrderedModel, PretalxModel):
objects = ScopedManager(event='event', _manager_class=TalkQuestionManager)
all_objects = ScopedManager(event='event', _manager_class=AllTalkQuestionManager)
- log_prefix = 'pretalx.question'
+ log_prefix = 'eventyay.question'
class Meta:
ordering = ('position', 'id')
diff --git a/app/eventyay/base/models/room.py b/app/eventyay/base/models/room.py
index bb3751c184..5f38ff83fb 100644
--- a/app/eventyay/base/models/room.py
+++ b/app/eventyay/base/models/room.py
@@ -41,12 +41,29 @@ def with_permission(
):
from .auth import RoomGrant, EventGrant
- traits = traits or user.traits
+ # Normalize traits input
+ # - If traits is explicitly provided, use it; otherwise, read from user when available
+ # - Always coerce to a simple list[str] for proper DB array parameterization
+ if traits is None:
+ if user is not None:
+ traits = user.traits
+ else:
+ traits = []
+
allow_empty_traits = not user or user.type == User.UserType.PERSON
+
# Ensure traits is always a proper list of strings for SQL parameterization
- if traits and isinstance(traits, str):
- # e.g. "(trait1,trait2)" → ["trait1", "trait2"]
- traits = [t.strip(" '") for t in traits.strip("()").split(",") if t.strip()]
+ if isinstance(traits, str):
+ # Accept legacy "(a,b,c)" string format by parsing it into a list
+ traits = [t.strip() for t in traits.strip("()").split(",") if t.strip()]
+ else:
+ try:
+ # Convert any iterable (set/tuple/queryset) to a list of strings
+ traits = [str(t) for t in list(traits or [])]
+ except TypeError:
+ # Non-iterables (shouldn't happen) fallback to empty list
+ traits = []
+
if event.has_permission_implicit(
traits=traits,
permissions=[permission],
@@ -160,7 +177,7 @@ class Room(VersionedModel, OrderedModel, PretalxModel):
are not in use right now.
"""
- log_prefix = "pretalx.room"
+ log_prefix = "eventyay.room"
deleted = models.BooleanField(default=False)
description = I18nCharField(
@@ -205,6 +222,11 @@ class Room(VersionedModel, OrderedModel, PretalxModel):
pretalx_id = models.IntegerField(default=0)
schedule_data = JSONField(null=True, blank=True)
force_join = models.BooleanField(default=False)
+ setup_complete = models.BooleanField(default=False)
+ hidden = models.BooleanField(default=False)
+ # Default is True (hidden) for safety - rooms should be hidden until configured.
+ # The _create_room function and migration explicitly set this based on module_config.
+ sidebar_hidden = models.BooleanField(default=True)
objects = RoomQuerySet.as_manager()
@@ -294,6 +316,9 @@ class Meta:
"pretalx_id",
"force_join",
"schedule_data",
+ "setup_complete",
+ "hidden",
+ "sidebar_hidden",
)
diff --git a/app/eventyay/base/models/schedule.py b/app/eventyay/base/models/schedule.py
index 8a72a3359a..90b1ffc7cc 100644
--- a/app/eventyay/base/models/schedule.py
+++ b/app/eventyay/base/models/schedule.py
@@ -102,7 +102,7 @@ def freeze(self, name: str, user=None, notify_speakers: bool = True, comment: st
wip_schedule = Schedule.objects.create(event=self.event)
self.save(update_fields=['published', 'version', 'comment'])
- self.log_action('pretalx.schedule.release', person=user, orga=True)
+ self.log_action('eventyay.schedule.release', person=user, orga=True)
# Set visibility
self.talks.all().update(is_visible=False)
@@ -601,7 +601,7 @@ def build_data(self, all_talks=False, filter_updated=None, all_rooms=False):
'submission__submission_type',
).prefetch_related('submission__speakers')
talks = talks.order_by('start')
- rooms = set(self.event.rooms.filter(deleted=False)) if all_rooms else set()
+ rooms = set(self.event.rooms.filter(deleted=False, hidden=False)) if all_rooms else set()
tracks = set()
speakers = set()
result = {
@@ -614,7 +614,7 @@ def build_data(self, all_talks=False, filter_updated=None, all_rooms=False):
show_do_not_record = self.event.cfp.request_do_not_record
for talk in talks:
# Only add room if it's not deleted
- if talk.room and not talk.room.deleted:
+ if talk.room and not talk.room.deleted and not talk.room.hidden:
rooms.add(talk.room)
if talk.submission:
tracks.add(talk.submission.track)
@@ -669,7 +669,7 @@ def build_data(self, all_talks=False, filter_updated=None, all_rooms=False):
'name': room.name,
'description': room.description,
}
- for room in self.event.rooms.all()
+ for room in self.event.rooms.filter(hidden=False)
if room in rooms
]
include_avatar = self.event.cfp.request_avatar
diff --git a/app/eventyay/base/models/submission.py b/app/eventyay/base/models/submission.py
index 3b75292dd5..b37b282c84 100644
--- a/app/eventyay/base/models/submission.py
+++ b/app/eventyay/base/models/submission.py
@@ -287,7 +287,7 @@ class comes with a ``method_names`` dictionary for method lookup.
deleted_objects = ScopedManager(event='event', _manager_class=DeletedSubmissionManager)
all_objects = ScopedManager(event='event', _manager_class=AllSubmissionManager)
- log_prefix = 'pretalx.submission'
+ log_prefix = 'eventyay.submission'
class Meta:
rules_permissions = {
diff --git a/app/eventyay/base/models/tag.py b/app/eventyay/base/models/tag.py
index 3ca3296458..27bd040b9e 100644
--- a/app/eventyay/base/models/tag.py
+++ b/app/eventyay/base/models/tag.py
@@ -39,7 +39,7 @@ class Tag(PretalxModel):
),
)
- log_prefix = 'pretalx.tag'
+ log_prefix = 'eventyay.tag'
class Meta:
rules_permissions = {
diff --git a/app/eventyay/base/models/tax.py b/app/eventyay/base/models/tax.py
index fa9a91fd72..c7c436d874 100644
--- a/app/eventyay/base/models/tax.py
+++ b/app/eventyay/base/models/tax.py
@@ -173,7 +173,7 @@ def allow_delete(self):
return (
not OrderFee.objects.filter(tax_rule=self, order__event=self.event).exists()
and not OrderPosition.all.filter(tax_rule=self, order__event=self.event).exists()
- and not self.event.items.filter(tax_rule=self).exists()
+ and not self.event.products.filter(tax_rule=self).exists()
and self.event.settings.tax_rate_default != self
)
diff --git a/app/eventyay/base/models/track.py b/app/eventyay/base/models/track.py
index b13698280c..0152eec41b 100644
--- a/app/eventyay/base/models/track.py
+++ b/app/eventyay/base/models/track.py
@@ -51,7 +51,7 @@ class Track(OrderedModel, PretalxModel):
default=False,
)
- log_prefix = 'pretalx.track'
+ log_prefix = 'eventyay.track'
class Meta:
ordering = ('position',)
diff --git a/app/eventyay/base/models/type.py b/app/eventyay/base/models/type.py
index 1306a4df42..ace581c503 100644
--- a/app/eventyay/base/models/type.py
+++ b/app/eventyay/base/models/type.py
@@ -46,7 +46,7 @@ class SubmissionType(PretalxModel):
default=False,
)
- log_prefix = 'pretalx.submission_type'
+ log_prefix = 'eventyay.submission_type'
class Meta:
ordering = ['default_duration']
diff --git a/app/eventyay/base/models/vouchers.py b/app/eventyay/base/models/vouchers.py
index 92e0399452..5b258295ba 100644
--- a/app/eventyay/base/models/vouchers.py
+++ b/app/eventyay/base/models/vouchers.py
@@ -446,7 +446,7 @@ def applies_to(self, product: Product, variation: ProductVariation = None) -> bo
return self.product_id == product.pk
if self.product_id:
return (self.product_id == product.pk) and (variation and self.variation_id == variation.pk)
- return true
+ return True
def is_active(self):
"""
diff --git a/app/eventyay/base/notifications.py b/app/eventyay/base/notifications.py
index 34f595c3eb..7b4732ede6 100644
--- a/app/eventyay/base/notifications.py
+++ b/app/eventyay/base/notifications.py
@@ -235,73 +235,73 @@ def register_default_notification_types(sender, **kwargs):
return (
ParametrizedOrderNotificationType(
sender,
- 'pretix.event.order.placed',
+ 'eventyay.event.order.placed',
_('New order placed'),
_('A new order has been placed: {order.code}'),
),
ParametrizedOrderNotificationType(
sender,
- 'pretix.event.order.placed.require_approval',
+ 'eventyay.event.order.placed.require_approval',
_('New order requires approval'),
_('A new order has been placed that requires approval: {order.code}'),
),
ParametrizedOrderNotificationType(
sender,
- 'pretix.event.order.paid',
+ 'eventyay.event.order.paid',
_('Order marked as paid'),
_('Order {order.code} has been marked as paid.'),
),
ParametrizedOrderNotificationType(
sender,
- 'pretix.event.order.canceled',
+ 'eventyay.event.order.canceled',
_('Order canceled'),
_('Order {order.code} has been canceled.'),
),
ParametrizedOrderNotificationType(
sender,
- 'pretix.event.order.reactivated',
+ 'eventyay.event.order.reactivated',
_('Order reactivated'),
_('Order {order.code} has been reactivated.'),
),
ParametrizedOrderNotificationType(
sender,
- 'pretix.event.order.expired',
+ 'eventyay.event.order.expired',
_('Order expired'),
_('Order {order.code} has been marked as expired.'),
),
ParametrizedOrderNotificationType(
sender,
- 'pretix.event.order.modified',
+ 'eventyay.event.order.modified',
_('Order information changed'),
_('The ticket information of order {order.code} has been changed.'),
),
ParametrizedOrderNotificationType(
sender,
- 'pretix.event.order.contact.changed',
+ 'eventyay.event.order.contact.changed',
_('Order contact address changed'),
_('The contact address of order {order.code} has been changed.'),
),
ParametrizedOrderNotificationType(
sender,
- 'pretix.event.order.changed.*',
+ 'eventyay.event.order.changed.*',
_('Order changed'),
_('Order {order.code} has been changed.'),
),
ParametrizedOrderNotificationType(
sender,
- 'pretix.event.order.overpaid',
+ 'eventyay.event.order.overpaid',
_('Order has been overpaid'),
_('Order {order.code} has been overpaid.'),
),
ParametrizedOrderNotificationType(
sender,
- 'pretix.event.order.refund.created.externally',
+ 'eventyay.event.order.refund.created.externally',
_('External refund of payment'),
_('An external refund for {order.code} has occurred.'),
),
ParametrizedOrderNotificationType(
sender,
- 'pretix.event.order.refund.requested',
+ 'eventyay.event.order.refund.requested',
_('Refund requested'),
_('You have been requested to issue a refund for {order.code}.'),
),
diff --git a/app/eventyay/base/payment.py b/app/eventyay/base/payment.py
index bf9a13b4f2..ad9ff93891 100644
--- a/app/eventyay/base/payment.py
+++ b/app/eventyay/base/payment.py
@@ -337,7 +337,7 @@ def settings_form_fields(self):
'payment provider. Click here '
"for detailed information on what this does. Don't forget to set the correct fees "
'above!'
- ).format(docs_url='https://docs.eventyay.com/en/latest/user/payments/fees.html'),
+ ).format(docs_url='https://docs.eventyay.com/'),
required=False,
),
),
@@ -1253,8 +1253,14 @@ def checkout_prepare(self, request: HttpRequest, cart: Dict[str, Any]) -> Union[
return
cs = cart_session(request)
+
+ gift_card_code = request.POST.get('giftcard', '').strip()
+ if not gift_card_code:
+ messages.error(request, _('Please enter a gift card code.'))
+ return
+
try:
- gc = self.event.organizer.accepted_gift_cards.get(secret=request.POST.get('giftcard'))
+ gc = self.event.organizer.accepted_gift_cards.get(secret=gift_card_code)
if gc.currency != self.event.currency:
messages.error(request, _('This gift card does not support this currency.'))
return
@@ -1306,7 +1312,7 @@ def checkout_prepare(self, request: HttpRequest, cart: Dict[str, Any]) -> Union[
kwargs['cart_namespace'] = request.resolver_match.kwargs['cart_namespace']
return eventreverse(self.event, 'presale:event.checkout', kwargs=kwargs)
except GiftCard.DoesNotExist:
- if self.event.vouchers.filter(code__iexact=request.POST.get('giftcard')).exists():
+ if self.event.vouchers.filter(code__iexact=gift_card_code).exists():
messages.warning(
request,
_(
@@ -1334,8 +1340,13 @@ def payment_prepare(self, request: HttpRequest, payment: OrderPayment) -> Union[
)
return
+ gift_card_code = request.POST.get('giftcard', '').strip()
+ if not gift_card_code:
+ messages.error(request, _('Please enter a gift card code.'))
+ return
+
try:
- gc = self.event.organizer.accepted_gift_cards.get(secret=request.POST.get('giftcard'))
+ gc = self.event.organizer.accepted_gift_cards.get(secret=gift_card_code)
if gc.currency != self.event.currency:
messages.error(request, _('This gift card does not support this currency.'))
return
@@ -1357,7 +1368,7 @@ def payment_prepare(self, request: HttpRequest, payment: OrderPayment) -> Union[
return True
except GiftCard.DoesNotExist:
- if self.event.vouchers.filter(code__iexact=request.POST.get('giftcard')).exists():
+ if self.event.vouchers.filter(code__iexact=gift_card_code).exists():
messages.warning(
request,
_(
diff --git a/app/eventyay/base/services/cancelevent.py b/app/eventyay/base/services/cancelevent.py
index 2a57acea1b..56c0d97ede 100644
--- a/app/eventyay/base/services/cancelevent.py
+++ b/app/eventyay/base/services/cancelevent.py
@@ -83,7 +83,7 @@ def _send_mail(
real_subject,
message,
email_context,
- 'pretix.event.order.email.event_canceled',
+ 'eventyay.event.order.email.event_canceled',
user,
)
except SendMailException:
@@ -108,7 +108,7 @@ def _send_mail(
real_subject,
message,
email_context,
- 'pretix.event.order.email.event_canceled',
+ 'eventyay.event.order.email.event_canceled',
position=p,
user=user,
)
@@ -191,13 +191,13 @@ def cancel_event(
for se in subevents:
se.log_action(
- 'pretix.subevent.canceled',
+ 'eventyay.subevent.canceled',
user=user,
)
se.active = False
se.save(update_fields=['active'])
se.log_action(
- 'pretix.subevent.changed',
+ 'eventyay.subevent.changed',
user=user,
data={'active': False, '_source': 'cancel_event'},
)
@@ -210,11 +210,11 @@ def cancel_event(
user=user,
)
- for i in event.items.filter(active=True):
+ for i in event.products.filter(active=True):
i.active = False
i.save(update_fields=['active'])
i.log_action(
- 'pretix.event.item.changed',
+ 'pretix.event.product.changed',
user=user,
data={'active': False, '_source': 'cancel_event'},
)
diff --git a/app/eventyay/base/services/event.py b/app/eventyay/base/services/event.py
index f7945a4b39..cd1ef6889f 100644
--- a/app/eventyay/base/services/event.py
+++ b/app/eventyay/base/services/event.py
@@ -113,6 +113,7 @@ def get_rooms(event, user):
.values("c")
)
)
+ .order_by("sorting_priority", "name")
)
if user:
qs = qs.with_permission(event=event, user=user)
@@ -174,6 +175,9 @@ def get_room_config(room, permissions):
"force_join": room.force_join,
"modules": [],
"schedule_data": room.schedule_data or None,
+ "setup_complete": room.setup_complete,
+ "hidden": room.hidden,
+ "sidebar_hidden": room.sidebar_hidden,
}
if hasattr(room, "current_roomviews"):
@@ -251,6 +255,12 @@ def _create_room(data, with_channel=False, permission_preset="public", creator=N
}
else:
data["trait_grants"] = {}
+ has_modules = bool(data.get("module_config"))
+ data.setdefault("setup_complete", has_modules)
+ # Hidden rooms disappear from the schedule editor, this keeps them visible by default
+ data.setdefault("hidden", False)
+ # Sidebar should not display a room until a room is configured
+ data.setdefault("sidebar_hidden", data["hidden"] or not data["setup_complete"])
if (
data.get("event")
diff --git a/app/eventyay/base/services/export.py b/app/eventyay/base/services/export.py
index 9a7e171438..61716d21e5 100644
--- a/app/eventyay/base/services/export.py
+++ b/app/eventyay/base/services/export.py
@@ -57,6 +57,14 @@ def set_progress(val):
'Your data table is too big for a PDF page. Please reduce the amount of data you are exporting.'
)
raise ExportError(msg) from e
+ except Exception as e:
+ logger.exception(f'Error during export with provider {provider}.')
+ # Provide specific error message based on exception type
+ error_msg = str(e) if str(e) else type(e).__name__
+ msg = gettext(
+ 'An error occurred while generating your export: {error}. Please try again or contact support if the problem persists.'
+ ).format(error=error_msg)
+ raise ExportError(msg) from e
if d is None:
raise ExportError(gettext('Your export did not contain any data.'))
file.filename, file.type, data = d
@@ -113,7 +121,21 @@ def set_progress(val):
continue
ex = response(events, set_progress)
if ex.identifier == provider:
- d = ex.render(form_data)
+ try:
+ d = ex.render(form_data)
+ except LayoutError as e:
+ logger.exception('Error while making PDF.')
+ msg = gettext(
+ 'Your data table is too big for a PDF page. Please reduce the amount of data you are exporting.'
+ )
+ raise ExportError(msg) from e
+ except Exception as e:
+ logger.exception(f'Error during multi-event export with provider {provider}.')
+ error_msg = str(e) if str(e) else type(e).__name__
+ msg = gettext(
+ 'An error occurred while generating your export: {error}. Please try again or contact support if the problem persists.'
+ ).format(error=error_msg)
+ raise ExportError(msg) from e
if d is None:
raise ExportError(gettext('Your export did not contain any data.'))
file.filename, file.type, data = d
diff --git a/app/eventyay/base/services/mail.py b/app/eventyay/base/services/mail.py
index 469644335e..1e31bddc2f 100644
--- a/app/eventyay/base/services/mail.py
+++ b/app/eventyay/base/services/mail.py
@@ -406,7 +406,7 @@ def cm():
'likely too large to arrive.'
)
order.log_action(
- 'pretix.event.order.email.attachments.skipped',
+ 'eventyay.event.order.email.attachments.skipped',
data={
'subject': 'Attachments skipped',
'message': message,
@@ -480,7 +480,7 @@ def cm():
except MaxRetriesExceededError:
if order:
order.log_action(
- 'pretix.event.order.email.error',
+ 'eventyay.event.order.email.error',
data={
'subject': 'SMTP code {}, max retries exceeded'.format(e.smtp_code),
'message': e.smtp_error.decode()
@@ -495,7 +495,7 @@ def cm():
logger.exception('Error sending email')
if order:
order.log_action(
- 'pretix.event.order.email.error',
+ 'eventyay.event.order.email.error',
data={
'subject': 'SMTP code {}'.format(e.smtp_code),
'message': e.smtp_error.decode() if isinstance(e.smtp_error, bytes) else str(e.smtp_error),
@@ -525,7 +525,7 @@ def cm():
message.append(f'{e}: {val[0]} {val[1].decode()}')
order.log_action(
- 'pretix.event.order.email.error',
+ 'eventyay.event.order.email.error',
data={
'subject': 'SMTP error',
'message': '\n'.join(message),
@@ -552,7 +552,7 @@ def cm():
except MaxRetriesExceededError:
if order:
order.log_action(
- 'pretix.event.order.email.error',
+ 'eventyay.event.order.email.error',
data={
'subject': 'Internal error',
'message': 'Max retries exceeded',
@@ -563,7 +563,7 @@ def cm():
raise e
if order:
order.log_action(
- 'pretix.event.order.email.error',
+ 'eventyay.event.order.email.error',
data={
'subject': 'Internal error',
'message': str(e),
diff --git a/app/eventyay/base/services/orderimport.py b/app/eventyay/base/services/orderimport.py
index f0537c76b1..3f6e8f9fd3 100644
--- a/app/eventyay/base/services/orderimport.py
+++ b/app/eventyay/base/services/orderimport.py
@@ -165,7 +165,7 @@ def import_orders(event: Event, fileid: str, settings: dict, locale: str, user)
for c in cols:
c.save(o)
o.log_action(
- 'pretix.event.order.placed',
+ 'eventyay.event.order.placed',
user=user,
data={'source': 'import'},
)
diff --git a/app/eventyay/base/services/quotas.py b/app/eventyay/base/services/quotas.py
index 67fa6b976e..4010de37de 100644
--- a/app/eventyay/base/services/quotas.py
+++ b/app/eventyay/base/services/quotas.py
@@ -214,7 +214,7 @@ def _close(self, quotas):
if self.results[q][0] <= Quota.AVAILABILITY_ORDERED and q.close_when_sold_out and not q.closed:
q.closed = True
q.save(update_fields=['closed'])
- q.log_action('pretix.event.quota.closed')
+ q.log_action('eventyay.event.quota.closed')
def _compute(self, quotas, now_dt):
# Quotas we want to look at now
diff --git a/app/eventyay/base/services/room.py b/app/eventyay/base/services/room.py
index 32477c3d94..686d9cbb5e 100644
--- a/app/eventyay/base/services/room.py
+++ b/app/eventyay/base/services/room.py
@@ -71,6 +71,15 @@ async def get_viewers(event: Event, room: Room):
@database_sync_to_async
@atomic
def save_room(event, room, update_fields, old_data, by_user):
+ extra_fields = []
+ if room.module_config and not room.setup_complete:
+ room.setup_complete = True
+ extra_fields.append("setup_complete")
+ if room.sidebar_hidden:
+ room.sidebar_hidden = False
+ extra_fields.append("sidebar_hidden")
+ if extra_fields:
+ update_fields = list(set(update_fields) | set(extra_fields))
room.save(update_fields=update_fields)
new = RoomConfigSerializer(room).data
@@ -113,12 +122,14 @@ def delete_room(event, room, by_user):
def reorder_rooms(event, id_list, by_user):
def key(r):
try:
- return id_list.index(str(r.id)), r.sorting_priority, r.name
+ return id_list.index(str(r.id)), r.sorting_priority, r.name, r.id
except Exception:
- return sys.maxsize, r.sorting_priority, r.name
+ return sys.maxsize, r.sorting_priority, r.name, r.id
all_rooms = list(
- event.rooms.filter(deleted=False).only("id", "name", "sorting_priority")
+ event.rooms.filter(deleted=False)
+ .only("id", "name", "sorting_priority")
+ .order_by("sorting_priority", "name", "id")
)
all_rooms.sort(key=key)
to_update = []
diff --git a/app/eventyay/base/services/stats.py b/app/eventyay/base/services/stats.py
index 4fc099ea0d..e305bfda07 100644
--- a/app/eventyay/base/services/stats.py
+++ b/app/eventyay/base/services/stats.py
@@ -21,6 +21,7 @@
from eventyay.base.models.event import SubEvent
from eventyay.base.models.orders import OrderFee, OrderPayment
from eventyay.base.signals import order_fee_type_name
+from eventyay.helpers.timezone import get_browser_timezone
class DummyObject:
@@ -93,6 +94,7 @@ def order_overview(
date_until=None,
fees=False,
admission_only=False,
+ browser_timezone=None,
) -> Tuple[List[Tuple[ProductCategory, List[Product]]], Dict[str, Tuple[Decimal, Decimal]]]:
products = (
event.products.all()
@@ -111,18 +113,20 @@ def order_overview(
products = products.filter(admission=True)
if date_from and isinstance(date_from, date):
+ tz = get_browser_timezone(browser_timezone)
date_from = make_aware(
datetime.combine(date_from, time(hour=0, minute=0, second=0, microsecond=0)),
- event.timezone,
+ tz,
)
if date_until and isinstance(date_until, date):
+ tz = get_browser_timezone(browser_timezone)
date_until = make_aware(
datetime.combine(
date_until + timedelta(days=1),
time(hour=0, minute=0, second=0, microsecond=0),
),
- event.timezone,
+ tz,
)
if date_filter == 'order_date':
diff --git a/app/eventyay/base/services/vouchers.py b/app/eventyay/base/services/vouchers.py
index 6b9b80a235..df1222a884 100644
--- a/app/eventyay/base/services/vouchers.py
+++ b/app/eventyay/base/services/vouchers.py
@@ -45,7 +45,7 @@ def vouchers_send(
v.comment = gettext('The voucher has been sent to {recipient}.').format(recipient=r['email'])
logs.append(
v.log_action(
- 'pretix.voucher.sent',
+ 'eventyay.voucher.sent',
user=user,
data={
'recipient': r['email'],
diff --git a/app/eventyay/base/settings.py b/app/eventyay/base/settings.py
index e7a14c00e1..0fb19379e6 100644
--- a/app/eventyay/base/settings.py
+++ b/app/eventyay/base/settings.py
@@ -113,8 +113,20 @@ def validate_event_settings(event, settings_dict):
default_locale = settings_dict.get('locale')
locales = settings_dict.get('locales', [])
+ if not isinstance(locales, list):
+ locales = list(locales)
if default_locale and default_locale not in locales:
raise ValidationError({'locale': _('Your default locale must also be enabled for your event (see box above).')})
+ content_locales = settings_dict.get('content_locales')
+ if content_locales is None:
+ content_locales = locales
+ elif not isinstance(content_locales, list):
+ content_locales = list(content_locales)
+ if content_locales:
+ if invalid_content_locales := set(content_locales) - set(locales):
+ raise ValidationError(
+ {'content_locales': _('Content languages must be a subset of the active languages.')}
+ )
if settings_dict.get('attendee_names_required') and not settings_dict.get('attendee_names_asked'):
raise ValidationError(
{'attendee_names_required': _('You cannot require specifying attendee names if you do not ask for them.')}
diff --git a/app/eventyay/base/views/mixins.py b/app/eventyay/base/views/mixins.py
index ff9b2c2cb2..39a5c6ffac 100644
--- a/app/eventyay/base/views/mixins.py
+++ b/app/eventyay/base/views/mixins.py
@@ -30,7 +30,7 @@ class BaseQuestionsViewMixin:
@staticmethod
def _keyfunc(pos):
- # Sort addons after the item they are an addon to
+ # Sort addons after the product they are an addon to
if isinstance(pos, OrderPosition):
i = pos.addon_to.positionid if pos.addon_to else pos.positionid
else:
@@ -67,10 +67,10 @@ def forms(self):
)
form.pos = cartpos or orderpos
form.show_copy_answers_to_addon_button = form.pos.addon_to and (
- set(form.pos.addon_to.item.questions.all()) & set(form.pos.item.questions.all())
+ set(form.pos.addon_to.product.questions.all()) & set(form.pos.product.questions.all())
or (
- form.pos.addon_to.item.admission
- and form.pos.item.admission
+ form.pos.addon_to.product.admission
+ and form.pos.product.admission
and (
self.request.event.settings.attendee_names_asked
or self.request.event.settings.attendee_emails_asked
diff --git a/app/eventyay/cfp/views/user.py b/app/eventyay/cfp/views/user.py
index 2cb9a25d5d..d53498102c 100644
--- a/app/eventyay/cfp/views/user.py
+++ b/app/eventyay/cfp/views/user.py
@@ -219,6 +219,7 @@ def get_object(self):
def dispatch(self, request, *args, **kwargs):
if request.user.is_anonymous:
return get_login_redirect(request)
+
if not request.user.has_perm('base.is_speaker_submission', self.submission):
self.template_name = 'cfp/event/user_submission_confirm_error.html'
return super().dispatch(request, *args, **kwargs)
diff --git a/talk/src/pretalx/common/auth.py b/app/eventyay/common/auth.py
similarity index 92%
rename from talk/src/pretalx/common/auth.py
rename to app/eventyay/common/auth.py
index 60251ce07f..60ca3fcf7c 100644
--- a/talk/src/pretalx/common/auth.py
+++ b/app/eventyay/common/auth.py
@@ -1,7 +1,7 @@
from rest_framework import exceptions
from rest_framework.authentication import TokenAuthentication
-from pretalx.person.models import UserApiToken
+from eventyay.base.models.auth_token import UserApiToken
class UserTokenAuthentication(TokenAuthentication):
diff --git a/app/eventyay/common/log_display.py b/app/eventyay/common/log_display.py
index 9d472ebf80..14297930a9 100644
--- a/app/eventyay/common/log_display.py
+++ b/app/eventyay/common/log_display.py
@@ -30,106 +30,188 @@
# we log the deletion of an object, we don't have the object anymore,
# so we'll want to format the message instead.
TEMPLATE_LOG_NAMES = {
+ 'eventyay.event.delete': _('The event {name} ({slug}) by {organiser} was deleted.'),
+ 'eventyay.organiser.delete': _('The organiser {name} was deleted.'),
+ # Legacy pretalx prefixes (for backward compatibility with historic data)
'pretalx.event.delete': _('The event {name} ({slug}) by {organiser} was deleted.'),
'pretalx.organiser.delete': _('The organiser {name} was deleted.'),
}
# These log names were used in the past, and we still support them for display purposes
+# Map old pretalx prefixes to new eventyay prefixes
LOG_ALIASES = {
- 'pretalx.event.invite.orga.accept': 'pretalx.invite.orga.accept',
- 'pretalx.event.invite.orga.retract': 'pretalx.invite.orga.retract',
- 'pretalx.event.invite.orga.send': 'pretalx.invite.orga.send',
- 'pretalx.event.invite.reviewer.retract': 'pretalx.invite.reviewer.retract',
- 'pretalx.event.invite.reviewer.send': 'pretalx.invite.reviewer.send',
- 'pretalx.submission.confirmation': 'pretalx.submission.confirm',
- 'pretalx.submission.answerupdate': 'pretalx.submission.answer.update',
- 'pretalx.submission.answercreate': 'pretalx.submission.answer.create',
- # This isn't really the same thing, as the create takes place when the submission is
- # created, e.g. as a draft proposal, and the make_submitted takes place when the submission
- # is submitted to the CfP. But as we treat draft proposals as not existing at all
- # yet, we can treat this as a create action.
- 'pretalx.submission.make_submitted': 'pretalx.submission.create',
+ # Old event-level aliases
+ 'pretalx.event.invite.orga.accept': 'eventyay.invite.orga.accept',
+ 'pretalx.event.invite.orga.retract': 'eventyay.invite.orga.retract',
+ 'pretalx.event.invite.orga.send': 'eventyay.invite.orga.send',
+ 'pretalx.event.invite.reviewer.retract': 'eventyay.invite.reviewer.retract',
+ 'pretalx.event.invite.reviewer.send': 'eventyay.invite.reviewer.send',
+ # Old submission aliases
+ 'pretalx.submission.confirmation': 'eventyay.submission.confirm',
+ 'pretalx.submission.answerupdate': 'eventyay.submission.answer.update',
+ 'pretalx.submission.answercreate': 'eventyay.submission.answer.create',
+ 'pretalx.submission.make_submitted': 'eventyay.submission.create',
+ # Map all old pretalx.* prefixes to new eventyay.* prefixes
+ 'pretalx.cfp.update': 'eventyay.cfp.update',
+ 'pretalx.event.create': 'eventyay.event.create',
+ 'pretalx.event.update': 'eventyay.event.update',
+ 'pretalx.event.activate': 'eventyay.event.activate',
+ 'pretalx.event.deactivate': 'eventyay.event.deactivate',
+ 'pretalx.event.plugins.enabled': 'eventyay.event.plugins.enabled',
+ 'pretalx.event.plugins.disabled': 'eventyay.event.plugins.disabled',
+ 'pretalx.invite.orga.accept': 'eventyay.invite.orga.accept',
+ 'pretalx.invite.orga.retract': 'eventyay.invite.orga.retract',
+ 'pretalx.invite.orga.send': 'eventyay.invite.orga.send',
+ 'pretalx.invite.reviewer.retract': 'eventyay.invite.reviewer.retract',
+ 'pretalx.invite.reviewer.send': 'eventyay.invite.reviewer.send',
+ 'pretalx.team.member.remove': 'eventyay.team.member.remove',
+ 'pretalx.mail.create': 'eventyay.mail.create',
+ 'pretalx.mail.delete': 'eventyay.mail.delete',
+ 'pretalx.mail.delete_all': 'eventyay.mail.delete_all',
+ 'pretalx.mail.sent': 'eventyay.mail.sent',
+ 'pretalx.mail.update': 'eventyay.mail.update',
+ 'pretalx.mail_template.create': 'eventyay.mail_template.create',
+ 'pretalx.mail_template.delete': 'eventyay.mail_template.delete',
+ 'pretalx.mail_template.update': 'eventyay.mail_template.update',
+ 'pretalx.question.create': 'eventyay.question.create',
+ 'pretalx.question.delete': 'eventyay.question.delete',
+ 'pretalx.question.update': 'eventyay.question.update',
+ 'pretalx.question.option.create': 'eventyay.question.option.create',
+ 'pretalx.question.option.delete': 'eventyay.question.option.delete',
+ 'pretalx.question.option.update': 'eventyay.question.option.update',
+ 'pretalx.tag.create': 'eventyay.tag.create',
+ 'pretalx.tag.delete': 'eventyay.tag.delete',
+ 'pretalx.tag.update': 'eventyay.tag.update',
+ 'pretalx.room.create': 'eventyay.room.create',
+ 'pretalx.room.update': 'eventyay.room.update',
+ 'pretalx.room.delete': 'eventyay.room.delete',
+ 'pretalx.schedule.release': 'eventyay.schedule.release',
+ 'pretalx.submission.accept': 'eventyay.submission.accept',
+ 'pretalx.submission.cancel': 'eventyay.submission.cancel',
+ 'pretalx.submission.confirm': 'eventyay.submission.confirm',
+ 'pretalx.submission.create': 'eventyay.submission.create',
+ 'pretalx.submission.deleted': 'eventyay.submission.deleted',
+ 'pretalx.submission.reject': 'eventyay.submission.reject',
+ 'pretalx.submission.resource.create': 'eventyay.submission.resource.create',
+ 'pretalx.submission.resource.delete': 'eventyay.submission.resource.delete',
+ 'pretalx.submission.resource.update': 'eventyay.submission.resource.update',
+ 'pretalx.submission.review.delete': 'eventyay.submission.review.delete',
+ 'pretalx.submission.review.update': 'eventyay.submission.review.update',
+ 'pretalx.submission.review.create': 'eventyay.submission.review.create',
+ 'pretalx.submission.speakers.add': 'eventyay.submission.speakers.add',
+ 'pretalx.submission.speakers.invite': 'eventyay.submission.speakers.invite',
+ 'pretalx.submission.speakers.remove': 'eventyay.submission.speakers.remove',
+ 'pretalx.submission.unconfirm': 'eventyay.submission.unconfirm',
+ 'pretalx.submission.update': 'eventyay.submission.update',
+ 'pretalx.submission.withdraw': 'eventyay.submission.withdraw',
+ 'pretalx.submission.answer.update': 'eventyay.submission.answer.update',
+ 'pretalx.submission.answer.create': 'eventyay.submission.answer.create',
+ 'pretalx.submission.comment.create': 'eventyay.submission.comment.create',
+ 'pretalx.submission.comment.delete': 'eventyay.submission.comment.delete',
+ 'pretalx.submission_type.create': 'eventyay.submission_type.create',
+ 'pretalx.submission_type.delete': 'eventyay.submission_type.delete',
+ 'pretalx.submission_type.make_default': 'eventyay.submission_type.make_default',
+ 'pretalx.submission_type.update': 'eventyay.submission_type.update',
+ 'pretalx.access_code.create': 'eventyay.access_code.create',
+ 'pretalx.access_code.send': 'eventyay.access_code.send',
+ 'pretalx.access_code.update': 'eventyay.access_code.update',
+ 'pretalx.access_code.delete': 'eventyay.access_code.delete',
+ 'pretalx.track.create': 'eventyay.track.create',
+ 'pretalx.track.delete': 'eventyay.track.delete',
+ 'pretalx.track.update': 'eventyay.track.update',
+ 'pretalx.speaker.arrived': 'eventyay.speaker.arrived',
+ 'pretalx.speaker.unarrived': 'eventyay.speaker.unarrived',
+ 'pretalx.speaker_information.create': 'eventyay.speaker_information.create',
+ 'pretalx.speaker_information.update': 'eventyay.speaker_information.update',
+ 'pretalx.speaker_information.delete': 'eventyay.speaker_information.delete',
+ 'pretalx.user.token.reset': 'eventyay.user.token.reset',
+ 'pretalx.user.token.revoke': 'eventyay.user.token.revoke',
+ 'pretalx.user.token.upgrade': 'eventyay.user.token.upgrade',
+ 'pretalx.user.password.reset': 'eventyay.user.password.reset',
+ 'pretalx.user.password.update': 'eventyay.user.password.update',
+ 'pretalx.user.profile.update': 'eventyay.user.profile.update',
}
LOG_NAMES = {
- 'pretalx.cfp.update': _('The CfP has been modified.'),
- 'pretalx.event.create': _('The event has been added.'),
- 'pretalx.event.update': _('The event was modified.'),
- 'pretalx.event.activate': _('The event was made public.'),
- 'pretalx.event.deactivate': _('The event was deactivated.'),
- 'pretalx.event.plugins.enabled': _('A plugin was enabled.'),
- 'pretalx.event.plugins.disabled': _('A plugin was disabled.'),
- 'pretalx.invite.orga.accept': _('The invitation to the event orga was accepted.'),
- 'pretalx.invite.orga.retract': _('An invitation to the event orga was retracted.'),
- 'pretalx.invite.orga.send': _('An invitation to the event orga was sent.'),
- 'pretalx.invite.reviewer.retract': _('The invitation to the review team was retracted.'),
- 'pretalx.invite.reviewer.send': _('The invitation to the review team was sent.'),
- 'pretalx.team.member.remove': _('A team member was removed'),
- 'pretalx.mail.create': _('An email was created.'),
- 'pretalx.mail.delete': _('A pending email was deleted.'),
- 'pretalx.mail.delete_all': _('All pending emails were deleted.'),
- 'pretalx.mail.sent': _('An email was sent.'),
- 'pretalx.mail.update': _('An email was modified.'),
- 'pretalx.mail_template.create': _('A mail template was added.'),
- 'pretalx.mail_template.delete': _('A mail template was deleted.'),
- 'pretalx.mail_template.update': _('A mail template was modified.'),
- 'pretalx.question.create': _('A custom field was added.'),
- 'pretalx.question.delete': _('A custom field was deleted.'),
- 'pretalx.question.update': _('A custom field was modified.'),
- 'pretalx.question.option.create': _('A custom field option was added.'),
- 'pretalx.question.option.delete': _('A custom field option was deleted.'),
- 'pretalx.question.option.update': _('A custom field option was modified.'),
- 'pretalx.tag.create': _('A tag was added.'),
- 'pretalx.tag.delete': _('A tag was deleted.'),
- 'pretalx.tag.update': _('A tag was modified.'),
- 'pretalx.room.create': _('A new room was added.'),
- 'pretalx.room.update': _('A room was modified.'),
- 'pretalx.room.delete': _('A room was deleted.'),
- 'pretalx.schedule.release': _('A new schedule version was released.'),
- 'pretalx.submission.accept': _('The proposal was accepted.'),
- 'pretalx.submission.cancel': _('The proposal was cancelled.'),
- 'pretalx.submission.confirm': _('The proposal was confirmed.'),
- 'pretalx.submission.create': _('The proposal was added.'),
- 'pretalx.submission.deleted': _('The proposal was deleted.'),
- 'pretalx.submission.reject': _('The proposal was rejected.'),
- 'pretalx.submission.resource.create': _('A proposal resource was added.'),
- 'pretalx.submission.resource.delete': _('A proposal resource was deleted.'),
- 'pretalx.submission.resource.update': _('A proposal resource was modified.'),
- 'pretalx.submission.review.delete': _('A review was deleted.'),
- 'pretalx.submission.review.update': _('A review was modified.'),
- 'pretalx.submission.review.create': _('A review was added.'),
- 'pretalx.submission.speakers.add': _('A speaker was added to the proposal.'),
- 'pretalx.submission.speakers.invite': _('A speaker was invited to the proposal.'),
- 'pretalx.submission.speakers.remove': _('A speaker was removed from the proposal.'),
- 'pretalx.submission.unconfirm': _('The proposal was unconfirmed.'),
- 'pretalx.submission.update': _('The proposal was modified.'),
- 'pretalx.submission.withdraw': _('The proposal was withdrawn.'),
- 'pretalx.submission.answer.update': _('A custom field response was modified.'),
- 'pretalx.submission.answer.create': _('A custom field response was added.'),
- 'pretalx.submission.comment.create': _('A proposal comment was added.'),
- 'pretalx.submission.comment.delete': _('A proposal comment was deleted.'),
- 'pretalx.submission_type.create': _('A session type was added.'),
- 'pretalx.submission_type.delete': _('A session type was deleted.'),
- 'pretalx.submission_type.make_default': _('The session type was made default.'),
- 'pretalx.submission_type.update': _('A session type was modified.'),
- 'pretalx.access_code.create': _('An access code was added.'),
- 'pretalx.access_code.send': _('An access code was sent.'),
- 'pretalx.access_code.update': _('An access code was modified.'),
- 'pretalx.access_code.delete': _('An access code was deleted.'),
- 'pretalx.track.create': _('A track was added.'),
- 'pretalx.track.delete': _('A track was deleted.'),
- 'pretalx.track.update': _('A track was modified.'),
- 'pretalx.speaker.arrived': _('A speaker has been marked as arrived.'),
- 'pretalx.speaker.unarrived': _('A speaker has been marked as not arrived.'),
- 'pretalx.speaker_information.create': _('A speaker information note was added.'),
- 'pretalx.speaker_information.update': _('A speaker information note was modified.'),
- 'pretalx.speaker_information.delete': _('A speaker information note was deleted.'),
- 'pretalx.user.token.reset': _('The API token was reset.'),
- 'pretalx.user.token.revoke': _('The API token was revoked.'),
- 'pretalx.user.token.upgrade': _('The API token was upgraded to the latest version.'),
- 'pretalx.user.password.reset': phrases.base.password_reset_success,
- 'pretalx.user.password.update': _('The password was modified.'),
- 'pretalx.user.profile.update': _('The profile was modified.'),
+ # Primary eventyay prefixes (used for new logs)
+ 'eventyay.cfp.update': _('The CfP has been modified.'),
+ 'eventyay.event.create': _('The event has been added.'),
+ 'eventyay.event.update': _('The event was modified.'),
+ 'eventyay.event.activate': _('The event was made public.'),
+ 'eventyay.event.deactivate': _('The event was deactivated.'),
+ 'eventyay.event.plugins.enabled': _('A plugin was enabled.'),
+ 'eventyay.event.plugins.disabled': _('A plugin was disabled.'),
+ 'eventyay.invite.orga.accept': _('The invitation to the event orga was accepted.'),
+ 'eventyay.invite.orga.retract': _('An invitation to the event orga was retracted.'),
+ 'eventyay.invite.orga.send': _('An invitation to the event orga was sent.'),
+ 'eventyay.invite.reviewer.retract': _('The invitation to the review team was retracted.'),
+ 'eventyay.invite.reviewer.send': _('The invitation to the review team was sent.'),
+ 'eventyay.team.member.remove': _('A team member was removed'),
+ 'eventyay.mail.create': _('An email was created.'),
+ 'eventyay.mail.delete': _('A pending email was deleted.'),
+ 'eventyay.mail.delete_all': _('All pending emails were deleted.'),
+ 'eventyay.mail.sent': _('An email was sent.'),
+ 'eventyay.mail.update': _('An email was modified.'),
+ 'eventyay.mail_template.create': _('A mail template was added.'),
+ 'eventyay.mail_template.delete': _('A mail template was deleted.'),
+ 'eventyay.mail_template.update': _('A mail template was modified.'),
+ 'eventyay.question.create': _('A custom field was added.'),
+ 'eventyay.question.delete': _('A custom field was deleted.'),
+ 'eventyay.question.update': _('A custom field was modified.'),
+ 'eventyay.question.option.create': _('A custom field option was added.'),
+ 'eventyay.question.option.delete': _('A custom field option was deleted.'),
+ 'eventyay.question.option.update': _('A custom field option was modified.'),
+ 'eventyay.tag.create': _('A tag was added.'),
+ 'eventyay.tag.delete': _('A tag was deleted.'),
+ 'eventyay.tag.update': _('A tag was modified.'),
+ 'eventyay.room.create': _('A new room was added.'),
+ 'eventyay.room.update': _('A room was modified.'),
+ 'eventyay.room.delete': _('A room was deleted.'),
+ 'eventyay.schedule.release': _('A new schedule version was released.'),
+ 'eventyay.submission.accept': _('The proposal was accepted.'),
+ 'eventyay.submission.cancel': _('The proposal was cancelled.'),
+ 'eventyay.submission.confirm': _('The proposal was confirmed.'),
+ 'eventyay.submission.create': _('The proposal was added.'),
+ 'eventyay.submission.deleted': _('The proposal was deleted.'),
+ 'eventyay.submission.reject': _('The proposal was rejected.'),
+ 'eventyay.submission.resource.create': _('A proposal resource was added.'),
+ 'eventyay.submission.resource.delete': _('A proposal resource was deleted.'),
+ 'eventyay.submission.resource.update': _('A proposal resource was modified.'),
+ 'eventyay.submission.review.delete': _('A review was deleted.'),
+ 'eventyay.submission.review.update': _('A review was modified.'),
+ 'eventyay.submission.review.create': _('A review was added.'),
+ 'eventyay.submission.speakers.add': _('A speaker was added to the proposal.'),
+ 'eventyay.submission.speakers.invite': _('A speaker was invited to the proposal.'),
+ 'eventyay.submission.speakers.remove': _('A speaker was removed from the proposal.'),
+ 'eventyay.submission.unconfirm': _('The proposal was unconfirmed.'),
+ 'eventyay.submission.update': _('The proposal was modified.'),
+ 'eventyay.submission.withdraw': _('The proposal was withdrawn.'),
+ 'eventyay.submission.answer.update': _('A custom field response was modified.'),
+ 'eventyay.submission.answer.create': _('A custom field response was added.'),
+ 'eventyay.submission.comment.create': _('A proposal comment was added.'),
+ 'eventyay.submission.comment.delete': _('A proposal comment was deleted.'),
+ 'eventyay.submission_type.create': _('A session type was added.'),
+ 'eventyay.submission_type.delete': _('A session type was deleted.'),
+ 'eventyay.submission_type.make_default': _('The session type was made default.'),
+ 'eventyay.submission_type.update': _('A session type was modified.'),
+ 'eventyay.access_code.create': _('An access code was added.'),
+ 'eventyay.access_code.send': _('An access code was sent.'),
+ 'eventyay.access_code.update': _('An access code was modified.'),
+ 'eventyay.access_code.delete': _('An access code was deleted.'),
+ 'eventyay.track.create': _('A track was added.'),
+ 'eventyay.track.delete': _('A track was deleted.'),
+ 'eventyay.track.update': _('A track was modified.'),
+ 'eventyay.speaker.arrived': _('A speaker has been marked as arrived.'),
+ 'eventyay.speaker.unarrived': _('A speaker has been marked as not arrived.'),
+ 'eventyay.speaker_information.create': _('A speaker information note was added.'),
+ 'eventyay.speaker_information.update': _('A speaker information note was modified.'),
+ 'eventyay.speaker_information.delete': _('A speaker information note was deleted.'),
+ 'eventyay.user.token.reset': _('The API token was reset.'),
+ 'eventyay.user.token.revoke': _('The API token was revoked.'),
+ 'eventyay.user.token.upgrade': _('The API token was upgraded to the latest version.'),
+ 'eventyay.user.password.reset': phrases.base.password_reset_success,
+ 'eventyay.user.password.update': _('The password was modified.'),
+ 'eventyay.user.profile.update': _('The profile was modified.'),
}
diff --git a/app/eventyay/common/templatetags/event_tags.py b/app/eventyay/common/templatetags/event_tags.py
index c3fecc9f92..0d4eb227ce 100644
--- a/app/eventyay/common/templatetags/event_tags.py
+++ b/app/eventyay/common/templatetags/event_tags.py
@@ -34,3 +34,5 @@ def cfp_locale_switch_url(context, locale_code):
query['locale'] = locale_code
query['next'] = request.get_full_path()
return f"{base}?{query.urlencode()}"
+
+
diff --git a/app/eventyay/common/urls.py b/app/eventyay/common/urls.py
index 8f1db6dbc2..6f3384cdee 100644
--- a/app/eventyay/common/urls.py
+++ b/app/eventyay/common/urls.py
@@ -2,10 +2,24 @@
from urllib.parse import urljoin, urlparse
from django.conf import settings
-from django.urls import resolve, reverse
+from django.urls import register_converter, resolve, reverse
from urlman import Urls
+# Custom path converter for organizer slugs that allows dots
+class OrganizerSlugConverter:
+ regex = r'[a-zA-Z0-9_.-]+'
+
+ def to_python(self, value):
+ return value
+
+ def to_url(self, value):
+ return value
+
+
+register_converter(OrganizerSlugConverter, 'orgslug')
+
+
def get_base_url(event=None, url=None):
if url and url.startswith('/orga'):
return settings.SITE_URL
diff --git a/app/eventyay/config/settings.py b/app/eventyay/config/settings.py
index 1893ef69dc..2be9c432c6 100644
--- a/app/eventyay/config/settings.py
+++ b/app/eventyay/config/settings.py
@@ -445,25 +445,43 @@ def instance_name(request):
ALL_LANGUAGES = [
('en', _('English')),
('de', _('German')),
- ('de-formal', _('German (informal)')),
+ ('de-formal', _('German (formal)')),
('ar', _('Arabic')),
- ('zh-hans', _('Chinese (simplified)')),
+ ('bg', _('Bulgarian')),
+ ('ca', _('Catalan')),
+ ('cs', _('Czech')),
('da', _('Danish')),
- ('nl', _('Dutch')),
- ('nl-informal', _('Dutch (informal)')),
- ('fr', _('French')),
- ('fi', _('Finnish')),
('el', _('Greek')),
+ ('es', _('Spanish')),
+ ('fa-ir', _('Persian')),
+ ('fi', _('Finnish')),
+ ('fr', _('French')),
+ ('hu', _('Hungarian')),
+ ('id', _('Indonesian')),
('it', _('Italian')),
+ ('ja-jp', _('Japanese')),
+ ('ko', _('Korean')),
('lv', _('Latvian')),
+ ('ms', _('Malay')),
+ ('nb-no', _('Norwegian Bokmål')),
+ ('nl', _('Dutch')),
+ ('nl-informal', _('Dutch (informal)')),
('pl', _('Polish')),
+ ('pl-informal', _('Polish (informal)')),
+ ('pt-br', _('Brazilian Portuguese')),
('pt-pt', _('Portuguese (Portugal)')),
- ('pt-br', _('Portuguese (Brazil)')),
+ ('ro', _('Romanian')),
('ru', _('Russian')),
- ('es', _('Spanish')),
+ ('si', _('Sinhala')),
+ ('sl', _('Slovenian')),
+ ('sv', _('Swedish')),
('sw', _('Swahili')),
+ ('th', _('Thai')),
('tr', _('Turkish')),
('uk', _('Ukrainian')),
+ ('vi', _('Vietnamese')),
+ ('zh-hans', _('Chinese (Simplified)')),
+ ('zh-hant', _('Chinese (Traditional)')),
]
LANGUAGES_OFFICIAL = {'en', 'de', 'de-formal'}
LANGUAGES_INCUBATING = {'pl', 'fi', 'pt-br'} - set(config.get('languages', 'allow_incubating', fallback='').split(','))
@@ -487,7 +505,7 @@ def instance_name(request):
'de-formal': {
'bidi': False,
'code': 'de-formal',
- 'name': 'German (informal)',
+ 'name': 'German (formal)',
'name_local': 'Deutsch',
'public_code': 'de',
},
@@ -569,7 +587,7 @@ def instance_name(request):
'natural_name': 'Deutsch',
'official': True,
'percentage': 100,
- 'path': 'de_DE',
+ 'path': 'de',
},
'de-formal': {
'name': _('German (formal)'),
@@ -585,12 +603,30 @@ def instance_name(request):
'official': False,
'percentage': 72,
},
+ 'bg': {
+ 'name': _('Bulgarian'),
+ 'natural_name': 'Български',
+ 'official': False,
+ 'percentage': 0,
+ },
+ 'ca': {
+ 'name': _('Catalan'),
+ 'natural_name': 'Català',
+ 'official': False,
+ 'percentage': 0,
+ },
'cs': {
'name': _('Czech'),
'natural_name': 'Čeština',
'official': False,
'percentage': 97,
},
+ 'da': {
+ 'name': _('Danish'),
+ 'natural_name': 'Dansk',
+ 'official': False,
+ 'percentage': 0,
+ },
'el': {
'name': _('Greek'),
'natural_name': 'Ελληνικά',
@@ -605,18 +641,30 @@ def instance_name(request):
},
'fa-ir': {
'name': _('Persian'),
- 'natural_name': 'قارسی',
+ 'natural_name': 'فارسی',
'official': False,
'percentage': 99,
'path': 'fa_IR',
'public_code': 'fa_IR',
},
+ 'fi': {
+ 'name': _('Finnish'),
+ 'natural_name': 'Suomi',
+ 'official': False,
+ 'percentage': 0,
+ },
'fr': {
'name': _('French'),
'natural_name': 'Français',
'official': False,
'percentage': 98,
- 'path': 'fr_FR',
+ 'path': 'fr',
+ },
+ 'hu': {
+ 'name': _('Hungarian'),
+ 'natural_name': 'Magyar',
+ 'official': False,
+ 'percentage': 0,
},
'it': {
'name': _('Italian'),
@@ -624,12 +672,45 @@ def instance_name(request):
'official': False,
'percentage': 95,
},
+ 'id': {
+ 'name': _('Indonesian'),
+ 'natural_name': 'Bahasa Indonesia',
+ 'official': False,
+ 'percentage': 90,
+ },
'ja-jp': {
'name': _('Japanese'),
'natural_name': '日本語',
'official': False,
'percentage': 69,
'public_code': 'jp',
+ 'path': 'ja',
+ },
+ 'ko': {
+ 'name': _('Korean'),
+ 'natural_name': '한국어',
+ 'official': False,
+ 'percentage': 88,
+ },
+ 'lv': {
+ 'name': _('Latvian'),
+ 'natural_name': 'Latviešu',
+ 'official': False,
+ 'percentage': 0,
+ },
+ 'ms': {
+ 'name': _('Malay'),
+ 'natural_name': 'Bahasa Melayu',
+ 'official': False,
+ 'percentage': 0,
+ },
+ 'nb-no': {
+ 'name': _('Norwegian Bokmål'),
+ 'natural_name': 'Norsk bokmål',
+ 'official': False,
+ 'percentage': 0,
+ 'public_code': 'nb',
+ 'path': 'nb_NO',
},
'nl': {
'name': _('Dutch'),
@@ -637,12 +718,28 @@ def instance_name(request):
'official': False,
'percentage': 88,
},
+ 'pl': {
+ 'name': _('Polish'),
+ 'natural_name': 'Polski',
+ 'official': False,
+ 'percentage': 0,
+ 'path': 'pl',
+ },
+ 'pl-informal': {
+ 'name': _('Polish (informal)'),
+ 'natural_name': 'Polski (nieformalny)',
+ 'official': False,
+ 'percentage': 0,
+ 'public_code': 'pl',
+ 'path': 'pl_Informal',
+ },
'pt-br': {
- 'name': _('Brasilian Portuguese'),
+ 'name': _('Brazilian Portuguese'),
'natural_name': 'Português brasileiro',
'official': False,
'percentage': 89,
'public_code': 'pt',
+ 'path': 'pt_BR',
},
'pt-pt': {
'name': _('Portuguese'),
@@ -650,6 +747,13 @@ def instance_name(request):
'official': False,
'percentage': 89,
'public_code': 'pt',
+ 'path': 'pt_PT',
+ },
+ 'ro': {
+ 'name': _('Romanian'),
+ 'natural_name': 'Română',
+ 'official': False,
+ 'percentage': 0,
},
'ru': {
'name': _('Russian'),
@@ -657,31 +761,71 @@ def instance_name(request):
'official': True,
'percentage': 0,
},
+ 'si': {
+ 'name': _('Sinhala'),
+ 'natural_name': 'සිංහල',
+ 'official': False,
+ 'percentage': 0,
+ },
+ 'sl': {
+ 'name': _('Slovenian'),
+ 'natural_name': 'Slovenščina',
+ 'official': False,
+ 'percentage': 0,
+ },
+ 'sv': {
+ 'name': _('Swedish'),
+ 'natural_name': 'Svenska',
+ 'official': False,
+ 'percentage': 0,
+ },
'sw': {
'name': _('Swahili'),
'natural_name': 'Kiswahili',
'official': False,
'percentage': 0,
},
- 'ua': {
+ 'th': {
+ 'name': _('Thai'),
+ 'natural_name': 'ไทย',
+ 'official': False,
+ 'percentage': 0,
+ },
+ 'tr': {
+ 'name': _('Turkish'),
+ 'natural_name': 'Türkçe',
+ 'official': False,
+ 'percentage': 0,
+ },
+ 'uk': {
'name': _('Ukrainian'),
'natural_name': 'Українська',
'official': True,
'percentage': 0,
+ 'public_code': 'uk',
+ 'path': 'ua',
+ },
+ 'vi': {
+ 'name': _('Vietnamese'),
+ 'natural_name': 'Tiếng Việt',
+ 'official': False,
+ 'percentage': 0,
},
'zh-hant': {
- 'name': _('Traditional Chinese (Taiwan)'),
- 'natural_name': '漢語',
+ 'name': _('Chinese (Traditional)'),
+ 'natural_name': '繁體中文',
'official': False,
'percentage': 66,
'public_code': 'zh',
+ 'path': 'zh_Hant',
},
'zh-hans': {
- 'name': _('Simplified Chinese'),
+ 'name': _('Chinese (Simplified)'),
'natural_name': '简体中文',
'official': False,
'percentage': 86,
'public_code': 'zh',
+ 'path': 'zh_Hans',
},
}
LANGUAGES_RTL = {
diff --git a/app/eventyay/config/urls.py b/app/eventyay/config/urls.py
index e87f1afbe2..afbc0149b2 100644
--- a/app/eventyay/config/urls.py
+++ b/app/eventyay/config/urls.py
@@ -4,16 +4,12 @@
from django.conf.urls.static import static
from django.urls import include, path
from django.urls import re_path as url
-from django.views.generic import RedirectView
import eventyay.control.urls
import eventyay.eventyay_common.urls
import eventyay.presale.urls
-from eventyay.base.views import health, redirect
+from eventyay.base.views import cachedfiles, csp, health, js_catalog, js_helpers, metrics, redirect
from eventyay.control.views import pages
-from eventyay.base.views import js_helpers
-from eventyay.base.views import cachedfiles, csp, health, js_catalog, metrics, redirect
-
base_patterns = [
url(
diff --git a/app/eventyay/control/__init__.py b/app/eventyay/control/__init__.py
index 332dcf16d0..eb7aeed422 100644
--- a/app/eventyay/control/__init__.py
+++ b/app/eventyay/control/__init__.py
@@ -7,3 +7,4 @@ class ControlConfig(AppConfig):
def ready(self):
from . import tasks # noqa
+ from . import logdisplay # noqa
diff --git a/app/eventyay/control/forms/checkin.py b/app/eventyay/control/forms/checkin.py
index 612ef15ba2..dd969fa82c 100644
--- a/app/eventyay/control/forms/checkin.py
+++ b/app/eventyay/control/forms/checkin.py
@@ -44,7 +44,7 @@ def __init__(self, **kwargs):
self.event = kwargs.pop('event')
kwargs.pop('locales', None)
super().__init__(**kwargs)
- self.fields['limit_products'].queryset = self.event.items.all()
+ self.fields['limit_products'].queryset = self.event.products.all()
self.fields['auto_checkin_sales_channels'] = forms.MultipleChoiceField(
label=self.fields['auto_checkin_sales_channels'].label,
help_text=self.fields['auto_checkin_sales_channels'].help_text,
@@ -117,7 +117,7 @@ def __init__(self, **kwargs):
self.event = kwargs.pop('event')
kwargs.pop('locales', None)
super().__init__(**kwargs)
- self.fields['limit_products'].queryset = self.event.items.all()
+ self.fields['limit_products'].queryset = self.event.products.all()
if not self.event.organizer.gates.exists():
del self.fields['gates']
diff --git a/app/eventyay/control/forms/event.py b/app/eventyay/control/forms/event.py
index 015a7e777e..4be7d2400b 100644
--- a/app/eventyay/control/forms/event.py
+++ b/app/eventyay/control/forms/event.py
@@ -1,4 +1,4 @@
-from urllib.parse import urlencode, urlparse
+from urllib.parse import urlencode
from django import forms
from django.conf import settings
@@ -7,9 +7,9 @@
from django.db.models import Q
from django.forms import CheckboxSelectMultiple, formset_factory
from django.urls import reverse
+from django.utils.crypto import get_random_string
from django.utils.html import escape
from django.utils.safestring import mark_safe
-from django.utils.crypto import get_random_string
from django.utils.timezone import get_current_timezone_name
from django.utils.translation import gettext, pgettext_lazy
from django.utils.translation import gettext_lazy as _
@@ -78,7 +78,7 @@ def __init__(self, *args, **kwargs):
# Make organizer required only if more than one exists
organizer_count = qs.count()
is_required = organizer_count > 1
-
+
self.fields['organizer'] = forms.ModelChoiceField(
label=_('Organizer'),
queryset=qs,
@@ -176,11 +176,11 @@ def __init__(self, *args, **kwargs):
self.fields['location'].widget.attrs['rows'] = '3'
self.fields['location'].widget.attrs['placeholder'] = _('Sample Conference Center\nHeidelberg, Germany')
self.fields['slug'].widget.prefix = build_absolute_uri(self.organizer, 'presale:organizer.index')
-
+
# Generate a unique slug if none provided
if not self.initial.get('slug'):
charset = list('abcdefghjklmnpqrstuvwxyz3789')
-
+
# Try different lengths until we find a unique slug
length = 6
counter = 0
@@ -192,11 +192,11 @@ def __init__(self, *args, **kwargs):
# Fallback: add counter to ensure uniqueness
candidate = f'{get_random_string(length=4, allowed_chars=charset)}{counter}'
counter += 1
-
+
if not self.organizer.events.filter(slug__iexact=candidate).exists():
self.initial['slug'] = candidate
break
-
+
if self.has_subevents:
del self.fields['presale_start']
del self.fields['presale_end']
@@ -549,6 +549,7 @@ class EventSettingsForm(SettingsForm):
'banner_text',
'banner_text_bottom',
'order_email_asked_twice',
+ 'include_wikimedia_username',
'allow_modifications',
'last_order_modification_date',
'allow_modifications_after_checkin',
@@ -1451,7 +1452,7 @@ def __init__(self, *args, **kwargs):
plugins_active = self.obj.get_plugins()
if ('eventyay_stripe' not in plugins_active) or (not self.obj.settings.payment_stripe_client_id):
del self.fields['payment_stripe__enabled']
- if 'pretix.plugins.banktransfer' not in plugins_active:
+ if 'eventyay.plugins.banktransfer' not in plugins_active:
del self.fields['payment_banktransfer__enabled']
self.fields['payment_banktransfer_bank_details'].required = False
for f in self.fields.values():
diff --git a/app/eventyay/control/forms/filter.py b/app/eventyay/control/forms/filter.py
index 410acc11b9..0d5728d102 100644
--- a/app/eventyay/control/forms/filter.py
+++ b/app/eventyay/control/forms/filter.py
@@ -1,7 +1,6 @@
from datetime import datetime, time, timedelta
from decimal import Decimal
from urllib.parse import urlencode
-
from django import forms
from django.apps import apps
from django.conf import settings
@@ -19,6 +18,7 @@
from django.urls import reverse, reverse_lazy
from django.utils.formats import date_format, localize
from django.utils.functional import cached_property
+from django.utils import timezone
from django.utils.timezone import get_current_timezone, make_aware, now
from django.utils.translation import gettext, pgettext_lazy
from django.utils.translation import gettext_lazy as _
@@ -28,6 +28,7 @@
DatePickerWidget,
SplitDateTimePickerWidget,
)
+from eventyay.helpers.timezone import get_browser_timezone, attach_timezone_to_naive_clock_time
from eventyay.base.models import (
Checkin,
Event,
@@ -470,6 +471,12 @@ class EventOrderExpertFilterForm(EventOrderFilterForm):
label=_('Order placed before'),
required=False,
)
+ browser_timezone = forms.CharField(
+ widget=forms.HiddenInput(attrs={'class': 'browser-timezone-field'}),
+ required=False,
+ initial='UTC',
+ label=_('Timezone'),
+ )
email = forms.CharField(required=False, label=_('E-mail address'))
comment = forms.CharField(required=False, label=_('Comment'))
locale = forms.ChoiceField(required=False, label=_('Locale'), choices=settings.LANGUAGES)
@@ -589,10 +596,16 @@ def filter_qs(self, qs):
).distinct()
if fdata.get('email'):
qs = qs.filter(email__icontains=fdata.get('email'))
- if fdata.get('created_from'):
- qs = qs.filter(datetime__gte=fdata.get('created_from'))
- if fdata.get('created_to'):
- qs = qs.filter(datetime__lte=fdata.get('created_to'))
+ if fdata.get('created_from') or fdata.get('created_to'):
+ browser_tz = get_browser_timezone(fdata.get('browser_timezone'))
+
+ def attach_timezone(dt_value):
+ return attach_timezone_to_naive_clock_time(dt_value, browser_tz)
+
+ if fdata.get('created_from'):
+ qs = qs.filter(datetime__gte=attach_timezone(fdata['created_from']))
+ if fdata.get('created_to'):
+ qs = qs.filter(datetime__lt=attach_timezone(fdata['created_to']))
if fdata.get('comment'):
qs = qs.filter(comment__icontains=fdata.get('comment'))
if fdata.get('sales_channel'):
@@ -1781,6 +1794,12 @@ class OverviewFilterForm(FilterForm):
required=False,
widget=DatePickerWidget,
)
+ browser_timezone = forms.CharField(
+ widget=forms.HiddenInput(attrs={'class': 'browser-timezone-field'}),
+ required=False,
+ initial='UTC',
+ label=_('Timezone'),
+ )
def __init__(self, *args, **kwargs):
self.event = kwargs.pop('event')
diff --git a/app/eventyay/control/forms/orders.py b/app/eventyay/control/forms/orders.py
index 3fc843d8fd..3b87ec9dda 100644
--- a/app/eventyay/control/forms/orders.py
+++ b/app/eventyay/control/forms/orders.py
@@ -27,10 +27,10 @@
)
from eventyay.base.models import (
InvoiceAddress,
- ProductAddOn,
Order,
OrderFee,
OrderPosition,
+ ProductAddOn,
TaxRule,
)
from eventyay.base.models.event import SubEvent
diff --git a/app/eventyay/control/forms/product.py b/app/eventyay/control/forms/product.py
index 3f99d5f8ac..100a50c731 100644
--- a/app/eventyay/control/forms/product.py
+++ b/app/eventyay/control/forms/product.py
@@ -402,7 +402,7 @@ def save(self, *args, **kwargs):
quota = self.cleaned_data.get('quota_add_existing')
quota.products.add(self.instance)
quota.log_action(
- 'pretix.event.quota.changed',
+ 'eventyay.event.quota.changed',
user=self.user,
data={'product_added': self.instance.pk},
)
@@ -413,7 +413,7 @@ def save(self, *args, **kwargs):
quota = Quota.objects.create(event=self.event, name=quota_name, size=quota_size)
quota.products.add(self.instance)
quota.log_action(
- 'pretix.event.quota.added',
+ 'eventyay.event.quota.added',
user=self.user,
data={
'name': quota_name,
diff --git a/app/eventyay/control/logdisplay.py b/app/eventyay/control/logdisplay.py
index 299f7fc96a..426accf1de 100644
--- a/app/eventyay/control/logdisplay.py
+++ b/app/eventyay/control/logdisplay.py
@@ -29,11 +29,11 @@
OVERVIEW_BANLIST = ['eventyay.plugins.sendmail.order.email.sent']
-def _display_order_changed(event: Event, logentry: LogEntry):
+def _display_order_changed(event: Event, logentry: LogEntry, action_type: str):
data = json.loads(logentry.data)
text = _('The order has been changed:')
- if logentry.action_type == 'eventyay.event.order.changed.product':
+ if action_type == 'eventyay.event.order.changed.product':
old_product = str(event.products.get(pk=data['old_product']))
if data['old_variation']:
old_product += ' - ' + str(ProductVariation.objects.get(product__event=event, pk=data['old_variation']))
@@ -51,7 +51,7 @@ def _display_order_changed(event: Event, logentry: LogEntry):
new_price=money_filter(Decimal(data['new_price']), event.currency),
)
)
- elif logentry.action_type == 'eventyay.event.order.changed.seat':
+ elif action_type == 'eventyay.event.order.changed.seat':
return (
text
+ ' '
@@ -61,7 +61,7 @@ def _display_order_changed(event: Event, logentry: LogEntry):
new_seat=data.get('new_seat'),
)
)
- elif logentry.action_type == 'eventyay.event.order.changed.subevent':
+ elif action_type == 'eventyay.event.order.changed.subevent':
old_se = str(event.subevents.get(pk=data['old_subevent']))
new_se = str(event.subevents.get(pk=data['new_subevent']))
return (
@@ -77,7 +77,7 @@ def _display_order_changed(event: Event, logentry: LogEntry):
new_price=money_filter(Decimal(data['new_price']), event.currency),
)
)
- elif logentry.action_type == 'eventyay.event.order.changed.price':
+ elif action_type == 'eventyay.event.order.changed.price':
return (
text
+ ' '
@@ -87,7 +87,7 @@ def _display_order_changed(event: Event, logentry: LogEntry):
new_price=money_filter(Decimal(data['new_price']), event.currency),
)
)
- elif logentry.action_type == 'eventyay.event.order.changed.tax_rule':
+ elif action_type == 'eventyay.event.order.changed.tax_rule':
if 'positionid' in data:
return (
text
@@ -108,9 +108,9 @@ def _display_order_changed(event: Event, logentry: LogEntry):
new_rule=TaxRule.objects.get(pk=data['new_taxrule']),
)
)
- elif logentry.action_type == 'eventyay.event.order.changed.addfee':
+ elif action_type == 'eventyay.event.order.changed.addfee':
return text + ' ' + str(_('A fee has been added'))
- elif logentry.action_type == 'eventyay.event.order.changed.feevalue':
+ elif action_type == 'eventyay.event.order.changed.feevalue':
return (
text
+ ' '
@@ -119,7 +119,7 @@ def _display_order_changed(event: Event, logentry: LogEntry):
new_price=money_filter(Decimal(data['new_price']), event.currency),
)
)
- elif logentry.action_type == 'eventyay.event.order.changed.cancelfee':
+ elif action_type == 'eventyay.event.order.changed.cancelfee':
return (
text
+ ' '
@@ -127,7 +127,7 @@ def _display_order_changed(event: Event, logentry: LogEntry):
old_price=money_filter(Decimal(data['old_price']), event.currency),
)
)
- elif logentry.action_type == 'eventyay.event.order.changed.cancel':
+ elif action_type == 'eventyay.event.order.changed.cancel':
old_product = str(event.products.get(pk=data['old_product']))
if data['old_variation']:
old_product += ' - ' + str(ProductVariation.objects.get(pk=data['old_variation']))
@@ -140,7 +140,7 @@ def _display_order_changed(event: Event, logentry: LogEntry):
old_price=money_filter(Decimal(data['old_price']), event.currency),
)
)
- elif logentry.action_type == 'eventyay.event.order.changed.add':
+ elif action_type == 'eventyay.event.order.changed.add':
product = str(event.products.get(pk=data['product']))
if data['variation']:
product += ' - ' + str(ProductVariation.objects.get(product__event=event, pk=data['variation']))
@@ -166,7 +166,7 @@ def _display_order_changed(event: Event, logentry: LogEntry):
price=money_filter(Decimal(data['price']), event.currency),
)
)
- elif logentry.action_type == 'eventyay.event.order.changed.secret':
+ elif action_type == 'eventyay.event.order.changed.secret':
return (
text
+ ' '
@@ -174,7 +174,7 @@ def _display_order_changed(event: Event, logentry: LogEntry):
posid=data.get('positionid', '?'),
)
)
- elif logentry.action_type == 'eventyay.event.order.changed.split':
+ elif action_type == 'eventyay.event.order.changed.split':
old_product = str(event.products.get(pk=data['old_product']))
if data['old_variation']:
old_product += ' - ' + str(ProductVariation.objects.get(pk=data['old_variation']))
@@ -196,13 +196,13 @@ def _display_order_changed(event: Event, logentry: LogEntry):
old_price=money_filter(Decimal(data['old_price']), event.currency),
)
)
- elif logentry.action_type == 'eventyay.event.order.changed.split_from':
+ elif action_type == 'eventyay.event.order.changed.split_from':
return _('This order has been created by splitting the order {order}').format(
order=data['original_order'],
)
-def _display_checkin(event, logentry):
+def _display_checkin(event, logentry, action_type: str):
data = logentry.parsed_data
show_dt = False
@@ -220,7 +220,7 @@ def _display_checkin(event, logentry):
else:
checkin_list = _('(unknown)')
- if logentry.action_type == 'eventyay.event.checkin.unknown':
+ if action_type == 'eventyay.event.checkin.unknown':
if show_dt:
return _('Unknown scan of code "{barcode}…" at {datetime} for list "{list}", type "{type}".').format(
posid=data.get('positionid'),
@@ -237,7 +237,7 @@ def _display_checkin(event, logentry):
list=checkin_list,
)
- if logentry.action_type == 'eventyay.event.checkin.revoked':
+ if action_type == 'eventyay.event.checkin.revoked':
if show_dt:
return _(
'Scan scan of revoked code "{barcode}…" at {datetime} for list "{list}", type "{type}", was uploaded.'
@@ -256,7 +256,7 @@ def _display_checkin(event, logentry):
list=checkin_list,
)
- if logentry.action_type == 'eventyay.event.checkin.denied':
+ if action_type == 'eventyay.event.checkin.denied':
if show_dt:
return _(
'Denied scan of position #{posid} at {datetime} for list "{list}", type "{type}", '
@@ -312,8 +312,154 @@ def _display_checkin(event, logentry):
)
+# Map legacy pretix.* AND pretalx.* action types to eventyay.* for backward compatibility
+# This ensures old log entries with legacy prefixes still display correctly
+PRETIX_LEGACY_ALIASES = {
+ # pretix.* (old ticketing system) mappings - comprehensive list from all 47+ changes
+ 'pretix.event.quota.added': 'eventyay.event.quota.added',
+ 'pretix.event.quota.changed': 'eventyay.event.quota.changed',
+ 'pretix.event.quota.deleted': 'eventyay.event.quota.deleted',
+ 'pretix.event.quota.opened': 'eventyay.event.quota.opened',
+ 'pretix.event.quota.closed': 'eventyay.event.quota.closed',
+ 'pretix.subevent.quota.added': 'eventyay.subevent.quota.added',
+ 'pretix.subevent.quota.changed': 'eventyay.subevent.quota.changed',
+ 'pretix.subevent.quota.deleted': 'eventyay.subevent.quota.deleted',
+ 'pretix.event.category.added': 'eventyay.event.category.added',
+ 'pretix.event.category.deleted': 'eventyay.event.category.deleted',
+ 'pretix.team.created': 'eventyay.team.created',
+ 'pretix.team.deleted': 'eventyay.team.deleted',
+ 'pretix.team.member.added': 'eventyay.team.member.added',
+ 'pretix.team.member.removed': 'eventyay.team.member.removed',
+ 'pretix.team.member.joined': 'eventyay.team.member.joined',
+ 'pretix.team.member.left': 'eventyay.team.member.left',
+ 'pretix.team.token.created': 'eventyay.team.token.created',
+ 'pretix.user.settings.changed': 'eventyay.user.settings.changed',
+ 'pretix.user.settings.2fa.enabled': 'eventyay.user.settings.2fa.enabled',
+ 'pretix.user.settings.2fa.disabled': 'eventyay.user.settings.2fa.disabled',
+ 'pretix.user.settings.2fa.device.added': 'eventyay.user.settings.2fa.device.added',
+ 'pretix.user.settings.2fa.regenemergency': 'eventyay.user.settings.2fa.regenemergency',
+ 'pretix.user.settings.notifications.enabled': 'eventyay.user.settings.notifications.enabled',
+ 'pretix.user.settings.notifications.disabled': 'eventyay.user.settings.notifications.disabled',
+ 'pretix.user.anonymized': 'eventyay.user.anonymized',
+ 'pretix.control.auth.user.forgot_password.mail_sent': 'eventyay.control.auth.user.forgot_password.mail_sent',
+ 'pretix.voucher.added': 'eventyay.voucher.added',
+ 'pretix.voucher.changed': 'eventyay.voucher.changed',
+ 'pretix.voucher.deleted': 'eventyay.voucher.deleted',
+ 'pretix.voucher.redeemed': 'eventyay.voucher.redeemed',
+ 'pretix.event.product.added': 'eventyay.event.product.added',
+ 'pretix.event.product.changed': 'eventyay.event.product.changed',
+ 'pretix.event.product.deleted': 'eventyay.event.product.deleted',
+ 'pretix.event.order.modified': 'eventyay.event.order.modified',
+ 'pretix.event.order.unpaid': 'eventyay.event.order.unpaid',
+ 'pretix.event.order.secret.changed': 'eventyay.event.order.secret.changed',
+ 'pretix.event.order.expirychanged': 'eventyay.event.order.expirychanged',
+ 'pretix.event.order.expired': 'eventyay.event.order.expired',
+ 'pretix.event.order.paid': 'eventyay.event.order.paid',
+ 'pretix.event.order.refunded': 'eventyay.event.order.refunded',
+ 'pretix.event.order.canceled': 'eventyay.event.order.canceled',
+ 'pretix.event.order.reactivated': 'eventyay.event.order.reactivated',
+ 'pretix.event.order.placed': 'eventyay.event.order.placed',
+ 'pretix.event.order.approved': 'eventyay.event.order.approved',
+ 'pretix.event.order.denied': 'eventyay.event.order.denied',
+ 'pretix.event.order.invoice.generated': 'eventyay.event.order.invoice.generated',
+ 'pretix.event.order.invoice.regenerated': 'eventyay.event.order.invoice.regenerated',
+ 'pretix.event.order.invoice.reissued': 'eventyay.event.order.invoice.reissued',
+ 'pretix.event.order.changed': 'eventyay.event.order.changed',
+ 'pretix.event.order.changed.item': 'eventyay.event.order.changed.item',
+ 'pretix.event.settings': 'eventyay.event.settings',
+ 'pretix.event.live.activated': 'eventyay.event.live.activated',
+ 'pretix.event.live.deactivated': 'eventyay.event.live.deactivated',
+ 'pretix.event.testmode.activated': 'eventyay.event.testmode.activated',
+ 'pretix.event.testmode.deactivated': 'eventyay.event.testmode.deactivated',
+ 'pretix.subevent.added': 'eventyay.subevent.added',
+ 'pretix.subevent.changed': 'eventyay.subevent.changed',
+ 'pretix.subevent.deleted': 'eventyay.subevent.deleted',
+ 'pretix.device.created': 'eventyay.device.created',
+ 'pretix.device.revoked': 'eventyay.device.revoked',
+ 'pretix.gate.created': 'eventyay.gate.created',
+ 'pretix.gate.deleted': 'eventyay.gate.deleted',
+ 'pretix.giftcards.created': 'eventyay.giftcards.created',
+ 'pretix.giftcards.modified': 'eventyay.giftcards.modified',
+ 'pretix.property.created': 'eventyay.property.created',
+ 'pretix.property.deleted': 'eventyay.property.deleted',
+ 'pretix.event.orders.waitinglist.deleted': 'eventyay.event.orders.waitinglist.deleted',
+ 'pretix.event.checkin': 'eventyay.event.checkin',
+ 'pretix.event.checkin.unknown': 'eventyay.event.checkin.unknown',
+ 'pretix.event.checkin.revoked': 'eventyay.event.checkin.revoked',
+ 'pretix.event.checkin.denied': 'eventyay.event.checkin.denied',
+ 'pretix.event.checkin.reverted': 'eventyay.event.checkin.reverted',
+ 'pretix.control.views.checkin': 'eventyay.control.views.checkin',
+ 'pretix.control.views.checkin.reverted': 'eventyay.control.views.checkin.reverted',
+
+ # Additional mappings for complete backward compatibility
+ 'pretix.event.category.changed': 'eventyay.event.category.changed',
+ 'pretix.event.question.added': 'eventyay.event.question.added',
+ 'pretix.event.question.changed': 'eventyay.event.question.changed',
+ 'pretix.event.question.deleted': 'eventyay.event.question.deleted',
+ 'pretix.event.question.option.added': 'eventyay.event.question.option.added',
+ 'pretix.event.question.option.changed': 'eventyay.event.question.option.changed',
+ 'pretix.event.question.option.deleted': 'eventyay.event.question.option.deleted',
+ 'pretix.event.checkinlist.added': 'eventyay.event.checkinlist.added',
+ 'pretix.event.checkinlist.changed': 'eventyay.event.checkinlist.changed',
+ 'pretix.event.checkinlists.deleted': 'eventyay.event.checkinlists.deleted', # Note: typo exists in codebase
+ 'pretix.event.order.refund.requested': 'eventyay.event.order.refund.requested',
+ 'pretix.event.order.payment.canceled': 'eventyay.event.order.payment.canceled',
+ 'pretix.event.order.payment.canceled.failed': 'eventyay.event.order.payment.canceled.failed',
+ 'pretix.event.order.payment.failed': 'eventyay.event.order.payment.failed',
+ 'pretix.event.order.refund.canceled': 'eventyay.event.order.refund.canceled',
+ 'pretix.event.order.refund.created': 'eventyay.event.order.refund.created',
+ 'pretix.plugins.ticketoutputpdf.layout.added': 'eventyay.plugins.ticketoutputpdf.layout.added',
+ 'pretix.plugins.ticketoutputpdf.layout.deleted': 'eventyay.plugins.ticketoutputpdf.layout.deleted',
+ 'pretix.plugins.ticketoutputpdf.layout.changed': 'eventyay.plugins.ticketoutputpdf.layout.changed',
+ 'pretix.plugins.badges.layout.added': 'eventyay.plugins.badges.layout.added',
+ 'pretix.plugins.badges.layout.changed': 'eventyay.plugins.badges.layout.changed',
+ 'pretix.plugins.badges.layout.deleted': 'eventyay.plugins.badges.layout.deleted',
+ 'pretix.team.invite.deleted': 'eventyay.team.invite.deleted',
+ 'pretix.team.invite.resent': 'eventyay.team.invite.resent',
+ 'pretix.team.invite.created': 'eventyay.team.invite.created',
+ 'pretix.team.token.deleted': 'eventyay.team.token.deleted',
+ 'pretix.user.settings.2fa.device.deleted': 'eventyay.user.settings.2fa.device.deleted',
+ 'pretix.user.settings.notifications.changed': 'eventyay.user.settings.notifications.changed',
+ 'pretix.control.auth.user.impersonated': 'eventyay.control.auth.user.impersonated',
+ 'pretix.control.auth.user.impersonate_stopped': 'eventyay.control.auth.user.impersonate_stopped',
+ 'pretix.giftcards.acceptance.added': 'eventyay.giftcards.acceptance.added',
+ 'pretix.giftcards.acceptance.removed': 'eventyay.giftcards.acceptance.removed',
+ 'pretix.giftcards.transaction.manual': 'eventyay.giftcards.transaction.manual',
+ 'pretix.gate.changed': 'eventyay.gate.changed',
+ 'pretix.device.changed': 'eventyay.device.changed',
+ 'pretix.property.changed': 'eventyay.property.changed',
+
+ # Additional order and email action mappings for complete coverage
+ 'pretix.event.order.contact.confirmed': 'eventyay.event.order.contact.confirmed',
+ 'pretix.event.order.comment': 'eventyay.event.order.comment',
+ 'pretix.event.order.checkin_attention': 'eventyay.event.order.checkin_attention',
+ 'pretix.event.order.phone.changed': 'eventyay.event.order.phone.changed',
+ 'pretix.event.order.locale.changed': 'eventyay.event.order.locale.changed',
+ 'pretix.event.order.email.attachments.skipped': 'eventyay.event.order.email.attachments.skipped',
+ 'pretix.event.order.email.error': 'eventyay.event.order.email.error',
+ 'pretix.event.order.email.event_canceled': 'eventyay.event.order.email.event_canceled',
+ 'pretix.event.order.email.expire_warning_sent': 'eventyay.event.order.email.expire_warning_sent',
+ 'pretix.event.order.email.custom_sent': 'eventyay.event.order.email.custom_sent',
+ 'pretix.event.order.position.email.custom_sent': 'eventyay.event.order.position.email.custom_sent',
+ 'pretix.event.order.cancellationrequest.deleted': 'eventyay.event.order.cancellationrequest.deleted',
+ 'pretix.event.order.placed.require_approval': 'eventyay.event.order.placed.require_approval',
+ 'pretix.event.order.overpaid': 'eventyay.event.order.overpaid',
+ 'pretix.event.order.refund.created.externally': 'eventyay.event.order.refund.created.externally',
+ 'pretix.subevent.canceled': 'eventyay.subevent.canceled',
+ 'pretix.voucher.sent': 'eventyay.voucher.sent',
+
+ # pretalx.* (old talk system) mappings - map to eventyay equivalents
+ 'pretalx.room.create': 'eventyay.room.create',
+ 'pretalx.room.update': 'eventyay.room.update',
+ 'pretalx.room.delete': 'eventyay.room.delete',
+}
+
+
@receiver(signal=logentry_display, dispatch_uid='eventyaycontrol_logentry_display')
def eventyaycontrol_logentry_display(sender: Event, logentry: LogEntry, **kwargs):
+ # Map legacy pretix.* prefixes to eventyay.* for backward compatibility
+ action_type = PRETIX_LEGACY_ALIASES.get(logentry.action_type, logentry.action_type)
+
plains = {
'eventyay.object.cloned': _('This object has been created by cloning.'),
'eventyay.organizer.changed': _('The organizer has been changed.'),
@@ -471,16 +617,9 @@ def eventyaycontrol_logentry_display(sender: Event, logentry: LogEntry, **kwargs
'eventyay.event.quota.opened': _('The quota has been re-opened.'),
'eventyay.event.category.added': _('The category has been added.'),
'eventyay.event.category.deleted': _('The category has been deleted.'),
- 'eventyay.event.category.changed': _('The category has been changed.'),
- 'eventyay.event.question.added': _('The question has been added.'),
- 'eventyay.event.question.deleted': _('The question has been deleted.'),
- 'eventyay.event.question.changed': _('The question has been changed.'),
'eventyay.event.taxrule.added': _('The tax rule has been added.'),
'eventyay.event.taxrule.deleted': _('The tax rule has been deleted.'),
'eventyay.event.taxrule.changed': _('The tax rule has been changed.'),
- 'eventyay.event.checkinlist.added': _('The check-in list has been added.'),
- 'eventyay.event.checkinlist.deleted': _('The check-in list has been deleted.'),
- 'eventyay.event.checkinlist.changed': _('The check-in list has been changed.'),
'eventyay.event.settings': _('The event settings have been changed.'),
'eventyay.event.tickets.settings': _('The ticket download settings have been changed.'),
'eventyay.event.plugins.enabled': _('A plugin has been enabled.'),
@@ -491,9 +630,6 @@ def eventyaycontrol_logentry_display(sender: Event, logentry: LogEntry, **kwargs
'eventyay.event.testmode.deactivated': _('The test mode has been disabled.'),
'eventyay.event.added': _('The event has been created.'),
'eventyay.event.changed': _('The event details have been changed.'),
- 'eventyay.event.question.option.added': _('An answer option has been added to the question.'),
- 'eventyay.event.question.option.deleted': _('An answer option has been removed from the question.'),
- 'eventyay.event.question.option.changed': _('An answer option has been changed.'),
'eventyay.event.permissions.added': _('A user has been added to the event team.'),
'eventyay.event.permissions.invited': _('A user has been invited to the event team.'),
'eventyay.event.permissions.changed': _("A user's permissions have been changed."),
@@ -508,6 +644,9 @@ def eventyaycontrol_logentry_display(sender: Event, logentry: LogEntry, **kwargs
'eventyay.gate.created': _('The gate has been created.'),
'eventyay.gate.changed': _('The gate has been changed.'),
'eventyay.gate.deleted': _('The gate has been deleted.'),
+ 'eventyay.room.create': _('A new room was added.'),
+ 'eventyay.room.update': _('A room was modified.'),
+ 'eventyay.room.delete': _('A room was deleted.'),
'eventyay.subevent.deleted': pgettext_lazy('subevent', 'The event date has been deleted.'),
'eventyay.subevent.canceled': pgettext_lazy('subevent', 'The event date has been canceled.'),
'eventyay.subevent.changed': pgettext_lazy('subevent', 'The event date has been changed.'),
@@ -524,6 +663,26 @@ def eventyaycontrol_logentry_display(sender: Event, logentry: LogEntry, **kwargs
'eventyay.giftcards.created': _('The gift card has been created.'),
'eventyay.giftcards.modified': _('The gift card has been changed.'),
'eventyay.giftcards.transaction.manual': _('A manual transaction has been performed.'),
+ 'eventyay.property.deleted': _('The property has been deleted.'),
+ 'eventyay.property.changed': _('The property has been changed.'),
+ 'eventyay.plugins.badges.layout.deleted': _('The badge layout has been deleted.'),
+ 'eventyay.plugins.badges.layout.added': _('A new badge layout has been created.'),
+ 'eventyay.plugins.badges.layout.changed': _('The badge layout has been changed.'),
+ 'eventyay.plugins.ticketoutputpdf.layout.added': _('A new ticket layout has been created.'),
+ 'eventyay.plugins.ticketoutputpdf.layout.deleted': _('The ticket layout has been deleted.'),
+ 'eventyay.plugins.ticketoutputpdf.layout.changed': _('The ticket layout has been changed.'),
+ 'eventyay.event.checkinlist.added': _('A new check-in list has been created.'),
+ 'eventyay.event.checkinlist.changed': _('The check-in list has been changed.'),
+ 'eventyay.event.checkinlist.deleted': _('The check-in list has been deleted.'),
+ 'eventyay.event.checkinlists.deleted': _('The check-in list has been deleted.'), # Typo variant
+ 'eventyay.event.question.added': _('A new question has been created.'),
+ 'eventyay.event.question.changed': _('The question has been changed.'),
+ 'eventyay.event.question.deleted': _('The question has been deleted.'),
+ 'eventyay.event.question.option.added': _('A new answer option has been created.'),
+ 'eventyay.event.question.option.changed': _('The answer option has been changed.'),
+ 'eventyay.event.question.option.deleted': _('The answer option has been deleted.'),
+ 'eventyay.control.auth.user.impersonated': _('User impersonation has started.'),
+ 'eventyay.control.auth.user.impersonate_stopped': _('User impersonation has been stopped.'),
}
try:
@@ -531,7 +690,7 @@ def eventyaycontrol_logentry_display(sender: Event, logentry: LogEntry, **kwargs
except (TypeError, json.JSONDecodeError):
data = {}
- if logentry.action_type.startswith('eventyay.event.product.variation'):
+ if action_type.startswith('eventyay.event.product.variation'):
if 'value' not in data:
# Backwards compatibility
var = ProductVariation.objects.filter(id=data['id']).first()
@@ -542,28 +701,28 @@ def eventyaycontrol_logentry_display(sender: Event, logentry: LogEntry, **kwargs
else:
data['value'] = LazyI18nString(data['value'])
- if logentry.action_type in plains:
+ if action_type in plains:
data = defaultdict(lambda: '?', data)
- return plains[logentry.action_type].format_map(data)
+ return plains[action_type].format_map(data)
- if logentry.action_type.startswith('eventyay.event.order.changed'):
- return _display_order_changed(sender, logentry)
+ if action_type.startswith('eventyay.event.order.changed'):
+ return _display_order_changed(sender, logentry, action_type)
- if logentry.action_type.startswith('eventyay.event.payment.provider.'):
+ if action_type.startswith('eventyay.event.payment.provider.'):
return _('The settings of a payment provider have been changed.')
- if logentry.action_type.startswith('eventyay.event.tickets.provider.'):
+ if action_type.startswith('eventyay.event.tickets.provider.'):
return _('The settings of a ticket output provider have been changed.')
- if logentry.action_type == 'eventyay.event.order.consent':
+ if action_type == 'eventyay.event.order.consent':
return _('The user confirmed the following message: "{}"').format(
bleach.clean(logentry.parsed_data.get('msg'), tags=[], strip=True)
)
- if sender and logentry.action_type.startswith('eventyay.event.checkin'):
- return _display_checkin(sender, logentry)
+ if sender and action_type.startswith('eventyay.event.checkin'):
+ return _display_checkin(sender, logentry, action_type)
- if logentry.action_type == 'eventyay.control.views.checkin':
+ if action_type == 'eventyay.control.views.checkin':
# deprecated
dt = dateutil.parser.parse(data.get('datetime'))
tz = pytz.timezone(sender.settings.timezone)
@@ -586,7 +745,7 @@ def eventyaycontrol_logentry_display(sender: Event, logentry: LogEntry, **kwargs
posid=data.get('positionid'), datetime=dt_formatted, list=checkin_list
)
- if logentry.action_type in (
+ if action_type in (
'eventyay.control.views.checkin.reverted',
'eventyay.event.checkin.reverted',
):
@@ -603,33 +762,33 @@ def eventyaycontrol_logentry_display(sender: Event, logentry: LogEntry, **kwargs
list=checkin_list,
)
- if logentry.action_type == 'eventyay.team.member.added':
+ if action_type == 'eventyay.team.member.added':
return _('{user} has been added to the team.').format(user=data.get('email'))
- if logentry.action_type == 'eventyay.team.member.removed':
+ if action_type == 'eventyay.team.member.removed':
return _('{user} has been removed from the team.').format(user=data.get('email'))
- if logentry.action_type == 'eventyay.team.member.joined':
+ if action_type == 'eventyay.team.member.joined':
return _('{user} has joined the team using the invite sent to {email}.').format(
user=data.get('email'), email=data.get('invite_email')
)
- if logentry.action_type == 'eventyay.team.invite.created':
+ if action_type == 'eventyay.team.invite.created':
return _('{user} has been invited to the team.').format(user=data.get('email'))
- if logentry.action_type == 'eventyay.team.invite.resent':
+ if action_type == 'eventyay.team.invite.resent':
return _('Invite for {user} has been resent.').format(user=data.get('email'))
- if logentry.action_type == 'eventyay.team.invite.deleted':
+ if action_type == 'eventyay.team.invite.deleted':
return _('The invite for {user} has been revoked.').format(user=data.get('email'))
- if logentry.action_type == 'eventyay.team.token.created':
+ if action_type == 'eventyay.team.token.created':
return _('The token "{name}" has been created.').format(name=data.get('name'))
- if logentry.action_type == 'eventyay.team.token.deleted':
+ if action_type == 'eventyay.team.token.deleted':
return _('The token "{name}" has been revoked.').format(name=data.get('name'))
- if logentry.action_type == 'eventyay.user.settings.changed':
+ if action_type == 'eventyay.user.settings.changed':
text = str(_('Your account settings have been changed.'))
if 'email' in data:
text = text + ' ' + str(_('Your email address has been changed to {email}.').format(email=data['email']))
@@ -641,8 +800,10 @@ def eventyaycontrol_logentry_display(sender: Event, logentry: LogEntry, **kwargs
text = text + ' ' + str(_('Your account has been disabled.'))
return text
- if logentry.action_type == 'eventyay.control.auth.user.impersonated':
+ if action_type == 'eventyay.control.auth.user.impersonated':
return str(_('You impersonated {}.')).format(data['other_email'])
- if logentry.action_type == 'eventyay.control.auth.user.impersonate_stopped':
+ if action_type == 'eventyay.control.auth.user.impersonate_stopped':
return str(_('You stopped impersonating {}.')).format(data['other_email'])
+
+
diff --git a/app/eventyay/control/templates/pretixcontrol/base.html b/app/eventyay/control/templates/pretixcontrol/base.html
index fc9e9cda69..9f69ab6f5d 100644
--- a/app/eventyay/control/templates/pretixcontrol/base.html
+++ b/app/eventyay/control/templates/pretixcontrol/base.html
@@ -272,7 +272,7 @@
- {% trans "TEST MODE" %}
+
{% endif %}
@@ -289,7 +289,7 @@
{% endif %}
{% endif %}
- {{ nav.label }}
+
{% if nav.children %}
@@ -301,7 +301,7 @@
- {{ item.label }}
+
{% endfor %}
diff --git a/app/eventyay/control/templates/pretixcontrol/event/settings.html b/app/eventyay/control/templates/pretixcontrol/event/settings.html
index 097e334462..fa0f3052dd 100644
--- a/app/eventyay/control/templates/pretixcontrol/event/settings.html
+++ b/app/eventyay/control/templates/pretixcontrol/event/settings.html
@@ -62,6 +62,7 @@ {% trans "Customer data (once per order)" %}
{% bootstrap_field sform.order_email_asked_twice layout="control" %}
+ {% bootstrap_field sform.include_wikimedia_username layout="control" %}
{% bootstrap_field sform.order_phone_asked_required layout="control" %}