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
Commits on Source (31)
Showing
with 120 additions and 116 deletions
......@@ -9,32 +9,57 @@ and this project adheres to `Semantic Versioning`_.
Unreleased
----------
Breaking changes
~~~~~~~~~~~~~~~~
Added
~~~~~
* Notification drawer in top nav bar
* GraphQL queries and mutations for core data management
Changed
~~~~~~~
* Rewrite of frontend using Vuetify
Removed
~~~~~~~
* Support for materialize-based frontend views (deprecated in 2.11)
* Django debug toolbar
* It caused major performance issues and is not useful with the new
frontend anymore
`2.11`_ - 2022-08-27
--------------------
* The frontend is being rewritten as a Vue.js application compeltely separated from
the backend. This requires changes to deployments. The admin docs have been updated
accordingly. Administrators need to make sure to read the updated guide before
upgrading AlekSIS-Core to this version!
This release sunsets the 2.x series of the AleKSIS core.
Deprecated
~~~~~~~~~~
* All frontends using Django views and Django templates are deprecated and support
for them will be removed in AlekSIS-Core 4.0. All frontend code must be written in
for them will be removed in AlekSIS-Core 3.0. All frontend code must be written in
Vue.js and be properly separated from the backend. In the same spirit, all backend
features must expose GraphQL APIs for the frontendd to use
features must expose GraphQL APIs for the frontend to use.
Added
~~~~~
The following features are introduced here mainly to simplify gradual
updates. GraphQL and the Vuetify/Vue.js frontend mechanisms are preview
functionality and app developers should not rely on them before AlekSIS-Core
3.0.
* Introduce GraphQL API and Vue.js frontend implementation
* Introduce webpack bundling for frontend code
`2.10.2`_ - 2022-08-25
----------------------
Fixed
~~~~~
* Celery's logging did not honour Django's logging level
* Automatically clean up expired OAuth tokens after 24 hourse
`2.10.1`_ - 2022-07-24
----------------------
......@@ -911,3 +936,5 @@ Fixed
.. _2.9: https://edugit.org/AlekSIS/Official/AlekSIS/-/tags/2.9
.. _2.10: https://edugit.org/AlekSIS/Official/AlekSIS/-/tags/2.10
.. _2.10.1: https://edugit.org/AlekSIS/Official/AlekSIS/-/tags/2.10.1
.. _2.10.2: https://edugit.org/AlekSIS/Official/AlekSIS/-/tags/2.10.2
.. _2.11: https://edugit.org/AlekSIS/Official/AlekSIS/-/tags/2.11
......@@ -6,6 +6,8 @@ import "vuetify/dist/vuetify.min.css"
import ApolloClient from 'apollo-boost'
import VueApollo from 'vue-apollo'
import "./css/global.scss"
Vue.use(Vuetify)
Vue.use(VueRouter)
......
......@@ -12,11 +12,12 @@
:value="next_url"
type="hidden"
></v-text-field>
<input
<v-text-field
v-show="false"
v-model="language"
name="language"
:value="current_language"
type="hidden"
>
></v-text-field>
<v-menu offset-y>
<template v-slot:activator="{ on, attrs }">
<v-btn
......@@ -26,16 +27,16 @@
color="primary"
>
<v-icon icon color="white">mdi-translate</v-icon>
{{ current_language }}
{{ language }}
</v-btn>
</template>
<v-list id="language-dropdown" class="dropdown-content">
<v-list-item-group
v-model="current_language"
v-model="language"
color="primary"
>
<v-list-item v-for="language in items" :key="language[0]" :value="language[0]" @click="submit(language[0])">
<v-list-item-title>{{ language[1] }}</v-list-item-title>
<v-list-item v-for="language_option in items" :key="language_option[0]" :value="language_option[0]" @click="submit(language_option[0])">
<v-list-item-title>{{ language_option[1] }}</v-list-item-title>
</v-list-item>
</v-list-item-group>
</v-list>
......@@ -47,12 +48,14 @@
export default {
data: () => ({
items: JSON.parse(document.getElementById("language-info-list").textContent),
current_language: JSON.parse(document.getElementById("current-language").textContent),
language: JSON.parse(document.getElementById("current-language").textContent),
}),
methods: {
submit: function (language) {
this.current_language = language;
// this.$refs.form.submit()
submit: function (new_language) {
this.language = new_language;
this.$nextTick(() => {
this.$refs.form.submit();
});
},
},
props: ["action", "csrf_value", "next_url"],
......
......@@ -22,7 +22,7 @@
<v-list-item-action v-if="notification.link">
<v-btn text :href="notification.link">
{{ this.$root.django.gettext('More information →') }}
{{ $root.django.gettext('More information →') }}
</v-btn>
</v-list-item-action>
......
......@@ -4,14 +4,14 @@
:pollInterval="1000"
>
<template v-slot="{ result: { error, data }, isLoading }">
<v-list two-line v-if="data && data.myNotifications.notifications">
<v-list two-line v-if="data && data.myNotifications.notifications.length">
<NotificationItem
v-for="notification in data.myNotifications.notifications"
:key="notification.id"
:notification="notification"
/>
</v-list>
<p v-else>{{ this.$root.django.gettext('No notifications available yet.') }}</p>
<p v-else>{{ $root.django.gettext('No notifications available yet.') }}</p>
</template>
</ApolloQuery>
</template>
......
......@@ -31,4 +31,5 @@ class Command(BaseYarnCommand):
# Run webpack
config_path = os.path.join(settings.BASE_DIR, "aleksis", "core", "webpack.config.js")
shutil.copy(config_path, settings.NODE_MODULES_ROOT)
yarn_adapter.call_yarn(["run", "webpack"])
mode = "development" if settings.DEBUG else "production"
yarn_adapter.call_yarn(["run", "webpack", f"--mode={mode}"])
......@@ -56,29 +56,6 @@ SECRET_KEY = _settings.get("secret_key", "DoNotUseInProduction")
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = _settings.get("maintenance.debug", False)
INTERNAL_IPS = _settings.get("maintenance.internal_ips", [])
DEBUG_TOOLBAR_CONFIG = {
"RENDER_PANELS": True,
"SHOW_COLLAPSED": True,
"JQUERY_URL": "",
"SHOW_TOOLBAR_CALLBACK": "aleksis.core.util.core_helpers.dt_show_toolbar",
"DISABLE_PANELS": {},
}
DEBUG_TOOLBAR_PANELS = [
"debug_toolbar.panels.versions.VersionsPanel",
"debug_toolbar.panels.timer.TimerPanel",
"debug_toolbar.panels.settings.SettingsPanel",
"debug_toolbar.panels.headers.HeadersPanel",
"debug_toolbar.panels.request.RequestPanel",
"debug_toolbar.panels.sql.SQLPanel",
"debug_toolbar.panels.cache.CachePanel",
"debug_toolbar.panels.staticfiles.StaticFilesPanel",
"debug_toolbar.panels.templates.TemplatesPanel",
"debug_toolbar.panels.signals.SignalsPanel",
"debug_toolbar.panels.logging.LoggingPanel",
"debug_toolbar.panels.profiling.ProfilingPanel",
"django_uwsgi.panels.UwsgiPanel",
]
UWSGI = {
"module": "aleksis.core.wsgi",
......@@ -124,7 +101,6 @@ INSTALLED_APPS = [
"menu_generator",
"reversion",
"phonenumber_field",
"debug_toolbar",
"django_prometheus",
"django_select2",
"templated_email",
......@@ -178,7 +154,6 @@ MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"debug_toolbar.middleware.DebugToolbarMiddleware",
"django.middleware.locale.LocaleMiddleware",
"django.middleware.http.ConditionalGetMiddleware",
"django.contrib.sites.middleware.CurrentSiteMiddleware",
......@@ -282,7 +257,6 @@ else:
}
INSTALLED_APPS.append("cachalot")
DEBUG_TOOLBAR_PANELS.append("cachalot.panels.CachalotPanel")
CACHALOT_TIMEOUT = _settings.get("caching.cachalot.timeout", None)
CACHALOT_DATABASES = set(["default", "default_oot"])
SILENCED_SYSTEM_CHECKS += ["cachalot.W001"]
......@@ -400,6 +374,7 @@ OAUTH2_PROVIDER = {
"SCOPES_BACKEND_CLASS": "aleksis.core.util.auth_helpers.AppScopes",
"OAUTH2_VALIDATOR_CLASS": "aleksis.core.util.auth_helpers.CustomOAuth2Validator",
"OIDC_ENABLED": True,
"REFRESH_TOKEN_EXPIRE_SECONDS": _settings.get("oauth2.token_expiry", 86400),
}
OAUTH2_PROVIDER_APPLICATION_MODEL = "core.OAuthApplication"
OAUTH2_PROVIDER_GRANT_MODEL = "core.OAuthGrant"
......@@ -591,13 +566,13 @@ YARN_INSTALLED_APPS = [
"deepmerge@^4.2.2",
"graphql@^15.8.0",
"graphql-tag@^2.12.6",
"sass@~1.32",
"sass@^1.32",
"vue@^2.7.7",
"vue-apollo@^3.1.0",
"vuetify@^2.6.7",
"vue-router@^3.5.2",
"css-loader@^6.7.1",
"sass-loader@^8.0",
"sass-loader@^13.0",
"vue-loader@^15.0.0",
"vue-style-loader@^4.1.3",
"vue-template-compiler@^2.7.7",
......
{# -*- engine:django -*- #}
{% for item in FOOTER_MENU.items.all %}
<v-btn plain tile href="{{ item.url }}" color="primary">
<v-btn text rounded href="{{ item.url }}" color="white" class="ma-2">
{% if item.icon %}
<v-icon left>mdi-{{ item.icon }}</v-icon>
{% endif %}
......
......@@ -98,7 +98,8 @@
</v-toolbar-title>
<v-spacer></v-spacer>
<language-form action="{% url "set_language" %}" csrf_value={{ csrf_token }} next_url={{ request.get_full_path }}></language-form>
<language-form action="{% url "set_language" %}"
csrf_value={{ csrf_token }} next_url={{ request.get_full_path }}></language-form>
{% if user.is_authenticated %}
<v-menu offset-y>
<template v-slot:activator="{ on, attrs }">
......@@ -122,7 +123,7 @@
</template>
{% get_menu "NAVBAR_ACCOUNT_MENU" as account_menu %}
<v-list>
<v-subheader>REPORTS</v-subheader>
<v-subheader>{% trans "Logged in as" %} {% firstof user.person.full_name user.get_username %}</v-subheader>
{% for item in account_menu %}
{% if item.divider %}
<v-divider></v-divider>
......@@ -168,36 +169,53 @@
</v-container>
</v-main>
<v-footer app absolute inset class="white--text">
<v-container>
<v-row>
<v-col cols="12" lg="6">
{% include "core/partials/vue_footer_menu.html" %}
</v-col>
<v-col cols="12" >
<v-footer app absolute inset dark class="pa-0 d-flex" color="primary lighten-1">
<v-card
flat
tile
class="primary white--text flex-grow-1"
>
{% if FOOTER_MENU.items.all %}
<v-card-text class="pa-0">
<v-container class="px-6">
<v-row
justify="center"
no-gutters
>
{% include "core/partials/vue_footer_menu.html" %}
</v-row>
</v-container>
</v-card-text>
<v-divider></v-divider>
{% endif %}
<v-card-text class="pa-0">
<v-container class="px-6">
<v-row>
<v-btn plain rounded href="{% url "about_aleksis" %}" color="white">
{% trans "About AlekSIS® — The Free School Information System" %}
</v-btn>
<span>© The AlekSIS Team</span>
<v-spacer></v-spacer>
{% if request.site.preferences.footer__imprint_url %}
<v-btn plain rounded href="{{ request.site.preferences.footer__imprint_url }}" color="white">
{% trans "Imprint" %}
</v-btn>
{% endif %}
{% if request.site.preferences.footer__privacy_url and request.site.preferences.footer__imprint_url %}
·
{% endif %}
{% if request.site.preferences.footer__privacy_url %}
<v-btn plain rounded href="{{ request.site.preferences.footer__privacy_url }}" color="white">
{% trans "Privacy Policy" %}
</v-btn>
{% endif %}
<v-col class="white--text d-flex align-center subtitle-2">
<div>
<a href="{% url "about_aleksis" %}" class="white--text text-decoration-none">
{% trans "About AlekSIS® — The Free School Information System" %}
</a>
<span>© The AlekSIS Team</span>
</div>
</v-col>
<v-col class="d-flex justify-end">
{% if request.site.preferences.footer__imprint_url %}
<v-btn small text href="{{ request.site.preferences.footer__imprint_url }}" color="white">
{% trans "Imprint" %}
</v-btn>
{% endif %}
{% if request.site.preferences.footer__privacy_url %}
<v-btn small text href="{{ request.site.preferences.footer__privacy_url }}" color="white">
{% trans "Privacy Policy" %}
</v-btn>
{% endif %}
</v-col>
</v-row>
</v-col>
</v-row>
</v-container>
</v-container>
</v-card-text>
</v-card>
</v-footer>
</v-app>
</main>
......
......@@ -7,10 +7,8 @@ from django.views.decorators.csrf import csrf_exempt
from django.views.i18n import JavaScriptCatalog
import calendarweek.django
import debug_toolbar
from ckeditor_uploader import views as ckeditor_uploader_views
from django_js_reverse.views import urls_js
from graphene_django.views import GraphQLView
from health_check.urls import urlpatterns as health_urls
from oauth2_provider.views import ConnectDiscoveryInfoView
from rules.contrib.views import permission_required
......@@ -144,7 +142,7 @@ urlpatterns = [
name="oauth2_provider:authorize",
),
path("oauth/", include("oauth2_provider.urls", namespace="oauth2_provider")),
path("graphql/", csrf_exempt(GraphQLView.as_view(graphiql=True)), name="graphql"),
path("graphql/", csrf_exempt(views.PrivateGraphQLView.as_view(graphiql=True)), name="graphql"),
path("__i18n__/", include("django.conf.urls.i18n")),
path(
"ckeditor/upload/",
......@@ -336,10 +334,6 @@ if hasattr(settings, "TWILIO_ACCOUNT_SID"):
urlpatterns += [path("", include(tf_twilio_urls))]
# Serve javascript-common if in development
if settings.DEBUG:
urlpatterns.append(path("__debug__/", include(debug_toolbar.urls)))
# Automatically mount URLs from all installed AlekSIS apps
for app_config in apps.app_configs.values():
if not app_config.name.startswith("aleksis.apps."):
......
......@@ -41,25 +41,6 @@ def copyright_years(years: Sequence[int], separator: str = ", ", joiner: str = "
return separator.join(years_strs)
def dt_show_toolbar(request: HttpRequest) -> bool:
"""Add a helper to determine if Django debug toolbar should be displayed.
Extends the default behaviour by enabling DJDT for superusers independent
of source IP.
"""
from debug_toolbar.middleware import show_toolbar # noqa
if not settings.DEBUG:
return False
if show_toolbar(request):
return True
elif hasattr(request, "user") and request.user.is_superuser:
return True
return False
def get_app_packages(only_official: bool = False) -> Sequence[str]:
"""Find all registered apps from the setuptools entrypoint."""
apps = []
......
......@@ -45,6 +45,7 @@ from django_celery_results.models import TaskResult
from django_filters.views import FilterView
from django_tables2 import RequestConfig, SingleTableMixin, SingleTableView
from dynamic_preferences.forms import preference_form_builder
from graphene_django.views import GraphQLView
from guardian.shortcuts import GroupObjectPermission, UserObjectPermission, get_objects_for_user
from haystack.generic_views import SearchView
from haystack.inputs import AutoQuery
......@@ -1615,3 +1616,7 @@ class ICalFeedCreateView(PermissionRequiredMixin, AdvancedCreateView):
obj.person = self.request.user.person
obj.save()
return super().form_valid(form)
class PrivateGraphQLView(LoginRequiredMixin, GraphQLView):
"""GraphQL view that requires a valid user session."""
......@@ -36,16 +36,15 @@ module.exports = {
use: ['vue-style-loader', 'css-loader'],
},
{
test: /\.s(c|a)ss$/,
test: /\.scss$/,
use: [
'vue-style-loader',
'css-loader',
{
loader: 'sass-loader',
options: {
implementation: require('sass'),
sassOptions: {
indentedSyntax: true
indentedSyntax: false
},
},
},
......
......@@ -31,7 +31,7 @@ author = "The AlekSIS Team"
# The short X.Y version
version = "3.0"
# The full version, including alpha/beta/rc tags
release = "3.0.dev1"
release = "3.0.dev2"
# -- General configuration ---------------------------------------------------
......
[tool.poetry]
name = "AlekSIS-Core"
version = "3.0.dev1"
version = "3.0.dev2"
packages = [
{ include = "aleksis" }
]
......@@ -57,7 +57,6 @@ secondary = true
python = "^3.9"
Django = "^3.2.5"
django-any-js = "^1.1"
django-debug-toolbar = "^3.2"
django-menu-generator-ng = "^1.2.3"
django-tables2 = "^2.1"
django-phonenumber-field = {version = "^6.1", extras = ["phonenumbers"]}
......@@ -113,7 +112,7 @@ django-allauth = "^0.51.0"
django-uwsgi-ng = "^1.1.0"
django-extensions = "^3.1.1"
ipython = "^8.0.0"
django-oauth-toolkit = "^1.7.0"
django-oauth-toolkit = "^2.0.0"
django-redis = "^5.0.0"
django-storages = {version = "^1.11.1", optional = true}
boto3 = {version = "^1.17.33", optional = true}
......@@ -122,7 +121,7 @@ djangorestframework = "^3.12.4"
Whoosh = "^2.7.4"
django-titofisto = "^0.2.0"
haystack-redis = "^0.0.1"
python-gnupg = "^0.4.7"
python-gnupg = "^0.5.0"
sentry-sdk = {version = "^1.4.3", optional = true}
django-cte = "^1.1.5"
pycountry = "^22.0.0"
......