Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • hansegucker/AlekSIS-Core
  • pinguin/AlekSIS-Core
  • AlekSIS/official/AlekSIS-Core
  • sunweaver/AlekSIS-Core
  • sggua/AlekSIS-Core
  • edward/AlekSIS-Core
  • magicfelix/AlekSIS-Core
7 results
Show changes
<div class="chip {{ classes }}">
{% if img %}
<img class="{{ img_classes }}" src="{{ img }}" alt="{{ alt }}">
{% endif %}
{{ content }}
{% if close %}
<i class="close material-icons"></i>
{% endif %}
</div>
......@@ -2,7 +2,7 @@
{% extends "core/base.html" %}
{% load i18n data_helpers %}
{% load i18n data_helpers rules %}
{% load render_table from django_tables2 %}
{% block browser_title %}{% blocktrans %}Dashboard widgets{% endblocktrans %}{% endblock %}
......@@ -10,13 +10,28 @@
{% 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 class="btn green waves-effect waves-light dropdown-trigger" href="#" data-target="widget-dropdown">
<i class="material-icons left">add</i>
{% trans "Create dashboard widget" %}
</a>
<ul id="widget-dropdown" class="dropdown-content">
{% for ct, model in widget_types %}
<li>
<a href="{% url 'create_dashboard_widget' ct.app_label ct.model %}">
{% verbose_name_object model as widget_name %}
{% blocktrans with name=widget_name %}Create {{ name }}{% endblocktrans %}
</a>
</li>
{% endfor %}
</ul>
{% has_perm "core.edit_default_dashboard" user as can_edit_default_dashboard %}
{% if can_edit_default_dashboard %}
<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>
{% endfor %}
{% endif %}
{% render_table table %}
{% endblock %}
{% 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 %}
{% trans "Edit dashboard" %}
{% else %}
{% trans "Edit default dashboard" %}
{% endif %}
{% endblock %}
{% block page_title %}
{% if not default_dashboard %}
{% trans "Edit dashboard" %}
{% 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" %}
......
......@@ -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>
......
{% load i18n %}
<ul class="tabs">
<li class="tab ">
<a href="{% url registry_url %}"
class="{% if not active_section %}active{% endif %}"
target="_self">
{% trans "All" %}
</a>
{% for section in registry.section_objects.values %}
<li class="tab">
<a class="{% if active_section == section.name %}active{% endif %}"
......
......@@ -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
......
......@@ -14,6 +14,7 @@ else:
import importlib_metadata as metadata
from django.conf import settings
from django.db import transaction
from django.db.models import Model, QuerySet
from django.http import HttpRequest
from django.shortcuts import get_object_or_404
......@@ -209,7 +210,7 @@ def celery_optional(orig: Callable) -> Callable:
def wrapped(*args, **kwargs):
if is_celery_enabled():
return task.delay(*args, **kwargs), False
return transaction.on_commit(lambda: task.delay(*args, **kwargs)), False
else:
return orig(*args, **kwargs), True
......
from collections import OrderedDict
from material import Layout, Row
class PreferenceLayout(Layout):
"""django-material Layout object for managing preferences."""
def __init__(self, form_base_class, section=None):
"""
Create Layout object for the given form_base_class.
:param form_base_class: A Form class used as the base. Must have a ``registry` attribute
:param section: A section where the layout builder will load preferences
"""
registry = form_base_class.registry
if section:
# Try to use section param
preferences_obj = registry.preferences(section=section)
else:
# display all preferences in the form
preferences_obj = registry.preferences()
rows = OrderedDict()
for preference in preferences_obj:
row_name = preference.get("row", preference.identifier())
rows.setdefault(row_name, [])
rows[row_name].append(preference.identifier())
rows_material = []
for fields in rows.values():
rows_material.append(Row(*fields))
super().__init__(*rows_material)
......@@ -75,6 +75,7 @@ from .tables import (
from .util import messages
from .util.apps import AppConfig
from .util.core_helpers import objectgetter_optional
from .util.forms import PreferenceLayout
@permission_required("core.view_dashboard")
......@@ -94,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
......@@ -113,7 +120,7 @@ def about(request: HttpRequest) -> HttpResponse:
return render(request, "core/pages/about.html", context)
class SchoolTermListView(SingleTableView, PermissionRequiredMixin):
class SchoolTermListView(PermissionRequiredMixin, SingleTableView):
"""Table of all school terms."""
model = SchoolTerm
......@@ -123,7 +130,7 @@ class SchoolTermListView(SingleTableView, PermissionRequiredMixin):
@method_decorator(never_cache, name="dispatch")
class SchoolTermCreateView(AdvancedCreateView, PermissionRequiredMixin):
class SchoolTermCreateView(PermissionRequiredMixin, AdvancedCreateView):
"""Create view for school terms."""
model = SchoolTerm
......@@ -135,7 +142,7 @@ class SchoolTermCreateView(AdvancedCreateView, PermissionRequiredMixin):
@method_decorator(never_cache, name="dispatch")
class SchoolTermEditView(AdvancedEditView, PermissionRequiredMixin):
class SchoolTermEditView(PermissionRequiredMixin, AdvancedEditView):
"""Edit view for school terms."""
model = SchoolTerm
......@@ -381,7 +388,7 @@ def data_management(request: HttpRequest) -> HttpResponse:
return render(request, "core/management/data_management.html", context)
class SystemStatus(MainView, PermissionRequiredMixin):
class SystemStatus(PermissionRequiredMixin, MainView):
"""View giving information about the system status."""
template_name = "core/pages/system_status.html"
......@@ -538,9 +545,16 @@ def preferences(
# Invalid registry name passed from URL
return HttpResponseNotFound()
if not section and len(registry.sections()) > 0:
default_section = list(registry.sections())[0]
return redirect(f"preferences_{registry_name}", default_section)
# Build final form from dynamic-preferences
form_class = preference_form_builder(form_class, instance=instance, section=section)
# Get layout
form_class.layout = PreferenceLayout(form_class, section=section)
if request.method == "POST":
form = form_class(request.POST, request.FILES or None)
if form.is_valid():
......@@ -765,7 +779,7 @@ class SolveDataCheckView(PermissionRequiredMixin, RevisionMixin, DetailView):
return HttpResponseNotFound()
class DashboardWidgetListView(SingleTableView, PermissionRequiredMixin):
class DashboardWidgetListView(PermissionRequiredMixin, SingleTableView):
"""Table of all dashboard widgets."""
model = DashboardWidget
......@@ -783,7 +797,7 @@ class DashboardWidgetListView(SingleTableView, PermissionRequiredMixin):
@method_decorator(never_cache, name="dispatch")
class DashboardWidgetEditView(AdvancedEditView, PermissionRequiredMixin):
class DashboardWidgetEditView(PermissionRequiredMixin, AdvancedEditView):
"""Edit view for dashboard widgets."""
def get_form_class(self) -> Type[BaseModelForm]:
......@@ -798,7 +812,7 @@ class DashboardWidgetEditView(AdvancedEditView, PermissionRequiredMixin):
@method_decorator(never_cache, name="dispatch")
class DashboardWidgetCreateView(AdvancedCreateView, PermissionRequiredMixin):
class DashboardWidgetCreateView(PermissionRequiredMixin, AdvancedCreateView):
"""Create view for dashboard widgets."""
def get_model(self, request, *args, **kwargs):
......@@ -840,11 +854,23 @@ 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()
widgets = request.user.person.dashboard_widgets
not_used_widgets = DashboardWidget.objects.exclude(pk__in=[w.pk for w in widgets])
context["default_dashboard"] = self.default_dashboard
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]).filter(
active=True
)
context["widgets"] = widgets
context["not_used_widgets"] = not_used_widgets
......@@ -863,8 +889,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 = []
......@@ -874,22 +900,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)
This diff is collapsed.
[tool.poetry]
name = "AlekSIS-Core"
version = "2.0a3.dev0"
version = "2.0a4.dev0"
packages = [
{ include = "aleksis" }
]
......@@ -38,7 +38,7 @@ Django = "^3.0"
django-any-js = "^1.0"
django-debug-toolbar = "^2.0"
django-middleware-global-request = "^0.1.2"
django-menu-generator = "^1.0.4"
django-menu-generator-ng = "^1.2.0"
django-tables2 = "^2.1"
Pillow = "^8.0"
django-phonenumber-field = {version = "<5.1", extras = ["phonenumbers"]}
......@@ -72,7 +72,7 @@ Celery = {version="^5.0.0", optional=true, extras=["django", "redis"]}
django-celery-results = {version="^2.0.0", optional=true}
django-celery-beat = {version="^2.0.0", optional=true}
django-celery-email = {version="^3.0.0", optional=true}
django-jsonstore = "^0.4.1"
django-jsonstore = "^0.5.0"
django-polymorphic = "^3.0.0"
django-colorfield = "^0.3.0"
django-bleach = "^0.6.1"
......@@ -93,6 +93,7 @@ django-cachalot = "^2.3.2"
django-prometheus = "^2.1.0"
importlib-metadata = {version = "^3.0.0", python = "<3.9"}
django-model-utils = "^4.0.0"
bs4 = "^0.0.1"
[tool.poetry.extras]
ldap = ["django-auth-ldap"]
......
......@@ -9,9 +9,11 @@ whitelist_externals = poetry
skip_install = true
envdir = {toxworkdir}/globalenv
commands_pre =
- poetry install
poetry install
poetry run aleksis-admin yarn install
poetry run aleksis-admin collectstatic --no-input
commands =
- poetry run pytest --cov=. {posargs} aleksis/
poetry run pytest --cov=. {posargs} aleksis/
[testenv:selenium]
setenv =
......