From 078400635d74803f2354a70ebbdd1c1d2f7d9f8f 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 15:00:44 +0200
Subject: [PATCH] add registered events view, finished lector view,
 optimizations

---
 ...023_alter_lecturelector_avatar_and_more.py | 24 ++++++
 lectures/models.py                            |  4 +-
 lectures/templates/lectures/view_lector.html  | 61 +++++++++++++-
 lectures/templates/lectures/view_lecture.html | 55 +++++++-----
 .../templates/lectures/view_registered.html   | 11 +++
 lectures/urls.py                              |  6 ++
 lectures/views.py                             | 83 ++++++++++++++++---
 requirements/base.txt                         |  1 +
 shared/templates/shared/includes/base.html    | 17 +++-
 9 files changed, 219 insertions(+), 43 deletions(-)
 create mode 100644 lectures/migrations/0023_alter_lecturelector_avatar_and_more.py
 create mode 100644 lectures/templates/lectures/view_registered.html

diff --git a/lectures/migrations/0023_alter_lecturelector_avatar_and_more.py b/lectures/migrations/0023_alter_lecturelector_avatar_and_more.py
new file mode 100644
index 0000000..9b43512
--- /dev/null
+++ b/lectures/migrations/0023_alter_lecturelector_avatar_and_more.py
@@ -0,0 +1,24 @@
+# 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'),
+    ]
+
+    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'),
+        ),
+        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'),
+        ),
+    ]
diff --git a/lectures/models.py b/lectures/models.py
index 8b143d9..687a2fd 100644
--- a/lectures/models.py
+++ b/lectures/models.py
@@ -176,13 +176,13 @@ class LectureLector(NameStrMixin, models.Model):
         ),
     )
 
-    avatar = models.FileField(
+    avatar = models.ImageField(
         blank=True,
         null=True,
         upload_to=get_file_location,
         verbose_name="Profilový obrázek",
         help_text=(
-            "Vložený soubor má prioritu nad obrázkem synchronizovaným " "z Chobotnice."
+            "Vložený soubor má prioritu nad obrázkem synchronizovaným z Chobotnice."
         ),
     )
 
diff --git a/lectures/templates/lectures/view_lector.html b/lectures/templates/lectures/view_lector.html
index d3d8247..9a1f1bd 100644
--- a/lectures/templates/lectures/view_lector.html
+++ b/lectures/templates/lectures/view_lector.html
@@ -5,19 +5,72 @@
 {% block content %}
     {% render_bundle "view_group_lectures" %}
 
-    {% include "shared/includes/double_heading.html" with heading=header_name subheading=header_desc icon="ico--user" %}
+    {% if related_lecture_id %}
+        <div class="flex gap-2 mb-10">
+            <i class="ico--chevron-left"></i>
+            <a
+                href="{% url "lectures:view_lecture" related_lecture_id %}{% if related_group_id %}?related_group_id={{ related_group_id }}{% endif %}"
+            >Zpět na školení</a>
+        </div>
+    {% endif %}
 
+    <div class="flex gap-4 mb-10 items-center">
+        <img
+            alt="Profilový obrázek lektora"
+            {% if lector.avatar %}
+                src="{{ lector.avatar.url }}"
+            {% elif lector.username %}
+                src="https://a.pirati.cz/piratar/300/{{ lector.username }}.jpg"
+            {% else %}
+                src="https://a.pirati.cz/piratar/300/default.jpg"
+            {% endif %}
+            class="h-20 w-20 md:h-24 md:w-24 rounded-full"
+        >
+        <div>
+            <h1 class="head-alt-md md:head-alt-lg">{{ header_name }}</h1>
+            <h2 class="head-alt-sm">{{ header_desc }}</h2>
+        </div>
+    </div>
 
+    {% if lector.description %}
+        <div class="mb-10 prose max-w-none">
+            {{ lector.description|markdownify|safe }}
+        </div>
+    {% endif %}
 
-    <h2 class="head-alt-md my-8">Vytvořená školení</h2>
+    <div class="flex flex-col gap-2 my-4 py-4 border-y border-gray-200 mb-10">
+        <div class="flex justify-between gap-2 text-lg text-gray-600">
+            <div class="flex gap-2 items-center">
+                <i class="ico--globe"></i>
+                <span>Web</span>
+            </div>
+            <a
+                href="{{ lector.url }}"
+                class="underline"
+            >
+                {{ lector.url }}
+            </a>
+        </div>
+        <div class="flex justify-between gap-2 text-lg text-gray-600">
+            <div class="flex gap-2 items-center">
+                <i class="ico--book"></i>
+                <span>Počet školení</span>
+            </div>
+            <div>
+                {{ lectures|length }}
+            </div>
+        </div>
+    </div>
 
-    <ul class="flex flex-col gap-2">
+    <h2 class="head-alt-md my-8">Vyučovaná školení</h2>
+
+    <ul class="grid grid-cols-2 gap-2">
         {% if lectures %}
             {% for lecture in lectures %}
                 {% include "lectures/includes/lecture.html" %}
             {% endfor %}
         {% else %}
-            <span class="text-gray-600">Nebyly nalezeny žádné výsledky.</span>
+            <span class="text-gray-600">Žádná evidovaná školení.</span>
         {% endif %}
     </ul>
 {% endblock %}
diff --git a/lectures/templates/lectures/view_lecture.html b/lectures/templates/lectures/view_lecture.html
index 722cdbd..f36e41d 100644
--- a/lectures/templates/lectures/view_lecture.html
+++ b/lectures/templates/lectures/view_lecture.html
@@ -17,17 +17,19 @@
     <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" %}
 
-        <button
-            id="set-rsvp"
-            class="font-alt text-lg bg-black text-white px-5 py-2 duration-100 hover:bg-white hover:text-black"
-            data-url="{% url "lectures:rsvp_lecture" lecture.id %}"
-        >
-            {% if not user|is_registered:lecture %}
-                Zaregistrovat se
-            {% else %}
-                Zrušit registraci
-            {% endif %}
-        </button>
+        {% if user.is_authenticated %}
+            <button
+                id="set-rsvp"
+                class="font-alt text-lg bg-black text-white px-5 py-2 duration-100 hover:bg-white hover:text-black"
+                data-url="{% url "lectures:rsvp_lecture" lecture.id %}"
+            >
+                {% if not user|is_registered:lecture %}
+                    Zaregistrovat se
+                {% else %}
+                    Zrušit registraci
+                {% endif %}
+            </button>
+        {% endif %}
     </div>
 
     <script>
@@ -139,11 +141,13 @@
                 <ul class="grid grid-cols-1 md:grid-cols-2 gap-4">
                     {% for lector in lectors %}
                         <li class="card elevation-3">
-                            <a
-                                href="{% url "lectures:view_lector" lector.id %}"
-                                class="hover:no-underline flex items-center gap-4 {% if lector.url %}duration-150 hover:bg-gray-100{% endif %} card__body"
+                            <div
+                                class="hover:no-underline flex items-start gap-4 {% if lector.url %}duration-150 hover:bg-gray-100{% endif %} card__body"
                             >
-                                <div class="avatar badge__avatar avatar--sm">
+                                <a
+                                    class="avatar shrink-0 object-contain badge__avatar avatar--sm"
+                                    href="{% url "lectures:view_lector" lector.id %}?related_lecture_id={{ lecture.id }}{% if related_group_id %}&related_group_id={{ related_group_id }}{% endif %}"
+                                >
                                     <img
                                         {% if lector.avatar %}
                                             src="{{ lector.avatar.url }}"
@@ -152,23 +156,30 @@
                                         {% else %}
                                             src="https://a.pirati.cz/piratar/300/default.jpg"
                                         {% endif %}
+                                        class="w-20 h-20"
                                         alt="Profilový obrázek {{ lector.name }}"
                                     >
-                                </div>
+                                </a>
                                 <div class="flex flex-col gap-2">
                                     <span class="head-heavy-2xs">
-                                        <div class="content-block--nostyle">
-                                            {{ lector.name }}
-                                        </div>
+                                        <a
+                                            class="content-block--nostyle"
+                                            href="{% url "lectures:view_lector" lector.id %}?related_lecture_id={{ lecture.id }}{% if related_group_id %}&related_group_id={{ related_group_id }}{% endif %}"
+                                        >{{ lector.name }}</a>
                                     </span>
+                                    {% if lector.description %}
+                                        <div class="prose max-w-none">
+                                            {{ lector.description|markdownify|safe }}
+                                        </div>
+                                    {% endif %}
                                     {% if lector.url %}
-                                        <div
+                                        <a
                                             class="block text-ellipsis overflow-hidden"
                                             href="{{ lector.url }}"
-                                        >{{ lector.url }}</div>
+                                        >{{ lector.url }}</a>
                                     {% endif %}
                                 </div>
-                            </a>
+                            </div>
                         </li>
                     {% endfor %}
                 </ul>
diff --git a/lectures/templates/lectures/view_registered.html b/lectures/templates/lectures/view_registered.html
new file mode 100644
index 0000000..d6241c4
--- /dev/null
+++ b/lectures/templates/lectures/view_registered.html
@@ -0,0 +1,11 @@
+{% extends "shared/includes/base.html" %}
+{% load render_bundle from webpack_loader %}
+{% load markdownify static %}
+
+{% block content %}
+    {% include "shared/includes/double_heading.html" with heading=header_name icon="ico--pin" %}
+
+    <div class="__js-root">
+        <ui-year-calendar events='{{ events|safe }}'></ui-year-calendar>
+    </div>
+{% endblock %}
diff --git a/lectures/urls.py b/lectures/urls.py
index 90954a4..292b455 100644
--- a/lectures/urls.py
+++ b/lectures/urls.py
@@ -15,6 +15,12 @@ urlpatterns = [
         name="download_material_file",
     ),
     path("lectures/<int:lecture_id>/rsvp", views.rsvp_lecture, name="rsvp_lecture"),
+    path("lectors/<int:id>", views.view_lector, name="view_lector"),
+    path(
+        "lectures/registered",
+        views.view_registered,
+        name="view_registered",
+    ),
     path(
         "search",
         views.search,
diff --git a/lectures/views.py b/lectures/views.py
index 06ddf8a..1624c92 100644
--- a/lectures/views.py
+++ b/lectures/views.py
@@ -5,6 +5,7 @@ from datetime import datetime
 from itertools import chain
 
 from django.conf import settings
+from django.contrib.auth.decorators import login_required
 from django.db import models
 from django.http import HttpResponseRedirect, JsonResponse
 from django.shortcuts import get_object_or_404, render
@@ -258,14 +259,7 @@ def view_group_lectures(request, group_id: int):
     )
 
 
-def view_lecture(request, lecture_id: int):
-    is_successful, lecture = get_lectures(request, models.Q(id=lecture_id))
-
-    if not is_successful:
-        return lecture
-
-    lecture = lecture.first()
-
+def get_related_group_id(request):
     related_group_id = request.GET.get("related_group_id")
 
     if related_group_id not in ("", None):
@@ -273,7 +267,7 @@ def view_lecture(request, lecture_id: int):
             raise HTTPExceptions.BAD_REQUEST
 
         if not (
-            get_objects_for_user(request.user, "lectures.view_lecture")
+            get_objects_for_user(request.user, "lectures.view_lecturegroup")
             .filter(id=related_group_id)
             .exists()
         ):
@@ -281,6 +275,17 @@ def view_lecture(request, lecture_id: int):
             # just because of the related_group_id being wrong.
             related_group_id = None
 
+    return related_group_id
+
+
+def view_lecture(request, lecture_id: int):
+    is_successful, lecture = get_lectures(request, models.Q(id=lecture_id))
+
+    if not is_successful:
+        return lecture
+
+    lecture = lecture.first()
+
     return render(
         request,
         "lectures/view_lecture.html",
@@ -290,7 +295,7 @@ def view_lecture(request, lecture_id: int):
             "description": f"e-Learningové školení {lecture.name}.",
             "header_name": lecture.name,
             "lecture": lecture,
-            "related_group_id": related_group_id,
+            "related_group_id": get_related_group_id(request),
         },
     )
 
@@ -349,6 +354,21 @@ def view_lector(request, id):
         request, models.Q(id__in=lector.lectures.all()), get_exceptions=False
     )
 
+    related_lecture_id = request.GET.get("related_lecture_id")
+
+    if related_lecture_id not in ("", None):
+        if not str(related_lecture_id).isnumeric():
+            raise HTTPExceptions.BAD_REQUEST
+
+        if not (
+            get_objects_for_user(request.user, "lectures.view_lecture")
+            .filter(id=related_lecture_id)
+            .exists()
+        ):
+            # Ignore the wrong part of the URL and move on, don't raise exceptions
+            # just because of the related_lecture_id being wrong.
+            related_lecture_id = None
+
     return render(
         request,
         "lectures/view_lector.html",
@@ -356,15 +376,54 @@ def view_lector(request, id):
             **get_base_context(request),
             "title": lector.name,
             "description": f"Informace o lektorovi - {lector.name}.",
-            "header_name": "Lektor",
-            "header_desc": lector.name,
+            "header_name": lector.name,
+            "header_desc": "Lektor",
+            "related_lecture_id": related_lecture_id,
+            "related_group_id": get_related_group_id(request),
             "lector": lector,
             "lectures": lectures,
         },
     )
 
 
+@login_required
+def view_registered(request):
+    lectures = get_lectures(
+        request, models.Q(rsvp_users=request.user), get_exceptions=False
+    )
+
+    events = []
+
+    for lecture in lectures:
+        events.append(
+            {
+                "title": lecture.name,
+                "start": lecture.timestamp.isoformat(),
+                "url": request.build_absolute_uri(
+                    reverse("lectures:view_lecture", args=(lecture.id,))
+                ),
+                "description": (
+                    lecture.description if lecture.description is not None else ""
+                ),
+            }
+        )
+
+    return render(
+        request,
+        "lectures/view_registered.html",
+        {
+            **get_base_context(request),
+            "title": "Tvá školení",
+            "description": "Zobrazení tvých zaregistrovaných školení.",
+            "header_name": "Tvá školení",
+            "lectures": lectures,
+            "events": json.dumps(events),
+        },
+    )
+
+
 @require_POST
+@login_required
 def rsvp_lecture(request, lecture_id: int):
     is_successful, lecture = get_lecture(request, models.Q(id=lecture_id))
 
diff --git a/requirements/base.txt b/requirements/base.txt
index 97e5804..7056f4a 100644
--- a/requirements/base.txt
+++ b/requirements/base.txt
@@ -14,3 +14,4 @@ django-environ==0.9.0
 django-http-exceptions==1.4.0
 django-guardian==2.4.0
 PyJWT==2.6.0
+Pillow==9.5.0
diff --git a/shared/templates/shared/includes/base.html b/shared/templates/shared/includes/base.html
index b07cf3f..21596f7 100644
--- a/shared/templates/shared/includes/base.html
+++ b/shared/templates/shared/includes/base.html
@@ -36,13 +36,14 @@
             media="all"
         >
 
-       <link
-            href="https://styleguide.pirati.cz/2.12.x/css/styles.css"
+        <!-- TODO: Replace with 2.13.x once released -->
+        <link
+            href="http://localhost:3000/css/styles.css"
             rel="stylesheet"
             media="all"
         >
         <link
-            href="https://styleguide.pirati.cz/2.12.x/css/pattern-scaffolding.css"
+            href="http://localhost:3000/css/pattern-scaffolding.css"
             rel="stylesheet"
             media="all"
         >
@@ -86,6 +87,16 @@
                                         </li>
                                     {% endif %}
 
+                                    <li class="navbar-menu__item">
+                                        <a
+                                            href="{% url "lectures:view_registered" %}"
+                                            data-href="{% url "lectures:view_registered" %}"
+                                            class="navbar-menu__link flex items-center gap-2"
+                                        >
+                                            <i class="ico--pin text-sm"></i>Tvá školení
+                                        </a>
+                                    </li>
+
                                     <li class="navbar-menu__item">
                                         <a
                                             href="{% url "lectures:search" %}"
-- 
GitLab