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
Showing
with 2025 additions and 294 deletions
import rules
from rules import is_superuser
from .models import AdditionalField, Announcement, Group, GroupType, Person
from .util.predicates import (
......@@ -338,5 +339,8 @@ rules.add_perm("core.delete_oauth_applications_rule", delete_oauth_applications_
upload_files_ckeditor_predicate = has_person & has_global_perm("core.upload_files_ckeditor")
rules.add_perm("core.upload_files_ckeditor_rule", upload_files_ckeditor_predicate)
manage_person_permissions_predicate = has_person & is_superuser
rules.add_perm("core.manage_permissions", manage_person_permissions_predicate)
test_pdf_generation_predicate = has_person & has_global_perm("core.test_pdf")
rules.add_perm("core.test_pdf_rule", test_pdf_generation_predicate)
......@@ -21,6 +21,10 @@ for directory in DIRS_FOR_DYNACONF:
SETTINGS_FILE_FOR_DYNACONF += glob(os.path.join(directory, "*.ini"))
SETTINGS_FILE_FOR_DYNACONF += glob(os.path.join(directory, "*.yaml"))
SETTINGS_FILE_FOR_DYNACONF += glob(os.path.join(directory, "*.toml"))
SETTINGS_FILE_FOR_DYNACONF += glob(os.path.join(directory, "*/*.json"))
SETTINGS_FILE_FOR_DYNACONF += glob(os.path.join(directory, "*/*.ini"))
SETTINGS_FILE_FOR_DYNACONF += glob(os.path.join(directory, "*/*.yaml"))
SETTINGS_FILE_FOR_DYNACONF += glob(os.path.join(directory, "*/*.toml"))
_settings = LazySettings(
ENVVAR_PREFIX_FOR_DYNACONF=ENVVAR_PREFIX_FOR_DYNACONF,
......@@ -493,7 +497,7 @@ NODE_MODULES_ROOT = _settings.get("node_modules.root", os.path.join(BASE_DIR, "n
YARN_INSTALLED_APPS = [
"@fontsource/roboto",
"jquery",
"materialize-css",
"@materializecss/materialize",
"material-design-icons-iconfont",
"select2",
"select2-materialize",
......@@ -513,7 +517,7 @@ SELECT2_JS = JS_URL + "/select2/dist/js/select2.min.js"
SELECT2_I18N_PATH = JS_URL + "/select2/dist/js/i18n"
ANY_JS = {
"materialize": {"js_url": JS_URL + "/materialize-css/dist/js/materialize.min.js"},
"materialize": {"js_url": JS_URL + "/@materializecss/materialize/dist/js/materialize.min.js"},
"jQuery": {"js_url": JS_URL + "/jquery/dist/jquery.min.js"},
"material-design-icons": {
"css_url": JS_URL + "/material-design-icons-iconfont/dist/material-design-icons.css"
......@@ -543,7 +547,9 @@ SASS_PROCESSOR_CUSTOM_FUNCTIONS = {
"get-preference": "aleksis.core.util.sass_helpers.get_preference",
}
SASS_PROCESSOR_INCLUDE_DIRS = [
_settings.get("materialize.sass_path", os.path.join(JS_ROOT, "materialize-css", "sass")),
_settings.get(
"materialize.sass_path", os.path.join(JS_ROOT, "@materializecss", "materialize", "sass")
),
os.path.join(STATIC_ROOT, "public"),
]
......
......@@ -768,3 +768,29 @@ main figure.alert {
.margin-bottom {
margin-bottom: 0.7rem !important;
}
/* Tabs with icons */
.tabs-icons, .tabs-icons .tab, .tabs-icons a {
height: 72px;
}
.tabs-icons .tab {
display: inline-flex;
flex-direction: column;
}
.tabs-icons .tab a {
padding-top: 12px;
line-height: 14px;
font-size: 12px;
font-weight: 500;
letter-spacing: 0.08em;
}
.tabs-icons .tab i {
font-size: 24px;
line-height: 1;
height: 24px;
margin-bottom: 8px;
}
......@@ -91,3 +91,66 @@ class DashboardWidgetTable(tables.Table):
def render_widget_name(self, value, record):
return record._meta.verbose_name
class PermissionDeleteColumn(tables.LinkColumn):
"""Link column with label 'Delete'."""
def __init__(self, url, **kwargs):
super().__init__(
url,
args=[A("pk")],
text=_("Delete"),
attrs={"a": {"class": "btn-flat waves-effect waves-red red-text"}},
verbose_name=_("Actions"),
**kwargs
)
class PermissionTable(tables.Table):
"""Table to list permissions."""
class Meta:
attrs = {"class": "responsive-table highlight"}
permission = tables.Column()
class ObjectPermissionTable(PermissionTable):
"""Table to list object permissions."""
content_object = tables.Column()
class GlobalPermissionTable(PermissionTable):
"""Table to list global permissions."""
pass
class GroupObjectPermissionTable(ObjectPermissionTable):
"""Table to list assigned group object permissions."""
group = tables.Column()
delete = PermissionDeleteColumn("delete_group_object_permission")
class UserObjectPermissionTable(ObjectPermissionTable):
"""Table to list assigned user object permissions."""
user = tables.Column()
delete = PermissionDeleteColumn("delete_user_object_permission")
class GroupGlobalPermissionTable(GlobalPermissionTable):
"""Table to list assigned global user permissions."""
group = tables.Column()
delete = PermissionDeleteColumn("delete_group_global_permission")
class UserGlobalPermissionTable(GlobalPermissionTable):
"""Table to list assigned global group permissions."""
user = tables.Column()
delete = PermissionDeleteColumn("delete_user_global_permission")
......@@ -69,7 +69,7 @@
</div>
<!-- Nav bar (logged in as, logout) -->
<nav>
<nav class="nav-extended">
<div class="nav-wrapper">
<a class="brand-logo" href="/">{{ request.site.preferences.general__title }}</a>
......@@ -82,6 +82,9 @@
{% endif %}
</ul>
</div>
<div class="nav-content">
{% block nav_content %}{% endblock %}
</div>
</nav>
<!-- Main nav (sidenav) -->
......@@ -169,7 +172,7 @@
<div class="container">
<div class="left">
<a class="blue-text text-lighten-4" href="{% url "about_aleksis" %}">
{% trans "About AlekSIS — The Free School Information System" %}
{% trans "About AlekSIS® — The Free School Information System" %}
</a>
© The AlekSIS Team
</div>
......
......@@ -69,7 +69,7 @@
</div>
<div class="right">
{% trans "Powered by AlekSIS" %}
{% trans "Powered by AlekSIS®" %}
</div>
</footer>
</div>
......
......@@ -85,7 +85,7 @@
<ul class="collection">
{% for notification in notifications %}
<li class="collection-item">
<span class="badge new primary-color">{{ notification.app }}</span>
<span class="badge new primary-color">{{ notification.sender }}</span>
<span class="title">{{ notification.title }}</span>
<p>
<i class="material-icons left">access_time</i> {{ notification.created }}
......
......@@ -3,8 +3,8 @@
{% load i18n %}
{% block browser_title %}{% blocktrans %}About AlekSIS{% endblocktrans %}{% endblock %}
{% block page_title %}{% blocktrans %}AlekSIS – The Free School Information System{% endblocktrans %}{% endblock %}
{% block browser_title %}{% blocktrans %}About AlekSIS®{% endblocktrans %}{% endblock %}
{% block page_title %}{% blocktrans %}AlekSIS® – The Free School Information System{% endblocktrans %}{% endblock %}
{% block content %}
......@@ -15,11 +15,16 @@
<span class="card-title">{% blocktrans %}About AlekSIS{% endblocktrans %}</span>
<p>
{% blocktrans %}
This platform is powered by AlekSIS, a web-based school information system (SIS) which can be used
This platform is powered by AlekSIS®, a web-based school information system (SIS) which can be used
to manage and/or publish organisational artifacts of educational institutions. AlekSIS is free software and
can be used by anyone.
{% endblocktrans %}
</p>
<p>
{% blocktrans %}
AlekSIS® is a registered trademark of the AlekSIS open source project, represented by Teckids e.V.
{% endblocktrans %}
</p>
</div>
<div class="card-action">
<a class="" href="https://aleksis.org/">{% trans "Website of AlekSIS" %}</a>
......
{# -*- engine:django -*- #}
{% extends "core/base.html" %}
{% load material_form i18n any_js %}
{% block extra_head %}
{{ form.media.css }}
{% include_css "select2-materialize" %}
{% endblock %}
{% block browser_title %}{% blocktrans %}Assign permission{% endblocktrans %}{% endblock %}
{% block page_title %}{% blocktrans %}Assign permission{% endblocktrans %}{% endblock %}
{% block content %}
<p class="flow-text">
{% trans "Selected permission" %}: {{ object }}
</p>
<form method="post">
{% csrf_token %}
<input type="hidden" name="step" value="{{ step }}">
{% form form=form %}{% endform %}
<button type="submit" class="btn green waves-effect waves-light">
<i class="material-icons left">save</i>
{% trans "Assign" %}
</button>
</form>
{% include_js "select2-materialize" %}
{{ form.media.js }}
{% endblock %}
{# -*- engine:django -*- #}
{% extends "core/base.html" %}
{% load material_form i18n any_js django_tables2 %}
{% block extra_head %}
{{ filter.form.media.css }}
{{ assign_form.media.css }}
{% include_css "select2-materialize" %}
{% endblock %}
{% block browser_title %}{% blocktrans %}Manage permissions{% endblocktrans %}{% endblock %}
{% block page_title %}{% blocktrans %}Manage permissions{% endblocktrans %}{% endblock %}
{% block content %}
<div class="card">
<div class="card-content">
<form action="{% url "select_permission_for_assign" %}?next={% url "manage_"|add:tab|add:"_permissions" %}"
method="post">
<div class="card-title">{% trans "Assign a new permission" %}</div>
{% csrf_token %}
{% form form=assign_form %}{% endform %}
<button type="submit" class="btn green waves-effect waves-light">
{% trans "Select" %}
</button>
</form>
</div>
</div>
<ul class="tabs">
<li class="tab">
<a target="_self" href="{% url "manage_user_global_permissions" %}"
{% if tab == "user_global" %}class="active"{% endif %}>{% trans "Global (user)" %}</a>
</li>
<li class="tab">
<a target="_self" href="{% url "manage_group_global_permissions" %}"
{% if tab == "group_global" %}class="active"{% endif %}>{% trans "Global (group)" %}</a>
</li>
<li class="tab">
<a target="_self" href="{% url "manage_user_object_permissions" %}"
{% if tab == "user_object" %}class="active"{% endif %}>{% trans "Object (user)" %}</a>
</li>
<li class="tab">
<a target="_self" href="{% url "manage_group_object_permissions" %}"
{% if tab == "group_object" %}class="active"{% endif %}>{% trans "Object (group)" %}</a>
</li>
</ul>
<div class="card">
<div class="card-content">
<div class="card-title">{% trans "Filter permissions" %}</div>
<form method="get" action="">
{% csrf_token %}
{% form form=filter.form %}{% endform %}
<button type="submit" class="btn waves-effect waves-light">
<i class="material-icons left">refresh</i>
{% trans "Update" %}
</button>
</form>
</div>
</div>
<div class="card">
<div class="card-content">
{% render_table table %}
</div>
</div>
{% include_js "select2-materialize" %}
{{ filter.form.media.js }}
{{ assign_form.media.js }}
{% endblock %}
......@@ -245,6 +245,56 @@ urlpatterns = [
{"default": True},
name="edit_default_dashboard",
),
path(
"permissions/global/user/",
views.UserGlobalPermissionsListBaseView.as_view(),
name="manage_user_global_permissions",
),
path(
"permissions/global/group/",
views.GroupGlobalPermissionsListBaseView.as_view(),
name="manage_group_global_permissions",
),
path(
"permissions/object/user/",
views.UserObjectPermissionsListBaseView.as_view(),
name="manage_user_object_permissions",
),
path(
"permissions/object/group/",
views.GroupObjectPermissionsListBaseView.as_view(),
name="manage_group_object_permissions",
),
path(
"permissions/global/user/<int:pk>/delete/",
views.UserGlobalPermissionDeleteView.as_view(),
name="delete_user_global_permission",
),
path(
"permissions/global/group/<int:pk>/delete/",
views.GroupGlobalPermissionDeleteView.as_view(),
name="delete_group_global_permission",
),
path(
"permissions/object/user/<int:pk>/delete/",
views.UserObjectPermissionDeleteView.as_view(),
name="delete_user_object_permission",
),
path(
"permissions/object/group/<int:pk>/delete/",
views.GroupObjectPermissionDeleteView.as_view(),
name="delete_group_object_permission",
),
path(
"permissions/assign/",
views.SelectPermissionForAssignView.as_view(),
name="select_permission_for_assign",
),
path(
"permissions/<int:pk>/assign/",
views.AssignPermissionView.as_view(),
name="assign_permission",
),
path("pdfs/<int:pk>/", views.RedirectToPDFFile.as_view(), name="redirect_to_pdf_file"),
]
......
......@@ -47,11 +47,15 @@ class CustomOAuth2Validator(OAuth2Validator):
django_request = HttpRequest()
django_request.META = request.headers
scopes = request.scopes.copy()
if request.access_token:
scopes += request.access_token.scope.split(" ")
claims = {
"preferred_username": request.user.username,
}
if "profile" in request.scopes:
if "profile" in scopes:
if has_person(request.user):
claims["given_name"] = request.user.person.first_name
claims["family_name"] = request.user.person.last_name
......@@ -66,13 +70,13 @@ class CustomOAuth2Validator(OAuth2Validator):
claims["given_name"] = request.user.first_name
claims["family_name"] = request.user.last_name
if "email" in request.scopes:
if "email" in scopes:
if has_person(request.user):
claims["email"] = request.user.person.email
else:
claims["email"] = request.user.email
if "address" in request.scopes and has_person(request.user):
if "address" in scopes and has_person(request.user):
claims["address"] = {
"street_address": request.user.person.street
+ " "
......@@ -81,8 +85,10 @@ class CustomOAuth2Validator(OAuth2Validator):
"postal_code": request.user.person.postal_code,
}
if "groups" in request.scopes and has_person(request.user):
claims["groups"] = request.user.person.groups.values_list("name", flat=True).all()
if "groups" in scopes and has_person(request.user):
claims["groups"] = list(
request.user.person.member_of.values_list("name", flat=True).all()
)
return claims
......
from typing import Any, Optional, Type
from typing import Any, Dict, Optional, Type
from urllib.parse import urlencode
from django.apps import apps
from django.conf import settings
from django.contrib.auth.models import Group as DjangoGroup
from django.contrib.auth.models import Permission, User
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import PermissionDenied, ValidationError
from django.core.paginator import Paginator
......@@ -24,6 +27,7 @@ from django.utils.translation import get_language
from django.utils.translation import gettext_lazy as _
from django.views.decorators.cache import never_cache
from django.views.defaults import ERROR_500_TEMPLATE_NAME
from django.views.generic import FormView
from django.views.generic.base import TemplateView, View
from django.views.generic.detail import DetailView, SingleObjectMixin
from django.views.generic.edit import DeleteView
......@@ -35,9 +39,10 @@ from allauth.socialaccount.adapter import get_adapter
from allauth.socialaccount.models import SocialAccount
from celery_progress.views import get_progress
from django_celery_results.models import TaskResult
from django_tables2 import RequestConfig, SingleTableView
from django_filters.views import FilterView
from django_tables2 import RequestConfig, SingleTableMixin, SingleTableView
from dynamic_preferences.forms import preference_form_builder
from guardian.shortcuts import get_objects_for_user
from guardian.shortcuts import GroupObjectPermission, UserObjectPermission, get_objects_for_user
from haystack.generic_views import SearchView
from haystack.inputs import AutoQuery
from haystack.query import SearchQuerySet
......@@ -50,9 +55,17 @@ from rules.contrib.views import PermissionRequiredMixin, permission_required
from aleksis.core.data_checks import DataCheckRegistry, check_data
from .celery import app
from .filters import GroupFilter, PersonFilter
from .filters import (
GroupFilter,
GroupGlobalPermissionFilter,
GroupObjectPermissionFilter,
PersonFilter,
UserGlobalPermissionFilter,
UserObjectPermissionFilter,
)
from .forms import (
AnnouncementForm,
AssignPermissionForm,
ChildGroupsForm,
DashboardWidgetOrderFormSet,
EditAdditionalFieldForm,
......@@ -63,9 +76,10 @@ from .forms import (
PersonForm,
PersonPreferenceForm,
SchoolTermForm,
SelectPermissionForm,
SitePreferenceForm,
)
from .mixins import AdvancedCreateView, AdvancedDeleteView, AdvancedEditView
from .mixins import AdvancedCreateView, AdvancedDeleteView, AdvancedEditView, SuccessNextMixin
from .models import (
AdditionalField,
Announcement,
......@@ -90,10 +104,14 @@ from .registries import (
from .tables import (
AdditionalFieldsTable,
DashboardWidgetTable,
GroupGlobalPermissionTable,
GroupObjectPermissionTable,
GroupsTable,
GroupTypesTable,
PersonsTable,
SchoolTermTable,
UserGlobalPermissionTable,
UserObjectPermissionTable,
)
from .util import messages
from .util.apps import AppConfig
......@@ -184,9 +202,9 @@ def index(request: HttpRequest) -> HttpResponse:
person = DummyPerson()
widgets = []
activities = person.activities.all()[:5]
notifications = person.notifications.all()[:5]
unread_notifications = person.notifications.all().filter(read=False)
activities = person.activities.all().order_by("-created")[:5]
notifications = person.notifications.all().order_by("-created")[:5]
unread_notifications = person.notifications.all().filter(read=False).order_by("-created")
context["activities"] = activities
context["notifications"] = notifications
......@@ -1033,6 +1051,136 @@ class EditDashboardView(PermissionRequiredMixin, View):
return render(request, "core/edit_dashboard.html", context=context)
class PermissionsListBaseView(PermissionRequiredMixin, SingleTableMixin, FilterView):
"""Base view for list of all permissions."""
template_name = "core/perms/list.html"
permission_required = "core.manage_permissions"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["assign_form"] = SelectPermissionForm()
context["tab"] = self.tab
return context
class UserGlobalPermissionsListBaseView(PermissionsListBaseView):
"""List all global user permissions."""
filterset_class = UserGlobalPermissionFilter
table_class = UserGlobalPermissionTable
tab = "user_global"
class GroupGlobalPermissionsListBaseView(PermissionsListBaseView):
"""List all global group permissions."""
filterset_class = GroupGlobalPermissionFilter
table_class = GroupGlobalPermissionTable
tab = "group_global"
class UserObjectPermissionsListBaseView(PermissionsListBaseView):
"""List all object user permissions."""
filterset_class = UserObjectPermissionFilter
table_class = UserObjectPermissionTable
tab = "user_object"
class GroupObjectPermissionsListBaseView(PermissionsListBaseView):
"""List all object group permissions."""
filterset_class = GroupObjectPermissionFilter
table_class = GroupObjectPermissionTable
tab = "group_object"
class SelectPermissionForAssignView(PermissionRequiredMixin, FormView):
"""View for selecting a permission to assign."""
permission_required = "core.manage_permissions"
form_class = SelectPermissionForm
def form_valid(self, form: SelectPermissionForm) -> HttpResponse:
url = reverse("assign_permission", args=[form.cleaned_data["selected_permission"].pk])
params = {"next": self.request.GET["next"]} if "next" in self.request.GET else {}
return redirect(f"{url}?{urlencode(params)}")
def form_invalid(self, form: SelectPermissionForm) -> HttpResponse:
return redirect("manage_group_object_permissions")
class AssignPermissionView(SuccessNextMixin, PermissionRequiredMixin, DetailView, FormView):
"""View for assigning a permission to users/groups for all/some objects."""
permission_required = "core.manage_permissions"
queryset = Permission.objects.all()
template_name = "core/perms/assign.html"
form_class = AssignPermissionForm
success_url = "manage_user_global_permissions"
def get_form_kwargs(self) -> Dict[str, Any]:
kwargs = super().get_form_kwargs()
kwargs["permission"] = self.get_object()
return kwargs
def get_context_data(self, **kwargs: Any) -> Dict[str, Any]:
# Overwrite get_context_data to ensure correct function call order
self.object = self.get_object()
context = super().get_context_data(**kwargs)
return context
def form_valid(self, form: AssignPermissionForm) -> HttpResponse:
form.save_perms()
messages.success(
self.request,
_("We have successfully assigned the permissions."),
)
return redirect(self.get_success_url())
class UserGlobalPermissionDeleteView(PermissionRequiredMixin, AdvancedDeleteView):
"""Delete a global user permission."""
permission_required = "core.manage_permissions"
model = User.user_permissions.through
success_message = _("The global user permission has been deleted.")
success_url = reverse_lazy("manage_user_global_permissions")
template_name = "core/pages/delete.html"
class GroupGlobalPermissionDeleteView(PermissionRequiredMixin, AdvancedDeleteView):
"""Delete a global group permission."""
permission_required = "core.manage_permissions"
model = DjangoGroup.permissions.through
success_message = _("The global group permission has been deleted.")
success_url = reverse_lazy("manage_group_global_permissions")
template_name = "core/pages/delete.html"
class UserObjectPermissionDeleteView(PermissionRequiredMixin, AdvancedDeleteView):
"""Delete a object user permission."""
permission_required = "core.manage_permissions"
model = UserObjectPermission
success_message = _("The object user permission has been deleted.")
success_url = reverse_lazy("manage_user_object_permissions")
template_name = "core/pages/delete.html"
class GroupObjectPermissionDeleteView(PermissionRequiredMixin, AdvancedDeleteView):
"""Delete a object group permission."""
permission_required = "core.manage_permissions"
model = GroupObjectPermission
success_message = _("The object group permission has been deleted.")
success_url = reverse_lazy("manage_group_object_permissions")
template_name = "core/pages/delete.html"
class OAuth2ListView(PermissionRequiredMixin, ListView):
"""List view for all the applications."""
......
......@@ -48,8 +48,7 @@ wait_database() {
prepare_database() {
# Migrate database; should only be run in app container or job
aleksis-admin migrate
aleksis-admin createinitialrevisions
aleksis-admin migrate && aleksis-admin createinitialrevisions
}
# Wait for database to be reachable under all conditions
......
......@@ -29,9 +29,9 @@ copyright = "2019, 2020, AlekSIS team"
author = "AlekSIS team"
# The short X.Y version
version = "2.1"
version = "2.2"
# The full version, including alpha/beta/rc tags
release = "2.1.1"
release = "2.2"
# -- General configuration ---------------------------------------------------
......
......@@ -29,6 +29,51 @@ To fully remove page or browser title, use these template tags::
{% block no_browser_title %}{% endblock %}
{% block no_page_title %}{% endblock %}
Extended navbar
---------------
The top navbar with the site title and the login status can be extended by an additional tab bar, for example.
To add normal Materialize tabs without icons, you can use a snippet like described in `Extended Navbar with Tabs`_:
.. code-block:: html+django
{% block nav_content %}
<ul class="tabs tabs-transparent">
<li class="tab"><a href="#test1">Test 1</a></li>
<li class="tab"><a class="active" href="#test2">Test 2</a></li>
<li class="tab disabled"><a href="#test3">Disabled Tab</a></li>
<li class="tab"><a href="#test4">Test 4</a></li>
</ul>
{% endblock %}
Furthermore, you can use tabs with integrated icons that are higher, but more compact in width:
.. code-block:: html+django
{% block nav_content %}
<ul class="tabs tabs-transparent tabs-icons tabs-fixed-width">
<li class="tab">
<a href="#test1">
<i class="material-icons">speaker_notes</i>
Test 1
</a>
</li>
<li class="tab">
<a href="#test2">
<i class="material-icons">people</i>
Test 2
</a>
</li>
</ul>
{% endblock %}
.. warning::
Don't define the ``nav_content`` block within the ``content`` block – that won't work.
Forms in templates
------------------
......@@ -91,3 +136,4 @@ After you've loaded the template tag, you can simply generate the table like thi
.. _MaterializeCSS: https://materializecss.com/
.. _django-material: https://pypi.org/project/django-material/
.. _Extended Navbar with Tabs: https://materializecss.com/navbar.html#navbar-tabs
\ No newline at end of file
This diff is collapsed.
[tool.poetry]
name = "AlekSIS-Core"
version = "2.1.1"
version = "2.3.dev0"
packages = [
{ include = "aleksis" }
]
......@@ -29,7 +29,7 @@ repository = "https://edugit.org/AlekSIS/official/AlekSIS-Core"
documentation = "https://aleksis.org/AlekSIS-Core/docs/html/"
keywords = ["SIS", "education", "school", "digitisation", "school apps"]
classifiers = [
"Development Status :: 4 - Beta",
"Development Status :: 5 - Production/Stable",
"Environment :: Web Environment",
"Framework :: Django :: 3.0",
"Intended Audience :: Developers",
......@@ -74,7 +74,7 @@ html2text = "^2020.0.0"
django-ckeditor = "^6.0.0"
django-js-reverse = "^0.9.1"
calendarweek = "^0.5.0"
Celery = {version="^5.0.0", extras=["django", "redis"]}
Celery = {version=">=5.0,<5.2", extras=["django", "redis"]}
django-celery-results = "^2.0.1"
django-celery-beat = "^2.2.0"
django-celery-email = "^3.0.0"
......