Skip to content
Snippets Groups Projects
Commit 46ed4f3f authored by Jonathan Weth's avatar Jonathan Weth :keyboard:
Browse files

Merge branch 'feature/dashboard-widgets' into 'master'

Implement core functionality for dashboard widgets

See merge request AlekSIS/AlekSIS!142
parents bc9dfffe 69662eb4
No related branches found
No related tags found
1 merge request!142Implement core functionality for dashboard widgets
Pipeline #765 failed
......@@ -10,3 +10,7 @@
path = apps/official/AlekSIS-App-Exlibris
url = https://edugit.org/AlekSIS/AlekSIS-App-Exlibris
ignore = untracked
[submodule "apps/official/AlekSIS-App-DashboardFeeds"]
path = apps/official/AlekSIS-App-DashboardFeeds
url = https://edugit.org/AlekSIS/AlekSIS-App-DashboardFeeds.git
ignore = untracked
# Generated by Django 3.0.2 on 2020-01-29 16:45
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('contenttypes', '0002_remove_content_type_name'),
('core', '0008_rename_fields_notification_activity'),
]
operations = [
migrations.CreateModel(
name='DashboardWidget',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=150, verbose_name='Widget Title')),
('active', models.BooleanField(blank=True, verbose_name='Activate Widget')),
('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_core.dashboardwidget_set+', to='contenttypes.ContentType')),
],
options={
'verbose_name': 'Dashboard Widget',
'verbose_name_plural': 'Dashboard Widgets',
},
),
]
......@@ -6,6 +6,7 @@ from django.db import models
from django.utils.translation import ugettext_lazy as _
from image_cropping import ImageCropField, ImageRatioField
from phonenumber_field.modelfields import PhoneNumberField
from polymorphic.models import PolymorphicModel
from .mixins import ExtensibleModel
from .util.notifications import send_notification
......@@ -227,3 +228,46 @@ class Notification(models.Model):
class Meta:
verbose_name = _("Notification")
verbose_name_plural = _("Notifications")
class DashboardWidget(PolymorphicModel):
""" Base class for dashboard widgets on the index page
To implement a widget, add a model that subclasses DashboardWidget, sets the template
and implements the get_context method to return a dictionary to be passed as context
to the template.
If your widget does not add any database fields, you should mark it as a proxy model.
Example::
from aleksis.core.models import DashboardWidget
class MyWidget(DhasboardWIdget):
template = "myapp/widget.html"
def get_context(self):
context = {"some_content": "foo"}
return context
class Meta:
proxy = True
"""
template = None
title = models.CharField(max_length=150, verbose_name=_("Widget Title"))
active = models.BooleanField(blank=True, verbose_name=_("Activate Widget"))
def get_context(self):
raise NotImplementedError("A widget subclass needs to implement the get_context method.")
def get_template(self):
return self.template
def __str__(self):
return self.title
class Meta:
verbose_name = _("Dashboard Widget")
verbose_name_plural = _("Dashboard Widgets")
......@@ -51,6 +51,7 @@ INSTALLED_APPS = [
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"polymorphic",
"django_global_request",
"settings_context_processor",
"sass_processor",
......
const asyncIntervals = [];
const runAsyncInterval = async (cb, interval, intervalIndex) => {
await cb();
if (asyncIntervals[intervalIndex]) {
setTimeout(() => runAsyncInterval(cb, interval, intervalIndex), interval);
}
};
const setAsyncInterval = (cb, interval) => {
if (cb && typeof cb === "function") {
const intervalIndex = asyncIntervals.length;
asyncIntervals.push(true);
runAsyncInterval(cb, interval, intervalIndex);
return intervalIndex;
} else {
throw new Error('Callback must be a function');
}
};
const clearAsyncInterval = (intervalIndex) => {
if (asyncIntervals[intervalIndex]) {
asyncIntervals[intervalIndex] = false;
}
};
let live_load_interval = setAsyncInterval(async () => {
console.log('fetching new data');
const promise = new Promise((resolve) => {
$('#live_load').load(window.location.pathname + " #live_load");
resolve(1);
});
await promise;
console.log('data fetched successfully');
}, 15000);
{% extends 'core/base.html' %}
{% load i18n %}
{% load i18n static dashboard %}
{% block browser_title %}{% blocktrans %}Home{% endblocktrans %}{% endblock %}
......@@ -24,6 +24,14 @@
</div>
{% endfor %}
<div class="row" id="live_load">
{% for widget in widgets %}
<div class="col s12 m12 l6 xl4">
{% include_widget widget %}
</div>
{% endfor %}
</div>
<div class="row">
<div class="col s12 m6">
<h5>{% blocktrans %}Last activities{% endblocktrans %}</h5>
......@@ -77,4 +85,6 @@
</div>
</div>
{% endif %}
<script type="text/javascript" src="{% static "js/include_ajax_live.js" %}"></script>
{% endblock %}
from django.template import Library, loader
register = Library()
@register.simple_tag
def include_widget(widget) -> dict:
""" Render a template with context from a defined widget """
template = loader.get_template(widget.get_template())
context = widget.get_context()
return template.render(context)
......@@ -66,4 +66,9 @@ for app_config in apps.app_configs.values():
if not app_config.name.startswith("aleksis.apps."):
continue
urlpatterns.append(path("app/%s/" % app_config.label, include("%s.urls" % app_config.name)))
try:
urlpatterns.append(path("app/%s/" % app_config.label, include("%s.urls" % app_config.name)))
except ModuleNotFoundError:
# Ignore exception as app just has no URLs
pass # noqa
......@@ -16,7 +16,7 @@ from .forms import (
EditTermForm,
PersonsAccountsFormSet,
)
from .models import Activity, Group, Notification, Person, School
from .models import Activity, Group, Notification, Person, School, DashboardWidget
from .tables import GroupsTable, PersonsTable
from .util import messages
......@@ -33,6 +33,8 @@ def index(request: HttpRequest) -> HttpResponse:
context["notifications"] = notifications
context["unread_notifications"] = unread_notifications
context["widgets"] = DashboardWidget.objects.filter(active=True)
return render(request, "core/index.html", context)
......
Subproject commit 8207d487baa1767cff5ee43cc1706ab025bf644e
......@@ -63,6 +63,7 @@ django-celery-results = {version="^1.1.2", optional=true}
django-celery-beat = {version="^1.5.0", optional=true}
django-celery-email = {version="^3.0.0", optional=true}
django-jsonstore = "^0.4.1"
django-polymorphic = "^2.1.2"
[tool.poetry.extras]
ldap = ["django-auth-ldap"]
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment