diff --git a/smash/web/forms/privacy_notice.py b/smash/web/forms/privacy_notice.py new file mode 100644 index 0000000000000000000000000000000000000000..5c015dd268a417c0beb802c852968fb39bd73a06 --- /dev/null +++ b/smash/web/forms/privacy_notice.py @@ -0,0 +1,15 @@ +from django import forms +from django.forms import ModelForm +from web.models import PrivacyNotice + +import logging +logger = logging.getLogger(__name__) + +class PrivacyNoticeForm(ModelForm): + class Meta: + model = PrivacyNotice + fields = '__all__' + + def clean(self): + cleaned_data = super(PrivacyNoticeForm, self).clean() + return cleaned_data \ No newline at end of file diff --git a/smash/web/migrations/0177_auto_20201116_1508.py b/smash/web/migrations/0177_auto_20201116_1508.py new file mode 100644 index 0000000000000000000000000000000000000000..969d1db84635e6c75d2604ae95110eea53085dd4 --- /dev/null +++ b/smash/web/migrations/0177_auto_20201116_1508.py @@ -0,0 +1,29 @@ +# Generated by Django 2.0.13 on 2020-11-16 15:08 + +import django.core.files.storage +import django.core.validators +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('web', '0176_configurationitem_local_setting_clean'), + ] + + operations = [ + migrations.CreateModel( + name='PrivacyNotice', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('document', models.FileField(editable=False, upload_to='privacy_notices/', verbose_name='Study Privacy Notice file')), + ], + ), + migrations.AddField( + model_name='study', + name='study_privacy_notice', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='studies', to='web.PrivacyNotice', verbose_name='Study Privacy Note'), + ) + ] diff --git a/smash/web/migrations/0178_auto_20201116_1513.py b/smash/web/migrations/0178_auto_20201116_1513.py new file mode 100644 index 0000000000000000000000000000000000000000..c3c4c4cc683cd3adc24cb7dbcbdf5d9421d8e3d5 --- /dev/null +++ b/smash/web/migrations/0178_auto_20201116_1513.py @@ -0,0 +1,21 @@ +# Generated by Django 2.0.13 on 2020-11-16 15:13 + +import django.core.files.storage +import django.core.validators +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('web', '0177_auto_20201116_1508'), + ] + + operations = [ + migrations.AddField( + model_name='privacynotice', + name='study', + field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, to='web.Study', verbose_name='Study'), + ) + ] diff --git a/smash/web/migrations/0179_auto_20201116_1528.py b/smash/web/migrations/0179_auto_20201116_1528.py new file mode 100644 index 0000000000000000000000000000000000000000..da1ab045b22d246df336c1123b8be7f21ade2382 --- /dev/null +++ b/smash/web/migrations/0179_auto_20201116_1528.py @@ -0,0 +1,21 @@ +# Generated by Django 2.0.13 on 2020-11-16 15:28 + +import django.core.files.storage +import django.core.validators +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('web', '0178_auto_20201116_1513'), + ] + + operations = [ + migrations.AddField( + model_name='privacynotice', + name='updated_at', + field=models.DateTimeField(auto_now=True), + ) + ] diff --git a/smash/web/models/__init__.py b/smash/web/models/__init__.py index 9c193eaa042b0662683c6f399f21b5053222e89d..46f20636c434b587773fe96e454b017f2248cb14 100644 --- a/smash/web/models/__init__.py +++ b/smash/web/models/__init__.py @@ -38,6 +38,7 @@ from .contact_attempt import ContactAttempt from .mail_template import MailTemplate from .missing_subject import MissingSubject from .inconsistent_subject import InconsistentSubject, InconsistentField +from .privacy_notice import PrivacyNotice __all__ = [Study, FlyingTeam, Appointment, AppointmentType, Availability, Holiday, Item, Language, Location, Room, Subject, StudySubject, StudySubjectList, SubjectColumns, StudyNotificationParameters, diff --git a/smash/web/models/study.py b/smash/web/models/study.py index 2ff035ab10562e70f8ca58f223310c316efdd5b8..f672a5e1df67ffcd02c31a9f77fb9611ae9e3033 100644 --- a/smash/web/models/study.py +++ b/smash/web/models/study.py @@ -94,6 +94,13 @@ class Study(models.Model): blank=False ) + study_privacy_notice = models.ForeignKey("web.PrivacyNotice", + verbose_name='Study Privacy Note', + editable=True, + null=True, on_delete=models.CASCADE, #check cascade ondelete + related_name='studies' + ) + def check_nd_number(self, nd_number): regex = re.compile(self.nd_number_study_subject_regex) return regex.match(nd_number) is not None diff --git a/smash/web/templates/privacy_notice/add.html b/smash/web/templates/privacy_notice/add.html new file mode 100644 index 0000000000000000000000000000000000000000..6d4f87bf936db78ad7926b81338120374ef97eca --- /dev/null +++ b/smash/web/templates/privacy_notice/add.html @@ -0,0 +1,8 @@ +{% extends "privacy_notice/add_edit.html" %} + +{% block page_header %}New privacy notice{% endblock page_header %} + +{% block title %}{{ block.super }} - Add new privacy notice{% endblock %} + +{% block form-title %}Enter privacy notice details{% endblock %} +{% block save-button %}Add{% endblock %} \ No newline at end of file diff --git a/smash/web/templates/privacy_notice/add_edit.html b/smash/web/templates/privacy_notice/add_edit.html new file mode 100644 index 0000000000000000000000000000000000000000..17ad93d68448db002bc8e4c813749e38dacabfce --- /dev/null +++ b/smash/web/templates/privacy_notice/add_edit.html @@ -0,0 +1,83 @@ +{% extends "_base.html" %} +{% load static %} +{% load filters %} + +{% block styles %} + {{ block.super }} + <link rel="stylesheet" href="{% static 'AdminLTE/plugins/awesomplete/awesomplete.css' %}"/> + <style type="text/css"> + .help_text{ + margin-left: 5px; + } + </style> +{% endblock styles %} + +{% block ui_active_tab %}'subjects'{% endblock ui_active_tab %} +{% block page_description %}{% endblock page_description %} + +{% block breadcrumb %} + {% include "privacy_notice/breadcrumb.html" %} +{% endblock breadcrumb %} + +{% block maincontent %} + + {% block content %} + <div class="row"> + <div class="col-md-12"> + <div class="box box-success"> + <div class="box-header with-border"> + <h3 class="box-title">{% block form-title %}Enter privacy notice details{% endblock %}</h3> + </div> + + + <form method="post" action="" class="form-horizontal" enctype="multipart/form-data"> + {% csrf_token %} + + <div class="box-body"> + {% for field in form %} + <div class="form-group {% if field.errors %}has-error{% endif %}"> + <label class="col-sm-4 col-lg-offset-1 col-lg-2 control-label"> + {{ field.label }} + {% if field.help_text %} + <i class="fa fa-info-circle help_text" aria-hidden="true" data-toggle="tooltip" data-placement="top" title="{{field.help_text}}"></i> + {% endif %} + </label> + + <div class="col-sm-8 col-lg-4"> + {{ field|add_class:'form-control' }} + {% if field.errors %} + <span class="help-block">{{ field.errors }}</span> + {% endif %} + </div> + + + </div> + {% endfor %} + </div><!-- /.box-body --> + <div class="box-footer"> + <div class="col-sm-6"> + <button type="submit" class="btn btn-block btn-success">{% block save-button %} + Add{% endblock %}</button> + </div> + <div class="col-sm-6"> + <a href="{% url 'web.views.privacy_notices' %}" + class="btn btn-block btn-default">Cancel</a> + </div> + </div><!-- /.box-footer --> + </form> + </div> + + </div> + </div> + + {% endblock %} + + +{% endblock maincontent %} + +{% block scripts %} + {{ block.super }} + + <script src="{% static 'AdminLTE/plugins/awesomplete/awesomplete.min.js' %}"></script> + +{% endblock scripts %} \ No newline at end of file diff --git a/smash/web/templates/privacy_notice/breadcrumb.html b/smash/web/templates/privacy_notice/breadcrumb.html new file mode 100644 index 0000000000000000000000000000000000000000..3a3bdd23e7829eec33bf65d789fd90b566940f2b --- /dev/null +++ b/smash/web/templates/privacy_notice/breadcrumb.html @@ -0,0 +1,2 @@ +<li><a href="{% url 'web.views.appointments' %}"><i class="fa fa-dashboard"></i> Dashboard</a></li> +<li class="active"><a href="{% url 'web.views.privacy_notices' %}">Privacy Notices</a></li> \ No newline at end of file diff --git a/smash/web/templates/privacy_notice/confirm_delete.html b/smash/web/templates/privacy_notice/confirm_delete.html new file mode 100644 index 0000000000000000000000000000000000000000..8e618e11025dea0fff578d6dca8546038dadf85f --- /dev/null +++ b/smash/web/templates/privacy_notice/confirm_delete.html @@ -0,0 +1,62 @@ +{% extends "_base.html" %} +{% load static %} +{% load filters %} + +{% block styles %} + {{ block.super }} + <link rel="stylesheet" href="{% static 'AdminLTE/plugins/awesomplete/awesomplete.css' %}"/> + +{% endblock styles %} + +{% block ui_active_tab %}'subjects'{% endblock ui_active_tab %} +{% block page_header %}Delete privacy notice{% endblock page_header %} +{% block page_description %}{% endblock page_description %} + +{% block title %}{{ block.super }} - Delete privacy notice{% endblock %} + +{% block breadcrumb %} + {% include "privacy_notice/breadcrumb.html" %} +{% endblock breadcrumb %} + +{% block maincontent %} + + {% block content %} + <div class="row"> + <div class="col-md-12"> + <div class="box box-success"> + <div class="box-header with-border"> + <h3 class="box-title">Confirm deletion</h3> + </div> + + <form action="" method="post" class="form-horizontal">{% csrf_token %} + <div class="box-body"> + <p>Are you sure you want to delete privacy notice "{{ object.document.url |Â basename }}" from Study "{{object.study}}"?</p> + </div><!-- /.box-body --> + <div class="box-footer"> + <div class="col-sm-6"> + <button type="submit" class="btn btn-block btn-danger">Delete</button> + </div> + <div class="col-sm-6"> + <a href="{% url 'web.views.privacy_notices' %}" + class="btn btn-block btn-default">Cancel</a> + </div> + </div><!-- /.box-footer --> + </form> + </div> + + </div> + </div> + + {% endblock %} + + +{% endblock maincontent %} + +{% block scripts %} + {{ block.super }} + + <script src="{% static 'AdminLTE/plugins/awesomplete/awesomplete.min.js' %}"></script> + +{% endblock scripts %} + + diff --git a/smash/web/templates/privacy_notice/edit.html b/smash/web/templates/privacy_notice/edit.html new file mode 100644 index 0000000000000000000000000000000000000000..f1f0d651f3d94b4c6f27a7fcea64d0d4f86dcaf1 --- /dev/null +++ b/smash/web/templates/privacy_notice/edit.html @@ -0,0 +1,9 @@ +{% extends "privacy_notice/add_edit.html" %} + +{% block page_header %}Edit privacy notice "{{ privacy_notice.name }}"{% endblock page_header %} + +{% block title %}{{ block.super }} - Edit privacy notice "{{ privacy_notice.name }}"{% endblock %} + +{% block form-title %}Enter privacy notice details{% endblock %} + +{% block save-button %}Save{% endblock %} diff --git a/smash/web/templates/privacy_notice/list.html b/smash/web/templates/privacy_notice/list.html new file mode 100644 index 0000000000000000000000000000000000000000..f42a803ce373ac0f6437bd41cbbabc5a3ffad79a --- /dev/null +++ b/smash/web/templates/privacy_notice/list.html @@ -0,0 +1,84 @@ +{% extends "_base.html" %} +{% load static %} +{% load filters %} + +{% block styles %} + {{ block.super }} + <!-- DataTables --> + <link rel="stylesheet" href="{% static 'AdminLTE/plugins/datatables/dataTables.bootstrap.css' %}"> +{% endblock styles %} + +{% block ui_active_tab %}'privacy_notices'{% endblock ui_active_tab %} +{% block page_header %}Privacy Notices{% endblock page_header %} +{% block page_description %}{% endblock page_description %} + +{% block breadcrumb %} + {% include "privacy_notice/breadcrumb.html" %} +{% endblock breadcrumb %} + +{% block maincontent %} + + + <div class="box box-success"> + <div class="box-header with-border"> + <h3 class="box-title">Privacy Notices</h3> + </div> + <div class="box-body"> + + <div> + <a class="btn btn-app" href="{% url 'web.views.privacy_notice_add' %}"> + <i class="fa fa-plus"></i> Add new privacy notice + </a> + </div> + <table id="table" class="table table-bordered table-striped"> + <thead> + <tr> + <th>No.</th> + <th>Creation Date</th> + <th>Last Updated</th> + <th>Study</th> + <th>Download</th> + <th>Edit</th> + <th>Delete</th> + </tr> + </thead> + <tbody> + {% for privacy_notice in privacy_notices %} + <tr> + <td>{{ forloop.counter }}</td> + <td>{{ privacy_notice.created_at }}</td> + <td>{{ privacy_notice.updated_at }}</td> + <td>{{ privacy_notice.study }}</td> + <td><a href="{{ privacy_notice.document.url }}"><i class="fa fa-download"></i></a> {{privacy_notice.document.url |Â basename}}</td> + <td><a href="{% url 'web.views.privacy_notice_edit' privacy_notice.id %}"><i + class="fa fa-edit"></i></a></td> + <td><a href="{% url 'web.views.privacy_notice_delete' privacy_notice.id %}"><i + class="fa fa-trash text-danger"></i></a></td> + </tr> + {% endfor %} + </tbody> + </table> + </div> + </div> + +{% endblock maincontent %} + +{% block scripts %} + {{ block.super }} + + <script src="{% static 'AdminLTE/plugins/datatables/jquery.dataTables.min.js' %}"></script> + <script src="{% static 'AdminLTE/plugins/datatables/dataTables.bootstrap.min.js' %}"></script> + + <script> + $(function () { + $('#table').DataTable({ + "paging": true, + "lengthChange": false, + "searching": true, + "ordering": true, + "info": true, + "autoWidth": false + }); + }); + </script> +{% endblock scripts %} diff --git a/smash/web/templates/study/edit.html b/smash/web/templates/study/edit.html index 2fc025e025edd3f086121d2da0794b41b0ff3243..57ae2071e306d8cf8d9d4496e66d56a00870f737 100644 --- a/smash/web/templates/study/edit.html +++ b/smash/web/templates/study/edit.html @@ -66,6 +66,12 @@ {% endfor %} </div> + + <div> + <a class="btn btn-app" href="{% url 'web.views.mail_template_add' %}"> + <i class="fa fa-plus"></i> Add new privacy notice + </a> + </div> </div><!-- /.box-body --> <div class="box-header with-border"> diff --git a/smash/web/templatetags/filters.py b/smash/web/templatetags/filters.py index a7a63383cf825187fbac57307b97da48f68a0da0..6e2e122ef8c1ea1bc89c060ff220de717fc720be 100644 --- a/smash/web/templatetags/filters.py +++ b/smash/web/templatetags/filters.py @@ -2,7 +2,7 @@ from django import template from django.forms import CheckboxSelectMultiple, CheckboxInput from django.utils.safestring import mark_safe -import datetime +import datetime, os from web.models import ConfigurationItem from web.models.constants import VISIT_SHOW_VISIT_NUMBER_FROM_ZERO from distutils.util import strtobool @@ -59,4 +59,8 @@ def display_visit_number(visit_number): if strtobool(visit_from_zero): return (visit_number - 1) else: - return visit_number \ No newline at end of file + return visit_number + +@register.filter(name='basename') +def basename(path): + return os.path.basename(path) \ No newline at end of file diff --git a/smash/web/urls.py b/smash/web/urls.py index 651e74f0461c76cf28d7bfebc7d48d0ef8505ddd..05889ef12e873bbdeb356aaecf299862dbd9ae4a 100644 --- a/smash/web/urls.py +++ b/smash/web/urls.py @@ -161,6 +161,17 @@ urlpatterns = [ url(r'^equipment_and_rooms/rooms/delete/(?P<room_id>\d+)$', views.rooms.rooms_delete, name='web.views.equipment_and_rooms.rooms_delete'), + #################### + # PRIVACY NOTICE # + #################### + + url(r'^privacy_notices$', views.privacy_notice.PrivacyNoticeListView.as_view(), name='web.views.privacy_notices'), + + url(r'^privacy_notices/add$', views.privacy_notice.privacy_notice_add, name='web.views.privacy_notice_add'), + url(r'^privacy_notices/(?P<pk>\d+)/edit$', views.privacy_notice.privacy_notice_edit, name='web.views.privacy_notice_edit'), + + url(r'^privacy_notices/(?P<pk>\d+)/delete$', views.privacy_notice.PrivacyNoticeDeleteView.as_view(), name='web.views.privacy_notice_delete'), + #################### # MAIL # #################### diff --git a/smash/web/views/__init__.py b/smash/web/views/__init__.py index 90f254227fb955480ca1425d8a6a4764dd004ca6..a7dcf1704398be3c806e75e45eb32c08f5339a4a 100644 --- a/smash/web/views/__init__.py +++ b/smash/web/views/__init__.py @@ -105,4 +105,5 @@ from . import uploaded_files from . import study from . import password from . import appointment_type -from . import provenance \ No newline at end of file +from . import provenance +from . import privacy_notice \ No newline at end of file