From 5df81d160d9e01a7b8fac1919dc3affdf45c8093 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Valenta?= <git@imaniti.org>
Date: Tue, 18 Apr 2023 13:22:26 +0200
Subject: [PATCH] finish data structure, make views

---
 .gitignore                                    |   1 +
 groups/admin.py                               |  11 +-
 groups/apps.py                                |   5 +-
 groups/models.py                              |   3 +-
 lectures/admin.py                             |  61 ++++
 lectures/apps.py                              |   5 +-
 lectures/migrations/0001_initial.py           | 165 ++++++++---
 ...me_event_lecturelector_lecture_and_more.py |  41 +++
 ...003_alter_lecturematerial_file_and_more.py |  23 ++
 ...004_alter_lecturematerial_file_and_more.py |  23 ++
 lectures/migrations/0005_lecturegroup.py      |  27 ++
 ...006_alter_lecturegroup_options_and_more.py |  39 +++
 .../migrations/0007_alter_lecture_options.py  |  17 ++
 .../migrations/0008_alter_lecture_options.py  |  17 ++
 ...ve_lecturegroup_lectures_lecture_groups.py |  22 ++
 .../0010_alter_lecturegroup_options.py        |  17 ++
 lectures/models.py                            |  69 +++--
 lectures/templates/lectures/index.html        |   6 +
 lectures/urls.py                              |   8 +
 lectures/views.py                             |  52 +++-
 manage.py                                     |   4 +-
 .../364D1F51-B67A-4FA5-83E6-1F32D62B6A78.gif  | Bin 0 -> 19263 bytes
 oidc/apps.py                                  |   1 +
 oidc/auth.py                                  |   2 +
 shared/static/shared/.gitkeep                 |   1 -
 shared/static/shared/base.js                  |   2 +-
 shared/static/shared/favicon.png              | Bin 0 -> 9781 bytes
 shared/static/shared/runtime.js               |   2 +-
 shared/static/shared/style.css                | 263 ++++++++++++++++++
 shared/templates/shared/includes/base.html    | 198 +++++++++++++
 ucebnice/settings/base.py                     |   2 +-
 ucebnice/urls.py                              |   2 +
 users/admin.py                                |   7 +-
 users/migrations/0001_initial.py              | 112 ++++++--
 users/migrations/0002_user_rsvp_lectures.py   |  19 ++
 .../0003_alter_user_rsvp_lectures.py          |  19 ++
 .../0004_alter_user_rsvp_lectures.py          |  19 ++
 users/migrations/0005_alter_user_email.py     |  18 ++
 users/migrations/0006_user_sso_username.py    |  19 ++
 .../0007_alter_user_rsvp_lectures.py          |  19 ++
 users/models.py                               |  18 ++
 41 files changed, 1255 insertions(+), 84 deletions(-)
 create mode 100644 lectures/migrations/0002_lecturematerial_rename_event_lecturelector_lecture_and_more.py
 create mode 100644 lectures/migrations/0003_alter_lecturematerial_file_and_more.py
 create mode 100644 lectures/migrations/0004_alter_lecturematerial_file_and_more.py
 create mode 100644 lectures/migrations/0005_lecturegroup.py
 create mode 100644 lectures/migrations/0006_alter_lecturegroup_options_and_more.py
 create mode 100644 lectures/migrations/0007_alter_lecture_options.py
 create mode 100644 lectures/migrations/0008_alter_lecture_options.py
 create mode 100644 lectures/migrations/0009_remove_lecturegroup_lectures_lecture_groups.py
 create mode 100644 lectures/migrations/0010_alter_lecturegroup_options.py
 create mode 100644 lectures/templates/lectures/index.html
 create mode 100644 lectures/urls.py
 create mode 100644 media/364D1F51-B67A-4FA5-83E6-1F32D62B6A78.gif
 delete mode 100644 shared/static/shared/.gitkeep
 create mode 100644 shared/static/shared/favicon.png
 create mode 100644 shared/templates/shared/includes/base.html
 create mode 100644 users/migrations/0002_user_rsvp_lectures.py
 create mode 100644 users/migrations/0003_alter_user_rsvp_lectures.py
 create mode 100644 users/migrations/0004_alter_user_rsvp_lectures.py
 create mode 100644 users/migrations/0005_alter_user_email.py
 create mode 100644 users/migrations/0006_user_sso_username.py
 create mode 100644 users/migrations/0007_alter_user_rsvp_lectures.py

diff --git a/.gitignore b/.gitignore
index eca2a89..33687b8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,3 +3,4 @@ __pycache__/
 webpack-stats.json
 staticfiles
 node_modules
+shared/static/shared/*.{js,css,txt}
diff --git a/groups/admin.py b/groups/admin.py
index 8c38f3f..2780a04 100644
--- a/groups/admin.py
+++ b/groups/admin.py
@@ -1,3 +1,12 @@
 from django.contrib import admin
 
-# Register your models here.
+from shared.admin import MarkdownxGuardedModelAdmin
+
+from .models import Group
+
+
+class GroupAdmin(MarkdownxGuardedModelAdmin):
+    autocomplete_fields = ("lectures",)
+
+
+admin.site.register(Group, GroupAdmin)
diff --git a/groups/apps.py b/groups/apps.py
index 86b49bc..8c383e0 100644
--- a/groups/apps.py
+++ b/groups/apps.py
@@ -2,5 +2,6 @@ from django.apps import AppConfig
 
 
 class GroupsConfig(AppConfig):
-    default_auto_field = 'django.db.models.BigAutoField'
-    name = 'groups'
+    default_auto_field = "django.db.models.BigAutoField"
+    name = "groups"
+    verbose_name = "Skupiny"
diff --git a/groups/models.py b/groups/models.py
index fbab9a6..a18eb37 100644
--- a/groups/models.py
+++ b/groups/models.py
@@ -12,8 +12,7 @@ class Group(models.Model):
     lectures = models.ManyToManyField(
         "lectures.Lecture",
         blank=True,
-        null=True,
-        verbose_name="Události",
+        verbose_name="Lekce",
     )
 
     class Meta:
diff --git a/lectures/admin.py b/lectures/admin.py
index 8c38f3f..07ab444 100644
--- a/lectures/admin.py
+++ b/lectures/admin.py
@@ -1,3 +1,64 @@
 from django.contrib import admin
 
+from shared.admin import MarkdownxGuardedModelAdmin
+
+from .models import (
+    Lecture,
+    LectureGroup,
+    LectureLector,
+    LectureMaterial,
+    LectureRecording,
+)
+
 # Register your models here.
+
+
+class IndexHiddenModelAdmin(MarkdownxGuardedModelAdmin):
+    def has_module_permission(self, request):
+        return False
+
+
+class LectureGroupAdmin(MarkdownxGuardedModelAdmin):
+    autocomplete_fields = ("user_groups",)
+    search_fields = ("name",)
+
+
+class LectureLectorInline(admin.TabularInline):
+    model = LectureLector
+    extra = 1
+
+
+class LectureRecordingInline(admin.TabularInline):
+    model = LectureRecording
+    extra = 1
+
+
+class LectureMaterialInline(admin.StackedInline):
+    model = LectureMaterial
+    extra = 1
+
+
+class LectureAdmin(MarkdownxGuardedModelAdmin):
+    inlines = (
+        LectureLectorInline,
+        LectureRecordingInline,
+        LectureMaterialInline,
+    )
+
+    autocomplete_fields = ("groups",)
+    search_fields = ("name", "description")
+    list_display = (
+        "name",
+        "timestamp",
+    )
+
+
+for model in (
+    LectureLector,
+    LectureMaterial,
+    LectureRecording,
+):
+    admin.site.register(model, IndexHiddenModelAdmin)
+
+admin.site.register(Lecture, LectureAdmin)
+admin.site.register(LectureGroup, LectureGroupAdmin)
diff --git a/lectures/apps.py b/lectures/apps.py
index 30d62f2..5518d56 100644
--- a/lectures/apps.py
+++ b/lectures/apps.py
@@ -2,5 +2,6 @@ from django.apps import AppConfig
 
 
 class LecturesConfig(AppConfig):
-    default_auto_field = 'django.db.models.BigAutoField'
-    name = 'lectures'
+    default_auto_field = "django.db.models.BigAutoField"
+    name = "lectures"
+    verbose_name = "Lekce"
diff --git a/lectures/migrations/0001_initial.py b/lectures/migrations/0001_initial.py
index 549de6b..1728c28 100644
--- a/lectures/migrations/0001_initial.py
+++ b/lectures/migrations/0001_initial.py
@@ -1,70 +1,167 @@
 # Generated by Django 4.1.4 on 2023-04-17 10:56
 
-from django.db import migrations, models
 import django.db.models.deletion
 import markdownx.models
+from django.db import migrations, models
 
 
 class Migration(migrations.Migration):
-
     initial = True
 
-    dependencies = [
-    ]
+    dependencies = []
 
     operations = [
         migrations.CreateModel(
-            name='Lecture',
+            name="Lecture",
             fields=[
-                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
-                ('timestamp', models.DateTimeField(blank=True, null=True, verbose_name='Datum a čas konání')),
-                ('type', models.CharField(choices=[('recommended', 'Doporučené'), ('optional', 'Volitelné')], max_length=11, verbose_name='Typ')),
-                ('name', models.CharField(max_length=128, verbose_name='Název')),
-                ('description', markdownx.models.MarkdownxField(blank=True, help_text='Můžeš použít markdown.', null=True, verbose_name='Popis')),
+                (
+                    "id",
+                    models.BigAutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                (
+                    "timestamp",
+                    models.DateTimeField(
+                        blank=True, null=True, verbose_name="Datum a čas konání"
+                    ),
+                ),
+                (
+                    "type",
+                    models.CharField(
+                        choices=[
+                            ("recommended", "Doporučené"),
+                            ("optional", "Volitelné"),
+                        ],
+                        max_length=11,
+                        verbose_name="Typ",
+                    ),
+                ),
+                ("name", models.CharField(max_length=128, verbose_name="Název")),
+                (
+                    "description",
+                    markdownx.models.MarkdownxField(
+                        blank=True,
+                        help_text="Můžeš použít markdown.",
+                        null=True,
+                        verbose_name="Popis",
+                    ),
+                ),
             ],
             options={
-                'verbose_name': 'Lekce',
-                'verbose_name_plural': 'Lekce',
+                "verbose_name": "Lekce",
+                "verbose_name_plural": "Lekce",
             },
         ),
         migrations.CreateModel(
-            name='Material',
+            name="Material",
             fields=[
-                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
-                ('name', models.CharField(max_length=128, verbose_name='Název')),
-                ('link', models.URLField(blank=True, max_length=256, null=True, verbose_name='Odkaz')),
-                ('file', models.FileField(blank=True, null=True, upload_to='', verbose_name='Soubor')),
-                ('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='materials', to='lectures.lecture', verbose_name='Událost')),
+                (
+                    "id",
+                    models.BigAutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                ("name", models.CharField(max_length=128, verbose_name="Název")),
+                (
+                    "link",
+                    models.URLField(
+                        blank=True, max_length=256, null=True, verbose_name="Odkaz"
+                    ),
+                ),
+                (
+                    "file",
+                    models.FileField(
+                        blank=True, null=True, upload_to="", verbose_name="Soubor"
+                    ),
+                ),
+                (
+                    "event",
+                    models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        related_name="materials",
+                        to="lectures.lecture",
+                        verbose_name="Událost",
+                    ),
+                ),
             ],
             options={
-                'verbose_name': 'Materiál',
-                'verbose_name_plural': 'Materiály',
+                "verbose_name": "Materiál",
+                "verbose_name_plural": "Materiály",
             },
         ),
         migrations.CreateModel(
-            name='LectureRecording',
+            name="LectureRecording",
             fields=[
-                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
-                ('name', models.CharField(max_length=128, verbose_name='Název')),
-                ('link', models.URLField(blank=True, max_length=256, null=True, verbose_name='Odkaz')),
-                ('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='recordings', to='lectures.lecture', verbose_name='Událost')),
+                (
+                    "id",
+                    models.BigAutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                ("name", models.CharField(max_length=128, verbose_name="Název")),
+                (
+                    "link",
+                    models.URLField(
+                        blank=True, max_length=256, null=True, verbose_name="Odkaz"
+                    ),
+                ),
+                (
+                    "event",
+                    models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        related_name="recordings",
+                        to="lectures.lecture",
+                        verbose_name="Událost",
+                    ),
+                ),
             ],
             options={
-                'verbose_name': 'Nahrávka',
-                'verbose_name_plural': 'Nahrávky',
+                "verbose_name": "Nahrávka",
+                "verbose_name_plural": "Nahrávky",
             },
         ),
         migrations.CreateModel(
-            name='LectureLector',
+            name="LectureLector",
             fields=[
-                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
-                ('name', models.CharField(max_length=128, verbose_name='Jméno')),
-                ('link', models.URLField(blank=True, max_length=256, null=True, verbose_name='Odkaz')),
-                ('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='lectors', to='lectures.lecture', verbose_name='Událost')),
+                (
+                    "id",
+                    models.BigAutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                ("name", models.CharField(max_length=128, verbose_name="Jméno")),
+                (
+                    "link",
+                    models.URLField(
+                        blank=True, max_length=256, null=True, verbose_name="Odkaz"
+                    ),
+                ),
+                (
+                    "event",
+                    models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        related_name="lectors",
+                        to="lectures.lecture",
+                        verbose_name="Událost",
+                    ),
+                ),
             ],
             options={
-                'verbose_name': 'Lektor',
-                'verbose_name_plural': 'Lektoři',
+                "verbose_name": "Lektor",
+                "verbose_name_plural": "Lektoři",
             },
         ),
     ]
diff --git a/lectures/migrations/0002_lecturematerial_rename_event_lecturelector_lecture_and_more.py b/lectures/migrations/0002_lecturematerial_rename_event_lecturelector_lecture_and_more.py
new file mode 100644
index 0000000..39a23e3
--- /dev/null
+++ b/lectures/migrations/0002_lecturematerial_rename_event_lecturelector_lecture_and_more.py
@@ -0,0 +1,41 @@
+# Generated by Django 4.1.4 on 2023-04-18 07:33
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('lectures', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='LectureMaterial',
+            fields=[
+                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('name', models.CharField(max_length=128, verbose_name='Název')),
+                ('link', models.URLField(blank=True, max_length=256, null=True, verbose_name='Odkaz')),
+                ('file', models.FileField(blank=True, null=True, upload_to='', verbose_name='Soubor')),
+                ('lecture', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='materials', to='lectures.lecture', verbose_name='Událost')),
+            ],
+            options={
+                'verbose_name': 'Materiál',
+                'verbose_name_plural': 'Materiály',
+            },
+        ),
+        migrations.RenameField(
+            model_name='lecturelector',
+            old_name='event',
+            new_name='lecture',
+        ),
+        migrations.RenameField(
+            model_name='lecturerecording',
+            old_name='event',
+            new_name='lecture',
+        ),
+        migrations.DeleteModel(
+            name='Material',
+        ),
+    ]
diff --git a/lectures/migrations/0003_alter_lecturematerial_file_and_more.py b/lectures/migrations/0003_alter_lecturematerial_file_and_more.py
new file mode 100644
index 0000000..b605f4d
--- /dev/null
+++ b/lectures/migrations/0003_alter_lecturematerial_file_and_more.py
@@ -0,0 +1,23 @@
+# Generated by Django 4.1.4 on 2023-04-18 07:56
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('lectures', '0002_lecturematerial_rename_event_lecturelector_lecture_and_more'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='lecturematerial',
+            name='file',
+            field=models.FileField(blank=True, help_text='Zadej prosím pouze odkaz, nebo soubor.', null=True, upload_to='', verbose_name='Soubor'),
+        ),
+        migrations.AlterField(
+            model_name='lecturematerial',
+            name='link',
+            field=models.URLField(blank=True, help_text='Zadej prosím pouze odkaz, nebo soubor.', max_length=256, null=True, verbose_name='Odkaz'),
+        ),
+    ]
diff --git a/lectures/migrations/0004_alter_lecturematerial_file_and_more.py b/lectures/migrations/0004_alter_lecturematerial_file_and_more.py
new file mode 100644
index 0000000..a44a7da
--- /dev/null
+++ b/lectures/migrations/0004_alter_lecturematerial_file_and_more.py
@@ -0,0 +1,23 @@
+# Generated by Django 4.1.4 on 2023-04-18 07:58
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('lectures', '0003_alter_lecturematerial_file_and_more'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='lecturematerial',
+            name='file',
+            field=models.FileField(blank=True, help_text='Pokud máš vložený soubor, nemůžeš definovat odkaz.', null=True, upload_to='', verbose_name='Soubor'),
+        ),
+        migrations.AlterField(
+            model_name='lecturematerial',
+            name='link',
+            field=models.URLField(blank=True, help_text='Pokud máš zadaný odkaz, nemůžeš definovat soubor.', max_length=256, null=True, verbose_name='Odkaz'),
+        ),
+    ]
diff --git a/lectures/migrations/0005_lecturegroup.py b/lectures/migrations/0005_lecturegroup.py
new file mode 100644
index 0000000..0ac154f
--- /dev/null
+++ b/lectures/migrations/0005_lecturegroup.py
@@ -0,0 +1,27 @@
+# Generated by Django 4.1.4 on 2023-04-18 09:54
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('auth', '0012_alter_user_first_name_max_length'),
+        ('lectures', '0004_alter_lecturematerial_file_and_more'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='LectureGroup',
+            fields=[
+                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('name', models.CharField(max_length=128, verbose_name='Jméno')),
+                ('lectures', models.ManyToManyField(blank=True, related_name='groups', to='lectures.lecture', verbose_name='Lekce')),
+                ('user_groups', models.ManyToManyField(to='auth.group', verbose_name='Uživatelské skupiny')),
+            ],
+            options={
+                'verbose_name': 'Skupina',
+                'verbose_name_plural': 'Skupiny',
+            },
+        ),
+    ]
diff --git a/lectures/migrations/0006_alter_lecturegroup_options_and_more.py b/lectures/migrations/0006_alter_lecturegroup_options_and_more.py
new file mode 100644
index 0000000..4988853
--- /dev/null
+++ b/lectures/migrations/0006_alter_lecturegroup_options_and_more.py
@@ -0,0 +1,39 @@
+# Generated by Django 4.1.4 on 2023-04-18 10:12
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('auth', '0012_alter_user_first_name_max_length'),
+        ('lectures', '0005_lecturegroup'),
+    ]
+
+    operations = [
+        migrations.AlterModelOptions(
+            name='lecturegroup',
+            options={'verbose_name': 'Výuková skupina', 'verbose_name_plural': 'Výukové skupiny'},
+        ),
+        migrations.AlterField(
+            model_name='lecturegroup',
+            name='user_groups',
+            field=models.ManyToManyField(blank=True, help_text='Pokud nedefinuješ žádné, lekce ve skupině jsou dostupné všem.', to='auth.group', verbose_name='Uživatelské skupiny'),
+        ),
+        migrations.AlterField(
+            model_name='lecturelector',
+            name='lecture',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='lectors', to='lectures.lecture', verbose_name='Lekce'),
+        ),
+        migrations.AlterField(
+            model_name='lecturematerial',
+            name='lecture',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='materials', to='lectures.lecture', verbose_name='Lekce'),
+        ),
+        migrations.AlterField(
+            model_name='lecturerecording',
+            name='lecture',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='recordings', to='lectures.lecture', verbose_name='Lekce'),
+        ),
+    ]
diff --git a/lectures/migrations/0007_alter_lecture_options.py b/lectures/migrations/0007_alter_lecture_options.py
new file mode 100644
index 0000000..d1dbc97
--- /dev/null
+++ b/lectures/migrations/0007_alter_lecture_options.py
@@ -0,0 +1,17 @@
+# Generated by Django 4.1.4 on 2023-04-18 10:20
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('lectures', '0006_alter_lecturegroup_options_and_more'),
+    ]
+
+    operations = [
+        migrations.AlterModelOptions(
+            name='lecture',
+            options={'ordering': ('-timestamp',), 'verbose_name': 'Lekce', 'verbose_name_plural': 'Lekce'},
+        ),
+    ]
diff --git a/lectures/migrations/0008_alter_lecture_options.py b/lectures/migrations/0008_alter_lecture_options.py
new file mode 100644
index 0000000..fa169f9
--- /dev/null
+++ b/lectures/migrations/0008_alter_lecture_options.py
@@ -0,0 +1,17 @@
+# Generated by Django 4.1.4 on 2023-04-18 10:21
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('lectures', '0007_alter_lecture_options'),
+    ]
+
+    operations = [
+        migrations.AlterModelOptions(
+            name='lecture',
+            options={'ordering': ('-timestamp', '-name'), 'verbose_name': 'Lekce', 'verbose_name_plural': 'Lekce'},
+        ),
+    ]
diff --git a/lectures/migrations/0009_remove_lecturegroup_lectures_lecture_groups.py b/lectures/migrations/0009_remove_lecturegroup_lectures_lecture_groups.py
new file mode 100644
index 0000000..4cc5167
--- /dev/null
+++ b/lectures/migrations/0009_remove_lecturegroup_lectures_lecture_groups.py
@@ -0,0 +1,22 @@
+# Generated by Django 4.1.4 on 2023-04-18 10:28
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('lectures', '0008_alter_lecture_options'),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name='lecturegroup',
+            name='lectures',
+        ),
+        migrations.AddField(
+            model_name='lecture',
+            name='groups',
+            field=models.ManyToManyField(blank=True, related_name='lectures', to='lectures.lecturegroup', verbose_name='Výukové skupiny'),
+        ),
+    ]
diff --git a/lectures/migrations/0010_alter_lecturegroup_options.py b/lectures/migrations/0010_alter_lecturegroup_options.py
new file mode 100644
index 0000000..339cb6c
--- /dev/null
+++ b/lectures/migrations/0010_alter_lecturegroup_options.py
@@ -0,0 +1,17 @@
+# Generated by Django 4.1.4 on 2023-04-18 10:29
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('lectures', '0009_remove_lecturegroup_lectures_lecture_groups'),
+    ]
+
+    operations = [
+        migrations.AlterModelOptions(
+            name='lecturegroup',
+            options={'ordering': ('name',), 'verbose_name': 'Výuková skupina', 'verbose_name_plural': 'Výukové skupiny'},
+        ),
+    ]
diff --git a/lectures/models.py b/lectures/models.py
index 81cd2a0..cd8ea81 100644
--- a/lectures/models.py
+++ b/lectures/models.py
@@ -1,14 +1,38 @@
 from datetime import timedelta
 
+from django.contrib.auth.models import Group
 from django.core.exceptions import ValidationError
 from django.db import models
 from django.utils import timezone
 from markdownx.models import MarkdownxField
 
+from shared.models import NameStrMixin
+
 # Create your models here.
 
 
-class Lecture(models.Model):
+class LectureGroup(NameStrMixin, models.Model):
+    name = models.CharField(
+        max_length=128,
+        verbose_name="Jméno",
+    )
+
+    user_groups = models.ManyToManyField(
+        Group,
+        blank=True,
+        verbose_name="Uživatelské skupiny",
+        help_text=("Pokud nedefinuješ žádné, lekce ve skupině jsou dostupné všem."),
+    )
+
+    class Meta:
+        verbose_name = "Výuková skupina"
+        verbose_name_plural = "Výukové skupiny"
+        ordering = ("name",)
+
+
+class Lecture(NameStrMixin, models.Model):
+    is_current_treshold = timedelta(hours=8)
+
     timestamp = models.DateTimeField(
         verbose_name="Datum a čas konání",
         blank=True,
@@ -19,6 +43,13 @@ class Lecture(models.Model):
         RECOMMENDED = "recommended", "Doporučené"
         OPTIONAL = "optional", "Volitelné"
 
+    groups = models.ManyToManyField(
+        LectureGroup,
+        blank=True,
+        related_name="lectures",
+        verbose_name="Výukové skupiny",
+    )
+
     type = models.CharField(
         choices=TypeChoices.choices,
         max_length=11,
@@ -40,19 +71,20 @@ class Lecture(models.Model):
     @property
     def is_current(self) -> bool:
         # On or after the current time, minus 8 hours
-        return self.timestamp >= (timezone.now() - timedelta(hours=8))
+        return self.timestamp >= (timezone.now() - self.is_current_treshold)
 
     class Meta:
         verbose_name = "Lekce"
         verbose_name_plural = verbose_name
+        ordering = ("-timestamp", "-name")
 
 
-class LectureLector(models.Model):
-    event = models.ForeignKey(
+class LectureLector(NameStrMixin, models.Model):
+    lecture = models.ForeignKey(
         "Lecture",
         on_delete=models.CASCADE,
         related_name="lectors",
-        verbose_name="Událost",
+        verbose_name="Lekce",
     )
 
     name = models.CharField(
@@ -72,12 +104,12 @@ class LectureLector(models.Model):
         verbose_name_plural = "Lektoři"
 
 
-class LectureRecording(models.Model):
-    event = models.ForeignKey(
+class LectureRecording(NameStrMixin, models.Model):
+    lecture = models.ForeignKey(
         "Lecture",
         on_delete=models.CASCADE,
         related_name="recordings",
-        verbose_name="Událost",
+        verbose_name="Lekce",
     )
 
     name = models.CharField(
@@ -97,12 +129,12 @@ class LectureRecording(models.Model):
         verbose_name_plural = "Nahrávky"
 
 
-class Material(models.Model):
-    event = models.ForeignKey(
+class LectureMaterial(NameStrMixin, models.Model):
+    lecture = models.ForeignKey(
         "Lecture",
         on_delete=models.CASCADE,
         related_name="materials",
-        verbose_name="Událost",
+        verbose_name="Lekce",
     )
 
     name = models.CharField(
@@ -115,25 +147,28 @@ class Material(models.Model):
         blank=True,
         null=True,
         verbose_name="Odkaz",
+        help_text="Pokud máš zadaný odkaz, nemůžeš definovat soubor.",
     )
 
     file = models.FileField(
         blank=True,
         null=True,
         verbose_name="Soubor",
+        help_text="Pokud máš vložený soubor, nemůžeš definovat odkaz.",
     )
 
     def clean(self) -> None:
         BOTH_FILE_AND_LINK_DEFINED_ERROR = (
-            "Definuj prosím pouze odkaz, nebo soubor. "
-            "Nemůžeš mít oboje najednou."
+            "Definuj prosím pouze odkaz, nebo soubor. Nemůžeš mít oboje najednou."
         )
 
         if self.file and self.link:
-            raise ValidationError({
-                "link": BOTH_FILE_AND_LINK_DEFINED_ERROR,
-                "file": BOTH_FILE_AND_LINK_DEFINED_ERROR,
-            })
+            raise ValidationError(
+                {
+                    "link": BOTH_FILE_AND_LINK_DEFINED_ERROR,
+                    "file": BOTH_FILE_AND_LINK_DEFINED_ERROR,
+                }
+            )
 
     class Meta:
         verbose_name = "Materiál"
diff --git a/lectures/templates/lectures/index.html b/lectures/templates/lectures/index.html
new file mode 100644
index 0000000..c2f99e3
--- /dev/null
+++ b/lectures/templates/lectures/index.html
@@ -0,0 +1,6 @@
+{% extends "shared/includes/base.html" %}
+{% load markdownify %}
+
+{% block content %}
+    a
+{% endblock %}
diff --git a/lectures/urls.py b/lectures/urls.py
new file mode 100644
index 0000000..004bc53
--- /dev/null
+++ b/lectures/urls.py
@@ -0,0 +1,8 @@
+from django.urls import path
+
+from . import models, views
+
+app_name = "lectures"
+urlpatterns = [
+    path("", views.view_avilable_groups, name="view_avilable_groups"),
+]
diff --git a/lectures/views.py b/lectures/views.py
index 91ea44a..fe8c0d0 100644
--- a/lectures/views.py
+++ b/lectures/views.py
@@ -1,3 +1,51 @@
-from django.shortcuts import render
+from django.db import models
+from django.shortcuts import get_object_or_404, render
+from guardian.shortcuts import get_objects_for_user
 
-# Create your views here.
+from .models import LectureGroup
+
+
+def view_avilable_groups(request):
+    lecture_groups = (
+        get_objects_for_user(request.user, "lectures.view_lecturegroup")
+        .filter(user_groups__in=request.user.groups.all())
+        .distinct()
+        .all()
+    )
+
+    return render(
+        request,
+        "lectures/view_groups.html",
+        {
+            "title": "Výukové skupiny",
+            "site_url": "https://ucebnice.pirati.cz",  # TODO
+            "description": "Kurzy a školení zaměřené na politickou práci a organizaci kampaní.",
+            "header_name": "Pirátský e-Learning",
+            "lecture_groups": lecture_groups,
+        },
+    )
+
+
+def view_lectures(request, group_id: int):
+    group = get_object_or_404(
+        get_objects_for_user(request.user, "lectures.view_lecturegroup"),
+        id=group_id,
+    )
+
+    lectures = (
+        get_objects_for_user(request.user, "lectures.view_lecture")
+        .filter(groups=group)
+        .all()
+    )
+
+    return render(
+        request,
+        "lectures/view_lectures.html",
+        {
+            "title": f"Výuka pro {group.name}",
+            "site_url": "https://ucebnice.pirati.cz",  # TODO
+            "description": f"e-Learningová výuka pro skupinu {group.name}.",
+            "header_name": group.name,
+            "lectures": lectures,
+        },
+    )
diff --git a/manage.py b/manage.py
index b792cc0..701b8e3 100755
--- a/manage.py
+++ b/manage.py
@@ -6,7 +6,7 @@ import sys
 
 def main():
     """Run administrative tasks."""
-    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'ucebnice.settings')
+    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "ucebnice.settings")
     try:
         from django.core.management import execute_from_command_line
     except ImportError as exc:
@@ -18,5 +18,5 @@ def main():
     execute_from_command_line(sys.argv)
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     main()
diff --git a/media/364D1F51-B67A-4FA5-83E6-1F32D62B6A78.gif b/media/364D1F51-B67A-4FA5-83E6-1F32D62B6A78.gif
new file mode 100644
index 0000000000000000000000000000000000000000..676aff60c51e1b4301828c99f241174cf3b7ab71
GIT binary patch
literal 19263
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

literal 0
HcmV?d00001

diff --git a/oidc/apps.py b/oidc/apps.py
index 742acac..eab6c95 100644
--- a/oidc/apps.py
+++ b/oidc/apps.py
@@ -4,3 +4,4 @@ from django.apps import AppConfig
 class OidcConfig(AppConfig):
     default_auto_field = "django.db.models.BigAutoField"
     name = "oidc"
+    verbose_name = "OpenID Přihlašování"
diff --git a/oidc/auth.py b/oidc/auth.py
index 0a0e359..97ef6a2 100644
--- a/oidc/auth.py
+++ b/oidc/auth.py
@@ -53,6 +53,8 @@ class UcebniceOIDCAuthenticationBackend(PiratesOIDCAuthenticationBackend):
             access_token, options={"verify_signature": False}
         )
 
+        user.sso_username = decoded_access_token["preferred_username"]
+        user.email = decoded_access_token["email"]
         user_groups = user.groups.all()
 
         self._remove_old_user_groups(
diff --git a/shared/static/shared/.gitkeep b/shared/static/shared/.gitkeep
deleted file mode 100644
index 8d1c8b6..0000000
--- a/shared/static/shared/.gitkeep
+++ /dev/null
@@ -1 +0,0 @@
- 
diff --git a/shared/static/shared/base.js b/shared/static/shared/base.js
index d52da30..219ecfe 100644
--- a/shared/static/shared/base.js
+++ b/shared/static/shared/base.js
@@ -1 +1 @@
-(self.webpackChunkucebnice=self.webpackChunkucebnice||[]).push([[348],{208:()=>{}},e=>{e(e.s=208)}]);
\ No newline at end of file
+(self.webpackChunkucebnice=self.webpackChunkucebnice||[]).push([[348],{208:()=>{}},e=>{e(e.s=208)}]);
diff --git a/shared/static/shared/favicon.png b/shared/static/shared/favicon.png
new file mode 100644
index 0000000000000000000000000000000000000000..819126c749b3335134893d13561fd74a007e8f2a
GIT binary patch
literal 9781
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
I=akR{0K8|&eE<Le

literal 0
HcmV?d00001

diff --git a/shared/static/shared/runtime.js b/shared/static/shared/runtime.js
index 52a8280..3babfc0 100644
--- a/shared/static/shared/runtime.js
+++ b/shared/static/shared/runtime.js
@@ -1 +1 @@
-(()=>{"use strict";var r,e={},n={};function o(r){var t=n[r];if(void 0!==t)return t.exports;var i=n[r]={exports:{}};return e[r](i,i.exports,o),i.exports}o.m=e,r=[],o.O=(e,n,t,i)=>{if(!n){var a=1/0;for(l=0;l<r.length;l++){for(var[n,t,i]=r[l],s=!0,c=0;c<n.length;c++)(!1&i||a>=i)&&Object.keys(o.O).every((r=>o.O[r](n[c])))?n.splice(c--,1):(s=!1,i<a&&(a=i));if(s){r.splice(l--,1);var f=t();void 0!==f&&(e=f)}}return e}i=i||0;for(var l=r.length;l>0&&r[l-1][2]>i;l--)r[l]=r[l-1];r[l]=[n,t,i]},o.o=(r,e)=>Object.prototype.hasOwnProperty.call(r,e),(()=>{var r={666:0};o.O.j=e=>0===r[e];var e=(e,n)=>{var t,i,[a,s,c]=n,f=0;if(a.some((e=>0!==r[e]))){for(t in s)o.o(s,t)&&(o.m[t]=s[t]);if(c)var l=c(o)}for(e&&e(n);f<a.length;f++)i=a[f],o.o(r,i)&&r[i]&&r[i][0](),r[i]=0;return o.O(l)},n=self.webpackChunkucebnice=self.webpackChunkucebnice||[];n.forEach(e.bind(null,0)),n.push=e.bind(null,n.push.bind(n))})()})();
\ No newline at end of file
+(()=>{"use strict";var r,e={},n={};function o(r){var t=n[r];if(void 0!==t)return t.exports;var i=n[r]={exports:{}};return e[r](i,i.exports,o),i.exports}o.m=e,r=[],o.O=(e,n,t,i)=>{if(!n){var a=1/0;for(l=0;l<r.length;l++){for(var[n,t,i]=r[l],s=!0,c=0;c<n.length;c++)(!1&i||a>=i)&&Object.keys(o.O).every((r=>o.O[r](n[c])))?n.splice(c--,1):(s=!1,i<a&&(a=i));if(s){r.splice(l--,1);var f=t();void 0!==f&&(e=f)}}return e}i=i||0;for(var l=r.length;l>0&&r[l-1][2]>i;l--)r[l]=r[l-1];r[l]=[n,t,i]},o.o=(r,e)=>Object.prototype.hasOwnProperty.call(r,e),(()=>{var r={666:0};o.O.j=e=>0===r[e];var e=(e,n)=>{var t,i,[a,s,c]=n,f=0;if(a.some((e=>0!==r[e]))){for(t in s)o.o(s,t)&&(o.m[t]=s[t]);if(c)var l=c(o)}for(e&&e(n);f<a.length;f++)i=a[f],o.o(r,i)&&r[i]&&r[i][0](),r[i]=0;return o.O(l)},n=self.webpackChunkucebnice=self.webpackChunkucebnice||[];n.forEach(e.bind(null,0)),n.push=e.bind(null,n.push.bind(n))})()})();
diff --git a/shared/static/shared/style.css b/shared/static/shared/style.css
index 7958c7a..d11e2fb 100644
--- a/shared/static/shared/style.css
+++ b/shared/static/shared/style.css
@@ -520,18 +520,173 @@ html {
   --tw-backdrop-sepia:  ;
 }
 
+.container {
+  width: 100%;
+}
+
+@media (min-width: 640px) {
+  .container {
+    max-width: 640px;
+  }
+}
+
+@media (min-width: 768px) {
+  .container {
+    max-width: 768px;
+  }
+}
+
+@media (min-width: 1024px) {
+  .container {
+    max-width: 1024px;
+  }
+}
+
+@media (min-width: 1280px) {
+  .container {
+    max-width: 1280px;
+  }
+}
+
+@media (min-width: 1536px) {
+  .container {
+    max-width: 1536px;
+  }
+}
+
 .static {
   position: static;
 }
 
+.my-4 {
+  margin-top: 1rem;
+  margin-bottom: 1rem;
+}
+
+.mb-4 {
+  margin-bottom: 1rem;
+}
+
+.mb-6 {
+  margin-bottom: 1.5rem;
+}
+
+.ml-8 {
+  margin-left: 2rem;
+}
+
 .block {
   display: block;
 }
 
+.inline-block {
+  display: inline-block;
+}
+
 .flex {
   display: flex;
 }
 
+.w-32 {
+  width: 8rem;
+}
+
+.w-8 {
+  width: 2rem;
+}
+
+.cursor-pointer {
+  cursor: pointer;
+}
+
+.flex-col {
+  flex-direction: column;
+}
+
+.items-center {
+  align-items: center;
+}
+
+.gap-2 {
+  gap: 0.5rem;
+}
+
+.space-x-2 > :not([hidden]) ~ :not([hidden]) {
+  --tw-space-x-reverse: 0;
+  margin-right: calc(0.5rem * var(--tw-space-x-reverse));
+  margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse)));
+}
+
+.space-x-4 > :not([hidden]) ~ :not([hidden]) {
+  --tw-space-x-reverse: 0;
+  margin-right: calc(1rem * var(--tw-space-x-reverse));
+  margin-left: calc(1rem * calc(1 - var(--tw-space-x-reverse)));
+}
+
+.space-y-2 > :not([hidden]) ~ :not([hidden]) {
+  --tw-space-y-reverse: 0;
+  margin-top: calc(0.5rem * calc(1 - var(--tw-space-y-reverse)));
+  margin-bottom: calc(0.5rem * var(--tw-space-y-reverse));
+}
+
+.self-start {
+  align-self: flex-start;
+}
+
+.border-r {
+  border-right-width: 1px;
+}
+
+.py-4 {
+  padding-top: 1rem;
+  padding-bottom: 1rem;
+}
+
+.py-8 {
+  padding-top: 2rem;
+  padding-bottom: 2rem;
+}
+
+.pb-4 {
+  padding-bottom: 1rem;
+}
+
+.pb-6 {
+  padding-bottom: 1.5rem;
+}
+
+.pl-4 {
+  padding-left: 1rem;
+}
+
+.pr-8 {
+  padding-right: 2rem;
+}
+
+.text-lg {
+  font-size: 1.125rem;
+  line-height: 1.75rem;
+}
+
+.text-sm {
+  font-size: 0.875rem;
+  line-height: 1.25rem;
+}
+
+.text-xl {
+  font-size: 1.25rem;
+  line-height: 1.75rem;
+}
+
+.font-bold {
+  font-weight: 700;
+}
+
+.text-white {
+  --tw-text-opacity: 1;
+  color: rgb(255 255 255 / var(--tw-text-opacity));
+}
+
 .transition {
   transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter;
   transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter;
@@ -539,3 +694,111 @@ html {
   transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
   transition-duration: 150ms;
 }
+
+.hover\:text-white:hover {
+  --tw-text-opacity: 1;
+  color: rgb(255 255 255 / var(--tw-text-opacity));
+}
+
+@media (min-width: 640px) {
+  .sm\:flex-row {
+    flex-direction: row;
+  }
+
+  .sm\:space-x-4 > :not([hidden]) ~ :not([hidden]) {
+    --tw-space-x-reverse: 0;
+    margin-right: calc(1rem * var(--tw-space-x-reverse));
+    margin-left: calc(1rem * calc(1 - var(--tw-space-x-reverse)));
+  }
+
+  .sm\:space-y-0 > :not([hidden]) ~ :not([hidden]) {
+    --tw-space-y-reverse: 0;
+    margin-top: calc(0px * calc(1 - var(--tw-space-y-reverse)));
+    margin-bottom: calc(0px * var(--tw-space-y-reverse));
+  }
+}
+
+@media (min-width: 768px) {
+  .md\:w-40 {
+    width: 10rem;
+  }
+
+  .md\:flex-row {
+    flex-direction: row;
+  }
+
+  .md\:space-x-2 > :not([hidden]) ~ :not([hidden]) {
+    --tw-space-x-reverse: 0;
+    margin-right: calc(0.5rem * var(--tw-space-x-reverse));
+    margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse)));
+  }
+
+  .md\:space-y-0 > :not([hidden]) ~ :not([hidden]) {
+    --tw-space-y-reverse: 0;
+    margin-top: calc(0px * calc(1 - var(--tw-space-y-reverse)));
+    margin-bottom: calc(0px * var(--tw-space-y-reverse));
+  }
+}
+
+@media (min-width: 1024px) {
+  .lg\:my-0 {
+    margin-top: 0px;
+    margin-bottom: 0px;
+  }
+
+  .lg\:mb-0 {
+    margin-bottom: 0px;
+  }
+
+  .lg\:flex-col {
+    flex-direction: column;
+  }
+
+  .lg\:items-end {
+    align-items: flex-end;
+  }
+
+  .lg\:space-x-0 > :not([hidden]) ~ :not([hidden]) {
+    --tw-space-x-reverse: 0;
+    margin-right: calc(0px * var(--tw-space-x-reverse));
+    margin-left: calc(0px * calc(1 - var(--tw-space-x-reverse)));
+  }
+
+  .lg\:space-y-2 > :not([hidden]) ~ :not([hidden]) {
+    --tw-space-y-reverse: 0;
+    margin-top: calc(0.5rem * calc(1 - var(--tw-space-y-reverse)));
+    margin-bottom: calc(0.5rem * var(--tw-space-y-reverse));
+  }
+
+  .lg\:py-16 {
+    padding-top: 4rem;
+    padding-bottom: 4rem;
+  }
+
+  .lg\:py-24 {
+    padding-top: 6rem;
+    padding-bottom: 6rem;
+  }
+
+  .lg\:text-right {
+    text-align: right;
+  }
+}
+
+@media (min-width: 1280px) {
+  .xl\:flex-row {
+    flex-direction: row;
+  }
+
+  .xl\:space-x-2 > :not([hidden]) ~ :not([hidden]) {
+    --tw-space-x-reverse: 0;
+    margin-right: calc(0.5rem * var(--tw-space-x-reverse));
+    margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse)));
+  }
+
+  .xl\:space-y-0 > :not([hidden]) ~ :not([hidden]) {
+    --tw-space-y-reverse: 0;
+    margin-top: calc(0px * calc(1 - var(--tw-space-y-reverse)));
+    margin-bottom: calc(0px * var(--tw-space-y-reverse));
+  }
+}
diff --git a/shared/templates/shared/includes/base.html b/shared/templates/shared/includes/base.html
new file mode 100644
index 0000000..8f3808c
--- /dev/null
+++ b/shared/templates/shared/includes/base.html
@@ -0,0 +1,198 @@
+{% load static %}
+{% load render_bundle from webpack_loader %}
+
+<!DOCTYPE html>
+<html lang="cs">
+    <head>
+        <meta charset="utf-8">
+        <meta name="viewport" content="width=device-width, initial-scale=1">
+
+        <meta name="title" content="{{ title }} | Učebnice">
+        <meta name="description" content="{{ description }}">
+
+        {% comment %}Open Graph / Facebook{% endcomment %}
+        <meta property="og:type" content="website">
+        <meta property="og:url" content="{{ site_url }}">
+        <meta property="og:title" content="{{ title }} | Učebnice">
+        <meta property="og:description" content="{{ description }}">
+        {% comment %}<meta property="og:image" content="">{% endcomment %}
+
+        {% comment %}Twitter{% endcomment %}
+        <meta property="twitter:card" content="app">
+        <meta property="twitter:url" content="{{ site_url }}">
+        <meta property="twitter:title" content="{{ title }} | Učebnice">
+        <meta property="twitter:description" content="{{ description }}">
+        {% comment %}<meta property="twitter:image" content="">{% endcomment %}
+
+        <link
+            rel="icon"
+            type="image/png"
+            href="{% static "shared/favicon.png" %}"
+        >
+
+        <link
+            href="{% static "shared/style.css" %}"
+            rel="stylesheet"
+            media="all"
+        >
+
+        <link
+            href="https://styleguide.pirati.cz/2.12.x/css/styles.css"
+            rel="stylesheet"
+            media="all"
+        >
+        <link
+            href="https://styleguide.pirati.cz/2.12.x/css/pattern-scaffolding.css"
+            rel="stylesheet"
+            media="all"
+        >
+
+        {% render_bundle "base" %}
+
+        <title>{{ title }} | Učebnice</title>
+    </head>
+    <body>
+        <nav class="navbar navbar--simple __js-root">
+            <ui-app inline-template>
+                <ui-navbar inline-template>
+                    <div>
+                        <div class="container container--default navbar__content navbar__content--initialized">
+                            <div class="navbar__brand flex items-center pr-8 my-4 lg:my-0">
+                                <a href="{% url "lectures:view_avilable_groups" %}">
+                                    <img src="https://styleguide.pirati.cz/2.12.x/images/logo-round-white.svg" class="w-8">
+                                </a>
+                                <div class="pl-4 font-bold text-xl border-r border-grey-300 pr-8">
+                                    <a href="{% url "lectures:view_avilable_groups" %}">Učebnice</a>
+                                </div>
+                                {% if header_name %}
+                                    <div class="ml-8">
+                                        {{ header_name }}
+                                    </div>
+                                {% endif %}
+                            </div>
+
+                            <div class="navbar__main navbar__section navbar__section--expandable container-padding--zero lg:container-padding--auto">
+                                <ul class="navbar-menu text-white">
+                                    {% if user.is_staff %}
+                                        <li class="navbar-menu__item">
+                                            <a
+                                                href="{% url "admin:index" %}"
+                                                data-href="{% url "admin:index" %}"
+                                                class="navbar-menu__link flex items-center gap-2"
+                                            >
+                                                <i class="ico--power text-sm"></i>Administrace
+                                            </a>
+                                        </li>
+                                    {% endif %}
+                                </ul>
+                            </div>
+
+                            <div class="navbar__actions navbar__section navbar__section--expandable container-padding--zero lg:container-padding--auto self-start flex flex-col sm:flex-row lg:flex-col sm:space-x-4 space-y-2 sm:space-y-0 lg:space-y-2 xl:flex-row xl:space-x-2 xl:space-y-0">
+                                {% if user and not user.is_anonymous %}
+                                    <div class="flex items-center space-x-4">
+                                        <span class="head-heavy-2xs">{{ user.get_username }}</span>
+                                        <div class="avatar avatar--2xs">
+                                            <img
+                                                src="https://a.pirati.cz/piratar/100/{{ user.sso_username }}.jpg"
+                                                alt="Tvůj profilový obrázek"
+                                            >
+                                        </div>
+                                        <form action="{% url "oidc_logout" %}" method="POST">
+                                            {% csrf_token %}
+                                            <button
+                                                class="text-grey-200 hover:text-white __tooltipped"
+                                                type="submit"
+                                                aria-label="Odhlásit se"
+                                            ><i class="ico--log-out"></i></button>
+                                        </form>
+                                    </div>
+                                {% else %}
+                                    <a
+                                        class="btn btn--white btn--hoveractive cursor-pointer"
+                                        href="{% url "oidc_authentication_init" %}"
+                                    >
+                                        <div class="btn__body">Přihlásit se</div>
+                                    </a>
+                                {% endif %}
+                            </div>
+                        </div>
+                    </div>
+                </ui-navbar>
+            </ui-app>
+        </nav>
+
+        <div class="container container--default py-8 lg:py-24">
+            <main>
+                {% block content %}{% endblock %}
+            </main>
+        </div>
+
+        <footer class="footer bg-grey-700 text-white __js-root">
+            <div>
+                <div class="footer__main py-4 lg:py-16 container container--default">
+                    <section class="footer__brand">
+                        <a href="https://www.pirati.cz">
+                            <img
+                                src="https://styleguide.pirati.cz/2.12.x/images/logo-full-white.svg"
+                                alt="Logo Pirátské strany"
+                                class="w-32 md:w-40 pb-6"
+                            >
+                        </a>
+                        <p class="para mb-4 text-grey-200">
+                            <span class="copyleft inline-block">©</span> {% now "Y" %} Piráti.
+                            Všechna práva vyhlazena.
+                            Sdílejte a nechte ostatní sdílet za stejných podmínek.
+                        </p>
+                        <p class="para mb-6 lg:mb-0 text-grey-200">
+                            <a href="https://www.pirati.cz/ochrana-osobnich-udaju/">
+                                <span class="text-grey-200">Zásady ochrany osobních údajů</span>
+                            </a>
+                        </p>
+                    </section>
+                    <section class="footer__social lg:text-right">
+                        <div class="mb-4">
+                            <div class="social-icon-group space-x-2 text-white pb-4">
+                                <a href="https://www.pirati.cz" class="social-icon">
+                                    <i class="ico--home"></i>
+                                </a>
+                                <a href="https://www.facebook.com/ceska.piratska.strana/" class="social-icon">
+                                    <i class="ico--facebook"></i>
+                                </a>
+                                <a href="https://twitter.com/PiratskaStrana" class="social-icon">
+                                    <i class="ico--twitter"></i>
+                                </a>
+                                <a href="https://www.youtube.com/user/CeskaPiratskaStrana" class="social-icon">
+                                    <i class="ico--youtube"></i>
+                                </a>
+                                <a href="https://www.instagram.com/pirati.cz/" class="social-icon">
+                                    <i class="ico--instagram"></i>
+                                </a>
+                                <a href="https://www.flickr.com/photos/pirati/" class="social-icon">
+                                    <i class="ico--flickr"></i>
+                                </a>
+                            </div>
+                        </div>
+                        <div class="flex flex-col md:flex-row lg:flex-col lg:items-end space-y-2 md:space-y-0 md:space-x-2 lg:space-x-0 lg:space-y-2">
+                            <a href="https://dary.pirati.cz" class="btn btn--icon btn--cyan-200 btn--hoveractive text-lg btn--fullwidth sm:btn--autowidth">
+                                <div class="btn__body-wrap">
+                                    <div class="btn__body">Přispěj</div>
+                                    <div class="btn__icon "><i class="ico--pig"></i></div>
+                                </div>
+                            </a>
+                            <a href="https://nalodeni.pirati.cz" class="btn btn--icon btn--blue-300 btn--hoveractive text-lg btn--fullwidth sm:btn--autowidth">
+                                <div class="btn__body-wrap">
+                                    <div class="btn__body ">Naloď se</div>
+                                    <div class="btn__icon "><i class="ico--anchor"></i></div>
+                                </div>
+                            </a>
+                        </div>
+                    </section>
+                </div>
+            </div>
+        </footer>
+
+        <script
+            src="https://styleguide.pirati.cz/2.12.x/js/main.bundle.js"
+        ></script>
+    </body>
+</html>
diff --git a/ucebnice/settings/base.py b/ucebnice/settings/base.py
index 6c0fb6d..5b858b7 100644
--- a/ucebnice/settings/base.py
+++ b/ucebnice/settings/base.py
@@ -139,7 +139,7 @@ LOGOUT_REDIRECT_URL = "/"
 OIDC_RP_CLIENT_ID = env.str("OIDC_RP_CLIENT_ID")
 OIDC_RP_CLIENT_SECRET = env.str("OIDC_RP_CLIENT_SECRET")
 OIDC_RP_REALM_URL = env.str("OIDC_RP_REALM_URL")
-OIDC_RP_SCOPES = "openid profile groups"
+OIDC_RP_SCOPES = "openid profile email groups"
 OIDC_RP_SIGN_ALGO = "RS256"
 OIDC_RP_RESOURCE_ACCESS_CLIENT = env.str(
     "OIDC_RESOURCE_ACCESS_CLIENT", OIDC_RP_CLIENT_ID
diff --git a/ucebnice/urls.py b/ucebnice/urls.py
index d675a06..6767e30 100644
--- a/ucebnice/urls.py
+++ b/ucebnice/urls.py
@@ -24,5 +24,7 @@ from pirates.urls import urlpatterns as pirates_urlpatterns
 import ucebnice.admin
 
 urlpatterns = [
+    path("", include("lectures.urls")),
+    path("markdownx/", include("markdownx.urls")),
     path("admin/", admin.site.urls),
 ] + pirates_urlpatterns
diff --git a/users/admin.py b/users/admin.py
index d2e7cac..ecd2efa 100644
--- a/users/admin.py
+++ b/users/admin.py
@@ -4,4 +4,9 @@ from shared.admin import MarkdownxGuardedModelAdmin
 
 from .models import User
 
-admin.site.register(User, MarkdownxGuardedModelAdmin)
+
+class UserAdmin(MarkdownxGuardedModelAdmin):
+    autocomplete_fields = ("rsvp_lectures",)
+
+
+admin.site.register(User, UserAdmin)
diff --git a/users/migrations/0001_initial.py b/users/migrations/0001_initial.py
index ffe7ac0..2021daf 100644
--- a/users/migrations/0001_initial.py
+++ b/users/migrations/0001_initial.py
@@ -1,36 +1,114 @@
 # Generated by Django 4.1.4 on 2023-04-14 18:14
 
-from django.db import migrations, models
 import django.utils.timezone
+from django.db import migrations, models
 
 
 class Migration(migrations.Migration):
-
     initial = True
 
     dependencies = [
-        ('auth', '0012_alter_user_first_name_max_length'),
+        ("auth", "0012_alter_user_first_name_max_length"),
     ]
 
     operations = [
         migrations.CreateModel(
-            name='User',
+            name="User",
             fields=[
-                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
-                ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
-                ('sso_id', models.CharField(error_messages={'unique': 'A user with that SSO ID already exists.'}, max_length=150, unique=True, verbose_name='SSO ID')),
-                ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')),
-                ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
-                ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')),
-                ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
-                ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
-                ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
-                ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')),
-                ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')),
+                (
+                    "id",
+                    models.BigAutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                (
+                    "is_superuser",
+                    models.BooleanField(
+                        default=False,
+                        help_text="Designates that this user has all permissions without explicitly assigning them.",
+                        verbose_name="superuser status",
+                    ),
+                ),
+                (
+                    "sso_id",
+                    models.CharField(
+                        error_messages={
+                            "unique": "A user with that SSO ID already exists."
+                        },
+                        max_length=150,
+                        unique=True,
+                        verbose_name="SSO ID",
+                    ),
+                ),
+                (
+                    "first_name",
+                    models.CharField(
+                        blank=True, max_length=150, verbose_name="first name"
+                    ),
+                ),
+                (
+                    "last_name",
+                    models.CharField(
+                        blank=True, max_length=150, verbose_name="last name"
+                    ),
+                ),
+                (
+                    "email",
+                    models.EmailField(
+                        blank=True, max_length=254, verbose_name="email address"
+                    ),
+                ),
+                (
+                    "is_staff",
+                    models.BooleanField(
+                        default=False,
+                        help_text="Designates whether the user can log into this admin site.",
+                        verbose_name="staff status",
+                    ),
+                ),
+                (
+                    "is_active",
+                    models.BooleanField(
+                        default=True,
+                        help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.",
+                        verbose_name="active",
+                    ),
+                ),
+                (
+                    "date_joined",
+                    models.DateTimeField(
+                        default=django.utils.timezone.now, verbose_name="date joined"
+                    ),
+                ),
+                (
+                    "groups",
+                    models.ManyToManyField(
+                        blank=True,
+                        help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.",
+                        related_name="user_set",
+                        related_query_name="user",
+                        to="auth.group",
+                        verbose_name="groups",
+                    ),
+                ),
+                (
+                    "user_permissions",
+                    models.ManyToManyField(
+                        blank=True,
+                        help_text="Specific permissions for this user.",
+                        related_name="user_set",
+                        related_query_name="user",
+                        to="auth.permission",
+                        verbose_name="user permissions",
+                    ),
+                ),
             ],
             options={
-                'verbose_name': 'Uživatel',
-                'verbose_name_plural': 'Uživatelé',
+                "verbose_name": "Uživatel",
+                "verbose_name_plural": "Uživatelé",
             },
         ),
     ]
diff --git a/users/migrations/0002_user_rsvp_lectures.py b/users/migrations/0002_user_rsvp_lectures.py
new file mode 100644
index 0000000..642997e
--- /dev/null
+++ b/users/migrations/0002_user_rsvp_lectures.py
@@ -0,0 +1,19 @@
+# Generated by Django 4.1.4 on 2023-04-18 06:37
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('lectures', '0001_initial'),
+        ('users', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='user',
+            name='rsvp_lectures',
+            field=models.ManyToManyField(blank=True, null=True, to='lectures.lecture', verbose_name='Zaregistrované události'),
+        ),
+    ]
diff --git a/users/migrations/0003_alter_user_rsvp_lectures.py b/users/migrations/0003_alter_user_rsvp_lectures.py
new file mode 100644
index 0000000..09afbba
--- /dev/null
+++ b/users/migrations/0003_alter_user_rsvp_lectures.py
@@ -0,0 +1,19 @@
+# Generated by Django 4.1.4 on 2023-04-18 06:42
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('lectures', '0001_initial'),
+        ('users', '0002_user_rsvp_lectures'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='user',
+            name='rsvp_lectures',
+            field=models.ManyToManyField(blank=True, to='lectures.lecture', verbose_name='Zaregistrované události'),
+        ),
+    ]
diff --git a/users/migrations/0004_alter_user_rsvp_lectures.py b/users/migrations/0004_alter_user_rsvp_lectures.py
new file mode 100644
index 0000000..1124c13
--- /dev/null
+++ b/users/migrations/0004_alter_user_rsvp_lectures.py
@@ -0,0 +1,19 @@
+# Generated by Django 4.1.4 on 2023-04-18 07:08
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('lectures', '0001_initial'),
+        ('users', '0003_alter_user_rsvp_lectures'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='user',
+            name='rsvp_lectures',
+            field=models.ManyToManyField(blank=True, to='lectures.lecture', verbose_name='Zaregistrované lekce'),
+        ),
+    ]
diff --git a/users/migrations/0005_alter_user_email.py b/users/migrations/0005_alter_user_email.py
new file mode 100644
index 0000000..bd559b8
--- /dev/null
+++ b/users/migrations/0005_alter_user_email.py
@@ -0,0 +1,18 @@
+# Generated by Django 4.1.4 on 2023-04-18 07:33
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('users', '0004_alter_user_rsvp_lectures'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='user',
+            name='email',
+            field=models.EmailField(blank=True, max_length=254, null=True, verbose_name='E-mailová adresa'),
+        ),
+    ]
diff --git a/users/migrations/0006_user_sso_username.py b/users/migrations/0006_user_sso_username.py
new file mode 100644
index 0000000..f0a50e5
--- /dev/null
+++ b/users/migrations/0006_user_sso_username.py
@@ -0,0 +1,19 @@
+# Generated by Django 4.1.4 on 2023-04-18 08:32
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('users', '0005_alter_user_email'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='user',
+            name='sso_username',
+            field=models.CharField(default='', max_length=128, verbose_name='Username z SSO'),
+            preserve_default=False,
+        ),
+    ]
diff --git a/users/migrations/0007_alter_user_rsvp_lectures.py b/users/migrations/0007_alter_user_rsvp_lectures.py
new file mode 100644
index 0000000..c40dbef
--- /dev/null
+++ b/users/migrations/0007_alter_user_rsvp_lectures.py
@@ -0,0 +1,19 @@
+# Generated by Django 4.1.4 on 2023-04-18 09:54
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('lectures', '0005_lecturegroup'),
+        ('users', '0006_user_sso_username'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='user',
+            name='rsvp_lectures',
+            field=models.ManyToManyField(blank=True, related_name='rsvp_users', to='lectures.lecture', verbose_name='Zaregistrované lekce'),
+        ),
+    ]
diff --git a/users/models.py b/users/models.py
index f04e2e2..eca7b68 100644
--- a/users/models.py
+++ b/users/models.py
@@ -17,6 +17,24 @@ class Group:
 
 
 class User(pirates_models.AbstractUser):
+    sso_username = models.CharField(
+        max_length=128,
+        verbose_name="Username z SSO",
+    )
+
+    email = models.EmailField(
+        blank=True,
+        null=True,
+        verbose_name="E-mailová adresa",
+    )
+
+    rsvp_lectures = models.ManyToManyField(
+        "lectures.Lecture",
+        blank=True,
+        related_name="rsvp_users",
+        verbose_name="Zaregistrované lekce",
+    )
+
     def set_unusable_password(self) -> None:
         # Purely for compatibility with Guardian
         pass
-- 
GitLab