From 6df53cf16c6e8c4098cb5208a03f9125c30e346e Mon Sep 17 00:00:00 2001 From: Julian B Date: Sun, 5 Jan 2025 13:55:49 +0100 Subject: [PATCH 1/3] migrate comments to ParticipationComment --- ephios/core/admin.py | 3 +- .../migrations/0034_participationcomment.py | 79 +++++++++++++++++++ ...35_remove_abstractparticipation_comment.py | 17 ++++ ephios/core/models/events.py | 26 +++++- ephios/core/signup/disposition.py | 4 +- ephios/core/signup/forms.py | 2 +- 6 files changed, 124 insertions(+), 7 deletions(-) create mode 100644 ephios/core/migrations/0034_participationcomment.py create mode 100644 ephios/core/migrations/0035_remove_abstractparticipation_comment.py diff --git a/ephios/core/admin.py b/ephios/core/admin.py index 6a348695a..0311e1529 100644 --- a/ephios/core/admin.py +++ b/ephios/core/admin.py @@ -14,7 +14,7 @@ UserProfile, WorkingHours, ) -from ephios.core.models.events import PlaceholderParticipation +from ephios.core.models.events import ParticipationComment, PlaceholderParticipation from ephios.core.models.users import IdentityProvider admin.site.register(UserProfile) @@ -31,3 +31,4 @@ admin.site.register(PlaceholderParticipation) admin.site.register(Notification) admin.site.register(IdentityProvider) +admin.site.register(ParticipationComment) diff --git a/ephios/core/migrations/0034_participationcomment.py b/ephios/core/migrations/0034_participationcomment.py new file mode 100644 index 000000000..8f2d4b3ba --- /dev/null +++ b/ephios/core/migrations/0034_participationcomment.py @@ -0,0 +1,79 @@ +# Generated by Django 5.0.9 on 2025-01-05 12:37 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +def migrate_comment(apps, schema_editor): + ParticipationComment = apps.get_model("core", "ParticipationComment") + AbstractParticipation = apps.get_model("core", "AbstractParticipation") + db_alias = schema_editor.connection.alias + comments = [] + for participation in AbstractParticipation.objects.using(db_alias).all(): + if participation.comment: + comments.append( + ParticipationComment(participation=participation, text=participation.comment) + ) + ParticipationComment.objects.using(db_alias).bulk_create(comments) + + +def revert_comments(apps, schema_editor): + ParticipationComment = apps.get_model("core", "ParticipationComment") + AbstractParticipation = apps.get_model("core", "AbstractParticipation") + db_alias = schema_editor.connection.alias + for comment in ParticipationComment.objects.using(db_alias).all(): + comment.participation.comment = comment.text + comment.participation.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ("core", "0033_eventtype_show_participant_data"), + ] + + operations = [ + migrations.CreateModel( + name="ParticipationComment", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, primary_key=True, serialize=False, verbose_name="ID" + ), + ), + ( + "visibile_for", + models.IntegerField( + choices=[ + (0, "responsibles only"), + (1, "responsibles and corresponding participant"), + (2, "everyone"), + ], + default=0, + verbose_name="visible for", + ), + ), + ("text", models.CharField(max_length=255, verbose_name="Comment")), + ( + "authored_by_responsible", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "participation", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="comments", + to="core.abstractparticipation", + ), + ), + ], + ), + migrations.RunPython(migrate_comment, revert_comments), + ] diff --git a/ephios/core/migrations/0035_remove_abstractparticipation_comment.py b/ephios/core/migrations/0035_remove_abstractparticipation_comment.py new file mode 100644 index 000000000..50ec260bc --- /dev/null +++ b/ephios/core/migrations/0035_remove_abstractparticipation_comment.py @@ -0,0 +1,17 @@ +# Generated by Django 5.0.9 on 2025-01-05 12:52 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("core", "0034_participationcomment"), + ] + + operations = [ + migrations.RemoveField( + model_name="abstractparticipation", + name="comment", + ), + ] diff --git a/ephios/core/models/events.py b/ephios/core/models/events.py index c425e8ee6..8ad354267 100644 --- a/ephios/core/models/events.py +++ b/ephios/core/models/events.py @@ -239,9 +239,6 @@ def labels_dict(cls): individual_start_time = DateTimeField(_("individual start time"), null=True) individual_end_time = DateTimeField(_("individual end time"), null=True) - # human readable comment - comment = models.CharField(_("Comment"), max_length=255, blank=True) - """ The finished flag is used to make sure the participation_finished signal is only sent out once, even if the shift time is changed afterwards. @@ -286,6 +283,29 @@ def is_in_positive_state(self): ) +class ParticipationComment(Model): + class Visibility(models.IntegerChoices): + RESPONSIBLES_ONLY = 0, _("responsibles only") + PARTICIPANT = 1, _("responsibles and corresponding participant") + PUBLIC = 2, _("everyone") + + participation = models.ForeignKey( + AbstractParticipation, on_delete=models.CASCADE, related_name="comments" + ) + authored_by_responsible = models.ForeignKey( + "UserProfile", on_delete=models.SET_NULL, blank=True, null=True + ) + visibile_for = IntegerField( + _("visible for"), choices=Visibility.choices, default=Visibility.RESPONSIBLES_ONLY + ) + text = models.CharField(_("Comment"), max_length=255) + + def __str__(self): + return _("Participation comment for {participation}").format( + participation=self.participation + ) + + class Shift(DatetimeDisplayMixin, Model): event = ForeignKey( Event, on_delete=models.CASCADE, related_name="shifts", verbose_name=_("event") diff --git a/ephios/core/signup/disposition.py b/ephios/core/signup/disposition.py index ecd9ccecf..b16d0d488 100644 --- a/ephios/core/signup/disposition.py +++ b/ephios/core/signup/disposition.py @@ -40,10 +40,10 @@ def __init__(self, **kwargs): super().__init__(**kwargs) self.can_delete = self.instance.state == AbstractParticipation.States.GETTING_DISPATCHED - self.fields["comment"].disabled = True + # self.fields["comment"].disabled = True class Meta(BaseParticipationForm.Meta): - fields = ["state", "individual_start_time", "individual_end_time", "comment"] + fields = ["state", "individual_start_time", "individual_end_time"] widgets = {"state": forms.HiddenInput(attrs={"class": "state-input"})} diff --git a/ephios/core/signup/forms.py b/ephios/core/signup/forms.py index 922f9be72..3a590b349 100644 --- a/ephios/core/signup/forms.py +++ b/ephios/core/signup/forms.py @@ -43,7 +43,7 @@ def clean(self): class Meta: model = AbstractParticipation - fields = ["individual_start_time", "individual_end_time", "comment"] + fields = ["individual_start_time", "individual_end_time"] def __init__(self, *args, **kwargs): instance = kwargs["instance"] From 6334fed76a58e427e3871b6552e8e7215a8f6030 Mon Sep 17 00:00:00 2001 From: Julian B Date: Wed, 8 Jan 2025 10:53:39 +0100 Subject: [PATCH 2/3] adapt views to use new comment model --- .../migrations/0034_participationcomment.py | 5 +++ ephios/core/models/events.py | 6 +++- ephios/core/signup/disposition.py | 2 +- ephios/core/signup/forms.py | 17 +++++++++- .../disposition/fragment_participation.html | 10 ++++-- ephios/core/views/event.py | 32 +++++++++++-------- .../extra/widgets/previous_comments.html | 5 +++ ephios/extra/widgets.py | 15 ++++++++- .../participation_card_inline.html | 16 ++++++---- 9 files changed, 80 insertions(+), 28 deletions(-) create mode 100644 ephios/extra/templates/extra/widgets/previous_comments.html diff --git a/ephios/core/migrations/0034_participationcomment.py b/ephios/core/migrations/0034_participationcomment.py index 8f2d4b3ba..17a3b5d37 100644 --- a/ephios/core/migrations/0034_participationcomment.py +++ b/ephios/core/migrations/0034_participationcomment.py @@ -1,6 +1,7 @@ # Generated by Django 5.0.9 on 2025-01-05 12:37 import django.db.models.deletion +import django.utils.timezone from django.conf import settings from django.db import migrations, models @@ -65,6 +66,10 @@ class Migration(migrations.Migration): to=settings.AUTH_USER_MODEL, ), ), + ( + "created_at", + models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), + ), ( "participation", models.ForeignKey( diff --git a/ephios/core/models/events.py b/ephios/core/models/events.py index 8ad354267..6d619ff0c 100644 --- a/ephios/core/models/events.py +++ b/ephios/core/models/events.py @@ -251,7 +251,6 @@ def has_customized_signup(self): return bool( self.individual_start_time or self.individual_end_time - or self.comment or self.shift.structure.has_customized_signup(self) ) @@ -295,11 +294,16 @@ class Visibility(models.IntegerChoices): authored_by_responsible = models.ForeignKey( "UserProfile", on_delete=models.SET_NULL, blank=True, null=True ) + created_at = models.DateTimeField(auto_now_add=True) visibile_for = IntegerField( _("visible for"), choices=Visibility.choices, default=Visibility.RESPONSIBLES_ONLY ) text = models.CharField(_("Comment"), max_length=255) + @property + def author(self): + return self.authored_by_responsible or self.participation.participant + def __str__(self): return _("Participation comment for {participation}").format( participation=self.participation diff --git a/ephios/core/signup/disposition.py b/ephios/core/signup/disposition.py index b16d0d488..a3fb40129 100644 --- a/ephios/core/signup/disposition.py +++ b/ephios/core/signup/disposition.py @@ -40,7 +40,6 @@ def __init__(self, **kwargs): super().__init__(**kwargs) self.can_delete = self.instance.state == AbstractParticipation.States.GETTING_DISPATCHED - # self.fields["comment"].disabled = True class Meta(BaseParticipationForm.Meta): fields = ["state", "individual_start_time", "individual_end_time"] @@ -215,6 +214,7 @@ def get_formset(self): self.request.POST or None, queryset=self.object.participations.all(), prefix="participations", + form_kwargs={"acting_user": self.request.user}, ) return formset diff --git a/ephios/core/signup/forms.py b/ephios/core/signup/forms.py index 3a590b349..22650f086 100644 --- a/ephios/core/signup/forms.py +++ b/ephios/core/signup/forms.py @@ -7,9 +7,10 @@ from django.utils.translation import gettext_lazy as _ from ephios.core.models import AbstractParticipation, Shift +from ephios.core.models.events import ParticipationComment from ephios.core.signup.flow.participant_validation import get_conflicting_participations from ephios.core.signup.participants import AbstractParticipant -from ephios.extra.widgets import CustomSplitDateTimeWidget +from ephios.extra.widgets import CustomSplitDateTimeWidget, PreviousCommentWidget class BaseParticipationForm(forms.ModelForm): @@ -21,6 +22,7 @@ class BaseParticipationForm(forms.ModelForm): widget=CustomSplitDateTimeWidget, required=False, ) + comment = forms.CharField(label=_("Comment"), max_length=255, required=False) def clean_individual_start_time(self): if self.cleaned_data["individual_start_time"] == self.shift.start_time: @@ -41,18 +43,31 @@ def clean(self): self.add_error("individual_end_time", _("End time must not be before start time.")) return cleaned_data + def save(self, commit=True): + result = super().save(commit) + if comment := self.cleaned_data["comment"]: + ParticipationComment.objects.create( + participation=result, text=comment, authored_by_responsible=self.acting_user + ) + return result + class Meta: model = AbstractParticipation fields = ["individual_start_time", "individual_end_time"] def __init__(self, *args, **kwargs): instance = kwargs["instance"] + self.acting_user = kwargs.pop("acting_user", None) kwargs["initial"] = { **kwargs.get("initial", {}), "individual_start_time": instance.individual_start_time or self.shift.start_time, "individual_end_time": instance.individual_end_time or self.shift.end_time, } super().__init__(*args, **kwargs) + if self.instance and self.instance.comments.exists(): + self.fields["previous_comments"] = forms.CharField( + widget=PreviousCommentWidget(comments=self.instance.comments.all()), required=False + ) def get_customization_notification_info(self): """ diff --git a/ephios/core/templates/core/disposition/fragment_participation.html b/ephios/core/templates/core/disposition/fragment_participation.html index e7fec366b..ec63e4552 100644 --- a/ephios/core/templates/core/disposition/fragment_participation.html +++ b/ephios/core/templates/core/disposition/fragment_participation.html @@ -37,6 +37,9 @@