diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 7e7b7b0372303cb6d73cfaff5b30c29114290348..e1013e5c86e13b7996c40fef585573fa0d0cb8d4 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -12,12 +12,24 @@ Unreleased Added ~~~~~ +* Allow to disable exception mails to admins * Add possibility to create iCal feeds in all apps and dynamically create user-specific urls. Fixed ~~~~~ * The menu button used to be displayed twice on smaller screens. +* The icons were loaded from external servers instead from local server. +* Weekdays were not translated if system locales were missing + * Added locales-all to base image and note to docs +* The icons in the account menu were still the old ones. +* Due to a merge error, the once removed account menu in the sidenav appeared again. + +Changed +~~~~~~~ + +* [Dev] ActionForm now checks permissions on objects before executing +* [Dev] ActionForm now returns a proper return value from the executed action 2.8.1`_ - 2022-03-13 -------------------- diff --git a/Dockerfile b/Dockerfile index acb08f98ffe79dccc32f048590bb123c2763302f..4864ac54613a8823fbc118fe73291e127b7939e1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -36,6 +36,7 @@ RUN apt-get -y update && \ less \ libpq-dev \ libssl-dev \ + locales-all \ postgresql-client-14 \ pspg \ python3-dev \ diff --git a/aleksis/core/forms.py b/aleksis/core/forms.py index ef4184812f73d22bb1dcfa1ff84f74f722c3c774..3fb938822b093e250299a3fac04f0e0358e6f47a 100644 --- a/aleksis/core/forms.py +++ b/aleksis/core/forms.py @@ -39,7 +39,7 @@ from .registries import ( site_preferences_registry, ) from .util.auth_helpers import AppScopes -from .util.core_helpers import get_site_preferences +from .util.core_helpers import get_site_preferences, queryset_rules_filter class PersonForm(ExtensibleForm): @@ -722,17 +722,37 @@ class ActionForm(forms.Form): self.fields["selected_objects"].queryset = self.queryset self.fields["action"].choices = self._get_action_choices() - def execute(self) -> bool: + def clean_action(self): + action = self._get_actions_dict().get(self.cleaned_data["action"], None) + if not action: + raise ValidationError(_("The selected action does not exist.")) + return action + + def clean_selected_objects(self): + action = self.cleaned_data["action"] + if hasattr(action, "permission"): + selected_objects = queryset_rules_filter( + self.request, self.cleaned_data["selected_objects"], action.permission + ) + if selected_objects.count() < self.cleaned_data["selected_objects"].count(): + raise ValidationError( + _("You do not have permission to run {} on all selected objects.").format( + getattr(value, "short_description", value.__name__) + ) + ) + return self.cleaned_data["selected_objects"] + + def execute(self) -> Any: """Execute the selected action on all selected objects. - :return: If the form is not valid, it will return ``False``. + :return: the return value of the action """ if self.is_valid(): data = self.cleaned_data["selected_objects"] - action = self._get_actions_dict()[self.cleaned_data["action"]] - action(None, self.request, data) - return True - return False + action = self.cleaned_data["action"] + return action(None, self.request, data) + + raise TypeError("execute() must be called on a pre-validated form.") class ListActionForm(ActionForm): diff --git a/aleksis/core/menus.py b/aleksis/core/menus.py index 4d2a2d08328de7a4f9c3ce52d91a088d90dd1a88..a714559e822ebe69e3e32e7c3d1bdd1791436a1d 100644 --- a/aleksis/core/menus.py +++ b/aleksis/core/menus.py @@ -45,90 +45,10 @@ MENUS = { "validators": [ ( "aleksis.core.util.predicates.permission_validator", - "core.view_notifications", + "core.view_notifications_rule", ), ], }, - { - "name": _("Account"), - "url": "#", - "svg_icon": "mdi:account-outline", - "root": True, - "validators": ["menu_generator.validators.is_authenticated"], - "submenu": [ - { - "name": _("Stop impersonation"), - "url": "impersonate-stop", - "svg_icon": "mdi:stop", - "validators": [ - "menu_generator.validators.is_authenticated", - "aleksis.core.util.core_helpers.is_impersonate", - ], - }, - { - "name": _("Logout"), - "url": "logout", - "svg_icon": "mdi:logout-variant", - "validators": ["menu_generator.validators.is_authenticated"], - }, - { - "name": _("2FA"), - "url": "two_factor:profile", - "svg_icon": "mdi:two-factor-authentication", - "validators": [ - "menu_generator.validators.is_authenticated", - ], - }, - { - "name": _("Change password"), - "url": "account_change_password", - "svg_icon": "mdi:form-textbox-password", - "validators": [ - "menu_generator.validators.is_authenticated", - ( - "aleksis.core.util.predicates.permission_validator", - "core.can_change_password", - ), - ], - }, - { - "name": _("Me"), - "url": "person", - "svg_icon": "mdi:emoticon-outline", - "validators": [ - "menu_generator.validators.is_authenticated", - "aleksis.core.util.core_helpers.has_person", - ], - }, - { - "name": _("Preferences"), - "url": "preferences_person", - "svg_icon": "mdi:cog-outline", - "validators": [ - "menu_generator.validators.is_authenticated", - "aleksis.core.util.core_helpers.has_person", - ], - }, - { - "name": _("Third-party accounts"), - "url": "socialaccount_connections", - "svg_icon": "mdi:earth", - "validators": [ - "menu_generator.validators.is_authenticated", - "aleksis.core.util.core_helpers.has_person", - ], - }, - { - "name": _("Authorized applications"), - "url": "oauth2_provider:authorized-token-list", - "svg_icon": "mdi:gesture-tap-hold", - "validators": [ - "menu_generator.validators.is_authenticated", - "aleksis.core.util.core_helpers.has_person", - ], - }, - ], - }, { "name": _("Admin"), "url": "#", @@ -333,7 +253,7 @@ MENUS = { { "name": _("Stop impersonation"), "url": "impersonate-stop", - "icon": "stop", + "svg_icon": "mdi:stop", "validators": [ "menu_generator.validators.is_authenticated", "aleksis.core.util.core_helpers.is_impersonate", @@ -342,7 +262,7 @@ MENUS = { { "name": _("Account"), "url": "person", - "icon": "person", + "svg_icon": "mdi:account-outline", "validators": [ "menu_generator.validators.is_authenticated", "aleksis.core.util.core_helpers.has_person", @@ -351,7 +271,7 @@ MENUS = { { "name": _("Preferences"), "url": "preferences_person", - "icon": "settings", + "svg_icon": "mdi:cog-outline", "validators": [ "menu_generator.validators.is_authenticated", "aleksis.core.util.core_helpers.has_person", @@ -360,7 +280,7 @@ MENUS = { { "name": _("2FA"), "url": "two_factor:profile", - "icon": "phonelink_lock", + "svg_icon": "mdi:two-factor-authentication", "validators": [ "menu_generator.validators.is_authenticated", ], @@ -368,7 +288,7 @@ MENUS = { { "name": _("Change password"), "url": "account_change_password", - "icon": "lock", + "svg_icon": "mdi:form-textbox-password", "validators": [ "menu_generator.validators.is_authenticated", ( @@ -380,7 +300,7 @@ MENUS = { { "name": _("Third-party accounts"), "url": "socialaccount_connections", - "icon": "public", + "svg_icon": "mdi:earth", "validators": [ "menu_generator.validators.is_authenticated", "aleksis.core.util.core_helpers.has_person", @@ -389,7 +309,7 @@ MENUS = { { "name": _("Authorized applications"), "url": "oauth2_provider:authorized-token-list", - "icon": "touch_app", + "svg_icon": "mdi:gesture-tap-hold", "validators": [ "menu_generator.validators.is_authenticated", "aleksis.core.util.core_helpers.has_person", @@ -399,7 +319,7 @@ MENUS = { "divider": True, "name": _("Logout"), "url": "logout", - "icon": "exit_to_app", + "svg_icon": "mdi:logout-variant", "validators": ["menu_generator.validators.is_authenticated"], }, ], diff --git a/aleksis/core/settings.py b/aleksis/core/settings.py index e329e9bc69ae795aae7c125cd31f69bd5e1ced98..3fdcbd99316f7f70f6bf232e45509715735e428c 100644 --- a/aleksis/core/settings.py +++ b/aleksis/core/settings.py @@ -1,8 +1,10 @@ import os import warnings +from copy import deepcopy from glob import glob from socket import getfqdn +from django.utils.log import DEFAULT_LOGGING from django.utils.translation import gettext_lazy as _ from dynaconf import LazySettings @@ -870,21 +872,22 @@ BLEACH_STRIP_TAGS = True # Strip comments, or leave them in. BLEACH_STRIP_COMMENTS = True -LOGGING = { - "version": 1, - "disable_existing_loggers": False, - "handlers": { - "console": {"class": "logging.StreamHandler", "formatter": "verbose"}, - "null": {"class": "logging.NullHandler"}, - }, - "formatters": {"verbose": {"format": "%(levelname)s %(asctime)s %(module)s: %(message)s"}}, - "root": { - "handlers": ["console"], - "level": _settings.get("logging.level", "WARNING"), - }, - "loggers": {}, +LOGGING = deepcopy(DEFAULT_LOGGING) +# Set root logging level as default +LOGGING["root"] = { + "handlers": ["console"], + "level": _settings.get("logging.level", "WARNING"), } - +# Add null handler for selective silencing +LOGGING["handlers"]["null"] = {"class": "logging.NullHandler"} +# Make console logging independent of DEBUG +LOGGING["handlers"]["console"]["filters"].remove("require_debug_true") +# Use root log level for console +del LOGGING["handlers"]["console"]["level"] +# Disable exception mails if not desired +if not _settings.get("logging.mail_admins", True): + LOGGING["loggers"]["django"]["handlers"].remove("mail_admins") +# Disable mails on disaalowed host by default if not _settings.get("logging.disallowed_host", False): LOGGING["loggers"]["django.security.DisallowedHost"] = { "handlers": ["null"], diff --git a/aleksis/core/static/public/style.scss b/aleksis/core/static/public/style.scss index 889b790f98cb0be7ed2de37a12a884acff5c4ca9..eed6f85f6931bbb0edfce33645875fb0bff61dc2 100644 --- a/aleksis/core/static/public/style.scss +++ b/aleksis/core/static/public/style.scss @@ -853,15 +853,15 @@ $person-logo-size: 20vh; } } -i.material-icons.new-notification { +a.new-notification { position: relative; &:after { content: ""; position: absolute; - width: 12px; - height: 12px; - bottom: 27%; - right: -4%; + width: 10px; + height: 10px; + bottom: 30%; + right: 19%; background-color: $secondary-color; border-radius: 50%; } diff --git a/aleksis/core/templates/core/base.html b/aleksis/core/templates/core/base.html index 56ba89c532ea7fbbbe20f8ebfb68830d16d61044..3158986ac5ed921244b43663e64c2fe9d728cd32 100644 --- a/aleksis/core/templates/core/base.html +++ b/aleksis/core/templates/core/base.html @@ -80,11 +80,10 @@ <ul class="account-nav"> {% trans "Notifications" as notifications_text %} <li> - <a href="{% url "notifications" %}" class="tooltipped" data-position="bottom" + <a href="{% url "notifications" %}" data-position="bottom" + class="tooltipped {% if request.user.person.unread_notifications_count > 0 %}new-notification{% endif %}" data-tooltip="{{ notifications_text }}" aria-label="{{ notifications_text }}"> - <i class="material-icons {% if request.user.person.unread_notifications_count > 0 %}new-notification{% endif %}"> - notifications - </i> + <i class="material-icons iconify" data-icon="mdi:bell-outline"></i> </a> </li> <li> @@ -113,6 +112,8 @@ <a href="{{ item.url }}"> {% if item.icon %} <i class="material-icons">{{ item.icon }}</i> + {% elif item.svg_icon %} + <i class="material-icons iconify" data-icon="{{ item.svg_icon }}"></i> {% endif %} {{ item.name }} </a> diff --git a/docs/admin/10_install.rst b/docs/admin/10_install.rst index d41bd44e56c1fd85208467e9849d448d960c1900..f8b41d0d98efd5daa1df64a5fceea9ae5b86c662 100644 --- a/docs/admin/10_install.rst +++ b/docs/admin/10_install.rst @@ -34,6 +34,7 @@ For an installation on a dedicated server, the following prerequisites are neede * nginx * Python 3.9 * Some system dependencies to build Python modules and manage frontend files + * System locales for all supported languages * The aforementioned paths Install system packages @@ -53,7 +54,8 @@ Install some packages from the Debian package system. python3-virtualenv \ chromium \ redis-server \ - postgresql + postgresql \ + locales-all Create PostgreSQL user and database ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/pyproject.toml b/pyproject.toml index 5450eaeed7fc371b47fdcf9be589cf48c3108d0d..51f859a3a864acf61fc4755581242f751567d6dd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -127,7 +127,7 @@ sentry-sdk = {version = "^1.4.3", optional = true} django-cte = "^1.1.5" pycountry = "^22.0.0" django-ical = "^1.8.3" -django-iconify = "^0.1.0" +django-iconify = "^0.2.0" customidenticon = "^0.1.5" [tool.poetry.extras]