From 7bec9db10f6c1b06ac6b531886a589c38b10d24e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Valenta?= <git@imaniti.org> Date: Wed, 31 May 2023 18:43:21 +0200 Subject: [PATCH] custom required levels for each defined group, calendar views, colors --- lectures/admin.py | 11 ++- ...023_alter_lecturelector_avatar_and_more.py | 28 ++++-- ...ure_groups_remove_lecture_type_and_more.py | 67 ++++++++++++++ ...5_alter_lecturegrouptype_group_and_more.py | 33 +++++++ lectures/models.py | 89 ++++++++++++------- .../templates/lectures/includes/lecture.html | 10 ++- .../lectures/includes/lecture_type_color.html | 5 ++ .../lectures/view_group_lectures.html | 14 ++- lectures/templates/lectures/view_lecture.html | 22 +++-- lectures/templatetags/lecture_group_types.py | 8 ++ lectures/views.py | 49 +++++----- shared/templates/shared/includes/base.html | 6 +- 12 files changed, 269 insertions(+), 73 deletions(-) create mode 100644 lectures/migrations/0024_remove_lecture_groups_remove_lecture_type_and_more.py create mode 100644 lectures/migrations/0025_alter_lecturegrouptype_group_and_more.py create mode 100644 lectures/templates/lectures/includes/lecture_type_color.html create mode 100644 lectures/templatetags/lecture_group_types.py diff --git a/lectures/admin.py b/lectures/admin.py index ff04fa8..faa8401 100644 --- a/lectures/admin.py +++ b/lectures/admin.py @@ -5,6 +5,7 @@ from shared.admin import MarkdownxGuardedModelAdmin from .models import ( Lecture, LectureGroup, + LectureGroupType, LectureLector, LectureMaterial, LectureRecording, @@ -35,13 +36,20 @@ class LectureMaterialInline(admin.StackedInline): extra = 1 +class LectureGroupTypeInline(admin.StackedInline): + model = LectureGroupType + autocomplete_fields = ("group",) + extra = 1 + + class LectureAdmin(MarkdownxGuardedModelAdmin): inlines = ( + LectureGroupTypeInline, LectureRecordingInline, LectureMaterialInline, ) - autocomplete_fields = ("groups", "lectors", "rsvp_users") + autocomplete_fields = ("lectors", "rsvp_users") search_fields = ("name", "description") readonly_fields = ("rsvp_users",) @@ -58,6 +66,7 @@ class LectureLectorAdmin(MarkdownxGuardedModelAdmin): for model in ( LectureMaterial, LectureRecording, + LectureGroupType, ): admin.site.register(model, IndexHiddenModelAdmin) diff --git a/lectures/migrations/0023_alter_lecturelector_avatar_and_more.py b/lectures/migrations/0023_alter_lecturelector_avatar_and_more.py index 9b43512..c50b8d6 100644 --- a/lectures/migrations/0023_alter_lecturelector_avatar_and_more.py +++ b/lectures/migrations/0023_alter_lecturelector_avatar_and_more.py @@ -1,24 +1,36 @@ # Generated by Django 4.1.4 on 2023-05-31 13:01 from django.db import migrations, models + import lectures.models class Migration(migrations.Migration): - dependencies = [ - ('lectures', '0022_lecturelector_avatar_lecturelector_description_and_more'), + ("lectures", "0022_lecturelector_avatar_lecturelector_description_and_more"), ] operations = [ migrations.AlterField( - model_name='lecturelector', - name='avatar', - field=models.ImageField(blank=True, help_text='VloĹľenĂ˝ soubor má prioritu nad obrázkem synchronizovanĂ˝m z Chobotnice.', null=True, upload_to=lectures.models.get_file_location, verbose_name='ProfilovĂ˝ obrázek'), + model_name="lecturelector", + name="avatar", + field=models.ImageField( + blank=True, + help_text="VloĹľenĂ˝ soubor má prioritu nad obrázkem synchronizovanĂ˝m z Chobotnice.", + null=True, + upload_to=lectures.models.get_file_location, + verbose_name="ProfilovĂ˝ obrázek", + ), ), migrations.AlterField( - model_name='lecturelector', - name='username', - field=models.CharField(blank=True, help_text='NapĹ™. na fĂłru, nebo v Chobotnici. UĹľĂvá se k synchronizaci profilovĂ©ho obrázku, v budoucnu i dalšĂch informacĂ.', max_length=128, null=True, verbose_name='UĹľivatelskĂ© jmĂ©no'), + model_name="lecturelector", + name="username", + field=models.CharField( + blank=True, + help_text="NapĹ™. na fĂłru, nebo v Chobotnici. UĹľĂvá se k synchronizaci profilovĂ©ho obrázku, v budoucnu i dalšĂch informacĂ.", + max_length=128, + null=True, + verbose_name="UĹľivatelskĂ© jmĂ©no", + ), ), ] diff --git a/lectures/migrations/0024_remove_lecture_groups_remove_lecture_type_and_more.py b/lectures/migrations/0024_remove_lecture_groups_remove_lecture_type_and_more.py new file mode 100644 index 0000000..fd81a12 --- /dev/null +++ b/lectures/migrations/0024_remove_lecture_groups_remove_lecture_type_and_more.py @@ -0,0 +1,67 @@ +# Generated by Django 4.1.4 on 2023-05-31 15:04 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("lectures", "0023_alter_lecturelector_avatar_and_more"), + ] + + operations = [ + migrations.RemoveField( + model_name="lecture", + name="groups", + ), + migrations.RemoveField( + model_name="lecture", + name="type", + ), + migrations.CreateModel( + name="LectureGroupType", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "type", + models.CharField( + choices=[ + ("required", "SilnÄ› doporuÄŤenĂ© školenĂ"), + ("recommended", "DoporuÄŤenĂ© školenĂ"), + ("voluntary", "DobrovolnĂ© školenĂ"), + ], + max_length=11, + verbose_name="PoĹľadovanost", + ), + ), + ( + "group", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="lectures.lecturegroup", + verbose_name="VĂ˝uková skupina", + ), + ), + ( + "lecture", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="lectures.lecture", + verbose_name="Ĺ kolenĂ", + ), + ), + ], + options={ + "verbose_name": "ĂšroveĹ poĹľadovanosti pro skupinu", + "verbose_name_plural": "ĂšroveĹ poĹľadovanosti pro skupiny", + }, + ), + ] diff --git a/lectures/migrations/0025_alter_lecturegrouptype_group_and_more.py b/lectures/migrations/0025_alter_lecturegrouptype_group_and_more.py new file mode 100644 index 0000000..c66d099 --- /dev/null +++ b/lectures/migrations/0025_alter_lecturegrouptype_group_and_more.py @@ -0,0 +1,33 @@ +# Generated by Django 4.1.4 on 2023-05-31 15:36 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("lectures", "0024_remove_lecture_groups_remove_lecture_type_and_more"), + ] + + operations = [ + migrations.AlterField( + model_name="lecturegrouptype", + name="group", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="lecture_group_types", + to="lectures.lecturegroup", + verbose_name="VĂ˝uková skupina", + ), + ), + migrations.AlterField( + model_name="lecturegrouptype", + name="lecture", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="lecture_group_types", + to="lectures.lecture", + verbose_name="Ĺ kolenĂ", + ), + ), + ] diff --git a/lectures/models.py b/lectures/models.py index 687a2fd..72b0cf7 100644 --- a/lectures/models.py +++ b/lectures/models.py @@ -56,10 +56,10 @@ class LectureGroup(NameStrMixin, models.Model): blank=True, verbose_name="Popis", help_text=mark_safe( - 'MĹŻĹľeš pouĹľĂt <a href="' - "https://cs.wikipedia.org/wiki/Markdown" - '#P%C5%99%C3%ADklad_u%C5%BEit%C3%AD"' - ">Markdown</a>." + r'MĹŻĹľeš pouĹľĂt <a href="' + r"https://cs.wikipedia.org/wiki/Markdown" + r'#P%C5%99%C3%ADklad_u%C5%BEit%C3%AD"' + r">Markdown</a>." ), ) @@ -81,6 +81,36 @@ class LectureGroup(NameStrMixin, models.Model): ordering = ("priority", "name") +class LectureGroupType(models.Model): + lecture = models.ForeignKey( + "Lecture", + on_delete=models.CASCADE, + related_name="lecture_group_types", + verbose_name="Ĺ kolenĂ", + ) + group = models.ForeignKey( + "LectureGroup", + on_delete=models.CASCADE, + related_name="lecture_group_types", + verbose_name="VĂ˝uková skupina", + ) + + class TypeChoices(models.TextChoices): + REQUIRED = "required", "SilnÄ› doporuÄŤenĂ© školenĂ" + RECOMMENDED = "recommended", "DoporuÄŤenĂ© školenĂ" + VOLUNTARY = "voluntary", "DobrovolnĂ© školenĂ" + + type = models.CharField( + choices=TypeChoices.choices, + max_length=11, + verbose_name="PoĹľadovanost", + ) + + class Meta: + verbose_name = "ĂšroveĹ poĹľadovanosti pro skupinu" + verbose_name_plural = "ĂšroveĹ poĹľadovanosti pro skupiny" + + class Lecture(NameStrMixin, models.Model): is_current_starting_treshold = timedelta(hours=8) is_current_ending_treshold = timedelta(days=60) @@ -91,24 +121,6 @@ class Lecture(NameStrMixin, models.Model): null=True, ) # If undefined, assume this event was in the past - class TypeChoices(models.TextChoices): - RECOMMENDED = "recommended", "DoporuÄŤenĂ© školenĂ" - OPTIONAL = "optional", "VolitelnĂ© školenĂ" - - groups = models.ManyToManyField( - LectureGroup, - blank=True, - related_name="lectures", - verbose_name="VĂ˝ukovĂ© skupiny", - help_text="Pokud nevybereš žádnĂ© skupiny, školenĂ je dostupnĂ© všem.", - ) - - type = models.CharField( - choices=TypeChoices.choices, - max_length=11, - verbose_name="Typ", - ) - name = models.CharField( max_length=128, verbose_name="Název", @@ -119,10 +131,10 @@ class Lecture(NameStrMixin, models.Model): null=True, verbose_name="Popis", help_text=mark_safe( - 'MĹŻĹľeš pouĹľĂt <a href="' - "https://cs.wikipedia.org/wiki/Markdown" - '#P%C5%99%C3%ADklad_u%C5%BEit%C3%AD"' - ">Markdown</a>." + r'MĹŻĹľeš pouĹľĂt <a href="' + r"https://cs.wikipedia.org/wiki/Markdown" + r'#P%C5%99%C3%ADklad_u%C5%BEit%C3%AD"' + r">Markdown</a>." ), ) @@ -141,6 +153,23 @@ class Lecture(NameStrMixin, models.Model): settings = app_settings.LectureSettings("NastavenĂ") + def get_current_group_type(self, request, group=None): + if group is not None: + return LectureGroupType.objects.filter(lecture=self, group=group).first() + else: + return LectureGroupType.objects.filter( + lecture=self, + group__in=( + LectureGroup.filter( + models.Q(user_groups__in=request.user.groups.all()) + if not request.user.is_superuser + else models.Q(id__isnull=False) # Always True + ) + .distinct() + .all() + ), + ).first() + class Meta: verbose_name = "Ĺ kolenĂ" verbose_name_plural = verbose_name @@ -159,10 +188,10 @@ class LectureLector(NameStrMixin, models.Model): null=True, verbose_name="Popis", help_text=mark_safe( - 'MĹŻĹľeš pouĹľĂt <a href="' - "https://cs.wikipedia.org/wiki/Markdown" - '#P%C5%99%C3%ADklad_u%C5%BEit%C3%AD"' - ">Markdown</a>." + r'MĹŻĹľeš pouĹľĂt <a href="' + r"https://cs.wikipedia.org/wiki/Markdown" + r'#P%C5%99%C3%ADklad_u%C5%BEit%C3%AD"' + r">Markdown</a>." ), ) diff --git a/lectures/templates/lectures/includes/lecture.html b/lectures/templates/lectures/includes/lecture.html index feda986..eb8469b 100644 --- a/lectures/templates/lectures/includes/lecture.html +++ b/lectures/templates/lectures/includes/lecture.html @@ -1,4 +1,4 @@ -{% load markdownify %} +{% load markdownify lecture_group_types %} <li> <a @@ -8,7 +8,13 @@ <div class="card elevation-6"> <div class="card__body p-5 hover:bg-gray-100 duration-100"> <h2 class="head-alt-sm mb-4"> - <i class="ico--bookmark mr-2"></i> + <i + class=" + ico--bookmark mr-2 + {% get_lecture_type lecture request group as type %} + {% include "lectures/includes/lecture_type_color.html" %} + " + ></i> {{ lecture.name }} </h2> diff --git a/lectures/templates/lectures/includes/lecture_type_color.html b/lectures/templates/lectures/includes/lecture_type_color.html new file mode 100644 index 0000000..527b73a --- /dev/null +++ b/lectures/templates/lectures/includes/lecture_type_color.html @@ -0,0 +1,5 @@ +{% if type.type == type.TypeChoices.REQUIRED.value %} + text-red-700 +{% elif type.type == type.TypeChoices.RECOMMENDED.value %} + text-amber-500 +{% endif %} diff --git a/lectures/templates/lectures/view_group_lectures.html b/lectures/templates/lectures/view_group_lectures.html index 2d835f3..9c32e83 100644 --- a/lectures/templates/lectures/view_group_lectures.html +++ b/lectures/templates/lectures/view_group_lectures.html @@ -1,6 +1,6 @@ {% extends "shared/includes/base.html" %} {% load render_bundle from webpack_loader %} -{% load markdownify static %} +{% load markdownify static lecture_group_types %} {% block content %} {% render_bundle "view_group_lectures" %} @@ -90,7 +90,17 @@ {% for lecture in lectures %} <li> <a - class="inline-block px-2.5 py-1.5 bg-gray-200 rounded-sm duration-100 hover:no-underline hover:bg-gray-300" + class=" + inline-block px-2.5 py-1.5 rounded-sm duration-100 hover:no-underline + {% get_lecture_type lecture request group as type %} + {% if type.type == type.TypeChoices.REQUIRED.value %} + bg-red-700 hover:bg-red-800 text-white + {% elif type.type == type.TypeChoices.RECOMMENDED.value %} + bg-amber-500 hover:bg-amber-600 + {% else %} + bg-gray-200 hover:bg-gray-300 + {% endif %} + " href="{% url "lectures:view_lecture" lecture.id %}?related_group_id={{ group.id }}" >{{ lecture.name }}</a> </li> diff --git a/lectures/templates/lectures/view_lecture.html b/lectures/templates/lectures/view_lecture.html index f36e41d..932a2f0 100644 --- a/lectures/templates/lectures/view_lecture.html +++ b/lectures/templates/lectures/view_lecture.html @@ -1,6 +1,6 @@ {% extends "shared/includes/base.html" %} {% load render_bundle from webpack_loader %} -{% load markdownify rsvp %} +{% load markdownify rsvp lecture_group_types %} {% block content %} {% render_bundle "view_lecture" %} @@ -15,7 +15,19 @@ {% endif %} <div class="flex justify-between items-start gap-3 flex-col md:flex-row"> - {% include "shared/includes/double_heading.html" with heading=lecture.name subheading=lecture.get_type_display icon="ico--bookmark" %} + <div class="flex gap-4 mb-10"> + {% get_lecture_type lecture request group as type %} + <i + class=" + ico--bookmark text-6xl md:text-7xl + {% include "lectures/includes/lecture_type_color.html" %} + " + ></i> + <div> + <h1 class="head-alt-md md:head-alt-lg">{{ lecture.name }}</h1> + <h2 class="head-alt-sm">{{ type.get_type_display }}</h2> + </div> + </div> {% if user.is_authenticated %} <button @@ -56,12 +68,12 @@ <span>Skupiny</span> </div> <ul class="flex gap-2"> - {% for group in lecture.groups.all %} + {% for group_type in lecture.lecture_group_types.all %} <li> <a class="px-1.5 py-1 bg-gray-200 rounded-sm duration-150 hover:no-underline hover:bg-gray-300" - href="{% url "lectures:view_group_lectures" group.id %}" - >{{ group.name }}</a> + href="{% url "lectures:view_group_lectures" group_type.group.id %}" + >{{ group_type.group.name }}</a> </li> {% endfor %} </ul> diff --git a/lectures/templatetags/lecture_group_types.py b/lectures/templatetags/lecture_group_types.py new file mode 100644 index 0000000..aa2f69b --- /dev/null +++ b/lectures/templatetags/lecture_group_types.py @@ -0,0 +1,8 @@ +from django import template + +register = template.Library() + + +@register.simple_tag +def get_lecture_type(lecture, request, group=None): + return lecture.get_current_group_type(request, group) diff --git a/lectures/views.py b/lectures/views.py index 1624c92..db438c0 100644 --- a/lectures/views.py +++ b/lectures/views.py @@ -32,10 +32,7 @@ class LectureMaterialFileDownloadView(ObjectDownloadView): lecture__groups__in=( get_objects_for_user( self.current_user, "lectures.view_lecturegroup" - ).filter( - models.Q(user_groups__in=self.current_user.groups.all()) - | models.Q(user_groups=None) - ) + ).filter(models.Q(user_groups__in=self.current_user.groups.all())) ) ) ) @@ -74,12 +71,15 @@ def get_lectures(request, filter=None, get_exceptions: bool = True) -> tuple: if not ( get_objects_for_user(request.user, "lectures.view_lecturegroup") .filter( - models.Q(id__in=(LectureGroup.objects.filter(lectures__in=lectures))) - & ( - ( - models.Q(user_groups__in=request.user.groups.all()) - | models.Q(user_groups=None) + models.Q( + id__in=( + LectureGroup.objects.filter( + lecture_group_types__lecture__in=lectures + ) ) + ) + & ( + models.Q(user_groups__in=request.user.groups.all()) if not request.user.is_superuser else models.Q(id__isnull=False) # Always True ) @@ -103,10 +103,7 @@ def view_groups(request): lecture_groups = ( get_objects_for_user(request.user, "lectures.view_lecturegroup") .filter( - ( - models.Q(user_groups__in=request.user.groups.all()) - | models.Q(user_groups=None) - ) + models.Q(user_groups__in=request.user.groups.all()) if not request.user.is_superuser else models.Q(id__isnull=False) # Always True ) @@ -136,10 +133,7 @@ def view_group_lectures(request, group_id: int): group_id_exists = group.exists() if not request.user.is_superuser: - group = group.filter( - models.Q(user_groups__in=request.user.groups.all()) - | models.Q(user_groups=None) - ) + group = group.filter(models.Q(user_groups__in=request.user.groups.all())) if not group.exists(): if not group_id_exists: # Doesn't exist at all @@ -158,7 +152,7 @@ def view_group_lectures(request, group_id: int): past_lectures = ( get_objects_for_user(request.user, "lectures.view_lecture") .filter( - groups=group, + lecture_group_types__group=group, timestamp__lt=timestamp_starting_separator, ) .all() @@ -166,7 +160,7 @@ def view_group_lectures(request, group_id: int): current_lectures = ( get_objects_for_user(request.user, "lectures.view_lecture") .filter( - groups=group, + lecture_group_types__group=group, timestamp__gte=timestamp_starting_separator, timestamp__lte=timestamp_ending_separator, ) @@ -180,7 +174,10 @@ def view_group_lectures(request, group_id: int): current_lectures, ( get_objects_for_user(request.user, "lectures.view_lecture") - .filter(groups=group, timestamp__gte=timestamp_ending_separator) + .filter( + lecture_group_types__group=group, + timestamp__gte=timestamp_ending_separator, + ) .all() ), ) @@ -286,6 +283,13 @@ def view_lecture(request, lecture_id: int): lecture = lecture.first() + related_group_id = get_related_group_id(request) + group = ( + LectureGroup.objects.get(id=related_group_id) + if related_group_id is not None + else None + ) + return render( request, "lectures/view_lecture.html", @@ -295,7 +299,8 @@ def view_lecture(request, lecture_id: int): "description": f"e-LearningovĂ© školenĂ {lecture.name}.", "header_name": lecture.name, "lecture": lecture, - "related_group_id": get_related_group_id(request), + "related_group_id": related_group_id, + "group": group, }, ) @@ -425,7 +430,7 @@ def view_registered(request): @require_POST @login_required def rsvp_lecture(request, lecture_id: int): - is_successful, lecture = get_lecture(request, models.Q(id=lecture_id)) + is_successful, lecture = get_lectures(request, models.Q(id=lecture_id)) if not is_successful: return lecture diff --git a/shared/templates/shared/includes/base.html b/shared/templates/shared/includes/base.html index a302923..c8d20bb 100644 --- a/shared/templates/shared/includes/base.html +++ b/shared/templates/shared/includes/base.html @@ -38,12 +38,12 @@ <!-- TODO: Replace with 2.13.x once released --> <link - href="http://localhost:3000/css/styles.css" + href="https://styleguide.pirati.cz/2.13.x/css/styles.css" rel="stylesheet" media="all" > <link - href="http://localhost:3000/css/pattern-scaffolding.css" + href="https://styleguide.pirati.cz/2.13.x/css/pattern-scaffolding.css" rel="stylesheet" media="all" > @@ -214,7 +214,7 @@ </footer> <script - src="http://localhost:3000/js/main.bundle.js" + src="https://styleguide.pirati.cz/2.13.x/js/main.bundle.js" ></script> </body> </html> -- GitLab