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
{# -*- 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 %}
{# -*- 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 %}
{% 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 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".
</p>
</div>
<form action="" method="post" id="order-form">
{% csrf_token %}
{{ formset.management_form }}
{% for form in formset %}
{{ form.as_p }}
{% endfor %}
{% include "core/partials/save_button.html" %}
</form>
<h5>{% trans "Available widgets" %}</h5>
<div class="row card-panel grey lighten-3" id="not-used-widgets">
{% for widget in not_used_widgets %}
{% include "core/partials/edit_dashboard_widget.html" %}
{% endfor %}
</div>
<h5>{% trans "Your dashboard" %}</h5>
<div class="row card-panel grey lighten-3" id="widgets">
{% for widget in widgets %}
{% include "core/partials/edit_dashboard_widget.html" %}
{% endfor %}
</div>
<script src="{% static "js/edit_dashboard.js" %}"></script>
{% endblock %}
......@@ -2,13 +2,21 @@
{% load i18n static dashboard %}
{% block browser_title %}{% blocktrans %}Home{% endblocktrans %}{% endblock %}
{% block page_title %}{{ request.site.preferences.general__title }}{% endblock %}
{% block no_page_title %}{% endblock %}
{% block extra_head %}
{{ media }}
{% endblock %}
{% block content %}
<a class="btn-flat waves-effect waves-light right" href="{% url "edit_dashboard" %}">
<i class="material-icons left">edit</i>
{% trans "Edit dashboard" %}
</a>
<h4>
{{ request.site.preferences.general__title }}
</h4>
{% if user.is_authenticated %}
{% for notification in unread_notifications %}
<div class="alert primary scale-transition">
......@@ -31,9 +39,19 @@
<div class="row" id="live_load">
{% for widget in widgets %}
<div class="col s12 m12 l6 xl4">
<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>
......
<div class="col draggable s{{ widget.size_s }} m{{ widget.size_m }} l{{ widget.size_l }} xl{{ widget.size_xl }}"
data-pk="{{ widget.pk }}">
<div class="card placeholder">
<div class="card-content">
<i class="material-icons left small">drag_handle</i>
<span class="card-title">{{ widget.title }}</span>
</div>
</div>
</div>
......@@ -2,6 +2,8 @@
{% load i18n %}
{% block browser_title %}{% blocktrans %}Network error{% endblocktrans %}{% endblock %}
{% block content %}
<h3><i class="material-icons left medium" style="font-size: 2.92rem;">signal_wifi_off</i>{% blocktrans %}No internet
connection.{% endblocktrans %}</h3>
......
......@@ -22,8 +22,8 @@
<p>
{% blocktrans %}
To start using a token generator, please use your
smartphone to scan the QR code below. For example, use Google
Authenticator. Then, enter the token generated by the app.
favourite two factor authentication (TOTP) app to scan the QR code below.
Then, enter the token generated by the app.
{% endblocktrans %}
</p>
<p>
......
......@@ -57,6 +57,7 @@ urlpatterns = [
path("group/<int:id_>/edit", views.edit_group, name="edit_group_by_id"),
path("group/<int:id_>/delete", views.delete_group, name="delete_group_by_id"),
path("", views.index, name="index"),
path("dashboard/edit/", views.EditDashboardView.as_view(), name="edit_dashboard"),
path(
"notifications/mark-read/<int:id_>",
views.notification_mark_read,
......@@ -153,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
......
"""Management utilities for an AlekSIS installation."""
import os
import sys
from django.core.management import execute_from_command_line
def aleksis_cmd():
"""Run django-admin command with correct settings path."""
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "aleksis.core.settings")
execute_from_command_line(sys.argv)
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
from django.utils.decorators import method_decorator
from django.utils.translation import gettext_lazy as _
from django.views.decorators.cache import never_cache
from django.views.generic.base import View
import reversion
from django_tables2 import RequestConfig, SingleTableView
......@@ -24,6 +29,7 @@ from .filters import GroupFilter, PersonFilter
from .forms import (
AnnouncementForm,
ChildGroupsForm,
DashboardWidgetOrderFormSet,
EditAdditionalFieldForm,
EditGroupForm,
EditGroupTypeForm,
......@@ -34,11 +40,12 @@ from .forms import (
SchoolTermForm,
SitePreferenceForm,
)
from .mixins import AdvancedCreateView, AdvancedEditView
from .mixins import AdvancedCreateView, AdvancedDeleteView, AdvancedEditView
from .models import (
AdditionalField,
Announcement,
DashboardWidget,
DashboardWidgetOrder,
Group,
GroupType,
Notification,
......@@ -52,6 +59,7 @@ from .registries import (
)
from .tables import (
AdditionalFieldsTable,
DashboardWidgetTable,
GroupsTable,
GroupTypesTable,
PersonsTable,
......@@ -78,7 +86,7 @@ def index(request: HttpRequest) -> HttpResponse:
announcements = Announcement.objects.at_time().for_person(request.user.person)
context["announcements"] = announcements
widgets = DashboardWidget.objects.filter(active=True)
widgets = request.user.person.dashboard_widgets
media = DashboardWidget.get_media(widgets)
context["widgets"] = widgets
......@@ -87,11 +95,6 @@ def index(request: HttpRequest) -> HttpResponse:
return render(request, "core/index.html", context)
def offline(request: HttpRequest) -> HttpResponse:
"""Offline message for PWA."""
return render(request, "core/pages/offline.html")
def about(request: HttpRequest) -> HttpResponse:
"""About page listing all apps."""
context = {}
......@@ -112,6 +115,7 @@ class SchoolTermListView(SingleTableView, PermissionRequiredMixin):
template_name = "core/school_term/list.html"
@method_decorator(never_cache, name="dispatch")
class SchoolTermCreateView(AdvancedCreateView, PermissionRequiredMixin):
"""Create view for school terms."""
......@@ -123,6 +127,7 @@ class SchoolTermCreateView(AdvancedCreateView, PermissionRequiredMixin):
success_message = _("The school term has been created.")
@method_decorator(never_cache, name="dispatch")
class SchoolTermEditView(AdvancedEditView, PermissionRequiredMixin):
"""Edit view for school terms."""
......@@ -230,6 +235,7 @@ def groups(request: HttpRequest) -> HttpResponse:
return render(request, "core/group/list.html", context)
@never_cache
@permission_required("core.link_persons_accounts")
def persons_accounts(request: HttpRequest) -> HttpResponse:
"""View allowing to batch-process linking of users to persons."""
......@@ -250,6 +256,7 @@ def persons_accounts(request: HttpRequest) -> HttpResponse:
return render(request, "core/person/accounts.html", context)
@never_cache
@permission_required("core.assign_child_groups_to_groups")
def groups_child_groups(request: HttpRequest) -> HttpResponse:
"""View for batch-processing assignment from child groups to groups."""
......@@ -287,6 +294,7 @@ def groups_child_groups(request: HttpRequest) -> HttpResponse:
return render(request, "core/group/child_groups.html", context)
@never_cache
@permission_required("core.edit_person", fn=objectgetter_optional(Person))
def edit_person(request: HttpRequest, id_: Optional[int] = None) -> HttpResponse:
"""Edit view for a single person, defaulting to logged-in person."""
......@@ -325,6 +333,7 @@ def get_group_by_id(request: HttpRequest, id_: Optional[int] = None):
return None
@never_cache
@permission_required("core.edit_group", fn=objectgetter_optional(Group, None, False))
def edit_group(request: HttpRequest, id_: Optional[int] = None) -> HttpResponse:
"""View to edit or create a group."""
......@@ -377,11 +386,12 @@ class SystemStatus(MainView, PermissionRequiredMixin):
task_results = []
if "django_celery_results" in settings.INSTALLED_APPS:
from celery.task.control import inspect # noqa
from django_celery_results.models import TaskResult # noqa
if inspect().registered_tasks():
job_list = list(inspect().registered_tasks().values())[0]
from .celery import app # noqa
if app.control.inspect().registered_tasks():
job_list = list(app.control.inspect().registered_tasks().values())[0]
for job in job_list:
task_results.append(
TaskResult.objects.filter(task_name=job).order_by("date_done").last()
......@@ -417,6 +427,7 @@ def announcements(request: HttpRequest) -> HttpResponse:
return render(request, "core/announcement/list.html", context)
@never_cache
@permission_required(
"core.create_or_edit_announcement", fn=objectgetter_optional(Announcement, None, False)
)
......@@ -484,6 +495,7 @@ class PermissionSearchView(PermissionRequiredMixin, SearchView):
return render(self.request, self.template, context)
@never_cache
def preferences(
request: HttpRequest,
registry_name: str = "person",
......@@ -569,6 +581,7 @@ def delete_group(request: HttpRequest, id_: int) -> HttpResponse:
return redirect("groups")
@never_cache
@permission_required(
"core.change_additionalfield", fn=objectgetter_optional(AdditionalField, None, False)
)
......@@ -634,6 +647,7 @@ def delete_additional_field(request: HttpRequest, id_: int) -> HttpResponse:
return redirect("additional_fields")
@never_cache
@permission_required("core.change_grouptype", fn=objectgetter_optional(GroupType, None, False))
def edit_group_type(request: HttpRequest, id_: Optional[int] = None) -> HttpResponse:
"""View to edit or create a group_type."""
......@@ -686,3 +700,133 @@ 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.")
class EditDashboardView(View):
"""View for editing dashboard widget order."""
def get_context_data(self, request):
context = {}
widgets = request.user.person.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
order = 10
initial = []
for widget in widgets:
initial.append({"pk": widget, "order": order})
order += 10
for widget in not_used_widgets:
initial.append({"pk": widget, "order": 0})
formset = DashboardWidgetOrderFormSet(
request.POST or None, initial=initial, prefix="widget_order"
)
context["formset"] = formset
return context
def post(self, request):
context = self.get_context_data(request)
if context["formset"].is_valid():
added_objects = []
for form in context["formset"]:
if not form.cleaned_data["order"]:
continue
obj, created = DashboardWidgetOrder.objects.update_or_create(
widget=form.cleaned_data["pk"],
person=request.user.person,
defaults={"order": form.cleaned_data["order"]},
)
added_objects.append(obj.pk)
DashboardWidgetOrder.objects.filter(person=request.user.person).exclude(
pk__in=added_objects
).delete()
messages.success(
request, _("Your dashboard configuration has been saved successfully.")
)
return redirect("index")
def get(self, request):
context = self.get_context_data(request)
return render(request, "core/edit_dashboard.html", context=context)
This diff is collapsed.
......@@ -89,6 +89,7 @@ django-favicon-plus-reloaded = "^1.0.4"
django-health-check = "^3.12.1"
psutil = "^5.7.0"
celery-progress = "^0.0.14"
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"
......@@ -100,6 +101,9 @@ celery = ["Celery", "django-celery-results", "django-celery-beat", "django-celer
[tool.poetry.dev-dependencies]
aleksis-builddeps = "*"
[tool.poetry.scripts]
aleksis-admin = 'aleksis.core.util.manage:aleksis_cmd'
[tool.black]
line-length = 100
exclude = "/migrations/"
......