diff --git a/lectures/admin.py b/lectures/admin.py
index 6e9e288eadd8dddc9842a91cf092a7f368a819b0..e99d3583833c46a391558bbd24a1a94144b24848 100644
--- a/lectures/admin.py
+++ b/lectures/admin.py
@@ -1,6 +1,5 @@
 from django.contrib import admin
-
-from shared.admin import MarkdownxGuardedModelAdmin
+from markdownx.admin import MarkdownxModelAdmin
 
 from .forms import LectureGroupTypeFormset
 from .models import (
@@ -16,12 +15,12 @@ from .models import (
 # Register your models here.
 
 
-class IndexHiddenModelAdmin(MarkdownxGuardedModelAdmin):
+class IndexHiddenModelAdmin(MarkdownxModelAdmin):
     def has_module_permission(self, request):
         return False
 
 
-class LectureGroupAdmin(MarkdownxGuardedModelAdmin):
+class LectureGroupAdmin(MarkdownxModelAdmin):
     autocomplete_fields = ("user_groups",)
     search_fields = ("name",)
 
@@ -45,7 +44,7 @@ class LectureGroupTypeInline(admin.StackedInline):
     extra = 1
 
 
-class LectureAdmin(MarkdownxGuardedModelAdmin):
+class LectureAdmin(MarkdownxModelAdmin):
     inlines = (
         LectureGroupTypeInline,
         LectureRecordingInline,
@@ -77,11 +76,11 @@ class LectureAdmin(MarkdownxGuardedModelAdmin):
         return display_string
 
 
-class LectureCategoryAdmin(MarkdownxGuardedModelAdmin):
+class LectureCategoryAdmin(MarkdownxModelAdmin):
     search_fields = ("name",)
 
 
-class LectureLectorAdmin(MarkdownxGuardedModelAdmin):
+class LectureLectorAdmin(MarkdownxModelAdmin):
     search_fields = ("name", "username")
 
 
diff --git a/lectures/templates/lectures/view_group_lectures.html b/lectures/templates/lectures/view_group_lectures.html
index 45678c8ab21b572e3f5ce9174ce3ee8a47dd62bd..0bd7b8e569c16b3d53c1d24641d2133c67c5e1fb 100644
--- a/lectures/templates/lectures/view_group_lectures.html
+++ b/lectures/templates/lectures/view_group_lectures.html
@@ -25,6 +25,11 @@
         >
             <div class="flex justify-center mb-10">
                 <div class="switch overflow-x-auto">
+                    <a
+                        @click="toggleView('recordings')"
+                        class="switch__item whitespace-nowrap"
+                        :class="{'switch__item--active': isCurrentView('recordings')}"
+                    >Záznamy</a>
                     <a
                         @click="toggleView('current_lectures')"
                         class="switch__item whitespace-nowrap"
@@ -35,14 +40,58 @@
                         class="switch__item whitespace-nowrap"
                         :class="{'switch__item--active': isCurrentView('timeline')}"
                     >Časová osa</a>
-                    <a
-                        @click="toggleView('recordings')"
-                        class="switch__item whitespace-nowrap"
-                        :class="{'switch__item--active': isCurrentView('recordings')}"
-                    >Záznamy</a>
                 </div>
             </div>
             <div>
+                <template v-if="isCurrentView('recordings')">
+                    {% if past_lectures %}
+                        <div class="flex flex-col gap-3">
+                            {% regroup past_lectures by category as lecture_categories %}
+                            {% for category in lecture_categories %}
+                                <div
+                                    class="
+                                        flex flex-col gap-3 group justify-center p-4 cursor-pointer
+
+                                        hover:bg-gray-100
+
+                                        __lecture-category
+                                    "
+                                    data-is-open="false"
+                                >
+                                    <div class="flex justify-between items-center">
+                                        <h2 class="text-3xl font-alt">{{ category.grouper }}</h2>
+
+                                        <div>
+                                            <i
+                                                class="
+                                                    __lecture-category-opener
+                                                    ico--chevron-down text-2xl
+                                                "
+                                            ></i>
+                                        </div>
+                                    </div>
+
+                                    <noscript>
+                                        <ul class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
+                                            {% for lecture in category.list %}
+                                                {% include "lectures/includes/lecture.html" with lecture=lecture group=group %}
+                                            {% endfor %}
+                                        </ul>
+                                    </noscript>
+
+                                    <ul class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4 hidden __lecture-category-content">
+                                        {% for lecture in category.list %}
+                                            {% include "lectures/includes/lecture.html" with lecture=lecture group=group %}
+                                        {% endfor %}
+                                    </ul>
+                                </div>
+                            {% endfor %}
+                        </div>
+                    {% else %}
+                        <span class="text-gray-600">Žádné dostupné záznamy.</span>
+                    {% endif %}
+                </template>
+
                 <template v-if="isCurrentView('current_lectures')">
                     {% if current_lectures %}
                         <ul class="grid grid-cols-1 md:grid-cols-2 gap-4">
@@ -54,6 +103,7 @@
                         <span class="text-gray-600">Žádná dostupná aktuální školení.</span>
                     {% endif %}
                 </template>
+
                 <template v-if="isCurrentView('timeline')">
                     <div>
                         <div class="head-alt-md mb-4 flex items-center justify-between">
@@ -120,24 +170,6 @@
                         </script>
                     </div>
                 </template>
-                <template v-if="isCurrentView('recordings')">
-                    {% if past_lectures %}
-                        <div class="flex flex-col gap-3">
-                            {% regroup past_lectures by category as lecture_categories %}
-                            {% for category in lecture_categories %}
-                                <h2 class="text-3xl font-alt">{{ category.grouper }}</h2>
-
-                                <ul class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
-                                    {% for lecture in category.list %}
-                                        {% include "lectures/includes/lecture.html" with lecture=lecture group=group %}
-                                    {% endfor %}
-                                </ul>
-                            {% endfor %}
-                        </div>
-                    {% else %}
-                        <span class="text-gray-600">Žádné dostupné záznamy.</span>
-                    {% endif %}
-                </template>
             </div>
         </ui-view-provider>
     </div>
diff --git a/lectures/views.py b/lectures/views.py
index 59cd78afc95d3c023626a06a020b6b0bad2a9966..c1961a545114126363a6da69f1cc0cee3c0138da 100644
--- a/lectures/views.py
+++ b/lectures/views.py
@@ -14,9 +14,8 @@ from django.utils import timezone
 from django.views.decorators.http import require_POST
 from django_downloadview import ObjectDownloadView
 from django_http_exceptions import HTTPExceptions
-from guardian.shortcuts import get_objects_for_user
 
-from .models import Lecture, LectureGroup, LectureMaterial
+from .models import Lecture, LectureGroup, LectureLector, LectureMaterial
 
 
 class LectureMaterialFileDownloadView(ObjectDownloadView):
@@ -60,7 +59,7 @@ def generate_auth_redirect(request) -> HttpResponseRedirect:
 
 
 def get_lectures(request, filter=None, get_exceptions: bool = True) -> tuple:
-    lectures = get_objects_for_user(request.user, "lectures.view_lecture")
+    lectures = Lecture.objects
 
     if filter is not None:
         lectures = lectures.filter(filter)
@@ -68,9 +67,10 @@ def get_lectures(request, filter=None, get_exceptions: bool = True) -> tuple:
     if get_exceptions and not lectures.exists():
         raise HTTPExceptions.NOT_FOUND
 
+    lectures = lectures.all()
+
     if not (
-        get_objects_for_user(request.user, "lectures.view_lecturegroup")
-        .filter(
+        LectureGroup.objects.filter(
             models.Q(
                 id__in=(
                     LectureGroup.objects.filter(
@@ -104,8 +104,7 @@ def get_lectures(request, filter=None, get_exceptions: bool = True) -> tuple:
 
 def view_groups(request):
     lecture_groups = (
-        get_objects_for_user(request.user, "lectures.view_lecturegroup")
-        .filter(
+        LectureGroup.objects.filter(
             (
                 models.Q(user_groups__in=request.user.groups.all())
                 | models.Q(user_groups=None)
@@ -132,9 +131,7 @@ def view_groups(request):
 
 
 def view_group_lectures(request, group_id: int):
-    group = get_objects_for_user(request.user, "lectures.view_lecturegroup").filter(
-        id=group_id
-    )
+    group = LectureGroup.objects.filter(id=group_id)
 
     group_id_exists = group.exists()
 
@@ -159,22 +156,18 @@ def view_group_lectures(request, group_id: int):
     timestamp_ending_separator = timezone.now() + Lecture.is_current_ending_treshold
 
     past_lectures = (
-        get_objects_for_user(request.user, "lectures.view_lecture")
-        .filter(
+        Lecture.objects.filter(
             lecture_group_types__group=group,
             timestamp__lt=timestamp_starting_separator,
         )
+        .order_by("category")
         .all()
     )
-    current_lectures = (
-        get_objects_for_user(request.user, "lectures.view_lecture")
-        .filter(
-            lecture_group_types__group=group,
-            timestamp__gte=timestamp_starting_separator,
-            timestamp__lte=timestamp_ending_separator,
-        )
-        .all()
-    )
+    current_lectures = Lecture.objects.filter(
+        lecture_group_types__group=group,
+        timestamp__gte=timestamp_starting_separator,
+        timestamp__lte=timestamp_ending_separator,
+    ).all()
 
     calendar_data = []
     all_lectures = list(
@@ -182,12 +175,10 @@ def view_group_lectures(request, group_id: int):
             past_lectures,
             current_lectures,
             (
-                get_objects_for_user(request.user, "lectures.view_lecture")
-                .filter(
+                Lecture.objects.filter(
                     lecture_group_types__group=group,
                     timestamp__gte=timestamp_ending_separator,
-                )
-                .all()
+                ).all()
             ),
         )
     )
@@ -272,11 +263,7 @@ def get_related_group_id(request):
         if not str(related_group_id).isnumeric():
             raise HTTPExceptions.BAD_REQUEST
 
-        if not (
-            get_objects_for_user(request.user, "lectures.view_lecturegroup")
-            .filter(id=related_group_id)
-            .exists()
-        ):
+        if not (LectureGroup.objects.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
@@ -360,9 +347,7 @@ def search(request):
 
 
 def view_lector(request, id):
-    lector = get_object_or_404(
-        get_objects_for_user(request.user, "lectures.view_lecturelector"), id=id
-    )
+    lector = get_object_or_404(LectureLector, id=id)
 
     lectures = get_lectures(
         request, models.Q(id__in=lector.lectures.all()), get_exceptions=False
@@ -374,11 +359,7 @@ def view_lector(request, id):
         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()
-        ):
+        if not (Lecture.objects.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
diff --git a/package-lock.json b/package-lock.json
index ce73779d1e6fe369f75e3b0dca7919f8cfd3eb30..c666f3160a86514e8d3cb01cbdea05dbb0ed5941 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1283,9 +1283,9 @@
       }
     },
     "node_modules/nanoid": {
-      "version": "3.3.6",
-      "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
-      "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==",
+      "version": "3.3.7",
+      "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
+      "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
       "funding": [
         {
           "type": "github",
@@ -1447,9 +1447,9 @@
       }
     },
     "node_modules/postcss": {
-      "version": "8.4.27",
-      "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.27.tgz",
-      "integrity": "sha512-gY/ACJtJPSmUFPDCHtX78+01fHa64FaU4zaaWfuh1MhGJISufJAH4cun6k/8fwsHYeK4UQmENQK+tRLCFJE8JQ==",
+      "version": "8.4.33",
+      "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz",
+      "integrity": "sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==",
       "funding": [
         {
           "type": "opencollective",
@@ -1465,7 +1465,7 @@
         }
       ],
       "dependencies": {
-        "nanoid": "^3.3.6",
+        "nanoid": "^3.3.7",
         "picocolors": "^1.0.0",
         "source-map-js": "^1.0.2"
       },
diff --git a/requirements/base.txt b/requirements/base.txt
index 3366e4140d2f54d37723e52eb6f50416259ad040..9115d8a6957a9722b87f776de8bac0d47f6be0e7 100644
--- a/requirements/base.txt
+++ b/requirements/base.txt
@@ -11,7 +11,6 @@ pirates==0.6.0
 django-markdownx==4.0.0b1
 django-environ==0.9.0
 django-http-exceptions==1.4.0
-django-guardian==2.4.0
 gql[requests]==3.4.1
 requests==2.31.0
 PyJWT==2.6.0
diff --git a/shared/admin.py b/shared/admin.py
index a8fd3e7a1c340ff489022f303c29a36826d394e6..16ab4e9eec61ed030cafa6d8caa8b085190799c8 100644
--- a/shared/admin.py
+++ b/shared/admin.py
@@ -1,12 +1,6 @@
 import enum
 
 from django.contrib import admin
-from guardian.admin import GuardedModelAdmin
-from markdownx.admin import MarkdownxModelAdmin
-
-
-class MarkdownxGuardedModelAdmin(MarkdownxModelAdmin, GuardedModelAdmin):
-    pass
 
 
 class FieldsetInlineOrder(enum.Enum):
diff --git a/static_src/view_group_lectures.js b/static_src/view_group_lectures.js
index e60c9eb791e9ba1f4663431fae2d68c2a1a0c198..aa05271e51d2514cfb62ba47564d29f35b212d37 100644
--- a/static_src/view_group_lectures.js
+++ b/static_src/view_group_lectures.js
@@ -26,5 +26,34 @@ $(window).ready(
             window.currentTimelineYear--;
             showTimelineYear();
         }
+
+        $(".__lecture-category").on(
+            "click",
+            event => {
+                if (event.currentTarget.dataset.isOpen === 'true') {
+                    $(event.currentTarget).
+                    find(".__lecture-category-opener").
+                    addClass("ico--chevron-down").
+                    removeClass("ico--chevron-up")
+
+                    event.currentTarget.dataset.isOpen = 'false'
+
+                    $(event.currentTarget).
+                    find(".__lecture-category-content").
+                    addClass("hidden")
+                } else {
+                    $(event.currentTarget).
+                    find(".__lecture-category-opener").
+                    removeClass("ico--chevron-down").
+                    addClass("ico--chevron-up")
+
+                    event.currentTarget.dataset.isOpen = 'true'
+
+                    $(event.currentTarget).
+                    find(".__lecture-category-content").
+                    removeClass("hidden")
+                }
+            }
+        )
     }
 )
diff --git a/ucebnice/settings/base.py b/ucebnice/settings/base.py
index 96f9c481d307777d4abb2b64d2e16b4046e6e507..792f93dfc6b8cc8e2d71bfd935e081dee56ecbf0 100644
--- a/ucebnice/settings/base.py
+++ b/ucebnice/settings/base.py
@@ -52,7 +52,6 @@ INSTALLED_APPS = [
     "django.contrib.messages",
     "django.contrib.staticfiles",
     "dbsettings",
-    "guardian",
     "markdownx",
     "pirates",
     "webpack_loader",
@@ -131,7 +130,6 @@ AUTH_USER_MODEL = "users.User"
 AUTHENTICATION_BACKENDS = (
     "oidc.auth.UcebniceOIDCAuthenticationBackend",
     "django.contrib.auth.backends.ModelBackend",
-    "guardian.backends.ObjectPermissionBackend",
 )
 
 LOGIN_URL = "/oidc/authenticate/"
diff --git a/users/admin.py b/users/admin.py
index 128fc422e4ce148aaa75e58b4f4ef3bd3e2e9688..3772e66562cdf5a84df459c2325d65e192d7687d 100644
--- a/users/admin.py
+++ b/users/admin.py
@@ -1,11 +1,10 @@
 from django.contrib import admin
-
-from shared.admin import MarkdownxGuardedModelAdmin
+from markdownx.admin import MarkdownxModelAdmin
 
 from .models import User
 
 
-class UserAdmin(MarkdownxGuardedModelAdmin):
+class UserAdmin(MarkdownxModelAdmin):
     search_fields = ("first_name", "last_name", "email")
 
 
diff --git a/users/models.py b/users/models.py
index 49a91f105b367b8138566daf371a75b48eeb5438..259a6e1a1e92c651a3c93de41ca8be5245cf6414 100644
--- a/users/models.py
+++ b/users/models.py
@@ -30,10 +30,6 @@ class User(pirates_models.AbstractUser):
         verbose_name="E-mailová adresa",
     )
 
-    def set_unusable_password(self) -> None:
-        # Purely for compatibility with Guardian
-        pass
-
     def get_username(self) -> str:
         first_name = self.first_name