From 92a4b16985155b8553b914ae296c83318d11ecbb Mon Sep 17 00:00:00 2001
From: Jonathan Weth <git@jonathanweth.de>
Date: Mon, 4 Jan 2021 15:26:25 +0100
Subject: [PATCH] Show a default dashboard if a user didn't selected any
 dashboard wigets

---
 .../core/migrations/0009_default_dashboard.py | 30 ++++++++++++
 aleksis/core/models.py                        | 11 ++++-
 aleksis/core/rules.py                         |  3 ++
 .../templates/core/dashboard_widget/list.html |  5 ++
 .../core/templates/core/edit_dashboard.html   | 41 +++++++++++++---
 aleksis/core/templates/core/index.html        | 19 ++++----
 aleksis/core/urls.py                          |  6 +++
 aleksis/core/views.py                         | 48 +++++++++++++------
 8 files changed, 132 insertions(+), 31 deletions(-)
 create mode 100644 aleksis/core/migrations/0009_default_dashboard.py

diff --git a/aleksis/core/migrations/0009_default_dashboard.py b/aleksis/core/migrations/0009_default_dashboard.py
new file mode 100644
index 000000000..401396bb0
--- /dev/null
+++ b/aleksis/core/migrations/0009_default_dashboard.py
@@ -0,0 +1,30 @@
+# Generated by Django 3.1.4 on 2021-01-04 13:39
+
+from django.db import migrations, models
+import django.db.models.deletion
+import django.utils.timezone
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('core', '0008_data_check_result'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='dashboardwidgetorder',
+            name='default',
+            field=models.BooleanField(default=False, verbose_name='Part of the default dashboard'),
+        ),
+        migrations.AlterField(
+            model_name='dashboardwidgetorder',
+            name='person',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='core.person', verbose_name='Person'),
+        ),
+        migrations.AlterModelOptions(
+            name='dashboardwidget',
+            options={'permissions': (('edit_default_dashboard', 'Can edit default dashboard'),),
+                     'verbose_name': 'Dashboard Widget', 'verbose_name_plural': 'Dashboard Widgets'},
+        ),
+    ]
diff --git a/aleksis/core/models.py b/aleksis/core/models.py
index 5f5facd33..5e3a9e85a 100644
--- a/aleksis/core/models.py
+++ b/aleksis/core/models.py
@@ -731,6 +731,7 @@ class DashboardWidget(PolymorphicModel, PureDjangoModel):
         return self.title
 
     class Meta:
+        permissions = (("edit_default_dashboard", _("Can edit default dashboard")),)
         verbose_name = _("Dashboard Widget")
         verbose_name_plural = _("Dashboard Widgets")
 
@@ -739,8 +740,16 @@ class DashboardWidgetOrder(ExtensibleModel):
     widget = models.ForeignKey(
         DashboardWidget, on_delete=models.CASCADE, verbose_name=_("Dashboard widget")
     )
-    person = models.ForeignKey(Person, on_delete=models.CASCADE, verbose_name=_("Person"))
+    person = models.ForeignKey(
+        Person, on_delete=models.CASCADE, verbose_name=_("Person"), null=True, blank=True
+    )
     order = models.PositiveIntegerField(verbose_name=_("Order"))
+    default = models.BooleanField(default=False, verbose_name=_("Part of the default dashboard"))
+
+    @classproperty
+    def default_dashboard_widgets(cls):
+        """Get default order for dashboard widgets."""
+        return [w.widget for w in cls.objects.filter(person=None, default=True).order_by("order")]
 
     class Meta:
         verbose_name = _("Dashboard widget order")
diff --git a/aleksis/core/rules.py b/aleksis/core/rules.py
index 8ad48fbf1..ac713bbe5 100644
--- a/aleksis/core/rules.py
+++ b/aleksis/core/rules.py
@@ -306,3 +306,6 @@ rules.add_perm("core.edit_dashboardwidget", edit_dashboard_widget_predicate)
 
 delete_dashboard_widget_predicate = has_person & has_global_perm("core.delete_dashboardwidget")
 rules.add_perm("core.delete_dashboardwidget", delete_dashboard_widget_predicate)
+
+edit_default_dashboard_predicate = has_person & has_global_perm("core.edit_default_dashboard")
+rules.add_perm("core.edit_default_dashboard", edit_default_dashboard_predicate)
diff --git a/aleksis/core/templates/core/dashboard_widget/list.html b/aleksis/core/templates/core/dashboard_widget/list.html
index ab384e862..022101b9e 100644
--- a/aleksis/core/templates/core/dashboard_widget/list.html
+++ b/aleksis/core/templates/core/dashboard_widget/list.html
@@ -17,6 +17,11 @@
       {% blocktrans with name=widget_name %}Create {{ name }}{% endblocktrans %}
     </a>
   {% endfor %}
+  <a class="btn orange waves-effect waves-light" href="{% url "edit_default_dashboard" %}">
+    <i class="material-icons left">edit</i>
+    {% trans "Edit default dashboard" %}
+  </a>
+
 
   {% render_table table %}
 {% endblock %}
diff --git a/aleksis/core/templates/core/edit_dashboard.html b/aleksis/core/templates/core/edit_dashboard.html
index a15f24bff..2e8c633a3 100644
--- a/aleksis/core/templates/core/edit_dashboard.html
+++ b/aleksis/core/templates/core/edit_dashboard.html
@@ -1,16 +1,38 @@
 {% extends 'core/base.html' %}
 {% load i18n static dashboard any_js %}
 
-{% block browser_title %}{% blocktrans %}Edit dashboard{% endblocktrans %}{% endblock %}
-{% block page_title %}{% blocktrans %}Edit dashboard{% endblocktrans %}{% endblock %}
+{% block browser_title %}
+  {% if not default_dashboard %}
+    {% blocktrans %}Edit dashboard{% endblocktrans %}
+  {% else %}
+    {% trans "Edit default dashboard" %}
+  {% endif %}
+{% endblock %}
+{% block page_title %}
+  {% if not default_dashboard %}
+    {% blocktrans %}Edit dashboard{% endblocktrans %}
+  {% else %}
+    {% trans "Edit default dashboard" %}
+  {% endif %}
+{% endblock %}
 
 {% block content %}
   <div class="alert primary">
     <p>
       <i class="material-icons left">info</i>
-      On this page you can arrange your personal dashboard. You can drag any items from "Available widgets" to "Your
-      Dashboard" or change the order by moving the widgets. After you have finished, please don't forget to click on
-      "Save".
+      {% if not default_dashboard %}
+        {% blocktrans %}
+          On this page you can arrange your personal dashboard. You can drag any items from "Available widgets" to "Your
+          Dashboard" or change the order by moving the widgets. After you have finished, please don't forget to click on
+          "Save".
+        {% endblocktrans %}
+      {% else %}
+        {% blocktrans %}
+          On this page you can arrange the default dashboard which is shown when a user doesn't arrange his own
+          dashboard. You can drag any items from "Available widgets" to "Default Dashboard" or change the order
+          by moving the widgets. After you have finished, please don't forget to click on "Save".
+        {% endblocktrans %}
+      {% endif %}
     </p>
   </div>
 
@@ -30,7 +52,14 @@
     {% endfor %}
   </div>
 
-  <h5>{% trans "Your dashboard" %}</h5>
+  <h5>
+    {% if not default_dashboard %}
+      {% trans "Your dashboard" %}
+    {% else %}
+      {% trans "Default dashboard" %}
+    {% endif %}
+  </h5>
+
   <div class="row card-panel grey lighten-3" id="widgets">
     {% for widget in widgets %}
       {% include "core/partials/edit_dashboard_widget.html" %}
diff --git a/aleksis/core/templates/core/index.html b/aleksis/core/templates/core/index.html
index 93b80ddef..419bb59f1 100644
--- a/aleksis/core/templates/core/index.html
+++ b/aleksis/core/templates/core/index.html
@@ -42,19 +42,18 @@
         <div class="col s{{ widget.size_s }} m{{ widget.size_m }} l{{ widget.size_l }} xl{{ widget.size_xl }}">
           {% include_widget widget %}
         </div>
-        {% empty %}
-        <div class="col s12 grey-text center">
-          <i class="material-icons medium ">widgets</i>
-          <p class="flow-text">
-            {% blocktrans %}
-              You haven't selected any dashboard widgets. Please click on "Edit dashboard" to add widgets to your
-              personal dashboard.
-            {% endblocktrans %}
-          </p>
-        </div>
       {% endfor %}
     </div>
 
+    {% if default_dashboard and widgets %}
+      <div class="grey-text right">
+        {% blocktrans %}
+          You didn't customise your dashboard so that you see the system default. Please click on "Edit dashboard" to
+          customise your personal dashboard.
+        {% endblocktrans %}
+      </div>
+    {% endif %}
+
     <div class="row">
       <div class="col s12 m6">
         <h5>{% blocktrans %}Last activities{% endblocktrans %}</h5>
diff --git a/aleksis/core/urls.py b/aleksis/core/urls.py
index 930b3fbc8..30c38b11c 100644
--- a/aleksis/core/urls.py
+++ b/aleksis/core/urls.py
@@ -177,6 +177,12 @@ urlpatterns = [
         views.DashboardWidgetCreateView.as_view(),
         name="create_dashboard_widget",
     ),
+    path(
+        "dashboard_widgets/default/",
+        views.EditDashboardView.as_view(),
+        {"default": True},
+        name="edit_default_dashboard",
+    ),
 ]
 
 # Serve static files from STATIC_ROOT to make it work with runserver
diff --git a/aleksis/core/views.py b/aleksis/core/views.py
index c9f9c5cd3..1da251a8a 100644
--- a/aleksis/core/views.py
+++ b/aleksis/core/views.py
@@ -95,6 +95,12 @@ def index(request: HttpRequest) -> HttpResponse:
     context["announcements"] = announcements
 
     widgets = request.user.person.dashboard_widgets
+
+    if len(widgets) <= 0:
+        # Use default dashboard if there are no widgets
+        widgets = DashboardWidgetOrder.default_dashboard_widgets
+        context["default_dashboard"] = True
+
     media = DashboardWidget.get_media(widgets)
 
     context["widgets"] = widgets
@@ -848,10 +854,20 @@ class DashboardWidgetDeleteView(PermissionRequiredMixin, AdvancedDeleteView):
 class EditDashboardView(View):
     """View for editing dashboard widget order."""
 
-    def get_context_data(self, request):
+    def get_context_data(self, request, **kwargs):
         context = {}
+        self.default_dashboard = kwargs.get("default", False)
+
+        if self.default_dashboard and not request.user.has_perm("core.edit_default_dashboard"):
+            raise PermissionDenied()
+
+        context["default_dashboard"] = self.default_dashboard
 
-        widgets = request.user.person.dashboard_widgets
+        widgets = (
+            request.user.person.dashboard_widgets
+            if not self.default_dashboard
+            else DashboardWidgetOrder.default_dashboard_widgets
+        )
         not_used_widgets = DashboardWidget.objects.exclude(pk__in=[w.pk for w in widgets])
         context["widgets"] = widgets
         context["not_used_widgets"] = not_used_widgets
@@ -871,8 +887,8 @@ class EditDashboardView(View):
 
         return context
 
-    def post(self, request):
-        context = self.get_context_data(request)
+    def post(self, request, **kwargs):
+        context = self.get_context_data(request, **kwargs)
 
         if context["formset"].is_valid():
             added_objects = []
@@ -882,22 +898,26 @@ class EditDashboardView(View):
 
                 obj, created = DashboardWidgetOrder.objects.update_or_create(
                     widget=form.cleaned_data["pk"],
-                    person=request.user.person,
+                    person=request.user.person if not self.default_dashboard else None,
+                    default=self.default_dashboard,
                     defaults={"order": form.cleaned_data["order"]},
                 )
 
                 added_objects.append(obj.pk)
 
-            DashboardWidgetOrder.objects.filter(person=request.user.person).exclude(
-                pk__in=added_objects
-            ).delete()
+            DashboardWidgetOrder.objects.filter(
+                person=request.user.person if not self.default_dashboard else None,
+                default=self.default_dashboard,
+            ).exclude(pk__in=added_objects).delete()
 
-            messages.success(
-                request, _("Your dashboard configuration has been saved successfully.")
-            )
-            return redirect("index")
+            if not self.default_dashboard:
+                msg = _("Your dashboard configuration has been saved successfully.")
+            else:
+                msg = _("The configuration of the default dashboard has been saved successfully.")
+            messages.success(request, msg)
+            return redirect("index" if not self.default_dashboard else "dashboard_widgets")
 
-    def get(self, request):
-        context = self.get_context_data(request)
+    def get(self, request, **kwargs):
+        context = self.get_context_data(request, **kwargs)
 
         return render(request, "core/edit_dashboard.html", context=context)
-- 
GitLab