diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 403d2944ecfb87b125e0f1a33e3ce1af37fa6c5e..99ed48344cd7bdea19fb27982552b07431eb12cc 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,6 +9,14 @@ and this project adheres to `Semantic Versioning`_. Unreleased ---------- +Fixed +~~~~~ + +* Signup was forbidden even if it was enabled in settings +* Phone numbers were not properly linked and suboptimally formatted on person page +* Favicon upload failed with S3 storage. +* Some preferences were required when they shouldn't, and vice versa. + `2.6`_ - 2022-01-10 ------------------- diff --git a/aleksis/core/apps.py b/aleksis/core/apps.py index d37a1c616953013bec122aa8070ba0d6a389a921..ab264c53a91edac303e8cb574e7d7a56fdca00ac 100644 --- a/aleksis/core/apps.py +++ b/aleksis/core/apps.py @@ -17,7 +17,7 @@ from .registries import ( site_preferences_registry, ) from .util.apps import AppConfig -from .util.core_helpers import get_or_create_favicon, has_person +from .util.core_helpers import get_or_create_favicon, get_site_preferences, has_person from .util.sass_helpers import clean_scss @@ -103,11 +103,15 @@ class CoreConfig(AppConfig): is_favicon = name == "favicon" if new_value: - favicon_id = Favicon.on_site.update_or_create( + # Get file object from preferences instead of using new_value + # to prevent problems with special file storages + file_obj = get_site_preferences()[f"{section}__{name}"] + + favicon = Favicon.on_site.update_or_create( title=name, - defaults={"isFavicon": is_favicon, "faviconImage": new_value}, + defaults={"isFavicon": is_favicon, "faviconImage": file_obj}, )[0] - FaviconImg.objects.filter(faviconFK=favicon_id).delete() + FaviconImg.objects.filter(faviconFK=favicon).delete() else: Favicon.on_site.filter(title=name, isFavicon=is_favicon).delete() if name in settings.DEFAULT_FAVICON_PATHS: diff --git a/aleksis/core/models.py b/aleksis/core/models.py index 8bb6def6cc619e6729e73b4760ec329b77c46a07..04632a5615cd701cbae9e9d7f3a89b92ff82c74e 100644 --- a/aleksis/core/models.py +++ b/aleksis/core/models.py @@ -860,40 +860,7 @@ class AnnouncementRecipient(ExtensibleModel): class DashboardWidget(PolymorphicModel, PureDjangoModel): - """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. - - You can provide a Media meta class with custom JS and CSS files which - will be added to html head. For further information on media definition - see https://docs.djangoproject.com/en/3.0/topics/forms/media/ - - Example:: - - from django.forms.widgets import Media - - from aleksis.core.models import DashboardWidget - - class MyWidget(DashboardWidget): - template = "myapp/widget.html" - - def get_context(self, request): - context = {"some_content": "foo"} - return context - - class Meta: - proxy = True - - media = Media(css={ - 'all': ('pretty.css',) - }, - js=('animations.js', 'actions.js') - ) - """ + """Base class for dashboard widgets on the index page.""" objects = UninstallRenitentPolymorphicManager() diff --git a/aleksis/core/preferences.py b/aleksis/core/preferences.py index cb351100a5da31c4d5cffc2255542bc86f4a77c1..fe29f220f211616bf57d08c04e87a95b60c84365 100644 --- a/aleksis/core/preferences.py +++ b/aleksis/core/preferences.py @@ -40,8 +40,8 @@ class SiteTitle(StringPreference): section = general name = "title" default = "AlekSIS" - required = False verbose_name = _("Site title") + required = True @site_preferences_registry.register @@ -62,9 +62,9 @@ class ColourPrimary(StringPreference): section = theme name = "primary" default = "#0d5eaf" - required = False verbose_name = _("Primary colour") widget = ColorWidget + required = True @site_preferences_registry.register @@ -74,9 +74,9 @@ class ColourSecondary(StringPreference): section = theme name = "secondary" default = "#0d5eaf" - required = False verbose_name = _("Secondary colour") widget = ColorWidget + required = True @site_preferences_registry.register @@ -87,6 +87,7 @@ class Logo(PublicFilePreferenceMixin, FilePreference): field_class = ImageField name = "logo" verbose_name = _("Logo") + required = False @site_preferences_registry.register @@ -97,6 +98,7 @@ class Favicon(PublicFilePreferenceMixin, FilePreference): field_class = ImageField name = "favicon" verbose_name = _("Favicon") + required = False @site_preferences_registry.register @@ -107,6 +109,7 @@ class PWAIcon(PublicFilePreferenceMixin, FilePreference): field_class = ImageField name = "pwa_icon" verbose_name = _("PWA-Icon") + required = False @site_preferences_registry.register @@ -116,8 +119,8 @@ class MailOutName(StringPreference): section = mail name = "name" default = "AlekSIS" - required = False verbose_name = _("Mail out name") + required = True @site_preferences_registry.register @@ -127,9 +130,9 @@ class MailOut(StringPreference): section = mail name = "address" default = settings.DEFAULT_FROM_EMAIL - required = False verbose_name = _("Mail out address") field_class = EmailField + required = True @site_preferences_registry.register @@ -163,12 +166,12 @@ class AdressingNameFormat(ChoicePreference): section = notification name = "addressing_name_format" default = "first_last" - required = False verbose_name = _("Name format for addressing") choices = ( ("first_last", "John Doe"), ("last_fist", "Doe, John"), ) + required = True @person_preferences_registry.register @@ -300,6 +303,7 @@ class OAuthAllowedGrants(MultipleChoicePreference): verbose_name = _("Allowed Grant Flows for OAuth applications") field_attribute = {"initial": []} choices = AbstractApplication.GRANT_TYPES + required = False @site_preferences_registry.register @@ -313,6 +317,7 @@ class AvailableLanguages(MultipleChoicePreference): verbose_name = _("Available languages") field_attribute = {"initial": []} choices = settings.LANGUAGES + required = True @site_preferences_registry.register @@ -376,6 +381,7 @@ class EditableFieldsPerson(MultipleChoicePreference): verbose_name = _("Fields on person model which are editable by themselves.") field_attribute = {"initial": []} choices = [(field.name, field.name) for field in Person.syncable_fields()] + required = False @site_preferences_registry.register @@ -391,6 +397,7 @@ class SendNotificationOnPersonChange(MultipleChoicePreference): ) field_attribute = {"initial": []} choices = [(field.name, field.name) for field in Person.syncable_fields()] + required = False @site_preferences_registry.register @@ -401,6 +408,7 @@ class PersonChangeNotificationContact(StringPreference): name = "person_change_notification_contact" default = "" verbose_name = _("Contact for notification if a person changes their data") + required = False @site_preferences_registry.register diff --git a/aleksis/core/templates/core/person/full.html b/aleksis/core/templates/core/person/full.html index 749ba164229845057c965e4efd3e8d0fb9423f32..24e267db3caf7b6a9886f10269a4db654514f696 100644 --- a/aleksis/core/templates/core/person/full.html +++ b/aleksis/core/templates/core/person/full.html @@ -164,14 +164,22 @@ <i class="material-icons small">phone</i> </td> <td> - <a href="tel:{{ person.phone_number }}">{{ person.phone_number }}</a> - <small>({% trans "home number" %})</small> + {% if person.phone_number %} + <a href="{{ person.phone_number.as_rfc3966 }}">{{ person.phone_number.as_international }}</a> + {% else %} + – + {% endif %} + <small>({% trans "Home phone" %})</small> </td> </tr> <tr> <td> - <a href="tel:{{ person.phone_number }}">{{ person.mobile_number }}</a> - <small>({% trans "mobile number" %})</small> + {% if person.mobile_number %} + <a href="{{ person.mobile_number.as_rfc3966 }}">{{ person.mobile_number.as_international }}</a> + {% else %} + – + {% endif %} + <small>({% trans "Mobile phone" %})</small> </td> </tr> <tr> diff --git a/aleksis/core/views.py b/aleksis/core/views.py index 45167450e83746e97bb65c4027238676c098b1c8..ec38875ad876f6cc7f6b8943a70254a28704195b 100644 --- a/aleksis/core/views.py +++ b/aleksis/core/views.py @@ -56,7 +56,6 @@ from oauth2_provider.models import get_application_model from oauth2_provider.views import AuthorizationView from reversion import set_user from reversion.views import RevisionMixin -from rules import test_rule from rules.contrib.views import PermissionRequiredMixin, permission_required from two_factor.views.core import LoginView as AllAuthLoginView @@ -1409,7 +1408,9 @@ class AccountRegisterView(SignupView): success_url = "index" def dispatch(self, request, *args, **kwargs): - if not test_rule("core.can_register") and not request.session.get("account_verified_email"): + if not request.user.has_perm("core.can_register") and not request.session.get( + "account_verified_email" + ): raise PermissionDenied() return super(AccountRegisterView, self).dispatch(request, *args, **kwargs) diff --git a/docs/_static/create_dashboard_widget.png b/docs/_static/create_dashboard_widget.png new file mode 100644 index 0000000000000000000000000000000000000000..31cfcb31474aaf5b97b88e1e818f0c8363dda1a7 Binary files /dev/null and b/docs/_static/create_dashboard_widget.png differ diff --git a/docs/_static/dashboard.png b/docs/_static/dashboard.png new file mode 100644 index 0000000000000000000000000000000000000000..a25e5ca64139e80a35e05a07bffe3b7112113139 Binary files /dev/null and b/docs/_static/dashboard.png differ diff --git a/docs/_static/dashboard_widgets.png b/docs/_static/dashboard_widgets.png new file mode 100644 index 0000000000000000000000000000000000000000..7e345543a30489b902a261296fe53de8d2c67e30 Binary files /dev/null and b/docs/_static/dashboard_widgets.png differ diff --git a/docs/_static/edit_dashboard.png b/docs/_static/edit_dashboard.png new file mode 100644 index 0000000000000000000000000000000000000000..92a277482c91ecb778fd3651d9351dc97221c3a1 Binary files /dev/null and b/docs/_static/edit_dashboard.png differ diff --git a/docs/_static/edit_default_dashboard.png b/docs/_static/edit_default_dashboard.png new file mode 100644 index 0000000000000000000000000000000000000000..c7547249c4152b433b984f5f40a258cf149306df Binary files /dev/null and b/docs/_static/edit_default_dashboard.png differ diff --git a/docs/admin/10_dashboard.rst b/docs/admin/10_dashboard.rst new file mode 100644 index 0000000000000000000000000000000000000000..0c7f6a3b5a5ebda81f417b1a86a1135540a580ba --- /dev/null +++ b/docs/admin/10_dashboard.rst @@ -0,0 +1,92 @@ +Providing important information to users using the dashboard +============================================================ + +The dashboard is a central place for providing important information to users. +This is done by so-called dashboard widgets provided by the Core and apps. + +Built-in dashboard widgets +-------------------------- + +External link widget +^^^^^^^^^^^^^^^^^^^^ + +The external link widget will show a link to an external site on the dashboard, +optionally with an icon or picture next to it. It therefore provides the following additional attributes: + +* **URL**: The URL of the external site. +* **Icon URL**: The URL of the icon or picture shown next to the link. + +As link title, the widget title will be used. + +More dashboard widgets from apps +-------------------------------- + +In addition to the built-in widgets, apps can provide their own dashboard widgets. +Best examples for such apps are currently *AlekSIS-App-DashboardFeeds* and *AlekSIS-App-Chronos*. + +.. Add References to the apps + +.. _core-configure-dashboard-widgets: + +Add and configure dashboard widgets +----------------------------------- + +If you want to add a new dashboard widget, you can do so by adding the dashboard widget at *Admin → Dashboard widgets*. +There you will see all currently configured dashboard widgets and +can add new ones using the *Create dashboard widget* button which will ask your for the widget type. + +.. image:: ../_static/dashboard_widgets.png + :width: 100% + :alt: All configured dashboard widgets + +Each dashboard widget has at least the followong attributes + +* **Widget Title**: The title of the widget (will be shown in some widgets). +* **Activate Widget**: If this isn't checked, the widget will not be shown. +* **Widget is broken**: If this is checked, the widget will be shown + but the user will get a message that this widget is currently out of order because of an error. + This shouldn't be checked by yourself, but might be activated automatically by a widget if it encounters an error. + If this case enters, you should check for the cause of the error and fix it. After that, you can unmark the widget as broken. +* **Size on different screens**: The size of the widget on different screens. + We work with a grid system containing a maximum of 12 columns. So, one column is 1/12 of the screen width. + The width in the following fields has to be entered as number of columns (1 to 12). + + * **Size on mobile devices**: The size of the widget on mobile devices (600px and less). + * **Size on tablet devices**: The size of the widget on desktop devices (600px - 992px). + * **Size on desktop devices**: The size of the widget on desktop devices (992px - 1200px). + * **Size on large desktop devices**: The size of the widget on large desktop devices (1200px and above). + +All other attributes are specific to the widget type and are explained in the documentation of the widget. + +.. image:: ../_static/create_dashboard_widget.png + :width: 100% + :alt: Form to create an external link widget + +Setup a default dashboard +------------------------- + +To make the configured dashboard widgets accessible to all users, we recommend to configure the default dashboard. +If you don't do so, the dashboard widgets will only be available to users if they customise their dashboard. + +The default dashboard can be configured via *Admin → Dashboard widgets → Edit default dashboard*. +The edit page works exactly as the page described in :ref:`core-user-customising-dashboard`. + +.. image:: ../_static/edit_default_dashboard.png + :width: 100% + :alt: Edit the default dashboard + +Preferences +----------- + +The behavior of the dashboard can be configured via *Admin → Configuration → General*. The following settings are available: + +* **Show dashboard to users without login**: If this is checked, the dashboard will be also shown to users who are not logged in. + +.. warning:: + + That won't work with all dashboard widgets. Some widgets, like the timetable widgets, require a logged in user. + +* **Allow users to edit their dashboard**: With this preference, system administrators can decide whether users + can edit their own dashboard as described in :ref:`core-user-customising-dashboard`. +* **Automatically update the dashboard and its widgets sitewide**: If enabled, + the dashboard will be updated automatically every 15 seconds. diff --git a/docs/dev/10_dashboard_widgets.rst b/docs/dev/10_dashboard_widgets.rst new file mode 100644 index 0000000000000000000000000000000000000000..434c8c9a78cd2f8664f2a783ea997f0d4e1b6df1 --- /dev/null +++ b/docs/dev/10_dashboard_widgets.rst @@ -0,0 +1,40 @@ +Registering dashboard widgets +============================= + +Apps can register their own dashboard widgets which are automatically registered in the corresponding frontend for +configuring them. + +To implement a widget, add a model that subclasses ``DashboardWidget``, set the template +and implement the ``get_context`` method to return a dictionary to be passed as context +to the template. The template system works as in every Django view and allows you to use the normal Django +template language. + +If your widget does not add any custom database fields, you should mark it as a proxy model. + +You can provide a ``Media`` meta class with custom JS and CSS files which +will be added to the HTML head on the dashboard if the dashboard widget is shown. +For further information on media definition, see `Django Media`_. + +Example:: + + from django.forms.widgets import Media + + from aleksis.core.models import DashboardWidget + + class MyWidget(DashboardWidget): + template = "myapp/widget.html" + + def get_context(self, request): + context = {"some_content": "foo"} + return context + + class Meta: + proxy = True + + media = Media(css={ + 'all': ('pretty.css',) + }, + js=('animations.js', 'actions.js') + ) + +.. _Django Media: https://docs.djangoproject.com/en/3.0/topics/forms/media/ diff --git a/docs/user/02_dashboard.rst b/docs/user/02_dashboard.rst new file mode 100644 index 0000000000000000000000000000000000000000..20eae0260650350a23c2e64ce39ffbe36c33142d --- /dev/null +++ b/docs/user/02_dashboard.rst @@ -0,0 +1,43 @@ +Dashboard +========= + +The first thing you will see after the login is the dashboard. +Depending on what your system administrator configured, +you will be able to see information from different apps at one glance. + +.. image:: ../_static/dashboard.png + :width: 100% + :alt: The dashboard + +Dashboard widgets +----------------- + +The dashboard consists of different parts, the so-called *dashboard widgets*. +They are configured by the system administrator and can be freely +arranged on the dashboard (cf. :ref:`core-user-customising-dashboard`). + +.. _core-user-customising-dashboard: + +Customising the dashboard +------------------------- + +There are several options for customising your personal dashboard. By default, +you will see a layout provided by your system administrator. Using the button +*Edit dashboard* on the top right corner of the dashboard, +you can change the selection and position of the widgets. + +.. image:: ../_static/edit_dashboard.png + :width: 100% + :alt: Edit the dashboard + +On the edit page, you will see a list of all available widgets and your current dashboard. +If the section *Your dashboard* is empty, the default dashboard will be shown. +To make an own layout, you can drag widgets from the *Available widgets* to *Your dashboard*. +Within *Your dashboard* you also can arrange the widgets by dragging them. +To remove widgets from the dashboard, you just have to drag them back to *Available widgets*. + +In addition to editing the dashboard, you can also change same preferences referring to the dashboard. +This is done under the menu item *Account → Preferences → General*: + +* **Automatically update the dashboard and its widgets:** If enabled by you and the system administrator, + the dashboard will be updated automatically every 15 seconds. diff --git a/pyproject.toml b/pyproject.toml index 4bd523cd499c3c944cceeb8ded9b91a06a69be9a..8254a6a2a05a9790ea3babfad998f58636cce6f1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -112,7 +112,7 @@ django-cleavejs = "^0.1.0" django-allauth = "^0.47.0" django-uwsgi-ng = "^1.1.0" django-extensions = "^3.1.1" -ipython = "^7.20.0" +ipython = "^8.0.0" django-oauth-toolkit = "^1.6.2" django-redis = "^5.0.0" django-storages = {version = "^1.11.1", optional = true}