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

add rsvp option, redirect to auth when no access but resource exists

parent 737198f4
No related branches found
No related tags found
No related merge requests found
Pipeline #13065 passed
Showing
with 321 additions and 60 deletions
......@@ -4,16 +4,20 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('auth', '0012_alter_user_first_name_max_length'),
('lectures', '0015_alter_lecture_options_alter_lecture_type_and_more'),
("auth", "0012_alter_user_first_name_max_length"),
("lectures", "0015_alter_lecture_options_alter_lecture_type_and_more"),
]
operations = [
migrations.AlterField(
model_name='lecturegroup',
name='user_groups',
field=models.ManyToManyField(blank=True, help_text='Pokud žádné nedefinuješ, školení ve skupině jsou dostupné všem.', to='auth.group', verbose_name='Uživatelské skupiny'),
model_name="lecturegroup",
name="user_groups",
field=models.ManyToManyField(
blank=True,
help_text="Pokud žádné nedefinuješ, školení ve skupině jsou dostupné všem.",
to="auth.group",
verbose_name="Uživatelské skupiny",
),
),
]
......@@ -4,11 +4,9 @@ from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('lectures', '0016_alter_lecture_groups'),
('lectures', '0016_alter_lecturegroup_user_groups'),
("lectures", "0016_alter_lecture_groups"),
("lectures", "0016_alter_lecturegroup_user_groups"),
]
operations = [
]
operations = []
......@@ -4,20 +4,27 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('lectures', '0017_merge_20230526_0027'),
("lectures", "0017_merge_20230526_0027"),
]
operations = [
migrations.AlterModelOptions(
name='lecturegroup',
options={'ordering': ('priority', 'name'), 'verbose_name': 'Výuková skupina', 'verbose_name_plural': 'Výukové skupiny'},
name="lecturegroup",
options={
"ordering": ("priority", "name"),
"verbose_name": "Výuková skupina",
"verbose_name_plural": "Výukové skupiny",
},
),
migrations.AddField(
model_name='lecturegroup',
name='priority',
field=models.IntegerField(default=0, help_text='Čím nižší číslo, tím výš se skupina zobrazí.', verbose_name='Priorita'),
model_name="lecturegroup",
name="priority",
field=models.IntegerField(
default=0,
help_text="Čím nižší číslo, tím výš se skupina zobrazí.",
verbose_name="Priorita",
),
preserve_default=False,
),
]
# Generated by Django 4.1.4 on 2023-05-30 19:57
from django.db import migrations, models
import lectures.models
class Migration(migrations.Migration):
dependencies = [
("auth", "0012_alter_user_first_name_max_length"),
("lectures", "0018_alter_lecturegroup_options_lecturegroup_priority"),
]
operations = [
migrations.AlterField(
model_name="lecturegroup",
name="user_groups",
field=models.ManyToManyField(
blank=True,
help_text="Pokud žádné nedefinuješ, školení ve skupině jsou dostupná všem.",
to="auth.group",
verbose_name="Uživatelské skupiny",
),
),
migrations.AlterField(
model_name="lecturelector",
name="url",
field=models.URLField(
blank=True,
help_text='Běžně na <a href="https://lide.pirati.cz">aplikaci Lidé</a>.',
max_length=256,
null=True,
verbose_name="Odkaz",
),
),
migrations.AlterField(
model_name="lecturematerial",
name="file",
field=lectures.models.LectureMaterialFileField(
blank=True,
help_text="Pokud máš vložený soubor, nemůžeš definovat odkaz.",
null=True,
upload_to=lectures.models.get_lecture_material_file_location,
verbose_name="Soubor",
),
),
migrations.AlterField(
model_name="lecturerecording",
name="link",
field=models.URLField(
blank=True,
help_text='Běžně na <a href="https://tv.pirati.cz">Pirátskou TV</a>.',
max_length=256,
null=True,
verbose_name="Odkaz",
),
),
]
import mimetypes
import uuid
from datetime import datetime, timedelta
from django.contrib.auth.models import Group
......@@ -34,7 +33,7 @@ class LectureGroup(NameStrMixin, models.Model):
priority = models.IntegerField(
verbose_name="Priorita",
help_text="Čím nižší číslo, tím výš se skupina zobrazí."
help_text="Čím nižší číslo, tím výš se skupina zobrazí.",
)
user_groups = models.ManyToManyField(
......@@ -118,7 +117,9 @@ class LectureLector(NameStrMixin, models.Model):
blank=True,
null=True,
verbose_name="Odkaz",
help_text=mark_safe("Běžně na <a href=\"https://lide.pirati.cz\">aplikaci Lidé</a>.")
help_text=mark_safe(
'Běžně na <a href="https://lide.pirati.cz">aplikaci Lidé</a>.'
),
)
username = models.CharField(
......@@ -155,7 +156,9 @@ class LectureRecording(NameStrMixin, models.Model):
blank=True,
null=True,
verbose_name="Odkaz",
help_text=mark_safe("Běžně na <a href=\"https://tv.pirati.cz\">Pirátskou TV</a>.")
help_text=mark_safe(
'Běžně na <a href="https://tv.pirati.cz">Pirátskou TV</a>.'
),
)
class Meta:
......@@ -193,10 +196,7 @@ def get_lecture_material_file_location(instance, filename):
class LectureMaterialFileProxy(FieldFile):
@property
def url(self) -> str:
return reverse(
"lectures:download_material_file",
args=(str(self.instance.id),)
)
return reverse("lectures:download_material_file", args=(str(self.instance.id),))
class LectureMaterialFileField(models.FileField):
......
{% extends "shared/includes/base.html" %}
{% load markdownify %}
{% load render_bundle from webpack_loader %}
{% load markdownify rsvp %}
{% block content %}
{% render_bundle "view_lecture" %}
{% if related_group_id %}
<div class="flex gap-2 mb-10">
<i class="ico--chevron-left"></i>
......@@ -11,8 +14,30 @@
</div>
{% endif %}
<div class="flex justify-between items-start gap-3 flex-col md:flex-row">
{% include "shared/includes/double_heading.html" with heading=lecture.name subheading=lecture.get_type_display icon="ico--bookmark" %}
<button
id="set-rsvp"
class="font-alt text-lg bg-black text-white px-5 py-2 duration-100 hover:bg-white hover:text-black"
data-url="{% url "lectures:rsvp_lecture" lecture.id %}"
>
{% if not user|is_registered:lecture %}
Zaregistrovat se
{% else %}
Zrušit registraci
{% endif %}
</button>
</div>
<script>
window.isRegistered = {% if user|is_registered:lecture %}
true
{% else %}
false
{% endif %};
</script>
<div class="flex flex-col gap-2 my-4 py-4 border-y border-gray-200">
<div class="flex justify-between gap-2 text-lg text-gray-600">
<div class="flex gap-2 items-center">
......
import html
import markdownx
from django import template
register = template.Library()
@register.filter
def is_registered(user, notice) -> bool:
return user.get_lecture_registered(notice)
......@@ -13,5 +13,6 @@ urlpatterns = [
"lectures/materials/<str:pk>/file",
views.LectureMaterialFileDownloadView.as_view(),
name="download_material_file",
)
),
path("lectures/<int:lecture_id>/rsvp", views.rsvp_lecture, name="rsvp_lecture"),
]
......@@ -6,9 +6,11 @@ from itertools import chain
from django.conf import settings
from django.db import models
from django.http import HttpResponseRedirect, JsonResponse
from django.shortcuts import get_object_or_404, render
from django.urls import reverse
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
......@@ -28,10 +30,8 @@ class LectureMaterialFileDownloadView(ObjectDownloadView):
.filter(
lecture__groups__in=(
get_objects_for_user(
self.current_user,
"lectures.view_lecturegroup"
).
filter(
self.current_user, "lectures.view_lecturegroup"
).filter(
models.Q(user_groups__in=self.current_user.groups.all())
| models.Q(user_groups=None)
)
......@@ -39,14 +39,6 @@ class LectureMaterialFileDownloadView(ObjectDownloadView):
)
)
print(
queryset,
get_objects_for_user(
self.current_user,
"lectures.view_lecturegroup"
)
)
return queryset
def get(self, request, *args, **kwargs):
......@@ -61,13 +53,59 @@ def get_base_context(request) -> dict:
}
def generate_auth_redirect(request) -> HttpResponseRedirect:
return HttpResponseRedirect(
request.build_absolute_uri(reverse("oidc_authentication_init"))
+ "?next="
+ request.path
)
def get_lecture(request, id: int) -> tuple:
lecture = (
get_objects_for_user(request.user, "lectures.view_lecture")
.filter(id=id)
.first()
)
if lecture is None:
raise HTTPExceptions.NOT_FOUND
if not (
get_objects_for_user(request.user, "lectures.view_lecturegroup")
.filter(
models.Q(id__in=lecture.groups.all())
& (
(
models.Q(user_groups__in=request.user.groups.all())
| models.Q(user_groups=None)
)
if not request.user.is_superuser
else models.Q(id__isnull=False) # Always True
)
)
.distinct()
.exists()
): # User does not have access to related groups
if request.user.is_authenticated: # The user can log in
raise HTTPExceptions.NOT_FOUND
else: # They can log in
return False, generate_auth_redirect(request)
return True, lecture
def view_groups(request):
lecture_groups = (
get_objects_for_user(request.user, "lectures.view_lecturegroup")
.filter(
(
models.Q(user_groups__in=request.user.groups.all())
| models.Q(user_groups=None)
)
if not request.user.is_superuser
else models.Q(id__isnull=False) # Always True
)
.distinct()
.all()
)
......@@ -87,11 +125,29 @@ def view_groups(request):
def view_group_lectures(request, group_id: int):
group = get_object_or_404(
get_objects_for_user(request.user, "lectures.view_lecturegroup"),
id=group_id,
group = get_objects_for_user(request.user, "lectures.view_lecturegroup").filter(
id=group_id
)
group_id_exists = group.exists()
if not request.user.is_superuser:
group = group.filter(
models.Q(user_groups__in=request.user.groups.all())
| models.Q(user_groups=None)
)
if not group.exists():
if not group_id_exists: # Doesn't exist at all
raise HTTPExceptions.NOT_FOUND
elif group_id_exists: # Exists without permissions checks
if request.user_is_authenticated: # The user is logged in
raise HTTPExceptions.NOT_FOUND
else: # The user can log in
return generate_auth_redirect(request)
group = group.first()
timestamp_starting_separator = timezone.now() - Lecture.is_current_starting_treshold
timestamp_ending_separator = timezone.now() + Lecture.is_current_ending_treshold
......@@ -200,14 +256,10 @@ def view_group_lectures(request, group_id: int):
def view_lecture(request, lecture_id: int):
lecture = (
get_objects_for_user(request.user, "lectures.view_lecture")
.filter(id=lecture_id)
.first()
)
is_successful, lecture = get_lecture(request, lecture_id)
if lecture is None:
raise HTTPExceptions.NOT_FOUND
if not is_successful:
return lecture
related_group_id = request.GET.get("related_group_id")
......@@ -232,3 +284,27 @@ def view_lecture(request, lecture_id: int):
"related_group_id": related_group_id,
},
)
@require_POST
def rsvp_lecture(request, lecture_id: int):
is_successful, lecture = get_lecture(request, lecture_id)
if not is_successful:
return lecture
is_registered = request.POST.get("register", "false") == "true"
if is_registered is request.user.get_lecture_registered(
lecture
): # The RSVP state is the same
return JsonResponse({"success": False})
if is_registered:
request.user.rsvp_lectures.add(lecture)
else:
request.user.rsvp_lectures.remove(lecture)
request.user.save()
return JsonResponse({"success": True})
......@@ -11,8 +11,10 @@
"dependencies": {
"@fullcalendar/core": "^6.1.6",
"@tailwindcss/typography": "^0.5.9",
"alertifyjs": "^1.13.1",
"css-loader": "^6.7.3",
"jquery": "^3.6.3",
"js-cookie": "^3.0.5",
"style-loader": "^3.3.1",
"tailwindcss": "^3.2.4",
"tippy.js": "^6.3.7",
......@@ -428,6 +430,11 @@
"ajv": "^6.9.1"
}
},
"node_modules/alertifyjs": {
"version": "1.13.1",
"resolved": "https://registry.npmjs.org/alertifyjs/-/alertifyjs-1.13.1.tgz",
"integrity": "sha512-CckZE2dZDsEEXglOXKxT00vUDV5A6udZom+bn1XHdIWlbSFZgYq7UXCBlwkShhIH3Li/1VxLmr55GOQFQ12WSg=="
},
"node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
......@@ -1090,6 +1097,14 @@
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.4.tgz",
"integrity": "sha512-v28EW9DWDFpzcD9O5iyJXg3R3+q+mET5JhnjJzQUZMHOv67bpSIHq81GEYpPNZHG+XXHsfSme3nxp/hndKEcsQ=="
},
"node_modules/js-cookie": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz",
"integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==",
"engines": {
"node": ">=14"
}
},
"node_modules/json-parse-even-better-errors": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
......
......@@ -14,8 +14,10 @@
"dependencies": {
"@fullcalendar/core": "^6.1.6",
"@tailwindcss/typography": "^0.5.9",
"alertifyjs": "^1.13.1",
"css-loader": "^6.7.3",
"jquery": "^3.6.3",
"js-cookie": "^3.0.5",
"style-loader": "^3.3.1",
"tailwindcss": "^3.2.4",
"tippy.js": "^6.3.7",
......
import $ from "jquery";
import alertify from "alertifyjs";
import "alertifyjs/build/css/alertify.css";
import Cookies from "js-cookie";
$(window).ready(
() => {
$("#set-rsvp").on(
"click",
async (event) => {
const body = new FormData();
body.append("register", (!window.isRegistered).toString());
let response = await fetch(
event.currentTarget.dataset.url,
{
method: "POST",
credentials: "include",
headers: {
"X-CSRFToken": Cookies.get("csrftoken")
},
body: body
}
);
try {
response = await response.json();
} catch (e) {
alertify.error("Chyba při registraci - odpověď serveru.");
return;
}
if (response["success"]) {
window.isRegistered = !window.isRegistered;
if (window.isRegistered) {
event.currentTarget.innerHTML = "Zrušit registraci";
alertify.success("Školení zaregistrováno.");
} else {
event.currentTarget.innerHTML = "Zaregistrovat se";
alertify.success("Registrace zrušena.");
}
} else {
alertify.error("Chyba při registraci - odmítnuta.");
}
}
);
}
);
......@@ -4,15 +4,16 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('users', '0008_alter_user_rsvp_lectures'),
("users", "0008_alter_user_rsvp_lectures"),
]
operations = [
migrations.AlterField(
model_name='user',
name='sso_username',
field=models.CharField(blank=True, max_length=128, null=True, verbose_name='Username z SSO'),
model_name="user",
name="sso_username",
field=models.CharField(
blank=True, max_length=128, null=True, verbose_name="Username z SSO"
),
),
]
......@@ -49,6 +49,13 @@ class User(pirates_models.AbstractUser):
return f"{first_name}{self.last_name}"
def get_lecture_registered(self, lecture) -> bool:
from lectures.models import Lecture
return Lecture.objects.filter(
id=lecture.id, id__in=self.rsvp_lectures.all()
).exists()
class Meta:
app_label = "users"
verbose_name = "Uživatel"
......
......@@ -13,6 +13,10 @@ module.exports = {
import: path.resolve("static_src", "view_group_lectures.js"),
dependOn: "shared",
},
view_lecture: {
import: path.resolve("static_src", "view_lecture.js"),
dependOn: "shared",
},
shared: ["jquery"],
},
output: {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment