Skip to content
Snippets Groups Projects
Commit 7bec9db1 authored by Tomáš Valenta's avatar Tomáš Valenta
Browse files

custom required levels for each defined group, calendar views, colors

parent d450c980
Branches
No related tags found
No related merge requests found
Pipeline #13082 passed
Showing
with 269 additions and 73 deletions
...@@ -5,6 +5,7 @@ from shared.admin import MarkdownxGuardedModelAdmin ...@@ -5,6 +5,7 @@ from shared.admin import MarkdownxGuardedModelAdmin
from .models import ( from .models import (
Lecture, Lecture,
LectureGroup, LectureGroup,
LectureGroupType,
LectureLector, LectureLector,
LectureMaterial, LectureMaterial,
LectureRecording, LectureRecording,
...@@ -35,13 +36,20 @@ class LectureMaterialInline(admin.StackedInline): ...@@ -35,13 +36,20 @@ class LectureMaterialInline(admin.StackedInline):
extra = 1 extra = 1
class LectureGroupTypeInline(admin.StackedInline):
model = LectureGroupType
autocomplete_fields = ("group",)
extra = 1
class LectureAdmin(MarkdownxGuardedModelAdmin): class LectureAdmin(MarkdownxGuardedModelAdmin):
inlines = ( inlines = (
LectureGroupTypeInline,
LectureRecordingInline, LectureRecordingInline,
LectureMaterialInline, LectureMaterialInline,
) )
autocomplete_fields = ("groups", "lectors", "rsvp_users") autocomplete_fields = ("lectors", "rsvp_users")
search_fields = ("name", "description") search_fields = ("name", "description")
readonly_fields = ("rsvp_users",) readonly_fields = ("rsvp_users",)
...@@ -58,6 +66,7 @@ class LectureLectorAdmin(MarkdownxGuardedModelAdmin): ...@@ -58,6 +66,7 @@ class LectureLectorAdmin(MarkdownxGuardedModelAdmin):
for model in ( for model in (
LectureMaterial, LectureMaterial,
LectureRecording, LectureRecording,
LectureGroupType,
): ):
admin.site.register(model, IndexHiddenModelAdmin) admin.site.register(model, IndexHiddenModelAdmin)
......
# Generated by Django 4.1.4 on 2023-05-31 13:01 # Generated by Django 4.1.4 on 2023-05-31 13:01
from django.db import migrations, models from django.db import migrations, models
import lectures.models import lectures.models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('lectures', '0022_lecturelector_avatar_lecturelector_description_and_more'), ("lectures", "0022_lecturelector_avatar_lecturelector_description_and_more"),
] ]
operations = [ operations = [
migrations.AlterField( migrations.AlterField(
model_name='lecturelector', model_name="lecturelector",
name='avatar', 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'), 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( migrations.AlterField(
model_name='lecturelector', model_name="lecturelector",
name='username', 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'), 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",
),
), ),
] ]
# 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",
},
),
]
# 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í",
),
),
]
...@@ -56,10 +56,10 @@ class LectureGroup(NameStrMixin, models.Model): ...@@ -56,10 +56,10 @@ class LectureGroup(NameStrMixin, models.Model):
blank=True, blank=True,
verbose_name="Popis", verbose_name="Popis",
help_text=mark_safe( help_text=mark_safe(
'Můžeš použít <a href="' r'Můžeš použít <a href="'
"https://cs.wikipedia.org/wiki/Markdown" r"https://cs.wikipedia.org/wiki/Markdown"
'#P%C5%99%C3%ADklad_u%C5%BEit%C3%AD"' r'#P%C5%99%C3%ADklad_u%C5%BEit%C3%AD"'
">Markdown</a>." r">Markdown</a>."
), ),
) )
...@@ -81,6 +81,36 @@ class LectureGroup(NameStrMixin, models.Model): ...@@ -81,6 +81,36 @@ class LectureGroup(NameStrMixin, models.Model):
ordering = ("priority", "name") 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): class Lecture(NameStrMixin, models.Model):
is_current_starting_treshold = timedelta(hours=8) is_current_starting_treshold = timedelta(hours=8)
is_current_ending_treshold = timedelta(days=60) is_current_ending_treshold = timedelta(days=60)
...@@ -91,24 +121,6 @@ class Lecture(NameStrMixin, models.Model): ...@@ -91,24 +121,6 @@ class Lecture(NameStrMixin, models.Model):
null=True, null=True,
) # If undefined, assume this event was in the past ) # 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( name = models.CharField(
max_length=128, max_length=128,
verbose_name="Název", verbose_name="Název",
...@@ -119,10 +131,10 @@ class Lecture(NameStrMixin, models.Model): ...@@ -119,10 +131,10 @@ class Lecture(NameStrMixin, models.Model):
null=True, null=True,
verbose_name="Popis", verbose_name="Popis",
help_text=mark_safe( help_text=mark_safe(
'Můžeš použít <a href="' r'Můžeš použít <a href="'
"https://cs.wikipedia.org/wiki/Markdown" r"https://cs.wikipedia.org/wiki/Markdown"
'#P%C5%99%C3%ADklad_u%C5%BEit%C3%AD"' r'#P%C5%99%C3%ADklad_u%C5%BEit%C3%AD"'
">Markdown</a>." r">Markdown</a>."
), ),
) )
...@@ -141,6 +153,23 @@ class Lecture(NameStrMixin, models.Model): ...@@ -141,6 +153,23 @@ class Lecture(NameStrMixin, models.Model):
settings = app_settings.LectureSettings("Nastavení") 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: class Meta:
verbose_name = "Školení" verbose_name = "Školení"
verbose_name_plural = verbose_name verbose_name_plural = verbose_name
...@@ -159,10 +188,10 @@ class LectureLector(NameStrMixin, models.Model): ...@@ -159,10 +188,10 @@ class LectureLector(NameStrMixin, models.Model):
null=True, null=True,
verbose_name="Popis", verbose_name="Popis",
help_text=mark_safe( help_text=mark_safe(
'Můžeš použít <a href="' r'Můžeš použít <a href="'
"https://cs.wikipedia.org/wiki/Markdown" r"https://cs.wikipedia.org/wiki/Markdown"
'#P%C5%99%C3%ADklad_u%C5%BEit%C3%AD"' r'#P%C5%99%C3%ADklad_u%C5%BEit%C3%AD"'
">Markdown</a>." r">Markdown</a>."
), ),
) )
......
{% load markdownify %} {% load markdownify lecture_group_types %}
<li> <li>
<a <a
...@@ -8,7 +8,13 @@ ...@@ -8,7 +8,13 @@
<div class="card elevation-6"> <div class="card elevation-6">
<div class="card__body p-5 hover:bg-gray-100 duration-100"> <div class="card__body p-5 hover:bg-gray-100 duration-100">
<h2 class="head-alt-sm mb-4"> <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 }} {{ lecture.name }}
</h2> </h2>
......
{% if type.type == type.TypeChoices.REQUIRED.value %}
text-red-700
{% elif type.type == type.TypeChoices.RECOMMENDED.value %}
text-amber-500
{% endif %}
{% extends "shared/includes/base.html" %} {% extends "shared/includes/base.html" %}
{% load render_bundle from webpack_loader %} {% load render_bundle from webpack_loader %}
{% load markdownify static %} {% load markdownify static lecture_group_types %}
{% block content %} {% block content %}
{% render_bundle "view_group_lectures" %} {% render_bundle "view_group_lectures" %}
...@@ -90,7 +90,17 @@ ...@@ -90,7 +90,17 @@
{% for lecture in lectures %} {% for lecture in lectures %}
<li> <li>
<a <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 }}" href="{% url "lectures:view_lecture" lecture.id %}?related_group_id={{ group.id }}"
>{{ lecture.name }}</a> >{{ lecture.name }}</a>
</li> </li>
......
{% extends "shared/includes/base.html" %} {% extends "shared/includes/base.html" %}
{% load render_bundle from webpack_loader %} {% load render_bundle from webpack_loader %}
{% load markdownify rsvp %} {% load markdownify rsvp lecture_group_types %}
{% block content %} {% block content %}
{% render_bundle "view_lecture" %} {% render_bundle "view_lecture" %}
...@@ -15,7 +15,19 @@ ...@@ -15,7 +15,19 @@
{% endif %} {% endif %}
<div class="flex justify-between items-start gap-3 flex-col md:flex-row"> <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 %} {% if user.is_authenticated %}
<button <button
...@@ -56,12 +68,12 @@ ...@@ -56,12 +68,12 @@
<span>Skupiny</span> <span>Skupiny</span>
</div> </div>
<ul class="flex gap-2"> <ul class="flex gap-2">
{% for group in lecture.groups.all %} {% for group_type in lecture.lecture_group_types.all %}
<li> <li>
<a <a
class="px-1.5 py-1 bg-gray-200 rounded-sm duration-150 hover:no-underline hover:bg-gray-300" 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 %}" href="{% url "lectures:view_group_lectures" group_type.group.id %}"
>{{ group.name }}</a> >{{ group_type.group.name }}</a>
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>
......
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)
...@@ -32,10 +32,7 @@ class LectureMaterialFileDownloadView(ObjectDownloadView): ...@@ -32,10 +32,7 @@ class LectureMaterialFileDownloadView(ObjectDownloadView):
lecture__groups__in=( lecture__groups__in=(
get_objects_for_user( get_objects_for_user(
self.current_user, "lectures.view_lecturegroup" self.current_user, "lectures.view_lecturegroup"
).filter( ).filter(models.Q(user_groups__in=self.current_user.groups.all()))
models.Q(user_groups__in=self.current_user.groups.all())
| models.Q(user_groups=None)
)
) )
) )
) )
...@@ -74,12 +71,15 @@ def get_lectures(request, filter=None, get_exceptions: bool = True) -> tuple: ...@@ -74,12 +71,15 @@ def get_lectures(request, filter=None, get_exceptions: bool = True) -> tuple:
if not ( if not (
get_objects_for_user(request.user, "lectures.view_lecturegroup") get_objects_for_user(request.user, "lectures.view_lecturegroup")
.filter( .filter(
models.Q(id__in=(LectureGroup.objects.filter(lectures__in=lectures))) models.Q(
id__in=(
LectureGroup.objects.filter(
lecture_group_types__lecture__in=lectures
)
)
)
& ( & (
(
models.Q(user_groups__in=request.user.groups.all()) models.Q(user_groups__in=request.user.groups.all())
| models.Q(user_groups=None)
)
if not request.user.is_superuser if not request.user.is_superuser
else models.Q(id__isnull=False) # Always True else models.Q(id__isnull=False) # Always True
) )
...@@ -103,10 +103,7 @@ def view_groups(request): ...@@ -103,10 +103,7 @@ def view_groups(request):
lecture_groups = ( lecture_groups = (
get_objects_for_user(request.user, "lectures.view_lecturegroup") get_objects_for_user(request.user, "lectures.view_lecturegroup")
.filter( .filter(
(
models.Q(user_groups__in=request.user.groups.all()) models.Q(user_groups__in=request.user.groups.all())
| models.Q(user_groups=None)
)
if not request.user.is_superuser if not request.user.is_superuser
else models.Q(id__isnull=False) # Always True else models.Q(id__isnull=False) # Always True
) )
...@@ -136,10 +133,7 @@ def view_group_lectures(request, group_id: int): ...@@ -136,10 +133,7 @@ def view_group_lectures(request, group_id: int):
group_id_exists = group.exists() group_id_exists = group.exists()
if not request.user.is_superuser: if not request.user.is_superuser:
group = group.filter( group = group.filter(models.Q(user_groups__in=request.user.groups.all()))
models.Q(user_groups__in=request.user.groups.all())
| models.Q(user_groups=None)
)
if not group.exists(): if not group.exists():
if not group_id_exists: # Doesn't exist at all if not group_id_exists: # Doesn't exist at all
...@@ -158,7 +152,7 @@ def view_group_lectures(request, group_id: int): ...@@ -158,7 +152,7 @@ def view_group_lectures(request, group_id: int):
past_lectures = ( past_lectures = (
get_objects_for_user(request.user, "lectures.view_lecture") get_objects_for_user(request.user, "lectures.view_lecture")
.filter( .filter(
groups=group, lecture_group_types__group=group,
timestamp__lt=timestamp_starting_separator, timestamp__lt=timestamp_starting_separator,
) )
.all() .all()
...@@ -166,7 +160,7 @@ def view_group_lectures(request, group_id: int): ...@@ -166,7 +160,7 @@ def view_group_lectures(request, group_id: int):
current_lectures = ( current_lectures = (
get_objects_for_user(request.user, "lectures.view_lecture") get_objects_for_user(request.user, "lectures.view_lecture")
.filter( .filter(
groups=group, lecture_group_types__group=group,
timestamp__gte=timestamp_starting_separator, timestamp__gte=timestamp_starting_separator,
timestamp__lte=timestamp_ending_separator, timestamp__lte=timestamp_ending_separator,
) )
...@@ -180,7 +174,10 @@ def view_group_lectures(request, group_id: int): ...@@ -180,7 +174,10 @@ def view_group_lectures(request, group_id: int):
current_lectures, current_lectures,
( (
get_objects_for_user(request.user, "lectures.view_lecture") 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() .all()
), ),
) )
...@@ -286,6 +283,13 @@ def view_lecture(request, lecture_id: int): ...@@ -286,6 +283,13 @@ def view_lecture(request, lecture_id: int):
lecture = lecture.first() 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( return render(
request, request,
"lectures/view_lecture.html", "lectures/view_lecture.html",
...@@ -295,7 +299,8 @@ def view_lecture(request, lecture_id: int): ...@@ -295,7 +299,8 @@ def view_lecture(request, lecture_id: int):
"description": f"e-Learningové školení {lecture.name}.", "description": f"e-Learningové školení {lecture.name}.",
"header_name": lecture.name, "header_name": lecture.name,
"lecture": lecture, "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): ...@@ -425,7 +430,7 @@ def view_registered(request):
@require_POST @require_POST
@login_required @login_required
def rsvp_lecture(request, lecture_id: int): 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: if not is_successful:
return lecture return lecture
......
...@@ -38,12 +38,12 @@ ...@@ -38,12 +38,12 @@
<!-- TODO: Replace with 2.13.x once released --> <!-- TODO: Replace with 2.13.x once released -->
<link <link
href="http://localhost:3000/css/styles.css" href="https://styleguide.pirati.cz/2.13.x/css/styles.css"
rel="stylesheet" rel="stylesheet"
media="all" media="all"
> >
<link <link
href="http://localhost:3000/css/pattern-scaffolding.css" href="https://styleguide.pirati.cz/2.13.x/css/pattern-scaffolding.css"
rel="stylesheet" rel="stylesheet"
media="all" media="all"
> >
...@@ -214,7 +214,7 @@ ...@@ -214,7 +214,7 @@
</footer> </footer>
<script <script
src="http://localhost:3000/js/main.bundle.js" src="https://styleguide.pirati.cz/2.13.x/js/main.bundle.js"
></script> ></script>
</body> </body>
</html> </html>
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment