From 0debfe0b7eb9ca6a32591e54566eb9f8926a97ed Mon Sep 17 00:00:00 2001 From: Jonathan Weth <git@jonathanweth.de> Date: Sun, 20 Dec 2020 15:27:33 +0100 Subject: [PATCH] Add frontend for managing dashboard widgets Important: Doesn't include support for ordering or sizing yet --- aleksis/core/menus.py | 11 +++ aleksis/core/rules.py | 13 +++ aleksis/core/tables.py | 21 +++++ .../core/dashboard_widget/create.html | 23 ++++++ .../templates/core/dashboard_widget/edit.html | 23 ++++++ .../templates/core/dashboard_widget/list.html | 22 ++++++ aleksis/core/urls.py | 16 ++++ aleksis/core/views.py | 79 ++++++++++++++++++- 8 files changed, 206 insertions(+), 2 deletions(-) create mode 100644 aleksis/core/templates/core/dashboard_widget/create.html create mode 100644 aleksis/core/templates/core/dashboard_widget/edit.html create mode 100644 aleksis/core/templates/core/dashboard_widget/list.html diff --git a/aleksis/core/menus.py b/aleksis/core/menus.py index 2e71c3573..9b1c42e93 100644 --- a/aleksis/core/menus.py +++ b/aleksis/core/menus.py @@ -93,6 +93,17 @@ MENUS = { ), ], }, + { + "name": _("Dashboard widgets"), + "url": "dashboard_widgets", + "icon": "dashboard", + "validators": [ + ( + "aleksis.core.util.predicates.permission_validator", + "core.view_dashboardwidget", + ), + ], + }, { "name": _("Data management"), "url": "data_management", diff --git a/aleksis/core/rules.py b/aleksis/core/rules.py index 4b0cf23d6..2e36bc08b 100644 --- a/aleksis/core/rules.py +++ b/aleksis/core/rules.py @@ -278,3 +278,16 @@ view_group_stats_predicate = has_person & ( has_global_perm("core.view_group_stats") | has_object_perm("core.view_group_stats") ) rules.add_perm("core.view_group_stats", view_group_stats_predicate) + + +view_dashboard_widget_predicate = has_person & has_global_perm("core.view_dashboardwidget") +rules.add_perm("core.view_dashboardwidget", view_dashboard_widget_predicate) + +create_dashboard_widget_predicate = has_person & has_global_perm("core.add_dashboardwidget") +rules.add_perm("core.create_dashboardwidget", create_dashboard_widget_predicate) + +edit_dashboard_widget_predicate = has_person & has_global_perm("core.change_dashboardwidget") +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) diff --git a/aleksis/core/tables.py b/aleksis/core/tables.py index f562b61a1..54d3f3e5c 100644 --- a/aleksis/core/tables.py +++ b/aleksis/core/tables.py @@ -70,3 +70,24 @@ class GroupTypesTable(tables.Table): delete = tables.LinkColumn( "delete_group_type_by_id", args=[A("id")], verbose_name=_("Delete"), text=_("Delete") ) + + +class DashboardWidgetTable(tables.Table): + """Table to list dashboard widgets.""" + + class Meta: + attrs = {"class": "responsive-table highlight"} + + widget_name = tables.Column(accessor="pk") + title = tables.LinkColumn("edit_dashboard_widget", args=[A("id")]) + active = tables.BooleanColumn(yesno="check,cancel", attrs={"span": {"class": "material-icons"}}) + delete = tables.LinkColumn( + "delete_dashboard_widget", + args=[A("id")], + text=_("Delete"), + attrs={"a": {"class": "btn-flat waves-effect waves-red red-text"}}, + verbose_name=_("Actions"), + ) + + def render_widget_name(self, value, record): + return record._meta.verbose_name diff --git a/aleksis/core/templates/core/dashboard_widget/create.html b/aleksis/core/templates/core/dashboard_widget/create.html new file mode 100644 index 000000000..cfaa296ee --- /dev/null +++ b/aleksis/core/templates/core/dashboard_widget/create.html @@ -0,0 +1,23 @@ +{# -*- engine:django -*- #} + +{% extends "core/base.html" %} +{% load material_form i18n data_helpers %} + +{% block browser_title %} + {% verbose_name_object model as widget_title %} + {% blocktrans with widget=widget_title %}Create {{ widget }}{% endblocktrans %} +{% endblock %} +{% block page_title %} + {% verbose_name_object model as widget_title %} + {% blocktrans with widget=widget_title %}Create {{ widget }}{% endblocktrans %} +{% endblock %} + +{% block content %} + + <form method="post"> + {% csrf_token %} + {% form form=form %}{% endform %} + {% include "core/partials/save_button.html" %} + </form> + +{% endblock %} diff --git a/aleksis/core/templates/core/dashboard_widget/edit.html b/aleksis/core/templates/core/dashboard_widget/edit.html new file mode 100644 index 000000000..64dfe0eed --- /dev/null +++ b/aleksis/core/templates/core/dashboard_widget/edit.html @@ -0,0 +1,23 @@ +{# -*- engine:django -*- #} + +{% extends "core/base.html" %} +{% load material_form i18n data_helpers %} + +{% block browser_title %} + {% verbose_name_object object as widget_title %} + {% blocktrans with widget=widget_title %}Edit {{ widget }}{% endblocktrans %} +{% endblock %} +{% block page_title %} + {% verbose_name_object object as widget_title %} + {% blocktrans with widget=widget_title %}Edit {{ widget }}{% endblocktrans %} +{% endblock %} + +{% block content %} + + <form method="post"> + {% csrf_token %} + {% form form=form %}{% endform %} + {% include "core/partials/save_button.html" %} + </form> + +{% endblock %} diff --git a/aleksis/core/templates/core/dashboard_widget/list.html b/aleksis/core/templates/core/dashboard_widget/list.html new file mode 100644 index 000000000..ab384e862 --- /dev/null +++ b/aleksis/core/templates/core/dashboard_widget/list.html @@ -0,0 +1,22 @@ +{# -*- engine:django -*- #} + +{% extends "core/base.html" %} + +{% load i18n data_helpers %} +{% load render_table from django_tables2 %} + +{% block browser_title %}{% blocktrans %}Dashboard widgets{% endblocktrans %}{% endblock %} +{% block page_title %}{% blocktrans %}Dashboard widgets{% endblocktrans %}{% endblock %} + +{% block content %} + + {% for ct, model in widget_types %} + <a class="btn green waves-effect waves-light" href="{% url 'create_dashboard_widget' ct.app_label ct.model %}"> + <i class="material-icons left">add</i> + {% verbose_name_object model as widget_name %} + {% blocktrans with name=widget_name %}Create {{ name }}{% endblocktrans %} + </a> + {% endfor %} + + {% render_table table %} +{% endblock %} diff --git a/aleksis/core/urls.py b/aleksis/core/urls.py index 6c989ddf7..9061962e6 100644 --- a/aleksis/core/urls.py +++ b/aleksis/core/urls.py @@ -154,6 +154,22 @@ urlpatterns = [ name="preferences_group", ), path("health/", include(health_urls)), + path("dashboard_widgets/", views.DashboardWidgetListView.as_view(), name="dashboard_widgets"), + path( + "dashboard_widgets/<int:pk>/edit/", + views.DashboardWidgetEditView.as_view(), + name="edit_dashboard_widget", + ), + path( + "dashboard_widgets/<int:pk>/delete/", + views.DashboardWidgetDeleteView.as_view(), + name="delete_dashboard_widget", + ), + path( + "dashboard_widgets/<str:app>/<str:model>/new/", + views.DashboardWidgetCreateView.as_view(), + name="create_dashboard_widget", + ), ] # 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 6969d4148..d34aa8ae8 100644 --- a/aleksis/core/views.py +++ b/aleksis/core/views.py @@ -1,9 +1,11 @@ -from typing import Optional +from typing import Any, Dict, Optional, Type from django.apps import apps from django.conf import settings +from django.contrib.contenttypes.models import ContentType from django.core.exceptions import PermissionDenied from django.core.paginator import Paginator +from django.forms.models import BaseModelForm, modelform_factory from django.http import HttpRequest, HttpResponse, HttpResponseNotFound from django.shortcuts import get_object_or_404, redirect, render from django.urls import reverse_lazy @@ -36,7 +38,7 @@ from .forms import ( SchoolTermForm, SitePreferenceForm, ) -from .mixins import AdvancedCreateView, AdvancedEditView +from .mixins import AdvancedCreateView, AdvancedDeleteView, AdvancedEditView from .models import ( AdditionalField, Announcement, @@ -54,6 +56,7 @@ from .registries import ( ) from .tables import ( AdditionalFieldsTable, + DashboardWidgetTable, GroupsTable, GroupTypesTable, PersonsTable, @@ -699,3 +702,75 @@ def delete_group_type(request: HttpRequest, id_: int) -> HttpResponse: messages.success(request, _("The group type has been deleted.")) return redirect("group_types") + + +class DashboardWidgetListView(SingleTableView, PermissionRequiredMixin): + """Table of all dashboard widgets.""" + + model = DashboardWidget + table_class = DashboardWidgetTable + permission_required = "core.view_dashboardwidget" + template_name = "core/dashboard_widget/list.html" + + def get_context_data(self, **kwargs: Any) -> Dict[str, Any]: + context = super().get_context_data(**kwargs) + context["widget_types"] = [ + (ContentType.objects.get_for_model(m, False), m) + for m in DashboardWidget.__subclasses__() + ] + return context + + +@method_decorator(never_cache, name="dispatch") +class DashboardWidgetEditView(AdvancedEditView, PermissionRequiredMixin): + """Edit view for dashboard widgets.""" + + def get_form_class(self) -> Type[BaseModelForm]: + return modelform_factory(self.object.__class__, fields=self.fields) + + model = DashboardWidget + fields = "__all__" + permission_required = "core.edit_dashboardwidget" + template_name = "core/dashboard_widget/edit.html" + success_url = reverse_lazy("dashboard_widgets") + success_message = _("The dashboard widget has been saved.") + + +@method_decorator(never_cache, name="dispatch") +class DashboardWidgetCreateView(AdvancedCreateView, PermissionRequiredMixin): + """Create view for dashboard widgets.""" + + def get_model(self, request, *args, **kwargs): + app_label = kwargs.get("app") + model = kwargs.get("model") + ct = get_object_or_404(ContentType, app_label=app_label, model=model) + return ct.model_class() + + def get_context_data(self, **kwargs: Any) -> Dict[str, Any]: + context = super().get_context_data(**kwargs) + context["model"] = self.model + return context + + def get(self, request, *args, **kwargs): + self.model = self.get_model(request, *args, **kwargs) + return super().get(request, *args, **kwargs) + + def post(self, request, *args, **kwargs): + self.model = self.get_model(request, *args, **kwargs) + return super().post(request, *args, **kwargs) + + fields = "__all__" + permission_required = "core.add_dashboardwidget" + template_name = "core/dashboard_widget/create.html" + success_url = reverse_lazy("dashboard_widgets") + success_message = _("The dashboard widget has been created.") + + +class DashboardWidgetDeleteView(PermissionRequiredMixin, AdvancedDeleteView): + """Delete view for dashboard widgets.""" + + model = DashboardWidget + permission_required = "core.delete_dashboardwidget" + template_name = "core/pages/delete.html" + success_url = reverse_lazy("dashboard_widgets") + success_message = _("The dashboard widget has been deleted.") -- GitLab