From 77764da44e02d8ea9fa3b1446ab882a3d8b5894b Mon Sep 17 00:00:00 2001 From: Frank Elsinga Date: Mon, 4 Apr 2022 22:46:48 +0200 Subject: [PATCH 01/30] minor formatting changes --- bags/locale/de/LC_MESSAGES/django.po | 22 ++-- bags/models.py | 105 ++++---------------- bags/views.py | 2 +- fahrt/models.py | 38 ++----- guidedtours/locale/de/LC_MESSAGES/django.po | 10 +- guidedtours/models.py | 41 ++------ guidedtours/views.py | 14 +-- settool_common/models.py | 35 ++----- tutors/forms.py | 7 +- 9 files changed, 63 insertions(+), 211 deletions(-) diff --git a/bags/locale/de/LC_MESSAGES/django.po b/bags/locale/de/LC_MESSAGES/django.po index 55459307..bb6546e7 100644 --- a/bags/locale/de/LC_MESSAGES/django.po +++ b/bags/locale/de/LC_MESSAGES/django.po @@ -140,6 +140,13 @@ msgstr "E-Mail erfolgreich gesendet" msgid "Promise" msgstr "Zusage" +#: bags/models.py bags/templates/bags/bags_dashboard.html +#: bags/templates/bags/company/list_companys.html +#: bags/templates/bags/company/view_company.html +#: bags/templates/bags/maintenance/mail/send_mail.html +msgid "Contact again" +msgstr "Erneut kontaktieren" + #: bags/models.py bags/templates/bags/company/list_companys.html #: bags/templates/bags/company/view_company.html #: bags/templates/bags/maintenance/mail/del_mail.html @@ -148,13 +155,6 @@ msgstr "Zusage" msgid "Comment" msgstr "Kommentar" -#: bags/models.py bags/templates/bags/bags_dashboard.html -#: bags/templates/bags/company/list_companys.html -#: bags/templates/bags/company/view_company.html -#: bags/templates/bags/maintenance/mail/send_mail.html -msgid "Contact again" -msgstr "Erneut kontaktieren" - #: bags/models.py msgid "Giveaway-groups' name" msgstr "Name der Giveaway-Gruppe" @@ -185,10 +185,6 @@ msgstr "Jede {every_x_bags}te Tüte" msgid "{per_bag_count} every bag" msgstr "{per_bag_count} jede Tüte" -#: bags/models.py -msgid "Giveaway-description/comment" -msgstr "Werbegeschenk-Beschreibung/Kommentar" - #: bags/models.py bags/templates/bags/bags_dashboard.html #: bags/templates/bags/giveaways/giveaway/list/arrival/confirm_giveaways_arrivals.html #: bags/templates/bags/giveaways/giveaway/list/arrival/list_giveaways_arrivals.html @@ -213,6 +209,10 @@ msgstr "Ankunftszeit" msgid "Arrived" msgstr "Angekommen" +#: bags/models.py +msgid "Giveaway-description/comment" +msgstr "Werbegeschenk-Beschreibung/Kommentar" + #: bags/templates/bags/bags_dashboard.html bags/templates/bags/base_bags.html msgid "Dashboard" msgstr "Dashboard" diff --git a/bags/models.py b/bags/models.py index 22758cb9..cc0fff00 100644 --- a/bags/models.py +++ b/bags/models.py @@ -43,14 +43,8 @@ def get_mail_company(self): class BagSettings(models.Model): - semester = models.OneToOneField( - Semester, - on_delete=models.CASCADE, - ) - bag_count = models.PositiveSmallIntegerField( - verbose_name=_("Total amount of Bags"), - default=0, - ) + semester = models.OneToOneField(Semester, on_delete=models.CASCADE) + bag_count = models.PositiveSmallIntegerField(verbose_name=_("Total amount of Bags"), default=0) def __str__(self) -> str: return f"Bag-Settings for {self.semester}" @@ -59,22 +53,11 @@ def __str__(self) -> str: class Company(models.Model): class Meta: unique_together = ("semester", "name") - permissions = ( - ( - "view_companies", - "Can view and edit the companies", - ), - ) + permissions = (("view_companies", "Can view and edit the companies"),) - semester = models.ForeignKey( - Semester, - on_delete=models.CASCADE, - ) + semester = models.ForeignKey(Semester,on_delete=models.CASCADE) - name = models.CharField( - _("Name"), - max_length=200, - ) + name = models.CharField(_("Name"), max_length=200) contact_gender = models.CharField( _("Contact person (Gender)"), @@ -82,50 +65,18 @@ class Meta: choices=(("Herr", _("Herr")), ("Frau", _("Frau"))), blank=True, ) + contact_firstname = models.CharField(_("Contact person (First Name)"), max_length=200, blank=True) + contact_lastname = models.CharField(_("Contact person (Last Name)"), max_length=200, blank=True) - contact_firstname = models.CharField( - _("Contact person (First Name)"), - max_length=200, - blank=True, - ) + email = models.EmailField(_("Email address")) + email_sent = models.BooleanField(_("Email sent")) + email_sent_success = models.BooleanField(_("Email successfully sent")) - contact_lastname = models.CharField( - _("Contact person (Last Name)"), - max_length=200, - blank=True, - ) + promise = models.BooleanField(_("Promise"), null=True) + last_year = models.BooleanField(_("Participated last year")) + contact_again = models.BooleanField(_("Contact again"), null=True) - email = models.EmailField( - _("Email address"), - ) - - email_sent = models.BooleanField( - _("Email sent"), - ) - - email_sent_success = models.BooleanField( - _("Email successfully sent"), - ) - - promise = models.BooleanField( - _("Promise"), - null=True, - ) - - comment = models.CharField( - _("Comment"), - max_length=200, - blank=True, - ) - - last_year = models.BooleanField( - _("Participated last year"), - ) - - contact_again = models.BooleanField( - _("Contact again"), - null=True, - ) + comment = models.CharField(_("Comment"), max_length=200, blank=True) def __str__(self) -> str: return str(self.name) @@ -197,10 +148,7 @@ def custom_per_group_message(self): class Giveaway(models.Model): - company = models.ForeignKey( - Company, - on_delete=models.CASCADE, - ) + company = models.ForeignKey(Company, on_delete=models.CASCADE) group = models.ForeignKey( GiveawayGroup, verbose_name=_("Giveaway-title/group/tag"), @@ -208,27 +156,12 @@ class Giveaway(models.Model): null=True, ) - comment = models.CharField( - _("Giveaway-description/comment"), - blank=True, - max_length=200, - ) + item_count = models.PositiveSmallIntegerField(verbose_name=_("Item Count"), default=0) - item_count = models.PositiveSmallIntegerField( - verbose_name=_("Item Count"), - default=0, - ) + arrival_time = models.CharField(_("Arrival time"), max_length=200, blank=True) + arrived = models.BooleanField(_("Arrived"), default=False) - arrival_time = models.CharField( - _("Arrival time"), - max_length=200, - blank=True, - ) - - arrived = models.BooleanField( - _("Arrived"), - default=False, - ) + comment = models.CharField(_("Giveaway-description/comment"), blank=True, max_length=200) @property def custom_per_bag_message(self): diff --git a/bags/views.py b/bags/views.py index 45be9e24..e5147b26 100644 --- a/bags/views.py +++ b/bags/views.py @@ -408,7 +408,7 @@ def dashboard(request: WSGIRequest) -> HttpResponse: semester: Semester = get_object_or_404(Semester, pk=get_semester(request)) companies: QuerySet[Company] = Company.objects.filter(semester=semester) - g_companies: QuerySet[Company] = Company.objects.filter(giveaway__isnull=False, semester=semester) + g_companies: QuerySet[Company] = companies.filter(giveaway__isnull=False) giveaways: QuerySet[Giveaway] = Giveaway.objects.filter(company__semester=semester) g_by_group = list(giveaways.filter(group__isnull=False).values("group").annotate(group_count=Count("group"))) diff --git a/fahrt/models.py b/fahrt/models.py index 595d8584..a6f01343 100644 --- a/fahrt/models.py +++ b/fahrt/models.py @@ -61,22 +61,11 @@ def get_mail_participant(self): class Fahrt(models.Model): - semester = models.OneToOneField( - Semester, - on_delete=models.CASCADE, - ) - - date = models.DateField( - _("Date"), - ) + semester = models.OneToOneField(Semester, on_delete=models.CASCADE) - open_registration = models.DateTimeField( - _("Open registration"), - ) - - close_registration = models.DateTimeField( - _("Close registration"), - ) + date = models.DateField(_("Date")) + open_registration = models.DateTimeField(_("Open registration")) + close_registration = models.DateTimeField(_("Close registration")) mail_registration = models.ForeignKey( FahrtMail, @@ -158,11 +147,7 @@ class Transportation(models.Model): blank=True, ) - deparure_place = models.CharField( - _("The place we will start our trip"), - max_length=100, - blank=True, - ) + departure_place = models.CharField(_("The place we will start our trip"), max_length=100, blank=True) places = models.PositiveSmallIntegerField( _("Number of people (totally) for this mode of transport"), @@ -187,11 +172,7 @@ class Meta: uuid = models.UUIDField(unique=True, default=uuid.uuid4) registration_time = models.DateTimeField(_("Registration time"), auto_now_add=True) - semester = models.ForeignKey( - Semester, - on_delete=models.CASCADE, - related_name="fahrt_participant", - ) + semester = models.ForeignKey(Semester, on_delete=models.CASCADE, related_name="fahrt_participant") GENDER_CHOICES = ( ("male", _("male")), @@ -219,12 +200,7 @@ class Meta: nutrition = models.CharField(_("Nutrition"), max_length=200, choices=NUTRITION_CHOICES) allergies = models.CharField(_("Allergies"), max_length=200, blank=True) - transportation = models.ForeignKey( - Transportation, - on_delete=models.SET_NULL, - blank=True, - null=True, - ) + transportation = models.ForeignKey(Transportation, on_delete=models.SET_NULL, blank=True, null=True) publish_contact_to_other_paricipants = models.BooleanField( _("Publish your most relevant (mobile > phone > email), contact-info to other Fahrt-participants."), default=False, diff --git a/guidedtours/locale/de/LC_MESSAGES/django.po b/guidedtours/locale/de/LC_MESSAGES/django.po index c301caf6..63e4f4c7 100644 --- a/guidedtours/locale/de/LC_MESSAGES/django.po +++ b/guidedtours/locale/de/LC_MESSAGES/django.po @@ -67,6 +67,11 @@ msgstr "Beschreibung" msgid "Date" msgstr "Datum" +#: guidedtours/models.py guidedtours/templates/guidedtours/tour_dashboard.html +#: guidedtours/templates/guidedtours/tours/list_tours.html +msgid "Capacity" +msgstr "Kapazität" + #: guidedtours/models.py msgid "" "How long (minutes) a Participant should be blocked after a Tour (additonal " @@ -75,11 +80,6 @@ msgstr "" "Wie lang (Minuten) ein Teilnehmer nach dem beginn einer Tour (zusätzlich zu " "der 30Minuten Leadup-Blacklist-Zeit) blockert wird" -#: guidedtours/models.py guidedtours/templates/guidedtours/tour_dashboard.html -#: guidedtours/templates/guidedtours/tours/list_tours.html -msgid "Capacity" -msgstr "Kapazität" - #: guidedtours/models.py msgid "Open registration" msgstr "Anmeldungsstart" diff --git a/guidedtours/models.py b/guidedtours/models.py index 24cd298b..b6e8c05c 100644 --- a/guidedtours/models.py +++ b/guidedtours/models.py @@ -59,26 +59,13 @@ class Meta: ), ) - semester = models.ForeignKey( - Semester, - on_delete=models.CASCADE, - ) - - name = models.CharField( - max_length=200, - verbose_name=_("Name"), - ) - - description = models.TextField( - null=True, - blank=True, - verbose_name=_("Description"), - ) + semester = models.ForeignKey(Semester, on_delete=models.CASCADE) + name = models.CharField(max_length=200, verbose_name=_("Name")) + description = models.TextField(null=True, blank=True, verbose_name=_("Description")) - date = models.DateTimeField( - verbose_name=_("Date"), - ) + date = models.DateTimeField(verbose_name=_("Date")) + capacity = models.PositiveIntegerField(verbose_name=_("Capacity")) length = models.IntegerField( verbose_name=_( "How long (minutes) a Participant should be blocked after a Tour (additonal to 30min " @@ -87,17 +74,8 @@ class Meta: default=0, ) - capacity = models.PositiveIntegerField( - verbose_name=_("Capacity"), - ) - - open_registration = models.DateTimeField( - _("Open registration"), - ) - - close_registration = models.DateTimeField( - _("Close registration"), - ) + open_registration = models.DateTimeField(_("Open registration")) + close_registration = models.DateTimeField(_("Close registration")) def __str__(self) -> str: return self.name @@ -136,10 +114,7 @@ def status(self): class Setting(models.Model): - semester = models.OneToOneField( - Semester, - on_delete=models.CASCADE, - ) + semester = models.OneToOneField(Semester, on_delete=models.CASCADE) mail_registration = models.ForeignKey( TourMail, diff --git a/guidedtours/views.py b/guidedtours/views.py index a0a60fc4..ed5488fb 100644 --- a/guidedtours/views.py +++ b/guidedtours/views.py @@ -77,10 +77,7 @@ def view_tour(request: WSGIRequest, tour_pk: int) -> HttpResponse: def add_tour(request: WSGIRequest) -> HttpResponse: semester: Semester = get_object_or_404(Semester, pk=get_semester(request)) - form = TourForm( - request.POST or None, - semester=semester, - ) + form = TourForm(request.POST or None, semester=semester) if form.is_valid(): form.save() @@ -134,14 +131,9 @@ def del_tour(request: WSGIRequest, tour_pk: int) -> HttpResponse: @permission_required("guidedtours.view_participants") def filter_participants(request: WSGIRequest) -> HttpResponse: semester: Semester = get_object_or_404(Semester, pk=get_semester(request)) - participants: QuerySet[Participant] = Participant.objects.filter( - tour__semester=semester.id, - ).order_by("surname") + participants: QuerySet[Participant] = Participant.objects.filter(tour__semester=semester).order_by("surname") - filterform = FilterParticipantsForm( - request.POST or None, - semester=semester, - ) + filterform = FilterParticipantsForm(request.POST or None, semester=semester) if filterform.is_valid(): search: str = filterform.cleaned_data["search"] diff --git a/settool_common/models.py b/settool_common/models.py index ad250ebd..118f6e76 100644 --- a/settool_common/models.py +++ b/settool_common/models.py @@ -44,12 +44,7 @@ class Mail(models.Model): def check_perm(cls, user): return any(user.has_perm(perm) for perm in cls.required_perm) - sender = models.CharField( - max_length=100, - choices=FROM_CHOICES, - default=SET, - verbose_name=_("From"), - ) + sender = models.CharField(max_length=100, choices=FROM_CHOICES, default=SET, verbose_name=_("From")) subject = models.CharField( _("Email subject"), @@ -57,17 +52,9 @@ def check_perm(cls, user): help_text=_("You may use placeholders for the subject."), ) - text = models.TextField( - _("Text"), - help_text=_("You may use placeholders for the text."), - ) + text = models.TextField(_("Text"), help_text=_("You may use placeholders for the text.")) - comment = models.CharField( - _("Comment"), - max_length=200, - default="", - blank=True, - ) + comment = models.CharField(_("Comment"), max_length=200, default="", blank=True) def __str__(self) -> str: if self.comment: @@ -146,10 +133,9 @@ class Meta: verbose_name=_("Semester"), ) - year = models.PositiveIntegerField( - verbose_name=_("Year"), - ) + year = models.PositiveIntegerField(verbose_name=_("Year")) + @property def short_form(self) -> str: return f"{self.semester}{str(self.year)[2:]}" @@ -179,11 +165,7 @@ class Meta: BACHELOR = "BA" MASTER = "MA" - course_bundle = models.ForeignKey( - CourseBundle, - on_delete=models.CASCADE, - verbose_name=_("Course-bundle"), - ) + course_bundle = models.ForeignKey(CourseBundle, on_delete=models.CASCADE, verbose_name=_("Course-bundle")) degree = models.CharField( max_length=2, @@ -195,10 +177,7 @@ class Meta: verbose_name=_("Degree"), ) - subject = models.CharField( - max_length=100, - verbose_name=_("Subject"), - ) + subject = models.CharField(max_length=100, verbose_name=_("Subject")) def __str__(self) -> str: return f"{self.get_degree_display()} {self.subject} ({self.course_bundle})" diff --git a/tutors/forms.py b/tutors/forms.py index 109696f2..ea75a6fb 100644 --- a/tutors/forms.py +++ b/tutors/forms.py @@ -32,16 +32,13 @@ def clean(self): super().clean() mail = self.cleaned_data.get("email") if mail: - tutors = Tutor.objects.filter(email=mail, semester=self.semester) + tutors = Tutor.objects.filter(semester=self.semester, email=mail) if tutors.count() > 0 and tutors.first().id != self.instance.id: self.add_error("email", _("The email address is already in use.")) matriculation_number = self.cleaned_data.get("matriculation_number") if matriculation_number: - tutors = Tutor.objects.filter( - matriculation_number=matriculation_number, - semester=self.semester, - ) + tutors = Tutor.objects.filter(semester=self.semester, matriculation_number=matriculation_number) if tutors.count() > 0 and tutors.first().id != self.instance.id: self.add_error( "matriculation_number", From 3e544d6e8bbc4f6ebc48e2430acd42b6f6777428 Mon Sep 17 00:00:00 2001 From: Frank Elsinga Date: Mon, 4 Apr 2022 23:01:05 +0200 Subject: [PATCH 02/30] generified the logging and added a modification log to every db-object --- ...ated_at_bagsettings_updated_at_and_more.py | 58 ++++++++++++++ bags/models.py | 8 +- ...transportation_departure_place_and_more.py | 53 ++++++++++++ fahrt/models.py | 10 +-- ...ated_at_participant_updated_at_and_more.py | 47 +++++++++++ guidedtours/models.py | 6 +- ...17_anonymisationlog_created_at_and_more.py | 80 +++++++++++++++++++ settool_common/models.py | 21 +++-- tutors/models.py | 26 +++--- 9 files changed, 274 insertions(+), 35 deletions(-) create mode 100644 bags/migrations/0026_bagsettings_created_at_bagsettings_updated_at_and_more.py create mode 100644 fahrt/migrations/0031_rename_deparure_place_transportation_departure_place_and_more.py create mode 100644 guidedtours/migrations/0020_participant_created_at_participant_updated_at_and_more.py create mode 100644 settool_common/migrations/0017_anonymisationlog_created_at_and_more.py diff --git a/bags/migrations/0026_bagsettings_created_at_bagsettings_updated_at_and_more.py b/bags/migrations/0026_bagsettings_created_at_bagsettings_updated_at_and_more.py new file mode 100644 index 00000000..052e7441 --- /dev/null +++ b/bags/migrations/0026_bagsettings_created_at_bagsettings_updated_at_and_more.py @@ -0,0 +1,58 @@ +# Generated by Django 4.0.3 on 2022-04-01 13:10 + +import django.utils.timezone +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("bags", "0025_auto_20220321_1902"), + ] + + operations = [ + migrations.AddField( + model_name="bagsettings", + name="created_at", + field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), + preserve_default=False, + ), + migrations.AddField( + model_name="bagsettings", + name="updated_at", + field=models.DateTimeField(auto_now=True), + ), + migrations.AddField( + model_name="company", + name="created_at", + field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), + preserve_default=False, + ), + migrations.AddField( + model_name="company", + name="updated_at", + field=models.DateTimeField(auto_now=True), + ), + migrations.AddField( + model_name="giveaway", + name="created_at", + field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), + preserve_default=False, + ), + migrations.AddField( + model_name="giveaway", + name="updated_at", + field=models.DateTimeField(auto_now=True), + ), + migrations.AddField( + model_name="giveawaygroup", + name="created_at", + field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), + preserve_default=False, + ), + migrations.AddField( + model_name="giveawaygroup", + name="updated_at", + field=models.DateTimeField(auto_now=True), + ), + ] diff --git a/bags/models.py b/bags/models.py index cc0fff00..19fc2b1d 100644 --- a/bags/models.py +++ b/bags/models.py @@ -42,7 +42,7 @@ def get_mail_company(self): return self.get_mail(context) -class BagSettings(models.Model): +class BagSettings(common_models.LoggedModelBase): semester = models.OneToOneField(Semester, on_delete=models.CASCADE) bag_count = models.PositiveSmallIntegerField(verbose_name=_("Total amount of Bags"), default=0) @@ -50,7 +50,7 @@ def __str__(self) -> str: return f"Bag-Settings for {self.semester}" -class Company(models.Model): +class Company(common_models.LoggedModelBase): class Meta: unique_together = ("semester", "name") permissions = (("view_companies", "Can view and edit the companies"),) @@ -106,7 +106,7 @@ def contact_name(self): return f"{self.contact_firstname} {self.contact_lastname}" -class GiveawayGroup(models.Model): +class GiveawayGroup(common_models.LoggedModelBase): class Meta: unique_together = (("semester", "name"),) @@ -147,7 +147,7 @@ def custom_per_group_message(self): return _("{per_bag_count} every bag").format(per_bag_count=round(total_items / total_bags, 1)) -class Giveaway(models.Model): +class Giveaway(common_models.LoggedModelBase): company = models.ForeignKey(Company, on_delete=models.CASCADE) group = models.ForeignKey( GiveawayGroup, diff --git a/fahrt/migrations/0031_rename_deparure_place_transportation_departure_place_and_more.py b/fahrt/migrations/0031_rename_deparure_place_transportation_departure_place_and_more.py new file mode 100644 index 00000000..a49d90de --- /dev/null +++ b/fahrt/migrations/0031_rename_deparure_place_transportation_departure_place_and_more.py @@ -0,0 +1,53 @@ +# Generated by Django 4.0.3 on 2022-04-01 13:10 + +import django.utils.timezone +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("fahrt", "0030_auto_20220321_1911"), + ] + + operations = [ + migrations.AddField( + model_name="fahrt", + name="created_at", + field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), + preserve_default=False, + ), + migrations.AddField(model_name="fahrt", name="updated_at", field=models.DateTimeField(auto_now=True)), + migrations.AddField( + model_name="logentry", + name="created_at", + field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), + preserve_default=False, + ), + migrations.AddField(model_name="logentry", name="updated_at", field=models.DateTimeField(auto_now=True)), + migrations.AddField( + model_name="participant", + name="created_at", + field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), + preserve_default=False, + ), + migrations.AddField(model_name="participant", name="updated_at", field=models.DateTimeField(auto_now=True)), + migrations.AddField( + model_name="transportation", + name="created_at", + field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), + preserve_default=False, + ), + migrations.AddField(model_name="transportation", name="updated_at", field=models.DateTimeField(auto_now=True)), + migrations.AddField( + model_name="transportationcomment", + name="created_at", + field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), + preserve_default=False, + ), + migrations.AddField( + model_name="transportationcomment", + name="updated_at", + field=models.DateTimeField(auto_now=True), + ), + ] diff --git a/fahrt/models.py b/fahrt/models.py index a6f01343..e47a5b3f 100644 --- a/fahrt/models.py +++ b/fahrt/models.py @@ -60,7 +60,7 @@ def get_mail_participant(self): return self.get_mail(context) -class Fahrt(models.Model): +class Fahrt(common_models.LoggedModelBase): semester = models.OneToOneField(Semester, on_delete=models.CASCADE) date = models.DateField(_("Date")) @@ -114,7 +114,7 @@ def registration_open(self): return self.open_registration < timezone.now() < self.close_registration -class Transportation(models.Model): +class Transportation(common_models.LoggedModelBase): CAR = 0 TRAIN = 1 @@ -161,7 +161,7 @@ def __str__(self) -> str: return _("Car ({free_places} free)").format(free_places=free_places) -class Participant(models.Model): +class Participant(common_models.LoggedModelBase): class Meta: permissions = ( ( @@ -267,7 +267,7 @@ def toggle_mailinglist(self) -> None: # self.payment_deadline = deadline.strftime("%d.%m.%Y") -class TransportationComment(models.Model): +class TransportationComment(common_models.LoggedModelBase): sender = models.ForeignKey(Participant, on_delete=models.CASCADE) commented_on = models.ForeignKey(Transportation, on_delete=models.CASCADE) comment_content = models.CharField(_("Text"), max_length=200) @@ -284,7 +284,7 @@ def __str__(self) -> str: ) -class LogEntry(models.Model): +class LogEntry(common_models.LoggedModelBase): time = models.DateTimeField(_("Time"), auto_now_add=True) participant = models.ForeignKey(Participant, on_delete=models.CASCADE) text = models.CharField(_("Text"), max_length=200) diff --git a/guidedtours/migrations/0020_participant_created_at_participant_updated_at_and_more.py b/guidedtours/migrations/0020_participant_created_at_participant_updated_at_and_more.py new file mode 100644 index 00000000..f0cfde4d --- /dev/null +++ b/guidedtours/migrations/0020_participant_created_at_participant_updated_at_and_more.py @@ -0,0 +1,47 @@ +# Generated by Django 4.0.3 on 2022-04-01 13:10 + +import django.utils.timezone +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("guidedtours", "0019_auto_20220321_1913"), + ] + + operations = [ + migrations.AddField( + model_name="participant", + name="created_at", + field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), + preserve_default=False, + ), + migrations.AddField( + model_name="participant", + name="updated_at", + field=models.DateTimeField(auto_now=True), + ), + migrations.AddField( + model_name="setting", + name="created_at", + field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), + preserve_default=False, + ), + migrations.AddField( + model_name="setting", + name="updated_at", + field=models.DateTimeField(auto_now=True), + ), + migrations.AddField( + model_name="tour", + name="created_at", + field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), + preserve_default=False, + ), + migrations.AddField( + model_name="tour", + name="updated_at", + field=models.DateTimeField(auto_now=True), + ), + ] diff --git a/guidedtours/models.py b/guidedtours/models.py index b6e8c05c..e16d1270 100644 --- a/guidedtours/models.py +++ b/guidedtours/models.py @@ -50,7 +50,7 @@ def send_mail_participant(self, participant): return self.send_mail(context, participant.email) -class Tour(models.Model): +class Tour(common_models.LoggedModelBase): class Meta: permissions = ( ( @@ -85,7 +85,7 @@ def registration_open(self): return self.open_registration < timezone.now() < self.close_registration -class Participant(models.Model): +class Participant(common_models.LoggedModelBase): class Meta: unique_together = ("tour", "email") @@ -113,7 +113,7 @@ def status(self): return _("On waitinglist") -class Setting(models.Model): +class Setting(common_models.LoggedModelBase): semester = models.OneToOneField(Semester, on_delete=models.CASCADE) mail_registration = models.ForeignKey( diff --git a/settool_common/migrations/0017_anonymisationlog_created_at_and_more.py b/settool_common/migrations/0017_anonymisationlog_created_at_and_more.py new file mode 100644 index 00000000..da533976 --- /dev/null +++ b/settool_common/migrations/0017_anonymisationlog_created_at_and_more.py @@ -0,0 +1,80 @@ +# Generated by Django 4.0.3 on 2022-04-01 13:10 + +import django.utils.timezone +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("settool_common", "0016_alter_coursebundle_options_alter_subject_options_and_more"), + ] + + operations = [ + migrations.AddField( + model_name="anonymisationlog", + name="created_at", + field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), + preserve_default=False, + ), + migrations.AddField( + model_name="anonymisationlog", + name="updated_at", + field=models.DateTimeField(auto_now=True), + ), + migrations.AddField( + model_name="coursebundle", + name="created_at", + field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), + preserve_default=False, + ), + migrations.AddField( + model_name="coursebundle", + name="updated_at", + field=models.DateTimeField(auto_now=True), + ), + migrations.AddField( + model_name="mail", + name="created_at", + field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), + preserve_default=False, + ), + migrations.AddField( + model_name="mail", + name="updated_at", + field=models.DateTimeField(auto_now=True), + ), + migrations.AddField( + model_name="qrcode", + name="created_at", + field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), + preserve_default=False, + ), + migrations.AddField( + model_name="qrcode", + name="updated_at", + field=models.DateTimeField(auto_now=True), + ), + migrations.AddField( + model_name="semester", + name="created_at", + field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), + preserve_default=False, + ), + migrations.AddField( + model_name="semester", + name="updated_at", + field=models.DateTimeField(auto_now=True), + ), + migrations.AddField( + model_name="subject", + name="created_at", + field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), + preserve_default=False, + ), + migrations.AddField( + model_name="subject", + name="updated_at", + field=models.DateTimeField(auto_now=True), + ), + ] diff --git a/settool_common/models.py b/settool_common/models.py index 118f6e76..8adc7bd8 100644 --- a/settool_common/models.py +++ b/settool_common/models.py @@ -20,7 +20,16 @@ from .settings import SEMESTER_SESSION_KEY -class Mail(models.Model): + +class LoggedModelBase(models.Model): + class Meta: + abstract = True + + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + +class Mail(LoggedModelBase): SET = "SET-Team " SET_FAHRT = "SET-Fahrt-Team " SET_TUTOR = "SET-Tutor-Team " @@ -114,7 +123,7 @@ def clean_attachable(response: Union[HttpResponse, tuple[str, Any, str]]) -> tup @deconstructible -class Semester(models.Model): +class Semester(LoggedModelBase): class Meta: unique_together = (("semester", "year"),) ordering = ["year", "semester"] @@ -143,7 +152,7 @@ def __str__(self) -> str: return f"{self.get_semester_display()} {self.year:4}" -class CourseBundle(models.Model): +class CourseBundle(LoggedModelBase): class Meta: ordering = ["name"] @@ -157,7 +166,7 @@ def __str__(self) -> str: return self.name -class Subject(models.Model): +class Subject(LoggedModelBase): class Meta: unique_together = (("degree", "subject"),) ordering = ["degree", "course_bundle", "subject"] @@ -206,7 +215,7 @@ def get_semester(request: HttpRequest) -> int: return sem # noqa: R504 -class QRCode(models.Model): +class QRCode(LoggedModelBase): content = models.CharField(max_length=200, unique=True) qr_code = models.ImageField(upload_to="qr_codes", blank=True) @@ -265,7 +274,7 @@ def auto_delete_qr_code_on_delete(sender, instance, **_kwargs): os.remove(instance.qr_code.path) -class AnonymisationLog(models.Model): +class AnonymisationLog(LoggedModelBase): semester = models.ForeignKey(Semester, on_delete=models.CASCADE) anon_log_str = models.CharField(max_length=10) diff --git a/tutors/models.py b/tutors/models.py index 38071e24..a008ec23 100644 --- a/tutors/models.py +++ b/tutors/models.py @@ -58,15 +58,7 @@ def get_mail_task(self, tutor, task): return self.get_mail(context) -class BaseModel(models.Model): - created_at = models.DateTimeField(auto_now_add=True) - updated_at = models.DateTimeField(auto_now=True) - - class Meta: - abstract = True - - -class Settings(BaseModel): +class Settings(common_models.LoggedModelBase): semester = models.OneToOneField(Semester, on_delete=models.CASCADE) open_registration = models.DateTimeField(_("Open registration")) @@ -138,7 +130,7 @@ def log(self, user, text): pass -class Tutor(BaseModel): +class Tutor(common_models.LoggedModelBase): class Meta: unique_together = ("semester", "email") @@ -212,7 +204,7 @@ def log(self, user, text): pass -class Event(BaseModel): +class Event(common_models.LoggedModelBase): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) semester = models.ForeignKey(Semester, verbose_name=_("Semester"), on_delete=models.CASCADE) @@ -236,7 +228,7 @@ def __str__(self) -> str: return f"{self.name} {self.begin.date()}" -class Task(BaseModel): +class Task(common_models.LoggedModelBase): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) semester = models.ForeignKey(Semester, verbose_name=_("Semester"), on_delete=models.CASCADE) @@ -266,7 +258,7 @@ def log(self, user, text): pass -class TutorAssignment(BaseModel): +class TutorAssignment(common_models.LoggedModelBase): tutor = models.ForeignKey(Tutor, on_delete=models.CASCADE) task = models.ForeignKey(Task, on_delete=models.CASCADE) absent = models.BooleanField(_("absent"), default=False) @@ -275,7 +267,7 @@ def __str__(self) -> str: return f"{self.tutor}" -class Question(BaseModel): +class Question(common_models.LoggedModelBase): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) semester = models.ForeignKey(Semester, verbose_name=_("Semester"), on_delete=models.CASCADE) @@ -293,7 +285,7 @@ def log(self, user, text): pass -class Answer(BaseModel): +class Answer(common_models.LoggedModelBase): class Meta: unique_together = ("tutor", "question") @@ -314,7 +306,7 @@ def __str__(self) -> str: return f"{self.tutor}: {self.question} -> {self.answer}" -class MailTutorTask(BaseModel): +class MailTutorTask(common_models.LoggedModelBase): mail = models.ForeignKey(TutorMail, on_delete=models.CASCADE) tutor = models.ForeignKey(Tutor, on_delete=models.CASCADE) task = models.ForeignKey(Task, on_delete=models.CASCADE, blank=True, null=True) @@ -323,7 +315,7 @@ def __str__(self) -> str: return f"{self.created_at}: {self.tutor} -> {self.mail} - {self.task}" -class SubjectTutorCountAssignment(BaseModel): +class SubjectTutorCountAssignment(common_models.LoggedModelBase): semester = models.ForeignKey(Semester, on_delete=models.CASCADE) subject = models.ForeignKey(Subject, on_delete=models.CASCADE) wanted = models.PositiveIntegerField(default=0, null=True, blank=True) From f36cc7687c3c28527444d9af686ecb1b6b529f8d Mon Sep 17 00:00:00 2001 From: Frank Elsinga Date: Mon, 4 Apr 2022 23:04:01 +0200 Subject: [PATCH 03/30] fixed typo: deparure_place is spelled with a t --- ...place_transportation_departure_place_and_more.py | 13 +++++++++++++ .../transportation/management/list_transports.html | 2 +- settool_common/fixtures/showroom_fixture.py | 2 +- 3 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 fahrt/migrations/0032_rename_deparure_place_transportation_departure_place_and_more.py diff --git a/fahrt/migrations/0032_rename_deparure_place_transportation_departure_place_and_more.py b/fahrt/migrations/0032_rename_deparure_place_transportation_departure_place_and_more.py new file mode 100644 index 00000000..755f2d97 --- /dev/null +++ b/fahrt/migrations/0032_rename_deparure_place_transportation_departure_place_and_more.py @@ -0,0 +1,13 @@ +# Generated by Django 4.0.3 on 2022-04-01 16:07 + +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("fahrt", "0031_rename_deparure_place_transportation_departure_place_and_more"), + ] + + operations = [ + migrations.RenameField(model_name="transportation", old_name="deparure_place", new_name="departure_place"), + ] diff --git a/fahrt/templates/fahrt/transportation/management/list_transports.html b/fahrt/templates/fahrt/transportation/management/list_transports.html index 92179494..02590fd6 100644 --- a/fahrt/templates/fahrt/transportation/management/list_transports.html +++ b/fahrt/templates/fahrt/transportation/management/list_transports.html @@ -70,7 +70,7 @@

{{ transport_name_pl }}

{% trans "Meeting-Place:" %} - {{ transport.deparure_place|default:"n.A." }} + {{ transport.departure_place|default:"n.A." }} {% trans "Departure:" %} diff --git a/settool_common/fixtures/showroom_fixture.py b/settool_common/fixtures/showroom_fixture.py index d7576564..e42b7730 100644 --- a/settool_common/fixtures/showroom_fixture.py +++ b/settool_common/fixtures/showroom_fixture.py @@ -193,7 +193,7 @@ def _generate_transportation(fahrt_participants): return_departure_time=random.choice( [participant.semester.fahrt.date + timedelta(hours=random.randint(-5, 5)), None], ), - deparure_place=random.choice([lorem.sentence(), "", ""]), + departure_place=random.choice([lorem.sentence(), "", ""]), places=places_count, ) participant.transportation = trans From 7d3d8f0ec7afd07038ff08f24d415eb256071910 Mon Sep 17 00:00:00 2001 From: Frank Elsinga Date: Mon, 4 Apr 2022 23:13:58 +0200 Subject: [PATCH 04/30] generified Semesters and ids (except fahrt.participant) --- ...y_semester_alter_giveawaygroup_semester.py | 33 ++++++++ bags/models.py | 18 +--- fahrt/forms.py | 2 +- fahrt/models.py | 3 +- fahrt/views/maintinance_views.py | 4 +- fahrt/views/participants_views.py | 16 ++-- fahrt/views/tex_views.py | 2 +- .../migrations/0021_alter_tour_semester.py | 24 ++++++ guidedtours/models.py | 4 +- settool_common/cron.py | 8 +- .../locale/de/LC_MESSAGES/django.po | 8 +- .../0018_alter_anonymisationlog_semester.py | 23 ++++++ settool_common/models.py | 17 +++- tutors/locale/de/LC_MESSAGES/django.mo | Bin 15799 -> 15765 bytes tutors/locale/de/LC_MESSAGES/django.po | 4 - ..._event_id_alter_event_semester_and_more.py | 77 ++++++++++++++++++ ...mester_alter_question_semester_and_more.py | 60 ++++++++++++++ tutors/models.py | 26 ++---- 18 files changed, 262 insertions(+), 67 deletions(-) create mode 100644 bags/migrations/0027_alter_company_semester_alter_giveawaygroup_semester.py create mode 100644 guidedtours/migrations/0021_alter_tour_semester.py create mode 100644 settool_common/migrations/0018_alter_anonymisationlog_semester.py create mode 100644 tutors/migrations/0009_alter_event_id_alter_event_semester_and_more.py create mode 100644 tutors/migrations/0010_alter_event_semester_alter_question_semester_and_more.py diff --git a/bags/migrations/0027_alter_company_semester_alter_giveawaygroup_semester.py b/bags/migrations/0027_alter_company_semester_alter_giveawaygroup_semester.py new file mode 100644 index 00000000..651c6ae4 --- /dev/null +++ b/bags/migrations/0027_alter_company_semester_alter_giveawaygroup_semester.py @@ -0,0 +1,33 @@ +# Generated by Django 4.0.3 on 2022-04-01 16:16 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("settool_common", "0018_alter_anonymisationlog_semester"), + ("bags", "0026_bagsettings_created_at_bagsettings_updated_at_and_more"), + ] + + operations = [ + migrations.AlterField( + model_name="company", + name="semester", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="settool_common.semester", + verbose_name="Semester", + ), + ), + migrations.AlterField( + model_name="giveawaygroup", + name="semester", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="settool_common.semester", + verbose_name="Semester", + ), + ), + ] diff --git a/bags/models.py b/bags/models.py index 19fc2b1d..782c9d41 100644 --- a/bags/models.py +++ b/bags/models.py @@ -50,13 +50,10 @@ def __str__(self) -> str: return f"Bag-Settings for {self.semester}" -class Company(common_models.LoggedModelBase): +class Company(common_models.LoggedModelBase, common_models.SemesterModelBase): class Meta: unique_together = ("semester", "name") permissions = (("view_companies", "Can view and edit the companies"),) - - semester = models.ForeignKey(Semester,on_delete=models.CASCADE) - name = models.CharField(_("Name"), max_length=200) contact_gender = models.CharField( @@ -106,19 +103,10 @@ def contact_name(self): return f"{self.contact_firstname} {self.contact_lastname}" -class GiveawayGroup(common_models.LoggedModelBase): +class GiveawayGroup(common_models.LoggedModelBase, common_models.SemesterModelBase): class Meta: unique_together = (("semester", "name"),) - - semester = models.ForeignKey( - Semester, - on_delete=models.CASCADE, - ) - - name = models.CharField( - _("Giveaway-groups' name"), - max_length=200, - ) + name = models.CharField(_("Giveaway-groups' name"), max_length=200) def __str__(self) -> str: return self.name diff --git a/fahrt/forms.py b/fahrt/forms.py index 43794530..59faed0c 100644 --- a/fahrt/forms.py +++ b/fahrt/forms.py @@ -276,7 +276,7 @@ class ParticipantSelectForm(SemesterBasedForm): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.fields["selected"].queryset = self.semester.fahrt_participant.filter(status="confirmed").all() + self.fields["selected"].queryset = Participant.objects.filter(semester=self.semester, status="confirmed").all() class CSVFileUploadForm(forms.Form): diff --git a/fahrt/models.py b/fahrt/models.py index e47a5b3f..774931c5 100644 --- a/fahrt/models.py +++ b/fahrt/models.py @@ -161,7 +161,7 @@ def __str__(self) -> str: return _("Car ({free_places} free)").format(free_places=free_places) -class Participant(common_models.LoggedModelBase): +class Participant(common_models.LoggedModelBase, common_models.SemesterModelBase): class Meta: permissions = ( ( @@ -172,7 +172,6 @@ class Meta: uuid = models.UUIDField(unique=True, default=uuid.uuid4) registration_time = models.DateTimeField(_("Registration time"), auto_now_add=True) - semester = models.ForeignKey(Semester, on_delete=models.CASCADE, related_name="fahrt_participant") GENDER_CHOICES = ( ("male", _("male")), diff --git a/fahrt/views/maintinance_views.py b/fahrt/views/maintinance_views.py index c36ce1e9..989f2b0e 100644 --- a/fahrt/views/maintinance_views.py +++ b/fahrt/views/maintinance_views.py @@ -8,7 +8,7 @@ from settool_common.models import get_semester, Semester from ..forms import FahrtForm, MailForm -from ..models import Fahrt, FahrtMail +from ..models import Fahrt, FahrtMail, Participant @permission_required("fahrt.view_participants") @@ -68,7 +68,7 @@ def send_mail(request: WSGIRequest, mail_pk: int) -> HttpResponse: mail: FahrtMail = get_object_or_404(FahrtMail, pk=mail_pk) selected_participants = request.session["selected_participants"] semester: Semester = get_object_or_404(Semester, pk=get_semester(request)) - participants = semester.fahrt_participant.filter(id__in=selected_participants).order_by("surname") + participants = Participant.objects.filter(semester=semester, id__in=selected_participants).order_by("surname") subject, text, from_email = mail.get_mail_participant() diff --git a/fahrt/views/participants_views.py b/fahrt/views/participants_views.py index 55c6c4bb..a64f9c3e 100644 --- a/fahrt/views/participants_views.py +++ b/fahrt/views/participants_views.py @@ -30,7 +30,7 @@ @permission_required("fahrt.view_participants") def list_registered(request: WSGIRequest) -> HttpResponse: semester: Semester = get_object_or_404(Semester, pk=get_semester(request)) - participants = semester.fahrt_participant.filter(status="registered").order_by( + participants = Participant.objects.filter(semester=semester, status="registered").order_by( "-registration_time", ) @@ -43,7 +43,7 @@ def list_registered(request: WSGIRequest) -> HttpResponse: @permission_required("fahrt.view_participants") def list_waitinglist(request: WSGIRequest) -> HttpResponse: semester: Semester = get_object_or_404(Semester, pk=get_semester(request)) - participants = semester.fahrt_participant.filter(status="waitinglist").order_by("-registration_time") + participants = Participant.objects.filter(semester=semester, status="waitinglist").order_by("-registration_time") context = { "participants": participants, @@ -52,7 +52,7 @@ def list_waitinglist(request: WSGIRequest) -> HttpResponse: def get_possibly_filtered_participants(filterform, semester): - participants = semester.fahrt_participant.filter(status="confirmed").order_by( + participants = Participant.objects.filter(semester=semester, status="confirmed").order_by( "payment_deadline", "surname", "firstname", @@ -139,7 +139,7 @@ def get_nutritunal_information( participants: QuerySet[Participant], semester: Semester, ) -> list[dict[str, object]]: - nutritions = semester.fahrt_participant.filter(status="confirmed").values("nutrition").distinct() + nutritions = Participant.objects.filter(semester=semester, status="confirmed").values("nutrition").distinct() nutrition_choices = [choice["nutrition"] for choice in nutritions] return [ { @@ -154,7 +154,7 @@ def get_nutritunal_information( @permission_required("fahrt.view_participants") def list_cancelled(request: WSGIRequest) -> HttpResponse: semester: Semester = get_object_or_404(Semester, pk=get_semester(request)) - participants = semester.fahrt_participant.filter(status="cancelled").order_by("surname") + participants = Participant.objects.filter(semester=semester, status="cancelled").order_by("surname") context = { "participants": participants, @@ -387,7 +387,7 @@ def filter_participants(request: WSGIRequest) -> HttpResponse: except ObjectDoesNotExist: messages.error(request, _("You have to create Fahrt Settings to manage the fahrt")) return redirect("fahrt:settings") - participants = semester.fahrt_participant.order_by("surname") + participants = Participant.objects.filter(semester=semester).order_by("surname") filterform = FilterParticipantsForm(request.POST or None) if filterform.is_valid(): @@ -456,9 +456,7 @@ def set_request_session_filtered_participants( def filtered_list(request: WSGIRequest) -> HttpResponse: filtered_participants = request.session["filtered_participants"] semester: Semester = get_object_or_404(Semester, pk=get_semester(request)) - participants = semester.fahrt_participant.filter( - id__in=filtered_participants, - ).order_by("surname") + participants = Participant.objects.filter(semester=semester, id__in=filtered_participants).order_by("surname") form = SelectMailForm(request.POST or None) select_participant_form_set = formset_factory( diff --git a/fahrt/views/tex_views.py b/fahrt/views/tex_views.py index 04849477..eb0dcd2f 100644 --- a/fahrt/views/tex_views.py +++ b/fahrt/views/tex_views.py @@ -25,7 +25,7 @@ def export(request: WSGIRequest, file_format: str = "csv") -> Union[HttpResponse except ObjectDoesNotExist: messages.error(request, _("Please setup the SETtings for the Fahrt")) return redirect("fahrt:settings") - participants = semester.fahrt_participant.order_by("surname", "firstname") + participants = Participant.objects.filter(semester=semester).order_by("surname", "firstname") filename = f"fahrt_participants_{fahrt.semester}_{fahrt.date}_{time.strftime('%Y%m%d-%H%M')}" context = {"participants": participants, "fahrt": fahrt} if file_format == "csv": diff --git a/guidedtours/migrations/0021_alter_tour_semester.py b/guidedtours/migrations/0021_alter_tour_semester.py new file mode 100644 index 00000000..3259c6c1 --- /dev/null +++ b/guidedtours/migrations/0021_alter_tour_semester.py @@ -0,0 +1,24 @@ +# Generated by Django 4.0.3 on 2022-04-01 16:16 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("settool_common", "0018_alter_anonymisationlog_semester"), + ("guidedtours", "0020_participant_created_at_participant_updated_at_and_more"), + ] + + operations = [ + migrations.AlterField( + model_name="tour", + name="semester", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="settool_common.semester", + verbose_name="Semester", + ), + ), + ] diff --git a/guidedtours/models.py b/guidedtours/models.py index e16d1270..fdf72313 100644 --- a/guidedtours/models.py +++ b/guidedtours/models.py @@ -50,7 +50,7 @@ def send_mail_participant(self, participant): return self.send_mail(context, participant.email) -class Tour(common_models.LoggedModelBase): +class Tour(common_models.LoggedModelBase, common_models.SemesterModelBase): class Meta: permissions = ( ( @@ -58,8 +58,6 @@ class Meta: "Can view and edit the list of participants", ), ) - - semester = models.ForeignKey(Semester, on_delete=models.CASCADE) name = models.CharField(max_length=200, verbose_name=_("Name")) description = models.TextField(null=True, blank=True, verbose_name=_("Description")) diff --git a/settool_common/cron.py b/settool_common/cron.py index f5450884..601ddb27 100644 --- a/settool_common/cron.py +++ b/settool_common/cron.py @@ -57,7 +57,7 @@ def fahrt_date_reminder(semester: Semester, today: date) -> None: lookup_day = today + timedelta(days=max(current_fahrt.reminder_tour_days_count, 0)) if current_fahrt.date == lookup_day: participant: m_fahrt.Participant - for participant in semester.fahrt_participant.filter(Q(semester=semester) & Q(status="confirmed")): + for participant in m_fahrt.Participant.objects.filter(semester=semester, status="confirmed"): current_fahrt.mail_reminder.send_mail_participant(participant) @@ -66,8 +66,10 @@ def fahrt_payment_reminder(semester: Semester, today: date) -> None: if current_fahrt and current_fahrt.mail_payment_deadline: lookup_day = today + timedelta(days=max(current_fahrt.reminder_payment_deadline_days_count, 0)) participant: m_fahrt.Participant - for participant in semester.fahrt_participant.filter( - Q(status="confirmed") & Q(payment_deadline=lookup_day), + for participant in m_fahrt.Participant.objects.filter( + semester=semester, + status="confirmed", + payment_deadline=lookup_day, ): current_fahrt.mail_payment_deadline.send_mail_participant(participant) diff --git a/settool_common/locale/de/LC_MESSAGES/django.po b/settool_common/locale/de/LC_MESSAGES/django.po index 1047c0af..fb63e31b 100644 --- a/settool_common/locale/de/LC_MESSAGES/django.po +++ b/settool_common/locale/de/LC_MESSAGES/django.po @@ -24,6 +24,10 @@ msgstr "" msgid "I accept the terms and conditions of the following privacy policy:" msgstr "Ich stimme der folgenden Datenschutzerklärung zu:" +#: settool_common/models.py +msgid "Semester" +msgstr "Semester" + #: settool_common/models.py msgid "SET" msgstr "SET" @@ -84,10 +88,6 @@ msgstr "Wintersemester" msgid "Summer semester" msgstr "Sommersemester" -#: settool_common/models.py -msgid "Semester" -msgstr "Semester" - #: settool_common/models.py msgid "Year" msgstr "Jahr" diff --git a/settool_common/migrations/0018_alter_anonymisationlog_semester.py b/settool_common/migrations/0018_alter_anonymisationlog_semester.py new file mode 100644 index 00000000..f0dd14ce --- /dev/null +++ b/settool_common/migrations/0018_alter_anonymisationlog_semester.py @@ -0,0 +1,23 @@ +# Generated by Django 4.0.3 on 2022-04-01 16:16 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("settool_common", "0017_anonymisationlog_created_at_and_more"), + ] + + operations = [ + migrations.AlterField( + model_name="anonymisationlog", + name="semester", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="settool_common.semester", + verbose_name="Semester", + ), + ), + ] diff --git a/settool_common/models.py b/settool_common/models.py index 8adc7bd8..817b5aff 100644 --- a/settool_common/models.py +++ b/settool_common/models.py @@ -1,6 +1,7 @@ import datetime import os import re +import uuid from io import BytesIO from typing import Any, List, Optional, Union @@ -20,6 +21,19 @@ from .settings import SEMESTER_SESSION_KEY +class UUIDModelBase(models.Model): + class Meta: + abstract = True + + id = models.UUIDField(unique=True, primary_key=True, default=uuid.uuid4) + + +class SemesterModelBase(models.Model): + class Meta: + abstract = True + + semester = models.ForeignKey("settool_common.Semester", verbose_name=_("Semester"), on_delete=models.CASCADE) + class LoggedModelBase(models.Model): class Meta: @@ -274,8 +288,7 @@ def auto_delete_qr_code_on_delete(sender, instance, **_kwargs): os.remove(instance.qr_code.path) -class AnonymisationLog(LoggedModelBase): - semester = models.ForeignKey(Semester, on_delete=models.CASCADE) +class AnonymisationLog(LoggedModelBase, SemesterModelBase): anon_log_str = models.CharField(max_length=10) def __str__(self) -> str: diff --git a/tutors/locale/de/LC_MESSAGES/django.mo b/tutors/locale/de/LC_MESSAGES/django.mo index 2bb81b13ba5b657ae01bf7e162bc4c9f326565d3..6627ecbec9d31ef1230de868de315f85fee0718f 100644 GIT binary patch delta 3992 zcmYk;32;qU9LMp41R*agNvuJdO$0$A60s$gB8)PYl+lzl8e417SYJzR6|p?2Xlg4_ zTM>x{B}7%Hl&X$)24hKU8QP9%jn>xb_m_8cdZ+(<&N=tqv;5DwPs`^N%yC`|@F*}` zr-`P-z}m(*Y8W%uTdl_I_cO)^OVA%rpf6s;x>$w$XYTWdSIskQjJ0@J_nV`hkHk9I z*|v8_{&Sjs{H@204Ahue=!MfzFV01El!tn*05#xd)PN<{L#T-!N4aDN=zJ;E+2*YqWY6AOE10F*DGspR(4lklsb{+Nn9gM;Hff|4n z^QR#Wz+}wCUbq3h7~kBWpbqX~6Z``;pbxXrN`g@ni{y_6?23$GUdH-3+O|)?AnG%0 zdme^RFT@U5f*tV}OvMmp+?`sk#E}zQJL9_O5sj>zZ|val~@a} zqMo~fdhRwVa}QDdKeg5lBL8aeXWDw8DQY5NsP;~%j=LjCF&UVTQ&1~<~Q zsFhqq{eIlFbx%&R&Oj5?*0w}FABF0#J<^ZUB-tCOsF@D1jUYLgJa4u>O7oa*` zjhb*F#^D}RhOVI|8qBO(VGDc-Q}Gy1z;^onBbpgAh=$>)7mG0luVFIQYi`U~?1NFb z9kcK|oR9IL#6n9)bssgcbn>asYZ&Sbj7PF+X5(>OXYY>)ci+!Oectm>TbdtE{`Ep34Vv*m zjKB)iVZ4P(*#mpugPZEWfn?t_M}1Bys0j>1eFZu8{#?}aMW}xEAm`njKpn=*5#(P3 zSJ9vs@7o8TTm4Cg9t=ly6pLDMHw?i+n2Xt{3EV(We1x6w8EPSM9Gp7X6*b{B)ERiy zNkOT|Miyz_#spl49AI-E^};>ufi7jrDYxxcPlgm7zJdo`=d*v28zy zwW*&*4SWvk>HEJzfiKP6Ms?`X#+^bh)I@wyFE&M9Fk#lVsFZd=rMxFTFuTj+z)?c6I(w5FmmGYEA?hN31k3$;ZBs6E|} z`pNzpm65B+zh9;b)$bG3%DvjV@28|+z+U& zyN}-J#qX~s5@d}ctpT zNAajFOSScZs0nAF2F|hf-$C{FF8bqQ)WQopkbkXc6AdiF9K-~?jq11=KY#p)nF!?E zm;rbbbCEfk3_kK~%tO8Z5Mwc%Po*0U#c{X>b^j4gz(@ws)~|9>P)B~9-2?T){?xNk zDL;VP`){!!evew=9n^~cLhbo;R7L{$Rs*pG>g(u)YVT_8k6o#c#3*zYP>7^ZfuVRC z1F&`%_u*=U`s_NPI_!=;YVet1PwGoKQCis zL?g^m3hQaOfUPkngPN3ZYMLBeB@E#V6Uhh0_18##Bxb@7l(` zIECNR;SP8|9l2Va#|-7O3K!B)@0^A{I2ZL`4yvIIsQb2}URZ#7VX^fLs-x#o&t0|NMK8`vQSDa< zVt^Qc!Mxu@Q0PZRG`@gYSP^qE64#?TP=tEn8RXBL=R*x%M~&i);r6vKk`0!GY7 zbsT}QI0<{;9`xb;<~{{A@C3uKJiX8hBTyr0gz8v(KJ>y@k$0G3SQXQ4eI|x*zQERR zz#5$AVKf$F8+?R4F^XO}N=-TirF0T1#dGcXQq)vzwDozY%p5?a@UXpJf|~QMu>#&f z-FF{#-yf*VdC@zyU)35CLjF}zj|$z;4Aqe~_J)^G4JRPUF=@CAXQM{s7ivrg?2hGe z2CBoeP#MZbWpFLh2eSjUMvAQGLdiebFjuHhPamLCs=TS*7xm(L$oercsQcnkFB)v? zld%iulTgp?McsGGdJ&cBYe?VCO;m@TIkpf~(;ayfYK}Xi9_WpF@hhl~jlvH2CTfjr zMs;Kt>b`^464d>dQSIHtvH{w2rB&yS9|est4Ao#u)Rc6v^*vD|8iIOJI*!5VsQbT1 zE#99|Bf5?H9x2C4)_DYKEwn{VaVOOMaY%cP=|e#cCE5!kQ9VwvPO{EMbzljWZBNu( zu17Vz6E%>1*cwlxGW8hMek*!aA3NfUI1-O(|IeY&l8SEO#`MPts0WW@Q+$lE7#U$q zGA3gb7GWy>inFnQ9TqU2MZM?=s-5tV={8Tst9uW)vS9xr5pb3sKLnM|C6*otqT)Q0RyrT4{o3u@U}> zEwCbYa+jU~vemN~EX$ECZ8o4*^C67E`)<<i*8wcvMOUqEbE_)sffj z^%=-cH1kn&z6A^MICj$hpVHh-X+Boq!aYx zK59Va(32sWZW?naIDC@M4G zq3*kbnmRAOca^#N=!;EJ9qM3Bu=QhcIQ3InlYhP792GwJ1FFGWs0K^zdF^O-ByCY0 z?Sxe^4)t6=R69dZ9Z0q3<53--h3Zg_y}lMT@XgW8e;|cCD%9{1)X0jFL77X~4!!yM zQ^VbmFEjHpvVKek{)Fq0zM5HV_HmexdOn1$(j4QlD^9~PSctk_!=d#zDD*-4V)mmN zif-?|C_2fs1f?GH#MS~sJX9$%1B!b#vZ6$l4R>&vpVS%I&ui{#KfK4!`3;#@@equ`q9iMrY`o>o;qNFC@BA6$$nAqm|%{Q{* zdP;eoK)=w&yC@wZMiU`K7NLpL(3IyCLdQbS0KedVYbXT~397P(lHnmVSY=jO-%04m zby1kHKtfyZL*iY}ifXaW zW=j7*v>#YN>A`rrMe?)M`0Q46=2raX+!!J+E)( z*$ZoNA5qhu3oY4CJsJK@dlgXXMVuf~2^|Mq%mWMY};iho$e z_bBCf{`L=TKA%z=v7Cq@-Y1?Pd`XubpIcXY;sPRjE#a)ZtqH(Sh)kji(Syh*bQ~e} q5P`%NLdOJRII-fuj(|*`N*SqBrlw9l5K-%Ph<9SDJ@an8)c0R~d6xbF diff --git a/tutors/locale/de/LC_MESSAGES/django.po b/tutors/locale/de/LC_MESSAGES/django.po index 74f84095..9ff342ff 100644 --- a/tutors/locale/de/LC_MESSAGES/django.po +++ b/tutors/locale/de/LC_MESSAGES/django.po @@ -99,10 +99,6 @@ msgstr "" "Sende die Erinnerungs Email automatisch diese menge an Tagen vor dem Task " "(0=gleicher Tag)" -#: tutors/models.py -msgid "Semester" -msgstr "Semester" - #: tutors/models.py tutors/templates/tutors/task/view.html #: tutors/templates/tutors/tutor/delete.html #: tutors/templates/tutors/tutor/list.html diff --git a/tutors/migrations/0009_alter_event_id_alter_event_semester_and_more.py b/tutors/migrations/0009_alter_event_id_alter_event_semester_and_more.py new file mode 100644 index 00000000..1dcefbbf --- /dev/null +++ b/tutors/migrations/0009_alter_event_id_alter_event_semester_and_more.py @@ -0,0 +1,77 @@ +# Generated by Django 4.0.3 on 2022-04-01 16:07 + +import uuid + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("settool_common", "0017_anonymisationlog_created_at_and_more"), + ("tutors", "0008_auto_20220321_1851"), + ] + + operations = [ + migrations.AlterField( + model_name="event", + name="id", + field=models.UUIDField( + default=uuid.uuid4, + primary_key=True, + serialize=False, + unique=True, + ), + ), + migrations.AlterField( + model_name="event", + name="semester", + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="settool_common.semester"), + ), + migrations.AlterField( + model_name="question", + name="id", + field=models.UUIDField( + default=uuid.uuid4, + primary_key=True, + serialize=False, + unique=True, + ), + ), + migrations.AlterField( + model_name="question", + name="semester", + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="settool_common.semester"), + ), + migrations.AlterField( + model_name="task", + name="id", + field=models.UUIDField( + default=uuid.uuid4, + primary_key=True, + serialize=False, + unique=True, + ), + ), + migrations.AlterField( + model_name="task", + name="semester", + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="settool_common.semester"), + ), + migrations.AlterField( + model_name="tutor", + name="id", + field=models.UUIDField( + default=uuid.uuid4, + primary_key=True, + serialize=False, + unique=True, + ), + ), + migrations.AlterField( + model_name="tutor", + name="semester", + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="settool_common.semester"), + ), + ] diff --git a/tutors/migrations/0010_alter_event_semester_alter_question_semester_and_more.py b/tutors/migrations/0010_alter_event_semester_alter_question_semester_and_more.py new file mode 100644 index 00000000..70d8c22b --- /dev/null +++ b/tutors/migrations/0010_alter_event_semester_alter_question_semester_and_more.py @@ -0,0 +1,60 @@ +# Generated by Django 4.0.3 on 2022-04-01 16:16 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("settool_common", "0018_alter_anonymisationlog_semester"), + ("tutors", "0009_alter_event_id_alter_event_semester_and_more"), + ] + + operations = [ + migrations.AlterField( + model_name="event", + name="semester", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="settool_common.semester", + verbose_name="Semester", + ), + ), + migrations.AlterField( + model_name="question", + name="semester", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="settool_common.semester", + verbose_name="Semester", + ), + ), + migrations.AlterField( + model_name="subjecttutorcountassignment", + name="semester", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="settool_common.semester", + verbose_name="Semester", + ), + ), + migrations.AlterField( + model_name="task", + name="semester", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="settool_common.semester", + verbose_name="Semester", + ), + ), + migrations.AlterField( + model_name="tutor", + name="semester", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="settool_common.semester", + verbose_name="Semester", + ), + ), + ] diff --git a/tutors/models.py b/tutors/models.py index a008ec23..108b4961 100644 --- a/tutors/models.py +++ b/tutors/models.py @@ -1,5 +1,3 @@ -import uuid - from dateutil.relativedelta import relativedelta from django.core.validators import RegexValidator from django.db import models @@ -130,13 +128,9 @@ def log(self, user, text): pass -class Tutor(common_models.LoggedModelBase): +class Tutor(common_models.UUIDModelBase, common_models.LoggedModelBase, common_models.SemesterModelBase): class Meta: unique_together = ("semester", "email") - - id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) - semester = models.ForeignKey(Semester, verbose_name=_("Semester"), on_delete=models.CASCADE) - first_name = models.CharField(_("First name"), max_length=30) last_name = models.CharField(_("Last name"), max_length=50) email = models.EmailField(_("Email address")) @@ -204,10 +198,7 @@ def log(self, user, text): pass -class Event(common_models.LoggedModelBase): - id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) - semester = models.ForeignKey(Semester, verbose_name=_("Semester"), on_delete=models.CASCADE) - +class Event(common_models.UUIDModelBase, common_models.LoggedModelBase, common_models.SemesterModelBase): name = models.CharField(_("Name"), max_length=250) description = models.TextField(_("Description"), blank=True) begin = models.DateTimeField(_("Begin")) @@ -228,10 +219,7 @@ def __str__(self) -> str: return f"{self.name} {self.begin.date()}" -class Task(common_models.LoggedModelBase): - id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) - semester = models.ForeignKey(Semester, verbose_name=_("Semester"), on_delete=models.CASCADE) - +class Task(common_models.UUIDModelBase, common_models.LoggedModelBase, common_models.SemesterModelBase): name = models.CharField(_("Task name"), max_length=250) description = models.TextField(_("Description"), blank=True) begin = models.DateTimeField(_("Begin")) @@ -267,10 +255,7 @@ def __str__(self) -> str: return f"{self.tutor}" -class Question(common_models.LoggedModelBase): - id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) - semester = models.ForeignKey(Semester, verbose_name=_("Semester"), on_delete=models.CASCADE) - +class Question(common_models.UUIDModelBase, common_models.LoggedModelBase, common_models.SemesterModelBase): question = models.CharField(_("Question"), max_length=100) def __str__(self) -> str: @@ -315,8 +300,7 @@ def __str__(self) -> str: return f"{self.created_at}: {self.tutor} -> {self.mail} - {self.task}" -class SubjectTutorCountAssignment(common_models.LoggedModelBase): - semester = models.ForeignKey(Semester, on_delete=models.CASCADE) +class SubjectTutorCountAssignment(common_models.LoggedModelBase, common_models.SemesterModelBase): subject = models.ForeignKey(Subject, on_delete=models.CASCADE) wanted = models.PositiveIntegerField(default=0, null=True, blank=True) waitlist = models.PositiveIntegerField(default=0, null=True, blank=True) From 9c4ce88745abd3b046f7267a7d8d47072874943d Mon Sep 17 00:00:00 2001 From: Frank Elsinga Date: Mon, 4 Apr 2022 23:17:41 +0200 Subject: [PATCH 05/30] semester-reformating --- bags/models.py | 2 ++ guidedtours/models.py | 1 + tutors/models.py | 1 + 3 files changed, 4 insertions(+) diff --git a/bags/models.py b/bags/models.py index 782c9d41..bdd43366 100644 --- a/bags/models.py +++ b/bags/models.py @@ -54,6 +54,7 @@ class Company(common_models.LoggedModelBase, common_models.SemesterModelBase): class Meta: unique_together = ("semester", "name") permissions = (("view_companies", "Can view and edit the companies"),) + name = models.CharField(_("Name"), max_length=200) contact_gender = models.CharField( @@ -106,6 +107,7 @@ def contact_name(self): class GiveawayGroup(common_models.LoggedModelBase, common_models.SemesterModelBase): class Meta: unique_together = (("semester", "name"),) + name = models.CharField(_("Giveaway-groups' name"), max_length=200) def __str__(self) -> str: diff --git a/guidedtours/models.py b/guidedtours/models.py index fdf72313..9d4e5bbc 100644 --- a/guidedtours/models.py +++ b/guidedtours/models.py @@ -58,6 +58,7 @@ class Meta: "Can view and edit the list of participants", ), ) + name = models.CharField(max_length=200, verbose_name=_("Name")) description = models.TextField(null=True, blank=True, verbose_name=_("Description")) diff --git a/tutors/models.py b/tutors/models.py index 108b4961..8f83c89a 100644 --- a/tutors/models.py +++ b/tutors/models.py @@ -131,6 +131,7 @@ def log(self, user, text): class Tutor(common_models.UUIDModelBase, common_models.LoggedModelBase, common_models.SemesterModelBase): class Meta: unique_together = ("semester", "email") + first_name = models.CharField(_("First name"), max_length=30) last_name = models.CharField(_("Last name"), max_length=50) email = models.EmailField(_("Email address")) From 1d557fe29e30b8bc3b092fab1efce5c0f5684545 Mon Sep 17 00:00:00 2001 From: Frank Elsinga Date: Mon, 4 Apr 2022 23:19:31 +0200 Subject: [PATCH 06/30] migrated fahrt.participant from an integer-based id to a uuid --- fahrt/locale/de/LC_MESSAGES/django.mo | Bin 19786 -> 19782 bytes fahrt/locale/de/LC_MESSAGES/django.po | 6 +- ...0033_logentry_participant_uuid_and_more.py | 143 ++++++++++++++++++ ..._id_alter_logentry_participant_and_more.py | 52 +++++++ fahrt/models.py | 6 +- .../templates/fahrt/finanz/simple_finanz.html | 4 +- .../view_participant_details.html | 2 +- .../management/add_transport.html | 2 +- .../management/list_transports.html | 18 +-- .../management/transport_chat.html | 2 +- fahrt/urls.py | 22 +-- fahrt/views/finanz_views.py | 4 +- fahrt/views/participants_views.py | 23 +-- fahrt/views/transport_views.py | 2 +- 14 files changed, 240 insertions(+), 46 deletions(-) create mode 100644 fahrt/migrations/0033_logentry_participant_uuid_and_more.py create mode 100644 fahrt/migrations/0034_remove_participant_id_alter_logentry_participant_and_more.py diff --git a/fahrt/locale/de/LC_MESSAGES/django.mo b/fahrt/locale/de/LC_MESSAGES/django.mo index f68fab52467212b4f3b086d0c03652ba3b4aa959..7b453f590c90c4893a392e89a0f85e3bba243093 100644 GIT binary patch delta 3044 zcmXZe3rv<(9LMqVa=!^L7cZ|Uyb%}C3?r0_AQO0r7Un3 zInA;(vvA%@<}9_Pv&_orYKhL(k~T})oMxF4p?!Zl&&J-*Ip=xKIsfxN=RD)v&7KpR zJs&p(IbFt>lw@P_u-^b#=nJZ4}o%*Pn4 zz)-Bl-Z&k@aN$7Xm;?&X(x3@m!2;ZZvDl6p;1WjTbqvAVn2y2Bs(yp8FP5MNo{H-C z2oA@3jKufwUfhWq|9pyLBmSKRMc##)AUf4X>P1bQiHVqt8aRMMa54@>2g7j>dhvkk z3G`7vhyCzRRG?9QJARr&L6H=o0vLlz)eM}0bFdme!z_%s%TAPs+M;5djnh#BA4H9F z#(my_+KL|3Rzfy*+CA$z6^wE|zJ zT~RjrsRvN~YB2|!a1b8BO?U}Ia0O`$!j%}x{ALY>{xmdU7=DCRxEHlG8bzlz4wc#r z490v^3P+*_4&W@DfPHW$s^3wJz;_#23Gnj%uVG(wt zGBARCYOkl^He86xoR<@M4+d~4F2P8=j0*5N-iJ4_6tj8R(F^qy=>5W?1aMWQOg_@`a_5K{x!ses4Y!T{kH@fXF4<-N6G`v9rf973&G{7nR z0>4MjiFut%tIQlg1#})Y@kQ58)GfH~)~h%G%3KX9um;q3W&>)gTTxqdJjY=_DO{j| zZ82t;jWipTkwWC=m~zyLpFjn$+O-+A!VRcHx)s$gl|1Xil#N=@DAesKM`d6Z`tfmx zf>yE$)u9|eC_EV^Ue#Kbq z#5lZ#nY#ZGBkam@a4-)7u8S~%`a7t---o^N2UG?wqpsmK)WmdVPwAh!E!A| z^`C}%Zvp1&{y#^7Y?z%m&|}QcIEs2V%T>fhTvi>Xap=c7RA$zq4(E2v!mrVfSKW3m zch*Zi50#M$)a`i~wSal(V}7%Mf>N{wwc=*%i(9b_KSiyu3$=G4BkjOZsKb?vx;=jM z;Uv_zz793+YE&RiNOH^;)Hpw)qmCEc7b1#nq&`ffeF!SjQdH{3paPkNT2Y-_Uxs`l z%xctl`%(QaVlTYx`VS^i58)%z2Yn^vUk$@(kQJzu*PvEXj~Zwtj>p%KSWP=>%M#cp z9nJu1%PRSK8rPvRcpJ6yKBabxvQX`n$W1l1rQ}~9piML=(hpDp?8gQ?hBI+wnSH(y zHQ^4_KzlJ2PoPr$8!F%{sPS&#gBU#8-k!0jKpRox9CavY55Ga()6qbuMlR6viSw(12`zjerv<}emw4<5t(3OldUMInfW+o zYQosDc1w~li@F~*@L1G!n~W3idDIy=j)UEse3=p5-me<8~>06V}oZpV(67-R(Kp bcEwxk@l0u(ny@Ld?P~J50c|G>UP}5O>KahC delta 3050 zcmXZeeN5F=9LMoX(kgu>dvjVpP9n z_!!n=2)5(H*nt|qC)u$P|3QNy??+7#kzyl_K}|dsV=)Uga0zDMLL85cI0QezDC~4S ziwV>(;|RQq3N+kr#}7CZ6iF^BfKpVdDsd?;$NBgLreWl0JJBT67UknItUwL?Icl7X z?(e^$wqgLaRT1ppP)tW{wUbLhD=fyRaUL$h;~0#wW9)>9sFeheT`>8mR9CsbuS7jx zi%NMN@-YYaO2-SR4E3Qh>|y`bGQSC;z+9#d)o~~CG3WVe#yhwPH$G?#*)#p96^u%? zE6PAW^%7LS)i?pS;aL032H}33hlf#H<7E_`+Gtd2 z({K>xpi-EJ8n^_Xz&RL?9jJa^V=#7OBVNTETse*anBTlbK?A*y%ETemA-jm2M{@;p z@jfa8+2m7uy#(8F9V&A%oXCf;1naOKL+}PFz+TM6J}kft9(MFVEd^PRdFZ&Fz#-K8 zQJERUQ4hyZR3O7qE1QfujD@I)s!-1_M=h)dwPov3hx-+`y?H$O52IlR4SdYId})9S zcoM%y&WmZ|(ke5ZsDOG<6aVUZ6LkxE-Fi6(K$)vT1-1e8p4o}o>I0}P>YCsf!+A14 z)4;YE?;|$S3{*y@AUDSpqgK2M6+n|~D{6&1QHS(BRKL;WSudsx)Pf38x2G7Dfok;Q zQw{~Ki&mLvMbBPbbcstU5_!;-$w2I5gdj;pfYd+bq(*JCjJ|>LeFITstv>c zUs$gBsQyb(&pm?^b^l+WKsHPVrg)6`1@oy7uv|r)&t=tNnuC5^gBo}%>TvGEH2fC* zc-L)@qwGJJ8GP6bky;R`#|_q8)*{8(mnwdX%Q-QvrvI7N3Cd$ zTi=AdB1|)Cykn?-*DwNax!%J#>cPBZ;xQ?Y{Hr0023d+q`7+c>)}jV#!ZK_{Vl_Re zEsJNLbU4dUTUO53v$!3VL2rRw`AAHoo`GsFM{cUAEpY5#zc*=6q#vOIIEF7`7gl0H zq5XXiYQp`ffj-3)Jcmm44OGB?qQ>jP={RJXy*;y0fo?{PbK0SxJv@)Prv^ zI{8qk4?!P}!Ezjjy0;Cez1xc6xEFN|kD$)RXQ+Ws;R@_R{pT}wrv1NR8fxX$*rNMi zLm`=lyQm3cO6-=5z%=Rs)WEY**KIM*!VRc1a0b({AAiKuQu|{12YFeVqB8qxu1Ec? z=|#<>L3I915CyGpItJq|48?YwhX>u?|HUBc^Jm-VmZEOK3QWa148uL>$9O&m7I{+Ak$N@a0dLgXsXg0{xUe$U*thM^t423mvKu13eU k6-FKOn4`C&R(U*~M;FHI3h~z0AH6;5r$l)=`{lU*0QNplmjD0& diff --git a/fahrt/locale/de/LC_MESSAGES/django.po b/fahrt/locale/de/LC_MESSAGES/django.po index e51fa56f..af23a3ff 100644 --- a/fahrt/locale/de/LC_MESSAGES/django.po +++ b/fahrt/locale/de/LC_MESSAGES/django.po @@ -142,13 +142,13 @@ msgstr "Der Teilnehmer" msgid "" "If the Email is configured as the fahrt's registration mail, the " "participants' personalised non-liability form is automatically attached. " -"Please notify the Participant to atach his ID (THIS-->{{ participant.uuid }}" -"<--THIS) in the Payment-Subject-Line." +"Please notify the Participant to atach his ID (THIS-->{{ participant.id }}<--" +"THIS) in the Payment-Subject-Line." msgstr "" "Falls eine Anmeldungs Email konfiguriert ist wird der personalisierte " "Haftungsauschuss automatisch an die Teilnehmer*in gesendet, sobald sie sich " "registrieren. Bitte weise den Teilnehmer darauf hin, seine ID (THIS--" -">{{ participant.uuid }}<--THIS) in der Zahlungs-Betreff-Zeile zu nennen." +">{{ participant.id }}<--THIS) in der Zahlungs-Betreff-Zeile zu nennen." #: fahrt/models.py msgid "Date" diff --git a/fahrt/migrations/0033_logentry_participant_uuid_and_more.py b/fahrt/migrations/0033_logentry_participant_uuid_and_more.py new file mode 100644 index 00000000..d6cdc206 --- /dev/null +++ b/fahrt/migrations/0033_logentry_participant_uuid_and_more.py @@ -0,0 +1,143 @@ +# Generated by Django 4.0.3 on 2022-04-04 19:14 + +import uuid + +import django.db.models.deletion +from django.db import migrations, models + + +def migrate_participant_id_for_log_entry(apps, _): + LogEntry = apps.get_model("fahrt", "LogEntry") + for row in LogEntry.objects.all(): + row.participant_uuid_id = row.participant.uuid + row.save(update_fields=["participant_uuid"]) + + +def migrate_participant_id_for_transportation_comment(apps, _): + TransportationComment = apps.get_model("fahrt", "TransportationComment") + for row in TransportationComment.objects.all(): + row.sender_uuid_id = row.sender.uuid + row.save(update_fields=["sender_uuid"]) + + +def migrate_participant_id_for_transportation(apps, _): + Transportation = apps.get_model("fahrt", "Transportation") + for row in Transportation.objects.all(): + row.creator_uuid_id = row.creator.uuid + row.save(update_fields=["creator_uuid"]) + + +class Migration(migrations.Migration): + + dependencies = [ + ("fahrt", "0032_rename_deparure_place_transportation_departure_place_and_more"), + ] + operations = [ + migrations.AlterField( + model_name="participant", + name="uuid", + field=models.UUIDField(default=uuid.uuid4, editable=False, serialize=False, unique=True), + ), + # following https://stackoverflow.com/a/48235821 + # add different fk-fields for each participant and logentry + migrations.AddField( + model_name="logentry", + name="participant_uuid", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="logentry_uuid", + to="fahrt.participant", + to_field="uuid", + db_constraint=False, + ), + ), + migrations.AddField( + model_name="transportationcomment", + name="sender_uuid", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="transportationcomment_uuid", + to="fahrt.participant", + to_field="uuid", + db_constraint=False, + ), + ), + migrations.AddField( + model_name="transportation", + name="creator_uuid", + field=models.OneToOneField( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="fahrt_transportation_creator_uuid", + to="fahrt.participant", + to_field="uuid", + db_constraint=False, + ), + ), + migrations.AlterField( + model_name="logentry", + name="participant", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="logentry", + to="fahrt.participant", + ), + ), + migrations.AlterField( + model_name="transportationcomment", + name="sender", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="transportationcomment", + to="fahrt.participant", + ), + ), + # fill new fields with valid data + migrations.RunPython(migrate_participant_id_for_transportation_comment), + migrations.RunPython(migrate_participant_id_for_transportation), + migrations.RunPython(migrate_participant_id_for_log_entry), + # after filling, we can assure the db, that they are filled + migrations.AlterField( + model_name="logentry", + name="participant_uuid", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="logentry_uuid", + to="fahrt.participant", + to_field="uuid", + ), + ), + migrations.AlterField( + model_name="transportationcomment", + name="sender_uuid", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="logentry_uuid", + to="fahrt.participant", + to_field="uuid", + ), + ), + migrations.AlterField( + model_name="transportation", + name="creator_uuid", + field=models.OneToOneField( + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="fahrt_transportation_creator_uuid", + to="fahrt.participant", + to_field="uuid", + ), + ), + # remove old fields and rename new ones + migrations.RemoveField(model_name="logentry", name="participant"), + migrations.RenameField(model_name="logentry", old_name="participant_uuid", new_name="participant"), + migrations.RemoveField(model_name="transportationcomment", name="sender"), + migrations.RenameField(model_name="transportationcomment", old_name="sender_uuid", new_name="sender"), + migrations.RemoveField(model_name="transportation", name="creator"), + migrations.RenameField(model_name="transportation", old_name="creator_uuid", new_name="creator"), + ] diff --git a/fahrt/migrations/0034_remove_participant_id_alter_logentry_participant_and_more.py b/fahrt/migrations/0034_remove_participant_id_alter_logentry_participant_and_more.py new file mode 100644 index 00000000..9a1fb224 --- /dev/null +++ b/fahrt/migrations/0034_remove_participant_id_alter_logentry_participant_and_more.py @@ -0,0 +1,52 @@ +# Generated by Django 4.0.3 on 2022-04-04 19:44 + +import uuid + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("fahrt", "0033_logentry_participant_uuid_and_more"), + ] + + operations = [ + # uuid->id + migrations.RemoveField( + model_name="participant", + name="id", + ), + migrations.AlterField( + model_name="participant", + name="uuid", + field=models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False, unique=True), + ), + migrations.RenameField( + model_name="participant", + old_name="uuid", + new_name="id", + ), + # cleanup + migrations.AlterField( + model_name="transportationcomment", + name="sender", + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="fahrt.participant"), + ), + migrations.AlterField( + model_name="logentry", + name="participant", + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="fahrt.participant"), + ), + migrations.AlterField( + model_name="transportation", + name="creator", + field=models.OneToOneField( + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="fahrt_transportation_creator", + to="fahrt.participant", + ), + ), + ] diff --git a/fahrt/models.py b/fahrt/models.py index 774931c5..451c8e51 100644 --- a/fahrt/models.py +++ b/fahrt/models.py @@ -1,5 +1,4 @@ import datetime -import uuid from dateutil.relativedelta import relativedelta from django.contrib.auth import get_user_model @@ -25,7 +24,7 @@ class FahrtMail(common_models.Mail): notes = _( "If the Email is configured as the fahrt's registration mail, the participants' personalised non-liability " "form is automatically attached. Please notify the Participant to atach his ID " - "(THIS-->{{ participant.uuid }}<--THIS) in the Payment-Subject-Line.", + "(THIS-->{{ participant.id }}<--THIS) in the Payment-Subject-Line.", ) required_perm = common_models.Mail.required_perm + ["fahrt.view_participants"] @@ -161,7 +160,7 @@ def __str__(self) -> str: return _("Car ({free_places} free)").format(free_places=free_places) -class Participant(common_models.LoggedModelBase, common_models.SemesterModelBase): +class Participant(common_models.UUIDModelBase, common_models.LoggedModelBase, common_models.SemesterModelBase): class Meta: permissions = ( ( @@ -170,7 +169,6 @@ class Meta: ), ) - uuid = models.UUIDField(unique=True, default=uuid.uuid4) registration_time = models.DateTimeField(_("Registration time"), auto_now_add=True) GENDER_CHOICES = ( diff --git a/fahrt/templates/fahrt/finanz/simple_finanz.html b/fahrt/templates/fahrt/finanz/simple_finanz.html index c0a39537..cd95377d 100644 --- a/fahrt/templates/fahrt/finanz/simple_finanz.html +++ b/fahrt/templates/fahrt/finanz/simple_finanz.html @@ -55,11 +55,11 @@

{% trans "List of all confirmed participants" %}

{% for participant, select in participants_and_select %} {% if 'fahrt.view_participants' in perms %} - {{ participant.uuid }} + {{ participant.id }} {{ participant.firstname }} {{ participant.surname }} {% else %} - {{ participant.uuid }} + {{ participant.id }} {{ participant.firstname }} {{ participant.surname }} {% endif %} diff --git a/fahrt/templates/fahrt/participants/view_participant_details.html b/fahrt/templates/fahrt/participants/view_participant_details.html index 09486296..de28cd56 100644 --- a/fahrt/templates/fahrt/participants/view_participant_details.html +++ b/fahrt/templates/fahrt/participants/view_participant_details.html @@ -169,7 +169,7 @@ {% if participant.status == "confirmed" %} {% trans "View transportation as participant" %} {% endif %} diff --git a/fahrt/templates/fahrt/transportation/management/add_transport.html b/fahrt/templates/fahrt/transportation/management/add_transport.html index 74f42c4a..a6c1c548 100644 --- a/fahrt/templates/fahrt/transportation/management/add_transport.html +++ b/fahrt/templates/fahrt/transportation/management/add_transport.html @@ -22,7 +22,7 @@ {{ transport_name_pl }} {% endif %} {% if not calling_participant and transport.creator %} - - + + {% endif %} @@ -101,7 +101,7 @@

{{ transport_name_pl }}

{% for participant in transport.participant_set.all %} - {% if participant.uuid != transport.creator.uuid %} + {% if participant.id != transport.creator.id %} {{ transport_name_pl }} {% if not calling_participant %} - - + + {% endif %} @@ -146,8 +146,8 @@

{{ transport_name_pl }}

{% else %} - - + + {% endif %} {% endfor %} @@ -164,7 +164,7 @@

{{ transport_name_pl }}

> {% trans "Chatwall" %} {% if transport.transportationcomment_set.count == 0 %} @@ -188,7 +188,7 @@

{{ transport_name_pl }}

Add {{ transport_name }} {% endblocktrans %} -
+
diff --git a/fahrt/urls.py b/fahrt/urls.py index 732f3c24..68caadaa 100644 --- a/fahrt/urls.py +++ b/fahrt/urls.py @@ -25,12 +25,12 @@ ], ), ), - path("view//", participants_views.view_participant, name="view_participant"), - path("edit//", participants_views.edit_participant, name="edit_participant"), - path("delete//", participants_views.del_participant, name="del_participant"), - path("non_liability/", tex_views.non_liability_form, name="non_liability_form"), + path("view//", participants_views.view_participant, name="view_participant"), + path("edit//", participants_views.edit_participant, name="edit_participant"), + path("delete//", participants_views.del_participant, name="del_participant"), + path("non_liability/", tex_views.non_liability_form, name="non_liability_form"), path( - "togglemailinglist//", + "togglemailinglist//", participants_views.toggle_mailinglist, name="toggle_mailinglist", ), @@ -38,14 +38,14 @@ "set/", include( [ - path("paid//", participants_views.set_paid, name="set_paid"), + path("paid//", participants_views.set_paid, name="set_paid"), path( - "nonliability//", + "nonliability//", participants_views.set_nonliability, name="set_nonliability", ), path( - "payment_deadline///", + "payment_deadline///", participants_views.set_payment_deadline, name="set_payment_deadline", ), @@ -54,17 +54,17 @@ include( [ path( - "confirm//", + "confirm//", participants_views.set_status_confirmed, name="set_status_confirmed", ), path( - "waitinglist//", + "waitinglist//", participants_views.set_status_waitinglist, name="set_status_waitinglist", ), path( - "cancel//", + "cancel//", participants_views.set_status_canceled, name="set_status_canceled", ), diff --git a/fahrt/views/finanz_views.py b/fahrt/views/finanz_views.py index d633d3f5..364c56b1 100644 --- a/fahrt/views/finanz_views.py +++ b/fahrt/views/finanz_views.py @@ -126,8 +126,8 @@ def finanz_auto_matching(request: WSGIRequest) -> HttpResponse: semester: Semester = get_object_or_404(Semester, pk=get_semester(request)) participants: QuerySet[Participant] = Participant.objects.filter(semester=semester, status="confirmed") - # mypy is weird for this one. equivalent [but not Typechecking]: participants.values_list("uuid", flat=True) - participants_ids_pre_mypy: list[Optional[UUID]] = [element["uuid"] for element in participants.values("uuid")] + # mypy is weird for this one. equivalent [but not Typechecking]: participants.values_list("id", flat=True) + participants_ids_pre_mypy: list[Optional[UUID]] = [element["id"] for element in participants.values("id")] participants_ids: list[UUID] = [uuid for uuid in participants_ids_pre_mypy if uuid] transactions: list[Entry] = [Entry.from_json(entry) for entry in request.session["results"]] diff --git a/fahrt/views/participants_views.py b/fahrt/views/participants_views.py index a64f9c3e..38c3023f 100644 --- a/fahrt/views/participants_views.py +++ b/fahrt/views/participants_views.py @@ -1,5 +1,6 @@ from datetime import date, timedelta from typing import Optional +from uuid import UUID from django import forms from django.contrib import messages @@ -163,7 +164,7 @@ def list_cancelled(request: WSGIRequest) -> HttpResponse: @permission_required("fahrt.view_participants") -def view_participant(request: WSGIRequest, participant_pk: int) -> HttpResponse: +def view_participant(request: WSGIRequest, participant_pk: UUID) -> HttpResponse: participant = get_object_or_404(Participant, pk=participant_pk) log_entries = participant.logentry_set.order_by("time") @@ -184,7 +185,7 @@ def view_participant(request: WSGIRequest, participant_pk: int) -> HttpResponse: @permission_required("fahrt.view_participants") -def edit_participant(request: WSGIRequest, participant_pk: int) -> HttpResponse: +def edit_participant(request: WSGIRequest, participant_pk: UUID) -> HttpResponse: participant = get_object_or_404(Participant, pk=participant_pk) form = ParticipantAdminForm( @@ -206,7 +207,7 @@ def edit_participant(request: WSGIRequest, participant_pk: int) -> HttpResponse: @permission_required("fahrt.view_participants") -def del_participant(request: WSGIRequest, participant_pk: int) -> HttpResponse: +def del_participant(request: WSGIRequest, participant_pk: UUID) -> HttpResponse: participant = get_object_or_404(Participant, pk=participant_pk) form = forms.Form(request.POST or None) @@ -223,7 +224,7 @@ def del_participant(request: WSGIRequest, participant_pk: int) -> HttpResponse: @permission_required("fahrt.view_participants") -def toggle_mailinglist(request: WSGIRequest, participant_pk: int) -> HttpResponse: +def toggle_mailinglist(request: WSGIRequest, participant_pk: UUID) -> HttpResponse: participant = get_object_or_404(Participant, pk=participant_pk) participant.toggle_mailinglist() participant.log(request.user, "Toggle mailinglist") @@ -232,7 +233,7 @@ def toggle_mailinglist(request: WSGIRequest, participant_pk: int) -> HttpRespons @permission_required("fahrt.view_participants") -def set_paid(request: WSGIRequest, participant_pk: int) -> HttpResponse: +def set_paid(request: WSGIRequest, participant_pk: UUID) -> HttpResponse: participant = get_object_or_404(Participant, pk=participant_pk) Participant.objects.filter(pk=participant_pk).update( paid=timezone.now().date(), @@ -243,7 +244,7 @@ def set_paid(request: WSGIRequest, participant_pk: int) -> HttpResponse: @permission_required("fahrt.view_participants") -def set_nonliability(request: WSGIRequest, participant_pk: int) -> HttpResponse: +def set_nonliability(request: WSGIRequest, participant_pk: UUID) -> HttpResponse: participant = get_object_or_404(Participant, pk=participant_pk) Participant.objects.filter(pk=participant_pk).update( non_liability=timezone.now().date(), @@ -254,7 +255,7 @@ def set_nonliability(request: WSGIRequest, participant_pk: int) -> HttpResponse: @permission_required("fahrt.view_participants") -def set_status_confirmed(request: WSGIRequest, participant_pk: int) -> HttpResponse: +def set_status_confirmed(request: WSGIRequest, participant_pk: UUID) -> HttpResponse: participant = get_object_or_404(Participant, pk=participant_pk) Participant.objects.filter(pk=participant_pk).update( status="confirmed", @@ -265,7 +266,7 @@ def set_status_confirmed(request: WSGIRequest, participant_pk: int) -> HttpRespo @permission_required("fahrt.view_participants") -def set_status_waitinglist(request: WSGIRequest, participant_pk: int) -> HttpResponse: +def set_status_waitinglist(request: WSGIRequest, participant_pk: UUID) -> HttpResponse: participant = get_object_or_404(Participant, pk=participant_pk) Participant.objects.filter(pk=participant_pk).update( status="waitinglist", @@ -276,7 +277,7 @@ def set_status_waitinglist(request: WSGIRequest, participant_pk: int) -> HttpRes @permission_required("fahrt.view_participants") -def set_payment_deadline(request: WSGIRequest, participant_pk: int, weeks: int) -> HttpResponse: +def set_payment_deadline(request: WSGIRequest, participant_pk: UUID, weeks: int) -> HttpResponse: weeks = int(weeks) # save due to regex in urls.py if weeks not in [1, 2, 3]: raise Http404("Invalid number of weeks") @@ -291,7 +292,7 @@ def set_payment_deadline(request: WSGIRequest, participant_pk: int, weeks: int) @permission_required("fahrt.view_participants") -def set_status_canceled(request: WSGIRequest, participant_pk: int) -> HttpResponse: +def set_status_canceled(request: WSGIRequest, participant_pk: UUID) -> HttpResponse: participant = get_object_or_404(Participant, pk=participant_pk) Participant.objects.filter(pk=participant_pk).update( status="cancelled", @@ -448,7 +449,7 @@ def set_request_session_filtered_participants( u18 = filterform.cleaned_data["u18"] should_not_filter = u18 is None - filtered_participants: list[int] = [p.id for p in participants if should_not_filter or p.u18 is u18] + filtered_participants: list[UUID] = [p.id for p in participants if should_not_filter or p.u18 is u18] request.session["filtered_participants"] = filtered_participants diff --git a/fahrt/views/transport_views.py b/fahrt/views/transport_views.py index a3f4d772..00c242cd 100644 --- a/fahrt/views/transport_views.py +++ b/fahrt/views/transport_views.py @@ -311,7 +311,7 @@ def transport_chat(request: WSGIRequest, participant_uuid: UUID, transport_pk: i form = TransportationCommentForm(request.POST or None, transport=transport, participant=participant) if form.is_valid(): form.save() - return redirect("fahrt:transport_chat", participant.uuid, transport.pk) + return redirect("fahrt:transport_chat", participant.id, transport.pk) context = { "form": form, From c38646f0d424b4064536e20892e4ab5c40773839 Mon Sep 17 00:00:00 2001 From: Frank Elsinga Date: Sun, 3 Apr 2022 11:50:04 +0200 Subject: [PATCH 07/30] changed datetime.datetime to timezone to stop this migration from complaining about TZ-Support --- guidedtours/migrations/0005_participant_time.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/guidedtours/migrations/0005_participant_time.py b/guidedtours/migrations/0005_participant_time.py index 3657e4f1..44642d9e 100644 --- a/guidedtours/migrations/0005_participant_time.py +++ b/guidedtours/migrations/0005_participant_time.py @@ -3,6 +3,7 @@ import datetime from django.db import migrations, models +from django.utils import timezone class Migration(migrations.Migration): @@ -16,7 +17,7 @@ class Migration(migrations.Migration): model_name="participant", name="time", field=models.DateTimeField( - default=datetime.datetime.now, + default=timezone.now, verbose_name="Registration Time", ), ), From 303ffc28c3c9cfbfd0c98e089faa275ae5402f63 Mon Sep 17 00:00:00 2001 From: Frank Elsinga Date: Mon, 4 Apr 2022 23:29:48 +0200 Subject: [PATCH 08/30] Fixed SIM909 Remove reflexive assignment --- tutors/tests/test_file_export.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tutors/tests/test_file_export.py b/tutors/tests/test_file_export.py index 8ca48fd1..c56ac8cc 100644 --- a/tutors/tests/test_file_export.py +++ b/tutors/tests/test_file_export.py @@ -174,7 +174,7 @@ class TaskExportTest(django.test.TestCase): fixtures = ["Tutors.json"] def setUp(self): - self.client = self.client = get_mocked_logged_in_client() + self.client = get_mocked_logged_in_client() def test_pdf_export_task_no_data(self): self.task_pdf_generation("3cd2b4b0-c36f-4348-8a93-b3bb72029f46") From d8a1d2458794ec5e33b5088531dfbdc2c5cb251d Mon Sep 17 00:00:00 2001 From: CommanderStorm Date: Fri, 1 Apr 2022 19:09:15 +0000 Subject: [PATCH 09/30] Automated Change: updated pre-commit requirements --- .pre-commit-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6470abff..083061a4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -17,7 +17,7 @@ repos: args: [--fix=lf] - id: requirements-txt-fixer - repo: https://github.com/paulhfischer/pre-commit-hooks - rev: v1.2.39 + rev: v1.2.40 hooks: - id: format-general - id: format-web @@ -28,11 +28,11 @@ repos: hooks: - id: isort - repo: https://github.com/asottile/add-trailing-comma - rev: v2.2.1 + rev: v2.2.2 hooks: - id: add-trailing-comma - repo: https://github.com/psf/black - rev: 22.1.0 + rev: 22.3.0 hooks: - id: black language_version: python3 From f29a41aa2c25c003d918b28e571fb4fc6aea5b4c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Apr 2022 23:02:49 +0000 Subject: [PATCH 10/30] Bump @popperjs/core from 2.11.2 to 2.11.4 Bumps [@popperjs/core](https://github.com/popperjs/popper-core) from 2.11.2 to 2.11.4. - [Release notes](https://github.com/popperjs/popper-core/releases) - [Commits](https://github.com/popperjs/popper-core/compare/v2.11.2...v2.11.4) --- updated-dependencies: - dependency-name: "@popperjs/core" dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- package-lock.json | 14 +++++++------- package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2b049cf7..40ad68ad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "AGPL-3.0-or-later", "dependencies": { - "@popperjs/core": "^2.11.2", + "@popperjs/core": "^2.11.4", "bootstrap": "^5.1.3", "bootstrap-icons": "^1.8.1", "bootstrap-switch-button": "^1.1.0", @@ -31,9 +31,9 @@ } }, "node_modules/@popperjs/core": { - "version": "2.11.2", - "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.2.tgz", - "integrity": "sha512-92FRmppjjqz29VMJ2dn+xdyXZBrMlE42AV6Kq6BwjWV7CNUW1hs2FtxSNLQE+gJhaZ6AAmYuO9y8dshhcBl7vA==", + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.4.tgz", + "integrity": "sha512-q/ytXxO5NKvyT37pmisQAItCFqA7FD/vNb8dgaJy3/630Fsc+Mz9/9f2SziBoIZ30TJooXyTwZmhi1zjXmObYg==", "funding": { "type": "opencollective", "url": "https://opencollective.com/popperjs" @@ -136,9 +136,9 @@ } }, "@popperjs/core": { - "version": "2.11.2", - "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.2.tgz", - "integrity": "sha512-92FRmppjjqz29VMJ2dn+xdyXZBrMlE42AV6Kq6BwjWV7CNUW1hs2FtxSNLQE+gJhaZ6AAmYuO9y8dshhcBl7vA==" + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.4.tgz", + "integrity": "sha512-q/ytXxO5NKvyT37pmisQAItCFqA7FD/vNb8dgaJy3/630Fsc+Mz9/9f2SziBoIZ30TJooXyTwZmhi1zjXmObYg==" }, "bootstrap": { "version": "5.1.3", diff --git a/package.json b/package.json index 435a4e7c..62733578 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "author": "SET-Referat FSMPI", "license": "AGPL-3.0-or-later", "dependencies": { - "@popperjs/core": "^2.11.2", + "@popperjs/core": "^2.11.4", "bootstrap": "^5.1.3", "bootstrap-icons": "^1.8.1", "bootstrap-switch-button": "^1.1.0", From 47d51f328c834d68fd46427e7ab82336818c2a25 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Apr 2022 23:03:04 +0000 Subject: [PATCH 11/30] Update django-stubs requirement from ~=1.9.0 to ~=1.10.1 Updates the requirements on [django-stubs](https://github.com/typeddjango/django-stubs) to permit the latest version. - [Release notes](https://github.com/typeddjango/django-stubs/releases) - [Commits](https://github.com/typeddjango/django-stubs/commits) --- updated-dependencies: - dependency-name: django-stubs dependency-type: direct:development ... Signed-off-by: dependabot[bot] --- requirements_dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_dev.txt b/requirements_dev.txt index fd2ddfc0..e66a0214 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -1,5 +1,5 @@ coverage~=6.3 -django-stubs~=1.9.0 +django-stubs~=1.10.1 lorem~=0.1.1 mypy~=0.931 pre-commit~=2.17.0 From 52b7a4ac04dc34c5fdf0c1b80dde2238402825c0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Apr 2022 23:02:53 +0000 Subject: [PATCH 12/30] Update pillow requirement from ~=9.0.1 to ~=9.1.0 Updates the requirements on [pillow](https://github.com/python-pillow/Pillow) to permit the latest version. - [Release notes](https://github.com/python-pillow/Pillow/releases) - [Changelog](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst) - [Commits](https://github.com/python-pillow/Pillow/compare/9.0.1...9.1.0) --- updated-dependencies: - dependency-name: pillow dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 9dddeeda..aa556d4e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,6 +8,6 @@ django-compref-keycloak~=1.2.0 django-crontab~=0.7.1 django-modeltranslation~=0.17.5 django-tex~= 1.1.10 -Pillow~=9.0.1 +Pillow~=9.1.0 python-dateutil~=2.8.2 qrcode~=7.3 From e5612653043c409ecf0cafee9ddc8c1f73cfddec Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Apr 2022 23:03:13 +0000 Subject: [PATCH 13/30] Update pylint requirement from ~=2.12.2 to ~=2.13.4 Updates the requirements on [pylint](https://github.com/PyCQA/pylint) to permit the latest version. - [Release notes](https://github.com/PyCQA/pylint/releases) - [Changelog](https://github.com/PyCQA/pylint/blob/main/ChangeLog) - [Commits](https://github.com/PyCQA/pylint/compare/v2.12.2...v2.13.4) --- updated-dependencies: - dependency-name: pylint dependency-type: direct:development ... Signed-off-by: dependabot[bot] --- requirements_dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_dev.txt b/requirements_dev.txt index e66a0214..eb0d1530 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -3,6 +3,6 @@ django-stubs~=1.10.1 lorem~=0.1.1 mypy~=0.931 pre-commit~=2.17.0 -pylint~=2.12.2 +pylint~=2.13.4 pylint-django~=2.5.2 types-python-dateutil~=2.8.9 From 281cd40bb8b3ac8b5266131f72116a5e2af38d75 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Apr 2022 23:02:56 +0000 Subject: [PATCH 14/30] Update types-python-dateutil requirement from ~=2.8.9 to ~=2.8.10 Updates the requirements on [types-python-dateutil](https://github.com/python/typeshed) to permit the latest version. - [Release notes](https://github.com/python/typeshed/releases) - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-python-dateutil dependency-type: direct:development ... Signed-off-by: dependabot[bot] --- requirements_dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_dev.txt b/requirements_dev.txt index eb0d1530..c3a92f23 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -5,4 +5,4 @@ mypy~=0.931 pre-commit~=2.17.0 pylint~=2.13.4 pylint-django~=2.5.2 -types-python-dateutil~=2.8.9 +types-python-dateutil~=2.8.10 From 0bff5b649530b2c4793c5ac0bf21b9c6b405d388 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Apr 2022 23:03:09 +0000 Subject: [PATCH 15/30] Update pylint-django requirement from ~=2.5.2 to ~=2.5.3 Updates the requirements on [pylint-django](https://github.com/PyCQA/pylint-django) to permit the latest version. - [Release notes](https://github.com/PyCQA/pylint-django/releases) - [Changelog](https://github.com/PyCQA/pylint-django/blob/master/CHANGELOG.rst) - [Commits](https://github.com/PyCQA/pylint-django/compare/v2.5.2...v2.5.3) --- updated-dependencies: - dependency-name: pylint-django dependency-type: direct:development ... Signed-off-by: dependabot[bot] --- requirements_dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_dev.txt b/requirements_dev.txt index c3a92f23..a1b1a390 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -4,5 +4,5 @@ lorem~=0.1.1 mypy~=0.931 pre-commit~=2.17.0 pylint~=2.13.4 -pylint-django~=2.5.2 +pylint-django~=2.5.3 types-python-dateutil~=2.8.10 From d01d83487b24ce3cfac770dbb771edb9f1810bf0 Mon Sep 17 00:00:00 2001 From: Frank Elsinga Date: Mon, 4 Apr 2022 23:57:31 +0200 Subject: [PATCH 16/30] made mypy happy by adding more type-hints --- fahrt/models.py | 7 ++++--- settool_common/models.py | 10 ++++++---- tutors/views.py | 9 ++++++--- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/fahrt/models.py b/fahrt/models.py index 451c8e51..77c15e10 100644 --- a/fahrt/models.py +++ b/fahrt/models.py @@ -3,6 +3,7 @@ from dateutil.relativedelta import relativedelta from django.contrib.auth import get_user_model from django.db import models +from django.http import HttpResponse from django.utils import timezone from django.utils.translation import gettext_lazy as _ @@ -34,7 +35,7 @@ def save(self, *args, **kwargs): self.sender = common_models.Mail.SET_FAHRT super().save(*args, **kwargs) - def send_mail_participant(self, participant): + def send_mail_participant(self, participant: "Participant") -> bool: context = { "vorname": participant.firstname, "frist": participant.payment_deadline, @@ -42,7 +43,7 @@ def send_mail_participant(self, participant): } return self.send_mail(context, participant.email) - def send_mail_registration(self, participant, non_liability): + def send_mail_registration(self, participant: "Participant", non_liability: HttpResponse) -> bool: context = { "vorname": participant.firstname, "frist": participant.payment_deadline, @@ -50,7 +51,7 @@ def send_mail_registration(self, participant, non_liability): } return self.send_mail(context, participant.email, attachments=[non_liability]) - def get_mail_participant(self): + def get_mail_participant(self) -> tuple[str, str, str]: context = { "vorname": "", "frist": "", diff --git a/settool_common/models.py b/settool_common/models.py index 817b5aff..2a220105 100644 --- a/settool_common/models.py +++ b/settool_common/models.py @@ -3,7 +3,7 @@ import re import uuid from io import BytesIO -from typing import Any, List, Optional, Union +from typing import Any, Optional, Union import qrcode from django.conf import settings @@ -99,8 +99,8 @@ def get_mail(self, context: Union[Context, dict[str, Any], None]) -> tuple[str, def send_mail( self, context: Union[Context, dict[str, Any], None], - recipients: Union[List[str], str], - attachments: Optional[Union[HttpResponse, list[tuple[str, Any, str]]]] = None, + recipients: Union[list[str], str], + attachments: Optional[list[Union[HttpResponse, tuple[str, Any, str]]]] = None, ) -> bool: if isinstance(recipients, str): recipients = [recipients] @@ -122,7 +122,9 @@ def send_mail( send_mail(subject, text, self.sender, recipients, fail_silently=False) else: mail = EmailMessage(subject, text, self.sender, recipients) - for (filename, content, mimetype) in [clean_attachable(attach) for attach in attachments]: + attach: Union[HttpResponse, tuple[str, Any, str]] + for attach in attachments: + (filename, content, mimetype) = clean_attachable(attach) mail.attach(filename, content, mimetype) mail.send(fail_silently=False) return True diff --git a/tutors/views.py b/tutors/views.py index 352c8100..a244af88 100644 --- a/tutors/views.py +++ b/tutors/views.py @@ -298,7 +298,7 @@ def del_tutor(request: WSGIRequest, uid: UUID) -> HttpResponse: @permission_required("tutors.edit_tutors") def edit_tutor(request: WSGIRequest, uid: UUID) -> HttpResponse: semester: Semester = get_object_or_404(Semester, pk=get_semester(request)) - tutor = get_object_or_404(Tutor, pk=uid) + tutor: Tutor = get_object_or_404(Tutor, pk=uid) question_count = Question.objects.filter(semester=semester).count() answers_existing = Answer.objects.filter(tutor=tutor) @@ -333,9 +333,12 @@ def edit_tutor(request: WSGIRequest, uid: UUID) -> HttpResponse: form.save() answer: AnswerForm for answer in answer_formset: - res = answer.save(commit=False) + res: Answer = answer.save(commit=False) res.tutor_id = tutor.id - res.question_id = answer.cleaned_data.get("question").id + res_question: Optional[Question] = answer.cleaned_data.get("question") + if res_question is None: + raise ValueError("Question should never be None") + res.question_id = res_question.id res.save() tutor.log(request.user, "Tutor edited") messages.success(request, f"Saved Tutor {tutor}.") From e63987b4f0cf5991db182bce5f9a3f808e30368a Mon Sep 17 00:00:00 2001 From: Frank Elsinga Date: Tue, 5 Apr 2022 00:01:05 +0200 Subject: [PATCH 17/30] made _make_hash_value not static because this caused issues in CI --- tutors/tokens.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tutors/tokens.py b/tutors/tokens.py index 96d8f4ba..844f27c0 100644 --- a/tutors/tokens.py +++ b/tutors/tokens.py @@ -2,8 +2,7 @@ class TokenGenerator(PasswordResetTokenGenerator): - @staticmethod - def _make_hash_value(user, timestamp): + def _make_hash_value(self, user, timestamp): return f"{user.pk}{timestamp}" From e13bada8f1b456089bb8c59cfac2a6284f404071 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Apr 2022 00:02:21 +0200 Subject: [PATCH 18/30] Update mypy requirement from ~=0.931 to ~=0.942 (#148) Updates the requirements on [mypy](https://github.com/python/mypy) to permit the latest version. - [Release notes](https://github.com/python/mypy/releases) - [Commits](https://github.com/python/mypy/compare/v0.931...v0.942) --- updated-dependencies: - dependency-name: mypy dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements_dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_dev.txt b/requirements_dev.txt index a1b1a390..3f673a7f 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -1,7 +1,7 @@ coverage~=6.3 django-stubs~=1.10.1 lorem~=0.1.1 -mypy~=0.931 +mypy~=0.942 pre-commit~=2.17.0 pylint~=2.13.4 pylint-django~=2.5.3 From 9c3098309a457d08606cadd2c3204687fe2d49d8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Apr 2022 00:03:20 +0200 Subject: [PATCH 19/30] Update pre-commit requirement from ~=2.17.0 to ~=2.18.1 (#149) Updates the requirements on [pre-commit](https://github.com/pre-commit/pre-commit) to permit the latest version. - [Release notes](https://github.com/pre-commit/pre-commit/releases) - [Changelog](https://github.com/pre-commit/pre-commit/blob/main/CHANGELOG.md) - [Commits](https://github.com/pre-commit/pre-commit/compare/v2.17.0...v2.18.1) --- updated-dependencies: - dependency-name: pre-commit dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements_dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_dev.txt b/requirements_dev.txt index 3f673a7f..57747b93 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -2,7 +2,7 @@ coverage~=6.3 django-stubs~=1.10.1 lorem~=0.1.1 mypy~=0.942 -pre-commit~=2.17.0 +pre-commit~=2.18.1 pylint~=2.13.4 pylint-django~=2.5.3 types-python-dateutil~=2.8.10 From 07a85ce427079bfd48d9400e1dc6b12bd3b710b4 Mon Sep 17 00:00:00 2001 From: Frank Elsinga Date: Tue, 22 Mar 2022 22:26:07 +0100 Subject: [PATCH 20/30] added the initial implementation of the calendar --- kalendar/__init__.py | 0 kalendar/admin.py | 1 + kalendar/apps.py | 5 + kalendar/feeds.py | 89 +++++++ kalendar/forms.py | 24 ++ kalendar/locale/de/LC_MESSAGES/django.mo | Bin 0 -> 2927 bytes kalendar/locale/de/LC_MESSAGES/django.po | 233 ++++++++++++++++++ kalendar/migrations/0001_initial.py | 120 +++++++++ kalendar/migrations/__init__.py | 0 kalendar/models.py | 157 ++++++++++++ .../templates/kalendar/base_kalendar.html | 27 ++ kalendar/templates/kalendar/dashboard.html | 16 ++ .../kalendar/management/add_date.html | 27 ++ .../kalendar/management/delete_date.html | 42 ++++ .../kalendar/management/edit_date.html | 30 +++ .../management/list/chronological.html | 86 +++++++ .../kalendar/management/list/grouped.html | 172 +++++++++++++ .../kalendar/management/view_date.html | 66 +++++ .../kalendar/management/view_date_public.html | 54 ++++ kalendar/tests/__init__.py | 0 kalendar/translation.py | 11 + kalendar/urls.py | 45 ++++ kalendar/views.py | 110 +++++++++ locale/de/LC_MESSAGES/django.mo | Bin 11070 -> 11128 bytes locale/de/LC_MESSAGES/django.po | 7 + requirements.txt | 2 + settool/settings/base_settings.py | 1 + settool/urls.py | 4 +- settool_common/cron.py | 35 +-- settool_common/tests/test_email.py | 13 +- templates/base.html | 4 + templates/base_card_layout.html | 2 + 32 files changed, 1365 insertions(+), 18 deletions(-) create mode 100644 kalendar/__init__.py create mode 100644 kalendar/admin.py create mode 100644 kalendar/apps.py create mode 100644 kalendar/feeds.py create mode 100644 kalendar/forms.py create mode 100644 kalendar/locale/de/LC_MESSAGES/django.mo create mode 100644 kalendar/locale/de/LC_MESSAGES/django.po create mode 100644 kalendar/migrations/0001_initial.py create mode 100644 kalendar/migrations/__init__.py create mode 100644 kalendar/models.py create mode 100644 kalendar/templates/kalendar/base_kalendar.html create mode 100644 kalendar/templates/kalendar/dashboard.html create mode 100644 kalendar/templates/kalendar/management/add_date.html create mode 100644 kalendar/templates/kalendar/management/delete_date.html create mode 100644 kalendar/templates/kalendar/management/edit_date.html create mode 100644 kalendar/templates/kalendar/management/list/chronological.html create mode 100644 kalendar/templates/kalendar/management/list/grouped.html create mode 100644 kalendar/templates/kalendar/management/view_date.html create mode 100644 kalendar/templates/kalendar/management/view_date_public.html create mode 100644 kalendar/tests/__init__.py create mode 100644 kalendar/translation.py create mode 100644 kalendar/urls.py create mode 100644 kalendar/views.py diff --git a/kalendar/__init__.py b/kalendar/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/kalendar/admin.py b/kalendar/admin.py new file mode 100644 index 00000000..846f6b40 --- /dev/null +++ b/kalendar/admin.py @@ -0,0 +1 @@ +# Register your models here. diff --git a/kalendar/apps.py b/kalendar/apps.py new file mode 100644 index 00000000..cce5d3e6 --- /dev/null +++ b/kalendar/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class KalendarConfig(AppConfig): + name = "kalendar" diff --git a/kalendar/feeds.py b/kalendar/feeds.py new file mode 100644 index 00000000..dd345b42 --- /dev/null +++ b/kalendar/feeds.py @@ -0,0 +1,89 @@ +import datetime +from typing import Optional +from uuid import UUID + +import django.utils.timezone +import icalendar +from django.db.models import QuerySet +from django.http import Http404, HttpRequest +from django.shortcuts import get_object_or_404 +from django.urls import reverse +from django.utils.html import escape +from django_ical.views import ICalFeed, ICAL_EXTRA_FIELDS + +from tutors.models import Tutor + +from .models import Date + + +class PersonalMeetingFeed(ICalFeed): + file_name = "meetings.ics" + timezone = "MET" + tutor: Optional[Tutor] = None + + # noinspection PyMethodOverriding + # pylint: disable=arguments-differ + def get_object(self, request: HttpRequest, tutor_uuid: UUID) -> Tutor: + tutor: Tutor = get_object_or_404(Tutor, pk=tutor_uuid) + self.tutor = tutor + return tutor + + @staticmethod + def product_id(tutor: Tutor) -> str: + return f"-//set.mpi.fs.tum.de//user//ical//{tutor.pk}" + + @staticmethod + def items(tutor: Tutor) -> QuerySet[Date]: + reference_time = django.utils.timezone.now() - datetime.timedelta(days=7 * 6) + return Date.get_dates_for_tutor(tutor.pk, reference_time) + + def item_title(self, item: Date) -> str: + super_type = item.group.group_type.capitalize() + # Titles should be double escaped by default + return escape(f"[SET-{super_type}] {item.group.super_group.name}") + + def item_updateddate(self, item: Date) -> datetime.datetime: + return item.updated_at + + def item_created(self, item: Date) -> datetime.datetime: + return item.created_at + + def item_description(self, item: Date) -> str: + return str(item.group.super_group.description) + + def item_link(self, item: Date) -> str: + if not self.tutor: + raise Http404() + return reverse("kalendar:view_date_public", args=[self.tutor.pk, item.pk]) + + @staticmethod + def item_organizer(_item: Date) -> icalendar.vCalAddress: + organizer = icalendar.vCalAddress("MAILTO:set-tutoren@fs.tum.de") + organizer.params["CN"] = icalendar.vText("SET-Tutor-Team") + return organizer + + @staticmethod + def item_start_datetime(item: Date) -> datetime.datetime: + return item.date + + @staticmethod + def item_end_datetime(item: Date) -> datetime.datetime: + return item.date + datetime.timedelta(minutes=item.probable_length) + + @staticmethod + def item_location(item: Date) -> str: + location = item.group.location + return str(location) if location else "" + + @staticmethod + def item_categories(item: Date) -> str: + group_type = item.group.group_type.upper() + return f"SET,{group_type}" + + def item_extra_kwargs(self, item): + kwargs = {} + for field in ICAL_EXTRA_FIELDS + ["categories"]: + val = self._get_dynamic_attr("item_" + field, item) + if val: + kwargs[field] = val + return kwargs diff --git a/kalendar/forms.py b/kalendar/forms.py new file mode 100644 index 00000000..e16a767c --- /dev/null +++ b/kalendar/forms.py @@ -0,0 +1,24 @@ +from bootstrap_datepicker_plus.widgets import DateTimePickerInput +from django import forms + +from kalendar.models import Date + + +class DateForm(forms.ModelForm): + class Meta: + model = Date + exclude: list[str] = [] + widgets = { + "date": DateTimePickerInput(format="%Y-%m-%d %H:%M"), + } + + def __init__(self, *args, **kwargs): + self.date_group = kwargs.pop("date_group") + super().__init__(*args, **kwargs) + + def save(self, commit: bool = True) -> Date: + date: Date = super().save(commit=False) + date.group = self.date_group + if commit: + date.save() + return date diff --git a/kalendar/locale/de/LC_MESSAGES/django.mo b/kalendar/locale/de/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..b76fcef39b22a6032fc55dbe63c2ba314cdbe893 GIT binary patch literal 2927 zcmaKs$&VF99LGzya!U>Pe6zuz5u!IYmo7A4y4~M zf!uc)q#nGC=dJ>|e-M=5dXVRif|U0#NIeZm{oVv=&yyg}ISt}S@hwPwe*p1`3ut)m zFCgvs2RI1+1=5}?G0E|bApNifAX8vF%P588^QNu#=hmbz_hdbHzXsLN0vL46dJcW*$YY^za+ zP=`@j=nse&?6?)AUszy*V8d;pSI5J{%|CUgU0CP`>dv^_gvvr+tV5;E_y*92EVQXa zWlYh}{Q@)8Wz)gZX9(RRqFX4S?03rY3Ws_W#aOx5F_5_ zU4uoDoKk+;i`}X_Ek>2?>Qs#8S*C3%_9)-$+zkk{@u07%5}Ts_RTv71kzK@eXQ-Ya=5!S60|}_PBXrB$d4+MJ!5C9 zc2=!CmJwcMK5$8k-tGrgNvDrS6rjpcoEkmNe=2FPUTF Aq5uE@ literal 0 HcmV?d00001 diff --git a/kalendar/locale/de/LC_MESSAGES/django.po b/kalendar/locale/de/LC_MESSAGES/django.po new file mode 100644 index 00000000..3da4df82 --- /dev/null +++ b/kalendar/locale/de/LC_MESSAGES/django.po @@ -0,0 +1,233 @@ +msgid "" +msgstr "" +"Report-Msgid-Bugs-To: elsinga fs.tum.de\n" +"Language: de\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: kalendar/models.py +#: kalendar/templates/kalendar/management/list/chronological.html +#: kalendar/templates/kalendar/management/list/grouped.html +msgid "short/simplified Address" +msgstr "verkürtzte/vereinfachte Adresse" + +#: kalendar/models.py +#: kalendar/templates/kalendar/management/list/chronological.html +#: kalendar/templates/kalendar/management/list/grouped.html +msgid "(Street)map-address" +msgstr "Straßenkarten-Adresse" + +#: kalendar/models.py +#: kalendar/templates/kalendar/management/list/chronological.html +#: kalendar/templates/kalendar/management/list/grouped.html +msgid "Room" +msgstr "Raum" + +#: kalendar/models.py kalendar/templates/kalendar/management/list/grouped.html +msgid "Comment" +msgstr "Kommentar" + +#: kalendar/models.py +msgid "Subscribers to a date_group" +msgstr "Abonnenten einer date_group" + +#: kalendar/models.py +#: kalendar/templates/kalendar/management/list/chronological.html +msgid "Event" +msgstr "Event" + +#: kalendar/models.py +#: kalendar/templates/kalendar/management/list/chronological.html +msgid "Task" +msgstr "Aufgabe" + +#: kalendar/models.py +msgid "Date and Time" +msgstr "Datum und Zeit" + +#: kalendar/models.py kalendar/templates/kalendar/management/delete_date.html +#: kalendar/templates/kalendar/management/view_date.html +#: kalendar/templates/kalendar/management/view_date_public.html +msgid "probable length in minutes" +msgstr "warscheinliche länge in Minuten" + +#: kalendar/models.py +msgid "Subscribers to one meeting" +msgstr "Abonnenten einer Sitzung" + +#: kalendar/templates/kalendar/base_kalendar.html +#: kalendar/templates/kalendar/dashboard.html +msgid "Dashboard" +msgstr "Dashboard" + +#: kalendar/templates/kalendar/base_kalendar.html +msgid "Management" +msgstr "Management" + +#: kalendar/templates/kalendar/base_kalendar.html +msgid "List all dates" +msgstr "Liste aller Daten" + +#: kalendar/templates/kalendar/base_kalendar.html +msgid "List future dates" +msgstr "Liste zukünftigen Daten" + +#: kalendar/templates/kalendar/base_kalendar.html +msgid "List dates by event" +msgstr "Daten nach Event auflisten" + +#: kalendar/templates/kalendar/management/add_date.html +msgid "Add date" +msgstr "Datum hinzufügen" + +#: kalendar/templates/kalendar/management/delete_date.html +#: kalendar/templates/kalendar/management/view_date.html +msgid "Delete date" +msgstr "Datum löschen" + +#: kalendar/templates/kalendar/management/delete_date.html +#: kalendar/templates/kalendar/management/view_date.html +#: kalendar/templates/kalendar/management/view_date_public.html +msgid "Date" +msgstr "Datum" + +#: kalendar/templates/kalendar/management/delete_date.html +#: kalendar/templates/kalendar/management/view_date.html +#: kalendar/templates/kalendar/management/view_date_public.html +msgid "Location" +msgstr "Standort" + +#: kalendar/templates/kalendar/management/delete_date.html +#: kalendar/templates/kalendar/management/list/grouped.html +#: kalendar/templates/kalendar/management/view_date.html +#: kalendar/templates/kalendar/management/view_date_public.html +msgid "no meeting point specified" +msgstr "kein Treffpunkt angegeben" + +#: kalendar/templates/kalendar/management/delete_date.html +#: kalendar/templates/kalendar/management/edit_date.html +msgid "Cancel" +msgstr "Abbrechen" + +#: kalendar/templates/kalendar/management/edit_date.html +#: kalendar/templates/kalendar/management/view_date.html +msgid "Edit date" +msgstr "Datum bearbeiten" + +#: kalendar/templates/kalendar/management/list/chronological.html +msgid "List of dates" +msgstr "Liste der Daten" + +#: kalendar/templates/kalendar/management/list/chronological.html +msgid "Event lies in the past" +msgstr "Event liegt in der vergangenheit" + +#: kalendar/templates/kalendar/management/list/chronological.html +msgid "Task lies in the past" +msgstr "Aufgabe liegt in der vergangenheit" + +#: kalendar/templates/kalendar/management/list/chronological.html +#: kalendar/templates/kalendar/management/view_date.html +#: kalendar/templates/kalendar/management/view_date_public.html +msgid "Name" +msgstr "Name" + +#: kalendar/templates/kalendar/management/list/chronological.html +msgid "Date/Time" +msgstr "Datum/Zeit" + +#: kalendar/templates/kalendar/management/list/chronological.html +msgid "Probable length" +msgstr "Warscheinliche länge" + +#: kalendar/templates/kalendar/management/list/chronological.html +msgid "Actions" +msgstr "Aktionen" + +#: kalendar/templates/kalendar/management/list/grouped.html +msgid "List of events" +msgstr "Liste der Events" + +#: kalendar/templates/kalendar/management/list/grouped.html +msgid "No Events exist" +msgstr "Keine Events existieren" + +#: kalendar/templates/kalendar/management/list/grouped.html +msgid "Event Data" +msgstr "Eventdaten" + +#: kalendar/templates/kalendar/management/list/grouped.html +msgid "Description:" +msgstr "Beschreibung:" + +#: kalendar/templates/kalendar/management/list/grouped.html +msgid "Location:" +msgstr "Standort:" + +#: kalendar/templates/kalendar/management/list/grouped.html +msgid "Dates:" +msgstr "Daten:" + +#: kalendar/templates/kalendar/management/list/grouped.html +msgid "Associated Tasks" +msgstr "Zugeordnete Aufgaben" + +#: kalendar/templates/kalendar/management/list/grouped.html +msgid "no Tasks exist" +msgstr "keine Aufgaben existeren" + +#: kalendar/templates/kalendar/management/view_date.html +#: kalendar/templates/kalendar/management/view_date_public.html +msgid "Date details" +msgstr "Datumsdetails" + +#: kalendar/templates/kalendar/management/view_date.html +#: kalendar/templates/kalendar/management/view_date_public.html +msgid "Event-information" +msgstr "Event-informationen" + +#: kalendar/templates/kalendar/management/view_date.html +#: kalendar/templates/kalendar/management/view_date_public.html +msgid "Task-information" +msgstr "Aufgaben-informationen" + +#: kalendar/templates/kalendar/management/view_date.html +#: kalendar/templates/kalendar/management/view_date_public.html +msgid "Description" +msgstr "Beschreibung" + +#: kalendar/templates/kalendar/management/view_date.html +#: kalendar/templates/kalendar/management/view_date_public.html +msgid "Date-information" +msgstr "Datum-informationen" + +#: kalendar/templates/kalendar/management/view_date.html +#: kalendar/templates/kalendar/management/view_date_public.html +msgid "Location-information" +msgstr "Standort-informationen" + +#: kalendar/templates/kalendar/management/view_date.html +#: kalendar/templates/kalendar/management/view_date_public.html +msgid "Back" +msgstr "Zurück" + +#: kalendar/templates/kalendar/management/view_date.html +msgid "Add date to the same group" +msgstr "Datum zur selben Datumsgruppe hinzufügen" + +#: kalendar/views.py +#, python-brace-format +msgid "Successfully added date {date}." +msgstr "Datum {date} wurde erfolgreich hinzugefügt" + +#: kalendar/views.py +#, python-brace-format +msgid "Successfully edited date {date}." +msgstr "Datum {date} wurde erfolgreich hinzugefügt" + +#: kalendar/views.py +#, python-brace-format +msgid "Deleted date {date}." +msgstr "Datum {date} wurde gelöscht" diff --git a/kalendar/migrations/0001_initial.py b/kalendar/migrations/0001_initial.py new file mode 100644 index 00000000..664167de --- /dev/null +++ b/kalendar/migrations/0001_initial.py @@ -0,0 +1,120 @@ +# Generated by Django 4.0.3 on 2022-03-23 00:34 + +import uuid + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ("tutors", "0008_auto_20220321_1851"), + ] + + operations = [ + migrations.CreateModel( + name="Date", + fields=[ + ("id", models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False, unique=True)), + ("date", models.DateTimeField(verbose_name="Date and Time")), + ("probable_length", models.IntegerField(default=60, verbose_name="probable length in minutes")), + ], + options={ + "abstract": False, + }, + ), + migrations.CreateModel( + name="DateGroup", + fields=[ + ("id", models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False, unique=True)), + ("comment", models.CharField(blank=True, default="", max_length=200, verbose_name="Comment")), + ], + options={ + "abstract": False, + }, + ), + migrations.CreateModel( + name="Location", + fields=[ + ("id", models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False, unique=True)), + ("shortname", models.CharField(max_length=200, verbose_name="short/simplified Address")), + ("shortname_de", models.CharField(max_length=200, null=True, verbose_name="short/simplified Address")), + ("shortname_en", models.CharField(max_length=200, null=True, verbose_name="short/simplified Address")), + ("address", models.CharField(blank=True, max_length=100, verbose_name="(Street)map-address")), + ( + "address_de", + models.CharField(blank=True, max_length=100, null=True, verbose_name="(Street)map-address"), + ), + ( + "address_en", + models.CharField(blank=True, max_length=100, null=True, verbose_name="(Street)map-address"), + ), + ("room", models.CharField(blank=True, max_length=50, verbose_name="Room")), + ("comment", models.CharField(blank=True, max_length=200, verbose_name="Comment")), + ], + options={ + "abstract": False, + }, + ), + migrations.CreateModel( + name="DateSubscriber", + fields=[ + ("id", models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False, unique=True)), + ("date", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="kalendar.date")), + ("tutor", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="tutors.tutor")), + ], + options={ + "abstract": False, + }, + ), + migrations.CreateModel( + name="DateGroupSubscriber", + fields=[ + ("id", models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False, unique=True)), + ("date", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="kalendar.dategroup")), + ("tutor", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="tutors.tutor")), + ], + options={ + "abstract": False, + }, + ), + migrations.AddField( + model_name="dategroup", + name="location", + field=models.ForeignKey( + blank=True, + default=None, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to="kalendar.location", + ), + ), + migrations.AddField( + model_name="dategroup", + name="subscribers", + field=models.ManyToManyField( + blank=True, + through="kalendar.DateGroupSubscriber", + to="tutors.tutor", + verbose_name="Subscribers to a date_group", + ), + ), + migrations.AddField( + model_name="date", + name="group", + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="kalendar.dategroup"), + ), + migrations.AddField( + model_name="date", + name="meeting_subscribers", + field=models.ManyToManyField( + blank=True, + through="kalendar.DateSubscriber", + to="tutors.tutor", + verbose_name="Subscribers to one meeting", + ), + ), + ] diff --git a/kalendar/migrations/__init__.py b/kalendar/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/kalendar/models.py b/kalendar/models.py new file mode 100644 index 00000000..a8817ac1 --- /dev/null +++ b/kalendar/models.py @@ -0,0 +1,157 @@ +import datetime +import uuid +from uuid import UUID + +from django.db import models +from django.db.models import Q, QuerySet +from django.utils import timezone +from django.utils.safestring import mark_safe +from django.utils.translation import gettext_lazy as _ + + +class BaseModel(models.Model): + class Meta: + abstract = True + + id = models.UUIDField(unique=True, primary_key=True, default=uuid.uuid4) + + +class Location(BaseModel): + shortname = models.CharField(_("short/simplified Address"), max_length=200) + + address = models.CharField(_("(Street)map-address"), blank=True, max_length=100) + room = models.CharField(_("Room"), blank=True, max_length=50) + + comment = models.CharField(_("Comment"), blank=True, max_length=200) + + def __str__(self) -> str: + message = self.shortname + if self.address: + message += f"
{_('Adress')}: {self.address}" + if self.room: + message += f"
{_('Room')}: {self.room}" + return mark_safe(message) # nosec: fully defined + + +class DateGroup(BaseModel): + location = models.ForeignKey(Location, null=True, blank=True, default=None, on_delete=models.SET_NULL) + comment = models.CharField(_("Comment"), blank=True, default="", max_length=200) + subscribers = models.ManyToManyField( + "tutors.Tutor", + verbose_name=_("Subscribers to a date_group"), + through="DateGroupSubscriber", + blank=True, + ) + + @property + def super_group(self): + if hasattr(self, "event"): + return self.event + if hasattr(self, "tour"): + return self.tour + return self.task + + @property + def group_type(self): + if hasattr(self, "event"): + return "event" + if hasattr(self, "tour"): + return "tour" + return "task" + + @property + def group_type_str(self): + lut = { + "event": _("Event"), + "tour": _("Tour"), + "task": _("Task"), + } + return lut[self.group_type] + + @property + def dates(self) -> list[datetime.datetime]: + return [date.date for date in Date.objects.filter(group=self.id).all()] + + @property + def date_objects(self) -> list["Date"]: + return list(Date.objects.filter(group=self.id).all()) + + def __str__(self) -> str: + name = self.super_group.name + if self.location: + return f"[{self.group_type_str}] {name} at {self.location}" + return f"[{self.group_type_str}] {name} ({_('no meeting point specified')})" + + +class DateGroupSubscriber(BaseModel): + tutor = models.ForeignKey("tutors.Tutor", on_delete=models.CASCADE) + date = models.ForeignKey(DateGroup, on_delete=models.CASCADE) + + def __str__(self) -> str: + return f"{self.tutor}" + + +class Date(BaseModel): + group = models.ForeignKey(DateGroup, on_delete=models.CASCADE) + date = models.DateTimeField(_("Date and Time")) + probable_length = models.IntegerField(_("probable length in minutes"), default=60) + meeting_subscribers = models.ManyToManyField( + "tutors.Tutor", + verbose_name=_("Subscribers to one meeting"), + through="DateSubscriber", + blank=True, + ) + + def __str__(self) -> str: + return self.date.strftime("%x %X") + + def intersects(self, other_date: "Date") -> bool: + latest_start = max(self.date, other_date.date) + end_self = self.date + datetime.timedelta(minutes=self.probable_length) + end_other = other_date.date + datetime.timedelta(minutes=other_date.probable_length) + earliest_end = min(end_self, end_other) + return latest_start <= earliest_end + + @property + def table_color(self) -> str: + lut = { + ("event", True): "warning", + ("event", False): "success", + ("task", True): "secondary", + ("task", False): "light", + ("tour", True): "primary", + ("tour", False): "info", + } + return lut[self.group.group_type, self.is_in_future] + + @classmethod + def get_dates_for_tutor(cls, tutor_uuid: UUID, reference_time: datetime.datetime) -> QuerySet["Date"]: + subbed_date_groups = DateGroupSubscriber.objects.filter(tutor=tutor_uuid).values("date") + subbed_dates = DateSubscriber.objects.filter(tutor=tutor_uuid).values("date") + return ( + cls.objects.filter(date__gte=reference_time) + .filter(Q(group__in=subbed_date_groups) | Q(pk__in=subbed_dates)) + .order_by("-date") + .distinct() + .all() + ) + + @property + def probable_end(self) -> datetime.datetime: + return self.date + datetime.timedelta(minutes=self.probable_length) + + @property + def is_in_future(self) -> bool: + return self.probable_end > timezone.now() + + +class DateSubscriber(BaseModel): + tutor = models.ForeignKey("tutors.Tutor", on_delete=models.CASCADE) + date = models.ForeignKey(Date, on_delete=models.CASCADE) + + def __str__(self) -> str: + return f"{self.tutor}" + + +def create_associated_meetings() -> UUID: + return kalendar.models.DateGroup.objects.create().id diff --git a/kalendar/templates/kalendar/base_kalendar.html b/kalendar/templates/kalendar/base_kalendar.html new file mode 100644 index 00000000..f1def2a5 --- /dev/null +++ b/kalendar/templates/kalendar/base_kalendar.html @@ -0,0 +1,27 @@ +{% extends "base_card_layout.html" %} +{% load active_link_tags %} +{% load i18n %} +{% load django_bootstrap5 %} + +{% block set_common_navigation %} + +{% if perms.tutors.edit_tutors %} +
{% trans "Dashboard" %} + +{% trans "List all dates" %} +{% trans "List future dates" %} +{% trans "List dates by event" %} +{% endif %} +{% endblock %} diff --git a/kalendar/templates/kalendar/dashboard.html b/kalendar/templates/kalendar/dashboard.html new file mode 100644 index 00000000..75f0bce4 --- /dev/null +++ b/kalendar/templates/kalendar/dashboard.html @@ -0,0 +1,16 @@ +{% extends "kalendar/base_kalendar.html" %} +{% load i18n %} +{% block head %} + +{% endblock %} + +{% block set_common_headercontent %}{% trans "Dashboard" %}{% endblock %} +{% block set_common_content %} +DASHBOARD +{% endblock %} diff --git a/kalendar/templates/kalendar/management/add_date.html b/kalendar/templates/kalendar/management/add_date.html new file mode 100644 index 00000000..da57201b --- /dev/null +++ b/kalendar/templates/kalendar/management/add_date.html @@ -0,0 +1,27 @@ +{% extends "kalendar/base_kalendar.html" %} +{% load static %} +{% load i18n %} +{% load django_bootstrap5 %} + +{% block set_common_headercontent %}{% trans "Add date" %}{% endblock %} + +{% block set_common_content %} +
+ {% csrf_token %} + +
+
+ {% bootstrap_form form %} +
+
+ + +
+ +{% endblock %} diff --git a/kalendar/templates/kalendar/management/delete_date.html b/kalendar/templates/kalendar/management/delete_date.html new file mode 100644 index 00000000..7056e1f9 --- /dev/null +++ b/kalendar/templates/kalendar/management/delete_date.html @@ -0,0 +1,42 @@ +{% extends "kalendar/base_kalendar.html" %} +{% load i18n %} + +{% block set_common_headercontent %}{% trans "Delete date" %}: {{ date }}{% endblock %} + +{% block set_common_content %} +
+
+ + + + + + + + + + + + {% trans "no meeting point specified" as no_meeting_point %} + + +
{% trans "Date" %}{{ date.date }}
{% trans "probable length in minutes" %}{{ date.probable_length }}
{% trans "Location" %}{{ date.group.location|default:no_meeting_point }}
+
+
+ +
+ {% csrf_token %} + + {% trans "Cancel" %} + +
+{% endblock %} diff --git a/kalendar/templates/kalendar/management/edit_date.html b/kalendar/templates/kalendar/management/edit_date.html new file mode 100644 index 00000000..7a9c9c60 --- /dev/null +++ b/kalendar/templates/kalendar/management/edit_date.html @@ -0,0 +1,30 @@ +{% extends "kalendar/base_kalendar.html" %} +{% load static %} +{% load i18n %} +{% load django_bootstrap5 %} + +{% block set_common_headercontent %}{% trans "Edit date" %}: {{ date }}{% endblock %} + +{% block set_common_content %} +
+ {% csrf_token %} + +
+
+ {% bootstrap_form form %} +
+
+ + {% trans "Cancel" %} + +
+{% endblock %} diff --git a/kalendar/templates/kalendar/management/list/chronological.html b/kalendar/templates/kalendar/management/list/chronological.html new file mode 100644 index 00000000..e54da67d --- /dev/null +++ b/kalendar/templates/kalendar/management/list/chronological.html @@ -0,0 +1,86 @@ +{% extends "kalendar/base_kalendar.html" %} +{% load i18n %} +{% load django_bootstrap5 %} + +{% block head %} + +{% endblock %} + +{% block set_common_headercontent %}{% trans "List of dates" %}{% endblock %} + +{% block set_common_content %} +
+ + + + + {% if show_past %}{% endif %} + + {% if show_past %}{% endif %} + + + + {% if show_past %}{% endif %} + + +
{% trans "Event" %}{% trans "Event lies in the past" %}{% trans "Task" %}{% trans "Task lies in the past" %}
{% trans "Guidedtour" %}{% trans "Guidedtour lies in the past" %}
+
+
+ + + + + + + + + + + + + + + {% for date in dates %} + + + + + + {% if date.group.location %} + + + + {% else %} + + + + {% endif %} + + + {% endfor %} + +
#{% trans "Name" %}{% trans "Date/Time" %}{% trans "Probable length" %}{% trans "short/simplified Address" %}{% trans "(Street)map-address" %}{% trans "Room" %}{% trans "Actions" %}
{{ forloop.counter }} + + {{ date.group.super_group.name }} + + + {{ date.date }} + {{ date.probable_length }}m{{ date.group.location.shortname|default:"-" }}{{ date.group.location.address|default:"-" }}{{ date.group.location.room|default:"-" }}--- + + +
+
+{% endblock %} diff --git a/kalendar/templates/kalendar/management/list/grouped.html b/kalendar/templates/kalendar/management/list/grouped.html new file mode 100644 index 00000000..e3059166 --- /dev/null +++ b/kalendar/templates/kalendar/management/list/grouped.html @@ -0,0 +1,172 @@ +{% extends "kalendar/base_kalendar.html" %} +{% load i18n %} +{% load django_bootstrap5 %} + +{% block set_common_headercontent %}{% trans "List of events" %}{% endblock %} + +{% block set_common_content_overide %} +{% if not events %}{% trans "No Events exist" %}{% endif %} +
+ {% trans "no meeting point specified" as no_meeting_point %} + {% for event in events %} +
+

+ +

+
+
+

{% trans "Event Data" %}

+ + + + + + + + + + + +
{% trans "Description:" %}
{{ event.description }}
+ + + + + + + + + {% if event.associated_meetings.location %} + + + + + {% else %} + + {% endif %} + + +
{% trans "Location:" %}
{% trans "short/simplified Address" %}: {{ event.associated_meetings.location.shortname|default:"-" }}{% trans "(Street)map-address" %}: {{ event.associated_meetings.location.address|default:"-" }}{% trans "Room" %}: {{ event.associated_meetings.location.room|default:"-" }}{% trans "Comment" %}: {{ event.associated_meetings.location.comment|default:"-" }}{{ no_meeting_point }}
+ {% if event.associated_meetings.date_set.exists %} + + + + + + + + {% for date in event.associated_meetings.date_set.all %} + + + + {% endfor %} + +
{% trans "Dates:" %}
{{ date.date }} ({{ date.probable_length }} minutes)
+ {% endif %} + +

{% trans "Associated Tasks" %}

+ {% if event.task_set.all %} +
+ {% trans "no meeting point specified" as no_meeting_point %} + {% for task in event.task_set.all %} +
+

+ +

+
+
+ + + + + + + + + + + +
{% trans "Description:" %}
{{ task.description }}
+ + + + + + + + + {% if task.associated_meetings.location %} + + + + + {% else %} + + {% endif %} + + +
{% trans "Location:" %}
{% trans "short/simplified Address" %}: {{ task.associated_meetings.location.shortname|default:"-" }}{% trans "(Street)map-address" %}: {{ task.associated_meetings.location.address|default:"-" }}{% trans "Room" %}: {{ task.associated_meetings.location.room|default:"-" }}{% trans "Comment" %}: {{ task.associated_meetings.location.comment|default:"-" }}{{ no_meeting_point }}
+ {% if task.associated_meetings.date_set.exists %} + + + + + + + + {% for date in task.associated_meetings.date_set.all %} + + + + {% endfor %} + +
{% trans "Dates:" %}
{{ date.date }} ({{ date.probable_length }} minutes)
+ {% endif %} +
+
+
+ {% endfor %} +
+ {% else %} + {% trans "no Tasks exist" %} + {% endif %} +
+
+
+ {% endfor %} +
+{% endblock %} diff --git a/kalendar/templates/kalendar/management/view_date.html b/kalendar/templates/kalendar/management/view_date.html new file mode 100644 index 00000000..88aad4d9 --- /dev/null +++ b/kalendar/templates/kalendar/management/view_date.html @@ -0,0 +1,66 @@ +{% extends "kalendar/base_kalendar.html" %} +{% load i18n %} +{% load django_bootstrap5 %} + +{% block set_common_headercontent %}{% trans "Date details" %}{% endblock %} + +{% block set_common_content %} +
+
+

+ {% if date.group.is_group %}{% trans "Event-information" %} + {% else %}{% trans "Task-information" %} + {% endif %} +

+ + + + + + + + + +
{% trans "Name" %}{{ date.group.super_group.name }}
{% trans "Description" %}{{ date.group.super_group.description }}
+
+
+

{% trans "Date-information" %}

+ + + + + + + + + +
{% trans "Date" %}{{ date.date }}
{% trans "probable length in minutes" %}{{ date.probable_length }}
+
+
+

{% trans "Location-information" %}

+ + + + {% trans "no meeting point specified" as no_meeting_point %} + + +
{% trans "Location" %}{{ date.group.location|default:no_meeting_point }}
+
+
+{% trans "Back" %} +{% trans "Edit date" %} +{% trans "Delete date" %} +{% trans "Add date to the same group" %} +{% endblock %} diff --git a/kalendar/templates/kalendar/management/view_date_public.html b/kalendar/templates/kalendar/management/view_date_public.html new file mode 100644 index 00000000..0ceeaebe --- /dev/null +++ b/kalendar/templates/kalendar/management/view_date_public.html @@ -0,0 +1,54 @@ +{% extends "kalendar/base_kalendar.html" %} +{% load i18n %} +{% load django_bootstrap5 %} + +{% block set_common_headercontent %}{% trans "Date details" %}{% endblock %} + +{% block set_common_content %} +
+
+

+ {% if date.group.is_group %}{% trans "Event-information" %} + {% else %}{% trans "Task-information" %} + {% endif %} +

+ + + + + + + + + +
{% trans "Name" %}{{ date.group.super_group.name }}
{% trans "Description" %}{{ date.group.super_group.description }}
+
+
+

{% trans "Date-information" %}

+ + + + + + + + + +
{% trans "Date" %}{{ date.date }}
{% trans "probable length in minutes" %}{{ date.probable_length }}
+
+
+

{% trans "Location-information" %}

+ + + + {% trans "no meeting point specified" as no_meeting_point %} + + +
{% trans "Location" %}{{ date.group.location|default:no_meeting_point }}
+
+
+{% trans "Back" %} +{% endblock %} diff --git a/kalendar/tests/__init__.py b/kalendar/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/kalendar/translation.py b/kalendar/translation.py new file mode 100644 index 00000000..f5e39931 --- /dev/null +++ b/kalendar/translation.py @@ -0,0 +1,11 @@ +from modeltranslation.translator import TranslationOptions, translator + +from kalendar.models import Location + + +class LocationTranslationOptions(TranslationOptions): + fields = ("shortname", "address") + required_languages = ("en", "de") + + +translator.register(Location, LocationTranslationOptions) diff --git a/kalendar/urls.py b/kalendar/urls.py new file mode 100644 index 00000000..b4268b32 --- /dev/null +++ b/kalendar/urls.py @@ -0,0 +1,45 @@ +from django.urls import include, path +from django.views.generic import RedirectView + +from . import views +from .feeds import PersonalMeetingFeed + +app_name = "kalendar" +urlpatterns = [ + path("", RedirectView.as_view(pattern_name="kalendar:dashboard"), name="main_index"), + path("dashboard/", views.dashboard, name="dashboard"), + path( + "user/", + include( + [ + path( + "matching/", + include([]), + ), + path("/ical/", PersonalMeetingFeed(), name="ical_personal"), + path("/view//", views.view_date_public, name="view_date_public"), + ], + ), + ), + path( + "management/", + include( + [ + path( + "list/", + include( + [ + path("future/", views.list_future_dates, name="list_future_dates"), + path("chronologically/", views.list_dates, name="list_dates"), + path("grouped/", views.list_dates_grouped, name="list_dates_grouped"), + ], + ), + ), + path("add//", views.add_date, name="add_date"), + path("edit//", views.edit_date, name="edit_date"), + path("delete//", views.del_date, name="del_date"), + path("view//", views.view_date, name="view_date"), + ], + ), + ), +] diff --git a/kalendar/views.py b/kalendar/views.py new file mode 100644 index 00000000..f04c1322 --- /dev/null +++ b/kalendar/views.py @@ -0,0 +1,110 @@ +from typing import Any +from uuid import UUID + +from django import forms +from django.contrib import messages +from django.contrib.auth.decorators import login_required, permission_required +from django.core.handlers.wsgi import WSGIRequest +from django.db.models import QuerySet +from django.http import Http404, HttpResponse +from django.shortcuts import get_object_or_404, redirect, render +from django.utils.translation import gettext_lazy as _ + +from kalendar.forms import DateForm +from kalendar.models import Date, DateGroup, DateGroupSubscriber, DateSubscriber +from settool_common.models import get_semester, Semester +from tutors.models import Event, Tutor + + +@login_required +def dashboard(request: WSGIRequest) -> HttpResponse: + context: dict[str, Any] = {} + return render(request, "kalendar/dashboard.html", context) + + +@permission_required("tutors.edit_tutors") +def list_future_dates(request: WSGIRequest) -> HttpResponse: + dates: list[Date] = [date for date in Date.objects.order_by("date").all() if date.is_in_future] + context = {"dates": dates} + return render(request, "kalendar/management/list/chronological.html", context) + + +@permission_required("tutors.edit_tutors") +def list_dates(request: WSGIRequest) -> HttpResponse: + dates: QuerySet[Date] = Date.objects.order_by("date").all() + context = {"dates": dates, "show_past": True} + return render(request, "kalendar/management/list/chronological.html", context) + + +@permission_required("tutors.edit_tutors") +def list_dates_grouped(request: WSGIRequest) -> HttpResponse: + semester: Semester = get_object_or_404(Semester, pk=get_semester(request)) + events = Event.objects.filter(semester=semester).all() + context = {"events": events} + return render(request, "kalendar/management/list/grouped.html", context) + + +@permission_required("tutors.edit_tutors") +def add_date(request: WSGIRequest, date_group_pk: UUID) -> HttpResponse: + date_group: DateGroup = get_object_or_404(DateGroup, pk=date_group_pk) + + form = DateForm(request.POST or None, date_group=date_group) + if form.is_valid(): + date: Date = form.save() + messages.success(request, _("Successfully added date {date}.").format(date=date)) + return redirect("kalendar:main_index") + + context = {"date_group": date_group, "form": form} + return render(request, "kalendar/management/add_date.html", context) + + +@permission_required("tutors.edit_tutors") +def edit_date(request: WSGIRequest, date_pk: UUID) -> HttpResponse: + date: Date = get_object_or_404(Date, pk=date_pk) + + form = DateForm(request.POST or None, instance=date, date_group=date.group) + if form.is_valid(): + edited_date: Date = form.save() + messages.success(request, _("Successfully edited date {date}.").format(date=edited_date)) + return redirect("kalendar:main_index") + + context = {"date": date, "form": form} + return render(request, "kalendar/management/edit_date.html", context) + + +@permission_required("tutors.edit_tutors") +def del_date(request: WSGIRequest, date_pk: UUID) -> HttpResponse: + date: Date = get_object_or_404(Date, pk=date_pk) + + form = forms.Form(request.POST or None) + if form.is_valid(): + date.delete() + messages.success(request, _("Deleted date {date}.").format(date=date)) + return redirect("kalendar:main_index") + + context = {"date": date, "form": form} + return render(request, "kalendar/management/delete_date.html", context) + + +@permission_required("tutors.edit_tutors") +def view_date(request: WSGIRequest, date_pk: UUID) -> HttpResponse: + date: Date = get_object_or_404(Date, pk=date_pk) + + context = {"date": date} + return render(request, "kalendar/management/view_date.html", context) + + +def view_date_public(request: WSGIRequest, tutor_uuid: UUID, date_pk: UUID) -> HttpResponse: + tutor: Tutor = get_object_or_404(Tutor, pk=tutor_uuid) + date: Date = get_object_or_404(Date, pk=date_pk) + if ( + not DateSubscriber.objects.filter(tutor=tutor, date=date).exists() + and not DateGroupSubscriber.objects.filter(tutor=tutor, date=date.group).exists() + ): + raise Http404() + + context = { + "date": date, + "tutor": tutor, + } + return render(request, "kalendar/user/view_date_public.html", context) diff --git a/locale/de/LC_MESSAGES/django.mo b/locale/de/LC_MESSAGES/django.mo index 46b7ad6630e041d678a1d7c9f26a963df29d190e..387ec6f7dc7c1eb6bbaf5fccb97cfc889ea466f3 100644 GIT binary patch delta 816 zcmXxizfV(P6vpufTH)HuZ*GMO;w34dAeuNi7!$&Vo1qXFg~S>mD5SB2F_Ac$5Eu%J zk-@>gfW^V+A{&W`69aK1nka)42@HIn%k4?e{k*sLZO?g6`*r4cX7yKBc1y${*-sW5 zq+58Skqhw~=d#ite1KEU(oNhB`3&y0OBdO9)Ge{pG4e=3B(YUdYCkq{U&H|%L$GMr=)9*3BFW7>+=!Sl#9w09ra`C| z@Bq5r5Oz~v<6htdy7LlJOOMeVS22fg&=Yuvp3pk_i#BltKVS~OqYM8)f8PQ6CppAk z{Ew{|jxqlnivo)b7SSKLh92#0jBy(M6I8JQFOwWT87m~uyx)UygKo;-%vjnja?75PN9{B62dg=cduH9}BA4=(WuXVhk;5jKs&F11 z%%O5wM)tFn=>979a=wN&xPwY$FLEC_?2t+KFHi~GU<~gu!T$Eh!so&ZssnCw`GEmE z=6o3I$mkW7*;j@&nSA$9Ek5;`HE`=>uw3~i)^UD^+V~l@@dv8V5>h#>MD{n2MJ*1X z_83LIIEfBUqbgrObtZ@Up(X6c6?AYL^*g($Pf)-%JivOqLI)pE9eBaKUU*}nR+iAm z41WN%d<-j4YlSw~H})9{8W2{d@2P~;y$(i-p$@b&6zUF53&V+|*@~%fR+g$*Sv7`N x%(}3Zp;o0C>VT3}hg6M1RsKf{i(toH4^9)Q;%aQRv3TwMHV4;UGI-B!{sD{lM;ZVC diff --git a/locale/de/LC_MESSAGES/django.po b/locale/de/LC_MESSAGES/django.po index cf35a7ae..b162f2e5 100644 --- a/locale/de/LC_MESSAGES/django.po +++ b/locale/de/LC_MESSAGES/django.po @@ -31,6 +31,10 @@ msgstr "SET-Tutoren" msgid "Guided tours of the institutes" msgstr "Institutsführungen" +#: templates/base.html +msgid "SET-Calendar" +msgstr "SET-Kalender" + #: templates/base.html msgid "Settings" msgstr "Einstellungen" @@ -269,6 +273,9 @@ msgstr "Keine" msgid "Unknown" msgstr "Unbekannt" +#~ msgid "Description:" +#~ msgstr "Beschreibung" + #~ msgid "May the SET be ever in yor favour" #~ msgstr "May the SET be ever in yor favour" diff --git a/requirements.txt b/requirements.txt index aa556d4e..b231b4ac 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,8 +6,10 @@ django-bootstrap-modal-forms~=2.2.0 django-bootstrap5~=21.3 django-compref-keycloak~=1.2.0 django-crontab~=0.7.1 +django-ical~=1.8.3 django-modeltranslation~=0.17.5 django-tex~= 1.1.10 +icalendar~=4.0.9 Pillow~=9.1.0 python-dateutil~=2.8.2 qrcode~=7.3 diff --git a/settool/settings/base_settings.py b/settool/settings/base_settings.py index a29fc90b..a75c799b 100644 --- a/settool/settings/base_settings.py +++ b/settool/settings/base_settings.py @@ -51,6 +51,7 @@ "bags", "fahrt", "tutors", + "kalendar", ] MIDDLEWARE = [ diff --git a/settool/urls.py b/settool/urls.py index a0e23c11..fd43844b 100644 --- a/settool/urls.py +++ b/settool/urls.py @@ -25,7 +25,9 @@ # SET-Fahrt path("fahrt/", include("fahrt.urls")), path("f/", RedirectView.as_view(pattern_name="fahrt:signup"), name="short_fahrt_signup"), - # Tutoren + # kalendar (typo to avoid name-clash) + path("calendar/", include("kalendar.urls")), + # tutors path("tutors/", include("tutors.urls")), path("c/", RedirectView.as_view(pattern_name="tutors:collaborator_signup"), name="short_collaborator_signup"), path("t/", RedirectView.as_view(pattern_name="tutors:tutor_signup"), name="short_tutor_signup"), diff --git a/settool_common/cron.py b/settool_common/cron.py index 601ddb27..7880dfcf 100644 --- a/settool_common/cron.py +++ b/settool_common/cron.py @@ -107,7 +107,8 @@ def anonymize_fahrt(semester: Semester, log_name: str) -> bool: semester_participants.exclude(status=m_fahrt.Participant.STATUS_CONFIRMED).delete() participant: m_fahrt.Participant for participant in semester_participants.all(): - participant.registration_time = timezone.now() + participant.created_at = timezone.now() + participant.updated_at = timezone.now() participant.firstname = f"f {participant.pk}" participant.surname = f"l {participant.pk}" participant.birthday = timezone.now().date() @@ -126,18 +127,23 @@ def anonymize_fahrt(semester: Semester, log_name: str) -> bool: def anonymize_guidedtours(semester: Semester, log_name: str) -> bool: semester_tours: QuerySet[m_guidedtours.Tour] = m_guidedtours.Tour.objects.filter(semester=semester) most_recent_tour: Optional[m_guidedtours.Tour] = semester_tours.order_by("date").last() - if most_recent_tour and date_is_too_old(most_recent_tour.date, log_name): - # guidedtours is save to anonymize for this semester - participant: m_guidedtours.Participant - for participant in m_guidedtours.Participant.objects.filter(tour__semester=semester).all(): - participant.firstname = f"f {participant.pk}" - participant.surname = f"l {participant.pk}" - participant.email = f"{participant.pk}@example.com" - participant.phone = f"-1 000 {participant.pk}" - participant.time = timezone.now() - participant.save() - return True - return False + if not most_recent_tour or not most_recent_tour.associated_meetings: + return False + successfully_anonimised = False + for date_obj in most_recent_tour.associated_meetings.dates: + if date_obj and date_is_too_old(date_obj, log_name): + # guidedtours is save to anonymize for this semester + participant: m_guidedtours.Participant + for participant in m_guidedtours.Participant.objects.filter(tour__semester=semester).all(): + participant.firstname = f"f {participant.pk}" + participant.surname = f"l {participant.pk}" + participant.email = f"{participant.pk}@example.com" + participant.phone = f"-1 000 {participant.pk}" + participant.updated_at = timezone.now() + participant.created_at = timezone.now() + participant.save() + successfully_anonimised = True + return successfully_anonimised # noqa: R504 def anonymize_tutors(semester: Semester, log_name: str) -> bool: @@ -153,7 +159,8 @@ def anonymize_tutors(semester: Semester, log_name: str) -> bool: tutor.first_name = f"f {tutor.pk}" tutor.last_name = f"l {tutor.pk}" tutor.email = f"{tutor.pk}@example.com" - tutor.registration_time = timezone.now() + tutor.created_at = timezone.now() + tutor.updated_at = timezone.now() tutor.birthday = timezone.now() tutor.matriculation_number = str(tutor.pk).zfill(8)[:8] tutor.comment = "" diff --git a/settool_common/tests/test_email.py b/settool_common/tests/test_email.py index 1f462502..173e200e 100644 --- a/settool_common/tests/test_email.py +++ b/settool_common/tests/test_email.py @@ -244,14 +244,21 @@ def setUpTestData(cls) -> None: course_bundle=course_bundle_info, ) for i in range(4): - tour_models.Tour.objects.create( + tour = tour_models.Tour.objects.create( semester=common_models.current_semester(), - name="tour today", - date=cls.today + timedelta(days=i), + name=f"tour {i}", capacity=10, open_registration=cls.todatetime, close_registration=cls.todatetime, ) + meeting = tour.associated_meetings + if not meeting: + raise ValueError("No associated_meeting for Tour") + kalendar_m.Date.objects.create( + group=meeting, + date=cls.today + timedelta(days=i), + probable_length=60, + ) for tour in tour_models.Tour.objects.all(): for i in range(10): tour_models.Participant.objects.create( diff --git a/templates/base.html b/templates/base.html index 833f23ad..27f5efee 100644 --- a/templates/base.html +++ b/templates/base.html @@ -102,6 +102,10 @@ >{% trans "Guided tours of the institutes" %} {% endif %} {% if user.is_authenticated %} + {% trans "SET-Calendar" %}
{% endblock %} + {% block set_common_content_overide %}
{% block set_common_headercontent %}{% endblock %} @@ -28,6 +29,7 @@ {% block set_common_content %}{% endblock %}
+ {% endblock %} {% block navigation-override-2 %}
From ad7afdc4f6dd0cf07af5f6ef30f61625b177f630 Mon Sep 17 00:00:00 2001 From: Frank Elsinga Date: Thu, 24 Mar 2022 16:24:21 +0100 Subject: [PATCH 21/30] migrated tutors to support the kalendar --- tutors/forms.py | 36 +---- tutors/locale/de/LC_MESSAGES/django.mo | Bin 15765 -> 15776 bytes tutors/locale/de/LC_MESSAGES/django.po | 115 ++++++++------- .../0009_add_associated_meetings_and_more.py | 118 +++++++++++++++ .../0010_migrate_start_end_to_dategroup.py | 32 +++++ .../0011_migrate_meeting_point_to_locaton.py | 42 ++++++ .../0014_remove_tutor_registration_time.py | 14 ++ tutors/models.py | 135 +++++++++++++++--- tutors/templates/tutors/dashboard.html | 20 +-- tutors/templates/tutors/event/delete.html | 20 +-- tutors/templates/tutors/event/list.html | 10 +- tutors/templates/tutors/event/view.html | 20 +-- tutors/templates/tutors/task/delete.html | 10 +- tutors/templates/tutors/task/list.html | 10 +- tutors/templates/tutors/task/view.html | 10 +- tutors/templates/tutors/tutor/delete.html | 10 +- tutors/templates/tutors/tutor/view.html | 14 +- tutors/translation.py | 4 +- tutors/views.py | 60 ++++---- 19 files changed, 493 insertions(+), 187 deletions(-) create mode 100644 tutors/migrations/0009_add_associated_meetings_and_more.py create mode 100644 tutors/migrations/0010_migrate_start_end_to_dategroup.py create mode 100644 tutors/migrations/0011_migrate_meeting_point_to_locaton.py create mode 100644 tutors/migrations/0014_remove_tutor_registration_time.py diff --git a/tutors/forms.py b/tutors/forms.py index ea75a6fb..ba831d8d 100644 --- a/tutors/forms.py +++ b/tutors/forms.py @@ -91,11 +91,7 @@ def __init__(self, *args, **kwargs): class EventAdminForm(SemesterBasedModelForm): class Meta: model = Event - exclude = ["semester", "name", "description", "meeting_point"] - widgets = { - "begin": DateTimePickerInput(format="%Y-%m-%d %H:%M"), - "end": DateTimePickerInput(format="%Y-%m-%d %H:%M"), - } + exclude = ["semester", "name", "description", "associated_meetings"] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -122,26 +118,11 @@ def save(self, commit=True): return instance - def clean(self): - cleaned_data = super().clean() - - begin = cleaned_data.get("begin") - end = cleaned_data.get("end") - if begin and end and end <= begin: - msg = _("Begin time must be before end time.") - self.add_error("begin", msg) - self.add_error("end", msg) - return cleaned_data - class TaskAdminForm(SemesterBasedModelForm): class Meta: model = Task - exclude = ["semester", "name", "description", "meeting_point", "tutors"] - widgets = { - "begin": DateTimePickerInput(format="%Y-%m-%d %H:%M"), - "end": DateTimePickerInput(format="%Y-%m-%d %H:%M"), - } + exclude = ["semester", "name", "description", "tutors", "associated_meetings"] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -182,17 +163,6 @@ def save(self, commit=True): return instance - def clean(self): - cleaned_data = super().clean() - - begin = cleaned_data.get("begin") - end = cleaned_data.get("end") - if begin and end and end <= begin: - msg = _("Begin time must be before end time.") - self.add_error("begin", msg) - self.add_error("end", msg) - return cleaned_data - class TaskAssignmentForm(SemesterBasedModelForm): class Meta: @@ -252,7 +222,7 @@ def clean(self): begin = cleaned_data.get("open_registration") end = cleaned_data.get("close_registration") if begin and end and end <= begin: - msg = _("Begin time must be before end time.") + msg = _("open_registration time must be before close_registration time.") self.add_error("open_registration", msg) self.add_error("close_registration", msg) return cleaned_data diff --git a/tutors/locale/de/LC_MESSAGES/django.mo b/tutors/locale/de/LC_MESSAGES/django.mo index 6627ecbec9d31ef1230de868de315f85fee0718f..46ca85967e93e2f2fdd62b85e8d0a3a48afbcbb0 100644 GIT binary patch delta 4580 zcmZYBdsI}%0mt#-DIifnQ4qwnL4y%RKolh;YBbUKs?kylF-qkMTVYpWQ54(Nh$gWy z+JGi%S~aHD)Mv9)W32U+(&n^DV~$NyYbCAKlYfk@r$$dbZTkJ~9eOzF9DnIr=3SLQ$-fAf4Nd_4eYfQHYW2Psn)tKsD#`HihCSfD?#Elq-JCJ{7FCScL z+OR*KM1B7e>iiDuhBs~d9i+ct_%neok~z{n2Vguth`MkTs-sD$^JXB|nt7-jdaX-Q z16_f-Zi96jMsvIm)&Bv!58uMxx_>*B88lqNKA4^C&Y%Q!<9VnXRUrRNH6QAz2{ohV z@P6Ea!>|o|*p2&}7Aonu6LrJms2Q9=4d?^Z4X+`6nOoQ$ zWB5|-N!S-NQSF78iW4v&%dh}9;iGsK`DbDnT~JHYpNf`rAZm$6+T&u>NT=KOQjFoa z0=0xy_WK6ZGY?`v+<`jpO$;wJY6aU-{l91ZB!%@?!&Q62=cs|)wkO0gEp?oPOxon& zi#Q%Nv)^M8Msn9kEJh9ZVbn@ZL9Jvt(xq8~+9PYNTl%s7T8dpXXryhZB|L{}e;;+@ ztH|~+cTnfWv%b1fKUDi5EWrZQb<0raZ9uJXD>7EI9W{`n*7hJ3&G<6vS>8Zh@Gn%y zU!n$-l;+NK0BVnvq6V-4b)FY>UK6UHXHf%awa2g7<0GgEokFdA@Pci)j2ijhQ60yy z?u)P|>W1~G-M$(%gJ#t4#SVLX9QBOfL!EaK)z4+r_1A6tP1FGIxCTu$TR*&uQJW?c z^^CGm9TuQQJPz}4CKlluRL3XqYj_T`@EV@N1V)#OzrmS!7j@m^1Kihi4HoPDKS*T( zUwnqyI3?2<2bbaZ@MFxu(t+-c)}cDuhkB+Rs7Lk%YM}Q}kD{l?olrXJQ4F%?q9#~^ zu|X=+sb~e}SQnypcP(mWyRGMO5XaY1GmqmyyF3YXeHLmUd3X&+<5P95&9Y%@l2{43OozKi<%V?Ly!rTh%Fl=o1Z zq#y5yW;ztr@i^2>=c2x^#QShL>iTBX`7hh=_hUTAZ=a64zHkQe8Ya9J>SN(9jfCt)Dr#!i!qyzwOEPk z@j7a0YdCo}HlPOlYut{1LViL_Ly0lm$2>=y+GFvHF7?jpQ7RrXh-ee5Xw#|ma{1rqF=}(kCXzu`kP{l`%T%=S^m=rbtyG4P zG_u$>{=lkTUPiu7<`ex@YawrtdJ;)g=D3(|Viws)@`-kT9XUeIhu4Y9>m-46Cy$XI z5&c$ABx{IDIeD8nWD9wbbQZnSyU9!B6{519#D{C{$8jjM$NjO2d|%J$m$q>gK2DaC zwYE*@hexH9Y$N;03*;@Ll1(;}JaUKx$QV*XRHDcz;?n`;6nTUUAi96}KbhljHu*W( zMSez9hPs%^7_#-=_+zq{Y$xYPEm=sOA)849=~^1?!3gUNY}H;pO{Fq)sMnt(Qat{} zoM8OYaQeLhd6Q}1yYl;i6hR()t>_VB1Qjl;9tU+bBxad|53FdbjZ zQ^OAS313&^1XgvuE-t4yDl{`UDk4-na(_fidj7&tbAD06>`6|Y=c^<14KH)Nfu#+; hC9Fq7eLc70CQhyA|3}^B%o?v#<@iRloP6lge*t%M2KE2| delta 4542 zcmYk<3v^V)9mny>6B0-uK}aA#7DFBo6J7~~Ja{BQkq{^*hkQ(~z?#aJm6hzkX+2Cx=Sebv%Kdr*pq1$v~XhqfMV6?*#p?M}4!wZSdZvRh=tbSwit4BhbzKMQfqPI7?6w|9 z4fF)+zW1#cFp~P;QT_i5g1`kHJ`hMOcfa_$YDGE8Pvex+t91*NnWm13{0Z$vG{Hrw8b z%FJF=3isLb$53m23M245>bi@l>#m?O_c^NnudUHT$iEsA7`84Ljv7cBsyz?Y@pL39 zrUtj62Q{OcSb*b`j0wjE)PPr_GPDMj!N-wjnrBdZcK^*fmLDwE)9jJlqLS5HoJ%+mex2XQkVsL`Ce%;pnHz{a_{m8RYo{FlE zMa^Uy>H$@FKQ2Q(_!Mf>zKxp68Pxa3Uu`{#ovb~Of?C>9sOz&({f$TZ@tZ<>q6{_C z3hQF4gSx?s>aZ2HhMQ0wKZP1_Cr-cvs0>{|4K#^S-HI+8jb->c*5f$6{~05UnMcDy z)Qw%3jTf*8V@Dcu58jSh_#!&^JA4T9Q;oR|kDwm(396q+S7@zEP)k{j8t4+#(llX| zpTar{S{uLh3DgXCp=S7VRA#!ZJ*eIP4(fsbwT>D^nyHubp_wm7y#>vv`!}HmvK=qu z)99Z{VLiQ!#Uq%BA7d`|;p=P?PDUR#Vw29J23AQv^?FsK_COtyRkI#X;7)sfQF`dS z8})i`L@nvIbn>qoI%&{|k75S)pf=-WRLZW~^O2lX2XRRD%}CViRDv2nHR>&BwC7t< z*Y860a{$@z<|Jw}ewac2_25f1=*DaI!kg9v(xD5}Q61%=W;_j(aUQNkH);SEF$%xH zJiLLL$OJY{A1p=y_YcOo0uypOu!Gn|28WB8K6Qq+AfU|&3l ztcp2i+do3B{bf`?|3>YpTUclHmxjSTS4cr8W}{NN2(?+(Ag{97j0N}{=Hs7mKF0Bi zMB*}3ChkLJXoIb{p)%EF+mB*2_0y;azlX7U|39L@n`W+{I*hz6l)@O)Kn9?09FE*z z(yY0tlukvZd?sojci8jyVk-3&s5Re;N3aVEu_i|uW&JxSM9}a@R4PA2t>vev6#fU5 z!v4Gx+9O%0nHHitu0YMyvFBSbj{2jh`#VtAzhuw9f-%(J!2vwqoTX5PmoXm4j0?@Q zz*>gN%skW{sX`5C9cqa>P-}V^^_Be=DkJBS-!F3s)$doRna7L|oi9hfQagu&I$VId zp$RpEEjR$%Q8V3(%FOGi>;8aRx@*`UWBC5nK!#XzQ0?Vdg9}jo9zbQ_)d}QZ9lc3| zIy#T4e}$Ss;>6I}Cu2YAsi+&XQ61%@maNRy=b{E&gL-hIJ^vu8zlSjaA3;sLb0YcI zjCRw&B+OBqj8{+{kKpT%FENvW>>E>opWs?#jHZT{+>LFh`#;AVOy^aZhE;e!?m(UY z0_!o8hiK`a^ixnr1E+)@REl$`yHP2B8MXHB;$ZweYKB))Gx`#><~LCpN#tD}gf7(E zk%ww8w$8y~>WeW8{T&oCDfD0}Ucp3+o*LR*Ls73?9;(CXI3tYL3};e*j2)$!y^ZSl zYg7gcibB7h&G>!l`*92AaFH?_KnChJuTgl0h7WKoHkR-$hdYSVgpOB)Y@v@ z_u00mtv|5t!G~=5SJqy%KXfc576ePiyk^b77YRMzv=e$=HW1ouyND{H_jr~g7AuyC)h{oP>; zqgl?@){d`1Wm9N(8Gv$ug6)dfH8hoo-JS%*9+-ldFX0NNxX>!f-)O~wq zWrO2(Jh_2?WadP)7i8}be3@Mo5y%`DAC^+{&Fyvcco&0T?ejJ`-WHELkT-r`a1`ez S)I|mdQ82M2w*9T*bN>UE-`+R? diff --git a/tutors/locale/de/LC_MESSAGES/django.po b/tutors/locale/de/LC_MESSAGES/django.po index 9ff342ff..155ec9ca 100644 --- a/tutors/locale/de/LC_MESSAGES/django.po +++ b/tutors/locale/de/LC_MESSAGES/django.po @@ -24,8 +24,10 @@ msgid "I want to receive ECTS for my work as a SET-Colaborator." msgstr "Ich möchte ECTS für meine Arbeit als SET-Mitarbeiter erhalten." #: tutors/forms.py -msgid "Begin time must be before end time." -msgstr "Der Startzeitpunkt muss vor dem Endzeitpunkt liegen." +msgid "open_registration time must be before close_registration time." +msgstr "" +"Der open_registration-Zeitpunkt muss vor dem close_registration-Zeitpunkt " +"liegen." #: tutors/forms.py msgid "Tutors (selected have not yet received this email)" @@ -157,10 +159,6 @@ msgstr "T-Shirt Größe" msgid "Tshirt as Girls cut" msgstr "T-Shirt als Girls cut" -#: tutors/models.py -msgid "Registration Time" -msgstr "Zeitpunkt der Registrierung" - #: tutors/models.py msgid "Tutor Answers" msgstr "Antworten des Tutors" @@ -194,39 +192,13 @@ msgstr "Name" msgid "Description" msgstr "Beschreibung" -#: tutors/models.py tutors/templates/tutors/event/delete.html -#: tutors/templates/tutors/event/list.html -#: tutors/templates/tutors/event/view.html -#: tutors/templates/tutors/task/delete.html -#: tutors/templates/tutors/task/list.html -#: tutors/templates/tutors/task/view.html -#: tutors/templates/tutors/tutor/delete.html -#: tutors/templates/tutors/tutor/view.html -msgid "Begin" -msgstr "Start" - -#: tutors/models.py tutors/templates/tutors/dashboard.html -#: tutors/templates/tutors/event/delete.html -#: tutors/templates/tutors/event/list.html -#: tutors/templates/tutors/event/view.html -#: tutors/templates/tutors/task/delete.html -#: tutors/templates/tutors/task/list.html -#: tutors/templates/tutors/task/view.html -#: tutors/templates/tutors/tutor/delete.html -#: tutors/templates/tutors/tutor/view.html -msgid "End" -msgstr "Ende" +#: tutors/models.py +msgid "no meeting point specified" +msgstr "kein Treffpunkt festgelegt" -#: tutors/models.py tutors/templates/tutors/event/delete.html -#: tutors/templates/tutors/event/list.html -#: tutors/templates/tutors/event/view.html -#: tutors/templates/tutors/task/delete.html -#: tutors/templates/tutors/task/list.html -#: tutors/templates/tutors/task/view.html -#: tutors/templates/tutors/tutor/delete.html -#: tutors/templates/tutors/tutor/view.html -msgid "Meeting Point" -msgstr "Treffpunkt" +#: tutors/models.py +msgid "Associated Meetings" +msgstr "Zugeordnete Meetings" #: tutors/models.py tutors/templates/tutors/dashboard.html #: tutors/templates/tutors/event/delete.html @@ -235,14 +207,6 @@ msgstr "Treffpunkt" msgid "Subjects" msgstr "Studiengänge" -#: tutors/models.py -msgid "Task name" -msgstr "Taskname" - -#: tutors/models.py -msgid "Meeting point" -msgstr "Treffpunkt" - #: tutors/models.py tutors/templates/tutors/task/delete.html #: tutors/templates/tutors/task/list.html #: tutors/templates/tutors/task/view.html @@ -259,6 +223,10 @@ msgstr "Zugelassene Studiengänge" msgid "Requirements" msgstr "Voraussetzungen" +#: tutors/models.py +msgid "Assigned tutors" +msgstr "Eingeteilte Tutoren" + #: tutors/models.py msgid "Tutors (min)" msgstr "Tutoren (min)" @@ -267,10 +235,6 @@ msgstr "Tutoren (min)" msgid "Tutors (max)" msgstr "Tutoren (max)" -#: tutors/models.py -msgid "Assigned tutors" -msgstr "Eingeteilte Tutoren" - #: tutors/models.py msgid "absent" msgstr "abwesend" @@ -395,8 +359,28 @@ msgid "Next upcoming 5 events" msgstr "Nächste 5 anstehende Veranstaltungen" #: tutors/templates/tutors/dashboard.html -msgid "Start" -msgstr "Start" +#: tutors/templates/tutors/event/delete.html +#: tutors/templates/tutors/event/list.html +#: tutors/templates/tutors/event/view.html +#: tutors/templates/tutors/task/delete.html +#: tutors/templates/tutors/task/list.html +#: tutors/templates/tutors/task/view.html +#: tutors/templates/tutors/tutor/delete.html +#: tutors/templates/tutors/tutor/view.html +msgid "Begin of the first meeting" +msgstr "Beginn des ersten meetings" + +#: tutors/templates/tutors/dashboard.html +#: tutors/templates/tutors/event/delete.html +#: tutors/templates/tutors/event/list.html +#: tutors/templates/tutors/event/view.html +#: tutors/templates/tutors/task/delete.html +#: tutors/templates/tutors/task/list.html +#: tutors/templates/tutors/task/view.html +#: tutors/templates/tutors/tutor/delete.html +#: tutors/templates/tutors/tutor/view.html +msgid "End of the last meeting" +msgstr "Ende des letzen meetings" #: tutors/templates/tutors/dashboard.html msgid "Next upcoming 5 tasks" @@ -432,6 +416,17 @@ msgstr "Event hinzufügen" msgid "Delete event" msgstr "Event löschen" +#: tutors/templates/tutors/event/delete.html +#: tutors/templates/tutors/event/list.html +#: tutors/templates/tutors/event/view.html +#: tutors/templates/tutors/task/delete.html +#: tutors/templates/tutors/task/list.html +#: tutors/templates/tutors/task/view.html +#: tutors/templates/tutors/tutor/delete.html +#: tutors/templates/tutors/tutor/view.html +msgid "Meeting Point" +msgstr "Treffpunkt" + #: tutors/templates/tutors/event/delete.html #: tutors/templates/tutors/event/view.html #: tutors/templates/tutors/task/delete.html @@ -878,6 +873,10 @@ msgstr "Ablehnen" msgid "Send email to tutor" msgstr "Sende Email an Tutoren" +#: tutors/templates/tutors/tutor/view.html +msgid "Download tutors ical" +msgstr "ical vom Tutor downloaden" + #: tutors/templates/tutors/tutor/view.html msgid "Assigned Tasks" msgstr "Zugewiesene Tasks" @@ -993,6 +992,18 @@ msgstr "" "Batch-Aktionen versenden keine elektronischen Fernbriefe (eMails). Sie " "verändern lediglich den Status der StudentInnen." +#~ msgid "Registration Time" +#~ msgstr "Zeitpunkt der Registrierung" + +#~ msgid "Start" +#~ msgstr "Start" + +#~ msgid "End" +#~ msgstr "Ende" + +#~ msgid "Begin" +#~ msgstr "Start" + #~ msgid "I accept the terms and conditions of the following privacy policy:" #~ msgstr "Ich stimme der folgenden Datenschutzerklärung zu:" diff --git a/tutors/migrations/0009_add_associated_meetings_and_more.py b/tutors/migrations/0009_add_associated_meetings_and_more.py new file mode 100644 index 00000000..61475f81 --- /dev/null +++ b/tutors/migrations/0009_add_associated_meetings_and_more.py @@ -0,0 +1,118 @@ +# Generated by Django 4.0.3 on 2022-03-23 00:34 + +import django.db.models.deletion +from django.db import migrations, models + + +def initialise_associated_meetings(apps, _): + DateGroup = apps.get_model("kalendar", "dategroup") + + Task = apps.get_model("tutors", "task") + Event = apps.get_model("tutors", "event") + for instance in list(Event.objects.all()) + list(Task.objects.all()): + date_group = instance.associated_meetings + if not date_group: + date_group = DateGroup.objects.create() + instance.associated_meetings = date_group + instance.save() + + +class Migration(migrations.Migration): + dependencies = [ + ("kalendar", "0001_initial"), + ("tutors", "0008_auto_20220321_1851"), + ] + + operations = [ + migrations.AddField( + model_name="task", + name="associated_meetings", + field=models.OneToOneField( + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to="kalendar.dategroup", + verbose_name="Associated Meetings", + ), + ), + migrations.AddField( + model_name="event", + name="associated_meetings", + field=models.OneToOneField( + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to="kalendar.dategroup", + verbose_name="Associated Meetings", + ), + ), + migrations.RunPython(initialise_associated_meetings), + migrations.AddField( + model_name="event", + name="event_leader", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="tutors_event_event_leader", + to="tutors.tutor", + ), + ), + migrations.AddField( + model_name="event", + name="meeting_chairperson", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="tutors_event_meeting_chairperson", + to="tutors.tutor", + ), + ), + migrations.AddField( + model_name="task", + name="meeting_chairperson", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="tutors_task_meeting_chairperson", + to="tutors.tutor", + ), + ), + migrations.AddField( + model_name="task", + name="task_leader", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="tutors_task_task_leader", + to="tutors.tutor", + ), + ), + migrations.AlterField( + model_name="task", + name="name", + field=models.CharField(max_length=250, verbose_name="Name"), + ), + migrations.AlterField( + model_name="task", + name="name_de", + field=models.CharField(max_length=250, null=True, verbose_name="Name"), + ), + migrations.AlterField( + model_name="task", + name="name_en", + field=models.CharField(max_length=250, null=True, verbose_name="Name"), + ), + migrations.AlterField( + model_name="task", + name="tutors", + field=models.ManyToManyField( + blank=True, + related_name="tutors_task_tutors", + through="tutors.TutorAssignment", + to="tutors.tutor", + verbose_name="Assigned tutors", + ), + ), + ] diff --git a/tutors/migrations/0010_migrate_start_end_to_dategroup.py b/tutors/migrations/0010_migrate_start_end_to_dategroup.py new file mode 100644 index 00000000..b1c736a1 --- /dev/null +++ b/tutors/migrations/0010_migrate_start_end_to_dategroup.py @@ -0,0 +1,32 @@ +# Generated by Django 4.0.3 on 2022-03-23 00:35 +from math import ceil + +from django.db import migrations + + +def migrate_start_end_to_dategroup(apps, _): + Date = apps.get_model("kalendar", "date") + + Task = apps.get_model("tutors", "task") + Event = apps.get_model("tutors", "event") + for instance in list(Event.objects.all()) + list(Task.objects.all()): + date_group = instance.associated_meetings + start = instance.begin + end = instance.end + time_length = ceil((end - start).seconds / 60) + Date.objects.create(group=date_group, date=start, probable_length=time_length) + + +class Migration(migrations.Migration): + dependencies = [ + ("kalendar", "0001_initial"), + ("tutors", "0009_add_associated_meetings_and_more"), + ] + + operations = [ + migrations.RunPython(migrate_start_end_to_dategroup), + migrations.RemoveField(model_name="event", name="begin"), + migrations.RemoveField(model_name="event", name="end"), + migrations.RemoveField(model_name="task", name="begin"), + migrations.RemoveField(model_name="task", name="end"), + ] diff --git a/tutors/migrations/0011_migrate_meeting_point_to_locaton.py b/tutors/migrations/0011_migrate_meeting_point_to_locaton.py new file mode 100644 index 00000000..3bb86dcd --- /dev/null +++ b/tutors/migrations/0011_migrate_meeting_point_to_locaton.py @@ -0,0 +1,42 @@ +# Generated by Django 4.0.3 on 2022-03-24 15:33 + +from django.db import migrations + + +def migrate_meeting_point_to_locaton(apps, _): + Location = apps.get_model("kalendar", "Location") + + locations: dict[tuple[str, str, str], Location] = {} + + Task = apps.get_model("tutors", "task") + Event = apps.get_model("tutors", "event") + for instance in list(Event.objects.all()) + list(Task.objects.all()): + mp_trans = instance.meeting_point, instance.meeting_point_de, instance.meeting_point_en + date_group = instance.associated_meetings + if mp_trans not in locations: + (meeting_point, meeting_point_de, meeting_point_en) = mp_trans + location = Location.objects.create( + shortname=meeting_point, + shortname_de=meeting_point_de, + shortname_en=meeting_point_en, + ) + locations[mp_trans] = location + date_group.location = locations[mp_trans] + date_group.save() + + +class Migration(migrations.Migration): + dependencies = [ + ("kalendar", "0001_initial"), + ("tutors", "0010_migrate_start_end_to_dategroup"), + ] + + operations = [ + migrations.RunPython(migrate_meeting_point_to_locaton), + migrations.RemoveField(model_name="event", name="meeting_point"), + migrations.RemoveField(model_name="event", name="meeting_point_de"), + migrations.RemoveField(model_name="event", name="meeting_point_en"), + migrations.RemoveField(model_name="task", name="meeting_point"), + migrations.RemoveField(model_name="task", name="meeting_point_de"), + migrations.RemoveField(model_name="task", name="meeting_point_en"), + ] diff --git a/tutors/migrations/0014_remove_tutor_registration_time.py b/tutors/migrations/0014_remove_tutor_registration_time.py new file mode 100644 index 00000000..49574d68 --- /dev/null +++ b/tutors/migrations/0014_remove_tutor_registration_time.py @@ -0,0 +1,14 @@ +# Generated by Django 4.0.3 on 2022-04-05 15:39 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("tutors", "0013_migrate_meeting_point_to_locaton"), + ] + + operations = [ + migrations.RemoveField(model_name="tutor", name="registration_time"), + ] diff --git a/tutors/models.py b/tutors/models.py index 8f83c89a..79063b4a 100644 --- a/tutors/models.py +++ b/tutors/models.py @@ -1,9 +1,17 @@ from dateutil.relativedelta import relativedelta +import datetime +import uuid +from typing import Optional, Type + from django.core.validators import RegexValidator -from django.db import models +from django.db import IntegrityError, models +from django.db.models import QuerySet +from django.db.models.signals import post_save +from django.dispatch import receiver from django.utils import timezone from django.utils.translation import gettext_lazy as _ +import kalendar.models import settool_common.models as common_models from settool_common.models import Semester, Subject @@ -166,7 +174,9 @@ class Meta: tshirt_size = models.CharField(_("Tshirt size"), max_length=5, choices=TSHIRT_SIZES) tshirt_girls_cut = models.BooleanField(_("Tshirt as Girls cut")) - registration_time = models.DateTimeField(_("Registration Time"), auto_now_add=True) + @property + def registration_time(self): + return self.created_at answers = models.ManyToManyField("Question", verbose_name=_("Tutor Answers"), through="Answer", blank=True) @@ -199,12 +209,71 @@ def log(self, user, text): pass -class Event(common_models.UUIDModelBase, common_models.LoggedModelBase, common_models.SemesterModelBase): +class BaseTaskEvent(common_models.UUIDModelBase, common_models.LoggedModelBase, common_models.SemesterModelBase): + class Meta: + abstract = True + name = models.CharField(_("Name"), max_length=250) description = models.TextField(_("Description"), blank=True) - begin = models.DateTimeField(_("Begin")) - end = models.DateTimeField(_("End")) - meeting_point = models.CharField(_("Meeting Point"), max_length=200) + + @property + def meeting_point_str(self) -> str: + if not self.associated_meetings: + raise IntegrityError(f"{self.__class__} {self.id} has no associated_meetings") + if not self.associated_meetings.location: + return _("no meeting point specified") + return str(self.associated_meetings.location) + + associated_meetings = models.OneToOneField( + "kalendar.DateGroup", + verbose_name=_("Associated Meetings"), + null=True, + on_delete=models.SET_NULL, + ) + + @property + def first_datetime(self) -> Optional[datetime.datetime]: + if not self.associated_meetings: + raise IntegrityError(f"{self.__class__} {self.id} has no associated_meetings") + date: Optional[kalendar.models.Date] = self.associated_meetings.date_set.order_by("date").first() + if not date: + return None + return date.date + + @property + def last_datetime(self) -> Optional[datetime.datetime]: + if not self.associated_meetings: + raise IntegrityError(f"{self.__class__} {self.id} has no associated_meetings") + date: Optional[kalendar.models.Date] = self.associated_meetings.date_set.order_by("-date").first() + if not date: + return None + return date.date + datetime.timedelta(minutes=date.probable_length) + + @classmethod + def sorted_by_semester(cls, semester: int) -> list[type["BaseTaskEvent"]]: + all_instances: QuerySet[Type[BaseTaskEvent]] = cls.objects.filter(semester=semester).all() # type: ignore + instances_sorting: list[tuple[datetime.datetime, Type[BaseTaskEvent]]] = [ + (instance.first_datetime, instance) for instance in all_instances if instance.first_datetime # type: ignore + ] + return [instance for (_, instance) in sorted(instances_sorting, key=lambda t: t[0])] + + +class Event(BaseTaskEvent): + meeting_chairperson = models.ForeignKey( + Tutor, + related_name="tutors_event_meeting_chairperson", + null=True, + blank=True, + on_delete=models.SET_NULL, + ) + + event_leader = models.ForeignKey( + Tutor, + related_name="tutors_event_event_leader", + null=True, + blank=True, + on_delete=models.SET_NULL, + ) subjects = models.ManyToManyField(Subject, verbose_name=_("Subjects"), blank=True) @@ -217,24 +286,40 @@ def log(self, user, text): pass def __str__(self) -> str: - return f"{self.name} {self.begin.date()}" + return self.name -class Task(common_models.UUIDModelBase, common_models.LoggedModelBase, common_models.SemesterModelBase): - name = models.CharField(_("Task name"), max_length=250) - description = models.TextField(_("Description"), blank=True) - begin = models.DateTimeField(_("Begin")) - end = models.DateTimeField(_("End")) - meeting_point = models.CharField(_("Meeting point"), max_length=50) - +class Task(BaseTaskEvent): event = models.ForeignKey(Event, verbose_name=_("Event"), on_delete=models.CASCADE) + meeting_chairperson = models.ForeignKey( + Tutor, + related_name="tutors_task_meeting_chairperson", + null=True, + blank=True, + on_delete=models.SET_NULL, + ) + + task_leader = models.ForeignKey( + Tutor, + related_name="tutors_task_task_leader", + null=True, + blank=True, + on_delete=models.SET_NULL, + ) + allowed_subjects = models.ManyToManyField(Subject, verbose_name=_("Allowed Subjects"), blank=True) requirements = models.ManyToManyField("Question", verbose_name=_("Requirements"), blank=True) + + tutors = models.ManyToManyField( + Tutor, + related_name="tutors_task_tutors", + verbose_name=_("Assigned tutors"), + through="TutorAssignment", + blank=True, + ) min_tutors = models.IntegerField(_("Tutors (min)"), blank=True, null=True) max_tutors = models.IntegerField(_("Tutors (max)"), blank=True, null=True) - tutors = models.ManyToManyField(Tutor, verbose_name=_("Assigned tutors"), through="TutorAssignment", blank=True) - def __str__(self) -> str: return str(self.name) @@ -247,6 +332,24 @@ def log(self, user, text): pass +# pylint: disable=unused-argument +@receiver(post_save, sender=Event) +def create_event_meetings(sender, instance, created, **kwargs): + if not instance.associated_meetings: + instance.associated_meetings = kalendar.models.create_associated_meetings() + instance.save() + + +@receiver(post_save, sender=Task) +def create_task_meetings(sender, instance, created, **kwargs): + if not instance.associated_meetings: + instance.associated_meetings = kalendar.models.create_associated_meetings() + instance.save() + + +# pylint: enable=unused-argument + + class TutorAssignment(common_models.LoggedModelBase): tutor = models.ForeignKey(Tutor, on_delete=models.CASCADE) task = models.ForeignKey(Task, on_delete=models.CASCADE) diff --git a/tutors/templates/tutors/dashboard.html b/tutors/templates/tutors/dashboard.html index 33f80890..2f70c609 100644 --- a/tutors/templates/tutors/dashboard.html +++ b/tutors/templates/tutors/dashboard.html @@ -27,17 +27,17 @@
{% trans "Next upcoming 5 events" %}
# {% trans "Name" %} - {% trans "Start" %} - {% trans "End" %} + {% trans "Begin of the first meeting" %} + {% trans "End of the last meeting" %} - {% for event in events %} + {% for event in first_future_five_events %} {{ forloop.counter }}
{{ event.name }} - {{ event.begin }} - {{ event.end }} + {{ event.first_datetime }} + {{ event.last_datetime }} {% endfor %} @@ -55,17 +55,17 @@
{% trans "Next upcoming 5 tasks" %}
# {% trans "Name" %} - {% trans "Start" %} - {% trans "End" %} + {% trans "Begin of the first meeting" %} + {% trans "End of the last meeting" %} - {% for task in tasks %} + {% for task in first_future_five_tasks %} {{ forloop.counter }} {{ task.name }} - {{ task.begin }} - {{ task.end }} + {{ task.first_datetime }} + {{ task.last_datetime }} {% endfor %} diff --git a/tutors/templates/tutors/event/delete.html b/tutors/templates/tutors/event/delete.html index aa5f8e43..83a70276 100644 --- a/tutors/templates/tutors/event/delete.html +++ b/tutors/templates/tutors/event/delete.html @@ -12,12 +12,12 @@ {{ event.name }} - {% trans "Begin" %} - {{ event.begin }} + {% trans "Begin of the first meeting" %} + {{ event.first_datetime }} - {% trans "End" %} - {{ event.end }} + {% trans "End of the last meeting" %} + {{ event.last_datetime }} {% trans "Description" %} @@ -25,7 +25,7 @@ {% trans "Meeting Point" %} - {{ event.meeting_point }} + {{ event.meeting_point_str }} {% trans "Subjects" %} @@ -53,8 +53,8 @@

{% trans "This event has the following tasks which will be deleted" %}:

{% trans "Name" %} - {% trans "Begin" %} - {% trans "End" %} + {% trans "End of the last meeting" %} + {% trans "End of the last meeting" %} {% trans "Meeting Point" %} @@ -62,9 +62,9 @@

{% trans "This event has the following tasks which will be deleted" %}:

{% for task in event.task_set.all %} {{ task.name }} - {{ task.begin }} - {{ task.end }} - {{ task.meeting_point }} + {{ task.first_datetime }} + {{ task.last_datetime }} + {{ task.meeting_point_str }} {% endfor %} diff --git a/tutors/templates/tutors/event/list.html b/tutors/templates/tutors/event/list.html index 90a071df..6c3a3f4d 100644 --- a/tutors/templates/tutors/event/list.html +++ b/tutors/templates/tutors/event/list.html @@ -24,8 +24,8 @@ # {% trans "Name" %} - {% trans "Begin" %} - {% trans "End" %} + {% trans "Begin of the first meeting" %} + {% trans "End of the last meeting" %} {% trans "Meeting Point" %} {% trans "Actions" %} @@ -37,9 +37,9 @@ {{ event.name }} - {{ event.begin }} - {{ event.end }} - {{ event.meeting_point }} + {{ event.first_datetime }} + {{ event.last_datetime }} + {{ event.meeting_point_str }} diff --git a/tutors/templates/tutors/event/view.html b/tutors/templates/tutors/event/view.html index a4c21c08..16b14ce3 100644 --- a/tutors/templates/tutors/event/view.html +++ b/tutors/templates/tutors/event/view.html @@ -13,12 +13,12 @@ {{ event.name }} - {% trans "Begin" %} - {{ event.begin }} + {% trans "Begin of the first meeting" %} + {{ event.first_datetime }} - {% trans "End" %} - {{ event.end }} + {% trans "End of the last meeting" %} + {{ event.last_datetime }} {% trans "Description" %} @@ -26,7 +26,7 @@ {% trans "Meeting Point" %} - {{ event.meeting_point }} + {{ event.meeting_point_str }} {% trans "Subjects" %} @@ -72,8 +72,8 @@

{% trans "This event has the following tasks" %}: # {% trans "Name" %} - {% trans "Begin" %} - {% trans "End" %} + {% trans "Begin of the first meeting" %} + {% trans "End of the last meeting" %} {% trans "Meeting Point" %} @@ -82,9 +82,9 @@

{% trans "This event has the following tasks" %}: {{ forloop.counter }} {{ task.name }} - {{ task.begin }} - {{ task.end }} - {{ task.meeting_point }} + {{ task.first_datetime }} + {{ task.last_datetime }} + {{ task.meeting_point_str }} {% endfor %} diff --git a/tutors/templates/tutors/task/delete.html b/tutors/templates/tutors/task/delete.html index 3a50fff4..ee81bf4f 100644 --- a/tutors/templates/tutors/task/delete.html +++ b/tutors/templates/tutors/task/delete.html @@ -12,12 +12,12 @@ {{ task.name }} - {% trans "Begin" %} - {{ task.begin }} + {% trans "Begin of the first meeting" %} + {{ task.first_datetime }} - {% trans "End" %} - {{ task.end }} + {% trans "End of the last meeting" %} + {{ task.last_datetime }} {% trans "Description" %} @@ -25,7 +25,7 @@ {% trans "Meeting Point" %} - {{ task.meeting_point }} + {{ task.meeting_point_str }} {% trans "Event" %} diff --git a/tutors/templates/tutors/task/list.html b/tutors/templates/tutors/task/list.html index 13de7b00..20da6afa 100644 --- a/tutors/templates/tutors/task/list.html +++ b/tutors/templates/tutors/task/list.html @@ -26,8 +26,8 @@ # {% trans "Event" %} {% trans "Name" %} - {% trans "Begin" %} - {% trans "End" %} + {% trans "Begin of the first meeting" %} + {% trans "End of the last meeting" %} {% trans "Meeting Point" %} {% trans "# Missing Mails" %} {% trans "# Tutors" %} @@ -44,9 +44,9 @@ {{ task.name }} - {{ task.begin }} - {{ task.end }} - {{ task.meeting_point }} + {{ task.first_datetime }} + {{ task.last_datetime }} + {{ task.meeting_point_str }} {% mail_task_count task %} {{ task.tutors.count }}/{{ task.min_tutors|default_if_none:0 }}-{{ task.max_tutors|default_if_none:0 }} diff --git a/tutors/templates/tutors/task/view.html b/tutors/templates/tutors/task/view.html index b7002b5c..67b6ed1f 100644 --- a/tutors/templates/tutors/task/view.html +++ b/tutors/templates/tutors/task/view.html @@ -13,12 +13,12 @@ {{ task.name }} - {% trans "Begin" %} - {{ task.begin }} + {% trans "Begin of the first meeting" %} + {{ task.first_datetime }} - {% trans "End" %} - {{ task.end }} + {% trans "End of the last meeting" %} + {{ task.last_datetime }} {% trans "Description" %} @@ -26,7 +26,7 @@ {% trans "Meeting Point" %} - {{ task.meeting_point }} + {{ task.meeting_point_str }} {% trans "Event" %} diff --git a/tutors/templates/tutors/tutor/delete.html b/tutors/templates/tutors/tutor/delete.html index 51b05607..1b659358 100644 --- a/tutors/templates/tutors/tutor/delete.html +++ b/tutors/templates/tutors/tutor/delete.html @@ -64,8 +64,8 @@

{% trans "The tutor is assigned to the following tasks" %}:

# {% trans "Name" %} - {% trans "Begin" %} - {% trans "End" %} + {% trans "Begin of the first meeting" %} + {% trans "End of the last meeting" %} {% trans "Meeting Point" %} @@ -74,9 +74,9 @@

{% trans "The tutor is assigned to the following tasks" %}:

{{ forloop.counter }} {{ assignment.task.name }} - {{ assignment.task.begin }} - {{ assignment.task.end }} - {{ assignment.task.meeting_point }} + {{ assignment.task.first_datetime }} + {{ assignment.task.last_datetime }} + {{ assignment.task.meeting_point_str }} {% endfor %} diff --git a/tutors/templates/tutors/tutor/view.html b/tutors/templates/tutors/tutor/view.html index e47687c8..a59f8b7d 100644 --- a/tutors/templates/tutors/tutor/view.html +++ b/tutors/templates/tutors/tutor/view.html @@ -123,6 +123,10 @@ class="btn btn-warning" href="{% url "tutors:mail_tutor" tutor.id %}" >{% trans "Send email to tutor" %} + {% trans "Download tutors ical" %} {% trans "Assigned Tasks" %}: # {% trans "Name" %} - {% trans "Begin" %} - {% trans "End" %} + {% trans "Begin of the first meeting" %} + {% trans "End of the last meeting" %} {% trans "Meeting Point" %} @@ -171,9 +175,9 @@

{% trans "Assigned Tasks" %}:

{{ forloop.counter }}
{{ assignment.task.name }} - {{ assignment.task.begin }} - {{ assignment.task.end }} - {{ assignment.task.meeting_point }} + {{ assignment.task.first_datetime }} + {{ assignment.task.last_datetime }} + {{ assignment.task.meeting_point_str }} {% endfor %} diff --git a/tutors/translation.py b/tutors/translation.py index 4a167580..5b06f83a 100644 --- a/tutors/translation.py +++ b/tutors/translation.py @@ -4,12 +4,12 @@ class EventTranslationOptions(TranslationOptions): - fields = ("name", "description", "meeting_point") + fields = ("name", "description") required_languages = ("en", "de") class TaskTranslationOptions(TranslationOptions): - fields = ("name", "description", "meeting_point") + fields = ("name", "description") required_languages = ("en", "de") diff --git a/tutors/views.py b/tutors/views.py index a244af88..ef5797fe 100644 --- a/tutors/views.py +++ b/tutors/views.py @@ -7,7 +7,7 @@ from django.contrib.auth.decorators import permission_required from django.core.handlers.wsgi import WSGIRequest from django.db import IntegrityError -from django.db.models import Count, Q, QuerySet +from django.db.models import Count, QuerySet from django.forms import forms, modelformset_factory from django.http import Http404, HttpResponse, HttpResponseRedirect from django.shortcuts import get_object_or_404, redirect, render @@ -21,6 +21,7 @@ from django_tex.response import PDFResponse from django_tex.shortcuts import render_to_pdf +from kalendar.models import Date from settool_common import utils from settool_common.models import get_semester, Semester, Subject from tutors.forms import ( @@ -39,6 +40,7 @@ ) from tutors.models import ( Answer, + BaseTaskEvent, Event, MailTutorTask, Question, @@ -254,7 +256,7 @@ def list_participants(request: WSGIRequest, status: str) -> HttpResponse: request, "tutors/tutor/list.html", { - "tutors": tutors.order_by("registration_time"), + "tutors": tutors.order_by("created_at"), "status": status, "questions": Question.objects.filter(semester=semester), }, @@ -381,7 +383,7 @@ def edit_event(request: WSGIRequest, uid: UUID) -> HttpResponse: @permission_required("tutors.edit_tutors") def list_event(request: WSGIRequest) -> HttpResponse: semester: Semester = get_object_or_404(Semester, pk=get_semester(request)) - events = Event.objects.filter(semester=semester).order_by("begin") + events = Event.sorted_by_semester(semester.id) return render(request, "tutors/event/list.html", {"events": events}) @@ -450,7 +452,7 @@ def edit_task(request: WSGIRequest, uid: UUID) -> HttpResponse: @permission_required("tutors.edit_tutors") def list_task(request: WSGIRequest) -> HttpResponse: - tasks = Task.objects.filter(semester=get_semester(request)).order_by("begin") + tasks = Task.sorted_by_semester(get_semester(request)) return render(request, "tutors/task/list.html", {"tasks": tasks}) @@ -504,11 +506,16 @@ def view_task(request: WSGIRequest, uid: UUID) -> HttpResponse: messages.success(request, f"Saved Task Assignment {task.name}.") assigned_tutors = task.tutors.all().order_by("last_name") - parallel_task_tutors = Tutor.objects.filter( - Q(task__begin__gte=task.begin) | Q(task__end__lte=task.end), - Q(task__end__gt=task.begin), - Q(task__begin__lt=task.end), - ) + + paralel_dates: set[Date] = set() + for test_date in Date.objects.all(): + if not task.associated_meetings: + raise IntegrityError(f"task {task.id} has no associated_meetings") + for orig_date in task.associated_meetings.date_set.all(): + if not orig_date.intersects(test_date): + paralel_dates.add(test_date) + paralel_tasks = Task.objects.filter(associated_meetings__date__in=paralel_dates) + parallel_task_tutors = Tutor.objects.filter(tutorassignment__task__in=paralel_tasks) unassigned_tutors = ( Tutor.objects.filter(semester=semester, status=Tutor.STATUS_ACCEPTED) .exclude(id__in=assigned_tutors.values("id")) @@ -980,7 +987,7 @@ def batch_accept(request: WSGIRequest) -> HttpResponse: if wanted > accepted_count: need: int = wanted - accepted_count tutor: Tutor - for tutor in active_tutors.order_by("registration_time")[:need]: + for tutor in active_tutors.order_by("created_at")[:need]: tutor_ids.append(tutor.id) if subject not in to_be_accepted: @@ -1026,7 +1033,7 @@ def batch_decline(request: WSGIRequest) -> HttpResponse: keep: int = max(wanted - accepted_count + waitlist, 0) - for tutor in active_tutors.order_by("registration_time")[keep:]: + for tutor in active_tutors.order_by("created_at")[keep:]: tutor_ids.append(tutor.id) if subject not in to_be_declined: @@ -1082,6 +1089,18 @@ def _gather_batch_parameters( return semester, tutors_active, tutors_accepted_cnt, assignment_wishes, errors +def get_first_future_five(cls: type[BaseTaskEvent], semester_id: int) -> list[type[BaseTaskEvent]]: + sorted_instances: list[type[BaseTaskEvent]] = cls.sorted_by_semester(semester_id) + + future_instances = [] + for inst in sorted_instances: + if inst.last_datetime and inst.last_datetime < timezone.now(): # type:ignore + future_instances.append(inst) + if len(future_instances) == 5: + break + return future_instances + + @permission_required("tutors.edit_tutors") def dashboard(request: WSGIRequest) -> HttpResponse: semester: Semester = get_object_or_404(Semester, pk=get_semester(request)) @@ -1100,21 +1119,14 @@ def dashboard(request: WSGIRequest) -> HttpResponse: assignment_wish_counter.wanted, ) - events = ( - Event.objects.filter(semester=semester) - .filter(Q(begin__gt=timezone.now()) | Q(end__gt=timezone.now())) - .order_by("begin")[:5] - ) - tasks = ( - Task.objects.filter(semester=semester) - .filter(Q(begin__gt=timezone.now()) | Q(end__gt=timezone.now())) - .order_by("begin")[:5] - ) + first_future_five_events: list[type[BaseTaskEvent]] = get_first_future_five(Event, semester.id) + first_future_five_tasks: list[type[BaseTaskEvent]] = get_first_future_five(Task, semester.id) missing_mails = 0 + task: Task for task in Task.objects.filter(semester=semester): missing_mails += ( - Tutor.objects.filter(task=task) + Tutor.objects.filter(tutorassignment__task=task) .exclude(id__in=MailTutorTask.objects.filter(task=task).values("tutor_id")) .count() ) @@ -1126,8 +1138,8 @@ def dashboard(request: WSGIRequest) -> HttpResponse: "tutors/dashboard.html", { "subject_counts": count_results, - "events": events, - "tasks": tasks, + "first_future_five_events": first_future_five_events, + "first_future_five_tasks": first_future_five_tasks, "missing_mails": missing_mails, "accepted_tutors": accepted_tutors, "waiting_tutors": waiting_tutors, From 6610f9d381fdb99d0745fe5514a85c25939a4844 Mon Sep 17 00:00:00 2001 From: Frank Elsinga Date: Thu, 24 Mar 2022 16:25:10 +0100 Subject: [PATCH 22/30] migrated settool_common to support the kalendar --- settool_common/cron.py | 26 ++-- settool_common/fixtures/showroom_fixture.py | 130 ++++++++++++++++---- settool_common/tests/test_email.py | 36 +++--- 3 files changed, 144 insertions(+), 48 deletions(-) diff --git a/settool_common/cron.py b/settool_common/cron.py index 7880dfcf..281025e7 100644 --- a/settool_common/cron.py +++ b/settool_common/cron.py @@ -6,6 +6,7 @@ from django.conf import settings from django.core.mail import send_mail +from django.db import IntegrityError from django.db.models import QuerySet from django.db.models.query_utils import Q from django.utils import timezone @@ -13,6 +14,7 @@ import fahrt.models as m_fahrt import guidedtours.models as m_guidedtours +import kalendar.models as m_kalendar import settool_common.models as m_common import tutors.models as m_tutors from settool_common.models import AnonymisationLog, current_semester, Semester @@ -40,15 +42,15 @@ def tutor_reminder(semester: Semester, today: date) -> None: if tutor_settings and tutor_settings.mail_reminder: lookup_day = today + timedelta(days=max(tutor_settings.reminder_tour_days_count, 0)) task: m_tutors.Task - for task in m_tutors.Task.objects.filter( - Q(semester=semester) - & Q(begin__day=lookup_day.day) # begin is datetime - & Q(begin__month=lookup_day.month) - & Q(begin__year=lookup_day.year), - ): - tutor: m_tutors.Tutor - for tutor in list(task.tutors.all()): - tutor_settings.mail_reminder.send_mail_task(tutor, task) + for task in m_tutors.Task.objects.filter(semester=semester): + if not task.associated_meetings: + raise IntegrityError(f"task {task.id} has no associated_meetings") + meeting_date: m_kalendar.Date + for meeting_date in task.associated_meetings.date_set.all(): + if meeting_date.date.date() == lookup_day: + tutor: m_tutors.Tutor + for tutor in list(task.tutors.all()): + tutor_settings.mail_reminder.send_mail_task(tutor, task) def fahrt_date_reminder(semester: Semester, today: date) -> None: @@ -147,8 +149,10 @@ def anonymize_guidedtours(semester: Semester, log_name: str) -> bool: def anonymize_tutors(semester: Semester, log_name: str) -> bool: - most_recent_task: Optional[m_tutors.Task] = m_tutors.Task.objects.filter(semester=semester).order_by("end").last() - if most_recent_task and date_is_too_old(most_recent_task.end, log_name): + task_datetimes: list[real_datetime.datetime] = [ + task.last_datetime for task in m_tutors.Task.objects.filter(semester=semester) + ] + if task_datetimes and date_is_too_old(max(task_datetimes), log_name): # guidedtours is save to anonymize for this semester m_tutors.Answer.objects.filter(tutor__semester=semester).delete() m_tutors.MailTutorTask.objects.filter(tutor__semester=semester).delete() diff --git a/settool_common/fixtures/showroom_fixture.py b/settool_common/fixtures/showroom_fixture.py index e42b7730..f4bee47e 100644 --- a/settool_common/fixtures/showroom_fixture.py +++ b/settool_common/fixtures/showroom_fixture.py @@ -11,6 +11,7 @@ import bags.models import fahrt.models import guidedtours.models +import kalendar.models import settool_common.models import tutors.models from settool_common.fixtures.test_fixture import generate_semesters @@ -59,11 +60,104 @@ def showroom_fixture_state_no_confirmation(): tutors_questions = _generate_questions(common_semesters) _generate_answers(tutors_questions, tutors_list) tutors_events = _generate_events(common_semesters, common_subjects) - _generate_tasks_tutorasignemt(tutors_events, tutors_list, tutors_questions) + _generate_tasks_tutorasignent(tutors_events, tutors_list, tutors_questions) _generate_tutor_settings(common_semesters) # TODO tutors_mailtutortask # TODO tutors_subjecttutorcountassignment + # app kalendar + _generate_kalendar_locations() + _generate_kalendar_date_groups() + _generate_kalendar_dates() + _generate_kalendar_subscriptions() + + +def _generate_kalendar_subscriptions(): # nosec: this is only used in a fixture + actual_tutors = list(tutors.models.Tutor.objects.filter(status=tutors.models.Tutor.STATUS_ACCEPTED).all()) + collaborators = list(tutors.models.Tutor.objects.filter(status=tutors.models.Tutor.STATUS_EMPLOYEE).all()) + dates = list(kalendar.models.Date.objects.all()) + date_groups = list(kalendar.models.DateGroup.objects.all()) + for tutor in actual_tutors: + if random.choice((True, True, True, False)): + dates_for_tutor = random.randint(1, len(dates) // len(actual_tutors) + 1) + selected_dates = random.sample(dates, dates_for_tutor) + for date in selected_dates: + kalendar.models.DateSubscriber.objects.create(date=date, tutor=tutor) + for collaborator in collaborators: + # 90% get a normal amount, 5% get nothing, 5% get all + if random.randint(1, 100) > 10: + date_groups_for_collaborator = random.randint(1, len(dates) // len(collaborators) + 1) + selected_date_groups = random.sample(date_groups, date_groups_for_collaborator) + for date_group in selected_date_groups: + kalendar.models.DateGroupSubscriber.objects.create(date=date_group, tutor=collaborator) + elif random.choice((True, False)): + for date_group in date_groups: + kalendar.models.DateGroupSubscriber.objects.create(date=date_group, tutor=collaborator) + + +def _generate_kalendar_locations(): + locations = [ + "Fachschaftsbüro", + "Serverraum", + "LRZ", + "Chemie-gebäude", + "Interrims-Höhrsaal", + "MI-Magistrale", + "MI-Vorplazt", + "Grillareal", + "Galileo", + "MW2001", + "MW0001", + ] + for shortname in locations: + address = random.choice( + ( + f"Boltzmannstr. {random.randint(1, 30)}, 85748 Garching", + random.choice(("Arcisstraße 17, 80333 München", "")), + ), + ) + kalendar.models.Location.objects.create( + shortname=shortname, + shortname_de=shortname, + shortname_en=shortname, + address=address, + address_de=address, + address_en=address, + room=random.choice(("Magistrale", "FS-Büro", "MW0001", "MW2001", "007", "")), + comment=lorem.sentence()[:200] if random.choice((True, False, False, False)) else "", + ) + + +def _generate_kalendar_date_groups(): + locations: list[kalendar.models.Location] = list(kalendar.models.Location.objects.all()) + date_groups: list[kalendar.models.DateGroup] = list(kalendar.models.DateGroup.objects.all()) + for date_group in date_groups: + if random.choice((True, True, True, False)): + date_group.location = random.choice(locations) + if random.choice((True, False, False, False)): + date_group.comment = lorem.sentence()[:200] + date_group.save() + + +def _generate_kalendar_dates(): + date_groups: list[kalendar.models.DateGroup] = list(kalendar.models.DateGroup.objects.all()) + for date_group in date_groups: + if random.choice((True, True, False)): + for _ in range(random.choice((1, 1, 2, 2, 4, 4, 7, 7))): + kalendar.models.Date.objects.create( + group=date_group, + date=django.utils.timezone.make_aware( + datetime.today() + + timedelta(days=random.randint(1, 60)) + + timedelta( + hours=random.randint(0, 24), + minutes=random.randint(0, 60), + ) + - timedelta(days=random.randint(1, 30)), + ), + probable_length=random.choice((60, 60, 60, 120, 240, 0, 1, 20)), + ) + def _generate_tutor_settings(common_semesters): all_mail_by_set_tutor = tutors.models.TutorMail.objects.all() @@ -374,12 +468,13 @@ def _generate_giveaways( ) -def _generate_tasks_tutorasignemt( +def _generate_tasks_tutorasignent( events, tutors_list, questions, ): tasks = [] + all_tutors = list(tutors.models.Tutor.objects.all()) for event in events: tutors_current_semester = [tutor for tutor in tutors_list if tutor.semester == event.semester] number1 = random.randint(0, len(tutors_current_semester)) @@ -387,22 +482,17 @@ def _generate_tasks_tutorasignemt( filtered_questions = [question for question in questions if question.semester == event.semester] event_subjects = list(event.subjects.all()) for i in range(0, random.randint(0, 4)): + person_1, person_2 = random.sample(all_tutors, 2) + person_1 = random.choice((None, person_1, person_1, person_2, event.meeting_chairperson)) + person_2 = random.choice((None, person_2, person_2, event.event_leader)) task = tutors.models.Task.objects.create( semester=event.semester, name_en=f"Task {i}", name_de=f"Task {i}", description_en=lorem.paragraph(), description_de=lorem.paragraph(), - begin=django.utils.timezone.make_aware( - datetime.today().replace(day=1, month=1) - + timedelta(days=random.randint(0, 30), minutes=random.randint(1, 1000)), - ), - end=django.utils.timezone.make_aware( - datetime.today().replace(day=1, month=12) - - timedelta(days=random.randint(0, 30), minutes=random.randint(1, 1000)), - ), - meeting_point_en=lorem.sentence()[: random.randint(0, 49)], - meeting_point_de=lorem.sentence()[: random.randint(0, 49)], + meeting_chairperson=person_1, + task_leader=person_2, event=event, min_tutors=min(number1, number2), max_tutors=max(number1, number2), @@ -430,7 +520,11 @@ def _generate_tasks_tutorasignemt( def _generate_events(semesters, subjects): events = [] + all_tutors = list(tutors.models.Tutor.objects.all()) for i in range(random.randint(10, 20)): + person_1, person_2 = random.sample(all_tutors, 2) + person_1 = random.choice((None, person_1)) + person_2 = random.choice((None, person_2)) event = tutors.models.Event.objects.create( semester=random.choice(semesters), name=f"Event {i}", @@ -438,16 +532,8 @@ def _generate_events(semesters, subjects): name_de=f"Event {i}", description_en=lorem.paragraph(), description_de=lorem.paragraph(), - begin=django.utils.timezone.make_aware( - datetime.today().replace(day=1, month=1) - + timedelta(days=random.randint(0, 30), minutes=random.randint(1, 1000)), - ), - end=django.utils.timezone.make_aware( - datetime.today().replace(day=1, month=12) - - timedelta(days=random.randint(0, 30), minutes=random.randint(1, 1000)), - ), - meeting_point_en=lorem.sentence(), - meeting_point_de=lorem.sentence(), + meeting_chairperson=person_1, + event_leader=person_2, ) filtered_subjects = random.sample(subjects, random.randint(0, len(subjects))) event.subjects.set(filtered_subjects) diff --git a/settool_common/tests/test_email.py b/settool_common/tests/test_email.py index 173e200e..d586b13d 100644 --- a/settool_common/tests/test_email.py +++ b/settool_common/tests/test_email.py @@ -13,6 +13,7 @@ import fahrt.models as fahrt_models import guidedtours.models as tour_models +import kalendar.models as kalendar_m import settool_common.fixtures.showroom_fixture import settool_common.models as common_models import tutors.models as tutor_models @@ -154,12 +155,16 @@ def setUpTestData(cls) -> None: subject_en="Operatopms Recearch", course_bundle=course_bundle_info, ) + date_group = kalendar_m.DateGroup.objects.create() + kalendar_m.Date.objects.create( + group=date_group, + date=cls.todatetime, + probable_length=60, + ) event = tutor_models.Event.objects.create( semester=common_models.current_semester(), name="enent", - begin=cls.todatetime, - end=cls.todatetime, - meeting_point="-", + associated_meetings=date_group, ) for i in range(2): tutor_models.Tutor.objects.create( @@ -174,12 +179,16 @@ def setUpTestData(cls) -> None: ) tutors = list(tutor_models.Tutor.objects.all()) for i in range(10): + date_group = kalendar_m.DateGroup.objects.create() + kalendar_m.Date.objects.create( + group=date_group, + date=cls.todatetime, + probable_length=60, + ) task = tutor_models.Task.objects.create( semester=common_models.current_semester(), name=f"task {i}", - begin=cls.todatetime, - end=cls.todatetime, - meeting_point="-", + associated_meetings=date_group, event=event, ) for tutor in tutors: @@ -199,15 +208,12 @@ def test_tutor_reminder(self): lookup_day = self.today + timedelta(days=days_until) task: tutor_models.Task - for task in tutor_models.Task.objects.filter( - Q(semester=common_models.current_semester()) - & Q(begin__day=lookup_day.day) # begin is datetime - & Q(begin__month=lookup_day.month) - & Q(begin__year=lookup_day.year), - ): - tutor: tutor_models.Tutor - for tutor in list(task.tutors.all()): - self.setting.mail_reminder.send_mail_task(tutor, task) + for task in tutor_models.Task.objects.filter(semester=common_models.current_semester()): + first_datetime = task.first_datetime + if first_datetime.date() == lookup_day: + tutor: tutor_models.Tutor + for tutor in list(task.tutors.all()): + self.setting.mail_reminder.send_mail_task(tutor, task) expected = serialise_outbox() cron.tutor_reminder(common_models.current_semester(), self.today) curr = serialise_outbox() From 1ab8fabd43fa4673d9df54faa31648cfe34737bf Mon Sep 17 00:00:00 2001 From: Frank Elsinga Date: Fri, 1 Apr 2022 13:50:40 +0200 Subject: [PATCH 23/30] moved BaseTaskEvent to kalendar --- kalendar/models.py | 67 +++++++++++++++++++++++++++++++++++++--------- tutors/models.py | 52 ++--------------------------------- tutors/views.py | 11 ++++---- 3 files changed, 61 insertions(+), 69 deletions(-) diff --git a/kalendar/models.py b/kalendar/models.py index a8817ac1..baed19cb 100644 --- a/kalendar/models.py +++ b/kalendar/models.py @@ -1,22 +1,18 @@ import datetime -import uuid +from typing import Optional from uuid import UUID -from django.db import models +from django.db import models, IntegrityError from django.db.models import Q, QuerySet from django.utils import timezone from django.utils.safestring import mark_safe from django.utils.translation import gettext_lazy as _ - -class BaseModel(models.Model): - class Meta: - abstract = True - - id = models.UUIDField(unique=True, primary_key=True, default=uuid.uuid4) +import kalendar +from settool_common.models import UUIDModelBase, Semester, LoggedModelBase -class Location(BaseModel): +class Location(UUIDModelBase, LoggedModelBase): shortname = models.CharField(_("short/simplified Address"), max_length=200) address = models.CharField(_("(Street)map-address"), blank=True, max_length=100) @@ -33,7 +29,7 @@ def __str__(self) -> str: return mark_safe(message) # nosec: fully defined -class DateGroup(BaseModel): +class DateGroup(UUIDModelBase, LoggedModelBase): location = models.ForeignKey(Location, null=True, blank=True, default=None, on_delete=models.SET_NULL) comment = models.CharField(_("Comment"), blank=True, default="", max_length=200) subscribers = models.ManyToManyField( @@ -82,8 +78,53 @@ def __str__(self) -> str: return f"[{self.group_type_str}] {name} at {self.location}" return f"[{self.group_type_str}] {name} ({_('no meeting point specified')})" +class BaseDateGroupInstance(UUIDModelBase, LoggedModelBase): + class Meta: + abstract = True + + semester = models.ForeignKey(Semester, verbose_name=_("Semester"), on_delete=models.CASCADE) + + name = models.CharField(_("Name"), max_length=250) + description = models.TextField(_("Description"), blank=True) -class DateGroupSubscriber(BaseModel): + @property + def meeting_point_str(self) -> str: + if not self.associated_meetings: + raise IntegrityError(f"{self.__class__} {self.id} has no associated_meetings") + if not self.associated_meetings.location: + return _("no meeting point specified") + return str(self.associated_meetings.location) + + associated_meetings = models.OneToOneField(DateGroup,verbose_name=_("Associated Meetings"),null=True,on_delete=models.SET_NULL) + + @property + def first_datetime(self) -> Optional[datetime.datetime]: + if not self.associated_meetings: + raise IntegrityError(f"{self.__class__} {self.id} has no associated_meetings") + date: Optional[kalendar.models.Date] = self.associated_meetings.date_set.order_by("date").first() + if not date: + return None + return date.date + + @property + def last_datetime(self) -> Optional[datetime.datetime]: + if not self.associated_meetings: + raise IntegrityError(f"{self.__class__} {self.id} has no associated_meetings") + date: Optional[kalendar.models.Date] = self.associated_meetings.date_set.order_by("-date").first() + if not date: + return None + return date.date + datetime.timedelta(minutes=date.probable_length) + + @classmethod + def sorted_by_semester(cls, semester: int) -> list[type["BaseDateGroupInstance"]]: + all_instances: QuerySet[type[BaseDateGroupInstance]] = cls.objects.filter( + semester=semester).all() # type: ignore + instances_sorting: list[tuple[datetime.datetime, type[BaseDateGroupInstance]]] = [ + (instance.first_datetime, instance) for instance in all_instances if instance.first_datetime # type: ignore + ] + return [instance for (_, instance) in sorted(instances_sorting, key=lambda t: t[0])] + +class DateGroupSubscriber(UUIDModelBase, LoggedModelBase): tutor = models.ForeignKey("tutors.Tutor", on_delete=models.CASCADE) date = models.ForeignKey(DateGroup, on_delete=models.CASCADE) @@ -91,7 +132,7 @@ def __str__(self) -> str: return f"{self.tutor}" -class Date(BaseModel): +class Date(UUIDModelBase, LoggedModelBase): group = models.ForeignKey(DateGroup, on_delete=models.CASCADE) date = models.DateTimeField(_("Date and Time")) probable_length = models.IntegerField(_("probable length in minutes"), default=60) @@ -145,7 +186,7 @@ def is_in_future(self) -> bool: return self.probable_end > timezone.now() -class DateSubscriber(BaseModel): +class DateSubscriber(UUIDModelBase, LoggedModelBase): tutor = models.ForeignKey("tutors.Tutor", on_delete=models.CASCADE) date = models.ForeignKey(Date, on_delete=models.CASCADE) diff --git a/tutors/models.py b/tutors/models.py index 79063b4a..cc3b3c70 100644 --- a/tutors/models.py +++ b/tutors/models.py @@ -209,56 +209,8 @@ def log(self, user, text): pass -class BaseTaskEvent(common_models.UUIDModelBase, common_models.LoggedModelBase, common_models.SemesterModelBase): - class Meta: - abstract = True - - name = models.CharField(_("Name"), max_length=250) - description = models.TextField(_("Description"), blank=True) - - @property - def meeting_point_str(self) -> str: - if not self.associated_meetings: - raise IntegrityError(f"{self.__class__} {self.id} has no associated_meetings") - if not self.associated_meetings.location: - return _("no meeting point specified") - return str(self.associated_meetings.location) - - associated_meetings = models.OneToOneField( - "kalendar.DateGroup", - verbose_name=_("Associated Meetings"), - null=True, - on_delete=models.SET_NULL, - ) - @property - def first_datetime(self) -> Optional[datetime.datetime]: - if not self.associated_meetings: - raise IntegrityError(f"{self.__class__} {self.id} has no associated_meetings") - date: Optional[kalendar.models.Date] = self.associated_meetings.date_set.order_by("date").first() - if not date: - return None - return date.date - - @property - def last_datetime(self) -> Optional[datetime.datetime]: - if not self.associated_meetings: - raise IntegrityError(f"{self.__class__} {self.id} has no associated_meetings") - date: Optional[kalendar.models.Date] = self.associated_meetings.date_set.order_by("-date").first() - if not date: - return None - return date.date + datetime.timedelta(minutes=date.probable_length) - - @classmethod - def sorted_by_semester(cls, semester: int) -> list[type["BaseTaskEvent"]]: - all_instances: QuerySet[Type[BaseTaskEvent]] = cls.objects.filter(semester=semester).all() # type: ignore - instances_sorting: list[tuple[datetime.datetime, Type[BaseTaskEvent]]] = [ - (instance.first_datetime, instance) for instance in all_instances if instance.first_datetime # type: ignore - ] - return [instance for (_, instance) in sorted(instances_sorting, key=lambda t: t[0])] - - -class Event(BaseTaskEvent): +class Event(kalendar.models.BaseDateGroupInstance): meeting_chairperson = models.ForeignKey( Tutor, related_name="tutors_event_meeting_chairperson", @@ -289,7 +241,7 @@ def __str__(self) -> str: return self.name -class Task(BaseTaskEvent): +class Task(kalendar.models.BaseDateGroupInstance): event = models.ForeignKey(Event, verbose_name=_("Event"), on_delete=models.CASCADE) meeting_chairperson = models.ForeignKey( Tutor, diff --git a/tutors/views.py b/tutors/views.py index ef5797fe..13761436 100644 --- a/tutors/views.py +++ b/tutors/views.py @@ -21,7 +21,7 @@ from django_tex.response import PDFResponse from django_tex.shortcuts import render_to_pdf -from kalendar.models import Date +from kalendar.models import Date, BaseDateGroupInstance from settool_common import utils from settool_common.models import get_semester, Semester, Subject from tutors.forms import ( @@ -40,7 +40,6 @@ ) from tutors.models import ( Answer, - BaseTaskEvent, Event, MailTutorTask, Question, @@ -1089,8 +1088,8 @@ def _gather_batch_parameters( return semester, tutors_active, tutors_accepted_cnt, assignment_wishes, errors -def get_first_future_five(cls: type[BaseTaskEvent], semester_id: int) -> list[type[BaseTaskEvent]]: - sorted_instances: list[type[BaseTaskEvent]] = cls.sorted_by_semester(semester_id) +def get_first_future_five(cls: type[BaseDateGroupInstance], semester_id: int) -> list[type[BaseDateGroupInstance]]: + sorted_instances: list[type[BaseDateGroupInstance]] = cls.sorted_by_semester(semester_id) future_instances = [] for inst in sorted_instances: @@ -1119,8 +1118,8 @@ def dashboard(request: WSGIRequest) -> HttpResponse: assignment_wish_counter.wanted, ) - first_future_five_events: list[type[BaseTaskEvent]] = get_first_future_five(Event, semester.id) - first_future_five_tasks: list[type[BaseTaskEvent]] = get_first_future_five(Task, semester.id) + first_future_five_events: list[type[BaseDateGroupInstance]] = get_first_future_five(Event, semester.id) + first_future_five_tasks: list[type[BaseDateGroupInstance]] = get_first_future_five(Task, semester.id) missing_mails = 0 task: Task From 7d4b8562aae716302b68d74366b5c93b653557b7 Mon Sep 17 00:00:00 2001 From: Frank Elsinga Date: Tue, 5 Apr 2022 00:49:49 +0200 Subject: [PATCH 24/30] merged the new modelbase into the kalendar draft --- fahrt/forms.py | 2 +- .../0035_alter_participant_semester.py | 24 ++++++ fahrt/views/tex_views.py | 5 +- .../migrations/0005_participant_time.py | 2 - kalendar/locale/de/LC_MESSAGES/django.mo | Bin 2927 -> 2984 bytes kalendar/locale/de/LC_MESSAGES/django.po | 41 ++++++----- ...pdated_at_dategroup_created_at_and_more.py | 69 ++++++++++++++++++ kalendar/models.py | 36 +++++---- tutors/locale/de/LC_MESSAGES/django.mo | Bin 15776 -> 15838 bytes tutors/locale/de/LC_MESSAGES/django.po | 46 +++++------- ... 0011_add_associated_meetings_and_more.py} | 2 +- ...=> 0012_migrate_start_end_to_dategroup.py} | 2 +- ... 0013_migrate_meeting_point_to_locaton.py} | 2 +- tutors/models.py | 8 +- tutors/views.py | 2 +- 15 files changed, 167 insertions(+), 74 deletions(-) create mode 100644 fahrt/migrations/0035_alter_participant_semester.py create mode 100644 kalendar/migrations/0002_date_created_at_date_updated_at_dategroup_created_at_and_more.py rename tutors/migrations/{0009_add_associated_meetings_and_more.py => 0011_add_associated_meetings_and_more.py} (97%) rename tutors/migrations/{0010_migrate_start_end_to_dategroup.py => 0012_migrate_start_end_to_dategroup.py} (94%) rename tutors/migrations/{0011_migrate_meeting_point_to_locaton.py => 0013_migrate_meeting_point_to_locaton.py} (96%) diff --git a/fahrt/forms.py b/fahrt/forms.py index 59faed0c..122a0d5f 100644 --- a/fahrt/forms.py +++ b/fahrt/forms.py @@ -214,7 +214,7 @@ def __init__(self, *args, **kwargs): def get_choosable_subjects(self) -> QuerySet[Subject]: choosable_subjects_ids = ( - self.semester.fahrt_participant.filter(status="confirmed") + Participant.objects.filter(semester=self.semester, status="confirmed") .values("subject") .distinct() .values_list("subject", flat=True) diff --git a/fahrt/migrations/0035_alter_participant_semester.py b/fahrt/migrations/0035_alter_participant_semester.py new file mode 100644 index 00000000..4acabfed --- /dev/null +++ b/fahrt/migrations/0035_alter_participant_semester.py @@ -0,0 +1,24 @@ +# Generated by Django 4.0.3 on 2022-04-04 22:42 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("settool_common", "0018_alter_anonymisationlog_semester"), + ("fahrt", "0034_remove_participant_id_alter_logentry_participant_and_more"), + ] + + operations = [ + migrations.AlterField( + model_name="participant", + name="semester", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="settool_common.semester", + verbose_name="Semester", + ), + ), + ] diff --git a/fahrt/views/tex_views.py b/fahrt/views/tex_views.py index eb0dcd2f..701d3e6f 100644 --- a/fahrt/views/tex_views.py +++ b/fahrt/views/tex_views.py @@ -1,5 +1,6 @@ import time from typing import Union +from uuid import UUID from django.contrib import messages from django.contrib.auth.decorators import permission_required @@ -48,11 +49,11 @@ def export(request: WSGIRequest, file_format: str = "csv") -> Union[HttpResponse @permission_required("fahrt.view_participants") -def non_liability_form(request: WSGIRequest, participant_pk: int) -> PDFResponse: +def non_liability_form(request: WSGIRequest, participant_pk: UUID) -> PDFResponse: return get_non_liability(request, participant_pk) -def get_non_liability(request: WSGIRequest, participant_pk: int) -> PDFResponse: +def get_non_liability(request: WSGIRequest, participant_pk: UUID) -> PDFResponse: participant: Participant = get_object_or_404(Participant, pk=participant_pk) fahrt: Fahrt = get_object_or_404(Fahrt, semester=participant.semester) context = { diff --git a/guidedtours/migrations/0005_participant_time.py b/guidedtours/migrations/0005_participant_time.py index 44642d9e..d5c223c4 100644 --- a/guidedtours/migrations/0005_participant_time.py +++ b/guidedtours/migrations/0005_participant_time.py @@ -1,7 +1,5 @@ # Generated by Django 1.9 on 2015-12-21 08:08 -import datetime - from django.db import migrations, models from django.utils import timezone diff --git a/kalendar/locale/de/LC_MESSAGES/django.mo b/kalendar/locale/de/LC_MESSAGES/django.mo index b76fcef39b22a6032fc55dbe63c2ba314cdbe893..e6f6b707f69559bea140a7de2399157f4474b567 100644 GIT binary patch delta 1134 zcmYM!L5PfD7{Kvo&Dfn;YuU9s!&+;WwX9jzAf@5tvLi|ykmSIS%N%f!)F=mugJ=(u zloKU;n2Qvm9MncGj=N4Ah`31E^8e3%@;1-+d%t(T_x;}Id1v-}?m@mj-JPBY+Ge7k zc+?WH3wNZr(8kjdOR$1%coZ{O#XhVhf4_>0xxb5i;xU(2Y@qX=VisQ^pP1sZFrpss z>2xqKgD&tDeQ*|CplyD{0_?;d>`U&)(0OI_`EleE`?<8^A}@CEKfGn+*>+QY>RSKtVap&LJ(SVJC-%V;9k5+|_k zz&$!77Z1@5OrjNej+SZ)&HN*>r}%V86+&P-+_Ov<5nqsAhWkxJ$Jfx>TKhD`c3Np J-PTMw^%vJGQ(^!B delta 1100 zcmXxk&2LOm7=ZCtXV7+3r&D^>S4UNSv>gPAu0*_qS)0+ zVnVvn4H_Sbg~nf?n=Uj>L?WaT!OHX89w+xbzk5&5JsiosX(EfeM6*a0>Jd9m<3_I{FuE$I0My{v!yV%D3F_k(z zLwEKHo$x(6!6$U!&&V3$8@A(evWeB&UcoJxp_w^^JvfS0yn^Rnx%gi57xp9TF z=!+kdOSp+=^Y|9J(Vfks3;lua@DJ|A5{)kW2+|ZM(G8qVjwdf7Ymci~cj9Z@cqX^e z6iuQ#euAtao}>Lkul>9y5kSX6$?}j`~^+rPt5Q)n$j|Vw7nlaiUD-|6F7hulK0RIy=W!>z1+N` z!HE{p7ye>5cJc*QaVR;8{6O;J#1`KhBDN9RiTudWjhHP<`Z29MP`kN)pFDq4xsPZf zb`iS?C+{O%T!p9+`w5Fj!us-54x&e6AqjaF59CiP-^9*z@9xcv#iMZdJ;d-HDvRf_ kgD`FWFYp{KrnO4=VR_d7OKI|Wacrhhd|jBi*<3691CA(7!~g&Q diff --git a/kalendar/locale/de/LC_MESSAGES/django.po b/kalendar/locale/de/LC_MESSAGES/django.po index 3da4df82..d1471bb9 100644 --- a/kalendar/locale/de/LC_MESSAGES/django.po +++ b/kalendar/locale/de/LC_MESSAGES/django.po @@ -43,6 +43,29 @@ msgstr "Event" msgid "Task" msgstr "Aufgabe" +#: kalendar/models.py +#: kalendar/templates/kalendar/management/list/chronological.html +#: kalendar/templates/kalendar/management/view_date.html +#: kalendar/templates/kalendar/management/view_date_public.html +msgid "Name" +msgstr "Name" + +#: kalendar/models.py kalendar/templates/kalendar/management/view_date.html +#: kalendar/templates/kalendar/management/view_date_public.html +msgid "Description" +msgstr "Beschreibung" + +#: kalendar/models.py kalendar/templates/kalendar/management/delete_date.html +#: kalendar/templates/kalendar/management/list/grouped.html +#: kalendar/templates/kalendar/management/view_date.html +#: kalendar/templates/kalendar/management/view_date_public.html +msgid "no meeting point specified" +msgstr "kein Treffpunkt angegeben" + +#: kalendar/models.py +msgid "Associated Meetings" +msgstr "Zugeordnete Meetings" + #: kalendar/models.py msgid "Date and Time" msgstr "Datum und Zeit" @@ -99,13 +122,6 @@ msgstr "Datum" msgid "Location" msgstr "Standort" -#: kalendar/templates/kalendar/management/delete_date.html -#: kalendar/templates/kalendar/management/list/grouped.html -#: kalendar/templates/kalendar/management/view_date.html -#: kalendar/templates/kalendar/management/view_date_public.html -msgid "no meeting point specified" -msgstr "kein Treffpunkt angegeben" - #: kalendar/templates/kalendar/management/delete_date.html #: kalendar/templates/kalendar/management/edit_date.html msgid "Cancel" @@ -128,12 +144,6 @@ msgstr "Event liegt in der vergangenheit" msgid "Task lies in the past" msgstr "Aufgabe liegt in der vergangenheit" -#: kalendar/templates/kalendar/management/list/chronological.html -#: kalendar/templates/kalendar/management/view_date.html -#: kalendar/templates/kalendar/management/view_date_public.html -msgid "Name" -msgstr "Name" - #: kalendar/templates/kalendar/management/list/chronological.html msgid "Date/Time" msgstr "Datum/Zeit" @@ -193,11 +203,6 @@ msgstr "Event-informationen" msgid "Task-information" msgstr "Aufgaben-informationen" -#: kalendar/templates/kalendar/management/view_date.html -#: kalendar/templates/kalendar/management/view_date_public.html -msgid "Description" -msgstr "Beschreibung" - #: kalendar/templates/kalendar/management/view_date.html #: kalendar/templates/kalendar/management/view_date_public.html msgid "Date-information" diff --git a/kalendar/migrations/0002_date_created_at_date_updated_at_dategroup_created_at_and_more.py b/kalendar/migrations/0002_date_created_at_date_updated_at_dategroup_created_at_and_more.py new file mode 100644 index 00000000..78d0e349 --- /dev/null +++ b/kalendar/migrations/0002_date_created_at_date_updated_at_dategroup_created_at_and_more.py @@ -0,0 +1,69 @@ +# Generated by Django 4.0.3 on 2022-04-04 22:42 + +import django.utils.timezone +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("kalendar", "0001_initial"), + ] + + operations = [ + migrations.AddField( + model_name="date", + name="created_at", + field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), + preserve_default=False, + ), + migrations.AddField( + model_name="date", + name="updated_at", + field=models.DateTimeField(auto_now=True), + ), + migrations.AddField( + model_name="dategroup", + name="created_at", + field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), + preserve_default=False, + ), + migrations.AddField( + model_name="dategroup", + name="updated_at", + field=models.DateTimeField(auto_now=True), + ), + migrations.AddField( + model_name="dategroupsubscriber", + name="created_at", + field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), + preserve_default=False, + ), + migrations.AddField( + model_name="dategroupsubscriber", + name="updated_at", + field=models.DateTimeField(auto_now=True), + ), + migrations.AddField( + model_name="datesubscriber", + name="created_at", + field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), + preserve_default=False, + ), + migrations.AddField( + model_name="datesubscriber", + name="updated_at", + field=models.DateTimeField(auto_now=True), + ), + migrations.AddField( + model_name="location", + name="created_at", + field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), + preserve_default=False, + ), + migrations.AddField( + model_name="location", + name="updated_at", + field=models.DateTimeField(auto_now=True), + ), + ] diff --git a/kalendar/models.py b/kalendar/models.py index baed19cb..22ea096c 100644 --- a/kalendar/models.py +++ b/kalendar/models.py @@ -2,17 +2,17 @@ from typing import Optional from uuid import UUID -from django.db import models, IntegrityError +from django.db import IntegrityError, models from django.db.models import Q, QuerySet from django.utils import timezone from django.utils.safestring import mark_safe from django.utils.translation import gettext_lazy as _ import kalendar -from settool_common.models import UUIDModelBase, Semester, LoggedModelBase +import settool_common.models as common_models -class Location(UUIDModelBase, LoggedModelBase): +class Location(common_models.UUIDModelBase, common_models.LoggedModelBase): shortname = models.CharField(_("short/simplified Address"), max_length=200) address = models.CharField(_("(Street)map-address"), blank=True, max_length=100) @@ -29,7 +29,7 @@ def __str__(self) -> str: return mark_safe(message) # nosec: fully defined -class DateGroup(UUIDModelBase, LoggedModelBase): +class DateGroup(common_models.UUIDModelBase, common_models.LoggedModelBase): location = models.ForeignKey(Location, null=True, blank=True, default=None, on_delete=models.SET_NULL) comment = models.CharField(_("Comment"), blank=True, default="", max_length=200) subscribers = models.ManyToManyField( @@ -78,12 +78,15 @@ def __str__(self) -> str: return f"[{self.group_type_str}] {name} at {self.location}" return f"[{self.group_type_str}] {name} ({_('no meeting point specified')})" -class BaseDateGroupInstance(UUIDModelBase, LoggedModelBase): + +class BaseDateGroupInstance( + common_models.UUIDModelBase, + common_models.LoggedModelBase, + common_models.SemesterModelBase, +): class Meta: abstract = True - semester = models.ForeignKey(Semester, verbose_name=_("Semester"), on_delete=models.CASCADE) - name = models.CharField(_("Name"), max_length=250) description = models.TextField(_("Description"), blank=True) @@ -95,7 +98,12 @@ def meeting_point_str(self) -> str: return _("no meeting point specified") return str(self.associated_meetings.location) - associated_meetings = models.OneToOneField(DateGroup,verbose_name=_("Associated Meetings"),null=True,on_delete=models.SET_NULL) + associated_meetings = models.OneToOneField( + DateGroup, + verbose_name=_("Associated Meetings"), + null=True, + on_delete=models.SET_NULL, + ) @property def first_datetime(self) -> Optional[datetime.datetime]: @@ -117,14 +125,16 @@ def last_datetime(self) -> Optional[datetime.datetime]: @classmethod def sorted_by_semester(cls, semester: int) -> list[type["BaseDateGroupInstance"]]: - all_instances: QuerySet[type[BaseDateGroupInstance]] = cls.objects.filter( - semester=semester).all() # type: ignore + all_instances: QuerySet[type[BaseDateGroupInstance]] = cls.objects.filter( # type: ignore + semester=semester, + ).all() instances_sorting: list[tuple[datetime.datetime, type[BaseDateGroupInstance]]] = [ (instance.first_datetime, instance) for instance in all_instances if instance.first_datetime # type: ignore ] return [instance for (_, instance) in sorted(instances_sorting, key=lambda t: t[0])] -class DateGroupSubscriber(UUIDModelBase, LoggedModelBase): + +class DateGroupSubscriber(common_models.UUIDModelBase, common_models.LoggedModelBase): tutor = models.ForeignKey("tutors.Tutor", on_delete=models.CASCADE) date = models.ForeignKey(DateGroup, on_delete=models.CASCADE) @@ -132,7 +142,7 @@ def __str__(self) -> str: return f"{self.tutor}" -class Date(UUIDModelBase, LoggedModelBase): +class Date(common_models.UUIDModelBase, common_models.LoggedModelBase): group = models.ForeignKey(DateGroup, on_delete=models.CASCADE) date = models.DateTimeField(_("Date and Time")) probable_length = models.IntegerField(_("probable length in minutes"), default=60) @@ -186,7 +196,7 @@ def is_in_future(self) -> bool: return self.probable_end > timezone.now() -class DateSubscriber(UUIDModelBase, LoggedModelBase): +class DateSubscriber(common_models.UUIDModelBase, common_models.LoggedModelBase): tutor = models.ForeignKey("tutors.Tutor", on_delete=models.CASCADE) date = models.ForeignKey(Date, on_delete=models.CASCADE) diff --git a/tutors/locale/de/LC_MESSAGES/django.mo b/tutors/locale/de/LC_MESSAGES/django.mo index 46ca85967e93e2f2fdd62b85e8d0a3a48afbcbb0..974123e22f579d90e53dfce8784336a04f86f011 100644 GIT binary patch delta 3953 zcmYk;c~DkW7{~F0fiEDND6%LbDsHd1y=v~Fl#Q4rMJ{BSV<2EIm?UO)T{1H@qc_bx zMWrCk6eY`Dn2FJ$$^LM13~jSaOD$WbX0pD&_a0}u!}EU5IrrXk&w0+dSGO;`w9vT} z8@$eN)DdlnAx(|(1Q|0qLaoM>#uyWY3$Qu9g3-7ML$M0^Gu8ZXtEs~zJc~O2J?i?K z=)r$&yNAyBb4(1s;hboXdS*I?;ULtF_n|t0VIQ1`93R$wso z8dU#BFcRyr1 z!(b|dF$qWFc+>T8IP$NC+xCJxsDXqq9ksVYb)1S!-VDLla2#r8mvI2b^3*0c z1~uS^P??&D%H#~BYqJowN6M_*~L0z{VmEm2;SWP8rASbQo9SWN9PpGy02X#YuqT6vaYCx$t0Mk)>Bp)?^ z*{A_5u&zQ~zZKPAC2Alwwq9rJ=TH-KE>j3Bfo-^hnn5gi56lp!QSXg<@H*74--en= z1?szT)YdPc*7_#ux<63;+(F$R%1&2%b7X*yNpuUQlhudXJQ=998infcG1Q1BV{e>; z%1Ak?%n5890yKs{=0gW9=Emw*~*GHNM$qb4*IwG^4wY>aYfm_k7_oP}Dm66;dbE?xq!I+u4 z05!1YceLtVcaHSoh}qZ9ep zh02F)iMp^0s-s@08IM5TOp}eJScDqDx5&mef1xJOl8xiR6x2XFqxQfsR0gt;xtSd7 zkIy?4*sx|l>V^i)!0&J>rge31T!Ep~%aBzun{4}W)Y{jh`niaDYyL#_*OUd7BRHn*p`vGi9y%sfq zGZ>CvATOHv7U{<^LA+BTG$f%$)DCrHI_ieL*5RnsW}&Ws1T~O}_Ixq&CYl#e1KxrM zu?7d>jC41%6?hNzA23w!|8)vl%imC`4Cj?lDw9#0WFTs$V^AGWM$Pm&d;VpNq`m=l ze+BCLkL>v~7)Jd|)P3J$CjNne_kU1Ncc!B;m=iguO)?2J5C^qHn^9|8gZe}_pfd3d z^6!(micx5KxigMNozFsLbR4R`38?FzMMooFMIjnDqGni$%1ABh!V9Q1`voH~n(wa$ z(#GmTwP#^2PC)g005$WIsD93%`uWDz`MNoQktOtTUy~Hn4PI0S{ZP9;)7BqE4Kx?^ zz#`NP=VNnRgqrDE)CATeV>WxRKQ^NJYw2_UjY#&f{_GDkl7_EwHZnMq-IuQ$E<<&2 z9n-N5uZF&aSvU<>qR#(}1=yCS(4|>{>c?}h`=EiCMg2((WEi#7wGM?i3iYU&T|&+1 z25OCeLuDe2cT*pzc+^|qMYa2^BX9`yhp`K;#?E*M^;R@uEdGPqOECl8J?D5SsKfr4 z5yb3p1a*gZMl(Bw>iAbw3VRNA|Fz7=CDhAtIda;ssSW4*82CTBp52X(y3JtRVC#9WS~BcEM)<{)qTgrCrBqVj}Up zD)vag_xwLb_=2ka$&vAKd#M)_j4^O*<#&&Nbfh<|oYEHJUH_uU4nh0;J0e>ozC>NS zYB8Zrpo4v4PWc-my3EztLgW!8#9HF+p?$f7@Do}>9WN7lL+z1o4pUF`jsfs3g`99}zmViB}PNi{AI|k4|yQC>R|oqfseNwDokDpVuD##^Y9Np81-?v69S0+mHVL>6d!lCc1E%3iJoP0^M-8|; zDpLbdnH+_5X>w3|WRZ1482MK!3Te9TVe(# zqwbr5x^6it!}-Wq%~sSvj#w*P6g1;osI{y{-S7{pWKy~ax-p6AQ>VY#+yL~=t21`-jivn9e zfm-8>sOzqx`niR=|DJ7sh#Fvxqsw@+^*y^7wP~VIYt$OmVKQpO-7pDt?6yll088U^ciX?f?7BeYKB^hme!7_31*hu9sT zpkAXC@~gG)hh)=?!gH95I-eHfoF9nV)Ni2nz;x90^H2ldgl(`e#=id*G$?hK?FCh+ z4jv%cH_uV2j^Zv2AO*GS2cyo9MqNK2)z4aFADf-1&3Fd&;BwS`-=nUpY(xH4cw{g1 zB|qvY5H+J{$UZge zT@-Y~0qluoI1cN?IX6zkdeo;Qt6*|%`*zgY??rWV3bm7fUc7yWybr zPG*;49qM1BGI{~^|BtysL8-itO64=uCJEym(M;n|9d|>`bOh@B1Ps7AsQZ_quHS6W z??XT8r&0HPg?;cU24Z+eCd%_oTMFLT3AIVOpawD;wMO$$YnqSxL?1w9;ym*Ik*Pp+ zSc#f(wLKr3=w!45>bexvfHN@&N1;nI%b}oMy$p5XHq=@bqd#6o4WQEc*tYv8k#^c6 zP#w=g&3G}YpEam{_SkwEYKd=R1N<$C{Og8l8q`4zYHfTvIrTu)NFz}LNkGlKD+Xf^ zRENV+6Uau!X5PY7EJXEp4f#fy+sL*s-pM>1lak3l&oqrX^VbaaLv?Tn+hZl_3+T_Q zF&+n_&L74pcn7tFgLsh%ZZ;zRa(O8Hfcn-CPuApASYE*wuv8R^?-<3ZUY3Rky z(ae^hIxaz_@J~#~Sbi4a1YCyqP^o>Bi-+TE)PTRit@sP_2{E%XjNv(EC82@k5IW`) zJ2a*|Pn8F{7x_1fSwU?V5ko8|mJ&MNbMWkf0(Yr@Sjz>rr4+M?dA2UXiBH{s`zL!H zbGHu&3q44E2Emv-$98@XxL*&5_uENn8}W&IZ9prp&)i1?8bvIju3e>t)h5uvhB4>e zzXrs|t)irrIz!YRU(^;}@Q6DpFuvDuT1F6SiAZ8Daf;YXDBao=wZ}#ZZHNeBl5PCJ zy(6%hkDJw)@;=->|(&nHeh?sKA$I7;Y Date: Tue, 5 Apr 2022 21:24:30 +0200 Subject: [PATCH 25/30] minor formatting fixes in migrations --- bags/migrations/0004_auto_20160531_1312.py | 5 +---- bags/migrations/0008_auto_20160608_1212.py | 5 +---- bags/migrations/0017_remove_mail_semester.py | 5 +---- bags/migrations/0021_auto_20210326_1632.py | 10 ++-------- bags/migrations/0023_auto_20210330_1154.py | 5 +---- fahrt/migrations/0006_auto_20160906_1418.py | 19 ++++--------------- fahrt/migrations/0024_remove_mail_semester.py | 5 +---- fahrt/migrations/0028_auto_20210218_2258.py | 10 ++-------- ..._id_alter_logentry_participant_and_more.py | 5 +---- .../migrations/0013_remove_mail_semester.py | 5 +---- .../migrations/0011_remove_mail_semester.py | 5 +---- 11 files changed, 16 insertions(+), 63 deletions(-) diff --git a/bags/migrations/0004_auto_20160531_1312.py b/bags/migrations/0004_auto_20160531_1312.py index d863ae16..8874a54e 100644 --- a/bags/migrations/0004_auto_20160531_1312.py +++ b/bags/migrations/0004_auto_20160531_1312.py @@ -16,10 +16,7 @@ class Migration(migrations.Migration): "permissions": (("view_companies", "Can view and edit the companies"),), }, ), - migrations.RemoveField( - model_name="company", - name="zusage", - ), + migrations.RemoveField(model_name="company", name="zusage"), migrations.AddField( model_name="company", name="promise", diff --git a/bags/migrations/0008_auto_20160608_1212.py b/bags/migrations/0008_auto_20160608_1212.py index 3ff413a0..48756073 100644 --- a/bags/migrations/0008_auto_20160608_1212.py +++ b/bags/migrations/0008_auto_20160608_1212.py @@ -10,10 +10,7 @@ class Migration(migrations.Migration): ] operations = [ - migrations.RemoveField( - model_name="company", - name="contact", - ), + migrations.RemoveField(model_name="company", name="contact"), migrations.AddField( model_name="company", name="contact_firstname", diff --git a/bags/migrations/0017_remove_mail_semester.py b/bags/migrations/0017_remove_mail_semester.py index b70259c3..a3eed937 100644 --- a/bags/migrations/0017_remove_mail_semester.py +++ b/bags/migrations/0017_remove_mail_semester.py @@ -18,8 +18,5 @@ class Migration(migrations.Migration): operations = [ migrations.RunPython(subject_includes_semester), - migrations.RemoveField( - model_name="mail", - name="semester", - ), + migrations.RemoveField(model_name="mail", name="semester"), ] diff --git a/bags/migrations/0021_auto_20210326_1632.py b/bags/migrations/0021_auto_20210326_1632.py index f5c9d55b..3c682d42 100644 --- a/bags/migrations/0021_auto_20210326_1632.py +++ b/bags/migrations/0021_auto_20210326_1632.py @@ -12,14 +12,8 @@ class Migration(migrations.Migration): ] operations = [ - migrations.RemoveField( - model_name="giveaway", - name="every_x_bags", - ), - migrations.RemoveField( - model_name="giveaway", - name="per_bag_count", - ), + migrations.RemoveField(model_name="giveaway", name="every_x_bags"), + migrations.RemoveField(model_name="giveaway", name="per_bag_count"), migrations.AddField( model_name="giveaway", name="item_count", diff --git a/bags/migrations/0023_auto_20210330_1154.py b/bags/migrations/0023_auto_20210330_1154.py index 8ed19713..6728e6b1 100644 --- a/bags/migrations/0023_auto_20210330_1154.py +++ b/bags/migrations/0023_auto_20210330_1154.py @@ -33,8 +33,5 @@ class Migration(migrations.Migration): ), ), migrations.RunPython(keep_giveaway_data), - migrations.RemoveField( - model_name="giveaway", - name="name", - ), + migrations.RemoveField(model_name="giveaway", name="name"), ] diff --git a/fahrt/migrations/0006_auto_20160906_1418.py b/fahrt/migrations/0006_auto_20160906_1418.py index 5e596fa6..fadb5508 100644 --- a/fahrt/migrations/0006_auto_20160906_1418.py +++ b/fahrt/migrations/0006_auto_20160906_1418.py @@ -5,7 +5,6 @@ class Migration(migrations.Migration): - dependencies = [ ("settool_common", "0004_auto_20151220_2255"), ("fahrt", "0005_auto_20160906_1411"), @@ -144,9 +143,7 @@ class Migration(migrations.Migration): ), ( "mailinglist", - models.BooleanField( - verbose_name="Mailing list", - ), + models.BooleanField(verbose_name="Mailing list"), ), ( "comment", @@ -182,15 +179,7 @@ class Migration(migrations.Migration): ), ], ), - migrations.RemoveField( - model_name="person", - name="semester", - ), - migrations.RemoveField( - model_name="person", - name="subject", - ), - migrations.DeleteModel( - name="Person", - ), + migrations.RemoveField(model_name="person", name="semester"), + migrations.RemoveField(model_name="person", name="subject"), + migrations.DeleteModel(name="Person"), ] diff --git a/fahrt/migrations/0024_remove_mail_semester.py b/fahrt/migrations/0024_remove_mail_semester.py index ec8b19dd..187032d0 100644 --- a/fahrt/migrations/0024_remove_mail_semester.py +++ b/fahrt/migrations/0024_remove_mail_semester.py @@ -18,8 +18,5 @@ class Migration(migrations.Migration): operations = [ migrations.RunPython(subject_includes_semester), - migrations.RemoveField( - model_name="mail", - name="semester", - ), + migrations.RemoveField(model_name="mail", name="semester"), ] diff --git a/fahrt/migrations/0028_auto_20210218_2258.py b/fahrt/migrations/0028_auto_20210218_2258.py index 4aeba313..945ba076 100644 --- a/fahrt/migrations/0028_auto_20210218_2258.py +++ b/fahrt/migrations/0028_auto_20210218_2258.py @@ -32,14 +32,8 @@ class Migration(migrations.Migration): ), ], ), - migrations.RemoveField( - model_name="participant", - name="car", - ), - migrations.RemoveField( - model_name="participant", - name="car_places", - ), + migrations.RemoveField(model_name="participant", name="car"), + migrations.RemoveField(model_name="participant", name="car_places"), migrations.CreateModel( name="TransportationComment", fields=[ diff --git a/fahrt/migrations/0034_remove_participant_id_alter_logentry_participant_and_more.py b/fahrt/migrations/0034_remove_participant_id_alter_logentry_participant_and_more.py index 9a1fb224..72dbda6e 100644 --- a/fahrt/migrations/0034_remove_participant_id_alter_logentry_participant_and_more.py +++ b/fahrt/migrations/0034_remove_participant_id_alter_logentry_participant_and_more.py @@ -14,10 +14,7 @@ class Migration(migrations.Migration): operations = [ # uuid->id - migrations.RemoveField( - model_name="participant", - name="id", - ), + migrations.RemoveField(model_name="participant", name="id"), migrations.AlterField( model_name="participant", name="uuid", diff --git a/guidedtours/migrations/0013_remove_mail_semester.py b/guidedtours/migrations/0013_remove_mail_semester.py index 50c7b7d0..ba67cbd2 100644 --- a/guidedtours/migrations/0013_remove_mail_semester.py +++ b/guidedtours/migrations/0013_remove_mail_semester.py @@ -18,8 +18,5 @@ class Migration(migrations.Migration): operations = [ migrations.RunPython(subject_includes_semester), - migrations.RemoveField( - model_name="mail", - name="semester", - ), + migrations.RemoveField(model_name="mail", name="semester"), ] diff --git a/settool_common/migrations/0011_remove_mail_semester.py b/settool_common/migrations/0011_remove_mail_semester.py index 09d7ff71..6c25899b 100644 --- a/settool_common/migrations/0011_remove_mail_semester.py +++ b/settool_common/migrations/0011_remove_mail_semester.py @@ -18,8 +18,5 @@ class Migration(migrations.Migration): operations = [ migrations.RunPython(subject_includes_semester), - migrations.RemoveField( - model_name="mail", - name="semester", - ), + migrations.RemoveField(model_name="mail", name="semester"), ] From 40023947117dbc83f5ff72605babaabdcc69f1a3 Mon Sep 17 00:00:00 2001 From: Frank Elsinga Date: Tue, 5 Apr 2022 21:25:20 +0200 Subject: [PATCH 26/30] migrated fahrt to support the kalendar --- fahrt/locale/de/LC_MESSAGES/django.mo | Bin 19782 -> 19756 bytes fahrt/locale/de/LC_MESSAGES/django.po | 19 +++++++-------- fahrt/migrations/0036_remove_logentry_time.py | 14 +++++++++++ ...37_remove_participant_registration_time.py | 22 ++++++++++++++++++ fahrt/models.py | 7 +++--- .../view_participant_details.html | 6 ++--- fahrt/views/participants_views.py | 8 +++---- 7 files changed, 55 insertions(+), 21 deletions(-) create mode 100644 fahrt/migrations/0036_remove_logentry_time.py create mode 100644 fahrt/migrations/0037_remove_participant_registration_time.py diff --git a/fahrt/locale/de/LC_MESSAGES/django.mo b/fahrt/locale/de/LC_MESSAGES/django.mo index 7b453f590c90c4893a392e89a0f85e3bba243093..61502d417681b1bdc7d239dbe44b675b0973d787 100644 GIT binary patch delta 4081 zcmXxm3s6*59LMpq1;|5zT|hxaR!~+{kQGG{h|n-3D^Zh}l19z392G51EOjl-q%5mj zsm$2;NS)Fw^OdO)l~|gUAwClyF+MVNCe0~Lr|-{uyR&CM=ia^dod5Zsb1u}^xYpOW z0!^NvcMZo$N_$Fd3uCfe#851x^sdV7N1x&=ySYx6w74<+LRA7bZ#tKx&lQ9UZFcfEDCC*0$ zcpVkMExa3JR{V+Sx5>YedCdP(&4|sThmupbGoq z>!=M;k7}S1Bk&9=1D8>myo!1*p5B=~la6Y)2=&}x?2YB9wXi5aC4;YK&(h zHP>BGsm;RiSb!t&Bh+?tu|m~hIBFywB~w%TXyGjeN`!zPjR0Y@z*s zj*3!z1t;P)q%Sj?3DtuyBOkMauWCGnvvGXqJF{Ufpau|@YA?P7^m3k!daew6;7sg- z>v0J-;$8G_CXq*_cnY!s%ybOFx3LtLqSnSm)D+!7r8Jt>?#5(P=29^T{Wub{k#~-H z2ld=K48=Np5%*#shst2~o&uPUYG@%U1B+3M>kDN4nLXGK&mzmkbR>_O<3e14<*1C^ z#O~;4MNY*E48sOgfG5z8XS$L90xGfGts90rR$xBoPdTo^7MwSuGV=$v#H*-4wB$81 zFX}y!hU%yYb$p91o&a{|VuIOHbC$gey5mBwm!jHjLIMbw^Hj8mE@KRa^s*ml zi`w@EsEFS~b@VCfmAoBG@J}3w`K&yx@_DFC)Sw$zJJ+|N*1~>_#Df^6{r?-4R4!aa zjjV09{etj2K8~8RS5R|bgK@YEm4OD-Haw2%_#|qCzoTBU*H8m!)yLL{dcF|D=--r4 zAzNk?k_7V(Cc5}rfcJ2Imfd)~o zRJ)T=_fJQXVix3*e>GIg1#P?C&J9;lkp|`2YoawO(5|S|^+W|yjJj`_b3PV%C78*m z_LiZZ+lQ^N!SOW4a(*R`{I{kO#M?#}+Bs&TQeK1_$#7IdlduGzLt->_s42UJTAY67 zOH zFGir=h)=XA3HBGI8(lL&*#d1X@C2XUnp=!xaq->(-sIzjSQe0nJBl$jJy%?2Ss*I*nc2jC8 z{~tT39CuDW#qE?ilqHl^6dm7CmaD>1Kw&~ngVicJ%hhBRMkl#Gu|`G5#C^rpu9UqL zExbo5Ut87D_a`2t_5($S*6vOUuR61gvemj2o#NVTx!rBTHgfK#Y_R<9+~5<`ezc~z zV}ie>w$G||Cxxx2W>Jn=8{GrqzN4lUq2mLJR-duLJl*1sQmNM&#{?VmnKj6h;@V|R x^z=yom}{DmUnxIP-lp*CxpSQ4`)7)F-4~QYo4)sKjomcQH?6~_+eO7K{sXyY%QpZ3 delta 4106 zcmXxm2~<>79LMoHGVD6c45E%3lN%xtxq^Zs5`()3O+(Dm%#@B=q%C;L$>Wk1cGQv$glURqnpUTh&go=k8kyGj*L%(3^11iTd-wkD|K9sx!wT2o6|V3% ze)lrN=K`grO0+RuUFe~~=c3=3c36jP(35CP6m~{#HCc{*aTM+TPJ0zLq5UQ{!VfSO ztC6luEhZQfHg!~5bD!T!atdi>LsjlI#E) zVFc}V*bFB!N^=1NwwYJl#XrM||0Gk|-qh@{;gBaJ`zAp_mKptu)gHb6j zLS?QPwS?nP0hA%DV&*vQO4R*tH7Ec5sBGavDqg{QY~8|`ZrF^4*FZy%OQzUy2CBow zs1#RW5I;eFOdY?pbWv<51<)Qf^Gs}r1*nV+Z$fi^A$IGY;+(czEf>HHcM`ZO(Z&be{QP16t-LVw4xmSg$+(uG+j^~hcFFCU^<3TOR*8P6x$sSBdcjnA=}1;f1{#EURgKpQFzA-=db{2C_6X!%?^!b=-XHP|d74Y9?)v?3)}^s!N>j$D{5qMWy@^ za4Nc4NX9acpdMU?{Fu-9wGc1iES%Y%BZu{<2?RRWn=ciEv_q)pCSe9vU`O1GmG}d? z8Q;tzj}34RD%JCmgJ3E!3g5&c+=N;h^`cFifJ$w9jKEA(2D8zPAsmZ?kav(-k9uw& z#$YXmXHz*(r6-Q(3@U(^Q5~&8Wnu$rlO09&qdAMc@H#33J;QJHbE)f-|ADv%`9%(|mC zV;|H&C8+!FMNRAh)RL8@lYh;4w)4d^7*BgC@?&1*mpZ7%5AhhXP0SL`tTMA570_wa zz!x0rP^aLs(=K8ID03yKz{*kYnPp)rTI+SFH9COoCvyf_6=QC!nF4V&MoD1aNNO%&119-sxbqn&{| z|97Gyej63wA6u>mSYb7_xFDk72P-m$(DHx zNrqXEtz7&gfqiISXSyEj#aY#68h}BZjLOVH)aG1^Dflr4@mJ@%mow|7-3^tI0u1Z; z+(kt*xE}*J1(l-ts2MN9rnnmW;!e~I>rqSR&bB*_Lv5}WsM8Zf^*0ptXL&NJ-+8D& zDzeEx*)c1*ppH(U9z5gR5R+p^8o)%Zr=bGPMWwzF706iBj3ztnnaC@`%tQ6J74_T& zY=jpbuja7+jk)0FEz<-8sP^rS1*nvlpk`8v>SzuQ#OIJ$O)YB4{H&8UX9%@q!}#?O zK99=aP1MYrt$MpmC_BdII&_^GKIw5BAjgzd>a`7cvX%f$OmW?VG6MNvJ5)c*}({=iVt%*Wufct5trUr_^k`rAv= z7*l8mQ62Y39k&rU2p>o7fdkkHui;5-Gr)c^{fWFRP3}PZ)m(=0jBhSc(LjGA`^Yq) zH_fmw#^4JWhbyrN*E!$+g;BJJ4z}+bi8=-MU4IDkh{wLLsN@X6p@Hk}&Drht?2&e`S>=WEa(PHJx&p zvXAorXD^krPGbx1raVolq^zarbC9xH6+XEX7TBDyZX|SdowquBlU?svMc%|DZN^TN z&nN*(G39_&?k#Mk)%={IPafrc3a>!3lCr}(=WXlSZn=C-W4F@oOxbMR;>(WwhT2hU zye~2G5VdNn+?O1?k(x#M+FIkwPdY+v7Db=eDcXG-ta@Ks(iv(WQ}mf)W42j6{cT+z zSY!Pe>6^Hw6*))wg0hsNEu_x{$}x&g-yX`TZM*#MHI68qI&s_7l*e1UOD2}i*!F$F Hfare#e1Xm3 diff --git a/fahrt/locale/de/LC_MESSAGES/django.po b/fahrt/locale/de/LC_MESSAGES/django.po index af23a3ff..e6862fd1 100644 --- a/fahrt/locale/de/LC_MESSAGES/django.po +++ b/fahrt/locale/de/LC_MESSAGES/django.po @@ -235,12 +235,6 @@ msgstr "Bahn ({free_places} frei)" msgid "Car ({free_places} free)" msgstr "Auto ({free_places} frei)" -#: fahrt/models.py fahrt/templates/fahrt/participants/list/list_registered.html -#: fahrt/templates/fahrt/participants/list/list_waitinglist.html -#: fahrt/templates/fahrt/participants/view_participant_details.html -msgid "Registration time" -msgstr "Anmeldezeitpunkt" - #: fahrt/models.py msgid "male" msgstr "Männlich" @@ -366,10 +360,6 @@ msgstr "Text" msgid "{sender} on {commented_on}: {comment_content}" msgstr "{sender} auf {commented_on}: {comment_content}" -#: fahrt/models.py -msgid "Time" -msgstr "Zeit" - #: fahrt/templates/fahrt/base_fahrt.html #: fahrt/templates/fahrt/fahrt_dashboard.html msgid "Dashboard" @@ -775,6 +765,12 @@ msgstr "%(places)s Sitze in %(cars)s Autos" msgid "List registered participants" msgstr "Angemeldete Teilnehmer auflisten" +#: fahrt/templates/fahrt/participants/list/list_registered.html +#: fahrt/templates/fahrt/participants/list/list_waitinglist.html +#: fahrt/templates/fahrt/participants/view_participant_details.html +msgid "Registration time" +msgstr "Anmeldezeitpunkt" + #: fahrt/templates/fahrt/participants/list/list_waitinglist.html msgid "List participants on waitinglist" msgstr "Teilnehmer auf der Warteliste auflisten" @@ -1120,6 +1116,9 @@ msgstr "" "Derzeit ist dies nur eine Chatwall und kein Live-Chat. Das bedeutet, dass du " "die Seite aktualisieren musst, um neue Nachrichten zu erhalten." +#~ msgid "Time" +#~ msgstr "Zeit" + #~ msgid "With car" #~ msgstr "Mit Auto" diff --git a/fahrt/migrations/0036_remove_logentry_time.py b/fahrt/migrations/0036_remove_logentry_time.py new file mode 100644 index 00000000..0c11988a --- /dev/null +++ b/fahrt/migrations/0036_remove_logentry_time.py @@ -0,0 +1,14 @@ +# Generated by Django 4.0.3 on 2022-04-05 15:23 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("fahrt", "0035_alter_participant_semester"), + ] + + operations = [ + migrations.RemoveField(model_name="logentry", name="time"), + ] diff --git a/fahrt/migrations/0037_remove_participant_registration_time.py b/fahrt/migrations/0037_remove_participant_registration_time.py new file mode 100644 index 00000000..c4ba69a5 --- /dev/null +++ b/fahrt/migrations/0037_remove_participant_registration_time.py @@ -0,0 +1,22 @@ +# Generated by Django 4.0.3 on 2022-04-05 15:39 + +from django.db import migrations + + +def migrate_participant_registration_time_created_at(apps, schema_editor): + Participant = apps.get_model("fahrt", "Participant") + for participant in Participant.objects.all(): + participant.created_at = participant.registration_time + participant.save(update_fields=["created_at"]) + + +class Migration(migrations.Migration): + + dependencies = [ + ("fahrt", "0036_remove_logentry_time"), + ] + + operations = [ + migrations.RunPython(migrate_participant_registration_time_created_at), + migrations.RemoveField(model_name="participant", name="registration_time"), + ] diff --git a/fahrt/models.py b/fahrt/models.py index 77c15e10..8539548b 100644 --- a/fahrt/models.py +++ b/fahrt/models.py @@ -170,7 +170,9 @@ class Meta: ), ) - registration_time = models.DateTimeField(_("Registration time"), auto_now_add=True) + @property + def registration_time(self): + return self.created_at GENDER_CHOICES = ( ("male", _("male")), @@ -283,7 +285,6 @@ def __str__(self) -> str: class LogEntry(common_models.LoggedModelBase): - time = models.DateTimeField(_("Time"), auto_now_add=True) participant = models.ForeignKey(Participant, on_delete=models.CASCADE) text = models.CharField(_("Text"), max_length=200) user = models.ForeignKey( @@ -295,4 +296,4 @@ class LogEntry(common_models.LoggedModelBase): ) def __str__(self) -> str: - return f"{self.time}, {self.user}: {self.text}" + return f"{self.updated_at}, {self.user}: {self.text}" diff --git a/fahrt/templates/fahrt/participants/view_participant_details.html b/fahrt/templates/fahrt/participants/view_participant_details.html index de28cd56..f7d9f5cf 100644 --- a/fahrt/templates/fahrt/participants/view_participant_details.html +++ b/fahrt/templates/fahrt/participants/view_participant_details.html @@ -196,12 +196,12 @@ {% for entry in log_entries %} {% if entry.user %} {% if entry.user.full_name %} -
  • {{ entry.time }}, {{ entry.user.full_name }}: {{ entry.text }}
  • +
  • {{ entry.updated_at }}, {{ entry.user.full_name }}: {{ entry.text }}
  • {% else %} -
  • {{ entry.time }}, {{ entry.user }}: {{ entry.text }}
  • +
  • {{ entry.updated_at }}, {{ entry.user }}: {{ entry.text }}
  • {% endif %} {% else %} -
  • {{ entry.time }}, www: {{ entry.text }}
  • +
  • {{ entry.updated_at }}, www: {{ entry.text }}
  • {% endif %} {% endfor %} diff --git a/fahrt/views/participants_views.py b/fahrt/views/participants_views.py index 38c3023f..765cdb7d 100644 --- a/fahrt/views/participants_views.py +++ b/fahrt/views/participants_views.py @@ -31,9 +31,7 @@ @permission_required("fahrt.view_participants") def list_registered(request: WSGIRequest) -> HttpResponse: semester: Semester = get_object_or_404(Semester, pk=get_semester(request)) - participants = Participant.objects.filter(semester=semester, status="registered").order_by( - "-registration_time", - ) + participants = Participant.objects.filter(semester=semester, status="registered").order_by("-created_at") context = { "participants": participants, @@ -44,7 +42,7 @@ def list_registered(request: WSGIRequest) -> HttpResponse: @permission_required("fahrt.view_participants") def list_waitinglist(request: WSGIRequest) -> HttpResponse: semester: Semester = get_object_or_404(Semester, pk=get_semester(request)) - participants = Participant.objects.filter(semester=semester, status="waitinglist").order_by("-registration_time") + participants = Participant.objects.filter(semester=semester, status="waitinglist").order_by("-created_at") context = { "participants": participants, @@ -166,7 +164,7 @@ def list_cancelled(request: WSGIRequest) -> HttpResponse: @permission_required("fahrt.view_participants") def view_participant(request: WSGIRequest, participant_pk: UUID) -> HttpResponse: participant = get_object_or_404(Participant, pk=participant_pk) - log_entries = participant.logentry_set.order_by("time") + log_entries = participant.logentry_set.order_by("created_at") form = SelectMailForm(request.POST or None) From a87dea94a0cef2d4b82320e3d1bc87df022d5250 Mon Sep 17 00:00:00 2001 From: Frank Elsinga Date: Tue, 5 Apr 2022 21:25:35 +0200 Subject: [PATCH 27/30] migrated guidedtours to support the kalendar --- guidedtours/forms.py | 10 +- guidedtours/locale/de/LC_MESSAGES/django.mo | Bin 7521 -> 7035 bytes guidedtours/locale/de/LC_MESSAGES/django.po | 61 +++++---- ...e_tour_date_remove_tour_length_and_more.py | 88 ++++++++++++ ...ticipant_time_participant_date_and_more.py | 23 ++++ guidedtours/migrations/0024_alter_tour_id.py | 82 ++++++++++++ ...ur_uuid_alter_participant_tour_and_more.py | 34 +++++ guidedtours/models.py | 32 ++--- .../templates/guidedtours/signup/blocked.html | 2 +- .../templates/guidedtours/signup/signup.html | 11 +- guidedtours/urls.py | 8 +- guidedtours/views.py | 126 +++++++++++++----- 12 files changed, 387 insertions(+), 90 deletions(-) create mode 100644 guidedtours/migrations/0022_remove_tour_date_remove_tour_length_and_more.py create mode 100644 guidedtours/migrations/0023_remove_participant_time_participant_date_and_more.py create mode 100644 guidedtours/migrations/0024_alter_tour_id.py create mode 100644 guidedtours/migrations/0025_remove_tour_uuid_alter_participant_tour_and_more.py diff --git a/guidedtours/forms.py b/guidedtours/forms.py index d39f6387..c890a892 100644 --- a/guidedtours/forms.py +++ b/guidedtours/forms.py @@ -10,13 +10,17 @@ class ParticipantForm(CommonParticipantForm): class Meta: model = Participant - exclude = ["time"] + exclude = ["created_at", "updated_at"] def __init__(self, *args, **kwargs): - tours = kwargs.pop("tours") + self.tours_and_dates = kwargs.pop("tours_and_dates") + self.tours = kwargs.pop("tours") + self.dates = kwargs.pop("dates") super().__init__(*args, **kwargs) - self.fields["tour"].queryset = tours + self.fields["tour"].queryset = self.tours self.fields["tour"].widget.attrs = {"class": "no-automatic-choicejs"} + self.fields["date"].queryset = self.dates + self.fields["date"].widget.attrs = {"class": "no-automatic-choicejs"} class TourForm(SemesterBasedModelForm): diff --git a/guidedtours/locale/de/LC_MESSAGES/django.mo b/guidedtours/locale/de/LC_MESSAGES/django.mo index d6bd5fd43edc6ba36e7356167a5edf7dd7c9df56..b4d161efe6eff31c9a357f9382d6df258f4cd38c 100644 GIT binary patch delta 1912 zcmZA2YfQ~?9LMqRsfctTl5(p=g*Zi_;zT8sORDiCY?j+((`L3>9n8$i-NJ0S4P(rM z^MBxpi7=Oq%>xfSq1Zg|#ARlaOWvQ;|7_;B^Zmbm|NsB|{{P?a`~98jE%=%5e-j_k zVYFIe9Pu#BY$t9X#DV6GG@FAt$j7#F7>YYF9-A=^k6;R(!y(v(srUpV@FNb!PwxHi z$auf?)8S+BjCzF0cmaD*FWSpN4`{~(Jch&YDn?@f3$P2zun(7Fan!&I+fZvcj(qGi z2fkvLg4g|KH|b~v0i1;0t{-qD=TW>Z1t*{;;6rX%Eox#LPz$KX;n;wBK`Uy42i^M} zIEwR59DxDM=Kc1Njvn~cz404kIFDnrW4^uk!@u^QRDugCK4V;{k6h4Ml*R!z$Q$?>DY-g@EK|XKTrcbY-|}p{>Rg~!G&CWih9v6R1G6|i&7VhT(Tt8^=Y^b^DuxH z-0RhB7%iv{6LBMI0?kOa?GzT`Wh}=YKb;ynndDKaZgJg@DnUCcrB~eR-KZ5m#SDDy z>S1Rx2g|}VEJD3_J!(Ns7>@f;3pjvd(0`1MQgt0wf&gkj7it2JQ5ot*)w16`&mxax z*~(FwS&JIC4OOB>)bm>CHFWwGQ{dm;_bV>bHmIp$!@*nv#=P#GvkWuOK%elwDQ;Imioep^gXM$04AxF3AM z%;1^X0$fJSC6scNK_v;bN;;XL6Vxw83kbD*I-1}_LK)LOP@7En7Y4fnf8nOO{n@CL z)e@R9B@g~d)y`H^dJBnZgi7^)E9ZhTp{Ac%DxnhP5^5EMN>i=B|JoD<#5_XvQ`3IP zAWDgq#J??_lhuU&RI6;Oh)`QjM=38NN(e8pgqTjK%_3A{HEq^1<#qxc{bZ&Pp|*&Q zYF0(m5ZW;MFIY_I=cD~Vxy^YVc{t436m{Qo@?nW0H72mkaIPd}GS-J#5C$kdMh*W=sJVVJTK&F>XW) zTak|$ z+F=1%Xd^{ff|WRmHK=u7Mn2{Qmwc7_EjlXkSzLu5VF15DO`NwfGrkJ>mo< z(~MepJCcLx#3JlN%@@R4k1X;a zqjq{3^@@JPR{R}zVl5}H7pG9A|J3yh)GPTGRpRUJcnP~u1y*AX*0?5n=&%_xjd$Vm zs07|d?d&3E__Dc9L>UYObiOi!ab_Vr|7v1|C$lFSq3T9KO zs!>1OjCyzNs58)ons^^7(I-(mIfREf)z7-)S!Av83S@5MMJ3jTs^AXPq3uNnhtuP1 z;SwD^co~_?e2*&KO;l+s@5m&y7WJGTl~@BR@n+P-ov4NTk)oJ!Y{Mg{L%o1{{vxU} z-(i_b_y-*wnq{o|8kXThn8JFzj;f58I;s)@R3%!Fb7Z=aLYOVYW5naceT16+DsD9; ztyj2-P@-Bx>!;f~u5AQsEj2GaRY2LQk!QNKa8&~R#NEW51m`lHtPXPxp^E4{scp_+ z8c~(d>riXb`W>16(v<_7{?BM>fKDe-O>`4%);vMzj5HJfZ})Jcf3j6gwT;BibkCTr zu0GU(UQd*1|9V%q+WmAmmFaS8Oyz!99oimZ2cc7}wwLH3I40&1Vi%!5NsHJ`Y$qNd z_7U3%z54*6)<@h+JUYmwozTJ4>3xt;Ysg@_Tt%zf-|t%IszYhKzNj5>{F7EJYI%J@ zJ5E1UmwzlPb+%y9GrB7_ZG~gegO)E6iYDUDq~Efwz4k;rG#nbYqj76;B$fyVts%!6 z3de@WoSdWDcG!x?tj0QKvBHiWOpFJH!uIf3I5Zg#^oGb_ zZgzPpdygGS)vg%z-BOZg5sp}61O??l|14ow8--mLgKhwPoyY~=+{?&J)Qq-v|Wv;PIN6ArZi diff --git a/guidedtours/locale/de/LC_MESSAGES/django.po b/guidedtours/locale/de/LC_MESSAGES/django.po index 63e4f4c7..d55f4c31 100644 --- a/guidedtours/locale/de/LC_MESSAGES/django.po +++ b/guidedtours/locale/de/LC_MESSAGES/django.po @@ -54,32 +54,11 @@ msgstr "Der Zeitpunkt der Tour" msgid "'Tour' or 'Waitinglist' depending on on_the_tour status" msgstr "'Tour' oder 'Waitinglist' (je nach on_the_tour Status)" -#: guidedtours/models.py -msgid "Name" -msgstr "Name" - -#: guidedtours/models.py -msgid "Description" -msgstr "Beschreibung" - -#: guidedtours/models.py -#: guidedtours/templates/guidedtours/tours/list_tours.html -msgid "Date" -msgstr "Datum" - #: guidedtours/models.py guidedtours/templates/guidedtours/tour_dashboard.html #: guidedtours/templates/guidedtours/tours/list_tours.html msgid "Capacity" msgstr "Kapazität" -#: guidedtours/models.py -msgid "" -"How long (minutes) a Participant should be blocked after a Tour (additonal " -"to 30min leadup-blacklist-Time)" -msgstr "" -"Wie lang (Minuten) ein Teilnehmer nach dem beginn einer Tour (zusätzlich zu " -"der 30Minuten Leadup-Blacklist-Zeit) blockert wird" - #: guidedtours/models.py msgid "Open registration" msgstr "Anmeldungsstart" @@ -88,6 +67,11 @@ msgstr "Anmeldungsstart" msgid "Close registration" msgstr "Anmeldungsschluss" +#: guidedtours/models.py +#: guidedtours/templates/guidedtours/tours/list_tours.html +msgid "Date" +msgstr "Datum" + #: guidedtours/models.py #: guidedtours/templates/guidedtours/maintenance/mail/send_mail.html #: guidedtours/templates/guidedtours/participants/filtered_participants.html @@ -119,10 +103,6 @@ msgstr "Handynummer" msgid "Subject" msgstr "Studiengang" -#: guidedtours/models.py -msgid "Registration Time" -msgstr "Registrierungszeit" - #: guidedtours/models.py msgid "On the tour" msgstr "Bei der Tour" @@ -326,7 +306,7 @@ msgstr "Anmeldung Fehlgeschlagen" #: guidedtours/templates/guidedtours/signup/blocked.html #, python-format msgid "" -"We are sorry, you can't participate in two guidedtours: at the same time. If " +"We are sorry, you can't participate in two guidedtours at the same time. If " "you think there is an error, contact %(mail)s." msgstr "" "Wir sind Sorry, aber du kannst nicht an zwei Institutsführungen gleichzeitig " @@ -342,11 +322,6 @@ msgstr "Institutsführungs-Anmeldung" msgid "All availible Guided guidedtours:" msgstr "Alle zur verfügung stehenden Institutsführungen:" -#: guidedtours/templates/guidedtours/signup/signup.html -#, python-format -msgid "%(name)s on %(date)s" -msgstr "%(name)s am %(date)s" - #: guidedtours/templates/guidedtours/signup/signup.html msgid "Length:" msgstr "Länge:" @@ -423,6 +398,10 @@ msgstr "Bestätigte Teilnehmer als CSV ausgeben" msgid "Export confired participants as PDF" msgstr "Bestätigte Teilnehmer als PDF ausgeben" +#: guidedtours/views.py +msgid "No dates" +msgstr "" + #: guidedtours/views.py #, python-brace-format msgid "" @@ -443,6 +422,26 @@ msgstr "" "Konnte die Registrierungs-Email nicht senden. Bitte vergewissere dich, das " "diese richtig konfiguirert ist." +#, python-format +#~ msgid "%(name)s on %(date)s" +#~ msgstr "%(name)s am %(date)s" + +#~ msgid "Registration Time" +#~ msgstr "Registrierungszeit" + +#~ msgid "Name" +#~ msgstr "Name" + +#~ msgid "Description" +#~ msgstr "Beschreibung" + +#~ msgid "" +#~ "How long (minutes) a Participant should be blocked after a Tour " +#~ "(additonal to 30min leadup-blacklist-Time)" +#~ msgstr "" +#~ "Wie lang (Minuten) ein Teilnehmer nach dem beginn einer Tour (zusätzlich " +#~ "zu der 30Minuten Leadup-Blacklist-Zeit) blockert wird" + #~ msgid "I accept the terms and conditions of the following privacy policy:" #~ msgstr "Ich stimme der folgenden Datenschutzerklärung zu:" diff --git a/guidedtours/migrations/0022_remove_tour_date_remove_tour_length_and_more.py b/guidedtours/migrations/0022_remove_tour_date_remove_tour_length_and_more.py new file mode 100644 index 00000000..21933fd5 --- /dev/null +++ b/guidedtours/migrations/0022_remove_tour_date_remove_tour_length_and_more.py @@ -0,0 +1,88 @@ +# Generated by Django 4.0.3 on 2022-04-04 23:04 + +import django.db.models.deletion +from django.db import migrations, models + +import kalendar.models + + +def migrate_associated_meetings(apps, schema_editor): + Tour = apps.get_model("guidedtours", "Tour") + for tour in Tour.objects.all(): + tour.associated_meetings_id = kalendar.models.create_associated_meetings() + tour.save() + + +def migrate_tour_dates(apps, schema_editor): + date_lut = {} + Date = apps.get_model("kalendar", "Date") + Tour = apps.get_model("guidedtours", "Tour") + for tour in Tour.objects.all(): + group = tour.associated_meetings + date = Date.objects.create( + group=group, + date=tour.date, + probable_length=tour.length, + ) + date_lut[tour.id] = date + + Participant = apps.get_model("guidedtours", "Participant") + for participant in Participant.objects.all(): + participant.date = date_lut[participant.tour_id] + participant.save() + + +class Migration(migrations.Migration): + dependencies = [ + ("kalendar", "0002_date_created_at_date_updated_at_dategroup_created_at_and_more"), + ("guidedtours", "0021_alter_tour_semester"), + ] + + operations = [ + migrations.AlterField( + model_name="tour", + name="description", + field=models.TextField(blank=True, default="", verbose_name="Description"), + preserve_default=False, + ), + migrations.AlterField( + model_name="tour", + name="name", + field=models.CharField(max_length=250, verbose_name="Name"), + ), + migrations.AddField( + model_name="tour", + name="associated_meetings", + field=models.OneToOneField( + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to="kalendar.dategroup", + verbose_name="Associated Meetings", + ), + ), + migrations.RunPython(migrate_associated_meetings), + migrations.AddField( + model_name="participant", + name="date", + field=models.ForeignKey( + default=None, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="kalendar.date", + verbose_name="Date", + ), + preserve_default=False, + ), + migrations.RunPython(migrate_tour_dates), + migrations.AlterField( + model_name="participant", + name="date", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="kalendar.date", + verbose_name="Date", + ), + ), + migrations.RemoveField(model_name="tour", name="date"), + migrations.RemoveField(model_name="tour", name="length"), + ] diff --git a/guidedtours/migrations/0023_remove_participant_time_participant_date_and_more.py b/guidedtours/migrations/0023_remove_participant_time_participant_date_and_more.py new file mode 100644 index 00000000..5a893638 --- /dev/null +++ b/guidedtours/migrations/0023_remove_participant_time_participant_date_and_more.py @@ -0,0 +1,23 @@ +# Generated by Django 4.0.3 on 2022-04-05 15:23 + +from django.db import migrations + + +def migrate_participant_time_created_at(apps, schema_editor): + Participant = apps.get_model("guidedtours", "Participant") + for participant in Participant.objects.all(): + participant.created_at = participant.time + participant.save(update_fields=["created_at"]) + + +class Migration(migrations.Migration): + + dependencies = [ + ("kalendar", "0002_date_created_at_date_updated_at_dategroup_created_at_and_more"), + ("guidedtours", "0022_remove_tour_date_remove_tour_length_and_more"), + ] + + operations = [ + migrations.RunPython(migrate_participant_time_created_at), + migrations.RemoveField(model_name="participant", name="time"), + ] diff --git a/guidedtours/migrations/0024_alter_tour_id.py b/guidedtours/migrations/0024_alter_tour_id.py new file mode 100644 index 00000000..b02b5f03 --- /dev/null +++ b/guidedtours/migrations/0024_alter_tour_id.py @@ -0,0 +1,82 @@ +# Generated by Django 4.0.3 on 2022-04-05 15:39 + +import uuid + +import django +from django.db import migrations, models + + +def migrate_tour_uuid(apps, schema_editor): + Tour = apps.get_model("guidedtours", "Tour") + for tour in Tour.objects.all(): + tour.uuid = uuid.uuid4() + tour.save(update_fields=["uuid"]) + + +def migrate_tour_id_for_participant(apps, schema_editor): + Participant = apps.get_model("guidedtours", "Participant") + for participant in Participant.objects.all(): + participant.tour_uuid_id = participant.tour.uuid + participant.save(update_fields=["tour_uuid"]) + + +class Migration(migrations.Migration): + dependencies = [ + ("guidedtours", "0023_remove_participant_time_participant_date_and_more"), + ] + + operations = [ + # see https://stackoverflow.com/a/48235821 + # add a new uuid-field to the Tour model + migrations.AddField( + model_name="tour", + name="uuid", + field=models.UUIDField(default=uuid.uuid4, blank=True, null=True), + ), + migrations.RunPython(migrate_tour_uuid), + migrations.AlterField( + model_name="tour", + name="uuid", + field=models.UUIDField(default=uuid.uuid4, serialize=False, unique=True), + ), + # add different fk-fields for each participant and logentry + migrations.AddField( + model_name="participant", + name="tour_uuid", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="participant_uuid", + to="guidedtours.tour", + to_field="uuid", + db_constraint=False, + ), + ), + migrations.AlterField( + model_name="participant", + name="tour", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="participant", + to="guidedtours.tour", + to_field="id", + ), + ), + # fill new fields with valid data + migrations.RunPython(migrate_tour_id_for_participant), + # after filling, we can assure the db, that they are filled + migrations.AlterField( + model_name="participant", + name="tour_uuid", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="participant_uuid", + to="guidedtours.tour", + to_field="uuid", + ), + ), + # switch to the new uuid-field + # migrations.RemoveField(model_name="participant", name="tour"), + migrations.RenameField(model_name="participant", old_name="tour_uuid", new_name="tour"), + ] diff --git a/guidedtours/migrations/0025_remove_tour_uuid_alter_participant_tour_and_more.py b/guidedtours/migrations/0025_remove_tour_uuid_alter_participant_tour_and_more.py new file mode 100644 index 00000000..bb2fb670 --- /dev/null +++ b/guidedtours/migrations/0025_remove_tour_uuid_alter_participant_tour_and_more.py @@ -0,0 +1,34 @@ +# Generated by Django 4.0.3 on 2022-04-05 16:28 + +import uuid + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("guidedtours", "0024_alter_tour_id"), + ] + + operations = [ + # uuid->id for original field + migrations.RemoveField(model_name="tour", name="id"), + migrations.AlterField( + model_name="tour", + name="uuid", + field=models.UUIDField(default=uuid.uuid4, primary_key=True, serialize=False, unique=True), + ), + migrations.RenameField(model_name="tour", old_name="uuid", new_name="id"), + # cleanup + migrations.AlterField( + model_name="participant", + name="tour", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="guidedtours.tour", + verbose_name="Tour", + ), + ), + ] diff --git a/guidedtours/models.py b/guidedtours/models.py index 9d4e5bbc..6b0fb657 100644 --- a/guidedtours/models.py +++ b/guidedtours/models.py @@ -1,8 +1,11 @@ from dateutil.relativedelta import relativedelta from django.db import models +from django.db.models.signals import post_save +from django.dispatch import receiver from django.utils import timezone from django.utils.translation import gettext_lazy as _ +import kalendar.models import settool_common.models as common_models from settool_common.models import Semester, Subject @@ -50,7 +53,7 @@ def send_mail_participant(self, participant): return self.send_mail(context, participant.email) -class Tour(common_models.LoggedModelBase, common_models.SemesterModelBase): +class Tour(kalendar.models.BaseDateGroupInstance): class Meta: permissions = ( ( @@ -59,19 +62,7 @@ class Meta: ), ) - name = models.CharField(max_length=200, verbose_name=_("Name")) - description = models.TextField(null=True, blank=True, verbose_name=_("Description")) - - date = models.DateTimeField(verbose_name=_("Date")) - capacity = models.PositiveIntegerField(verbose_name=_("Capacity")) - length = models.IntegerField( - verbose_name=_( - "How long (minutes) a Participant should be blocked after a Tour (additonal to 30min " - "leadup-blacklist-Time)", - ), - default=0, - ) open_registration = models.DateTimeField(_("Open registration")) close_registration = models.DateTimeField(_("Close registration")) @@ -84,24 +75,35 @@ def registration_open(self): return self.open_registration < timezone.now() < self.close_registration +# pylint: disable=unused-argument +@receiver(post_save, sender=Tour) +def create_event_meetings(sender, instance, created, **kwargs): + if not instance.associated_meetings: + instance.associated_meetings = kalendar.models.create_associated_meetings() + instance.save() + + +# pylint: enable=unused-argument + + class Participant(common_models.LoggedModelBase): class Meta: unique_together = ("tour", "email") tour = models.ForeignKey(Tour, on_delete=models.CASCADE, verbose_name=_("Tour")) + date = models.ForeignKey("kalendar.Date", on_delete=models.CASCADE, verbose_name=_("Date")) firstname = models.CharField(max_length=200, verbose_name=_("First name")) surname = models.CharField(max_length=200, verbose_name=_("Surname")) email = models.EmailField(verbose_name=_("E-Mail")) phone = models.CharField(max_length=200, verbose_name=_("Mobile phone")) subject = models.ForeignKey(Subject, on_delete=models.CASCADE, verbose_name=_("Subject")) - time = models.DateTimeField(auto_now_add=True, verbose_name=_("Registration Time")) def __str__(self) -> str: return f"{self.firstname} {self.surname}" @property def on_the_tour(self): - participants = self.tour.participant_set.order_by("time") + participants = self.tour.participant_set.order_by("created_at") participants = participants[: self.tour.capacity] return self in participants diff --git a/guidedtours/templates/guidedtours/signup/blocked.html b/guidedtours/templates/guidedtours/signup/blocked.html index b3c90159..0a1094d8 100644 --- a/guidedtours/templates/guidedtours/signup/blocked.html +++ b/guidedtours/templates/guidedtours/signup/blocked.html @@ -5,6 +5,6 @@ {% block set_common_content %} {% blocktrans trimmed %} -We are sorry, you can't participate in two guidedtours: at the same time. If you think there is an error, contact {{ mail }}. +We are sorry, you can't participate in two guidedtours at the same time. If you think there is an error, contact {{ mail }}. {% endblocktrans %} {% endblock %} diff --git a/guidedtours/templates/guidedtours/signup/signup.html b/guidedtours/templates/guidedtours/signup/signup.html index b2cc6a19..f1611070 100644 --- a/guidedtours/templates/guidedtours/signup/signup.html +++ b/guidedtours/templates/guidedtours/signup/signup.html @@ -27,7 +27,7 @@

    {% trans "All availible Guided guidedtours:" %}

    class="accordion" id="accordion" > - {% for tour in tours %} + {% for tour,dates in tours_and_dates %}

    {% trans "All availible Guided guidedtours:" %}

    aria-controls="collapse{{ forloop.counter }}" onclick='assignTour("{{ tour.id }}")' > - {% blocktranslate trimmed with name=tour.name date=tour.date lenth=tour.length %} - {{ name }} on {{ date }} - {% endblocktranslate %} + {{ name }} + {% for date in dates %} +
    + {{ date.date }}-{{ date.probable_end }} +
    + {% endfor %}
    /", views.view_tour, name="view_tour"), - path("edit//", views.edit_tour, name="edit_tour"), - path("delete//", views.del_tour, name="del_tour"), - path("export///", views.export_tour, name="export_tour"), + path("view//", views.view_tour, name="view_tour"), + path("edit//", views.edit_tour, name="edit_tour"), + path("delete//", views.del_tour, name="del_tour"), + path("export///", views.export_tour, name="export_tour"), ], ), ), diff --git a/guidedtours/views.py b/guidedtours/views.py index ed5488fb..e747fb93 100644 --- a/guidedtours/views.py +++ b/guidedtours/views.py @@ -1,5 +1,6 @@ +import dataclasses +import datetime import time -from datetime import timedelta from typing import Any, Optional, Union from django import forms @@ -7,16 +8,16 @@ from django.contrib.auth.decorators import permission_required from django.core.handlers.wsgi import WSGIRequest from django.db.models import Q, QuerySet -from django.db.models.aggregates import Count from django.forms import formset_factory from django.http import HttpResponse from django.shortcuts import get_object_or_404, redirect, render from django.utils import timezone -from django.utils.datetime_safe import date from django.utils.translation import gettext as _ from django_tex.response import PDFResponse from django_tex.shortcuts import render_to_pdf +from uuid import UUID +from kalendar.models import Date from settool_common import utils from settool_common.models import get_semester, Semester @@ -41,27 +42,54 @@ def list_tours(request: WSGIRequest) -> HttpResponse: return render(request, "guidedtours/tours/list_tours.html", context) +@dataclasses.dataclass +class MinimalTour: + capacity: int + name: str + dates: list[datetime.datetime] + registered: int + + @property + def label(self) -> str: + label = [self.name] + if self.dates: + for date in self.dates: + label.append(f"{date.strftime('%d.%m %H')}Uhr") + else: + label.append(_("No dates")) + return " ".join(label) + + @permission_required("guidedtours.view_participants") def dashboard(request: WSGIRequest) -> HttpResponse: - tours = ( - Tour.objects.filter(Q(semester=get_semester(request)) & Q(date__gte=date.today())) - .values("capacity", "name", "date") - .annotate(registered=Count("participant")) - .order_by("date") - ) + tours_current_semester: list[Tour] = Tour.sorted_by_semester(get_semester(request)) # type:ignore + tours: list[MinimalTour] = [] + for tour in tours_current_semester: + registered = Participant.objects.filter(tour=tour).count() + meetings = tour.associated_meetings + if not meetings: + raise ValueError(f"Tour {tour} has no associated meetings") + tours.append( + MinimalTour( + capacity=tour.capacity, + name=tour.name, + dates=meetings.dates, + registered=registered, + ), + ) context = { - "tour_labels": [f"{tour['name']} {tour['date'].strftime('%d.%m %H')}Uhr" for tour in tours], - "tour_registrations": [tour["registered"] for tour in tours], - "tour_capacity": [tour["capacity"] for tour in tours], + "tour_labels": [tour.label for tour in tours], + "tour_registrations": [tour.registered for tour in tours], + "tour_capacity": [tour.capacity for tour in tours], } return render(request, "guidedtours/tour_dashboard.html", context) @permission_required("guidedtours.view_participants") -def view_tour(request: WSGIRequest, tour_pk: int) -> HttpResponse: +def view_tour(request: WSGIRequest, tour_pk: UUID) -> HttpResponse: tour = get_object_or_404(Tour, pk=tour_pk) - participants = tour.participant_set.order_by("time") + participants = tour.participant_set.order_by("created_at") waitinglist = participants[tour.capacity :] participants = participants[: tour.capacity] @@ -91,7 +119,7 @@ def add_tour(request: WSGIRequest) -> HttpResponse: @permission_required("guidedtours.view_participants") -def edit_tour(request: WSGIRequest, tour_pk: int) -> HttpResponse: +def edit_tour(request: WSGIRequest, tour_pk: UUID) -> HttpResponse: tour = get_object_or_404(Tour, pk=tour_pk) form = TourForm( @@ -112,7 +140,7 @@ def edit_tour(request: WSGIRequest, tour_pk: int) -> HttpResponse: @permission_required("guidedtours.view_participants") -def del_tour(request: WSGIRequest, tour_pk: int) -> HttpResponse: +def del_tour(request: WSGIRequest, tour_pk: UUID) -> HttpResponse: tour = get_object_or_404(Tour, pk=tour_pk) form = forms.Form(request.POST or None) @@ -299,28 +327,61 @@ def send_mail(request: WSGIRequest, mail_pk: int) -> HttpResponse: return render(request, "guidedtours/maintenance/mail/send_mail.html", context) +def _get_tours_and_dates(semester: Semester) -> tuple[list[tuple[Tour, list[Date]]], list[Tour], list[Date]]: + tours: QuerySet[Tour] = Tour.objects.filter( + semester=semester, + open_registration__lt=timezone.now(), + close_registration__gt=timezone.now(), + associated_meetings__isnull=False, + ).all() + tmp_tours_and_dates = [] + for tour in tours: + meeting = tour.associated_meetings + if not meeting: + raise ValueError("Tour without associated meeting") + first_datetime, dates = tour.first_datetime, meeting.date_objects + if first_datetime is not None and dates: + tmp_tours_and_dates.append((first_datetime, tour, dates)) + tours_and_dates = [(tour, dates) for first_datetime, tour, dates in sorted(tmp_tours_and_dates)] + all_tours: list[Tour] = [] + all_dates: list[Date] = [] + for tour, dates in tours_and_dates: + all_tours.append(tour) + all_dates += dates + return tours_and_dates, all_tours, all_dates + + def signup(request: WSGIRequest) -> HttpResponse: semester: Semester = get_object_or_404(Semester, pk=get_semester(request)) curr_settings: Setting = Setting.objects.get_or_create(semester=semester)[0] - tours = semester.tour_set.filter( - open_registration__lt=timezone.now(), - close_registration__gt=timezone.now(), - ).order_by("date") - if not tours: + tours_and_dates, all_tours, all_dates = _get_tours_and_dates(semester) + + if not tours_and_dates: context: dict[str, Any] = {"semester": semester} return render(request, "guidedtours/signup/signup_notour.html", context) - form = ParticipantForm(request.POST or None, tours=tours, semester=semester) + form = ParticipantForm( + request.POST or None, + tours_and_dates=tours_and_dates, + tours=all_tours, + dates=all_dates, + semester=semester, + ) if form.is_valid(): participant: Participant = form.save(commit=False) # if there is a tour with a participant using the same mail in the blocked time-window # (15min for transitioning and 15min for meeting) - conflicting_tours = semester.tour_set.filter( - date__gt=participant.tour.date - timedelta(minutes=30), - date__lt=participant.tour.date + timedelta(minutes=participant.tour.length) + timedelta(minutes=30), - ) - if Participant.objects.filter(Q(email=participant.email) & Q(tours__in=conflicting_tours)).exists(): + + conflicting_dates = [] + for tour, dates in tours_and_dates: + if tour == participant.tour: + continue + for date_obj in dates: + if date_obj.intersects(participant.date): + conflicting_dates.append(date_obj) + + if Participant.objects.filter(Q(email=participant.email) & Q(date__in=conflicting_dates)).exists(): return render(request, "guidedtours/signup/blocked.html", {"mail": TourMail.SET}) participant.save() if curr_settings.mail_registration: @@ -339,7 +400,7 @@ def signup(request: WSGIRequest) -> HttpResponse: context = { "semester": semester, "form": form, - "tours": tours, + "tours_and_dates": tours_and_dates, } return render(request, "guidedtours/signup/signup.html", context) @@ -382,15 +443,16 @@ def signup_internal(request: WSGIRequest) -> HttpResponse: @permission_required("guidedtours.view_participants") -def export_tour(request: WSGIRequest, file_format: str, tour_pk: int) -> Union[HttpResponse, PDFResponse]: +def export_tour(request: WSGIRequest, file_format: str, tour_pk: UUID) -> Union[HttpResponse, PDFResponse]: tour = get_object_or_404(Tour, pk=tour_pk) - participants = tour.participant_set.order_by("time") + participants = tour.participant_set.order_by("created_at") confirmed_participants = participants[: tour.capacity] - filename = f"participants_{tour.name}_{tour.date}_{time.strftime('%Y%m%d-%H%M')}" + tour_date = tour.first_datetime or "No-Date" + filename = f"participants_{tour.name}_{tour_date}_{time.strftime('%Y%m%d-%H%M')}" context = {"participants": confirmed_participants, "tour": tour} if file_format == "csv": return utils.download_csv( - ["surname", "firstname", "time", "email", "phone", "subject"], + ["surname", "firstname", "created_at", "updated_at", "email", "phone", "subject"], f"{filename}.csv", confirmed_participants, ) From 417d5fde5e5d05171eb54444e2235f404d35fb5c Mon Sep 17 00:00:00 2001 From: Frank Elsinga Date: Wed, 20 Apr 2022 16:02:07 +0200 Subject: [PATCH 28/30] guidedtours --- guidedtours/locale/de/LC_MESSAGES/django.mo | Bin 7035 -> 7128 bytes guidedtours/locale/de/LC_MESSAGES/django.po | 2 +- guidedtours/views.py | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/guidedtours/locale/de/LC_MESSAGES/django.mo b/guidedtours/locale/de/LC_MESSAGES/django.mo index b4d161efe6eff31c9a357f9382d6df258f4cd38c..7214ab9425a8e2c7324eab049a290ba84359e72f 100644 GIT binary patch delta 1965 zcmYk-YfR5k9LMo5m4CWWBFXKaYoa2TNN$l3MT*S>xB1)D78Y9?etHr!J!A7A_?Zd5d0jhe{7VL%r|;YAsKZkG-N| zh<)_?S?V_$T0sX6$0(jIGcW;5Fo+vb6WEK~YDZ8LJAqojDU8GOs2AKuP4K?^z8(8= z{TAb~1Jjl281B*ob5LL8V=S&j%{+vBtb&T(IjaE7a<8REuWS*)QEkW&N87g&akXx+W_3y-TuIum| zes%rFIXGHSBlgE;)CBILw&Xod$L}7E0vZ#@Un%ZDrMlJm5VZv_P$~W4`V%;bT5&Rt z#x!RMG6$={VOWoP@fp;DS}+XTPz&=O(@3K629>G~)E0!19}S2@O&|%Cp)Ay1E_K&6 zNVe@TDl;ch<1VAN=!Wwi>bcKR6a0WIz_VX8_Hb&W$&*%efY~Tz4X6nrw{pfXvHn#e)aeaBFT?>EP9(*13;(N%P>^;uFKd4ii zOCGhtSxA>HLw3(9P-nx#qqrYuVcI~p70Xc>X+UM*I4T2YF@g8nO&TP?Ke5HAqJb)7 zJMnAnn^vIovrGN~0>5a*(+J-Eujofihfs@B(WEu8Y(fhuB|IGx?WU>1sGRpZ@=n|GV?K7(px~SgVy0%Gf0B|Nmt?7b%2Jrb;R?*KhF; z!$ZienlfXG VqoNB#n<_)Wd301Z?MnO;{ulHiqZ9xD delta 1871 zcmZA2Uuc$990%~THnWyU8#93&ypp?^7aU{; z_~Q`iZL4yg>Guyakj7T)pBc95ZSZ9p_i;X#@EDi!GiCsPF&!J^Ih(JAn#x>` zu#2nsD>Jcn>6w-G%6?9F{un!XV2b?DR(Vgu3Z7wJ^f$AHP2Q5!O{S(YlWAYfom|Ob zo~+up*f5#UR?gsdW&rys+vOFm;hXH^*_z6L$|8A8st;5?%PheVlhU`U_EBcWXV}iK zD;w-gV<;V*&mQK*k1`Y5$3{NGOyD_Q&Ds%_r0QK}35J;tBg_CkVKVeRvz8aD^A35G z<AGT6lopqsgG9kb~+G6UVrJa;>1*nee@iouk_yp69jyY)M!!w>W+ ze>hmJGFe`;C+716JK4+6xtuN6jAf#i$v_{IfdQufBJ^_!WL z4T_A|lGlGyv$Io@-Zf&e$Wr~k^l3;YQt~yMC$dBTNxajVGsrDQ*} zi}m7u@!zu0$%7(4)mgR&#CW++B`NO_>qM8hTPzVNH;F89N;d0;Ok<8pzA`t6@p7k1 m)@-vF5ZN&K7hEgy^~rvib!zX##tEkmP1@9a>YM2on*Id}%8&T~ diff --git a/guidedtours/locale/de/LC_MESSAGES/django.po b/guidedtours/locale/de/LC_MESSAGES/django.po index d55f4c31..6e33c211 100644 --- a/guidedtours/locale/de/LC_MESSAGES/django.po +++ b/guidedtours/locale/de/LC_MESSAGES/django.po @@ -400,7 +400,7 @@ msgstr "Bestätigte Teilnehmer als PDF ausgeben" #: guidedtours/views.py msgid "No dates" -msgstr "" +msgstr "Keine Daten" #: guidedtours/views.py #, python-brace-format diff --git a/guidedtours/views.py b/guidedtours/views.py index e747fb93..dff4790f 100644 --- a/guidedtours/views.py +++ b/guidedtours/views.py @@ -2,6 +2,7 @@ import datetime import time from typing import Any, Optional, Union +from uuid import UUID from django import forms from django.contrib import messages @@ -15,7 +16,6 @@ from django.utils.translation import gettext as _ from django_tex.response import PDFResponse from django_tex.shortcuts import render_to_pdf -from uuid import UUID from kalendar.models import Date from settool_common import utils From e79a36bcfa592f6b46e2feab90b2c7e30da480ad Mon Sep 17 00:00:00 2001 From: Frank Elsinga Date: Wed, 20 Apr 2022 16:02:23 +0200 Subject: [PATCH 29/30] tutors --- tutors/locale/de/LC_MESSAGES/django.mo | Bin 15838 -> 15776 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/tutors/locale/de/LC_MESSAGES/django.mo b/tutors/locale/de/LC_MESSAGES/django.mo index 974123e22f579d90e53dfce8784336a04f86f011..46ca85967e93e2f2fdd62b85e8d0a3a48afbcbb0 100644 GIT binary patch delta 3904 zcmYk;c~DkW7{~EL0*bN-ihv?$ih`hm2%?1hnxf<6Fr;JZxDT0`8;1G1Z@JZr8*1Q^ zX=*NzYo|oqfseNwDokDpVuD##^Y9Np81-?v69S0+mHVL>6d!lCc1E%3iJoP0^M-8|; zDpLbdnH+_5X>w3|WRZ1482MK!3Te9TVe(# zqwbr5x^6it!}-Wq%~sSvj#w*P6g1;osI{y{-S7{pWKy~ax-p6AQ>VY#+yL~=t21`-jivn9e zfm-8>sOzqx`niR=|DJ7sh#Fvxqsw@+^*y^7wP~VIYt$OmVKQpO-7pDt?6yll088U^ciX?f?7BeYKB^hme!7_31*hu9sT zpkAXC@~gG)hh)=?!gH95I-eHfoF9nV)Ni2nz;x90^H2ldgl(`e#=id*G$?hK?FCh+ z4jv%cH_uV2j^Zv2AO*GS2cyo9MqNK2)z4aFADf-1&3Fd&;BwS`-=nUpY(xH4cw{g1 zB|qvY5H+J{$UZge zT@-Y~0qluoI1cN?IX6zkdeo;Qt6*|%`*zgY??rWV3bm7fUc7yWybr zPG*;49qM1BGI{~^|BtysL8-itO64=uCJEym(M;n|9d|>`bOh@B1Ps7AsQZ_quHS6W z??XT8r&0HPg?;cU24Z+eCd%_oTMFLT3AIVOpawD;wMO$$YnqSxL?1w9;ym*Ik*Pp+ zSc#f(wLKr3=w!45>bexvfHN@&N1;nI%b}oMy$p5XHq=@bqd#6o4WQEc*tYv8k#^c6 zP#w=g&3G}YpEam{_SkwEYKd=R1N<$C{Og8l8q`4zYHfTvIrTu)NFz}LNkGlKD+Xf^ zRENV+6Uau!X5PY7EJXEp4f#fy+sL*s-pM>1lak3l&oqrX^VbaaLv?Tn+hZl_3+T_Q zF&+n_&L74pcn7tFgLsh%ZZ;zRa(O8Hfcn-CPuApASYE*wuv8R^?-<3ZUY3Rky z(ae^hIxaz_@J~#~Sbi4a1YCyqP^o>Bi-+TE)PTRit@sP_2{E%XjNv(EC82@k5IW`) zJ2a*|Pn8F{7x_1fSwU?V5ko8|mJ&MNbMWkf0(Yr@Sjz>rr4+M?dA2UXiBH{s`zL!H zbGHu&3q44E2Emv-$98@XxL*&5_uENn8}W&IZ9prp&)i1?8bvIju3e>t)h5uvhB4>e zzXrs|t)irrIz!YRU(^;}@Q6DpFuvDuT1F6SiAZ8Daf;YXDBao=wZ}#ZZHNeBl5PCJ zy(6%hkDJw)@;=->|(&nHeh?sKA$I7;Y#uyWY3$Qu9g3-7ML$M0^Gu8ZXtEs~zJc~O2J?i?K z=)r$&yNAyBb4(1s;hboXdS*I?;ULtF_n|t0VIQ1`93R$wso z8dU#BFcRyr1 z!(b|dF$qWFc+>T8IP$NC+xCJxsDXqq9ksVYb)1S!-VDLla2#r8mvI2b^3*0c z1~uS^P??&D%H#~BYqJowN6M_*~L0z{VmEm2;SWP8rASbQo9SWN9PpGy02X#YuqT6vaYCx$t0Mk)>Bp)?^ z*{A_5u&zQ~zZKPAC2Alwwq9rJ=TH-KE>j3Bfo-^hnn5gi56lp!QSXg<@H*74--en= z1?szT)YdPc*7_#ux<63;+(F$R%1&2%b7X*yNpuUQlhudXJQ=998infcG1Q1BV{e>; z%1Ak?%n5890yKs{=0gW9=Emw*~*GHNM$qb4*IwG^4wY>aYfm_k7_oP}Dm66;dbE?xq!I+u4 z05!1YceLtVcaHSoh}qZ9ep zh02F)iMp^0s-s@08IM5TOp}eJScDqDx5&mef1xJOl8xiR6x2XFqxQfsR0gt;xtSd7 zkIy?4*sx|l>V^i)!0&J>rge31T!Ep~%aBzun{4}W)Y{jh`niaDYyL#_*OUd7BRHn*p`vGi9y%sfq zGZ>CvATOHv7U{<^LA+BTG$f%$)DCrHI_ieL*5RnsW}&Ws1T~O}_Ixq&CYl#e1KxrM zu?7d>jC41%6?hNzA23w!|8)vl%imC`4Cj?lDw9#0WFTs$V^AGWM$Pm&d;VpNq`m=l ze+BCLkL>v~7)Jd|)P3J$CjNne_kU1Ncc!B;m=iguO)?2J5C^qHn^9|8gZe}_pfd3d z^6!(micx5KxigMNozFsLbR4R`38?FzMMooFMIjnDqGni$%1ABh!V9Q1`voH~n(wa$ z(#GmTwP#^2PC)g005$WIsD93%`uWDz`MNoQktOtTUy~Hn4PI0S{ZP9;)7BqE4Kx?^ zz#`NP=VNnRgqrDE)CATeV>WxRKQ^NJYw2_UjY#&f{_GDkl7_EwHZnMq-IuQ$E<<&2 z9n-N5uZF&aSvU<>qR#(}1=yCS(4|>{>c?}h`=EiCMg2((WEi#7wGM?i3iYU&T|&+1 z25OCeLuDe2cT*pzc+^|qMYa2^BX9`yhp`K;#?E*M^;R@uEdGPqOECl8J?D5SsKfr4 z5yb3p1a*gZMl(Bw>iAbw3VRNA|Fz7=CDhAtIda;ssSW4*82CTBp52X(y3JtRVC#9WS~BcEM)<{)qTgrCrBqVj}Up zD)vag_xwLb_=2ka$&vAKd#M)_j4^O*<#&&Nbfh<|oYEHJUH_uU4nh0;J0e>ozC>NS zYB8Zrpo4v4PWc-my3EztLgW!8#9HF+p?$f7@Do}>9WN7lL+z1o4pUF`jsfs3g`99}zmViB}PNi{AI|k4|yQC>R Date: Wed, 20 Apr 2022 16:02:36 +0200 Subject: [PATCH 30/30] kalendar --- kalendar/feeds.py | 14 +++++++++----- kalendar/locale/de/LC_MESSAGES/django.mo | Bin 2984 -> 3173 bytes kalendar/locale/de/LC_MESSAGES/django.po | 12 ++++++++++++ kalendar/models.py | 8 ++++---- .../management/list/chronological.html | 10 ++++++++-- 5 files changed, 33 insertions(+), 11 deletions(-) diff --git a/kalendar/feeds.py b/kalendar/feeds.py index dd345b42..91f8b4a8 100644 --- a/kalendar/feeds.py +++ b/kalendar/feeds.py @@ -9,7 +9,7 @@ from django.shortcuts import get_object_or_404 from django.urls import reverse from django.utils.html import escape -from django_ical.views import ICalFeed, ICAL_EXTRA_FIELDS +from django_ical.views import ICAL_EXTRA_FIELDS, ICalFeed from tutors.models import Tutor @@ -37,18 +37,22 @@ def items(tutor: Tutor) -> QuerySet[Date]: reference_time = django.utils.timezone.now() - datetime.timedelta(days=7 * 6) return Date.get_dates_for_tutor(tutor.pk, reference_time) - def item_title(self, item: Date) -> str: + @staticmethod + def item_title(item: Date) -> str: super_type = item.group.group_type.capitalize() # Titles should be double escaped by default return escape(f"[SET-{super_type}] {item.group.super_group.name}") - def item_updateddate(self, item: Date) -> datetime.datetime: + @staticmethod + def item_updateddate(item: Date) -> datetime.datetime: return item.updated_at - def item_created(self, item: Date) -> datetime.datetime: + @staticmethod + def item_created(item: Date) -> datetime.datetime: return item.created_at - def item_description(self, item: Date) -> str: + @staticmethod + def item_description(item: Date) -> str: return str(item.group.super_group.description) def item_link(self, item: Date) -> str: diff --git a/kalendar/locale/de/LC_MESSAGES/django.mo b/kalendar/locale/de/LC_MESSAGES/django.mo index e6f6b707f69559bea140a7de2399157f4474b567..efe9a9cb951a0fc3056379a5bcfbd598e0f4c998 100644 GIT binary patch delta 1297 zcmZY9T}YEr9LMpqHCNkgHqDpR+EXi2x85uxi0-rzmRCaHMHIQ(K&QAZf{4|f7J(H} zP~K=l5p>a@i;#j|kidH>YIi|b6&OU(Mc?1EBVBmd|9sAQp7WfS|Fi8;;BDl?s^j2%K{;5c#ya}qV~6zaP)f_g>QP|w{( z9c&IY@AClp*97xiaBu-N@mJK&|6&5e3{w9vDg!4_Cr_hNeib$GP2>z_29=2?$Q0%^ z@-d54%IFd{;-?__SIU3d8$rHldRH~51>zXT9_tt?V-Ii}K140FfZAXg_1q8KgaMB4 zV9eTs{J`AJXndXC|8_zTX(F9YC)h)5CYZ-fDY!pTrEwp@JZ{luS4ta=+LaD=*!E8A zR@A0;*?t0xdJigH72I^`OQO>$rE$U`lxC$)Wp@R)-b)=NVnjQkZ0g;25?(^Z{*{>h zw6_q-;0}TkcHhH+O2_z1y}qQUEm_D6rH1mOg>m!0=?rI5IVY2K^68W_b|#lEU9TGO v_y!nND*K;#W5=?&d?sJW=gz&EPmdR}=gq1sw@(LJOH+YaPh|D2uF!>l7~Ow2 delta 1130 zcmXxkO>9h26u|K_HSL&|(rPc}yg9Q;E3z2Bz|9^dM=Dgp1x9{F}&$%=G*nPiLpKi}j1Z@-1 zOFYa)?7(gFxX?!P5le6k3wRXg<4Nqn@$B!HaWVI|kxxA0(u4^*?{24>I&zM>D#q6-unBU-Q(JFq9aucGs6=<}nVs(S@7X6?=;|T!dYjgW32NT*i0}tCs6H`>9)442~&R?&?g&KyS`jSFZZS28EC z?!X;7Bo`0R4NRgHd5)H93eEfjvZwfhKKB#-&g59cqv%8DRnUaCqU-KL=kLQZ9zoYV zSLFQN`85X2_%531Gqkkt(S<%C?~31O=B`FJO6bDB-RpEXEm{kuyhq-BVlvn>?7KV^@NqPkwaR&k@PNN9S&su zVYFg{% trans "Task lies in the past" %}{% endif %} - {% trans "Guidedtour" %} - {% if show_past %}{% trans "Guidedtour lies in the past" %}{% endif %} + {% trans "Guidedtour" %} + {% if show_past %}{% trans "Guidedtour lies in the past" %}{% endif %}