From bcb5472dd6ec6da2bd83c5a4a66add4060525931 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 12:02:01 +0200
Subject: [PATCH] add lector views, searching, improve db structure, custom
 lector avatars / descriptions

---
 lectures/admin.py                             |  17 ++-
 ...e_lecturelector_lecture_lecture_lectors.py |  25 ++++
 .../migrations/0021_lecture_rsvp_users.py     |  24 ++++
 ...atar_lecturelector_description_and_more.py |  57 ++++++++
 lectures/models.py                            | 110 ++++++++++-----
 .../templates/lectures/includes/lecture.html  |   5 +-
 lectures/templates/lectures/view_lector.html  |  23 ++++
 lectures/templates/lectures/view_lecture.html |  19 ++-
 lectures/templates/lectures/view_search.html  |  28 ++++
 .../lectures/view_search_results.html         |  39 ++++++
 lectures/urls.py                              |   5 +
 lectures/views.py                             | 130 ++++++++++++++----
 shared/templates/shared/includes/base.html    |  10 ++
 users/admin.py                                |   2 +-
 .../0010_remove_user_rsvp_lectures.py         |  16 +++
 users/models.py                               |  11 +-
 16 files changed, 431 insertions(+), 90 deletions(-)
 create mode 100644 lectures/migrations/0020_remove_lecturelector_lecture_lecture_lectors.py
 create mode 100644 lectures/migrations/0021_lecture_rsvp_users.py
 create mode 100644 lectures/migrations/0022_lecturelector_avatar_lecturelector_description_and_more.py
 create mode 100644 lectures/templates/lectures/view_lector.html
 create mode 100644 lectures/templates/lectures/view_search.html
 create mode 100644 lectures/templates/lectures/view_search_results.html
 create mode 100644 users/migrations/0010_remove_user_rsvp_lectures.py

diff --git a/lectures/admin.py b/lectures/admin.py
index 552b6cf..ff04fa8 100644
--- a/lectures/admin.py
+++ b/lectures/admin.py
@@ -25,12 +25,7 @@ class LectureGroupAdmin(MarkdownxGuardedModelAdmin):
     list_display = ("name", "priority")
 
 
-class LectureLectorInline(admin.StackedInline):
-    model = LectureLector
-    extra = 1
-
-
-class LectureRecordingInline(admin.TabularInline):
+class LectureRecordingInline(admin.StackedInline):
     model = LectureRecording
     extra = 1
 
@@ -42,13 +37,13 @@ class LectureMaterialInline(admin.StackedInline):
 
 class LectureAdmin(MarkdownxGuardedModelAdmin):
     inlines = (
-        LectureLectorInline,
         LectureRecordingInline,
         LectureMaterialInline,
     )
 
-    autocomplete_fields = ("groups",)
+    autocomplete_fields = ("groups", "lectors", "rsvp_users")
     search_fields = ("name", "description")
+    readonly_fields = ("rsvp_users",)
 
     list_display = (
         "name",
@@ -56,12 +51,16 @@ class LectureAdmin(MarkdownxGuardedModelAdmin):
     )
 
 
+class LectureLectorAdmin(MarkdownxGuardedModelAdmin):
+    search_fields = ("name", "username")
+
+
 for model in (
-    LectureLector,
     LectureMaterial,
     LectureRecording,
 ):
     admin.site.register(model, IndexHiddenModelAdmin)
 
+admin.site.register(LectureLector, LectureLectorAdmin)
 admin.site.register(Lecture, LectureAdmin)
 admin.site.register(LectureGroup, LectureGroupAdmin)
diff --git a/lectures/migrations/0020_remove_lecturelector_lecture_lecture_lectors.py b/lectures/migrations/0020_remove_lecturelector_lecture_lecture_lectors.py
new file mode 100644
index 0000000..b5798c9
--- /dev/null
+++ b/lectures/migrations/0020_remove_lecturelector_lecture_lecture_lectors.py
@@ -0,0 +1,25 @@
+# Generated by Django 4.1.4 on 2023-05-31 06:19
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        ("lectures", "0019_alter_lecturegroup_user_groups_and_more"),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name="lecturelector",
+            name="lecture",
+        ),
+        migrations.AddField(
+            model_name="lecture",
+            name="lectors",
+            field=models.ManyToManyField(
+                related_name="lectures",
+                to="lectures.lecturelector",
+                verbose_name="Lektoři",
+            ),
+        ),
+    ]
diff --git a/lectures/migrations/0021_lecture_rsvp_users.py b/lectures/migrations/0021_lecture_rsvp_users.py
new file mode 100644
index 0000000..930a00d
--- /dev/null
+++ b/lectures/migrations/0021_lecture_rsvp_users.py
@@ -0,0 +1,24 @@
+# Generated by Django 4.1.4 on 2023-05-31 06:55
+
+from django.conf import settings
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+        ("lectures", "0020_remove_lecturelector_lecture_lecture_lectors"),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name="lecture",
+            name="rsvp_users",
+            field=models.ManyToManyField(
+                blank=True,
+                related_name="rsvp_lectures",
+                to=settings.AUTH_USER_MODEL,
+                verbose_name="Zaregistrovaní uživatelé",
+            ),
+        ),
+    ]
diff --git a/lectures/migrations/0022_lecturelector_avatar_lecturelector_description_and_more.py b/lectures/migrations/0022_lecturelector_avatar_lecturelector_description_and_more.py
new file mode 100644
index 0000000..a33f32c
--- /dev/null
+++ b/lectures/migrations/0022_lecturelector_avatar_lecturelector_description_and_more.py
@@ -0,0 +1,57 @@
+# Generated by Django 4.1.4 on 2023-05-31 09:53
+
+import markdownx.models
+from django.db import migrations, models
+
+import lectures.models
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        ("lectures", "0021_lecture_rsvp_users"),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name="lecturelector",
+            name="avatar",
+            field=models.FileField(
+                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.AddField(
+            model_name="lecturelector",
+            name="description",
+            field=markdownx.models.MarkdownxField(
+                blank=True,
+                help_text='Můžeš použít <a href="https://cs.wikipedia.org/wiki/Markdown#P%C5%99%C3%ADklad_u%C5%BEit%C3%AD">Markdown</a>.',
+                max_length=512,
+                null=True,
+                verbose_name="Popis",
+            ),
+        ),
+        migrations.AlterField(
+            model_name="lecture",
+            name="description",
+            field=markdownx.models.MarkdownxField(
+                blank=True,
+                help_text='Můžeš použít <a href="https://cs.wikipedia.org/wiki/Markdown#P%C5%99%C3%ADklad_u%C5%BEit%C3%AD">Markdown</a>.',
+                null=True,
+                verbose_name="Popis",
+            ),
+        ),
+        migrations.AlterField(
+            model_name="lecturegroup",
+            name="description",
+            field=markdownx.models.MarkdownxField(
+                blank=True,
+                help_text='Můžeš použít <a href="https://cs.wikipedia.org/wiki/Markdown#P%C5%99%C3%ADklad_u%C5%BEit%C3%AD">Markdown</a>.',
+                null=True,
+                verbose_name="Popis",
+            ),
+        ),
+    ]
diff --git a/lectures/models.py b/lectures/models.py
index b26acfd..8b143d9 100644
--- a/lectures/models.py
+++ b/lectures/models.py
@@ -18,6 +18,33 @@ from . import settings as app_settings
 # Create your models here.
 
 
+def get_file_location(instance, filename, path_prefix="public"):
+    mimetypes_instance = mimetypes.MimeTypes()
+
+    current_time = datetime.today()
+    guessed_type = mimetypes_instance.guess_type(filename, strict=False)[0]
+
+    extension = ""
+
+    if guessed_type is not None:
+        for mapper in mimetypes_instance.types_map_inv:
+            if guessed_type not in mapper:
+                continue
+
+            extension = mapper[guessed_type]
+
+            if isinstance(extension, list):
+                extension = extension[0]
+
+            break
+
+    return (
+        f"{path_prefix}/"
+        f"{current_time.year}/{current_time.month}/{current_time.day}/"
+        f"{uuid.uuid4()}{extension}"
+    )
+
+
 class LectureGroup(NameStrMixin, models.Model):
     name = models.CharField(
         max_length=128,
@@ -28,7 +55,12 @@ class LectureGroup(NameStrMixin, models.Model):
         null=True,
         blank=True,
         verbose_name="Popis",
-        help_text="Můžeš použít Markdown.",
+        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>."
+        ),
     )
 
     priority = models.IntegerField(
@@ -86,7 +118,23 @@ class Lecture(NameStrMixin, models.Model):
         blank=True,
         null=True,
         verbose_name="Popis",
-        help_text="Můžeš použít Markdown.",
+        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>."
+        ),
+    )
+
+    lectors = models.ManyToManyField(
+        "LectureLector", related_name="lectures", verbose_name="Lektoři"
+    )
+
+    rsvp_users = models.ManyToManyField(
+        "users.User",
+        blank=True,
+        related_name="rsvp_lectures",
+        verbose_name="Zaregistrovaní uživatelé",
     )
 
     # Settings
@@ -100,18 +148,24 @@ class Lecture(NameStrMixin, models.Model):
 
 
 class LectureLector(NameStrMixin, models.Model):
-    lecture = models.ForeignKey(
-        "Lecture",
-        on_delete=models.CASCADE,
-        related_name="lectors",
-        verbose_name="Školení",
-    )
-
     name = models.CharField(
         max_length=128,
         verbose_name="Jméno",
     )
 
+    description = MarkdownxField(
+        max_length=512,
+        blank=True,
+        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>."
+        ),
+    )
+
     url = models.URLField(
         max_length=256,
         blank=True,
@@ -122,6 +176,16 @@ class LectureLector(NameStrMixin, models.Model):
         ),
     )
 
+    avatar = models.FileField(
+        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."
+        ),
+    )
+
     username = models.CharField(
         max_length=128,
         blank=True,
@@ -129,7 +193,8 @@ class LectureLector(NameStrMixin, models.Model):
         verbose_name="Uživatelské jméno",
         help_text=(
             "Např. na fóru, nebo v Chobotnici. Užívá se "
-            "k synchronizaci profilového obrázku."
+            "k synchronizaci profilového obrázku, v budoucnu "
+            "i dalších informací."
         ),
     )
 
@@ -167,30 +232,7 @@ class LectureRecording(NameStrMixin, models.Model):
 
 
 def get_lecture_material_file_location(instance, filename):
-    mimetypes_instance = mimetypes.MimeTypes()
-
-    current_time = datetime.today()
-    guessed_type = mimetypes_instance.guess_type(filename, strict=False)[0]
-
-    extension = ""
-
-    if guessed_type is not None:
-        for mapper in mimetypes_instance.types_map_inv:
-            if guessed_type not in mapper:
-                continue
-
-            extension = mapper[guessed_type]
-
-            if isinstance(extension, list):
-                extension = extension[0]
-
-            break
-
-    return (
-        "_private/"
-        f"{current_time.year}/{current_time.month}/{current_time.day}/"
-        f"{uuid.uuid4()}{extension}"
-    )
+    return get_file_location(instane, filename, path_prefix="_private")
 
 
 class LectureMaterialFileProxy(FieldFile):
diff --git a/lectures/templates/lectures/includes/lecture.html b/lectures/templates/lectures/includes/lecture.html
index 15aa277..feda986 100644
--- a/lectures/templates/lectures/includes/lecture.html
+++ b/lectures/templates/lectures/includes/lecture.html
@@ -1,7 +1,10 @@
 {% load markdownify %}
 
 <li>
-    <a href="{% url "lectures:view_lecture" lecture.id %}?related_group_id={{ group.id }}" class="hover:no-underline">
+    <a
+        href="{% url "lectures:view_lecture" lecture.id %}{% if group %}?related_group_id={{ group.id }}{% endif %}"
+        class="hover:no-underline"
+    >
         <div class="card elevation-6">
             <div class="card__body p-5 hover:bg-gray-100 duration-100">
                 <h2 class="head-alt-sm mb-4">
diff --git a/lectures/templates/lectures/view_lector.html b/lectures/templates/lectures/view_lector.html
new file mode 100644
index 0000000..d3d8247
--- /dev/null
+++ b/lectures/templates/lectures/view_lector.html
@@ -0,0 +1,23 @@
+{% extends "shared/includes/base.html" %}
+{% load render_bundle from webpack_loader %}
+{% load markdownify static %}
+
+{% block content %}
+    {% render_bundle "view_group_lectures" %}
+
+    {% include "shared/includes/double_heading.html" with heading=header_name subheading=header_desc icon="ico--user" %}
+
+
+
+    <h2 class="head-alt-md my-8">Vytvořená školení</h2>
+
+    <ul class="flex flex-col 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>
+        {% endif %}
+    </ul>
+{% endblock %}
diff --git a/lectures/templates/lectures/view_lecture.html b/lectures/templates/lectures/view_lecture.html
index 02de8cf..722cdbd 100644
--- a/lectures/templates/lectures/view_lecture.html
+++ b/lectures/templates/lectures/view_lecture.html
@@ -139,16 +139,19 @@
                 <ul class="grid grid-cols-1 md:grid-cols-2 gap-4">
                     {% for lector in lectors %}
                         <li class="card elevation-3">
-                            {% if lector.url %}
-                            <a href="{{ lector.url }}"
-                            {% else %}
-                            <div
-                            {% endif %}
+                            <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="avatar badge__avatar avatar--sm">
                                     <img
-                                        src="https://a.pirati.cz/piratar/300/{% if lector.username %}{{ lector.username }}{% else %}default{% endif %}.jpg"
+                                        {% 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 %}
                                         alt="Profilový obrázek {{ lector.name }}"
                                     >
                                 </div>
@@ -165,11 +168,7 @@
                                         >{{ lector.url }}</div>
                                     {% endif %}
                                 </div>
-                            {% if lector.url %}
                             </a>
-                            {% else %}
-                            </div>
-                            {% endif %}
                         </li>
                     {% endfor %}
                 </ul>
diff --git a/lectures/templates/lectures/view_search.html b/lectures/templates/lectures/view_search.html
new file mode 100644
index 0000000..70fe084
--- /dev/null
+++ b/lectures/templates/lectures/view_search.html
@@ -0,0 +1,28 @@
+{% extends "shared/includes/base.html" %}
+{% load render_bundle from webpack_loader %}
+{% load markdownify static %}
+
+{% block content %}
+    {% render_bundle "view_group_lectures" %}
+
+    {% include "shared/includes/double_heading.html" with heading=header_name subheading=header_desc icon="ico--search" %}
+
+    <form
+        class="flex flex-row justify-center"
+        method="get"
+    >
+        <input
+            name="q"
+            class="bg-gray-200 w-56 lg:w-80 h-10 px-4 text-lg xl:h-14 xl:px-5"
+            type="text"
+            placeholder="Hledat školení..."
+            aria-label="Vyhledávací box"
+        >
+        <button
+            class="text-lg bg-black text-white px-5 py-2 duration-100 h-10 w-12 min-h-0 min-w-0 xl:h-14 xl:w-14 hover:bg-white hover:text-black"
+            aria-label="Vyhledat"
+        >
+            <i class="ico--search"></i>
+        </button>
+    </div>
+{% endblock %}
diff --git a/lectures/templates/lectures/view_search_results.html b/lectures/templates/lectures/view_search_results.html
new file mode 100644
index 0000000..7bb34be
--- /dev/null
+++ b/lectures/templates/lectures/view_search_results.html
@@ -0,0 +1,39 @@
+{% extends "shared/includes/base.html" %}
+{% load render_bundle from webpack_loader %}
+{% load markdownify static %}
+
+{% block content %}
+    {% render_bundle "view_group_lectures" %}
+
+    {% include "shared/includes/double_heading.html" with heading=header_name subheading=header_desc icon="ico--search" %}
+
+    <form
+        class="flex flex-row justify-center mb-8"
+        method="get"
+    >
+        <input
+            name="q"
+            class="bg-gray-200 w-56 lg:w-80 h-10 px-4 text-lg xl:h-14 xl:px-5"
+            type="text"
+            placeholder="Hledat školení..."
+            value="{{ query }}"
+            aria-label="Vyhledávací box"
+        >
+        <button
+            class="text-lg bg-black text-white px-5 py-2 duration-100 h-10 w-12 min-h-0 min-w-0 xl:h-14 xl:w-14 hover:bg-white hover:text-black"
+            aria-label="Vyhledat"
+        >
+            <i class="ico--search"></i>
+        </button>
+    </form>
+
+    <ul class="flex flex-col 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>
+        {% endif %}
+    </ul>
+{% endblock %}
diff --git a/lectures/urls.py b/lectures/urls.py
index 4434de4..90954a4 100644
--- a/lectures/urls.py
+++ b/lectures/urls.py
@@ -15,4 +15,9 @@ urlpatterns = [
         name="download_material_file",
     ),
     path("lectures/<int:lecture_id>/rsvp", views.rsvp_lecture, name="rsvp_lecture"),
+    path(
+        "search",
+        views.search,
+        name="search",
+    ),
 ]
diff --git a/lectures/views.py b/lectures/views.py
index 87bc580..06ddf8a 100644
--- a/lectures/views.py
+++ b/lectures/views.py
@@ -61,20 +61,19 @@ def generate_auth_redirect(request) -> HttpResponseRedirect:
     )
 
 
-def get_lecture(request, id: int) -> tuple:
-    lecture = (
-        get_objects_for_user(request.user, "lectures.view_lecture")
-        .filter(id=id)
-        .first()
-    )
+def get_lectures(request, filter=None, get_exceptions: bool = True) -> tuple:
+    lectures = get_objects_for_user(request.user, "lectures.view_lecture")
 
-    if lecture is None:
+    if filter is not None:
+        lectures = lectures.filter(filter)
+
+    if get_exceptions and not lectures.exists():
         raise HTTPExceptions.NOT_FOUND
 
     if not (
         get_objects_for_user(request.user, "lectures.view_lecturegroup")
         .filter(
-            models.Q(id__in=lecture.groups.all())
+            models.Q(id__in=(LectureGroup.objects.filter(lectures__in=lectures)))
             & (
                 (
                     models.Q(user_groups__in=request.user.groups.all())
@@ -87,12 +86,16 @@ def get_lecture(request, id: int) -> tuple:
         .distinct()
         .exists()
     ):  # User does not have access to related groups
-        if request.user.is_authenticated:  # The user can log in
-            raise HTTPExceptions.NOT_FOUND
-        else:  # They can log in
-            return False, generate_auth_redirect(request)
+        if get_exceptions:
+            if request.user.is_authenticated:  # The user can log in
+                raise HTTPExceptions.NOT_FOUND
+            else:  # They can log in
+                return False, generate_auth_redirect(request)
 
-    return True, lecture
+    if get_exceptions:
+        return True, lectures
+    else:
+        return lectures
 
 
 def view_groups(request):
@@ -256,21 +259,27 @@ def view_group_lectures(request, group_id: int):
 
 
 def view_lecture(request, lecture_id: int):
-    is_successful, lecture = get_lecture(request, lecture_id)
+    is_successful, lecture = get_lectures(request, models.Q(id=lecture_id))
 
     if not is_successful:
         return lecture
 
+    lecture = lecture.first()
+
     related_group_id = request.GET.get("related_group_id")
 
-    if related_group_id is not None and not (
-        get_objects_for_user(request.user, "lectures.view_lecture")
-        .filter(id=related_group_id)
-        .exists()
-    ):
-        # Ignore the wrong part of the URL and move on, don't raise exceptions
-        # just because of the related_group_id being wrong.
-        related_group_id = None
+    if related_group_id not in ("", None):
+        if not str(related_group_id).isnumeric():
+            raise HTTPExceptions.BAD_REQUEST
+
+        if not (
+            get_objects_for_user(request.user, "lectures.view_lecture")
+            .filter(id=related_group_id)
+            .exists()
+        ):
+            # Ignore the wrong part of the URL and move on, don't raise exceptions
+            # just because of the related_group_id being wrong.
+            related_group_id = None
 
     return render(
         request,
@@ -286,13 +295,84 @@ def view_lecture(request, lecture_id: int):
     )
 
 
+def search(request):
+    query = request.GET.get("q")
+
+    if query:
+        queryset = get_lectures(request, get_exceptions=False)
+
+        queryset = queryset.annotate(
+            name_lower=models.Func(models.F("name"), function="LOWER"),
+            description_lower=models.Func(models.F("description"), function="LOWER"),
+        )
+
+        formatted_query = query.lower()
+
+        lectures = queryset.filter(
+            models.Q(name_lower__contains=formatted_query)
+            | models.Q(description_lower__contains=formatted_query)
+        ).all()
+
+        return render(
+            request,
+            "lectures/view_search_results.html",
+            {
+                **get_base_context(request),
+                "title": f"Vyhledávání - {query}",
+                "description": "Vyhledávání školení v Pirátském e-Learningu.",
+                "header_name": "Výsledky vyhledávání",
+                "header_desc": f"„{query}“",
+                "query": query,
+                "lectures": lectures,
+            },
+        )
+    else:
+        return render(
+            request,
+            "lectures/view_search.html",
+            {
+                **get_base_context(request),
+                "title": "Vyhledávání",
+                "description": "Vyhledávání školení v Pirátském e-Learningu.",
+                "header_name": "Vyhledávání",
+                "header_desc": "školení",
+            },
+        )
+
+
+def view_lector(request, id):
+    lector = get_object_or_404(
+        get_objects_for_user(request.user, "lectures.view_lecturelector"), id=id
+    )
+
+    lectures = get_lectures(
+        request, models.Q(id__in=lector.lectures.all()), get_exceptions=False
+    )
+
+    return render(
+        request,
+        "lectures/view_lector.html",
+        {
+            **get_base_context(request),
+            "title": lector.name,
+            "description": f"Informace o lektorovi - {lector.name}.",
+            "header_name": "Lektor",
+            "header_desc": lector.name,
+            "lector": lector,
+            "lectures": lectures,
+        },
+    )
+
+
 @require_POST
 def rsvp_lecture(request, lecture_id: int):
-    is_successful, lecture = get_lecture(request, lecture_id)
+    is_successful, lecture = get_lecture(request, models.Q(id=lecture_id))
 
     if not is_successful:
         return lecture
 
+    lecture = lecture.first()
+
     is_registered = request.POST.get("register", "false") == "true"
 
     if is_registered is request.user.get_lecture_registered(
@@ -301,9 +381,9 @@ def rsvp_lecture(request, lecture_id: int):
         return JsonResponse({"success": False})
 
     if is_registered:
-        request.user.rsvp_lectures.add(lecture)
+        lecture.rsvp_users.add(request.user)
     else:
-        request.user.rsvp_lectures.remove(lecture)
+        lecture.rsvp_users.remove(request.user)
 
     request.user.save()
 
diff --git a/shared/templates/shared/includes/base.html b/shared/templates/shared/includes/base.html
index 087da92..b07cf3f 100644
--- a/shared/templates/shared/includes/base.html
+++ b/shared/templates/shared/includes/base.html
@@ -85,6 +85,16 @@
                                             </a>
                                         </li>
                                     {% endif %}
+
+                                    <li class="navbar-menu__item">
+                                        <a
+                                            href="{% url "lectures:search" %}"
+                                            data-href="{% url "lectures:search" %}"
+                                            class="navbar-menu__link flex items-center gap-2"
+                                        >
+                                            <i class="ico--search text-sm"></i>Hledat
+                                        </a>
+                                    </li>
                                 </ul>
                             </div>
 
diff --git a/users/admin.py b/users/admin.py
index ecd2efa..128fc42 100644
--- a/users/admin.py
+++ b/users/admin.py
@@ -6,7 +6,7 @@ from .models import User
 
 
 class UserAdmin(MarkdownxGuardedModelAdmin):
-    autocomplete_fields = ("rsvp_lectures",)
+    search_fields = ("first_name", "last_name", "email")
 
 
 admin.site.register(User, UserAdmin)
diff --git a/users/migrations/0010_remove_user_rsvp_lectures.py b/users/migrations/0010_remove_user_rsvp_lectures.py
new file mode 100644
index 0000000..c7a3d95
--- /dev/null
+++ b/users/migrations/0010_remove_user_rsvp_lectures.py
@@ -0,0 +1,16 @@
+# Generated by Django 4.1.4 on 2023-05-31 06:55
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        ("users", "0009_alter_user_sso_username"),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name="user",
+            name="rsvp_lectures",
+        ),
+    ]
diff --git a/users/models.py b/users/models.py
index 3557edc..49a91f1 100644
--- a/users/models.py
+++ b/users/models.py
@@ -30,13 +30,6 @@ class User(pirates_models.AbstractUser):
         verbose_name="E-mailová adresa",
     )
 
-    rsvp_lectures = models.ManyToManyField(
-        "lectures.Lecture",
-        blank=True,
-        related_name="rsvp_users",
-        verbose_name="Zaregistrovaná školení",
-    )
-
     def set_unusable_password(self) -> None:
         # Purely for compatibility with Guardian
         pass
@@ -52,9 +45,7 @@ class User(pirates_models.AbstractUser):
     def get_lecture_registered(self, lecture) -> bool:
         from lectures.models import Lecture
 
-        return Lecture.objects.filter(
-            id=lecture.id, id__in=self.rsvp_lectures.all()
-        ).exists()
+        return Lecture.objects.filter(id=lecture.id, rsvp_users=self).exists()
 
     class Meta:
         app_label = "users"
-- 
GitLab