diff --git a/base/locale/de_DE/LC_MESSAGES/django.po b/base/locale/de_DE/LC_MESSAGES/django.po index a958af6c..91b3fd82 100644 --- a/base/locale/de_DE/LC_MESSAGES/django.po +++ b/base/locale/de_DE/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2021-07-15 11:01+0000\n" +"POT-Creation-Date: 2021-07-20 14:28+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -201,6 +201,10 @@ msgstr "Profilbild" msgid "Stared courses:" msgstr "Favorisierte Kurse" +#: base/models/profile.py:47 +msgid "Accepted privacy note?" +msgstr "Datenschutzerklärung akzeptiert?" + #: base/models/social.py:36 msgid "Rated content" msgstr "Bewerteter Inhalt" diff --git a/base/migrations/0017_privacy_accepted.py b/base/migrations/0017_privacy_accepted.py new file mode 100644 index 00000000..e894ed32 --- /dev/null +++ b/base/migrations/0017_privacy_accepted.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.5 on 2021-07-20 12:03 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('base', '0016_auto_20210302_2352'), + ] + + operations = [ + migrations.AddField( + model_name='profile', + name='accepted_privacy_note', + field=models.BooleanField(blank=True, default=False, verbose_name='Accepted privacy note?'), + ), + ] diff --git a/base/migrations/0018_stared_courses_fix.py b/base/migrations/0018_stared_courses_fix.py new file mode 100644 index 00000000..5c821891 --- /dev/null +++ b/base/migrations/0018_stared_courses_fix.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.5 on 2021-07-20 14:12 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('base', '0017_privacy_accepted'), + ] + + operations = [ + migrations.AlterField( + model_name='profile', + name='stared_courses', + field=models.ManyToManyField(blank=True, related_name='staring_users', to='base.Course', verbose_name='Stared courses:'), + ), + ] diff --git a/base/models/profile.py b/base/models/profile.py index 472de761..f06f7a9a 100644 --- a/base/models/profile.py +++ b/base/models/profile.py @@ -43,7 +43,8 @@ class Meta: bio = models.TextField(verbose_name=_("Biography"), blank=True) pic = models.ImageField(verbose_name=_("Profile picture"), upload_to="profile_pics", blank=True) stared_courses = models.ManyToManyField("Course", verbose_name=_("Stared courses:"), - related_name="staring_users") + related_name="staring_users", blank=True) + accepted_privacy_note = models.BooleanField(verbose_name=_("Accepted privacy note?"), blank=True, default=False) def __str__(self): """String representation diff --git a/collab_coursebook/settings.py b/collab_coursebook/settings.py index c141e9aa..2ec3fb48 100644 --- a/collab_coursebook/settings.py +++ b/collab_coursebook/settings.py @@ -12,7 +12,9 @@ import os +from django.urls import reverse_lazy from django.utils.translation import gettext_lazy as _ +from split_settings.tools import optional, include # Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) @@ -126,6 +128,7 @@ 'givenName': 'first_name', 'mail': 'email', } +CAS_REDIRECT_URL = reverse_lazy("frontend:privacy_accept") # Internationalization # https://docs.djangoproject.com/en/3.0/topics/i18n/ @@ -176,7 +179,8 @@ FOOTER_INFO = { "repo_url": "https://github.com/DataManagementLab/collab-coursebook", "impress_text": "", - "impress_url": "" + "impress_url": "", + "privacy_note_url": "", } ALLOW_PUBLIC_COURSE_EDITING_BY_EVERYONE = True @@ -186,3 +190,7 @@ # optional settings: REVERSION_COMPARE_FOREIGN_OBJECTS_AS_ID = False REVERSION_COMPARE_IGNORE_NOT_REGISTERED = False + +DATA_PROTECTION_REQURE_CONFIRMATION = False + +include(optional("settings/*.py")) diff --git a/frontend/forms/__init__.py b/frontend/forms/__init__.py index 24910f64..6451f07d 100644 --- a/frontend/forms/__init__.py +++ b/frontend/forms/__init__.py @@ -9,3 +9,5 @@ from .content import AddContentForm, EditContentForm, TranslateForm from .comment import CommentForm + +from .page import AcceptPrivacyNoteForm diff --git a/frontend/forms/page.py b/frontend/forms/page.py new file mode 100644 index 00000000..a43e28a7 --- /dev/null +++ b/frontend/forms/page.py @@ -0,0 +1,7 @@ +from django import forms +from django.utils.translation import gettext_lazy as _ + + +class AcceptPrivacyNoteForm(forms.Form): + accept = forms.BooleanField(label=_("I accept the privacy note"), + help_text=_("(required to use this website)")) diff --git a/frontend/locale/de_DE/LC_MESSAGES/django.po b/frontend/locale/de_DE/LC_MESSAGES/django.po index 4cacf488..2c657e68 100644 --- a/frontend/locale/de_DE/LC_MESSAGES/django.po +++ b/frontend/locale/de_DE/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2021-07-15 11:01+0000\n" +"POT-Creation-Date: 2021-07-20 14:28+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -51,6 +51,14 @@ msgstr "Änderungsprotokoll" msgid "Please write down your changes of this content" msgstr "Bitte notieren Sie Ihre Änderungen an diesem Inhalt" +#: frontend/forms/page.py:6 +msgid "I accept the privacy note" +msgstr "Ich akzeptiere die Datenschutzerklärung" + +#: frontend/forms/page.py:7 +msgid "(required to use this website)" +msgstr "(notwendig zur Nutzung dieser Webseite)" + #: frontend/templates/frontend/base.html:34 msgid "" "Are you sure you want to change the language now? This will clear the form!" @@ -63,6 +71,10 @@ msgid "Impress" msgstr "Impressum" #: frontend/templates/frontend/base.html:71 +msgid "Privacy Note" +msgstr "Datenschutzerklärung" + +#: frontend/templates/frontend/base.html:76 msgid "This software is open source" msgstr "Diese Software ist Open Source" @@ -214,7 +226,7 @@ msgstr "Lesemodus" #: frontend/templates/frontend/content/detail.html:40 #: frontend/templates/frontend/content/detail.html:45 -#: frontend/templates/frontend/course/coursebook.html:21 frontend/urls.py:69 +#: frontend/templates/frontend/course/coursebook.html:21 frontend/urls.py:75 msgid "Coursebook" msgstr "Kursbuch" @@ -532,6 +544,24 @@ msgstr "Alle Kurse für den Zeitraum" msgid "All Courses by Category" msgstr "Alle Kurse für die Kategorie" +#: frontend/templates/frontend/data_protection.html:11 +#: frontend/templates/frontend/data_protection_accept.html:11 +#: frontend/templates/frontend/data_protection_accept.html:22 +msgid "Data Protection" +msgstr "Datenschutz" + +#: frontend/templates/frontend/data_protection_accept.html:24 +msgid "" +"Please review and accept the following data privacy note to continue using " +"Collab Coursebook" +msgstr "" +"Bitte die folgende Datenschutzerklärung lesen und akzeptieren, um Collab " +"Coursebook zu verwenden" + +#: frontend/templates/frontend/data_protection_accept.html:37 +msgid "Continue" +msgstr "Fortfahren" + #: frontend/templates/frontend/history/history.html:16 #: frontend/templates/frontend/history/history.html:24 #: frontend/templates/frontend/tutorial/history.html:8 diff --git a/frontend/templates/frontend/base.html b/frontend/templates/frontend/base.html index c2938062..e4410a22 100644 --- a/frontend/templates/frontend/base.html +++ b/frontend/templates/frontend/base.html @@ -66,6 +66,11 @@ {% trans "Impress" %} · {% endif %} + {% if FI.privacy_note_url %} + + {% trans "Privacy Note" %} + · + {% endif %} {% if FI.repo_url %} {% trans 'This software is open source' %} diff --git a/frontend/templates/frontend/base_logged_in.html b/frontend/templates/frontend/base_logged_in.html index 6d3857f0..65a49397 100644 --- a/frontend/templates/frontend/base_logged_in.html +++ b/frontend/templates/frontend/base_logged_in.html @@ -4,7 +4,9 @@
{# Navigation bar #}
- {% include 'frontend/navbar.html' %} + {% block navbar %} + {% include 'frontend/navbar.html' %} + {% endblock %}
{# Content container #} diff --git a/frontend/templates/frontend/data_protection.html b/frontend/templates/frontend/data_protection.html new file mode 100644 index 00000000..17b50544 --- /dev/null +++ b/frontend/templates/frontend/data_protection.html @@ -0,0 +1,16 @@ +{% extends 'frontend/base_logged_in.html' %} + +{# Load the tag library #} +{% load static %} +{% load i18n %} +{% load bootstrap4 %} +{% load fontawesome_5 %} +{% load cc_frontend_tags %} + +{% block title %} + {% trans 'Data Protection' %} - Collab Coursebook +{% endblock %} + +{% block content %} + {% include "frontend/data_protection_text.html" %} +{% endblock %} diff --git a/frontend/templates/frontend/data_protection_accept.html b/frontend/templates/frontend/data_protection_accept.html new file mode 100644 index 00000000..55bfe9b3 --- /dev/null +++ b/frontend/templates/frontend/data_protection_accept.html @@ -0,0 +1,41 @@ +{% extends 'frontend/base_logged_in.html' %} + +{# Load the tag library #} +{% load static %} +{% load i18n %} +{% load bootstrap4 %} +{% load fontawesome_5 %} +{% load cc_frontend_tags %} + +{% block title %} + {% trans 'Data Protection' %} - Collab Coursebook +{% endblock %} + +{% block navbar %} + {# Empty navbar, privacy note has to be accepted first #} +

+ Collab Coursebook +

+{% endblock %} + +{% block content %} +

{% trans 'Data Protection' %}

+ +

{% trans 'Please review and accept the following data privacy note to continue using Collab Coursebook' %}

+ +
+ {% include "frontend/data_protection_text.html" %} +
+ +
+
+ {% csrf_token %} + {% bootstrap_form form %} + + +
+
+{% endblock %} diff --git a/frontend/urls.py b/frontend/urls.py index a8178006..f78919ad 100644 --- a/frontend/urls.py +++ b/frontend/urls.py @@ -29,6 +29,12 @@ path('tutorial/', views.TutorialView.as_view(), name='tutorial'), + path('privacy/', + views.PrivacyNoteView.as_view(), + name='privacy'), + path('privacy-accept/', + views.AcceptPrivacyNoteView.as_view(), + name='privacy_accept'), path('profile//', include([ path('', views.ProfileView.as_view(), diff --git a/frontend/views/__init__.py b/frontend/views/__init__.py index 188bd8c6..007fdfea 100644 --- a/frontend/views/__init__.py +++ b/frontend/views/__init__.py @@ -17,7 +17,7 @@ from .history import CourseHistoryCompareView, PdfHistoryCompareView, ImageHistoryCompareView from .history import LatexHistoryCompareView, YTVideoHistoryCompareView -from .page import StartView, DashboardView, TutorialView +from .page import StartView, DashboardView, TutorialView, PrivacyNoteView, AcceptPrivacyNoteView from .profile import ProfileView, ProfileEditView diff --git a/frontend/views/page.py b/frontend/views/page.py index 6ce17c4e..b51eaaf2 100644 --- a/frontend/views/page.py +++ b/frontend/views/page.py @@ -4,9 +4,13 @@ """ from django.contrib.auth.mixins import LoginRequiredMixin -from django.views.generic import TemplateView +from django.shortcuts import redirect +from django.urls import reverse_lazy +from django.views.generic import TemplateView, FormView from base.models import Period, Category +from collab_coursebook.settings import DATA_PROTECTION_REQURE_CONFIRMATION +from frontend.forms import AcceptPrivacyNoteForm class StartView(TemplateView): @@ -20,6 +24,11 @@ class StartView(TemplateView): """ template_name = "frontend/index.html" + def dispatch(self, request, *args, **kwargs): + if request.user.is_authenticated: + return redirect("frontend:dashboard") + return super().dispatch(request, *args, **kwargs) + class DashboardView(LoginRequiredMixin, TemplateView): """Dashboard view @@ -31,6 +40,13 @@ class DashboardView(LoginRequiredMixin, TemplateView): """ template_name = "frontend/dashboard.html" + def dispatch(self, request, *args, **kwargs): + if DATA_PROTECTION_REQURE_CONFIRMATION \ + and request.user.is_authenticated \ + and not request.user.profile.accepted_privacy_note: + return redirect(reverse_lazy("frontend:privacy_accept")) + return super().dispatch(request, *args, **kwargs) + def get_context_data(self, **kwargs): """Context data @@ -59,3 +75,34 @@ class TutorialView(TemplateView): :type TutorialView.template_name: str """ template_name = "frontend/tutorial.html" + + +class PrivacyNoteView(TemplateView): + """ + View to access privacy note + """ + template_name = "frontend/data_protection.html" + + +class AcceptPrivacyNoteView(FormView): + """ + View to review and accept privacy note + """ + template_name = "frontend/data_protection_accept.html" + form_class = AcceptPrivacyNoteForm + success_url = reverse_lazy("frontend:dashboard") + + def form_valid(self, form): + profile = self.request.user.profile + profile.accepted_privacy_note = True + profile.save() + return super().form_valid(form) + + def dispatch(self, request, *args, **kwargs): + if request.user.is_authenticated \ + and ( + not DATA_PROTECTION_REQURE_CONFIRMATION + or request.user.profile.accepted_privacy_note + ): + return redirect(reverse_lazy("frontend:dashboard")) + return super().dispatch(request, *args, **kwargs) diff --git a/requirements.txt b/requirements.txt index 02e3e630..c8a3f3ec 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,13 +1,14 @@ Django==3.2.5 -pdf2image==1.16.0 django-bootstrap4==3.0.1 -django-fontawesome-5==1.0.18 -django-timezone-field==4.2.1 django-cas-ng==4.2.1 -django-imagekit==4.0.2 -pillow==8.3.1 django-debug-toolbar==3.2.1 -python-magic==0.4.24 +django-fontawesome-5==1.0.18 +django-imagekit==4.0.2 django-reversion==4.0.0 -django-reversion-compare==0.14.0 +django-reversion-compare==0.14.1 +django-split-settings==1.0.1 +django-timezone-field==4.2.1 +pdf2image==1.16.0 +pillow==8.3.1 polib==1.1.1 +python-magic==0.4.24 diff --git a/utils/cron.sh b/utils/cron.sh new file mode 100644 index 00000000..27670a0a --- /dev/null +++ b/utils/cron.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +# Regular maintenance tasks for Collab Coursebook +# execute as utils/cron.sh + +# abort on error, print executed commands +set -ex + +# activate virtualenv if necessary +if [ -z ${VIRTUAL_ENV+x} ]; then + source venv/bin/activate +fi + +# set environment variable when we want to update in production +if [ "$1" = "--prod" ]; then + export DJANGO_SETTINGS_MODULE=collab_coursebook.settings_production +fi + +./manage.py clearsessions +./manage.py django_cas_ng_clean_sessions