From b75e1e940d61fb4effb08c58d45bd347eb5a3256 Mon Sep 17 00:00:00 2001 From: "p.fherrma" Date: Tue, 12 May 2020 10:55:09 +0200 Subject: [PATCH 01/54] feat(files): restrict listing and download of datafiles to users with "view_dsmo" permissions. Does NOT fix issue 36 which is a related but different problem --- discuss_data/dddatasets/views.py | 34 +++++++++++++++----- discuss_data/templates/dddatasets/files.html | 2 +- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/discuss_data/dddatasets/views.py b/discuss_data/dddatasets/views.py index fd31f7d..dba4b73 100644 --- a/discuss_data/dddatasets/views.py +++ b/discuss_data/dddatasets/views.py @@ -5,6 +5,7 @@ from django.http import ( Http404, HttpResponse, FileResponse, + HttpResponseForbidden, ) from django.shortcuts import render from django.utils.translation import gettext as _ @@ -17,6 +18,12 @@ from actstream.actions import ( ) from actstream.models import followers +from discuss_data.core.views import ( + check_perms, + check_perms_403, + get_help_texts, +) + from discuss_data.dddatasets.models import ( DataSet, DataSetManagementObject, @@ -124,8 +131,15 @@ def dataset_metadata(request, uuid): @login_required def dataset_files(request, uuid): ds = check_published(uuid) + datafiles = list() + for datafile in ds.get_datafiles(): + if check_perms("view_dsmo", request.user, ds.dataset_management_object): + datafiles.append(datafile) + return render( - request, "dddatasets/files.html", {"ds": ds, "updated": True, "type": "files"}, + request, + "dddatasets/files.html", + {"ds": ds, "updated": True, "type": "files", "datafiles": datafiles}, ) @@ -146,13 +160,13 @@ def dataset_files_pdf(request, uuid): @login_required def dataset_files_zip(request, uuid): - # TODO: no rights management whatsoever implemented yet! ds = DataSet.objects.get(uuid=uuid) in_memory = BytesIO() zipfile = ZipFile(in_memory, "a") zipfile.writestr(ds.get_pdf_file_name(), create_pdf(request, ds)) for file in ds.get_datafiles(): - zipfile.writestr(file.name, file.file.read()) + if check_perms("view_dsmo", request.user, ds.dataset_management_object): + zipfile.writestr(file.name, file.file.read()) # fix for Linux zip files read in Windows for file in zipfile.filelist: file.create_system = 0 @@ -170,11 +184,15 @@ def dataset_files_zip(request, uuid): @login_required def dataset_files_download(request, uuid, df_uuid): - # TODO: no rights management whatsoever implemented yet! - datafile = DataFile.objects.get(uuid=df_uuid).file - response = FileResponse(datafile) - response["Content-Disposition"] = "attachment; filename=%s" % (datafile,) - return response + datafile = DataFile.objects.get(uuid=df_uuid) + if check_perms( + "view_dsmo", request.user, datafile.dataset.dataset_management_object + ): + response = FileResponse(datafile.file) + response["Content-Disposition"] = "attachment; filename=%s" % (datafile.file,) + return response + else: + return HttpResponseForbidden() @login_required diff --git a/discuss_data/templates/dddatasets/files.html b/discuss_data/templates/dddatasets/files.html index 0e4d9c4..9bd685e 100644 --- a/discuss_data/templates/dddatasets/files.html +++ b/discuss_data/templates/dddatasets/files.html @@ -11,7 +11,7 @@ {% include 'dddatasets/_file_card.html' with fileicon='images/file-pdf.svg' fileurl='dddatasets:files_pdf' dsuuid=ds.uuid filename=ds.get_pdf_file_name filetype='metadata' %} {# actual data files #} - {% for file in ds.get_datafiles %} + {% for file in datafiles %} {% include 'dddatasets/_file_card.html' with fileicon='images/file-pdf.svg' fileurl='dddatasets:files_download' fileuuid=file.uuid dsuuid=ds.uuid filename=file.name filetype=file.get_data_file_type_display %} {% endfor %} -- GitLab From fe7d2117275f1c0ebce477050fa460d126669ea2 Mon Sep 17 00:00:00 2001 From: "p.fherrma" Date: Tue, 12 May 2020 11:34:12 +0200 Subject: [PATCH 02/54] fix(files): use the restricted access view dataset permissions for datafile access check --- discuss_data/dddatasets/views.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/discuss_data/dddatasets/views.py b/discuss_data/dddatasets/views.py index dba4b73..18fb1e5 100644 --- a/discuss_data/dddatasets/views.py +++ b/discuss_data/dddatasets/views.py @@ -133,7 +133,8 @@ def dataset_files(request, uuid): ds = check_published(uuid) datafiles = list() for datafile in ds.get_datafiles(): - if check_perms("view_dsmo", request.user, ds.dataset_management_object): + # check restricted access view permissions for the dataset + if check_perms("ra_view_dataset", request.user, ds): datafiles.append(datafile) return render( @@ -165,7 +166,8 @@ def dataset_files_zip(request, uuid): zipfile = ZipFile(in_memory, "a") zipfile.writestr(ds.get_pdf_file_name(), create_pdf(request, ds)) for file in ds.get_datafiles(): - if check_perms("view_dsmo", request.user, ds.dataset_management_object): + # check restricted access view permissions for the dataset + if check_perms("ra_view_dataset", request.user, ds): zipfile.writestr(file.name, file.file.read()) # fix for Linux zip files read in Windows for file in zipfile.filelist: @@ -185,9 +187,8 @@ def dataset_files_zip(request, uuid): @login_required def dataset_files_download(request, uuid, df_uuid): datafile = DataFile.objects.get(uuid=df_uuid) - if check_perms( - "view_dsmo", request.user, datafile.dataset.dataset_management_object - ): + # check restricted access view permissions for the dataset + if check_perms("ra_view_dataset", request.user, datafile.dataset): response = FileResponse(datafile.file) response["Content-Disposition"] = "attachment; filename=%s" % (datafile.file,) return response -- GitLab From 82a98baa6bbb1e9b836c1c379d50b7d4af5b78a1 Mon Sep 17 00:00:00 2001 From: "p.fherrma" Date: Tue, 12 May 2020 12:39:37 +0200 Subject: [PATCH 03/54] feat(files): list datafiles of datasets with restricted access as "restricted" in public view with "request access" button and lock icon --- discuss_data/dddatasets/views.py | 3 +++ discuss_data/static/sass/project.scss | 9 ++++++++ .../templates/dddatasets/_file_card.html | 22 +++++++++++-------- discuss_data/templates/dddatasets/files.html | 9 ++++++-- 4 files changed, 32 insertions(+), 11 deletions(-) diff --git a/discuss_data/dddatasets/views.py b/discuss_data/dddatasets/views.py index 18fb1e5..f869cc3 100644 --- a/discuss_data/dddatasets/views.py +++ b/discuss_data/dddatasets/views.py @@ -136,6 +136,9 @@ def dataset_files(request, uuid): # check restricted access view permissions for the dataset if check_perms("ra_view_dataset", request.user, ds): datafiles.append(datafile) + else: + datafiles.append("RA") + print("DATAFILES", datafiles) return render( request, diff --git a/discuss_data/static/sass/project.scss b/discuss_data/static/sass/project.scss index 584c631..4cc8633 100644 --- a/discuss_data/static/sass/project.scss +++ b/discuss_data/static/sass/project.scss @@ -269,6 +269,15 @@ body { border: 1px solid theme-color("border-gray"); } +.border-restricted { + border-color: theme-color("danger"); +} + +.fileicon { + font-size: x-large; + color: theme-color("danger"); +} + /* * Sidebar */ diff --git a/discuss_data/templates/dddatasets/_file_card.html b/discuss_data/templates/dddatasets/_file_card.html index 2ebd4b6..2acbf4d 100644 --- a/discuss_data/templates/dddatasets/_file_card.html +++ b/discuss_data/templates/dddatasets/_file_card.html @@ -1,21 +1,25 @@ {% load static i18n %} -
+
-
-
+
+
-
- +
+ {% if filename == "RA" %}{% else %}{% endif %}
-
{{ filename }}
+
{% if filename == "RA" %}{% trans 'Restricted Access' %}{% else %}{{ filename }}{% endif %}
-
- +
+ {% if filename == "RA" %} + + {% else %} + + {% endif %}
-
+
\ No newline at end of file diff --git a/discuss_data/templates/dddatasets/files.html b/discuss_data/templates/dddatasets/files.html index 9bd685e..44c3cfe 100644 --- a/discuss_data/templates/dddatasets/files.html +++ b/discuss_data/templates/dddatasets/files.html @@ -7,15 +7,20 @@ {% include 'dddatasets/_dataset_header.html' with ds=ds type='files' %}

{% trans "Files" %}

+

{% trans 'Datafiles in this Dataset' %}: {{ ds.get_datafiles_count }}

{# autogenerated metadata pdf #} {% include 'dddatasets/_file_card.html' with fileicon='images/file-pdf.svg' fileurl='dddatasets:files_pdf' dsuuid=ds.uuid filename=ds.get_pdf_file_name filetype='metadata' %} {# actual data files #} {% for file in datafiles %} - {% include 'dddatasets/_file_card.html' with fileicon='images/file-pdf.svg' fileurl='dddatasets:files_download' fileuuid=file.uuid dsuuid=ds.uuid filename=file.name filetype=file.get_data_file_type_display %} + {% if file == "RA" %} + {% include 'dddatasets/_file_card.html' with fileicon='images/file-pdf.svg' dsuuid=ds.uuid filename="RA" filetype="restricted" %} + {% else %} + {% include 'dddatasets/_file_card.html' with fileicon='images/file-pdf.svg' fileurl='dddatasets:files_download' fileuuid=file.uuid dsuuid=ds.uuid filename=file.name filetype=file.get_data_file_type_display %} + {% endif %} {% endfor %} {# autogenerated zip file #} - {% include 'dddatasets/_file_card.html' with fileicon='images/file-pdf.svg' fileurl='dddatasets:files_zip' dsuuid=ds.uuid filename="All files in a zip file" filetype='data' %} + {% include 'dddatasets/_file_card.html' with fileicon='images/file-pdf.svg' fileurl='dddatasets:files_zip' dsuuid=ds.uuid filename="All files in a zip file" filetype='archive' %}
{% endblock ic-content %} -- GitLab From 694ee65c33ce1972dff7688b1d8223644f9fe289 Mon Sep 17 00:00:00 2001 From: "p.fherrma" Date: Tue, 12 May 2020 15:11:02 +0200 Subject: [PATCH 04/54] feat(files): add file format guessing using filetype for icon choosing in templates --- discuss_data/core/templatetags/core_tags.py | 17 +++++++++++++ discuss_data/dddatasets/models.py | 25 ++++++++++++++++--- discuss_data/static/sass/project.scss | 6 +++-- .../templates/dddatasets/_file_card.html | 19 +++++++++++--- discuss_data/templates/dddatasets/files.html | 9 +++---- requirements/base.txt | 3 +++ 6 files changed, 65 insertions(+), 14 deletions(-) diff --git a/discuss_data/core/templatetags/core_tags.py b/discuss_data/core/templatetags/core_tags.py index f0b013b..3a17bd5 100644 --- a/discuss_data/core/templatetags/core_tags.py +++ b/discuss_data/core/templatetags/core_tags.py @@ -51,3 +51,20 @@ def get_value(dictionary, key): return dictionary[key] except Exception: return None + + +@register.filter +def get_file_icon(mime_str): + """ + Returns the fa icon string for the mime group + """ + if mime_str == "application/zip": + return "fa-file" + if mime_str == "archive/pdf" or mime_str == "application/pdf": + return "fa-file-pdf" + + try: + kind, ext = mime_str.split("/") + return "fa-file-" + kind + except Exception: + return "fa-file" diff --git a/discuss_data/dddatasets/models.py b/discuss_data/dddatasets/models.py index d17591f..8faf200 100644 --- a/discuss_data/dddatasets/models.py +++ b/discuss_data/dddatasets/models.py @@ -1,13 +1,13 @@ import logging import os import uuid +import filetype from actstream import action from datetime import datetime from dcxml import simpledc from PIL import Image - from taggit.managers import TaggableManager from taggit.models import ( TagBase, @@ -90,6 +90,11 @@ class Sponsor(models.Model): class DataFile(models.Model): + FILE_FORMATS = { + "png": "image", + "jpeg": "image", + "jpg": "image", + } DATA_FILE_TYPES = ( ("DAT", _("data")), ("MET", _("metadata")), @@ -117,6 +122,12 @@ class DataFile(models.Model): extension = os.path.splitext(self.file.name) return extension + def get_file_format(self): + file_format = filetype.guess(self.file) + if file_format is None: + return "other" + return file_format.mime + def get_user(self): return self.dataset.owner @@ -272,10 +283,13 @@ class DataSetExternalLink(models.Model): class DataSet(models.Model): + OPEN_ACCESS = "OA" + METADATA_ONLY = "MO" + RESTRICTED_ACCESS = "RA" DATA_ACCESS_CHOICES = [ - ("OA", _("Open Access")), - ("MO", _("Metadata only")), - ("RA", _("Restricted access")), + (OPEN_ACCESS, _("Open Access")), + (METADATA_ONLY, _("Metadata only")), + (RESTRICTED_ACCESS, _("Restricted access")), ] uuid = models.UUIDField( @@ -655,6 +669,9 @@ class DataSet(models.Model): def get_datafiles(self): return DataFile.objects.filter(dataset=self) + def get_datafiles_count(self): + return self.get_datafiles().count() + def dublin_core(self): dc_dict = dict( titles=[self.title_en, self.subtitle_en], diff --git a/discuss_data/static/sass/project.scss b/discuss_data/static/sass/project.scss index 4cc8633..461c757 100644 --- a/discuss_data/static/sass/project.scss +++ b/discuss_data/static/sass/project.scss @@ -274,10 +274,12 @@ body { } .fileicon { - font-size: x-large; - color: theme-color("danger"); + font-size: xx-large; + color: theme-color("primary"); } + + /* * Sidebar */ diff --git a/discuss_data/templates/dddatasets/_file_card.html b/discuss_data/templates/dddatasets/_file_card.html index 2acbf4d..518965b 100644 --- a/discuss_data/templates/dddatasets/_file_card.html +++ b/discuss_data/templates/dddatasets/_file_card.html @@ -1,4 +1,4 @@ -{% load static i18n %} +{% load static i18n core_tags %}
@@ -6,10 +6,23 @@
- {% if filename == "RA" %}{% else %}{% endif %} + + {% if filetype == "restricted" %} + + {% else %} + {% if fileicon == "pdf" %} + + {% elif fileicon == "zip" %} + + {% else %} + + {% endif %} + {% endif %}
-
{% if filename == "RA" %}{% trans 'Restricted Access' %}{% else %}{{ filename }}{% endif %}
+
{% if filetype == "restricted" %}{% trans 'Restricted Access' %}{% else %}{{ filename }}{% endif %}
+ +

{% if filetype == "restricted" %}{% trans 'Access to this file is restricted and can be requested by the dataset owner.' %}{% endif %}

{% if filename == "RA" %} diff --git a/discuss_data/templates/dddatasets/files.html b/discuss_data/templates/dddatasets/files.html index 44c3cfe..d99e8f2 100644 --- a/discuss_data/templates/dddatasets/files.html +++ b/discuss_data/templates/dddatasets/files.html @@ -7,20 +7,19 @@ {% include 'dddatasets/_dataset_header.html' with ds=ds type='files' %}

{% trans "Files" %}

-

{% trans 'Datafiles in this Dataset' %}: {{ ds.get_datafiles_count }}

{# autogenerated metadata pdf #} - {% include 'dddatasets/_file_card.html' with fileicon='images/file-pdf.svg' fileurl='dddatasets:files_pdf' dsuuid=ds.uuid filename=ds.get_pdf_file_name filetype='metadata' %} + {% include 'dddatasets/_file_card.html' with fileicon='pdf' fileurl='dddatasets:files_pdf' dsuuid=ds.uuid filename=ds.get_pdf_file_name filetype='metadata' fileformat='archive/pdf' %} {# actual data files #} {% for file in datafiles %} {% if file == "RA" %} - {% include 'dddatasets/_file_card.html' with fileicon='images/file-pdf.svg' dsuuid=ds.uuid filename="RA" filetype="restricted" %} + {% include 'dddatasets/_file_card.html' with dsuuid=ds.uuid filename="RA" filetype="restricted" %} {% else %} - {% include 'dddatasets/_file_card.html' with fileicon='images/file-pdf.svg' fileurl='dddatasets:files_download' fileuuid=file.uuid dsuuid=ds.uuid filename=file.name filetype=file.get_data_file_type_display %} + {% include 'dddatasets/_file_card.html' with fileurl='dddatasets:files_download' fileuuid=file.uuid dsuuid=ds.uuid filename=file.name filetype=file.get_data_file_type_display fileformat=file.get_file_format %} {% endif %} {% endfor %} {# autogenerated zip file #} - {% include 'dddatasets/_file_card.html' with fileicon='images/file-pdf.svg' fileurl='dddatasets:files_zip' dsuuid=ds.uuid filename="All files in a zip file" filetype='archive' %} + {% include 'dddatasets/_file_card.html' with fileicon='zip' fileurl='dddatasets:files_zip' dsuuid=ds.uuid filename="All files in a zip file" filetype='archive' fileformat='archive/zip' %}
{% endblock ic-content %} diff --git a/requirements/base.txt b/requirements/base.txt index 6d9699c..a7c3f6a 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -51,3 +51,6 @@ wagtailmenus==3.0.1 #sentry sentry-sdk==0.14.3 + +# filetype +filetype==1.0.7 \ No newline at end of file -- GitLab From cfcfa3b7b345f2dd63abcddaba209a4cabf393c2 Mon Sep 17 00:00:00 2001 From: "p.fherrma" Date: Tue, 12 May 2020 16:26:37 +0200 Subject: [PATCH 05/54] fix(files): add dataset uuid to datafile download query; add datafile download file name model method --- discuss_data/dddatasets/models.py | 5 +++++ discuss_data/dddatasets/views.py | 9 ++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/discuss_data/dddatasets/models.py b/discuss_data/dddatasets/models.py index 8faf200..359706a 100644 --- a/discuss_data/dddatasets/models.py +++ b/discuss_data/dddatasets/models.py @@ -118,6 +118,11 @@ class DataFile(models.Model): repository_file_id = models.CharField(max_length=200, default="not set") repository = models.CharField(max_length=200, default="dariah-repository") + def get_download_file_name(self): + return "DiscussData-{}-{}".format( + self.dataset.title.replace(" ", "_"), self.file + ) + def extension(self): extension = os.path.splitext(self.file.name) return extension diff --git a/discuss_data/dddatasets/views.py b/discuss_data/dddatasets/views.py index f869cc3..915ffff 100644 --- a/discuss_data/dddatasets/views.py +++ b/discuss_data/dddatasets/views.py @@ -138,7 +138,6 @@ def dataset_files(request, uuid): datafiles.append(datafile) else: datafiles.append("RA") - print("DATAFILES", datafiles) return render( request, @@ -189,11 +188,15 @@ def dataset_files_zip(request, uuid): @login_required def dataset_files_download(request, uuid, df_uuid): - datafile = DataFile.objects.get(uuid=df_uuid) + datafile = DataFile.objects.get(uuid=df_uuid, dataset__uuid=uuid) + download_name = datafile.get_download_file_name() + print("DN", download_name) # check restricted access view permissions for the dataset if check_perms("ra_view_dataset", request.user, datafile.dataset): response = FileResponse(datafile.file) - response["Content-Disposition"] = "attachment; filename=%s" % (datafile.file,) + response["Content-Disposition"] = "attachment; filename={}".format( + download_name, + ) return response else: return HttpResponseForbidden() -- GitLab From 8a5636a38e066068312647fef1fb5ffeb045ea92 Mon Sep 17 00:00:00 2001 From: "p.fherrma" Date: Tue, 12 May 2020 19:45:22 +0200 Subject: [PATCH 06/54] fix(files): overwrite datafile.uuid with new generated uuid in datafile.clone() --- discuss_data/dddatasets/models.py | 1 + 1 file changed, 1 insertion(+) diff --git a/discuss_data/dddatasets/models.py b/discuss_data/dddatasets/models.py index 359706a..b7fb71d 100644 --- a/discuss_data/dddatasets/models.py +++ b/discuss_data/dddatasets/models.py @@ -149,6 +149,7 @@ class DataFile(models.Model): new_file.name = self.file.name df = self df.pk = None + df.uuid = uuid.uuid4() df.file = new_file df.dataset = new_ds df.save() -- GitLab From 031b3ea78ffb6bdae123da5983f250825f4f942c Mon Sep 17 00:00:00 2001 From: "p.fherrma" Date: Tue, 12 May 2020 19:47:06 +0200 Subject: [PATCH 07/54] fix(files): remove two commented out lines in datafile.clone() --- discuss_data/dddatasets/models.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/discuss_data/dddatasets/models.py b/discuss_data/dddatasets/models.py index b7fb71d..067dbf9 100644 --- a/discuss_data/dddatasets/models.py +++ b/discuss_data/dddatasets/models.py @@ -143,7 +143,6 @@ class DataFile(models.Model): return self.name def clone(self, new_ds): - # orig_id = self.id self.save() new_file = ContentFile(self.file.read()) new_file.name = self.file.name @@ -153,7 +152,6 @@ class DataFile(models.Model): df.file = new_file df.dataset = new_ds df.save() - # print("cloned datafile " + str(df.id)) return df class Meta: -- GitLab From 667bd061b26cee931e6a8d92fc11d3d1b7e47851 Mon Sep 17 00:00:00 2001 From: "p.fherrma" Date: Wed, 13 May 2020 08:41:51 +0200 Subject: [PATCH 08/54] feat(notifications): add Notification model and form to ddcomments app --- discuss_data/ddcomments/forms.py | 27 ++++++++++++++----- .../migrations/0009_notification.py | 26 ++++++++++++++++++ discuss_data/ddcomments/models.py | 18 +++++++++++++ 3 files changed, 65 insertions(+), 6 deletions(-) create mode 100644 discuss_data/ddcomments/migrations/0009_notification.py diff --git a/discuss_data/ddcomments/forms.py b/discuss_data/ddcomments/forms.py index 119cd57..afbdf0b 100644 --- a/discuss_data/ddcomments/forms.py +++ b/discuss_data/ddcomments/forms.py @@ -13,7 +13,10 @@ from crispy_forms.layout import ( ) from crispy_forms.bootstrap import FormActions, TabHolder, Tab -from .models import Comment +from .models import ( + Comment, + Notification, +) UPDATED_MSG = """ @@ -40,9 +43,21 @@ class CommentForm(ModelForm): helper.form_show_labels = False helper.layout = Layout( - Div( - Div("text", css_class="col-md-12", rows="3",), - css_class="row", - # Field('text', css_class='col-md-12 row', rows='5', ), - ), + Div(Div("text", css_class="col-md-12", rows="3",), css_class="row",), + ) + + +class NotificationForm(ModelForm): + class Meta: + model = Notification + fields = [ + "text", + ] + + helper = FormHelper() + helper.form_tag = False + helper.form_show_labels = False + + helper.layout = Layout( + Div(Div("text", css_class="col-md-12", rows="3",), css_class="row",), ) diff --git a/discuss_data/ddcomments/migrations/0009_notification.py b/discuss_data/ddcomments/migrations/0009_notification.py new file mode 100644 index 0000000..4773ff4 --- /dev/null +++ b/discuss_data/ddcomments/migrations/0009_notification.py @@ -0,0 +1,26 @@ +# Generated by Django 2.2.11 on 2020-05-12 07:05 + +from django.db import migrations, models +import django_bleach.models +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + ('ddcomments', '0008_auto_20200506_1402'), + ] + + operations = [ + migrations.CreateModel( + name='Notification', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('uuid', models.UUIDField(default=uuid.uuid4, editable=False)), + ('text', django_bleach.models.BleachField()), + ('read', models.BooleanField()), + ('deleted', models.BooleanField()), + ('notification_type', models.CharField(choices=[('USER', 'user'), ('CUR', 'curator')], default='USER', max_length=4)), + ], + ), + ] diff --git a/discuss_data/ddcomments/models.py b/discuss_data/ddcomments/models.py index 44df892..e0f88cf 100644 --- a/discuss_data/ddcomments/models.py +++ b/discuss_data/ddcomments/models.py @@ -48,3 +48,21 @@ class Comment(models.Model): self.owner, self.date_added, ) + + +class Notification(models.Model): + USER = "USER" + CURATOR = "CUR" + + NOTIFICATION_TYPE_CHOICES = [ + (USER, _("user")), + (CURATOR, _("curator")), + ] + + uuid = models.UUIDField(default=uuid.uuid4, editable=False) + text = BleachField() + read = models.BooleanField() + deleted = models.BooleanField() + notification_type = models.CharField( + max_length=4, choices=NOTIFICATION_TYPE_CHOICES, default=USER, + ) -- GitLab From 21ce4dcd5fc8627bb3c49206162fe788303632f1 Mon Sep 17 00:00:00 2001 From: "p.fherrma" Date: Wed, 13 May 2020 08:43:14 +0200 Subject: [PATCH 09/54] feat(notifications): add views for sending access request notifications for restricted datasets --- discuss_data/dddatasets/urls.py | 6 ++++++ discuss_data/dddatasets/views.py | 23 +++++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/discuss_data/dddatasets/urls.py b/discuss_data/dddatasets/urls.py index 748b6e5..1023e88 100644 --- a/discuss_data/dddatasets/urls.py +++ b/discuss_data/dddatasets/urls.py @@ -12,6 +12,12 @@ urlpatterns = [ path("public//", views.dataset_detail, name="detail"), path("public//follow/", views.dataset_follow, name="follow"), path("public//metadata/", views.dataset_metadata, name="metadata"), + path("public//access/", views.dataset_access, name="access"), + path( + "public//access/request/", + views.dataset_access_request, + name="access_request", + ), path("public//files/", views.dataset_files, name="files"), path("public//files/pdf/", views.dataset_files_pdf, name="files_pdf"), path("public//files/zip/", views.dataset_files_zip, name="files_zip"), diff --git a/discuss_data/dddatasets/views.py b/discuss_data/dddatasets/views.py index 915ffff..e9a24e8 100644 --- a/discuss_data/dddatasets/views.py +++ b/discuss_data/dddatasets/views.py @@ -39,6 +39,9 @@ from discuss_data.dddatasets.utils import ( from discuss_data.core.views import core_search_view +from discuss_data.ddcomments.models import Notification +from discuss_data.ddcomments.forms import NotificationForm + @login_required def dataset_search(request): @@ -128,6 +131,26 @@ def dataset_metadata(request, uuid): ) +@login_required +def dataset_access(request, uuid): + ds = check_published(uuid) + return render( + request, + "dddatasets/access.html", + {"ds": ds, "updated": True, "type": "access"}, + ) + + +@login_required +def dataset_access_request(request, uuid): + ds = check_published(uuid) + # check restricted access view permissions for the dataset + if check_perms("ra_view_dataset", request.user, ds): + # user has already access + pass + pass + + @login_required def dataset_files(request, uuid): ds = check_published(uuid) -- GitLab From 71a1ee981e05464cd9f3fc2bd32de6c83b96f391 Mon Sep 17 00:00:00 2001 From: "p.fherrma" Date: Wed, 13 May 2020 08:45:44 +0200 Subject: [PATCH 10/54] feat(notifications): add new public dataset tab access --- .../templates/dddatasets/_nav_datasets.html | 5 +- discuss_data/templates/dddatasets/access.html | 58 +++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 discuss_data/templates/dddatasets/access.html diff --git a/discuss_data/templates/dddatasets/_nav_datasets.html b/discuss_data/templates/dddatasets/_nav_datasets.html index 77bdf37..f2917d7 100644 --- a/discuss_data/templates/dddatasets/_nav_datasets.html +++ b/discuss_data/templates/dddatasets/_nav_datasets.html @@ -1,7 +1,7 @@ {% load i18n %}
-
+
diff --git a/discuss_data/templates/dddatasets/access.html b/discuss_data/templates/dddatasets/access.html new file mode 100644 index 0000000..0eb0fd1 --- /dev/null +++ b/discuss_data/templates/dddatasets/access.html @@ -0,0 +1,58 @@ +{% extends request.is_intercooler|yesno:"blank.html,dddatasets/_base_datasets.html" %} +{% load static i18n %}} + +{% block ic-content %} +
+ {# dataset nav #} + {% include 'dddatasets/_dataset_header.html' with ds=ds type='access' %} + +

{% trans "Access" %}

+ + {% if ds.data_access == ds.OPEN_ACCESS %} +
+
+
{% trans "Open Access" %}
+
+
+

+ +

+
+ +
+ {% elif ds.data_access == ds.RESTRICTED_ACCESS %} +
+
+
{% trans "Restricted Access" %}
+
+
+

+ +

+
+ +
+ {% elif ds.data_access == ds.METADATA_ONLY %} +
+
+
{% trans "Metadata Only" %}
+
+
+

+

+
+ +
+ + {% else %} + {% trans 'Error: No access set' %} + {% endif %} + +
+{% endblock ic-content %} -- GitLab From 87e96afb3f5924084599b4fe48012184aa0abe5a Mon Sep 17 00:00:00 2001 From: "p.fherrma" Date: Wed, 13 May 2020 08:46:55 +0200 Subject: [PATCH 11/54] fix(notifications): increase row width to accomodate new access tab --- discuss_data/templates/dddatasets/_base_datasets.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discuss_data/templates/dddatasets/_base_datasets.html b/discuss_data/templates/dddatasets/_base_datasets.html index 4ccbde2..69be479 100644 --- a/discuss_data/templates/dddatasets/_base_datasets.html +++ b/discuss_data/templates/dddatasets/_base_datasets.html @@ -11,7 +11,7 @@ {% block content %}
-
+
{% block ic-content %} -- GitLab From 9092853ff21e84386ac54e46d8664a26b7687eac Mon Sep 17 00:00:00 2001 From: "p.fherrma" Date: Wed, 13 May 2020 08:47:53 +0200 Subject: [PATCH 12/54] fix(codestyle): apply import cosmetics --- discuss_data/dddatasets/utils.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/discuss_data/dddatasets/utils.py b/discuss_data/dddatasets/utils.py index 314ae08..6b6ac2d 100644 --- a/discuss_data/dddatasets/utils.py +++ b/discuss_data/dddatasets/utils.py @@ -6,7 +6,11 @@ from weasyprint import HTML from actstream import action -from discuss_data.dddatasets.models import DataSet, DataSetManagementObject, DataFile +from discuss_data.dddatasets.models import ( + DataSet, + DataSetManagementObject, + DataFile, +) from discuss_data.pages.models import ManualPage -- GitLab From 09f1ed8d141b7f687c34f2580a3822ef01729d7e Mon Sep 17 00:00:00 2001 From: "p.fherrma" Date: Wed, 13 May 2020 08:49:00 +0200 Subject: [PATCH 13/54] fix(notifications): change request access text on file cards --- discuss_data/templates/dddatasets/_file_card.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discuss_data/templates/dddatasets/_file_card.html b/discuss_data/templates/dddatasets/_file_card.html index 518965b..29d60e0 100644 --- a/discuss_data/templates/dddatasets/_file_card.html +++ b/discuss_data/templates/dddatasets/_file_card.html @@ -22,7 +22,7 @@
{% if filetype == "restricted" %}{% trans 'Restricted Access' %}{% else %}{{ filename }}{% endif %}
-

{% if filetype == "restricted" %}{% trans 'Access to this file is restricted and can be requested by the dataset owner.' %}{% endif %}

+

{% if filetype == "restricted" %}{% trans 'Access to the files of this dataset is restricted and can be requested by the dataset owner.' %}{% endif %}

{% if filename == "RA" %} -- GitLab From ca58bf1bb81b6102dfde5076ab456b4b7d26253a Mon Sep 17 00:00:00 2001 From: "p.fherrma" Date: Wed, 13 May 2020 08:49:55 +0200 Subject: [PATCH 14/54] fix(access): do not iterate trough datafiles when access mode is metadata_only --- discuss_data/templates/dddatasets/files.html | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/discuss_data/templates/dddatasets/files.html b/discuss_data/templates/dddatasets/files.html index d99e8f2..502dd0d 100644 --- a/discuss_data/templates/dddatasets/files.html +++ b/discuss_data/templates/dddatasets/files.html @@ -11,13 +11,15 @@ {% include 'dddatasets/_file_card.html' with fileicon='pdf' fileurl='dddatasets:files_pdf' dsuuid=ds.uuid filename=ds.get_pdf_file_name filetype='metadata' fileformat='archive/pdf' %} {# actual data files #} - {% for file in datafiles %} - {% if file == "RA" %} - {% include 'dddatasets/_file_card.html' with dsuuid=ds.uuid filename="RA" filetype="restricted" %} - {% else %} - {% include 'dddatasets/_file_card.html' with fileurl='dddatasets:files_download' fileuuid=file.uuid dsuuid=ds.uuid filename=file.name filetype=file.get_data_file_type_display fileformat=file.get_file_format %} - {% endif %} - {% endfor %} + {% if not ds.data_access == ds.METADATA_ONLY %} + {% for file in datafiles %} + {% if file == "RA" %} + {% include 'dddatasets/_file_card.html' with dsuuid=ds.uuid filename="RA" filetype="restricted" %} + {% else %} + {% include 'dddatasets/_file_card.html' with fileurl='dddatasets:files_download' fileuuid=file.uuid dsuuid=ds.uuid filename=file.name filetype=file.get_data_file_type_display fileformat=file.get_file_format %} + {% endif %} + {% endfor %} + {% endif %} {# autogenerated zip file #} {% include 'dddatasets/_file_card.html' with fileicon='zip' fileurl='dddatasets:files_zip' dsuuid=ds.uuid filename="All files in a zip file" filetype='archive' fileformat='archive/zip' %} -- GitLab From 61d3a35c838ad937147c2ece41e0d2936eb23523 Mon Sep 17 00:00:00 2001 From: "p.fherrma" Date: Wed, 13 May 2020 12:02:07 +0200 Subject: [PATCH 15/54] feat(notification): add access request notification --- discuss_data/dddatasets/views.py | 24 +++++++++++++++++-- .../ddcomments/_notification_add.html | 10 ++++++++ discuss_data/templates/dddatasets/access.html | 5 ++-- 3 files changed, 34 insertions(+), 5 deletions(-) create mode 100644 discuss_data/templates/ddcomments/_notification_add.html diff --git a/discuss_data/dddatasets/views.py b/discuss_data/dddatasets/views.py index e9a24e8..fc6cbfe 100644 --- a/discuss_data/dddatasets/views.py +++ b/discuss_data/dddatasets/views.py @@ -147,8 +147,28 @@ def dataset_access_request(request, uuid): # check restricted access view permissions for the dataset if check_perms("ra_view_dataset", request.user, ds): # user has already access - pass - pass + return HttpResponse("Access granted already, nothing to do") + if request.method == "POST": + form = NotificationForm(request.POST) + if form.is_valid(): + notification = Notification() + notification = form.save() + print(notification) + return HttpResponse("Access granted") + else: + None + else: + form = NotificationForm() + return render( + request, + "ddcomments/_notification_add.html", + { + "form": form, + "add_url": "dddatasets:access_request", + "object": ds, + "btn_text": "Request Access", + }, + ) @login_required diff --git a/discuss_data/templates/ddcomments/_notification_add.html b/discuss_data/templates/ddcomments/_notification_add.html new file mode 100644 index 0000000..fad5d8b --- /dev/null +++ b/discuss_data/templates/ddcomments/_notification_add.html @@ -0,0 +1,10 @@ +{% load i18n crispy_forms_tags static %} + + +
+ {% csrf_token %} + {% crispy form %} + +
+
+ diff --git a/discuss_data/templates/dddatasets/access.html b/discuss_data/templates/dddatasets/access.html index 0eb0fd1..5ad70a5 100644 --- a/discuss_data/templates/dddatasets/access.html +++ b/discuss_data/templates/dddatasets/access.html @@ -1,5 +1,5 @@ {% extends request.is_intercooler|yesno:"blank.html,dddatasets/_base_datasets.html" %} -{% load static i18n %}} +{% load static i18n %} {% block ic-content %}
@@ -29,11 +29,10 @@

-

{% elif ds.data_access == ds.METADATA_ONLY %} -- GitLab From 96bc36a1e4e86d2a72b552443b19c6f6e9b6a736 Mon Sep 17 00:00:00 2001 From: "p.fherrma" Date: Wed, 13 May 2020 14:51:37 +0200 Subject: [PATCH 16/54] feat(actions): register Notification models for actions --- discuss_data/ddcomments/apps.py | 1 + 1 file changed, 1 insertion(+) diff --git a/discuss_data/ddcomments/apps.py b/discuss_data/ddcomments/apps.py index 9e33001..6aa0e91 100644 --- a/discuss_data/ddcomments/apps.py +++ b/discuss_data/ddcomments/apps.py @@ -8,3 +8,4 @@ class DdcommentsConfig(AppConfig): from actstream import registry registry.register(self.get_model("Comment")) + registry.register(self.get_model("Notification")) -- GitLab From ceace4234d51f1a458764b481acb046836b32cd1 Mon Sep 17 00:00:00 2001 From: "p.fherrma" Date: Wed, 13 May 2020 14:52:49 +0200 Subject: [PATCH 17/54] fix(notifications): set default False for Boolean Fields in Notification model --- .../migrations/0010_auto_20200513_1225.py | 23 +++++++++++++++++++ discuss_data/ddcomments/models.py | 10 ++++++-- 2 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 discuss_data/ddcomments/migrations/0010_auto_20200513_1225.py diff --git a/discuss_data/ddcomments/migrations/0010_auto_20200513_1225.py b/discuss_data/ddcomments/migrations/0010_auto_20200513_1225.py new file mode 100644 index 0000000..78273a6 --- /dev/null +++ b/discuss_data/ddcomments/migrations/0010_auto_20200513_1225.py @@ -0,0 +1,23 @@ +# Generated by Django 2.2.11 on 2020-05-13 12:25 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('ddcomments', '0009_notification'), + ] + + operations = [ + migrations.AlterField( + model_name='notification', + name='deleted', + field=models.BooleanField(default=False), + ), + migrations.AlterField( + model_name='notification', + name='read', + field=models.BooleanField(default=False), + ), + ] diff --git a/discuss_data/ddcomments/models.py b/discuss_data/ddcomments/models.py index e0f88cf..d229607 100644 --- a/discuss_data/ddcomments/models.py +++ b/discuss_data/ddcomments/models.py @@ -60,9 +60,15 @@ class Notification(models.Model): ] uuid = models.UUIDField(default=uuid.uuid4, editable=False) + # owner = models.ForeignKey("ddusers.User", related_name="notification_owner", on_delete=models.PROTECT) + # recipient = models.ForeignKey("ddusers.User", related_name="notification_recipient", on_delete=models.PROTECT) + # date_added = models.DateTimeField(auto_now_add=True) + # reply_to = models.ForeignKey( + # "Notification", on_delete=models.PROTECT, blank=True, null=True + # ) text = BleachField() - read = models.BooleanField() - deleted = models.BooleanField() + read = models.BooleanField(default=False) + deleted = models.BooleanField(default=False) notification_type = models.CharField( max_length=4, choices=NOTIFICATION_TYPE_CHOICES, default=USER, ) -- GitLab From c021bc3ea901d6e6c0d24221f9a9ecc1c11b0c73 Mon Sep 17 00:00:00 2001 From: "p.fherrma" Date: Wed, 13 May 2020 14:54:05 +0200 Subject: [PATCH 18/54] feat(notifications): view and template for dataset access request notification --- discuss_data/dddatasets/views.py | 15 +++++++++++---- .../templates/ddcomments/_notification_add.html | 9 ++++++++- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/discuss_data/dddatasets/views.py b/discuss_data/dddatasets/views.py index fc6cbfe..78e69da 100644 --- a/discuss_data/dddatasets/views.py +++ b/discuss_data/dddatasets/views.py @@ -11,7 +11,9 @@ from django.shortcuts import render from django.utils.translation import gettext as _ from django.contrib.auth.decorators import login_required from django.shortcuts import redirect +from django.contrib import messages +from actstream import action from actstream.actions import ( follow, unfollow, @@ -151,12 +153,17 @@ def dataset_access_request(request, uuid): if request.method == "POST": form = NotificationForm(request.POST) if form.is_valid(): - notification = Notification() notification = form.save() - print(notification) - return HttpResponse("Access granted") + # send an action + action.send( + sender=request.user, + verb="requested access to", + target=ds, + action_object=notification, + ) + messages.success(request, "Request sent") else: - None + messages.warning(request, "An error occured") else: form = NotificationForm() return render( diff --git a/discuss_data/templates/ddcomments/_notification_add.html b/discuss_data/templates/ddcomments/_notification_add.html index fad5d8b..ec72c8e 100644 --- a/discuss_data/templates/ddcomments/_notification_add.html +++ b/discuss_data/templates/ddcomments/_notification_add.html @@ -6,5 +6,12 @@ {% crispy form %} -
+ +{% if messages %} +
    + {% for message in messages %} +
  • {{ message }}
  • + {% endfor %} +
+{% endif %} -- GitLab From 0f1ba41d3800c1de5b1d16ffe8575362ec2fbf74 Mon Sep 17 00:00:00 2001 From: "p.fherrma" Date: Thu, 14 May 2020 13:57:24 +0200 Subject: [PATCH 19/54] feat(notifications): add managers for querying Actions and Notifications --- config/settings/base.py | 13 +++++-- discuss_data/core/managers.py | 68 +++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+), 3 deletions(-) create mode 100644 discuss_data/core/managers.py diff --git a/config/settings/base.py b/config/settings/base.py index 6a95b5a..cf2868b 100644 --- a/config/settings/base.py +++ b/config/settings/base.py @@ -71,7 +71,7 @@ THIRD_PARTY_APPS = [ "actstream", "crispy_forms", "django_activeurl", - 'django_bleach', + "django_bleach", "guardian", "intercoolerjs", "rest_framework", @@ -354,8 +354,15 @@ BLEACH_STRIP_COMMENTS = True sentry_sdk.init( dsn="https://824e439c530d423e989185abee8c5cc2@dev2.discuss-data.net/2", integrations=[DjangoIntegration()], - # If you wish to associate users to errors (assuming you are using # django.contrib.auth) you may enable sending PII data. - send_default_pii=True + send_default_pii=True, ) + +# actstream +ACTSTREAM_SETTINGS = { + "MANAGER": "core.managers.DiscussDataActionManager", + "FETCH_RELATIONS": True, + "USE_JSONFIELD": False, + "GFK_FETCH_DEPTH": 1, +} diff --git a/discuss_data/core/managers.py b/discuss_data/core/managers.py new file mode 100644 index 0000000..955c39a --- /dev/null +++ b/discuss_data/core/managers.py @@ -0,0 +1,68 @@ +from datetime import datetime + +from django.contrib.contenttypes.models import ContentType + +from actstream.managers import ActionManager, stream +from actstream.registry import check + +from django.apps import apps +from django.db.models import Q + + +class DiscussDataActionManager(ActionManager): + @stream + def mystream(self, obj, verb="posted", time=None): + if time is None: + time = datetime.now() + return obj.actor_actions.filter(verb=verb, timestamp__lte=time) + + @stream + def dataset_access_requests_sent(self, sender, recipient=None): + notification_content_type = ContentType.objects.get( + app_label="ddcomments", model="notification" + ) + dataset_content_type = ContentType.objects.get( + app_label="dddatasets", model="dataset" + ) + filters = {"action_object_content_type": notification_content_type.id} + filters["target_content_type"] = dataset_content_type.id + if recipient: # optional recipient + filters["target"] = recipient + # Actions where sender is the actor with extra filters applied + return sender.actor_actions.filter(**filters) + + @stream + def access_requests_received(self, datasets): + notification_content_type = ContentType.objects.get( + app_label="ddcomments", model="notification" + ) + dataset_content_type = ContentType.objects.get( + app_label="dddatasets", model="dataset" + ) + + return self.filter( + action_object_content_type=notification_content_type.id, + target_content_type=dataset_content_type.id, + target_object_id__in=datasets, + ) + + def everything(self, *args, **kwargs): + """ + Return all actions (public=True and False) + """ + return self.filter(*args, **kwargs) + + @stream + def any_everything(self, obj, **kwargs): + """ + Stream of most recent actions where obj is the actor OR target OR action_object. + Includes also Actions which are not public + """ + check(obj) + ctype = ContentType.objects.get_for_model(obj) + return self.everything( + Q(actor_content_type=ctype, actor_object_id=obj.pk,) + | Q(target_content_type=ctype, target_object_id=obj.pk,) + | Q(action_object_content_type=ctype, action_object_object_id=obj.pk,), + **kwargs, + ) -- GitLab From fc193190ae50819d2db8b54e1ab03a5b50992208 Mon Sep 17 00:00:00 2001 From: "p.fherrma" Date: Thu, 14 May 2020 14:00:04 +0200 Subject: [PATCH 20/54] feat(notifications): add urls and views for feeds --- discuss_data/ddcomments/models.py | 22 +++++++++++++ discuss_data/ddusers/urls.py | 5 +-- discuss_data/ddusers/views.py | 55 +++++++++++++++++++++++-------- 3 files changed, 66 insertions(+), 16 deletions(-) diff --git a/discuss_data/ddcomments/models.py b/discuss_data/ddcomments/models.py index d229607..85d73eb 100644 --- a/discuss_data/ddcomments/models.py +++ b/discuss_data/ddcomments/models.py @@ -6,8 +6,11 @@ from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.models import ContentType from django.utils.translation import gettext as _ +from django.contrib.contenttypes.fields import GenericRelation from django_bleach.models import BleachField +from actstream.models import Action + @reversion.register() class Comment(models.Model): @@ -49,6 +52,9 @@ class Comment(models.Model): self.date_added, ) + def class_name(self): + return self.__class__.__name__ + class Notification(models.Model): USER = "USER" @@ -72,3 +78,19 @@ class Notification(models.Model): notification_type = models.CharField( max_length=4, choices=NOTIFICATION_TYPE_CHOICES, default=USER, ) + actions = GenericRelation( + Action, + related_query_name="notification", + content_type_field="action_object_content_type", + object_id_field="action_object_object_id", + ) + + def class_name(self): + return self.__class__.__name__ + + # tags = GenericRelation( + # TaggedItem, + # content_type_field='content_type_fk', + # object_id_field='object_primary_key', + # ) + # GenericRelation(object_id_field='resource_id', content_type_field='resource_type') diff --git a/discuss_data/ddusers/urls.py b/discuss_data/ddusers/urls.py index ff5a488..84fb6c0 100644 --- a/discuss_data/ddusers/urls.py +++ b/discuss_data/ddusers/urls.py @@ -13,8 +13,9 @@ urlpatterns = [ path("public//", views.user_detail, name="detail"), path("follow//", views.user_follow, name="follow"), path("follow/", views.user_follow_status, name="follow_status"), - path("follow/userfeed/", views.user_act_feed, name="user_feed"), - path("follow/datasetfeed/", views.dataset_act_feed, name="dataset_feed"), + path("feed/users/", views.user_act_feed, name="user_feed"), + path("feed/datasets/", views.dataset_act_feed, name="dataset_feed"), + path("feed/notifications", views.notification_act_feed, name="notification_feed"), # path("follow/otherfeed/", views.other_feed, name="other_feed"), path( "public//affiliations/", diff --git a/discuss_data/ddusers/views.py b/discuss_data/ddusers/views.py index 05a56b0..a03ce9c 100644 --- a/discuss_data/ddusers/views.py +++ b/discuss_data/ddusers/views.py @@ -6,6 +6,7 @@ from django.http import Http404 from django.shortcuts import render, redirect from django.utils.translation import gettext as _ from django.contrib.auth.decorators import login_required +from django.apps import apps from actstream.actions import ( follow, @@ -16,6 +17,8 @@ from actstream.models import ( Follow, followers, user_stream, + Action, + model_stream, ) from discuss_data.core.models import KeywordTagged @@ -126,23 +129,47 @@ def following_act_feed(request): raise Http404("Page not found.") +@login_required +def notification_act_feed(request): + model = apps.get_model("dddatasets", "dataset") + user_datasets = list( + model.objects.filter(owner=request.user).values_list("id", flat=True) + ) + + # queries for actions with notification as action object and one of the users datasets as target + # TODO: use a list of ids of datasets where user has admin rights as user_datasets + actions = Action.objects.access_requests_received(user_datasets) + + feed_actions, paginator_range = format_act_feed(request, actions) + feed_type = "notification-act-feed" + + return render( + request, + "ddusers/dashboard.html", + { + "feed_actions": feed_actions, + "paginator_range": paginator_range, + "feed_type": feed_type, + }, + ) + + @login_required def user_act_feed(request): - if request.user.is_authenticated: - feed_actions, paginator_range = format_act_feed( - request, actor_stream(request.user) - ) - feed_type = "user-act-feed" + actions = Action.objects.any_everything(request.user) - return render( - request, - "ddusers/dashboard.html", - { - "feed_actions": feed_actions, - "paginator_range": paginator_range, - "feed_type": feed_type, - }, - ) + feed_actions, paginator_range = format_act_feed(request, actions) + + feed_type = "user-act-feed" + return render( + request, + "ddusers/dashboard.html", + { + "feed_actions": feed_actions, + "paginator_range": paginator_range, + "feed_type": feed_type, + }, + ) @login_required -- GitLab From 319a15d4f159c540e01e7456e03cd89d771db4bc Mon Sep 17 00:00:00 2001 From: "p.fherrma" Date: Thu, 14 May 2020 14:01:01 +0200 Subject: [PATCH 21/54] feat(notifications): add notifications to templates --- discuss_data/templates/actstream/action.html | 9 +++++++-- discuss_data/templates/ddusers/_feed_card.html | 10 +++++++++- discuss_data/templates/ddusers/_nav_dashboard.html | 3 +++ 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/discuss_data/templates/actstream/action.html b/discuss_data/templates/actstream/action.html index e0e18e1..1044bd4 100644 --- a/discuss_data/templates/actstream/action.html +++ b/discuss_data/templates/actstream/action.html @@ -3,12 +3,17 @@ {% if action.actor.get_absolute_url %}{{ action.actor }} {% else %}{{ action.actor }}{% endif %} -{% if action.verb %}{{ action.verb }}{% endif %} +{% if action.verb %} + {{ action.verb }} +{% endif %} {% if action.action_object %} + {% if action.action_object.class_name == 'Notification' %} + {% else %} {{ action.action_object }} + {% endif %} {% endif %} -{% if action.action_object and action.target %}to{% endif %} +{% if action.action_object and action.target %}{% if action.target.class_name == 'DataSet' %}for Dataset{% else %}to{% endif %}{% endif %} {% if action.target %} {{ action.target }} {% endif %} diff --git a/discuss_data/templates/ddusers/_feed_card.html b/discuss_data/templates/ddusers/_feed_card.html index dad916e..205c7d2 100644 --- a/discuss_data/templates/ddusers/_feed_card.html +++ b/discuss_data/templates/ddusers/_feed_card.html @@ -11,7 +11,15 @@
{% if action.target %} - {% if action.target.class_name == 'DataSet' %} + {% if action.action_object %} + {% if action.action_object.class_name == 'Notification' %} +
+
+ {{ action.action_object.text }} +
+
+ {% endif %} + {% elif action.target.class_name == 'DataSet' %} {% include 'dddatasets/_user_dataset.html' with dataset=action.target %} {% endif %} {% endif %} diff --git a/discuss_data/templates/ddusers/_nav_dashboard.html b/discuss_data/templates/ddusers/_nav_dashboard.html index ab4f333..47bd317 100644 --- a/discuss_data/templates/ddusers/_nav_dashboard.html +++ b/discuss_data/templates/ddusers/_nav_dashboard.html @@ -9,6 +9,9 @@
  • +
  • + +
  • \ No newline at end of file -- GitLab From 1348b3df7c4258af416e579c8d7ea3557004c5d9 Mon Sep 17 00:00:00 2001 From: "p.fherrma" Date: Thu, 14 May 2020 14:02:01 +0200 Subject: [PATCH 22/54] feat(notifications): change action verb for sending notification --- discuss_data/dddatasets/views.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/discuss_data/dddatasets/views.py b/discuss_data/dddatasets/views.py index 78e69da..626fd9d 100644 --- a/discuss_data/dddatasets/views.py +++ b/discuss_data/dddatasets/views.py @@ -156,10 +156,11 @@ def dataset_access_request(request, uuid): notification = form.save() # send an action action.send( - sender=request.user, - verb="requested access to", + request.user, + verb="sent access request", target=ds, action_object=notification, + public=False, ) messages.success(request, "Request sent") else: -- GitLab From 30b135c717b15b3f3fd574b482a026ae856c2b60 Mon Sep 17 00:00:00 2001 From: "p.fherrma" Date: Sat, 16 May 2020 09:52:46 +0200 Subject: [PATCH 23/54] feat(perms): add model function and template tag to check if a user has view perm to restrict access dataset --- discuss_data/core/templatetags/core_tags.py | 7 +++++++ discuss_data/dddatasets/models.py | 3 +++ 2 files changed, 10 insertions(+) diff --git a/discuss_data/core/templatetags/core_tags.py b/discuss_data/core/templatetags/core_tags.py index 3a17bd5..b71eb02 100644 --- a/discuss_data/core/templatetags/core_tags.py +++ b/discuss_data/core/templatetags/core_tags.py @@ -1,6 +1,8 @@ from django.urls import resolve from django import template + from discuss_data.ddusers.models import User +from discuss_data.dddatasets.models import DataSet register = template.Library() DEFAULT_USER_IMAGE = "/static/images/user_default.png" @@ -68,3 +70,8 @@ def get_file_icon(mime_str): return "fa-file-" + kind except Exception: return "fa-file" + + +@register.filter +def user_has_restricted_access(dataset, user): + return dataset.user_has_ra_view_right(user) diff --git a/discuss_data/dddatasets/models.py b/discuss_data/dddatasets/models.py index 067dbf9..30f2cef 100644 --- a/discuss_data/dddatasets/models.py +++ b/discuss_data/dddatasets/models.py @@ -442,6 +442,9 @@ class DataSet(models.Model): # get all users with permissions, but exclude owner return get_users_with_perms(self).exclude(uuid=self.owner.uuid) + def user_has_ra_view_right(self, user): + return user.has_perm("ra_view_dataset", self) + def class_name(self): return self.__class__.__name__ -- GitLab From 94691bb1b50ada3970376ee1259cb68e2c638cb8 Mon Sep 17 00:00:00 2001 From: "p.fherrma" Date: Sat, 16 May 2020 09:56:12 +0200 Subject: [PATCH 24/54] feat(perms): dataset admin can grant access to dataset from notification feed --- discuss_data/dddatasets/views_prep.py | 15 ++++++++++++--- discuss_data/templates/ddusers/_feed_card.html | 13 +++++++++++++ 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/discuss_data/dddatasets/views_prep.py b/discuss_data/dddatasets/views_prep.py index 70c2327..00918e1 100644 --- a/discuss_data/dddatasets/views_prep.py +++ b/discuss_data/dddatasets/views_prep.py @@ -75,7 +75,10 @@ from discuss_data.dddatasets.utils import ( from discuss_data.ddpublications.forms import PublicationForm from discuss_data.ddpublications.models import Publication from discuss_data.ddusers.models import Country, User -from discuss_data.ddusers.views import user_search +from discuss_data.ddusers.views import ( + user_search, + notification_act_feed, +) logger = logging.getLogger(__name__) @@ -131,9 +134,12 @@ def prep_dataset_add(request): form = DataSetDetailForm(request.POST, request.FILES, instance=ds) if form.is_valid(): ds = form.save(commit=False) + print("1", DataSet.objects.filter(title=ds.title)) ds.owner = request.user ds.save() + print("2", DataSet.objects.filter(title=ds.title)) form.save_m2m() + print("3", DataSet.objects.filter(title=ds.title)) return redirect("dddatasets:prep_edit", ds_uuid=ds.uuid) else: None @@ -514,6 +520,7 @@ def prep_dataset_edit_access_user(request, ds_uuid): userid = request.POST["userid"] user = User.objects.get(uuid=userid) perm_code = request.POST["perm"] + feed = request.POST.get("requestsrc") if perm_code == "clr": print("clearing perms for", user) @@ -523,8 +530,10 @@ def prep_dataset_edit_access_user(request, ds_uuid): ds.assign_user_permissions(user, "ra_view_dataset") else: logger.error("requested non existing perm_code", perm_code, exc_info=1) - - return prep_dataset_edit_publish(request, ds_uuid) + if feed: + return notification_act_feed(request) + else: + return prep_dataset_edit_publish(request, ds_uuid) @login_required diff --git a/discuss_data/templates/ddusers/_feed_card.html b/discuss_data/templates/ddusers/_feed_card.html index 205c7d2..dae6f18 100644 --- a/discuss_data/templates/ddusers/_feed_card.html +++ b/discuss_data/templates/ddusers/_feed_card.html @@ -18,6 +18,19 @@ {{ action.action_object.text }}
    + {% if action.verb == 'sent access request' %} + {% if action.target|user_has_restricted_access:action.actor %} + + {% else %} +
    + {% csrf_token %} + + + + +
    + {% endif %} + {% endif %} {% endif %} {% elif action.target.class_name == 'DataSet' %} {% include 'dddatasets/_user_dataset.html' with dataset=action.target %} -- GitLab From b6422668be493ea41b7005bcdbe202a7de0f99e7 Mon Sep 17 00:00:00 2001 From: "p.fherrma" Date: Sat, 16 May 2020 09:57:41 +0200 Subject: [PATCH 25/54] fix(i18n): add trans tags --- discuss_data/templates/ddusers/_user_detail_card.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/discuss_data/templates/ddusers/_user_detail_card.html b/discuss_data/templates/ddusers/_user_detail_card.html index c527748..bec0397 100644 --- a/discuss_data/templates/ddusers/_user_detail_card.html +++ b/discuss_data/templates/ddusers/_user_detail_card.html @@ -28,21 +28,21 @@ {% csrf_token %} - + {% elif cardtype == 'pubaccess_search' %}
    {% csrf_token %} - +
    {% elif cardtype == 'pubaccess' %}
    {% csrf_token %} - +
    {% else %} {% if object != request.user %} -- GitLab From 96a3b344ed250f16b04440d99fbff41f2937e9d0 Mon Sep 17 00:00:00 2001 From: "p.fherrma" Date: Sat, 16 May 2020 09:59:20 +0200 Subject: [PATCH 26/54] feat(perms): add User method to query for all datasets a user has admin rights to to use for notification stream --- discuss_data/ddusers/models.py | 9 ++++++--- discuss_data/ddusers/views.py | 9 ++++----- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/discuss_data/ddusers/models.py b/discuss_data/ddusers/models.py index d4d9d21..659ff9b 100644 --- a/discuss_data/ddusers/models.py +++ b/discuss_data/ddusers/models.py @@ -13,6 +13,9 @@ from actstream.models import ( followers, user_stream, ) + +from guardian.shortcuts import get_objects_for_user + from django_bleach.models import BleachField from taggit.managers import TaggableManager @@ -142,9 +145,9 @@ class User(AbstractUser): def get_affiliations(self): return self.affiliation_user.all() - # def get_datasets(self): - # datasets = get_objects_for_user(self, 'view_dataset') - # return datasets #DataSet.objects.filter(user=self).order_by('title') + def get_published_admin_datasets(self): + model = apps.get_model("dddatasets", "dataset") + return get_objects_for_user(self, "admin_dsmo", model).filter(published=True) def get_published_datasets(self): # use get model to avoid circular import diff --git a/discuss_data/ddusers/views.py b/discuss_data/ddusers/views.py index a03ce9c..3029bec 100644 --- a/discuss_data/ddusers/views.py +++ b/discuss_data/ddusers/views.py @@ -131,14 +131,13 @@ def following_act_feed(request): @login_required def notification_act_feed(request): - model = apps.get_model("dddatasets", "dataset") - user_datasets = list( - model.objects.filter(owner=request.user).values_list("id", flat=True) + # list of ids of datasets where user has admin rights + user_admin_datasets = list( + request.user.get_published_admin_datasets().values_list("id", flat=True) ) # queries for actions with notification as action object and one of the users datasets as target - # TODO: use a list of ids of datasets where user has admin rights as user_datasets - actions = Action.objects.access_requests_received(user_datasets) + actions = Action.objects.access_requests_received(user_admin_datasets) feed_actions, paginator_range = format_act_feed(request, actions) feed_type = "notification-act-feed" -- GitLab From b834d8efde4b9cc140c1e2865e16984d58d978ff Mon Sep 17 00:00:00 2001 From: "p.fherrma" Date: Sat, 16 May 2020 10:01:58 +0200 Subject: [PATCH 27/54] feat(perms): point request access button for files to dataset access page --- discuss_data/templates/dddatasets/_file_card.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discuss_data/templates/dddatasets/_file_card.html b/discuss_data/templates/dddatasets/_file_card.html index 29d60e0..712a498 100644 --- a/discuss_data/templates/dddatasets/_file_card.html +++ b/discuss_data/templates/dddatasets/_file_card.html @@ -26,7 +26,7 @@
    {% if filename == "RA" %} - + {% else %} {% endif %} -- GitLab From c719b192b4ad1088d17106a6690f83c6f19215c8 Mon Sep 17 00:00:00 2001 From: "p.fherrma" Date: Sat, 16 May 2020 21:32:19 +0200 Subject: [PATCH 28/54] refactor(search): add underscore to template filename as it is a partial --- discuss_data/core/views.py | 2 +- .../ddusers/{search_results.html => _search_results.html} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename discuss_data/templates/ddusers/{search_results.html => _search_results.html} (100%) diff --git a/discuss_data/core/views.py b/discuss_data/core/views.py index 3da0e7f..2476b29 100644 --- a/discuss_data/core/views.py +++ b/discuss_data/core/views.py @@ -130,7 +130,7 @@ def core_search_view(request, search_index, objects_on_page): else: # default search index is user_index - template = "ddusers/search_results.html" + template = "ddusers/_search_results.html" queryset = index_search( "user_index", query, countries=countries, categories=categories ) diff --git a/discuss_data/templates/ddusers/search_results.html b/discuss_data/templates/ddusers/_search_results.html similarity index 100% rename from discuss_data/templates/ddusers/search_results.html rename to discuss_data/templates/ddusers/_search_results.html -- GitLab From 7657d086c2c0304a3da0aca19ea130954e489652 Mon Sep 17 00:00:00 2001 From: "p.fherrma" Date: Sat, 16 May 2020 21:33:34 +0200 Subject: [PATCH 29/54] fix(license): exclude form for changing license for published dataset --- .../prep/dataset_edit_publish_access.html | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/discuss_data/templates/dddatasets/prep/dataset_edit_publish_access.html b/discuss_data/templates/dddatasets/prep/dataset_edit_publish_access.html index a2692d8..9fd3b73 100644 --- a/discuss_data/templates/dddatasets/prep/dataset_edit_publish_access.html +++ b/discuss_data/templates/dddatasets/prep/dataset_edit_publish_access.html @@ -45,11 +45,14 @@
    @@ -66,11 +69,13 @@
    @@ -87,11 +92,14 @@
    -- GitLab From c4e0ea36689ef62269126808ef9b1bab3fa25dcd Mon Sep 17 00:00:00 2001 From: "p.fherrma" Date: Sat, 16 May 2020 21:37:51 +0200 Subject: [PATCH 30/54] feat(notification): add notification actions; change path to _search_results.html partial --- discuss_data/dddatasets/views_prep.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/discuss_data/dddatasets/views_prep.py b/discuss_data/dddatasets/views_prep.py index 00918e1..635a912 100644 --- a/discuss_data/dddatasets/views_prep.py +++ b/discuss_data/dddatasets/views_prep.py @@ -454,7 +454,7 @@ def prep_dataset_edit_metadata(request, ds_uuid): @login_required def prep_dataset_edit_collaboration(request, ds_uuid): """ - Prep area tab to edit access + Prep area tab to edit collaborators """ ds = DataSet.objects.get(uuid=ds_uuid) dsmo = ds.dataset_management_object @@ -505,7 +505,7 @@ def prep_dataset_edit_access_user_search(request, ds_uuid): return render( request, - "ddusers/search_results.html", + "ddusers/_search_results.html", {"object_list": qs, "ds": ds, "search_type": search_type}, ) @@ -523,17 +523,23 @@ def prep_dataset_edit_access_user(request, ds_uuid): feed = request.POST.get("requestsrc") if perm_code == "clr": - print("clearing perms for", user) + logger.debug("clearing perms for", user) ds.clear_user_permissions(user) + action.send( + request.user, verb="revoked access to", target=ds, public=False, + ) elif perm_code == "acc": - print("assigning perms for", user) + logger.debug("assigning perms for", user) ds.assign_user_permissions(user, "ra_view_dataset") + action.send( + request.user, verb="granted access to", target=ds, public=False, + ) else: logger.error("requested non existing perm_code", perm_code, exc_info=1) if feed: return notification_act_feed(request) else: - return prep_dataset_edit_publish(request, ds_uuid) + return prep_dataset_edit_publish_access(request, ds_uuid) @login_required @@ -546,7 +552,7 @@ def prep_dataset_edit_collaboration_user_search(request, ds_uuid): return render( request, - "ddusers/search_results.html", + "ddusers/_search_results.html", {"object_list": qs, "ds": ds, "search_type": search_type}, ) -- GitLab From fb2a80077e6b7292c7f764cd3d698d572cd10772 Mon Sep 17 00:00:00 2001 From: "p.fherrma" Date: Sat, 16 May 2020 21:41:56 +0200 Subject: [PATCH 31/54] feat(notifications): add notification send form to ddusers:detail; add standalone notification form --- discuss_data/ddusers/urls.py | 27 +++---- discuss_data/ddusers/views.py | 90 ++++++++++++++++++---- discuss_data/templates/ddusers/detail.html | 90 +++++++++++++--------- 3 files changed, 142 insertions(+), 65 deletions(-) diff --git a/discuss_data/ddusers/urls.py b/discuss_data/ddusers/urls.py index 84fb6c0..b4f3e79 100644 --- a/discuss_data/ddusers/urls.py +++ b/discuss_data/ddusers/urls.py @@ -6,29 +6,24 @@ from discuss_data.ddusers import views app_name = "ddusers" -# public URLs + urlpatterns = [ - path("public/search/", views.search, name="search"), - path("public/list/", views.user_list, name="list"), - path("public//", views.user_detail, name="detail"), + # public URLs + path("search/", views.search, name="search"), + path("list/", views.user_list, name="list"), + path("/", views.user_detail, name="detail"), path("follow//", views.user_follow, name="follow"), path("follow/", views.user_follow_status, name="follow_status"), path("feed/users/", views.user_act_feed, name="user_feed"), path("feed/datasets/", views.dataset_act_feed, name="dataset_feed"), path("feed/notifications", views.notification_act_feed, name="notification_feed"), # path("follow/otherfeed/", views.other_feed, name="other_feed"), - path( - "public//affiliations/", - views.user_affiliations, - name="affiliations", - ), - path("public//projects/", views.user_projects, name="projects"), - path( - "public//publications/", - views.user_publications, - name="publications", - ), - path("public//datasets/", views.user_datasets, name="datasets"), + path("/affiliations/", views.user_affiliations, name="affiliations",), + path("/projects/", views.user_projects, name="projects"), + path("/publications/", views.user_publications, name="publications",), + path("/datasets/", views.user_datasets, name="datasets"), + path("/notifications/", views.user_notification, name="notification"), + # edit urls # users search for dataset access control path("edit/search/", views.user_search, name="user_search"), # user profile: edit pages diff --git a/discuss_data/ddusers/views.py b/discuss_data/ddusers/views.py index 3029bec..1a8e4ed 100644 --- a/discuss_data/ddusers/views.py +++ b/discuss_data/ddusers/views.py @@ -7,7 +7,10 @@ from django.shortcuts import render, redirect from django.utils.translation import gettext as _ from django.contrib.auth.decorators import login_required from django.apps import apps +from django.contrib import messages +from django.shortcuts import get_object_or_404 +from actstream import action from actstream.actions import ( follow, unfollow, @@ -43,6 +46,9 @@ from discuss_data.ddusers.models import ( User, ) +from discuss_data.ddcomments.models import Notification +from discuss_data.ddcomments.forms import NotificationForm + logger = logging.getLogger(__name__) @@ -92,16 +98,16 @@ def user_list(request, page=1): def format_act_feed(request, feed, object_list=None): action_list = list() - for action in feed: - if action.target.class_name() == "User": - action_list.append(action) - if action.target.class_name() == "DataSet" and action.target.published: - action_list.append(action) + for act in feed: + if act.target.class_name() == "User": + action_list.append(act) + if act.target.class_name() == "DataSet" and act.target.published: + action_list.append(act) if ( - action.target.class_name() == "DataSetManagementObject" - and action.target.published + act.target.class_name() == "DataSetManagementObject" + and act.target.published ): - action_list.append(action) + action_list.append(act) paginator = Paginator(action_list, 5) page = request.GET.get("page") feed_actions = paginator.get_page(page) @@ -217,11 +223,69 @@ def user_follow_status(request): @login_required def user_detail(request, us_uuid): - try: - user = User.objects.get(uuid=us_uuid) - except Exception: - raise Http404("Page not found.") - return render(request, "ddusers/detail.html", {"user": user}) + user = get_object_or_404(User, uuid=us_uuid) + if request.method == "POST": + form = NotificationForm(request.POST) + if form.is_valid(): + notification = form.save() + # send an action + action.send( + request.user, + verb=_("sent message"), + target=user, + action_object=notification, + public=False, + ) + messages.success(request, _("Message sent")) + else: + messages.warning(request, form.errors) + else: + form = NotificationForm() + + return render( + request, + "ddusers/detail.html", + { + "form": form, + "add_url": "ddusers:detail", + "object": user, + "ictarget": "#ds-content", + "btn_text": _("Send message"), + "user": user, + }, + ) + + +@login_required +def user_notification(request, us_uuid): + user = User.objects.get(uuid=us_uuid) + if request.method == "POST": + form = NotificationForm(request.POST) + if form.is_valid(): + notification = form.save() + # send an action + action.send( + request.user, + verb=_("sent message to"), + target=user, + action_object=notification, + public=False, + ) + messages.success(request, _("Message sent")) + else: + messages.warning(request, form.errors) + else: + form = NotificationForm() + return render( + request, + "ddcomments/_notification_add.html", + { + "form": form, + "add_url": "ddusers:notification", + "object": user, + "btn_text": _("Send message"), + }, + ) @login_required diff --git a/discuss_data/templates/ddusers/detail.html b/discuss_data/templates/ddusers/detail.html index c8bc2f6..60c8b24 100644 --- a/discuss_data/templates/ddusers/detail.html +++ b/discuss_data/templates/ddusers/detail.html @@ -13,44 +13,62 @@ {% include 'dddatasets/_manual_page.html' with manpage=manpage %} {% endif %} -
    -
    - {% if user %} -
    -

    {% trans "Contact" %}

    -

    - {% trans "E-Mail" %}: {{ user.email|urlize }} -

    -

    - {% if user.external_profile %} - {% trans "External Profile" %}: {{ user.external_profile }} - {% endif %} -

    -

    {% trans "Research Profile" %}

    - {% include 'ddusers/_research_profile.html' %} - - {% if user.get_countries %} -

    {% trans "Research Interests by Country" %}

    -

    - {% for country in user.get_countries %} - {{ country }} - {% endfor %} -

    - {% endif %} - - {% if user.get_interests %} -

    {% trans "Keywords" %}

    -

    - {% for keyword in user.get_interests %} - {{ keyword }} - {% endfor %} -

    - {% endif %} + + {% if user %} +

    {% trans "Contact" %}

    + + {% if request.user.is_authenticated %} +

    + {% trans "E-Mail" %}: {{ user.email|urlize }} +

    + + + {% if messages %} +
      + {% for message in messages %} +
    • {{ message }}
    • + {% endfor %} +
    + {% endif %} +
    +
    + {% include "ddcomments/_notification_add.html" with form=form %}
    - {% else %} -

    {% trans "User not found" %}

    +
    + + {% endif %} +

    + {% if user.external_profile %} + {% trans "External Profile" %}: {{ user.external_profile }} + {% endif %} +

    + + +

    {% trans "Research Profile" %}

    + {% include 'ddusers/_research_profile.html' %} + + {% if user.get_countries %} +

    {% trans "Research Interests by Country" %}

    +

    + {% for country in user.get_countries %} + {{ country }} + {% endfor %} +

    {% endif %} -
    + + {% if user.get_interests %} +

    {% trans "Keywords" %}

    +

    + {% for keyword in user.get_interests %} + {{ keyword }} + {% endfor %} +

    + {% endif %} + {% else %} +

    {% trans "User not found" %}

    + {% endif %}
    {% endblock %} -- GitLab From ea74adc6d83db318f24e4c1328fdb62e89dc7b6a Mon Sep 17 00:00:00 2001 From: "p.fherrma" Date: Sat, 16 May 2020 21:46:44 +0200 Subject: [PATCH 32/54] refactor(notifications): change NotificationForm to RequestAccessForm and add new NotificationForm --- discuss_data/ddcomments/forms.py | 17 +++++++++++++++++ discuss_data/dddatasets/views.py | 22 ++++++++++++++++------ 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/discuss_data/ddcomments/forms.py b/discuss_data/ddcomments/forms.py index afbdf0b..ed8ec02 100644 --- a/discuss_data/ddcomments/forms.py +++ b/discuss_data/ddcomments/forms.py @@ -1,3 +1,4 @@ +from django import forms from django.forms import ModelForm from crispy_forms.helper import FormHelper @@ -47,6 +48,22 @@ class CommentForm(ModelForm): ) +class AccessRequestForm(ModelForm): + class Meta: + model = Notification + fields = [ + "text", + ] + + helper = FormHelper() + helper.form_tag = False + helper.form_show_labels = False + + helper.layout = Layout( + Div(Div("text", css_class="col-md-12", rows="3",), css_class="row",), + ) + + class NotificationForm(ModelForm): class Meta: model = Notification diff --git a/discuss_data/dddatasets/views.py b/discuss_data/dddatasets/views.py index 626fd9d..f3dee22 100644 --- a/discuss_data/dddatasets/views.py +++ b/discuss_data/dddatasets/views.py @@ -42,7 +42,7 @@ from discuss_data.dddatasets.utils import ( from discuss_data.core.views import core_search_view from discuss_data.ddcomments.models import Notification -from discuss_data.ddcomments.forms import NotificationForm +from discuss_data.ddcomments.forms import AccessRequestForm @login_required @@ -149,9 +149,14 @@ def dataset_access_request(request, uuid): # check restricted access view permissions for the dataset if check_perms("ra_view_dataset", request.user, ds): # user has already access - return HttpResponse("Access granted already, nothing to do") + return HttpResponse( + ''.format( + _("You have access to this Dataset") + ) + ) + if request.method == "POST": - form = NotificationForm(request.POST) + form = AccessRequestForm(request.POST) if form.is_valid(): notification = form.save() # send an action @@ -162,11 +167,16 @@ def dataset_access_request(request, uuid): action_object=notification, public=False, ) - messages.success(request, "Request sent") + return HttpResponse( + ''.format( + _("Access request sent to the Datasets administrators") + ) + ) else: - messages.warning(request, "An error occured") + messages.warning(request, form.errors) else: - form = NotificationForm() + initial = {"text": "Please grant me access to this Dataset"} + form = AccessRequestForm(initial=initial) return render( request, "ddcomments/_notification_add.html", -- GitLab From 86fbd55872b3bcc17adfb9524f399beea92a1a86 Mon Sep 17 00:00:00 2001 From: "p.fherrma" Date: Sat, 16 May 2020 21:48:55 +0200 Subject: [PATCH 33/54] fix(notification): add ictarget variable to notification form --- discuss_data/templates/ddcomments/_notification_add.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discuss_data/templates/ddcomments/_notification_add.html b/discuss_data/templates/ddcomments/_notification_add.html index ec72c8e..419e6f0 100644 --- a/discuss_data/templates/ddcomments/_notification_add.html +++ b/discuss_data/templates/ddcomments/_notification_add.html @@ -1,7 +1,7 @@ {% load i18n crispy_forms_tags static %} -
    + {% csrf_token %} {% crispy form %} -- GitLab From dca33a97c65232711781e1a52787cc3362e77275 Mon Sep 17 00:00:00 2001 From: "p.fherrma" Date: Sat, 16 May 2020 21:50:13 +0200 Subject: [PATCH 34/54] refactor(access): code cosmetics in template --- discuss_data/templates/dddatasets/access.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/discuss_data/templates/dddatasets/access.html b/discuss_data/templates/dddatasets/access.html index 5ad70a5..6eda5d2 100644 --- a/discuss_data/templates/dddatasets/access.html +++ b/discuss_data/templates/dddatasets/access.html @@ -23,7 +23,7 @@
    {% elif ds.data_access == ds.RESTRICTED_ACCESS %} -
    +
    {% trans "Restricted Access" %}
    @@ -32,7 +32,7 @@

    {% elif ds.data_access == ds.METADATA_ONLY %} @@ -50,7 +50,7 @@
    {% else %} - {% trans 'Error: No access set' %} + {% trans 'Error: No access set' %} {% endif %}
    -- GitLab From 1372b31c4d486c171c72091ca099845b088363a6 Mon Sep 17 00:00:00 2001 From: "p.fherrma" Date: Mon, 18 May 2020 09:32:38 +0200 Subject: [PATCH 35/54] feat(perms): add DataSet method and template tag to check if a user has admin rights on ds --- discuss_data/core/templatetags/core_tags.py | 5 +++++ discuss_data/dddatasets/models.py | 3 +++ 2 files changed, 8 insertions(+) diff --git a/discuss_data/core/templatetags/core_tags.py b/discuss_data/core/templatetags/core_tags.py index b71eb02..bcbab81 100644 --- a/discuss_data/core/templatetags/core_tags.py +++ b/discuss_data/core/templatetags/core_tags.py @@ -75,3 +75,8 @@ def get_file_icon(mime_str): @register.filter def user_has_restricted_access(dataset, user): return dataset.user_has_ra_view_right(user) + + +@register.filter +def user_has_admin_right(dataset, user): + return dataset.user_has_admin_right(user) diff --git a/discuss_data/dddatasets/models.py b/discuss_data/dddatasets/models.py index 30f2cef..8d323b4 100644 --- a/discuss_data/dddatasets/models.py +++ b/discuss_data/dddatasets/models.py @@ -445,6 +445,9 @@ class DataSet(models.Model): def user_has_ra_view_right(self, user): return user.has_perm("ra_view_dataset", self) + def user_has_admin_right(self, user): + return user.has_perm("admin_dsmo", self.dataset_management_object) + def class_name(self): return self.__class__.__name__ -- GitLab From 6a5c82afb6e705e59efcd736d11a12122328a271 Mon Sep 17 00:00:00 2001 From: "p.fherrma" Date: Mon, 18 May 2020 09:34:05 +0200 Subject: [PATCH 36/54] feat(messages): integrate django messages in notification form --- discuss_data/ddcomments/forms.py | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/discuss_data/ddcomments/forms.py b/discuss_data/ddcomments/forms.py index ed8ec02..d2f6956 100644 --- a/discuss_data/ddcomments/forms.py +++ b/discuss_data/ddcomments/forms.py @@ -1,4 +1,5 @@ from django import forms +from django.contrib import messages from django.forms import ModelForm from crispy_forms.helper import FormHelper @@ -20,11 +21,24 @@ from .models import ( ) +MSG = """ +{% if messages %} + {% for message in messages %} + {% if message.level == DEFAULT_MESSAGE_LEVELS.SUCCESS %} + + {% endif %} + {% if message.level == DEFAULT_MESSAGE_LEVELS.ERROR %} + + {% endif %} + {% endfor %} +{% endif %} +""" + UPDATED_MSG = """ {% if updated %}{% endif %} - {% if err %} {% endif %}""" + {{ target|title }} saved
    {% endif %}""" ADD_EDIT_HEADING = ( "

    {% if new %}Add{% else %}Edit{% endif %} {{ target|title }}


    " @@ -60,7 +74,9 @@ class AccessRequestForm(ModelForm): helper.form_show_labels = False helper.layout = Layout( - Div(Div("text", css_class="col-md-12", rows="3",), css_class="row",), + Div( + Div("text", css_class="col-md-12 notification", rows="3",), css_class="row", + ), ) @@ -76,5 +92,5 @@ class NotificationForm(ModelForm): helper.form_show_labels = False helper.layout = Layout( - Div(Div("text", css_class="col-md-12", rows="3",), css_class="row",), + Div(Div("text", css_class="col-md-12", rows="3",), css_class="row",), HTML(MSG), ) -- GitLab From d71349bb28b4569ecea6d142d7639851e2446ddb Mon Sep 17 00:00:00 2001 From: "p.fherrma" Date: Mon, 18 May 2020 09:36:09 +0200 Subject: [PATCH 37/54] fix(notifications): add actual target to actions --- discuss_data/dddatasets/views_prep.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/discuss_data/dddatasets/views_prep.py b/discuss_data/dddatasets/views_prep.py index 635a912..fef265d 100644 --- a/discuss_data/dddatasets/views_prep.py +++ b/discuss_data/dddatasets/views_prep.py @@ -526,13 +526,21 @@ def prep_dataset_edit_access_user(request, ds_uuid): logger.debug("clearing perms for", user) ds.clear_user_permissions(user) action.send( - request.user, verb="revoked access to", target=ds, public=False, + request.user, + verb="revoked access", + action_object=ds, + target=user, + public=False, ) elif perm_code == "acc": logger.debug("assigning perms for", user) ds.assign_user_permissions(user, "ra_view_dataset") action.send( - request.user, verb="granted access to", target=ds, public=False, + request.user, + verb="granted access", + action_object=ds, + target=user, + public=False, ) else: logger.error("requested non existing perm_code", perm_code, exc_info=1) -- GitLab From 66dbf8da4e25d7b9715f02bd0587173563ed7aa2 Mon Sep 17 00:00:00 2001 From: "p.fherrma" Date: Mon, 18 May 2020 10:00:10 +0200 Subject: [PATCH 38/54] feat(notifications): add combined query for notifications with target user and datasets administered by the user --- discuss_data/ddusers/views.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/discuss_data/ddusers/views.py b/discuss_data/ddusers/views.py index 1a8e4ed..cb24b7d 100644 --- a/discuss_data/ddusers/views.py +++ b/discuss_data/ddusers/views.py @@ -143,8 +143,16 @@ def notification_act_feed(request): ) # queries for actions with notification as action object and one of the users datasets as target - actions = Action.objects.access_requests_received(user_admin_datasets) + actions_ar = Action.objects.access_requests_received(user_admin_datasets) + notification_content_type = ContentType.objects.get( + app_label="ddcomments", model="notification" + ) + actions_notifications = Action.objects.any_everything(request.user).filter( + action_object_content_type=notification_content_type.id, + ) + + actions = actions_ar | actions_notifications feed_actions, paginator_range = format_act_feed(request, actions) feed_type = "notification-act-feed" @@ -283,6 +291,7 @@ def user_notification(request, us_uuid): "form": form, "add_url": "ddusers:notification", "object": user, + "ictarget": None, "btn_text": _("Send message"), }, ) -- GitLab From e3e2cce6fb1ebcf190000f67e89d8889e17076e3 Mon Sep 17 00:00:00 2001 From: "p.fherrma" Date: Mon, 18 May 2020 10:02:01 +0200 Subject: [PATCH 39/54] feat(notifications): author notifications in user profile and feed --- discuss_data/static/sass/project.scss | 10 +++++++++ discuss_data/templates/actstream/action.html | 4 +++- .../ddcomments/_notification_add.html | 13 ++---------- .../templates/ddusers/_feed_card.html | 6 +++++- discuss_data/templates/ddusers/detail.html | 21 +++---------------- 5 files changed, 23 insertions(+), 31 deletions(-) diff --git a/discuss_data/static/sass/project.scss b/discuss_data/static/sass/project.scss index 461c757..185fd88 100644 --- a/discuss_data/static/sass/project.scss +++ b/discuss_data/static/sass/project.scss @@ -278,7 +278,17 @@ body { color: theme-color("primary"); } +/* + * Notifications + */ + +.notification textarea.form-control { + height: 5rem; +} +.notification .form-control { + width: 30%; +} /* * Sidebar diff --git a/discuss_data/templates/actstream/action.html b/discuss_data/templates/actstream/action.html index 1044bd4..1844b04 100644 --- a/discuss_data/templates/actstream/action.html +++ b/discuss_data/templates/actstream/action.html @@ -13,7 +13,9 @@ {{ action.action_object }} {% endif %} {% endif %} -{% if action.action_object and action.target %}{% if action.target.class_name == 'DataSet' %}for Dataset{% else %}to{% endif %}{% endif %} +{% if action.action_object and action.target %} + {% if action.target.class_name == 'DataSet' %}for Dataset{% else %}to{% endif %} +{% endif %} {% if action.target %} {{ action.target }} {% endif %} diff --git a/discuss_data/templates/ddcomments/_notification_add.html b/discuss_data/templates/ddcomments/_notification_add.html index 419e6f0..99c87ce 100644 --- a/discuss_data/templates/ddcomments/_notification_add.html +++ b/discuss_data/templates/ddcomments/_notification_add.html @@ -1,17 +1,8 @@ {% load i18n crispy_forms_tags static %} - + {% csrf_token %} {% crispy form %} - - -{% if messages %} -
      - {% for message in messages %} -
    • {{ message }}
    • - {% endfor %} -
    -{% endif %} - + \ No newline at end of file diff --git a/discuss_data/templates/ddusers/_feed_card.html b/discuss_data/templates/ddusers/_feed_card.html index dae6f18..1f7ae80 100644 --- a/discuss_data/templates/ddusers/_feed_card.html +++ b/discuss_data/templates/ddusers/_feed_card.html @@ -18,10 +18,14 @@ {{ action.action_object.text }} +
    + +
    + {% if action.verb == 'sent access request' %} {% if action.target|user_has_restricted_access:action.actor %} - {% else %} + {% elif action.target|user_has_admin_right:request.user %}
    {% csrf_token %} diff --git a/discuss_data/templates/ddusers/detail.html b/discuss_data/templates/ddusers/detail.html index 60c8b24..0a1e376 100644 --- a/discuss_data/templates/ddusers/detail.html +++ b/discuss_data/templates/ddusers/detail.html @@ -18,27 +18,12 @@

    {% trans "Contact" %}

    {% if request.user.is_authenticated %} -

    - {% trans "E-Mail" %}: {{ user.email|urlize }} -

    - - - {% if messages %} -
      - {% for message in messages %} -
    • {{ message }}
    • - {% endfor %} -
    - {% endif %} -
    -
    +

    {% trans "E-Mail" %}: {{ user.email|urlize }}

    +
    {% include "ddcomments/_notification_add.html" with form=form %}
    -
    - {% endif %} +

    {% if user.external_profile %} {% trans "External Profile" %}: {{ user.external_profile }} -- GitLab From 35b9ccbbec59f55416eb3697ea6a8109d6203773 Mon Sep 17 00:00:00 2001 From: "p.fherrma" Date: Fri, 22 May 2020 13:21:16 +0200 Subject: [PATCH 40/54] dependencies(mptt): add django-mptt to the project, needed for comment/notification threads --- config/settings/base.py | 1 + requirements/base.txt | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/config/settings/base.py b/config/settings/base.py index cf2868b..42ed1cc 100644 --- a/config/settings/base.py +++ b/config/settings/base.py @@ -79,6 +79,7 @@ THIRD_PARTY_APPS = [ "shibboleth", "taggit", "django_elasticsearch_dsl", + "mptt", ] LOCAL_APPS = [ diff --git a/requirements/base.txt b/requirements/base.txt index a7c3f6a..a390cd7 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -53,4 +53,7 @@ wagtailmenus==3.0.1 sentry-sdk==0.14.3 # filetype -filetype==1.0.7 \ No newline at end of file +filetype==1.0.7 + +# django-mptt +django-mptt==0.11.0 \ No newline at end of file -- GitLab From 8231cb9a514ea8f358c6cf847825bb05f90323fb Mon Sep 17 00:00:00 2001 From: "p.fherrma" Date: Fri, 22 May 2020 13:28:38 +0200 Subject: [PATCH 41/54] fix(permissions): set False as default return value for permissions check --- discuss_data/core/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discuss_data/core/views.py b/discuss_data/core/views.py index 2476b29..e2e0d62 100644 --- a/discuss_data/core/views.py +++ b/discuss_data/core/views.py @@ -217,7 +217,6 @@ def landing_page(request): def check_perms(permission, user, dd_obj): - logger.debug("start checks_perms") try: if dd_obj.owner == user: logger.debug("is owner") @@ -233,6 +232,7 @@ def check_perms(permission, user, dd_obj): else: logger.debug("%s lacks perm %s on %s" % (user, permission, dd_obj)) return False + return False def check_perms_403(permission, user, dd_obj): -- GitLab From a70005e14f3152e0dc4e1f6b99182184dbcc0e9e Mon Sep 17 00:00:00 2001 From: "p.fherrma" Date: Fri, 22 May 2020 13:30:08 +0200 Subject: [PATCH 42/54] feat(forms): add template tags to get the parent in comment tree and to dynamically prepopulate a django form instance with template context data --- discuss_data/core/templatetags/core_tags.py | 71 +++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/discuss_data/core/templatetags/core_tags.py b/discuss_data/core/templatetags/core_tags.py index bcbab81..bc72ea0 100644 --- a/discuss_data/core/templatetags/core_tags.py +++ b/discuss_data/core/templatetags/core_tags.py @@ -4,6 +4,13 @@ from django import template from discuss_data.ddusers.models import User from discuss_data.dddatasets.models import DataSet +from discuss_data.ddcomments.forms import ( + NotificationForm, + CommentForm, +) + +from discuss_data.ddcomments.models import Notification + register = template.Library() DEFAULT_USER_IMAGE = "/static/images/user_default.png" @@ -80,3 +87,67 @@ def user_has_restricted_access(dataset, user): @register.filter def user_has_admin_right(dataset, user): return dataset.user_has_admin_right(user) + + +@register.filter +def prepare_notification_form(comment, user): + if comment.notification_type == Notification.ACCESS_REQUEST: + recipient_uuid = comment.owner.uuid + else: + if comment.owner: + recipient_uuid = comment.recipient.uuid + else: + recipient_uuid = comment.owner.uuid + parent_uuid = None + if comment.level > 0: + if not comment.get_next_sibling(): + if comment.parent: + parent_uuid = comment.parent.uuid + initial = { + "recipient_uuid": recipient_uuid, + "parent_uuid": parent_uuid, + } + return NotificationForm(initial=initial) + else: + if comment.is_leaf_node(): + parent_uuid = comment.uuid + initial = { + "recipient_uuid": recipient_uuid, + "parent_uuid": parent_uuid, + } + return NotificationForm(initial=initial) + return None + + +@register.filter +def prepare_comment_form(comment, user): + parent_uuid = None + if comment.level > 0: + if not comment.get_next_sibling(): + if comment.parent: + parent_uuid = comment.parent.uuid + initial = { + "parent_uuid": parent_uuid, + } + return CommentForm(initial=initial) + else: + if comment.is_leaf_node(): + parent_uuid = comment.uuid + initial = { + "parent_uuid": parent_uuid, + } + return CommentForm(initial=initial) + return None + + +@register.filter +def get_parent_comment(comment): + parent = None + if comment.level > 0: + if not comment.get_next_sibling(): + if comment.parent: + parent = comment.parent + else: + if comment.is_leaf_node(): + parent = comment + return parent -- GitLab From 3f42fc27dac57350a6bb5aaa6d30643205e32e9c Mon Sep 17 00:00:00 2001 From: "p.fherrma" Date: Fri, 22 May 2020 13:45:45 +0200 Subject: [PATCH 43/54] feat(comment_threads): change Notification and Comment models into subclasses of MPTTModel to allow threading --- .../ddcomments/migrations/0011_ddcomment.py | 44 +++++++ .../migrations/0012_auto_20200520_0802.py | 23 ++++ .../migrations/0013_delete_notification.py | 16 +++ .../migrations/0014_auto_20200520_0917.py | 20 +++ .../migrations/0015_auto_20200520_0919.py | 18 +++ .../migrations/0016_notification_permanent.py | 18 +++ .../migrations/0017_auto_20200520_1134.py | 18 +++ .../0018_remove_notification_doi.py | 17 +++ .../migrations/0019_auto_20200520_1139.py | 59 +++++++++ .../migrations/0020_auto_20200520_1410.py | 18 +++ .../migrations/0021_delete_comment.py | 16 +++ .../migrations/0022_auto_20200520_1436.py | 20 +++ .../migrations/0023_comment_deleted.py | 18 +++ discuss_data/ddcomments/models.py | 115 ++++++++++++------ 14 files changed, 382 insertions(+), 38 deletions(-) create mode 100644 discuss_data/ddcomments/migrations/0011_ddcomment.py create mode 100644 discuss_data/ddcomments/migrations/0012_auto_20200520_0802.py create mode 100644 discuss_data/ddcomments/migrations/0013_delete_notification.py create mode 100644 discuss_data/ddcomments/migrations/0014_auto_20200520_0917.py create mode 100644 discuss_data/ddcomments/migrations/0015_auto_20200520_0919.py create mode 100644 discuss_data/ddcomments/migrations/0016_notification_permanent.py create mode 100644 discuss_data/ddcomments/migrations/0017_auto_20200520_1134.py create mode 100644 discuss_data/ddcomments/migrations/0018_remove_notification_doi.py create mode 100644 discuss_data/ddcomments/migrations/0019_auto_20200520_1139.py create mode 100644 discuss_data/ddcomments/migrations/0020_auto_20200520_1410.py create mode 100644 discuss_data/ddcomments/migrations/0021_delete_comment.py create mode 100644 discuss_data/ddcomments/migrations/0022_auto_20200520_1436.py create mode 100644 discuss_data/ddcomments/migrations/0023_comment_deleted.py diff --git a/discuss_data/ddcomments/migrations/0011_ddcomment.py b/discuss_data/ddcomments/migrations/0011_ddcomment.py new file mode 100644 index 0000000..cf498da --- /dev/null +++ b/discuss_data/ddcomments/migrations/0011_ddcomment.py @@ -0,0 +1,44 @@ +# Generated by Django 2.2.11 on 2020-05-18 09:45 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import django_bleach.models +import mptt.fields +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('contenttypes', '0002_remove_content_type_name'), + ('ddcomments', '0010_auto_20200513_1225'), + ] + + operations = [ + migrations.CreateModel( + name='DDComment', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('uuid', models.UUIDField(default=uuid.uuid4, editable=False)), + ('comment_type', models.CharField(choices=[('PUB', 'public'), ('PRI', 'private'), ('CUR', 'curator'), ('PERM', 'permanent'), ('AR', 'access request')], default='PUB', max_length=4)), + ('doi', models.CharField(blank=True, max_length=200)), + ('text', django_bleach.models.BleachField()), + ('date_added', models.DateTimeField(auto_now_add=True)), + ('date_edited', models.DateTimeField(auto_now=True)), + ('object_id', models.IntegerField()), + ('lft', models.PositiveIntegerField(editable=False)), + ('rght', models.PositiveIntegerField(editable=False)), + ('tree_id', models.PositiveIntegerField(db_index=True, editable=False)), + ('level', models.PositiveIntegerField(editable=False)), + ('content_type', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='contenttypes.ContentType')), + ('owner', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='comment_owner', to=settings.AUTH_USER_MODEL)), + ('parent', mptt.fields.TreeForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='children', to='ddcomments.DDComment')), + ('recipient', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='comment_recipient', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/discuss_data/ddcomments/migrations/0012_auto_20200520_0802.py b/discuss_data/ddcomments/migrations/0012_auto_20200520_0802.py new file mode 100644 index 0000000..6c68f37 --- /dev/null +++ b/discuss_data/ddcomments/migrations/0012_auto_20200520_0802.py @@ -0,0 +1,23 @@ +# Generated by Django 2.2.11 on 2020-05-20 08:02 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('ddcomments', '0011_ddcomment'), + ] + + operations = [ + migrations.AddField( + model_name='comment', + name='deleted', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='comment', + name='read', + field=models.BooleanField(default=False), + ), + ] diff --git a/discuss_data/ddcomments/migrations/0013_delete_notification.py b/discuss_data/ddcomments/migrations/0013_delete_notification.py new file mode 100644 index 0000000..7a4d803 --- /dev/null +++ b/discuss_data/ddcomments/migrations/0013_delete_notification.py @@ -0,0 +1,16 @@ +# Generated by Django 2.2.11 on 2020-05-20 09:13 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('ddcomments', '0012_auto_20200520_0802'), + ] + + operations = [ + migrations.DeleteModel( + name='Notification', + ), + ] diff --git a/discuss_data/ddcomments/migrations/0014_auto_20200520_0917.py b/discuss_data/ddcomments/migrations/0014_auto_20200520_0917.py new file mode 100644 index 0000000..27d3717 --- /dev/null +++ b/discuss_data/ddcomments/migrations/0014_auto_20200520_0917.py @@ -0,0 +1,20 @@ +# Generated by Django 2.2.11 on 2020-05-20 09:17 + +from django.conf import settings +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('contenttypes', '0002_remove_content_type_name'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('ddcomments', '0013_delete_notification'), + ] + + operations = [ + migrations.RenameModel( + old_name='DDComment', + new_name='Notification', + ), + ] diff --git a/discuss_data/ddcomments/migrations/0015_auto_20200520_0919.py b/discuss_data/ddcomments/migrations/0015_auto_20200520_0919.py new file mode 100644 index 0000000..c0c9711 --- /dev/null +++ b/discuss_data/ddcomments/migrations/0015_auto_20200520_0919.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.11 on 2020-05-20 09:19 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('ddcomments', '0014_auto_20200520_0917'), + ] + + operations = [ + migrations.RenameField( + model_name='notification', + old_name='comment_type', + new_name='notification_type', + ), + ] diff --git a/discuss_data/ddcomments/migrations/0016_notification_permanent.py b/discuss_data/ddcomments/migrations/0016_notification_permanent.py new file mode 100644 index 0000000..beb2868 --- /dev/null +++ b/discuss_data/ddcomments/migrations/0016_notification_permanent.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.11 on 2020-05-20 11:33 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('ddcomments', '0015_auto_20200520_0919'), + ] + + operations = [ + migrations.AddField( + model_name='notification', + name='permanent', + field=models.BooleanField(default=False), + ), + ] diff --git a/discuss_data/ddcomments/migrations/0017_auto_20200520_1134.py b/discuss_data/ddcomments/migrations/0017_auto_20200520_1134.py new file mode 100644 index 0000000..48ba5b1 --- /dev/null +++ b/discuss_data/ddcomments/migrations/0017_auto_20200520_1134.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.11 on 2020-05-20 11:34 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('ddcomments', '0016_notification_permanent'), + ] + + operations = [ + migrations.AlterField( + model_name='notification', + name='notification_type', + field=models.CharField(choices=[('PUB', 'public'), ('PRI', 'private'), ('CUR', 'curator'), ('AR', 'access request')], default='PUB', max_length=4), + ), + ] diff --git a/discuss_data/ddcomments/migrations/0018_remove_notification_doi.py b/discuss_data/ddcomments/migrations/0018_remove_notification_doi.py new file mode 100644 index 0000000..1483521 --- /dev/null +++ b/discuss_data/ddcomments/migrations/0018_remove_notification_doi.py @@ -0,0 +1,17 @@ +# Generated by Django 2.2.11 on 2020-05-20 11:35 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('ddcomments', '0017_auto_20200520_1134'), + ] + + operations = [ + migrations.RemoveField( + model_name='notification', + name='doi', + ), + ] diff --git a/discuss_data/ddcomments/migrations/0019_auto_20200520_1139.py b/discuss_data/ddcomments/migrations/0019_auto_20200520_1139.py new file mode 100644 index 0000000..26c3e1b --- /dev/null +++ b/discuss_data/ddcomments/migrations/0019_auto_20200520_1139.py @@ -0,0 +1,59 @@ +# Generated by Django 2.2.11 on 2020-05-20 11:39 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import django_bleach.models +import mptt.fields +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + ('contenttypes', '0002_remove_content_type_name'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('ddcomments', '0018_remove_notification_doi'), + ] + + operations = [ + migrations.AlterField( + model_name='notification', + name='owner', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='notification_owner', to=settings.AUTH_USER_MODEL), + ), + migrations.AlterField( + model_name='notification', + name='parent', + field=mptt.fields.TreeForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='notification_children', to='ddcomments.Notification'), + ), + migrations.AlterField( + model_name='notification', + name='recipient', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='notification_recipient', to=settings.AUTH_USER_MODEL), + ), + migrations.CreateModel( + name='Comment2', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('uuid', models.UUIDField(default=uuid.uuid4, editable=False)), + ('doi', models.CharField(blank=True, max_length=200)), + ('notification_type', models.CharField(choices=[('PUB', 'public'), ('PRI', 'private'), ('CUR', 'curator'), ('PERM', 'permanent')], default='PUB', max_length=4)), + ('text', django_bleach.models.BleachField()), + ('date_added', models.DateTimeField(auto_now_add=True)), + ('date_edited', models.DateTimeField(auto_now=True)), + ('permanent', models.BooleanField(default=False)), + ('object_id', models.IntegerField()), + ('lft', models.PositiveIntegerField(editable=False)), + ('rght', models.PositiveIntegerField(editable=False)), + ('tree_id', models.PositiveIntegerField(db_index=True, editable=False)), + ('level', models.PositiveIntegerField(editable=False)), + ('content_type', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='contenttypes.ContentType')), + ('owner', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='comment_owner', to=settings.AUTH_USER_MODEL)), + ('parent', mptt.fields.TreeForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='comment_children', to='ddcomments.Comment2')), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/discuss_data/ddcomments/migrations/0020_auto_20200520_1410.py b/discuss_data/ddcomments/migrations/0020_auto_20200520_1410.py new file mode 100644 index 0000000..cd5b57c --- /dev/null +++ b/discuss_data/ddcomments/migrations/0020_auto_20200520_1410.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.11 on 2020-05-20 14:10 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('ddcomments', '0019_auto_20200520_1139'), + ] + + operations = [ + migrations.RenameField( + model_name='comment2', + old_name='notification_type', + new_name='comment_type', + ), + ] diff --git a/discuss_data/ddcomments/migrations/0021_delete_comment.py b/discuss_data/ddcomments/migrations/0021_delete_comment.py new file mode 100644 index 0000000..ef4c400 --- /dev/null +++ b/discuss_data/ddcomments/migrations/0021_delete_comment.py @@ -0,0 +1,16 @@ +# Generated by Django 2.2.11 on 2020-05-20 14:33 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('ddcomments', '0020_auto_20200520_1410'), + ] + + operations = [ + migrations.DeleteModel( + name='Comment', + ), + ] diff --git a/discuss_data/ddcomments/migrations/0022_auto_20200520_1436.py b/discuss_data/ddcomments/migrations/0022_auto_20200520_1436.py new file mode 100644 index 0000000..0263df0 --- /dev/null +++ b/discuss_data/ddcomments/migrations/0022_auto_20200520_1436.py @@ -0,0 +1,20 @@ +# Generated by Django 2.2.11 on 2020-05-20 14:36 + +from django.conf import settings +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('contenttypes', '0002_remove_content_type_name'), + ('ddcomments', '0021_delete_comment'), + ] + + operations = [ + migrations.RenameModel( + old_name='Comment2', + new_name='Comment', + ), + ] diff --git a/discuss_data/ddcomments/migrations/0023_comment_deleted.py b/discuss_data/ddcomments/migrations/0023_comment_deleted.py new file mode 100644 index 0000000..7a57452 --- /dev/null +++ b/discuss_data/ddcomments/migrations/0023_comment_deleted.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.11 on 2020-05-20 14:55 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('ddcomments', '0022_auto_20200520_1436'), + ] + + operations = [ + migrations.AddField( + model_name='comment', + name='deleted', + field=models.BooleanField(default=False), + ), + ] diff --git a/discuss_data/ddcomments/models.py b/discuss_data/ddcomments/models.py index 85d73eb..93569ec 100644 --- a/discuss_data/ddcomments/models.py +++ b/discuss_data/ddcomments/models.py @@ -7,36 +7,56 @@ from django.contrib.contenttypes.models import ContentType from django.utils.translation import gettext as _ from django.contrib.contenttypes.fields import GenericRelation + +from mptt.models import MPTTModel, TreeForeignKey + from django_bleach.models import BleachField from actstream.models import Action -@reversion.register() -class Comment(models.Model): +class Notification(MPTTModel): + """ + Notification with tree-traversal support for threads + """ + PUBLIC = "PUB" PRIVATE = "PRI" CURATOR = "CUR" - PERMANENT = "PERM" + ACCESS_REQUEST = "AR" - COMMENT_TYPE_CHOICES = [ + NOTIFICATION_TYPE_CHOICES = [ (PUBLIC, _("public")), (PRIVATE, _("private")), (CURATOR, _("curator")), - (PERMANENT, _("permanent")), + (ACCESS_REQUEST, _("access request")), ] uuid = models.UUIDField(default=uuid.uuid4, editable=False) - comment_type = models.CharField( - max_length=4, choices=COMMENT_TYPE_CHOICES, default=PUBLIC, + notification_type = models.CharField( + max_length=4, choices=NOTIFICATION_TYPE_CHOICES, default=PUBLIC, ) - doi = models.CharField(max_length=200, blank=True) text = BleachField() date_added = models.DateTimeField(auto_now_add=True) date_edited = models.DateTimeField(auto_now=True) - owner = models.ForeignKey("ddusers.User", on_delete=models.PROTECT) - reply_to = models.ForeignKey( - "Comment", on_delete=models.PROTECT, blank=True, null=True + owner = models.ForeignKey( + "ddusers.User", related_name="notification_owner", on_delete=models.PROTECT + ) + recipient = models.ForeignKey( + "ddusers.User", + related_name="notification_recipient", + on_delete=models.PROTECT, + null=True, + blank=True, + ) + permanent = models.BooleanField(default=False) + + parent = TreeForeignKey( + "self", + on_delete=models.CASCADE, + null=True, + blank=True, + related_name="notification_children", ) # mandatory fields for generic relation @@ -45,7 +65,8 @@ class Comment(models.Model): content_object = GenericForeignKey() def __str__(self): - return "%s %s, %s (%s)" % ( + return "[%s] %s %s (%s), %s" % ( + self.get_notification_type_display(), self.content_type, self.content_object, self.owner, @@ -56,41 +77,59 @@ class Comment(models.Model): return self.__class__.__name__ -class Notification(models.Model): - USER = "USER" +@reversion.register() +class Comment(MPTTModel): + """ + Comment with tree-traversal support for threads + """ + + PUBLIC = "PUB" + PRIVATE = "PRI" CURATOR = "CUR" + PERMANENT = "PERM" - NOTIFICATION_TYPE_CHOICES = [ - (USER, _("user")), + COMMENT_TYPE_CHOICES = [ + (PUBLIC, _("public")), + (PRIVATE, _("private")), (CURATOR, _("curator")), + (PERMANENT, _("permanent")), ] uuid = models.UUIDField(default=uuid.uuid4, editable=False) - # owner = models.ForeignKey("ddusers.User", related_name="notification_owner", on_delete=models.PROTECT) - # recipient = models.ForeignKey("ddusers.User", related_name="notification_recipient", on_delete=models.PROTECT) - # date_added = models.DateTimeField(auto_now_add=True) - # reply_to = models.ForeignKey( - # "Notification", on_delete=models.PROTECT, blank=True, null=True - # ) + doi = models.CharField(max_length=200, blank=True) + comment_type = models.CharField( + max_length=4, choices=COMMENT_TYPE_CHOICES, default=PUBLIC, + ) text = BleachField() - read = models.BooleanField(default=False) - deleted = models.BooleanField(default=False) - notification_type = models.CharField( - max_length=4, choices=NOTIFICATION_TYPE_CHOICES, default=USER, + date_added = models.DateTimeField(auto_now_add=True) + date_edited = models.DateTimeField(auto_now=True) + owner = models.ForeignKey( + "ddusers.User", related_name="comment_owner", on_delete=models.PROTECT ) - actions = GenericRelation( - Action, - related_query_name="notification", - content_type_field="action_object_content_type", - object_id_field="action_object_object_id", + permanent = models.BooleanField(default=False) + deleted = models.BooleanField(default=False) + + parent = TreeForeignKey( + "self", + on_delete=models.CASCADE, + null=True, + blank=True, + related_name="comment_children", ) + # mandatory fields for generic relation + content_type = models.ForeignKey(ContentType, on_delete=models.PROTECT) + object_id = models.IntegerField() + content_object = GenericForeignKey() + + def __str__(self): + return "[%s] %s %s (%s), %s" % ( + self.get_comment_type_display(), + self.content_type, + self.content_object, + self.owner, + self.date_added, + ) + def class_name(self): return self.__class__.__name__ - - # tags = GenericRelation( - # TaggedItem, - # content_type_field='content_type_fk', - # object_id_field='object_primary_key', - # ) - # GenericRelation(object_id_field='resource_id', content_type_field='resource_type') -- GitLab From f1ec3b19b6f4b936abb86ae35ca5946b12ecde16 Mon Sep 17 00:00:00 2001 From: "p.fherrma" Date: Fri, 22 May 2020 13:48:40 +0200 Subject: [PATCH 44/54] fix(modals): add js function to close modals properly --- discuss_data/static/js/project.js | 9 +++++++++ discuss_data/templates/core/_confirm_modal.html | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/discuss_data/static/js/project.js b/discuss_data/static/js/project.js index 4665f51..c68b8ec 100644 --- a/discuss_data/static/js/project.js +++ b/discuss_data/static/js/project.js @@ -58,3 +58,12 @@ function docucardActive(element) { $( element ).parents('.card-header').addClass('docucard-header-active'); } }; + +/* + * Remove modal manually in cases where an intercooler request conflicts + * with bootstraps data-dismiss="modal". + * Called like this: ic-on-beforeSend="removeModal()" + */ +function removeModal() { + $(".modal").modal('hide'); +}; \ No newline at end of file diff --git a/discuss_data/templates/core/_confirm_modal.html b/discuss_data/templates/core/_confirm_modal.html index 9c9afcb..a0a3370 100644 --- a/discuss_data/templates/core/_confirm_modal.html +++ b/discuss_data/templates/core/_confirm_modal.html @@ -16,7 +16,7 @@ - -- GitLab From 3f9f282a59890fa5dab53b8adef40de38a6dd5db Mon Sep 17 00:00:00 2001 From: "p.fherrma" Date: Fri, 22 May 2020 13:50:20 +0200 Subject: [PATCH 45/54] fix(messages): remove messages rendering from base.html --- discuss_data/templates/base.html | 6 ------ 1 file changed, 6 deletions(-) diff --git a/discuss_data/templates/base.html b/discuss_data/templates/base.html index 592ac56..104defd 100644 --- a/discuss_data/templates/base.html +++ b/discuss_data/templates/base.html @@ -44,12 +44,6 @@ {# sidebar nav #} {% include 'nav-sidebar.html' with editmode=editmode %} - {% if messages %} - {% for message in messages %} -

    {{ message }}
    - {% endfor %} - {% endif %} -
    {% block content %} {% block ic-content %} -- GitLab From 984b4f5e19b7c51d785c7cff1ec72efaecf2a5aa Mon Sep 17 00:00:00 2001 From: "p.fherrma" Date: Fri, 22 May 2020 13:51:50 +0200 Subject: [PATCH 46/54] fix(comments): query only for comments which are root nodes in the comments-tree --- discuss_data/dddatasets/models.py | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/discuss_data/dddatasets/models.py b/discuss_data/dddatasets/models.py index 8d323b4..fcdf366 100644 --- a/discuss_data/dddatasets/models.py +++ b/discuss_data/dddatasets/models.py @@ -497,16 +497,25 @@ class DataSet(models.Model): def get_prep_comments(self): # return prep comments in reverse date order - return self.comments.order_by("-date_added") + ct = ContentType.objects.get_for_model(DataSet) + return ( + Comment.objects.root_nodes() + .filter(content_type=ct, object_id=self.id,) + .order_by("-date_added") + ) def get_public_comments(self): # return public comments in reverse date order ct = ContentType.objects.get_for_model(DataSet) - return Comment.objects.filter( - content_type=ct, - object_id=self.id, - comment_type__in=(Comment.PUBLIC, Comment.PERMANENT), - ).order_by("-date_added") + return ( + Comment.objects.root_nodes() + .filter( + content_type=ct, + object_id=self.id, + comment_type__in=(Comment.PUBLIC, Comment.PERMANENT), + ) + .order_by("-date_added") + ) def get_pdf_file_name(self): return "DiscussData-%s-description.pdf" % (self.title.replace(" ", "_")) @@ -651,10 +660,7 @@ class DataSet(models.Model): assign_perm(perm, group, self) def save(self, *args, **kwargs): - # print(self.id) - # print(self.__dict__) if not self.dataset_management_object: - # print('creatin dsmo') dsmo = DataSetManagementObject() dsmo.owner = self.owner dsmo.save() -- GitLab From dc238c057da79e14665305638439ec86ab59c0bc Mon Sep 17 00:00:00 2001 From: "p.fherrma" Date: Fri, 22 May 2020 13:57:27 +0200 Subject: [PATCH 47/54] feat(comments): replace notifications/comments system with threaded trees using mptt. Tree level is limited (in views and templates, not generally) to a maximum depth of 1 --- discuss_data/ddcomments/forms.py | 25 ++-- discuss_data/dddatasets/urls.py | 10 ++ discuss_data/dddatasets/utils.py | 76 ++++++++--- discuss_data/dddatasets/views.py | 29 ++++- discuss_data/dddatasets/views_prep.py | 26 ++-- discuss_data/ddusers/urls.py | 3 +- discuss_data/ddusers/views.py | 122 ++++++++++++------ discuss_data/static/sass/project.scss | 14 +- .../templates/ddcomments/_comment_add.html | 27 ++-- .../ddcomments/_comment_block_load.html | 5 +- .../templates/ddcomments/_comment_card.html | 73 +++++++++++ .../templates/ddcomments/_comments_list.html | 20 ++- .../ddcomments/_notification_add.html | 14 +- .../ddcomments/_notification_card.html | 78 +++++++++++ discuss_data/templates/ddusers/_feed.html | 8 +- .../templates/ddusers/_feed_card.html | 2 +- .../templates/ddusers/_nav_dashboard.html | 4 +- discuss_data/templates/ddusers/detail.html | 13 +- .../templates/ddusers/notifications.html | 35 +++++ 19 files changed, 456 insertions(+), 128 deletions(-) create mode 100644 discuss_data/templates/ddcomments/_comment_card.html create mode 100644 discuss_data/templates/ddcomments/_notification_card.html create mode 100644 discuss_data/templates/ddusers/notifications.html diff --git a/discuss_data/ddcomments/forms.py b/discuss_data/ddcomments/forms.py index d2f6956..f52bbde 100644 --- a/discuss_data/ddcomments/forms.py +++ b/discuss_data/ddcomments/forms.py @@ -25,21 +25,17 @@ MSG = """ {% if messages %} {% for message in messages %} {% if message.level == DEFAULT_MESSAGE_LEVELS.SUCCESS %} -