From 9a203e116ebed5dd46d0488f0bfdef0f9cbd74ba Mon Sep 17 00:00:00 2001 From: Felix Rindt Date: Tue, 1 Sep 2020 23:58:35 +0200 Subject: [PATCH 1/3] refactor signup method --- contrib/signup.py | 9 +- event_management/signup.py | 256 ++++++++++-------- .../fragments/shift_box_big.html | 65 ++--- event_management/urls.py | 3 - event_management/views.py | 18 +- 5 files changed, 194 insertions(+), 157 deletions(-) diff --git a/contrib/signup.py b/contrib/signup.py index 91aee15f3..057c15ea0 100644 --- a/contrib/signup.py +++ b/contrib/signup.py @@ -5,14 +5,14 @@ from event_management.models import AbstractParticipation from event_management.signup import ( - AbstractSignupMethod, + BaseSignupMethod, register_signup_methods, ParticipationError, ) from user_management.models import Qualification -class SimpleQualificationsRequiredSignupMethod(AbstractSignupMethod): +class SimpleQualificationsRequiredSignupMethod(BaseSignupMethod): def __init__(self, shift): super().__init__(shift) if shift is not None: @@ -44,6 +44,7 @@ def get_configuration_fields(self): label=_("Required Qualifications"), queryset=Qualification.objects.all(), widget=Select2MultipleWidget, + required=False, ), "default": [], "publish_with_label": _("Required Qualification"), @@ -86,8 +87,8 @@ def get_configuration_fields(self): def render_shift_state(self): return get_template("jepcontrib/signup_instant_state.html").render({"shift": self.shift}) - def perform_signup(self, participator): - participation = super().perform_signup(participator) + def perform_signup(self, participator, **kwargs): + participation = super().perform_signup(participator, **kwargs) participation.state = AbstractParticipation.CONFIRMED participation.save() return participation diff --git a/event_management/signup.py b/event_management/signup.py index 8b6a49165..c7b08cacd 100644 --- a/event_management/signup.py +++ b/event_management/signup.py @@ -14,13 +14,15 @@ from django import forms from django.template import Template, Context from django.utils import timezone, dateparse, formats +from django.utils.functional import cached_property from django.utils.translation import gettext_lazy as _ from django.shortcuts import redirect from django.db.models import QuerySet +from django.views import View from contrib.json import CustomJSONDecoder, CustomJSONEncoder -from event_management.models import LocalParticipation, AbstractParticipation +from event_management.models import LocalParticipation, AbstractParticipation, Shift from user_management.models import Qualification register_signup_methods = django.dispatch.Signal(providing_args=[]) @@ -101,7 +103,64 @@ def get_configuration(self): return json.dumps(self.cleaned_data, cls=CustomJSONEncoder) -class AbstractSignupMethod: +def check_event_is_active(method, participator): + if not method.shift.event.active: + return ParticipationError(_("The event is not active.")) + + +def check_participation_state_for_signup(method, participator): + participation = participator.participation_for(method.shift) + if participation is not None: + if participation.state == AbstractParticipation.REQUESTED: + return ParticipationError( + _("You have already requested your participation for {shift}").format( + shift=method.shift + ) + ) + elif participation.state == AbstractParticipation.CONFIRMED: + return ParticipationError( + _("You are bindingly signed up for {shift}.").format(shift=method.shift) + ) + elif participation.state == AbstractParticipation.RESPONSIBLE_REJECTED: + return ParticipationError( + _("You are rejected from {shift}.").format(shift=method.shift) + ) + + +def check_participation_state_for_decline(method, participator): + participation = participator.participation_for(method.shift) + if participation is not None: + if participation.state == AbstractParticipation.CONFIRMED: + return ParticipationError( + _("You are bindingly signed up for {shift}.").format(shift=method.shift) + ) + elif participation.state == AbstractParticipation.RESPONSIBLE_REJECTED: + return ParticipationError( + _("You are rejected from {shift}.").format(shift=method.shift) + ) + elif participation.state == AbstractParticipation.USER_DECLINED: + return ParticipationError( + _("You have already declined participating in {shift}.").format(shift=method.shift) + ) + + +def check_inside_signup_timeframe(method, participator): + last_time = method.shift.end_time + if method.configuration.signup_until is not None: + last_time = min(last_time, method.configuration.signup_until) + if timezone.now() > last_time: + return ParticipationError(_("The signup period is over.")) + + +def check_participator_age(method, participator): + minimum_age = method.configuration.minimum_age + if minimum_age is not None and participator.age < minimum_age: + return ParticipationError( + _("You are too young. The minimum age is {age}.").format(age=minimum_age) + ) + + +class BaseSignupMethod: slug = "abstract" verbose_name = "abstract" description = """""" @@ -118,118 +177,65 @@ def __init__(self, shift): ).items(): setattr(self.configuration, key, value) - def can_sign_up(self, participator): - return not self.get_signup_errors(participator) + @property + def signup_view_class(self): + return BaseSignupView - def get_signup_errors(self, participator) -> List[ParticipationError]: + @cached_property + def signup_view(self): + return self.signup_view_class.as_view(method=self, shift=self.shift) + + @property + def signup_checkers(self): return [ - error - for error in ( - self.check_event_is_active(), - self.check_participation_state_for_signup(participator), - self.check_inside_signup_timeframe(), - self.check_participator_age(participator), - ) - if error is not None + check_event_is_active, + check_participation_state_for_signup, + check_inside_signup_timeframe, + check_participator_age, ] - def check_event_is_active(self): - if not self.shift.event.active: - return ParticipationError(_("The event is not active.")) + @property + def decline_checkers(self): + return [ + check_event_is_active, + check_participation_state_for_decline, + check_inside_signup_timeframe, + check_participator_age, + ] - def check_participation_state_for_signup(self, participator): - participation = participator.participation_for(self.shift) - if participation is not None: - if participation.state == AbstractParticipation.REQUESTED: - return ParticipationError( - _("You have already requested your participation for {shift}").format( - shift=self.shift - ) - ) - elif participation.state == AbstractParticipation.CONFIRMED: - return ParticipationError( - _("You are bindingly signed up for {shift}.").format(shift=self.shift) - ) - elif participation.state == AbstractParticipation.RESPONSIBLE_REJECTED: - return ParticipationError( - _("You are rejected from {shift}.").format(shift=self.shift) - ) + def get_signup_errors(self, participator) -> List[ParticipationError]: + return [ + error + for checker in self.signup_checkers + if (error := checker(self, participator)) is not None + ] - def check_participation_state_for_decline(self, participator): - participation = participator.participation_for(self.shift) - if participation is not None: - if participation.state == AbstractParticipation.CONFIRMED: - return ParticipationError( - _("You are bindingly signed up for {shift}.").format(shift=self.shift) - ) - elif participation.state == AbstractParticipation.RESPONSIBLE_REJECTED: - return ParticipationError( - _("You are rejected from {shift}.").format(shift=self.shift) - ) - elif participation.state == AbstractParticipation.USER_DECLINED: - return ParticipationError( - _("You have already declined participating in {shift}.").format( - shift=self.shift - ) - ) + def get_decline_errors(self, participator): + return [ + error + for checker in self.decline_checkers + if (error := checker(self, participator)) is not None + ] - def check_inside_signup_timeframe(self): - if ( - self.configuration.signup_until is not None - and timezone.now() > self.configuration.signup_until - ): - return ParticipationError(_("The signup period is over.")) + def can_decline(self, participator): + return not self.get_decline_errors(participator) - def check_participator_age(self, participator): - minimum_age = self.configuration.minimum_age - if minimum_age is not None and participator.age < minimum_age: - return ParticipationError( - _("You are too young. The minimum age is {age}.").format(age=minimum_age) - ) + def can_sign_up(self, participator): + return not self.get_signup_errors(participator) def get_participation_for(self, participator): return participator.participation_for(self.shift) or participator.create_participation( self.shift ) - def perform_signup(self, participator: AbstractParticipator): - """Create and configure a participation object for the given participator.""" + def perform_signup(self, participator: AbstractParticipator, **kwargs): + """Create and configure a participation object for the given participator. `kwargs` may contain further instructions from a e.g. a form.""" if errors := self.get_signup_errors(participator): raise ParticipationError(errors) return self.get_participation_for(participator) - def signup_view(self, request, *args, **kwargs): - try: - with transaction.atomic(): - self.perform_signup(request.user.as_participator()) - messages.success( - request, - _("You have successfully signed up for shift {shift}.").format( - shift=self.shift - ), - ) - except ParticipationError as errors: - for error in errors: - messages.error(request, _("Signup failed: ") + str(error)) - return redirect(self.shift.event.get_absolute_url()) - - def get_decline_errors(self, participator): - return [ - error - for error in ( - self.check_event_is_active(), - self.check_participation_state_for_decline(participator), - self.check_inside_signup_timeframe(), - self.check_participator_age(participator), - ) - if error is not None - ] - - def can_decline(self, participator): - return not self.get_decline_errors(participator) - - def perform_decline(self, participator): - """Create and configure a declining participation object for the given participator.""" + def perform_decline(self, participator, **kwargs): + """Create and configure a declining participation object for the given participator. `kwargs` may contain further instructions from a e.g. a form.""" if errors := self.get_decline_errors(participator): raise ParticipationError(errors) participation = self.get_participation_for(participator) @@ -237,18 +243,6 @@ def perform_decline(self, participator): participation.save() return participation - def decline_view(self, request): - try: - with transaction.atomic(): - self.perform_decline(request.user.as_participator()) - messages.info( - request, _("You have successfully declined {shift}.").format(shift=self.shift) - ) - except ParticipationError as errors: - for error in errors: - messages.error(request, _("Declining failed: ") + str(error)) - return redirect(self.shift.event.get_absolute_url()) - def get_configuration_fields(self): return { "minimum_age": { @@ -302,3 +296,49 @@ def render_configuration_form(self, form=None, *args, **kwargs): # HTML-Darstellung der Helfer (defaults to an unorderd list of Helfers) # Helferlisten-PDF-content (defautls to an unorderd list of Helfers) + + +class BaseSignupView(View): + shift: Shift = ... + method: BaseSignupMethod = ... + + def dispatch(self, request, *args, **kwargs): + if (choice := request.POST.get("signup_choice")) is not None: + if choice == "sign_up": + return self.signup_pressed(request, *args, **kwargs) + elif choice == "decline": + return self.decline_pressed(request, *args, **kwargs) + else: + raise ValueError( + _("'{choice}' is not a valid signup action.").format(choice=choice) + ) + return super().dispatch(request, *args, **kwargs) + + def signup_pressed(self, request, *args, **kwargs): + try: + with transaction.atomic(): + self.method.perform_signup(request.user.as_participator()) + messages.success( + request, + _("You have successfully signed up for shift {shift}.").format( + shift=self.shift + ), + ) + except ParticipationError as errors: + for error in errors: + messages.error(request, _("Signing up failed: ") + str(error)) + finally: + return redirect(self.shift.event.get_absolute_url()) + + def decline_pressed(self, request, *args, **kwargs): + try: + with transaction.atomic(): + self.method.perform_decline(request.user.as_participator()) + messages.info( + request, _("You have successfully declined {shift}.").format(shift=self.shift) + ) + except ParticipationError as errors: + for error in errors: + messages.error(request, _("Declining failed: ") + str(error)) + finally: + return redirect(self.shift.event.get_absolute_url()) diff --git a/event_management/templates/event_management/fragments/shift_box_big.html b/event_management/templates/event_management/fragments/shift_box_big.html index 2112350ab..13a358820 100644 --- a/event_management/templates/event_management/fragments/shift_box_big.html +++ b/event_management/templates/event_management/fragments/shift_box_big.html @@ -28,43 +28,46 @@
{{ value }}
{% endfor %} {% if event.active and not without_controls %} -
{{ shift|shift_status:request.user }}
-
- {% with can_sign_up=shift|can_sign_up:request.user %} - {% if can_sign_up %} - {% trans "Register" %} - {% else %} +
+ {% csrf_token %} +
{{ shift|shift_status:request.user }}
+
+ {% with can_sign_up=shift|can_sign_up:request.user %} - + {% for error in shift|signup_errors:request.user %} +

{{ error.message }}

+ {% endfor %}" + {% endif %}> +
- {% endif %} - {% endwith %} - {% with can_user_decline=shift|can_user_decline:request.user %} - {% if can_user_decline %} - {% trans "Decline" %} - {% else %} + {% endwith %} + {% with can_user_decline=shift|can_user_decline:request.user %} - + {% for error in shift|decline_errors:request.user %} +

{{ error.message }}

+ {% endfor %}" + {% endif %}> +
- {% endif %} - {% endwith %} -
+ {% endwith %} +
+ {% endif %} diff --git a/event_management/urls.py b/event_management/urls.py index 759c3d373..0fc767a02 100644 --- a/event_management/urls.py +++ b/event_management/urls.py @@ -17,9 +17,6 @@ path("shifts//register/", views.ShiftSignupView.as_view(), name="shift_register",), path("shifts//edit/", views.ShiftUpdateView.as_view(), name="shift_edit",), path("shifts//delete/", views.ShiftDeleteView.as_view(), name="shift_delete",), - path( - "shifts//user_decline/", views.ShiftDeclineView.as_view(), name="shift_user_decline" - ), path( "signup_methods//configuration_form/", ShiftConfigurationFormView.as_view(), diff --git a/event_management/views.py b/event_management/views.py index 87f79a013..42a85974a 100644 --- a/event_management/views.py +++ b/event_management/views.py @@ -280,19 +280,15 @@ def get_success_url(self): return self.object.event.get_absolute_url() -class ShiftSignupView(CustomPermissionRequiredMixin, SingleObjectMixin, View): - permission_required = "event_management.view_event" +class SignupMethodViewMixin(SingleObjectMixin): model = Shift - def get_permission_object(self): - return self.get_object().event + def dispatch(self, request, *args, **kwargs): + return self.get_object().signup_method.signup_view(request, *args, **kwargs) - def get(self, request, *args, **kwargs): - shift = get_object_or_404(Shift, id=self.kwargs["pk"]) - return shift.signup_method.signup_view(request, *args, **kwargs) +class ShiftSignupView(CustomPermissionRequiredMixin, SignupMethodViewMixin, View): + permission_required = "event_management.view_event" -class ShiftDeclineView(View): - def get(self, *args, **kwargs): - shift = get_object_or_404(Shift, id=self.kwargs["pk"]) - return shift.signup_method.decline_view(self.request) + def get_permission_object(self): + return self.get_object().event From 4f92df5a56e3ce03a56e855df46c0ae4677acbd4 Mon Sep 17 00:00:00 2001 From: Felix Rindt Date: Wed, 2 Sep 2020 22:42:45 +0200 Subject: [PATCH 2/3] can self-decline option --- contrib/signup.py | 36 ++++++++----------- event_management/models.py | 3 +- event_management/signup.py | 32 +++++++++++------ .../fragments/shift_box_big.html | 4 +-- event_management/urls.py | 2 +- 5 files changed, 41 insertions(+), 36 deletions(-) diff --git a/contrib/signup.py b/contrib/signup.py index 057c15ea0..394a83522 100644 --- a/contrib/signup.py +++ b/contrib/signup.py @@ -20,20 +20,13 @@ def __init__(self, shift): pk__in=self.configuration.required_qualification_ids ) - def get_signup_errors(self, participator): - errors = super().get_signup_errors(participator) - if (error := self.check_qualification(participator)) is not None: - errors.append(error) - return errors + @property + def signup_checkers(self): + return super().signup_checkers + [self.check_qualification] - def get_decline_errors(self, participator): - errors = super().get_decline_errors(participator) - if (error := self.check_qualification(participator)) is not None: - errors.append(error) - return errors - - def check_qualification(self, participator): - if not participator.has_qualifications(self.configuration.required_qualifications): + @staticmethod + def check_qualification(method, participator): + if not participator.has_qualifications(method.configuration.required_qualifications): return ParticipationError(_("You are not qualified.")) def get_configuration_fields(self): @@ -60,18 +53,17 @@ class InstantConfirmationSignupMethod(SimpleQualificationsRequiredSignupMethod): verbose_name = _("Instant Confirmation") description = _("""This method instantly confirms a signup.""") - def get_signup_errors(self, participator): - errors = super().get_signup_errors(participator) - if (error := self.check_maximum_number_of_participants()) is not None: - errors.append(error) - return errors + @property + def signup_checkers(self): + return super().signup_checkers + [self.check_maximum_number_of_participants] - def check_maximum_number_of_participants(self): - if self.configuration.maximum_number_of_participants is not None: + @staticmethod + def check_maximum_number_of_participants(method, participator): + if method.configuration.maximum_number_of_participants is not None: current_count = AbstractParticipation.objects.filter( - shift=self.shift, state=AbstractParticipation.CONFIRMED + shift=method.shift, state=AbstractParticipation.CONFIRMED ).count() - if current_count >= self.configuration.maximum_number_of_participants: + if current_count >= method.configuration.maximum_number_of_participants: return ParticipationError(_("The maximum number of participants is reached.")) def get_configuration_fields(self): diff --git a/event_management/models.py b/event_management/models.py index f9091490a..041687369 100644 --- a/event_management/models.py +++ b/event_management/models.py @@ -14,6 +14,7 @@ TextField, Manager, ) +from django.utils.functional import cached_property from polymorphic.models import PolymorphicModel from django.utils import dateformat, formats from jsonfallback.fields import FallbackJSONField @@ -92,7 +93,7 @@ class Meta: verbose_name = _("shift") verbose_name_plural = _("shifts") - @property + @cached_property def signup_method(self): from event_management.signup import signup_method_from_slug diff --git a/event_management/signup.py b/event_management/signup.py index c7b08cacd..bd3427a4c 100644 --- a/event_management/signup.py +++ b/event_management/signup.py @@ -13,6 +13,7 @@ from django.dispatch import receiver from django import forms from django.template import Template, Context +from django.template.defaultfilters import yesno from django.utils import timezone, dateparse, formats from django.utils.functional import cached_property from django.utils.translation import gettext_lazy as _ @@ -40,7 +41,7 @@ def signup_method_from_slug(slug, shift=None): raise ValueError(_("Signup Method '{slug}' was not found.").format(slug=slug)) -@dataclass +@dataclass(frozen=True) class AbstractParticipator: first_name: str last_name: str @@ -80,7 +81,7 @@ def has_qualifications(self, qualifications): return set(qualifications) <= self.collect_all_qualifications() -@dataclass +@dataclass(frozen=True) class LocalUserParticipator(AbstractParticipator): user: get_user_model() @@ -119,7 +120,7 @@ def check_participation_state_for_signup(method, participator): ) elif participation.state == AbstractParticipation.CONFIRMED: return ParticipationError( - _("You are bindingly signed up for {shift}.").format(shift=method.shift) + _("You are already signed up for {shift}.").format(shift=method.shift) ) elif participation.state == AbstractParticipation.RESPONSIBLE_REJECTED: return ParticipationError( @@ -130,7 +131,10 @@ def check_participation_state_for_signup(method, participator): def check_participation_state_for_decline(method, participator): participation = participator.participation_for(method.shift) if participation is not None: - if participation.state == AbstractParticipation.CONFIRMED: + if ( + participation.state == AbstractParticipation.CONFIRMED + and not method.configuration.user_can_decline_confirmed + ): return ParticipationError( _("You are bindingly signed up for {shift}.").format(shift=method.shift) ) @@ -200,9 +204,9 @@ def decline_checkers(self): check_event_is_active, check_participation_state_for_decline, check_inside_signup_timeframe, - check_participator_age, ] + @functools.lru_cache() def get_signup_errors(self, participator) -> List[ParticipationError]: return [ error @@ -210,6 +214,7 @@ def get_signup_errors(self, participator) -> List[ParticipationError]: if (error := checker(self, participator)) is not None ] + @functools.lru_cache() def get_decline_errors(self, participator): return [ error @@ -256,6 +261,18 @@ def get_configuration_fields(self): "publish_with_label": _("Signup until"), "format": functools.partial(formats.date_format, format="SHORT_DATETIME_FORMAT"), }, + "user_can_decline_confirmed": { + "formfield": forms.BooleanField( + label=_("Self-decline"), + required=False, + help_text=_( + "If enabled, confirmed users can decline by themselves if the signup timeframe hasn't ended." + ), + ), + "default": False, + "publish_with_label": _("Can decline after confirmation"), + "format": yesno, + }, } def get_signup_info(self): @@ -292,11 +309,6 @@ def render_configuration_form(self, form=None, *args, **kwargs): ).render(Context({"form": form})) return template - # menschenlesbare Füllstandsangabe (z.B. 3/8, 3/, 0/8 (4 interessiert)) vlt irgendwie mit weiteren color-coded Status wie [“Egal”, Helfers needed", “genug Interesse”, “voll besetzt”] - - # HTML-Darstellung der Helfer (defaults to an unorderd list of Helfers) - # Helferlisten-PDF-content (defautls to an unorderd list of Helfers) - class BaseSignupView(View): shift: Shift = ... diff --git a/event_management/templates/event_management/fragments/shift_box_big.html b/event_management/templates/event_management/fragments/shift_box_big.html index 13a358820..80a7df6e2 100644 --- a/event_management/templates/event_management/fragments/shift_box_big.html +++ b/event_management/templates/event_management/fragments/shift_box_big.html @@ -28,7 +28,7 @@
{{ value }}
{% endfor %} {% if event.active and not without_controls %} -
+ {% csrf_token %}
{{ shift|shift_status:request.user }}
@@ -45,7 +45,7 @@
{% if not can_sign_up %} style="pointer-events: none;" disabled {% endif %}> - {% trans "Register" %} + {{ shift.signup_method.registration_button_text }} {% endwith %} diff --git a/event_management/urls.py b/event_management/urls.py index 0fc767a02..50972f9a5 100644 --- a/event_management/urls.py +++ b/event_management/urls.py @@ -14,7 +14,7 @@ path("events//createshift/", views.ShiftCreateView.as_view(), name="event_createshift"), path("events//activate/", views.EventActivateView.as_view(), name="event_activate"), path("events/create/", views.EventCreateView.as_view(), name="event_create"), - path("shifts//register/", views.ShiftSignupView.as_view(), name="shift_register",), + path("shifts//signup-action/", views.ShiftSignupView.as_view(), name="shift_action",), path("shifts//edit/", views.ShiftUpdateView.as_view(), name="shift_edit",), path("shifts//delete/", views.ShiftDeleteView.as_view(), name="shift_delete",), path( From 1977795b66f9be9f99f33032f74801b45f8a726d Mon Sep 17 00:00:00 2001 From: Felix Rindt Date: Wed, 2 Sep 2020 22:53:39 +0200 Subject: [PATCH 3/3] translation --- event_management/signup.py | 10 +- locale/de/LC_MESSAGES/django.po | 361 ++++++++++++++++++++------------ 2 files changed, 231 insertions(+), 140 deletions(-) diff --git a/event_management/signup.py b/event_management/signup.py index bd3427a4c..542ec8f69 100644 --- a/event_management/signup.py +++ b/event_management/signup.py @@ -263,11 +263,9 @@ def get_configuration_fields(self): }, "user_can_decline_confirmed": { "formfield": forms.BooleanField( - label=_("Self-decline"), + label=_("Confirmed users can decline by themselves"), required=False, - help_text=_( - "If enabled, confirmed users can decline by themselves if the signup timeframe hasn't ended." - ), + help_text=_("only if the signup timeframe has not ended"), ), "default": False, "publish_with_label": _("Can decline after confirmation"), @@ -332,9 +330,7 @@ def signup_pressed(self, request, *args, **kwargs): self.method.perform_signup(request.user.as_participator()) messages.success( request, - _("You have successfully signed up for shift {shift}.").format( - shift=self.shift - ), + _("You have successfully signed up for {shift}.").format(shift=self.shift), ) except ParticipationError as errors: for error in errors: diff --git a/locale/de/LC_MESSAGES/django.po b/locale/de/LC_MESSAGES/django.po index c2e939505..c4b0b6624 100644 --- a/locale/de/LC_MESSAGES/django.po +++ b/locale/de/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-08-30 19:43+0200\n" +"POT-Creation-Date: 2020-09-02 23:14+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -18,23 +18,55 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: event_management/forms.py:29 +#: contrib/signup.py:30 +msgid "You are not qualified." +msgstr "Sie sind nicht qualifiziert." + +#: contrib/signup.py:37 +msgid "Required Qualifications" +msgstr "Erforderliche Qualifikationen" + +#: contrib/signup.py:43 +msgid "Required Qualification" +msgstr "Erforderliche Qualifikation" + +#: contrib/signup.py:53 +msgid "Instant Confirmation" +msgstr "Direkte Bestätigung" + +#: contrib/signup.py:54 +msgid "This method instantly confirms a signup." +msgstr "Dieses Anmeldeverfahren bestätigt alle Anmeldungen direkt." + +#: contrib/signup.py:67 +msgid "The maximum number of participants is reached." +msgstr "Die Maximalzahl an Teilnehmenden ist erreicht." + +#: contrib/signup.py:75 +msgid "Maximum number of participants" +msgstr "Maximalanzahl Teilnehmende" + +#: contrib/templates/jepcontrib/signup_instant_state.html:4 +msgid "Signed up" +msgstr "Angemeldet" + +#: event_management/forms.py:30 msgid "Visible for" msgstr "Sichtbar für" -#: event_management/forms.py:30 +#: event_management/forms.py:31 msgid "Select groups which the event shall be visible for." msgstr "Wählen Sie Gruppen, für die die Veranstaltung sichtbar sein soll." -#: event_management/forms.py:36 +#: event_management/forms.py:37 msgid "Responsible persons" msgstr "Verantwortliche Personen" -#: event_management/forms.py:42 +#: event_management/forms.py:43 msgid "Responsible groups" msgstr "Verantwortliche Gruppen" -#: event_management/forms.py:123 +#: event_management/forms.py:118 msgid "Meeting time must not be after start time!" msgstr "Treffpunkt darf nicht nach dem Beginn liegen!" @@ -78,152 +110,184 @@ msgstr "" "Die Teilnahme von {user} an der Schicht {shift} wurde geändert. Der Status " "ist jetzt {status}." -#: event_management/models.py:26 event_management/models.py:42 -#: user_management/models.py:110 user_management/models.py:118 +#: event_management/models.py:33 event_management/models.py:49 +#: user_management/models.py:125 user_management/models.py:134 msgid "title" msgstr "Titel" -#: event_management/models.py:27 +#: event_management/models.py:34 msgid "can grant qualification" msgstr "Kann Qualifikation gewähren" -#: event_management/models.py:30 event_management/models.py:45 +#: event_management/models.py:37 event_management/models.py:52 msgid "event type" msgstr "Veranstaltungstyp" -#: event_management/models.py:31 +#: event_management/models.py:38 msgid "event types" msgstr "Veranstaltungstypen" -#: event_management/models.py:43 +#: event_management/models.py:50 msgid "description" msgstr "Beschreibung" -#: event_management/models.py:44 +#: event_management/models.py:51 msgid "location" msgstr "Ort" -#: event_management/models.py:53 +#: event_management/models.py:60 msgid "event" msgstr "Veranstaltung" -#: event_management/models.py:54 +#: event_management/models.py:61 msgid "events" msgstr "Veranstaltungen" -#: event_management/models.py:77 event_management/models.py:87 +#: event_management/models.py:84 event_management/models.py:94 msgid "shifts" msgstr "Schichten" -#: event_management/models.py:79 +#: event_management/models.py:86 msgid "meeting time" msgstr "Treffpunkt" -#: event_management/models.py:80 +#: event_management/models.py:87 msgid "start time" msgstr "Beginn" -#: event_management/models.py:81 +#: event_management/models.py:88 msgid "end time" msgstr "Ende" -#: event_management/models.py:82 +#: event_management/models.py:89 msgid "signup method" msgstr "Anmeldeverfahren" -#: event_management/models.py:86 event_management/models.py:111 +#: event_management/models.py:93 event_management/models.py:138 msgid "shift" msgstr "Schicht" -#: event_management/models.py:105 +#: event_management/models.py:132 msgid "requested" msgstr "angemeldet" -#: event_management/models.py:106 +#: event_management/models.py:133 msgid "confirmed" msgstr "bestätigt" -#: event_management/models.py:107 +#: event_management/models.py:134 msgid "declined by user" msgstr "abgesagt durch helfende Person" -#: event_management/models.py:108 +#: event_management/models.py:135 msgid "rejected by responsible" msgstr "abgesagt durch verantwortliche Person" -#: event_management/models.py:112 +#: event_management/models.py:139 msgid "state" msgstr "Status" -#: event_management/signup.py:25 +#: event_management/signup.py:41 #, python-brace-format msgid "Signup Method '{slug}' was not found." msgstr "Anmeldeverfahren '{slug}' wurde nicht gefunden." -#: event_management/signup.py:82 -msgid "Sign up" -msgstr "Anmelden" +#: event_management/signup.py:109 +msgid "The event is not active." +msgstr "Die Veranstaltung {title} ist nicht aktiviert." -#: event_management/signup.py:102 -msgid "The event is not active, you cannot sign up for it." -msgstr "" -"Die Veranstaltung ist nicht aktiv, Sie können sich nicht dafür anmelden." +#: event_management/signup.py:117 +#, python-brace-format +msgid "You have already requested your participation for {shift}" +msgstr "Sie haben sich bereits eine Teilnahme für {shift} angefragt." -#: event_management/signup.py:109 +#: event_management/signup.py:123 #, python-brace-format -msgid "You have already requested your participation for shift {shift}" -msgstr "Sie haben sich bereits für die Schicht {shift} angemeldet." +msgid "You are already signed up for {shift}." +msgstr "Sie sind bereits für {shift} eingeteilt." -#: event_management/signup.py:115 +#: event_management/signup.py:127 event_management/signup.py:143 #, python-brace-format -msgid "You are already signed up for shift {shift}" -msgstr "Sie sind bereits für die Schicht {shift} eingeteilt." +msgid "You are rejected from {shift}." +msgstr "Sie wurden für {shift} abgelehnt." -#: event_management/signup.py:119 event_management/signup.py:153 +#: event_management/signup.py:139 #, python-brace-format -msgid "You are rejected from shift {shift}." -msgstr "Sie wurden für die Schicht {shift} abgelehnt." +msgid "You are bindingly signed up for {shift}." +msgstr "Sie sind verbindlich für {shift} eingeteilt." -#: event_management/signup.py:127 +#: event_management/signup.py:147 +#, python-brace-format +msgid "You have already declined participating in {shift}." +msgstr "Sie haben bereits für {shift} abgesagt." + +#: event_management/signup.py:156 msgid "The signup period is over." msgstr "Der Anmeldezeitraum ist vorüber." -#: event_management/signup.py:135 +#: event_management/signup.py:163 #, python-brace-format msgid "You are too young. The minimum age is {age}." msgstr "Sie sind zu jung. Das Mindestalter beträgt {age} Jahre." -#: event_management/signup.py:149 -#, python-brace-format -msgid "You are bindingly signed up for shift {shift}." -msgstr "Sie sind verbindlich für die Schicht {shift} eingeteilt." +#: event_management/signup.py:171 +msgid "Sign up" +msgstr "Anmelden" -#: event_management/signup.py:157 -#, python-brace-format -msgid "You have already declined participating in shift {shift}." -msgstr "Sie haben bereits für die Schicht {shift} abgesagt." +#: event_management/signup.py:256 +msgid "Minimum age" +msgstr "Mindestalter" + +#: event_management/signup.py:261 +msgid "Signup until" +msgstr "Anmelden bis" -#: event_management/signup.py:172 +#: event_management/signup.py:266 +#| msgid "" +#| "If enabled, confirmed users can decline by themselves if the signup " +#| "timeframe hasn't ended." +msgid "Confirmed users can decline by themselves" +msgstr "" +"Bestätigte Nutzer können selbstständig absagen." + +#: event_management/signup.py:269 +#| msgid "" +#| "If enabled, confirmed users can decline by themselves if the signup " +#| "timeframe hasn't ended." +msgid "only if the signup timeframe has not ended" +msgstr "" +"nur wenn der Anmeldezeitraum noch nicht vorrüber ist" + +#: event_management/signup.py:273 +msgid "Can decline after confirmation" +msgstr "Kann nach Bestätigung absagen" + +#: event_management/signup.py:325 #, python-brace-format -msgid "You have successfully signed up for shift {shift}." -msgstr "Sie haben sich erfolgreich für die Schicht {shift} angemeldet." +msgid "'{choice}' is not a valid signup action." +msgstr "'{choice}' ist keine gültige Anmeldeaktion." -#: event_management/signup.py:192 +#: event_management/signup.py:335 #, python-brace-format -msgid "You have declined a participation for shift {shift}." -msgstr "Sie haben für die Schicht {shift} abgesagt." +msgid "You have successfully signed up for {shift}." +msgstr "Sie haben sich erfolgreich für {shift} angemeldet." -#: event_management/signup.py:221 -msgid "Instant Confirmation" -msgstr "Direkte Bestätigung" +#: event_management/signup.py:339 +msgid "Signing up failed: " +msgstr "Anmeldung fehlgeschlagen: " -#: event_management/signup.py:222 -msgid "This method instantly confirms a signup." -msgstr "Dieses Anmeldeverfahren bestätigt alle Anmeldungen direkt." +#: event_management/signup.py:348 +#, python-brace-format +msgid "You have successfully declined {shift}." +msgstr "Sie haben erfolgreich für {shift} abgesagt." + +#: event_management/signup.py:352 +msgid "Declining failed: " +msgstr "Absagen fehlgeschlagen: " #: event_management/templates/event_management/event_confirm_delete.html:7 #: event_management/templates/event_management/event_confirm_delete.html:12 -#: event_management/templates/event_management/event_detail.html:36 +#: event_management/templates/event_management/event_detail.html:38 msgid "Delete event" msgstr "Veranstaltung löschen" @@ -233,14 +297,14 @@ msgid "Are you sure you want to delete the event \"%(object)s\"?" msgstr "Möchten Sie die Veranstaltung \"%(object)s\" wirklich löschen?" #: event_management/templates/event_management/event_confirm_delete.html:17 -#: event_management/templates/event_management/event_form.html:40 +#: event_management/templates/event_management/event_form.html:36 #: event_management/templates/event_management/shift_confirm_delete.html:17 #: user_management/templates/user_management/group_confirm_delete.html:17 msgid "Back" msgstr "Zurück" #: event_management/templates/event_management/event_confirm_delete.html:18 -#: event_management/templates/event_management/fragments/shift_box_big.html:13 +#: event_management/templates/event_management/fragments/shift_box_big.html:15 #: event_management/templates/event_management/fragments/shift_box_small.html:12 #: event_management/templates/event_management/shift_confirm_delete.html:18 #: event_management/templates/event_management/shift_form.html:36 @@ -258,9 +322,9 @@ msgstr "" "Bearbeiten der Veranstaltung fertig sind, können Sie sie speichern." #: event_management/templates/event_management/event_detail.html:14 -#: event_management/templates/event_management/event_form.html:41 +#: event_management/templates/event_management/event_form.html:37 #: event_management/templates/event_management/shift_form.html:39 -#: user_management/templates/user_management/group_form.html:28 +#: user_management/templates/user_management/group_form.html:24 msgid "Save" msgstr "Speichern" @@ -270,41 +334,42 @@ msgstr "" "Diese Veranstaltung wurde noch nicht gespeichert! Bitte fügen Sie eine " "Schicht hinzu, um sie zu speichern." -#: event_management/templates/event_management/event_detail.html:31 +#: event_management/templates/event_management/event_detail.html:33 #: event_management/templates/event_management/event_form.html:8 -#: event_management/templates/event_management/event_form.html:29 +#: event_management/templates/event_management/event_form.html:25 msgid "Edit event" msgstr "Veranstaltung bearbeiten" -#: event_management/templates/event_management/event_detail.html:51 +#: event_management/templates/event_management/event_detail.html:52 #: event_management/templates/event_management/shift_form.html:49 msgid "Add another shift" msgstr "Weitere Schicht hinzufügen" #: event_management/templates/event_management/event_form.html:10 -#: event_management/templates/event_management/event_form.html:31 +#: event_management/templates/event_management/event_form.html:27 msgid "Create new event" msgstr "Neue Veranstaltung anlegen" -#: event_management/templates/event_management/event_form.html:22 +#: event_management/templates/event_management/event_form.html:18 msgid "You have an unsaved event" msgstr "Sie haben eine nicht gespeicherte Veranstaltung" -#: event_management/templates/event_management/event_form.html:23 +#: event_management/templates/event_management/event_form.html:19 +#: event_management/templates/event_management/mails/new_event.html:7 msgid "View" msgstr "Anzeigen" -#: event_management/templates/event_management/event_form.html:43 -#: user_management/templates/user_management/group_form.html:27 +#: event_management/templates/event_management/event_form.html:39 +#: user_management/templates/user_management/group_form.html:23 msgid "Cancel" msgstr "Abbrechen" -#: event_management/templates/event_management/event_form.html:44 +#: event_management/templates/event_management/event_form.html:40 msgid "Next" msgstr "Weiter" #: event_management/templates/event_management/event_list.html:11 -#: templates/base.html:43 +#: templates/base.html:48 msgid "Events" msgstr "Veranstaltungen" @@ -322,7 +387,6 @@ msgid "Location" msgstr "Ort" #: event_management/templates/event_management/event_list.html:22 -#: event_management/templates/event_management/fragments/shift_box_big.html:22 #: event_management/templates/event_management/fragments/shift_box_small.html:17 msgid "Date" msgstr "Datum" @@ -341,46 +405,35 @@ msgstr "Aktion" msgid "Show" msgstr "Anzeigen" -#: event_management/templates/event_management/fragments/shift_box_big.html:6 -#: event_management/templates/event_management/fragments/shift_box_small.html:6 -#: event_management/templates/event_management/shift_form.html:74 -msgid "Shift" -msgstr "Schicht" - -#: event_management/templates/event_management/fragments/shift_box_big.html:10 +#: event_management/templates/event_management/fragments/shift_box_big.html:12 #: event_management/templates/event_management/fragments/shift_box_small.html:9 #: event_management/templates/event_management/shift_form.html:60 #: user_management/templates/user_management/group_list.html:51 msgid "Edit" msgstr "Bearbeiten" -#: event_management/templates/event_management/fragments/shift_box_big.html:23 +#: event_management/templates/event_management/fragments/shift_box_big.html:24 #: event_management/templates/event_management/fragments/shift_box_small.html:18 msgid "Meeting time" msgstr "Treffpunkt" -#: event_management/templates/event_management/fragments/shift_box_big.html:24 +#: event_management/templates/event_management/fragments/shift_box_big.html:65 +msgid "Decline" +msgstr "Absagen" + +#: event_management/templates/event_management/fragments/shift_box_small.html:6 +#: event_management/templates/event_management/shift_form.html:74 +msgid "Shift" +msgstr "Schicht" + #: event_management/templates/event_management/fragments/shift_box_small.html:19 msgid "Start time" msgstr "Beginn" -#: event_management/templates/event_management/fragments/shift_box_big.html:25 #: event_management/templates/event_management/fragments/shift_box_small.html:20 msgid "End time" msgstr "Ende" -#: event_management/templates/event_management/fragments/shift_box_big.html:27 -msgid "Status" -msgstr "Status" - -#: event_management/templates/event_management/fragments/shift_box_big.html:31 -msgid "Register" -msgstr "Anmelden" - -#: event_management/templates/event_management/fragments/shift_box_big.html:37 -msgid "Decline" -msgstr "Absagen" - #: event_management/templates/event_management/fragments/shift_box_small.html:21 msgid "Signup method" msgstr "Anmeldeverfahren" @@ -432,65 +485,85 @@ msgstr "Veranstaltungstyp" msgid "You are currently adding this shift" msgstr "Sie erstellen gerade diese Schicht" -#: event_management/views.py:136 event_management/views.py:191 +#: event_management/views.py:125 event_management/views.py:180 #, python-brace-format msgid "The event {title} has been saved." msgstr "Die Veranstaltung {title} wurde gespeichert." -#: event_management/views.py:265 +#: event_management/views.py:252 #, python-brace-format msgid "The shift {shift} has been saved." msgstr "Die Schicht {shift} wurde gespeichert." -#: event_management/views.py:284 +#: event_management/views.py:273 msgid "You cannot delete the last shift!" msgstr "Sie können die letzte Schicht nicht löschen!" -#: event_management/views.py:290 +#: event_management/views.py:279 msgid "The shift has been deleted." msgstr "Die Schicht wurde gelöscht." -#: jep/management/commands/setupdata.py:65 +#: jep/management/commands/setupdata.py:76 msgid "Volunteers" msgstr "Helfer" -#: jep/management/commands/setupdata.py:69 +#: jep/management/commands/setupdata.py:80 msgid "Planners" msgstr "Planer" -#: jep/management/commands/setupdata.py:73 +#: jep/management/commands/setupdata.py:84 msgid "Managers" msgstr "Manager" -#: templates/base.html:37 +#: jep/management/commands/setupdata.py:100 +msgid "Service" +msgstr "Dienst" + +#: jep/management/commands/setupdata.py:101 +msgid "Training" +msgstr "Ausbildung" + +#: jep/management/commands/setupdata.py:114 +msgid "Medical" +msgstr "Medizinisch" + +#: jep/management/commands/setupdata.py:136 +msgid "Concert Medical Service" +msgstr "Konzert Absicherung" + +#: jep/management/commands/setupdata.py:137 +msgid "Your contact is Lisa Example. Her Phone number is 012345678910" +msgstr "Euer Kontakt ist Lisa Beispiel. Ihre Telefonnummer ist 012345678910" + +#: templates/base.html:42 msgid "Home" msgstr "Startseite" -#: templates/base.html:49 +#: templates/base.html:55 #: user_management/templates/user_management/userprofile_list.html:11 msgid "Users" msgstr "Benutzer" -#: templates/base.html:57 +#: templates/base.html:63 #: user_management/templates/user_management/group_list.html:6 #: user_management/templates/user_management/group_list.html:32 msgid "Groups" msgstr "Gruppen" -#: templates/base.html:72 +#: templates/base.html:78 msgid "Administration" msgstr "Administration" -#: templates/base.html:76 +#: templates/base.html:82 #: user_management/templates/user_management/userprofile_detail.html:6 msgid "Profile" msgstr "Benutzerprofil" -#: templates/base.html:78 templates/registration/password_change_form.html:6 +#: templates/base.html:84 templates/registration/password_change_form.html:6 msgid "Change password" msgstr "Passwort ändern" -#: templates/base.html:79 +#: templates/base.html:85 msgid "Logout" msgstr "Abmelden" @@ -566,55 +639,59 @@ msgstr "" "Wählen Sie Gruppen, für die diese Gruppe Veranstaltungen sichtbar machen " "darf." -#: user_management/models.py:48 +#: user_management/models.py:52 msgid "email address" msgstr "E-Mail-Adresse" -#: user_management/models.py:51 +#: user_management/models.py:55 msgid "first name" msgstr "Vorname" -#: user_management/models.py:52 +#: user_management/models.py:56 msgid "last name" msgstr "Nachname" -#: user_management/models.py:53 +#: user_management/models.py:57 msgid "date of birth" msgstr "Geburtsdatum" -#: user_management/models.py:54 +#: user_management/models.py:58 msgid "phone number" msgstr "Telefonnummer" -#: user_management/models.py:55 +#: user_management/models.py:59 msgid "calendar token" msgstr "Kalender-Token" -#: user_management/models.py:67 user_management/models.py:135 +#: user_management/models.py:71 user_management/models.py:164 msgid "user profile" msgstr "Benutzerprofil" -#: user_management/models.py:68 +#: user_management/models.py:72 msgid "user profiles" msgstr "Benutzerprofile" -#: user_management/models.py:113 user_management/models.py:120 +#: user_management/models.py:128 msgid "qualification track" msgstr "Qualifikationskategorie" -#: user_management/models.py:114 +#: user_management/models.py:129 msgid "qualification tracks" msgstr "Qualifikationskategorien" -#: user_management/models.py:124 user_management/models.py:133 +#: user_management/models.py:140 +msgid "category" +msgstr "Kategorie" + +#: user_management/models.py:153 user_management/models.py:162 msgid "qualification" msgstr "Qualifikation" -#: user_management/models.py:125 +#: user_management/models.py:154 msgid "qualifications" msgstr "Qualifikationen" -#: user_management/models.py:136 +#: user_management/models.py:165 msgid "expiration date" msgstr "Ablaufdatum" @@ -628,11 +705,11 @@ msgstr "Gruppe löschen" msgid "Are you sure you want to delete the group \"%(group)s\"?" msgstr "Möchten Sie die Gruppe \"%(group)s\" wirklich löschen?" -#: user_management/templates/user_management/group_form.html:15 +#: user_management/templates/user_management/group_form.html:11 msgid "Edit group" msgstr "Gruppe bearbeiten" -#: user_management/templates/user_management/group_form.html:17 +#: user_management/templates/user_management/group_form.html:13 msgid "Create new group" msgstr "Neue Gruppe erstellen" @@ -716,6 +793,24 @@ msgstr "" msgid "Your password has been changed" msgstr "Ihr Passwort wurde geändert" -#: user_management/views.py:49 +#: user_management/views.py:46 msgid "Group successfully created." msgstr "Gruppe erfolgreich erstellt" + +#~| msgid "Decline" +#~ msgid "Self-decline" +#~ msgstr "Selbstständige Absage" + +#~ msgid "The event is not active, you cannot sign up for it." +#~ msgstr "" +#~ "Die Veranstaltung ist nicht aktiv, Sie können sich nicht dafür anmelden." + +#, python-brace-format +#~ msgid "You have declined a participation for shift {shift}." +#~ msgstr "Sie haben für die Schicht {shift} abgesagt." + +#~ msgid "Status" +#~ msgstr "Status" + +#~ msgid "Register" +#~ msgstr "Anmelden"