From 19d120e045991226a07bbe3929bc3ed0db818790 Mon Sep 17 00:00:00 2001 From: Andrea Lepori Date: Wed, 17 Mar 2021 22:20:58 +0100 Subject: users of group capi can optionally view documents --- client/migrations/0007_groupsettings.py | 23 + client/models.py | 4 + client/views.py | 17 +- server/templates/server/doc_list_readonly.html | 618 +++++++++++++++++++++++++ server/templates/server/index.html | 31 ++ server/urls.py | 1 + server/views.py | 278 ++++++++++- templates/registration/base_client.html | 6 + version.txt | 2 +- 9 files changed, 973 insertions(+), 7 deletions(-) create mode 100644 client/migrations/0007_groupsettings.py create mode 100644 server/templates/server/doc_list_readonly.html diff --git a/client/migrations/0007_groupsettings.py b/client/migrations/0007_groupsettings.py new file mode 100644 index 0000000..b5a23fa --- /dev/null +++ b/client/migrations/0007_groupsettings.py @@ -0,0 +1,23 @@ +# Generated by Django 3.1.2 on 2021-03-17 20:06 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('auth', '0012_alter_user_first_name_max_length'), + ('client', '0006_documenttype_max_instances'), + ] + + operations = [ + migrations.CreateModel( + name='GroupSettings', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('view_documents', models.BooleanField(default=False)), + ('group', models.ForeignKey(default=None, on_delete=django.db.models.deletion.CASCADE, to='auth.group')), + ], + ), + ] diff --git a/client/models.py b/client/models.py index d14a267..5b5db6a 100644 --- a/client/models.py +++ b/client/models.py @@ -111,3 +111,7 @@ class UserCode(models.Model): phone = models.CharField(default="", max_length=250) school = models.CharField(default="", max_length=250) year = models.IntegerField(default=0) + +class GroupSettings(models.Model): + group = models.ForeignKey(Group, default=None, on_delete=models.CASCADE) + view_documents = models.BooleanField(default=False) \ No newline at end of file diff --git a/client/views.py b/client/views.py index 32678f5..6fdd3f2 100644 --- a/client/views.py +++ b/client/views.py @@ -1,6 +1,6 @@ from random import randint from django.contrib.auth.models import Group, Permission, User -from client.models import UserCode, Keys, DocumentType, Document, PersonalData, KeyVal, MedicalData +from client.models import GroupSettings, UserCode, Keys, DocumentType, Document, PersonalData, KeyVal, MedicalData from django.db.models import Q from django.http import HttpResponseRedirect, FileResponse, HttpResponse from django.contrib.auth.decorators import login_required @@ -17,6 +17,7 @@ import pytz def index(request): context = {} + group_view = False # check if user is logged if (request.user.is_authenticated): if not (request.user.is_staff or request.user.has_perm("client.approved")): @@ -39,6 +40,19 @@ def index(request): user_code = "U" + str(usercode.code) context = {"user_code": user_code} else: + # get user group + group = request.user.groups.values_list('name', flat=True)[0] + + # get group settings + settings = GroupSettings.objects.filter(group__name=group) + + # check if settings exists + if len(settings) == 0: + group_view = False + else: + # set settings value + group_view = settings[0].view_documents + # user action if request.method == "POST": # get document id @@ -123,6 +137,7 @@ def index(request): context = { "docs": out, "empty": len(out) == 0, + "group_view": group_view, } return render(request, 'client/index.html', context) diff --git a/server/templates/server/doc_list_readonly.html b/server/templates/server/doc_list_readonly.html new file mode 100644 index 0000000..42a52e8 --- /dev/null +++ b/server/templates/server/doc_list_readonly.html @@ -0,0 +1,618 @@ +{% extends 'registration/base_admin.html' %} + +{% block title %}Admin - Documenti{% endblock %} + +{% block breadcrumb %} + Admin + Documenti +{% endblock %} +{% block toolbar %} + +{% endblock %} + +{% block content %} + + + +
+{% csrf_token %} + + + + + + +
+{% endblock %} + +{%block script%} +$(document).ready(function(){ + $('.collapsible').collapsible(); + $('#modal1').modal(); + $('#modal2').modal(); + $('#modal3').modal({"dismissible": false}); + $('.datepicker').datepicker(options); + {% if error %} + M.toast({html: '{{ error_text}}', classes: 'orange'}) + {% endif %} + {% if task_id %} + $('#modal3').modal('open'); + update(); + document.getElementById('progress_bar').className = "determinate"; + {% endif %} + lazyload(); +}); + +function resetModal() { + $('#modal3').modal('close'); + document.getElementById('progress_bar').className = "indeterminate"; + document.getElementById("modal_close_button").hidden = true; + document.getElementById('progress_text').innerHTML = ""; +} + +$('.chips').chips(); +$('#chips_type').chips({ + placeholder: 'Tipo', + secondaryPlaceholder: '+Altro tipo', + autocompleteOptions: { + data: { + {% for t in types %} + '{{t.name}}': null, + {% endfor %} + }, + limit: Infinity, + minLength: 0 + }, + data: [ + {% for d in chips_type %} + {tag: '{{d}}'}, + {% endfor %} + ] +}); +$('#chips_owner').chips({ + placeholder: 'Utente', + secondaryPlaceholder: '+Altro utente', + autocompleteOptions: { + data: { + {% for user in users %} + '{{user.username}} ({{user.first_name}} {{user.last_name}})': null, + {% endfor %} + }, + limit: Infinity, + minLength: 0 + }, + data: [ + {% for d in chips_owner %} + {tag: '{{d}}'}, + {% endfor %} + ] +}); +$('#chips_groups').chips({ + placeholder: 'Gruppo', + secondaryPlaceholder: '+Altro gruppo', + autocompleteOptions: { + data: { + {% for g in groups %} + '{{g}}': null, + {% endfor %} + }, + limit: Infinity, + minLength: 0 + }, + data: [ + {% for g in chips_groups %} + {tag: '{{g}}'}, + {% endfor %} + ] +}); + +function update() { + var box = document.getElementById('progress_text'); + var bar = document.getElementById('progress_bar'); + {% if task_id %} + var url = '{% url "progress" %}' + '?job=' + '{{task_id}}'; + {% else %} + var url = '{% url "progress" %}'; + {% endif %} + fetch(url).then(function(response) { + response.json().then(function(data) { + if (data[0] == data[1]) { + if (data[2]) { + bar.className = "indeterminate"; + url = '{% url "progress" %}' + '?job=' + '{{task_id}}' + '&download=true'; + box.innerHTML = "Il download dovrebbe partire automaticamente. Nel caso non succedesse cliccare il seguente link"; + document.getElementById("modal_close_button").hidden = false; + document.getElementById('downloadLink').click(); + } else { + bar.style.width = "100%"; + box.innerHTML = "Impacchettamento documenti..."; + setTimeout(update, 500, url); + } + } else { + box.innerHTML = data[0] + "/" + data[1]; + bar.style.width = data[0]/data[1]*100 + "%"; + setTimeout(update, 500, url); + } + }); + }); +} + +function send(id) { + var form = document.getElementById('selection') + var action = document.getElementById('action') + var owner = document.getElementById('owner') + var type = document.getElementById('type') + var groups = document.getElementById('groups') + var chips_owner = M.Chips.getInstance(document.getElementById('chips_owner')); + var chips_type = M.Chips.getInstance(document.getElementById('chips_type')); + var chips_groups = M.Chips.getInstance(document.getElementById('chips_groups')); + var type_array = [] + var owner_array = [] + var groups_array = [] + + for (i=0; i < chips_owner.chipsData.length; i++) { + owner_array.push(chips_owner.chipsData[i].tag) + } + + for (i=0; i < chips_type.chipsData.length; i++) { + type_array.push(chips_type.chipsData[i].tag) + } + + for (i=0; i < chips_groups.chipsData.length; i++) { + groups_array.push(chips_groups.chipsData[i].tag) + } + + action.setAttribute('value', id); + owner.setAttribute('value', owner_array.join("^|")) + type.setAttribute('value', type_array.join("^|")) + groups.setAttribute('value', groups_array.join("^|")) + form.submit() +} + +$('#select-all').click(function(event) { + if(this.checked) { + // Iterate each checkbox + $('.allselect').each(function() { + this.checked = true; + }); + } else { + $('.allselect').each(function() { + this.checked = false; + }); + } +}); + +var options = { + showClearBtn: true, + container: document.getElementById('main'), + yearRange:100, + format:'dd mmmm yyyy', + i18n: { + months: [ 'gennaio', 'febbraio', 'marzo', 'aprile', 'maggio', 'giugno', 'luglio', 'agosto', 'settembre', 'ottobre', 'novembre', 'dicembre' ], + monthsShort: [ 'gen', 'feb', 'mar', 'apr', 'mag', 'giu', 'lug', 'ago', 'set', 'ott', 'nov', 'dic' ], + weekdays: [ 'domenica', 'lunedì', 'martedì', 'mercoledì', 'giovedì', 'venerdì', 'sabato' ], + weekdaysShort: [ 'dom', 'lun', 'mar', 'mer', 'gio', 'ven', 'sab' ], + weekdaysAbbrev: [ 'D', 'L', 'M', 'M', 'G', 'V', 'S' ], + today: 'Oggi', + clear: 'Cancella', + close: 'Chiudi', + cancel: 'Annulla', + firstDay: 1, + format: 'dddd d mmmm yyyy', + formatSubmit: 'yyyy/mm/dd', + labelMonthNext: 'Mese successivo', + labelMonthPrev: 'Mese precedente', + labelMonthSelect: 'Seleziona un mese', + labelYearSelect: 'Seleziona un anno' + } +} +{% endblock %} \ No newline at end of file diff --git a/server/templates/server/index.html b/server/templates/server/index.html index de28169..d8a13f2 100644 --- a/server/templates/server/index.html +++ b/server/templates/server/index.html @@ -56,6 +56,23 @@ {% endif %}
+
+ {% csrf_token %} +
+
+
+ Documenti visibili ad aggiunti
+ +
+ +
+
+
    {% for doctype in docs %}
  • @@ -97,4 +114,18 @@
+{% endblock %} + +{% block script %} +function execute_confirm() { + var selection = document.getElementById('select_switch') + var button = document.getElementById('send_button') + selection.style.display = "none" + button.style.display = "inline-block" + {% if doc_view_check == 'checked="checked"'%} + button.innerHTML = "Applica (gli aggiunti NON potranno vedere i documenti)" + {% else %} + button.innerHTML = "Applica (gli aggiunti POTRANNO vedere i documenti)" + {% endif %} +} {% endblock %} \ No newline at end of file diff --git a/server/urls.py b/server/urls.py index 5ede527..5ff9eba 100644 --- a/server/urls.py +++ b/server/urls.py @@ -11,6 +11,7 @@ urlpatterns = [ path('doccreate', views.doccreate, name='doccreate'), path('docedit', views.docedit, name='docedit'), path('doclist', views.doclist, name='doclist'), + path('doclistro', views.doclist_readonly, name='doclistro'), path('docapprove', views.docapprove, name='docapprove'), path('docupload', views.upload_doc, name='docupload'), path('docpreview', views.docpreview, name='docpreview'), diff --git a/server/views.py b/server/views.py index 7ba0173..b65b2ea 100644 --- a/server/views.py +++ b/server/views.py @@ -1,5 +1,5 @@ from django.shortcuts import render -from client.models import UserCode, Keys, DocumentType, Document, KeyVal +from client.models import GroupSettings, UserCode, Keys, DocumentType, Document, KeyVal from django.contrib.auth.models import Group, Permission, User from django.db.models import Q from django.http import HttpResponseRedirect, FileResponse, HttpResponse @@ -34,6 +34,14 @@ def isStaff(user): return True return False +# function to check if "aggiunto" has permission to view documents +def isCapi_enabled(user): + group = user.groups.values_list('name', flat=True)[0] + settings = GroupSettings.objects.filter(group__name=group) + if len(settings) == 0: + return False + return settings[0].view_documents + @user_passes_test(isStaff) def index(request): context = {} @@ -41,6 +49,31 @@ def index(request): parent_group = request.user.groups.values_list('name', flat=True)[ 0] group = Group.objects.get(name=parent_group) + + # check for settings + doc_view_check = "" + settings = GroupSettings.objects.filter(group__name=group) + + # create settings if non existing + if len(settings) == 0: + settings = GroupSettings(group=group, view_documents=False) + else: + settings = settings[0] + + if settings.view_documents: + doc_view_check = 'checked="checked"' + + # check if changing settings + if request.method == "POST": + if "doc_view" in request.POST: + settings.view_documents = True + settings.save() + else: + settings.view_documents = False + settings.save() + + return HttpResponseRedirect("/server") + # users from younger to older users = User.objects.filter(groups__name=parent_group).order_by("-id") users_out = [] @@ -82,6 +115,7 @@ def index(request): context = { 'docs': docs, } + context["doc_view_check"] = doc_view_check return render(request, 'server/index.html', context) @@ -811,10 +845,6 @@ def doclist(request): error = True error_text = "Non puoi dearchiviare un documento non archiviato" - if len(selected) == 0: - error = True - error_text = "Seleziona almeno un documento" - # get filter values hidden = "filter_hidden" in request.POST wait = "filter_wait" in request.POST @@ -969,6 +999,244 @@ def doclist(request): return render(request, 'server/doc_list.html', context) +@user_passes_test(isCapi_enabled) +def doclist_readonly(request): + context = {} + + # group name and obj + parent_group = request.user.groups.values_list('name', flat=True)[ + 0] + group = Group.objects.get(name=parent_group) + + # create typezone + zurich = pytz.timezone('Europe/Zurich') + + # init error variables for users + error = False + error_text = "" + + # init checkboxes for filter + hidden = False + wait = True + selfsign = True + ok = True + signdoc = False + + hidden_check = 'checked="checked"' + wait_check = 'checked="checked"' + selfsign_check = 'checked="checked"' + ok_check = 'checked="checked"' + signdoc_check = 'checked="checked"' + + # set default dates for filters + newer = zurich.localize(dateparser.parse("1970-01-01")) + older = zurich.localize(datetime.now()) + + # init chips values + owner = [] + types = [] + groups = [] + chips_owner = [] + chips_types = [] + chips_groups = [] + + if request.method == "POST": + # if download request + if request.POST["action"][0] == 'k': + document = Document.objects.get(id=request.POST["action"][1:]) + # check if user has permission to view doc + if document.group.name == parent_group: + vac_file = "" + health_file = "" + sign_doc_file = "" + + # prepare images in base64 + if document.medical_data: + if document.medical_data.vac_certificate.name: + with open(document.medical_data.vac_certificate.name, 'rb') as image_file: + vac_file = base64.b64encode( + image_file.read()).decode() + + if document.medical_data.health_care_certificate.name: + with open(document.medical_data.health_care_certificate.name, 'rb') as image_file: + health_file = base64.b64encode( + image_file.read()).decode() + + if document.signed_doc: + with open(document.signed_doc.name, 'rb') as image_file: + sign_doc_file = base64.b64encode( + image_file.read()).decode() + + # build with template and render + template = get_template('server/download_doc.html') + doc = [document, KeyVal.objects.filter( + container=document), document.personal_data, document.medical_data, parent_group] + context = {'doc': doc, 'vac': vac_file, + 'health': health_file, 'sign_doc_file': sign_doc_file} + html = template.render(context) + pdf = pdfkit.from_string(html, False) + result = BytesIO(pdf) + result.seek(0) + return FileResponse(result, as_attachment=True, filename=document.user.username+"_"+document.document_type.name+".pdf") + + # get selected documents and check if user has permission to view + selected = [] + for i in request.POST.keys(): + if i.isdigit(): + docc = Document.objects.get(id=i) + if docc.group.name == parent_group: + selected.append(docc) + + # get filter values + hidden = "filter_hidden" in request.POST + wait = "filter_wait" in request.POST + selfsign = "filter_selfsign" in request.POST + ok = "filter_ok" in request.POST + signdoc = "filter_signdoc" in request.POST + newer = zurich.localize(dateparser.parse(request.POST["newer"])) + older = zurich.localize(dateparser.parse( + request.POST["older"]) + timedelta(days=1)) + owner = request.POST["owner"].split("^|") + types = request.POST["type"].split("^|") + groups = request.POST["groups"].split("^|") + + # clear filters + if request.POST["action"] == 'clear': + hidden = False + wait = True + selfsign = True + ok = True + signdoc = False + newer = zurich.localize(dateparser.parse("1970-01-01")) + older = zurich.localize(datetime.now()) + owner = [] + types = [] + groups = [] + + # filter documents based on group of staff + documents = Document.objects.filter(group__name=parent_group) + + # filter documents + if not hidden: + documents = documents.filter(~Q(status="archive")) + hidden_check = "" + if not wait: + documents = documents.filter(~Q(status="wait")) + wait_check = "" + if not selfsign: + documents = documents.filter(~Q(status="autosign")) + selfsign_check = "" + if not ok: + documents = documents.filter(~Q(status="ok")) + ok_check = "" + if not signdoc: + signdoc_check = "" + + # filter date range + documents = documents.filter(compilation_date__range=[newer, older]) + + # filter types, owner, groups using chips + if len(types) > 0: + if types[0] != "": + q_obj = Q() + for t in types: + q_obj |= Q(document_type__name=t) + chips_types.append(t) + + documents = documents.filter(q_obj) + + if len(owner) > 0: + if owner[0] != "": + q_obj = Q() + for u in owner: + user = u.split("(")[0][:-1] + q_obj |= Q(user__username=user) + chips_owner.append(u) + + documents = documents.filter(q_obj) + + if len(groups) > 0: + if groups[0] != "": + q_obj = Q() + for g in groups: + q_obj |= Q(group__name=g) + chips_groups.append(g) + + documents = documents.filter(q_obj) + + out = [] + for i in documents: + # filter for confirmed with attachment documents and approved + if signdoc: + if i.status == "ok" and not i.signed_doc: + continue + + # prepare images in base64 + personal = None + medical = None + vac_file = "" + health_file = "" + sign_doc_file = "" + if i.personal_data: + personal = i.personal_data + if i.medical_data: + medical = i.medical_data + if medical.vac_certificate.name: + vac_file = "/server/media/" + str(i.id) + "/vac_certificate/doc" + + if medical.health_care_certificate.name: + health_file = "/server/media/" + str(i.id) + "/health_care_certificate/doc" + + if i.signed_doc: + sign_doc_file = "/server/media/" + str(i.id) + "/signed_doc/doc" + + doc_group = i.user.groups.values_list('name', flat=True)[0] + + out.append([i, KeyVal.objects.filter(container=i), personal, + medical, doc_group, vac_file, health_file, sign_doc_file]) + + # get types and users for chips autocompletation + auto_types = DocumentType.objects.filter( + Q(group_private=False) | Q(group=group)) + users = User.objects.filter(groups__name=parent_group) + + context = { + "types": auto_types, + "users": users, + "groups": [parent_group], + "docs": out, + "hidden_check": hidden_check, + "wait_check": wait_check, + "selfsign_check": selfsign_check, + "ok_check": ok_check, + "signdoc_check": signdoc_check, + "newer": newer, + "older": older, + "chips_owner": chips_owner, + "chips_type": chips_types, + "chips_groups": chips_groups, + 'error': error, + 'error_text': error_text, + 'settings': settings, + } + + # check if download multiple documents + if request.method == "POST": + if "status" not in request.session: + request.session['status'] = True + + if request.POST["action"] == "download" and len(selected) > 0 and request.session['status']: + # save data in session + request.session['status'] = False + request.session['progress'] = 0 + request.session['total'] = len(selected) + # run job + threading.Thread(target=zip_documents, args=(selected, request.session.session_key)).start() + # flag the client to check for updates + context["task_id"] = "0" + + return render(request, 'server/doc_list_readonly.html', context) + def get_progress(request): # if user wants to download result if 'download' in request.GET: diff --git a/templates/registration/base_client.html b/templates/registration/base_client.html index fcef3b0..945ac99 100644 --- a/templates/registration/base_client.html +++ b/templates/registration/base_client.html @@ -86,6 +86,9 @@ {% endblock %}