From ad4f7fb7651c64ef114798505832ed6efc2b4f4b Mon Sep 17 00:00:00 2001 From: imperosol Date: Sat, 14 Mar 2026 23:14:56 +0100 Subject: [PATCH] include visibility settings in the user preferences page --- core/static/core/forms.scss | 1 + core/static/user/user_edit.scss | 11 ---- core/static/user/user_preferences.scss | 22 -------- core/templates/core/base/notifications.jinja | 13 ++--- .../core/fragment/user_visibility.jinja | 33 ++++++++++++ core/templates/core/user_edit.jinja | 12 ----- core/templates/core/user_preferences.jinja | 52 ++++++++++++------- core/urls.py | 6 +++ core/views/forms.py | 36 ++++++++++++- core/views/user.py | 37 ++++++++++++- 10 files changed, 147 insertions(+), 76 deletions(-) create mode 100644 core/templates/core/fragment/user_visibility.jinja diff --git a/core/static/core/forms.scss b/core/static/core/forms.scss index be876c9b..2abffbba 100644 --- a/core/static/core/forms.scss +++ b/core/static/core/forms.scss @@ -157,6 +157,7 @@ form { margin-bottom: .25rem; font-size: 80%; display: block; + max-width: calc(100% - calc(var(--nf-input-size) * 2)) } fieldset { diff --git a/core/static/user/user_edit.scss b/core/static/user/user_edit.scss index 20995da6..e428956a 100644 --- a/core/static/user/user_edit.scss +++ b/core/static/user/user_edit.scss @@ -5,17 +5,6 @@ } .profile { - &-visible { - display: flex; - flex-direction: column; - align-items: center; - gap: 5px; - padding-top: 10px; - input[type="checkbox"]+label { - max-width: unset; - } - } - &-pictures { box-sizing: border-box; display: flex; diff --git a/core/static/user/user_preferences.scss b/core/static/user/user_preferences.scss index c61142d0..ded5e08d 100644 --- a/core/static/user/user_preferences.scss +++ b/core/static/user/user_preferences.scss @@ -19,28 +19,6 @@ } } } - - &-cards, - &-trombi { - >p { - display: flex; - flex-direction: column; - align-items: flex-start; - text-align: justify; - gap: 5px; - margin: 0; - - >input, - >select { - min-width: 300px; - } - } - } - - &-submit-btn { - margin-top: 10px !important; - max-width: 100px; - } } .justify { diff --git a/core/templates/core/base/notifications.jinja b/core/templates/core/base/notifications.jinja index f941a1a6..89fb7aad 100644 --- a/core/templates/core/base/notifications.jinja +++ b/core/templates/core/base/notifications.jinja @@ -1,14 +1,11 @@
+ {% for message in messages %} + {% if message.extra_tags=="visibility" %} +
+ {{ message }} +
+ {% endif %} + {% endfor %} + {% csrf_token %} + {{ form.non_field_errors() }} +
+ {{ form.is_viewable|add_attr("x-model=isViewable") }} + {{ form.is_viewable.label_tag() }} + {{ form.is_viewable.help_text }} + {{ form.is_viewable.errors }} +
+
+ {{ form.whitelisted_users.as_field_group() }} +
+
+ {{ form.show_my_stats }} + {{ form.show_my_stats.label_tag() }} + + {{ form.show_my_stats.help_text }} + + {{ form.show_my_stats.errors }} +
+ + \ No newline at end of file diff --git a/core/templates/core/user_edit.jinja b/core/templates/core/user_edit.jinja index 46603562..9cad0dee 100644 --- a/core/templates/core/user_edit.jinja +++ b/core/templates/core/user_edit.jinja @@ -147,18 +147,7 @@ {%- endfor -%}
- {# Checkboxes #} -
-
- {{ form.is_viewable }} - {{ form.is_viewable.label_tag() }} -
- - {{ form.is_viewable.help_text }} - -
- {%- if form.instance == user -%}

{%- trans -%}Change my password{%- endtrans -%} @@ -170,7 +159,6 @@

{%- endif -%} -

diff --git a/core/templates/core/user_preferences.jinja b/core/templates/core/user_preferences.jinja index d20f708f..0276197e 100644 --- a/core/templates/core/user_preferences.jinja +++ b/core/templates/core/user_preferences.jinja @@ -1,7 +1,13 @@ {% extends "core/base.jinja" %} +{%- block additional_js -%} + +{%- endblock -%} + {%- block additional_css -%} + + {%- endblock -%} {% block title %} @@ -11,30 +17,22 @@ {% block content %}

{% trans %}Preferences{% endtrans %}

-

{% trans %}General{% endtrans %}

-
+
+

{% trans %}Notifications{% endtrans %}

+ {% csrf_token %} - {{ form.as_p() }} - +
+ {{ form.as_p() }} +
+
-

{% trans %}Trombi{% endtrans %}

- - {% if trombi_form %} -
- {% csrf_token %} - {{ trombi_form.as_p() }} - -
- - {% else %} -

{% trans trombi=profile.trombi_user.trombi %}You already choose to be in that Trombi: {{ trombi }}.{% endtrans %} -
- {% trans %}Go to my Trombi tools{% endtrans %} -

- {% endif %} +
+

{% trans %}Visibility{% endtrans %}

+ {{ user_visibility_fragment }} +
{% if student_card_fragment %}

{% trans %}Student card{% endtrans %}

{{ student_card_fragment }} @@ -43,5 +41,21 @@ add a student card yourself, you'll need a NFC reader. We store the UID of the card which is 14 characters long.{% endtrans %}

{% endif %} + +
+

{% trans %}Trombi{% endtrans %}

+ + {% if trombi_form %} +
+ {% csrf_token %} + {{ trombi_form.as_p() }} + +
+ {% else %} +

{% trans trombi=profile.trombi_user.trombi %}You already choose to be in that Trombi: {{ trombi }}.{% endtrans %} +
+ {% trans %}Go to my Trombi tools{% endtrans %} +

+ {% endif %}
{% endblock %} \ No newline at end of file diff --git a/core/urls.py b/core/urls.py index e617d6b0..7e4b4e74 100644 --- a/core/urls.py +++ b/core/urls.py @@ -77,6 +77,7 @@ from core.views import ( UserUpdateGroupView, UserUpdateProfileView, UserView, + UserVisibilityFormFragment, delete_user_godfather, logout, notification, @@ -135,6 +136,11 @@ urlpatterns = [ "group//detail/", GroupTemplateView.as_view(), name="group_detail" ), # User views + path( + "fragment/user//", + UserVisibilityFormFragment.as_view(), + name="user_visibility_fragment", + ), path( "user/me//", UserMeRedirect.as_view(), diff --git a/core/views/forms.py b/core/views/forms.py index f593c1d0..e46daf92 100644 --- a/core/views/forms.py +++ b/core/views/forms.py @@ -48,12 +48,13 @@ from phonenumber_field.widgets import RegionalPhoneNumberWidget from PIL import Image from antispam.forms import AntiSpamEmailField -from core.models import Gift, Group, Page, PageRev, SithFile, User +from core.models import Gift, Group, Page, PageRev, Preferences, SithFile, User from core.utils import resize_image from core.views.widgets.ajax_select import ( AutoCompleteSelect, AutoCompleteSelectGroup, AutoCompleteSelectMultipleGroup, + AutoCompleteSelectMultipleUser, AutoCompleteSelectUser, ) from core.views.widgets.markdown import MarkdownInput @@ -179,7 +180,6 @@ class UserProfileForm(forms.ModelForm): "school", "promo", "forum_signature", - "is_viewable", ] widgets = { "date_of_birth": SelectDate, @@ -264,6 +264,38 @@ class UserProfileForm(forms.ModelForm): self._post_clean() +class UserVisibilityForm(forms.ModelForm): + class Meta: + model = User + fields = ["is_viewable", "whitelisted_users"] + widgets = { + "is_viewable": forms.CheckboxInput(attrs={"class": "switch"}), + "whitelisted_users": AutoCompleteSelectMultipleUser, + } + + __preferences_fields = forms.fields_for_model( + Preferences, + ["show_my_stats"], + widgets={"show_my_stats": forms.CheckboxInput(attrs={"class": "switch"})}, + ) + show_my_stats = __preferences_fields["show_my_stats"] + + def __init__( + self, *args, initial: dict | None = None, instance: User | None = None, **kwargs + ): + if instance: + initial = initial or {} + initial["show_my_stats"] = instance.preferences.show_my_stats + super().__init__(*args, initial=initial, instance=instance, **kwargs) + + def save(self, commit=True) -> User: # noqa: FBT002 + instance = super().save(commit=commit) + if commit: + instance.preferences.show_my_stats = self.cleaned_data["show_my_stats"] + instance.preferences.save() + return instance + + class UserGroupsForm(forms.ModelForm): error_css_class = "error" required_css_class = "required" diff --git a/core/views/user.py b/core/views/user.py index 9991aa2f..f0e83edb 100644 --- a/core/views/user.py +++ b/core/views/user.py @@ -28,10 +28,12 @@ from datetime import timedelta from operator import itemgetter from smtplib import SMTPException +from django.contrib import messages from django.contrib.auth import login, views from django.contrib.auth.decorators import login_required from django.contrib.auth.forms import PasswordChangeForm, SetPasswordForm from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin +from django.contrib.messages.views import SuccessMessageMixin from django.core.exceptions import PermissionDenied from django.db.models import DateField, F, QuerySet, Sum from django.db.models.functions import Trunc @@ -64,8 +66,9 @@ from core.views.forms import ( UserGodfathersForm, UserGroupsForm, UserProfileForm, + UserVisibilityForm, ) -from core.views.mixins import TabedViewMixin, UseFragmentsMixin +from core.views.mixins import FragmentMixin, TabedViewMixin, UseFragmentsMixin from counter.models import Refilling, Selling from eboutic.models import Invoice from trombi.views import UserTrombiForm @@ -460,6 +463,30 @@ class UserClubView(UserTabsMixin, CanViewMixin, DetailView): current_tab = "clubs" +class UserVisibilityFormFragment(FragmentMixin, SuccessMessageMixin, UpdateView): + model = User + form_class = UserVisibilityForm + template_name = "core/fragment/user_visibility.jinja" + pk_url_kwarg = "user_id" + + def get_form_kwargs(self): + return super().get_form_kwargs() | {"label_suffix": ""} + + def form_valid(self, form): + response = super().form_valid(form) + messages.success( + self.request, _("Visibility parameters updated."), extra_tags="visibility" + ) + return response + + def render_fragment(self, request, **kwargs) -> SafeString: + self.object = kwargs.get("user") + return super().render_fragment(request, **kwargs) + + def get_success_url(self, **kwargs): + return self.request.path + + class UserPreferencesView(UserTabsMixin, UseFragmentsMixin, CanEditMixin, UpdateView): """Edit a user's preferences.""" @@ -473,7 +500,10 @@ class UserPreferencesView(UserTabsMixin, UseFragmentsMixin, CanEditMixin, Update current_tab = "prefs" def get_form_kwargs(self): - return super().get_form_kwargs() | {"instance": self.object.preferences} + return super().get_form_kwargs() | { + "instance": self.object.preferences, + "label_suffix": "", + } def get_success_url(self): return self.request.path @@ -483,6 +513,9 @@ class UserPreferencesView(UserTabsMixin, UseFragmentsMixin, CanEditMixin, Update from counter.views.student_card import StudentCardFormFragment res = super().get_fragment_context_data() + res["user_visibility_fragment"] = UserVisibilityFormFragment.as_fragment()( + self.request, user=self.object + ) if hasattr(self.object, "customer"): res["student_card_fragment"] = StudentCardFormFragment.as_fragment()( self.request, customer=self.object.customer