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

finish data structure, make views

parent cafece12
Branches
Tags
No related merge requests found
Showing
with 685 additions and 24 deletions
...@@ -6,7 +6,7 @@ import sys ...@@ -6,7 +6,7 @@ import sys
def main(): def main():
"""Run administrative tasks.""" """Run administrative tasks."""
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'ucebnice.settings') os.environ.setdefault("DJANGO_SETTINGS_MODULE", "ucebnice.settings")
try: try:
from django.core.management import execute_from_command_line from django.core.management import execute_from_command_line
except ImportError as exc: except ImportError as exc:
...@@ -18,5 +18,5 @@ def main(): ...@@ -18,5 +18,5 @@ def main():
execute_from_command_line(sys.argv) execute_from_command_line(sys.argv)
if __name__ == '__main__': if __name__ == "__main__":
main() main()
media/364D1F51-B67A-4FA5-83E6-1F32D62B6A78.gif

18.8 KiB

...@@ -4,3 +4,4 @@ from django.apps import AppConfig ...@@ -4,3 +4,4 @@ from django.apps import AppConfig
class OidcConfig(AppConfig): class OidcConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField" default_auto_field = "django.db.models.BigAutoField"
name = "oidc" name = "oidc"
verbose_name = "OpenID Přihlašování"
...@@ -53,6 +53,8 @@ class UcebniceOIDCAuthenticationBackend(PiratesOIDCAuthenticationBackend): ...@@ -53,6 +53,8 @@ class UcebniceOIDCAuthenticationBackend(PiratesOIDCAuthenticationBackend):
access_token, options={"verify_signature": False} 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() user_groups = user.groups.all()
self._remove_old_user_groups( self._remove_old_user_groups(
......
shared/static/shared/favicon.png

9.55 KiB

...@@ -520,18 +520,173 @@ html { ...@@ -520,18 +520,173 @@ html {
--tw-backdrop-sepia: ; --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 { .static {
position: 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 { .block {
display: block; display: block;
} }
.inline-block {
display: inline-block;
}
.flex { .flex {
display: 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 {
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, -webkit-backdrop-filter;
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, 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 { ...@@ -539,3 +694,111 @@ html {
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms; 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));
}
}
{% 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>
...@@ -139,7 +139,7 @@ LOGOUT_REDIRECT_URL = "/" ...@@ -139,7 +139,7 @@ LOGOUT_REDIRECT_URL = "/"
OIDC_RP_CLIENT_ID = env.str("OIDC_RP_CLIENT_ID") OIDC_RP_CLIENT_ID = env.str("OIDC_RP_CLIENT_ID")
OIDC_RP_CLIENT_SECRET = env.str("OIDC_RP_CLIENT_SECRET") OIDC_RP_CLIENT_SECRET = env.str("OIDC_RP_CLIENT_SECRET")
OIDC_RP_REALM_URL = env.str("OIDC_RP_REALM_URL") 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_SIGN_ALGO = "RS256"
OIDC_RP_RESOURCE_ACCESS_CLIENT = env.str( OIDC_RP_RESOURCE_ACCESS_CLIENT = env.str(
"OIDC_RESOURCE_ACCESS_CLIENT", OIDC_RP_CLIENT_ID "OIDC_RESOURCE_ACCESS_CLIENT", OIDC_RP_CLIENT_ID
......
...@@ -24,5 +24,7 @@ from pirates.urls import urlpatterns as pirates_urlpatterns ...@@ -24,5 +24,7 @@ from pirates.urls import urlpatterns as pirates_urlpatterns
import ucebnice.admin import ucebnice.admin
urlpatterns = [ urlpatterns = [
path("", include("lectures.urls")),
path("markdownx/", include("markdownx.urls")),
path("admin/", admin.site.urls), path("admin/", admin.site.urls),
] + pirates_urlpatterns ] + pirates_urlpatterns
...@@ -4,4 +4,9 @@ from shared.admin import MarkdownxGuardedModelAdmin ...@@ -4,4 +4,9 @@ from shared.admin import MarkdownxGuardedModelAdmin
from .models import User from .models import User
admin.site.register(User, MarkdownxGuardedModelAdmin)
class UserAdmin(MarkdownxGuardedModelAdmin):
autocomplete_fields = ("rsvp_lectures",)
admin.site.register(User, UserAdmin)
# Generated by Django 4.1.4 on 2023-04-14 18:14 # Generated by Django 4.1.4 on 2023-04-14 18:14
from django.db import migrations, models
import django.utils.timezone import django.utils.timezone
from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
initial = True initial = True
dependencies = [ dependencies = [
('auth', '0012_alter_user_first_name_max_length'), ("auth", "0012_alter_user_first_name_max_length"),
] ]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='User', name="User",
fields=[ 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')), "id",
('sso_id', models.CharField(error_messages={'unique': 'A user with that SSO ID already exists.'}, max_length=150, unique=True, verbose_name='SSO ID')), models.BigAutoField(
('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')), auto_created=True,
('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), primary_key=True,
('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')), serialize=False,
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), verbose_name="ID",
('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')), "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={ options={
'verbose_name': 'Uživatel', "verbose_name": "Uživatel",
'verbose_name_plural': 'Uživatelé', "verbose_name_plural": "Uživatelé",
}, },
), ),
] ]
# 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'),
),
]
# 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'),
),
]
# 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'),
),
]
# 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'),
),
]
# 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,
),
]
# 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'),
),
]
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment