Skip to content

Commit

Permalink
Merge 097e419 into 589f3ad
Browse files Browse the repository at this point in the history
  • Loading branch information
felixrindt committed Jan 20, 2021
2 parents 589f3ad + 097e419 commit 052dae3
Show file tree
Hide file tree
Showing 29 changed files with 1,096 additions and 393 deletions.
20 changes: 18 additions & 2 deletions ephios/event_management/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,15 @@
)
from django.utils import formats
from django.utils.translation import gettext_lazy as _
from guardian.shortcuts import assign_perm
from polymorphic.models import PolymorphicModel

from ephios import settings
from ephios.extra.json import CustomJSONDecoder, CustomJSONEncoder

if TYPE_CHECKING:
from ephios.event_management.signup import AbstractParticipant
from ephios.user_management.models import UserProfile


class ActiveManager(Manager):
Expand Down Expand Up @@ -93,9 +95,10 @@ class States(models.IntegerChoices):
CONFIRMED = 1, _("confirmed")
USER_DECLINED = 2, _("declined by user")
RESPONSIBLE_REJECTED = 3, _("rejected by responsible")
RESPONSIBLE_ADDED = 4, _("added by responsible")

shift = ForeignKey("Shift", on_delete=models.CASCADE, verbose_name=_("shift"))
state = IntegerField(_("state"), choices=States.choices, default=States.REQUESTED)
state = IntegerField(_("state"), choices=States.choices)
data = models.JSONField(default=dict)

@property
Expand Down Expand Up @@ -160,7 +163,20 @@ def __str__(self):


class LocalParticipation(AbstractParticipation):
user = ForeignKey(get_user_model(), on_delete=models.CASCADE)
user: "UserProfile" = ForeignKey(get_user_model(), on_delete=models.CASCADE)

def save(self, *args, **kwargs):
super().save(*args, **kwargs)
if (
not self.user.has_perm("event_management.view_event", obj=self.shift.event)
and self.state != self.States.RESPONSIBLE_ADDED
):
# If dispatched by a responsible, the user should be able to view
# the event, if not already permitted through its group.
# Currently, this permission does not get removed automatically.
assign_perm(
"event_management.view_event", user_or_group=self.user, obj=self.shift.event
)

@property
def participant(self):
Expand Down
8 changes: 8 additions & 0 deletions ephios/event_management/signup.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

from ephios.event_management.models import AbstractParticipation, LocalParticipation, Shift
from ephios.extra.widgets import CustomSplitDateTimeWidget
from ephios.plugins.basesignup.signup.disposition import BaseDispositionParticipationForm
from ephios.user_management.models import Qualification

register_signup_methods = django.dispatch.Signal()
Expand Down Expand Up @@ -217,6 +218,13 @@ def verbose_name(self):
decline_success_message = _("You have successfully declined {shift}.")
decline_error_message = _("Declining failed: {error}")

"""
This form will be used for participations in disposition.
Set to None if you don't want to support the default disposition.
"""
disposition_participation_form_class = BaseDispositionParticipationForm
disposition_show_requested_state = True

def __init__(self, shift):
self.shift = shift
self.configuration = Namespace(
Expand Down
6 changes: 2 additions & 4 deletions ephios/plugins/basesignup/signals.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
from django.dispatch import receiver

from ephios.event_management.signup import register_signup_methods
from ephios.plugins.basesignup.signup.instant import InstantConfirmationSignupMethod
from ephios.plugins.basesignup.signup.request_confirm import RequestConfirmSignupMethod
from ephios.plugins.basesignup.signup.section_based import SectionBasedSignupMethod
from ephios.plugins.basesignup.signup.simple import (
InstantConfirmationSignupMethod,
RequestConfirmSignupMethod,
)


@receiver(
Expand Down
Empty file.
43 changes: 43 additions & 0 deletions ephios/plugins/basesignup/signup/common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from django import forms
from django.utils.translation import gettext_lazy as _
from django_select2.forms import Select2MultipleWidget

from ephios.event_management.signup import BaseSignupMethod, ParticipationError
from ephios.user_management.models import Qualification


class SimpleQualificationsRequiredSignupMethod(BaseSignupMethod):
# pylint: disable=abstract-method
def __init__(self, shift):
super().__init__(shift)
if shift is not None:
self.configuration.required_qualifications = Qualification.objects.filter(
pk__in=self.configuration.required_qualification_ids
)

@property
def signup_checkers(self):
return super().signup_checkers + [self.check_qualification]

@staticmethod
def check_qualification(method, participant):
if not participant.has_qualifications(method.configuration.required_qualifications):
return ParticipationError(_("You are not qualified."))

def get_configuration_fields(self):
return {
**super().get_configuration_fields(),
"required_qualification_ids": {
"formfield": forms.ModelMultipleChoiceField(
label=_("Required Qualifications"),
queryset=Qualification.objects.all(),
widget=Select2MultipleWidget,
required=False,
),
"default": [],
"publish_with_label": _("Required Qualification"),
"format": lambda ids: ", ".join(
Qualification.objects.filter(id__in=ids).values_list("title", flat=True)
),
},
}
150 changes: 150 additions & 0 deletions ephios/plugins/basesignup/signup/disposition.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
from django import forms
from django.http import Http404
from django.shortcuts import redirect
from django.utils.translation import gettext_lazy as _
from django.views import View
from django.views.generic import TemplateView
from django.views.generic.base import TemplateResponseMixin
from django.views.generic.detail import SingleObjectMixin
from django_select2.forms import ModelSelect2Widget

from ephios.event_management.models import AbstractParticipation, Shift
from ephios.extra.permissions import CustomPermissionRequiredMixin
from ephios.user_management.models import UserProfile


class BaseDispositionParticipationForm(forms.ModelForm):
disposition_participation_template = "basesignup/common/fragment_participant.html"

def __init__(self, **kwargs):
super().__init__(**kwargs)
try:
self.shift = self.instance.shift
except AttributeError as e:
raise ValueError(f"{type(self)} must be initialized with an instance.") from e

class Meta:
model = AbstractParticipation
fields = ["state"]
widgets = dict(state=forms.HiddenInput(attrs={"class": "state-input"}))


def get_disposition_formset(form):
return forms.modelformset_factory(
model=AbstractParticipation,
form=form,
extra=0,
can_order=False,
can_delete=True,
)


def addable_users(shift):
"""
Return queryset of user objects that can be added to the shift.
This also includes users that already have a participation, as that might have gotten removed in JS.
This also includes users that can normally not see the event. The permission will be added accordingly.
If needed, this method could be moved to signup methods.
"""
return UserProfile.objects.all()


class AddUserForm(forms.Form):
user = forms.ModelChoiceField(
widget=ModelSelect2Widget(
model=UserProfile,
search_fields=["first_name__icontains", "last_name__icontains"],
attrs={"form": "add-user-form"},
),
queryset=UserProfile.objects.none(), # set using __init__
)

def __init__(self, user_queryset, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["user"].queryset = user_queryset


class DispositionBaseViewMixin(CustomPermissionRequiredMixin, SingleObjectMixin):
permission_required = "event_management.change_event"
model = Shift

def setup(self, request, *args, **kwargs):
super().setup(request, *args, **kwargs)
self.object: Shift = self.get_object()

def dispatch(self, request, *args, **kwargs):
if self.object.signup_method.disposition_participation_form_class is None:
raise Http404(_("This signup method does not support disposition."))
return super().dispatch(request, *args, **kwargs)

def get_permission_object(self):
return self.object.event


class AddUserView(DispositionBaseViewMixin, TemplateResponseMixin, View):
def get_template_names(self):
return [
self.object.signup_method.disposition_participation_form_class.disposition_participation_template
]

def post(self, request, *args, **kwargs):
shift = self.object
form = AddUserForm(
user_queryset=addable_users(shift),
data=request.POST,
)
if form.is_valid():
user: UserProfile = form.cleaned_data["user"]
instance = shift.signup_method.get_participation_for(user.as_participant())
instance.state = AbstractParticipation.States.RESPONSIBLE_ADDED
instance.save()

DispositionParticipationFormset = get_disposition_formset(
self.object.signup_method.disposition_participation_form_class
)
formset = DispositionParticipationFormset(
queryset=self.object.participations,
prefix="participations",
)
form = next(filter(lambda form: form.instance.id == instance.id, formset))
return self.render_to_response({"form": form})
raise Http404("User does not exist")


class DispositionView(DispositionBaseViewMixin, TemplateView):
template_name = "basesignup/common/disposition.html"

def get_formset(self):
DispositionParticipationFormset = get_disposition_formset(
self.object.signup_method.disposition_participation_form_class
)
formset = DispositionParticipationFormset(
self.request.POST or None,
queryset=self.object.participations,
prefix="participations",
)
return formset

def post(self, request, *args, **kwargs):
formset = self.get_formset()
if formset.is_valid():
formset.save()
self.object.participations.filter(
state=AbstractParticipation.States.RESPONSIBLE_ADDED
).delete()
return redirect(self.object.event.get_absolute_url())
return self.get(request, *args, **kwargs, formset=formset)

def get_context_data(self, **kwargs):
kwargs.setdefault("formset", self.get_formset())
kwargs.setdefault("states", AbstractParticipation.States)
kwargs.setdefault(
"participant_template",
self.object.signup_method.disposition_participation_form_class.disposition_participation_template,
)
kwargs.setdefault(
"add_user_form",
AddUserForm(user_queryset=addable_users(self.object)),
)
return super().get_context_data(**kwargs)
56 changes: 56 additions & 0 deletions ephios/plugins/basesignup/signup/instant.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
from django import forms
from django.template.loader import get_template
from django.urls import reverse
from django.utils.translation import gettext_lazy as _

from ephios.event_management.models import AbstractParticipation
from ephios.event_management.signup import ParticipationError
from ephios.plugins.basesignup.signup.common import SimpleQualificationsRequiredSignupMethod


class InstantConfirmationSignupMethod(SimpleQualificationsRequiredSignupMethod):
slug = "instant_confirmation"
verbose_name = _("Instant Confirmation")
description = _("""This method instantly confirms every signup after it was requested.""")
disposition_show_requested_state = False

@property
def signup_checkers(self):
return super().signup_checkers + [self.check_maximum_number_of_participants]

@staticmethod
def check_maximum_number_of_participants(method, participant):
if method.configuration.maximum_number_of_participants is not None:
current_count = AbstractParticipation.objects.filter(
shift=method.shift, state=AbstractParticipation.States.CONFIRMED
).count()
if current_count >= method.configuration.maximum_number_of_participants:
return ParticipationError(_("The maximum number of participants is reached."))

def get_configuration_fields(self):
return {
**super().get_configuration_fields(),
"maximum_number_of_participants": {
"formfield": forms.IntegerField(min_value=1, required=False),
"default": None,
"publish_with_label": _("Maximum number of participants"),
},
}

def render_shift_state(self, request):
return get_template("basesignup/instant/fragment_state.html").render(
{
"shift": self.shift,
"disposition_url": (
reverse("basesignup:shift_disposition", kwargs=dict(pk=self.shift.pk))
if request.user.has_perm("event_management.change_event", obj=self.shift.event)
else None
),
}
)

def perform_signup(self, participant, **kwargs):
participation = super().perform_signup(participant, **kwargs)
participation.state = AbstractParticipation.States.CONFIRMED
participation.save()
return participation
Loading

0 comments on commit 052dae3

Please sign in to comment.