Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Option to make participant data anonymous #1453

Merged
merged 6 commits into from
Jan 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions docs/user/events/events.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ Shifts can be continuous and overlapping.

While permissions are managed on event level, signup settings are configured on shift level.

Responsibles and visibility
---------------------------
Responsibles and event visibility
---------------------------------

Single users and groups can be made responsible for an event. That allows them to edit event details,
shift settings and do the disposition for the event's shifts.
Expand All @@ -22,7 +22,16 @@ Event types
-----------

Event types are used for organizing events into different categories. They can be used in filters
and come with defaults for visibility and responsibility settings.
and come with defaults for event visibility and responsibility settings.

Showing participant data
------------------------

For every event type you can configure who can view the names of
participants in events of that type.
By default, participant data is visible to everyone.
You can restrict it to people who have a confirmed participation in any of the event's shift.
The most strict option is to only show participant data to the event's responsibles.

Settings
--------
Expand Down
6 changes: 3 additions & 3 deletions ephios/api/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@
class ParticipationPermissionFilter(BaseFilterBackend):
def filter_queryset(self, request, queryset, view):
# to view public participation information (excl. email) you need to
# be able to see the event
viewable_events = get_objects_for_user(request.user, "core.view_event")
return queryset.filter(shift__event__in=viewable_events)
# * be able to see the event AND
# * the event types' show_participation_data mode must fit
return queryset.viewable_by(request.user)


class UserinfoParticipationPermissionFilter(ParticipationPermissionFilter):
Expand Down
8 changes: 4 additions & 4 deletions ephios/api/views/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
UserinfoParticipationSerializer,
UserProfileSerializer,
)
from ephios.core.models import LocalParticipation, UserProfile
from ephios.core.models import AbstractParticipation, LocalParticipation, UserProfile


class UserProfileMeView(RetrieveAPIView):
Expand Down Expand Up @@ -81,6 +81,6 @@ class UserParticipationView(viewsets.ReadOnlyModelViewSet):
required_scopes = ["CONFIDENTIAL_READ"]

def get_queryset(self):
return LocalParticipation.objects.filter(user=self.kwargs.get("user")).select_related(
"shift", "shift__event", "shift__event__type"
)
return AbstractParticipation.objects.filter(
localparticipation__user=self.kwargs.get("user")
).select_related("shift", "shift__event", "shift__event__type")
2 changes: 1 addition & 1 deletion ephios/core/forms/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ def __init__(self, *args, **kwargs):
class EventTypeForm(forms.ModelForm):
class Meta:
model = EventType
fields = ["title", "color"]
fields = ["title", "color", "show_participant_data"]
widgets = {"color": ColorInput()}

def clean_color(self):
Expand Down
27 changes: 27 additions & 0 deletions ephios/core/migrations/0033_eventtype_show_participant_data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Generated by Django 5.0.9 on 2025-01-03 19:18

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("core", "0032_shift_label"),
]

operations = [
migrations.AddField(
model_name="eventtype",
name="show_participant_data",
field=models.SmallIntegerField(
choices=[
(0, "to everyone"),
(1, "to confirmed participants"),
(2, "only to responsible users"),
],
default=0,
help_text="If you restrict who can see participant data, others will only be able to see that there is a participation, but not from whom.",
verbose_name="show participant data",
),
),
]
53 changes: 45 additions & 8 deletions ephios/core/models/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
JSONField,
Manager,
Model,
Q,
SlugField,
TextField,
)
Expand All @@ -23,9 +24,10 @@
from django.utils.translation import gettext_lazy as _
from django.utils.translation import pgettext
from dynamic_preferences.models import PerInstancePreferenceModel
from guardian.shortcuts import assign_perm
from guardian.shortcuts import assign_perm, get_objects_for_user
from polymorphic.managers import PolymorphicManager
from polymorphic.models import PolymorphicModel
from polymorphic.query import PolymorphicQuerySet

from ephios.core.signup.stats import SignupStats
from ephios.extra.json import CustomJSONDecoder, CustomJSONEncoder
Expand All @@ -45,8 +47,21 @@ def get_queryset(self):


class EventType(Model):
class ShowParticipantDataChoices(models.IntegerChoices):
EVERYONE = 0, _("to everyone")
CONFIRMED = 1, _("to confirmed participants")
RESPONSIBLES = 2, _("only to responsible users")

title = CharField(_("title"), max_length=254)
color = CharField(_("color"), max_length=7, default="#343a40")
show_participant_data = models.SmallIntegerField(
verbose_name=_("show participant data"),
felixrindt marked this conversation as resolved.
Show resolved Hide resolved
help_text=_(
"If you restrict who can see participant data, others will only be able to see that there is a participation, but not from whom."
),
choices=ShowParticipantDataChoices.choices,
default=ShowParticipantDataChoices.EVERYONE,
)

class Meta:
verbose_name = _("event type")
Expand Down Expand Up @@ -128,15 +143,37 @@ def activate(self):
register_model_for_logging(Event, ModelFieldsLogConfig())


class ParticipationQuerySet(PolymorphicQuerySet):

def viewable_by(self, user):
viewable_events = get_objects_for_user(user, "core.view_event")
viewable_userprofiles = get_objects_for_user(user, "core.view_userprofile")
editable_events = get_objects_for_user(user, "core.change_event")
participating_events = Event.objects.filter(
shifts__participations__in=user.participations.filter(
state=AbstractParticipation.States.CONFIRMED
)
)
qs = self.filter(shift__event__in=viewable_events).filter(
Q(
shift__event__type__show_participant_data=EventType.ShowParticipantDataChoices.EVERYONE
)
| Q(
shift__event__type__show_participant_data=EventType.ShowParticipantDataChoices.CONFIRMED,
shift__event__in=participating_events,
)
| Q(shift__event__in=editable_events)
| Q(localparticipation__user=user)
| Q(localparticipation__user__in=viewable_userprofiles)
)
return qs.distinct()


class ParticipationManager(PolymorphicManager):
def get_queryset(self):
return (
super()
.get_queryset()
.annotate(
start_time=Coalesce("individual_start_time", "shift__start_time"),
end_time=Coalesce("individual_end_time", "shift__end_time"),
)
return ParticipationQuerySet(self.model, using=self._db).annotate(
start_time=Coalesce("individual_start_time", "shift__start_time"),
end_time=Coalesce("individual_end_time", "shift__end_time"),
)


Expand Down
18 changes: 17 additions & 1 deletion ephios/core/views/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -446,7 +446,23 @@ def get_queryset(self):
base = Event.objects.all()
if self.request.user.has_perm("core.add_event"):
base = Event.all_objects.all()
return base.prefetch_related("shifts", "shifts__participations")
return base.prefetch_related("shifts").prefetch_related(
Prefetch(
"shifts__participations",
queryset=AbstractParticipation.objects.all().annotate(
show_participant_data=Case(
When(
id__in=AbstractParticipation.objects.all().viewable_by(
self.request.user
),
then=True,
),
default=False,
output_field=BooleanField(),
)
),
)
)

def get_context_data(self, **kwargs):
kwargs["can_change_event"] = self.request.user.has_perm("core.change_event", self.object)
Expand Down
Loading
Loading