diff --git a/com/views.py b/com/views.py
index ea5b742d..456ba6ec 100644
--- a/com/views.py
+++ b/com/views.py
@@ -244,9 +244,8 @@ class NewsListView(TemplateView):
.filter(
date_of_birth__month=localdate().month,
date_of_birth__day=localdate().day,
- is_viewable=True,
+ role__in=["STUDENT", "FORMER STUDENT"],
)
- .filter(role__in=["STUDENT", "FORMER STUDENT"])
.order_by("-date_of_birth"),
key=lambda u: u.date_of_birth.year,
)
diff --git a/core/admin.py b/core/admin.py
index a21086a0..74c0c0ab 100644
--- a/core/admin.py
+++ b/core/admin.py
@@ -63,6 +63,7 @@ class UserAdmin(admin.ModelAdmin):
"scrub_pict",
"user_permissions",
"groups",
+ "whitelisted_users",
)
inlines = (UserBanInline,)
search_fields = ["first_name", "last_name", "username"]
diff --git a/core/management/commands/populate.py b/core/management/commands/populate.py
index 7bb2d0ef..4c1c5379 100644
--- a/core/management/commands/populate.py
+++ b/core/management/commands/populate.py
@@ -116,7 +116,11 @@ class Command(BaseCommand):
)
main_club.board_group.permissions.add(
*Permission.objects.filter(
- codename__in=["view_subscription", "add_subscription"]
+ codename__in=[
+ "view_subscription",
+ "add_subscription",
+ "view_hidden_user",
+ ]
)
)
bar_club = Club.objects.create(
diff --git a/core/migrations/0049_user_whitelisted_users.py b/core/migrations/0049_user_whitelisted_users.py
new file mode 100644
index 00000000..6970caea
--- /dev/null
+++ b/core/migrations/0049_user_whitelisted_users.py
@@ -0,0 +1,37 @@
+# Generated by Django 5.2.12 on 2026-03-14 08:39
+
+from django.conf import settings
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+ dependencies = [("core", "0048_alter_user_options")]
+
+ operations = [
+ migrations.AddField(
+ model_name="user",
+ name="whitelisted_users",
+ field=models.ManyToManyField(
+ blank=True,
+ help_text=(
+ "Even if this profile is hidden, "
+ "the users in this list will still be able to see it."
+ ),
+ related_name="visible_by_whitelist",
+ to=settings.AUTH_USER_MODEL,
+ verbose_name="whitelisted users",
+ ),
+ ),
+ migrations.AlterField(
+ model_name="preferences",
+ name="show_my_stats",
+ field=models.BooleanField(
+ default=False,
+ help_text=(
+ "Allow subscribers (or whitelisted users "
+ "if your profile is hidden) to access your AE account stats."
+ ),
+ verbose_name="show your stats to others",
+ ),
+ ),
+ ]
diff --git a/core/models.py b/core/models.py
index 3b533751..0f0ef10e 100644
--- a/core/models.py
+++ b/core/models.py
@@ -131,7 +131,7 @@ class UserQuerySet(models.QuerySet):
if user.has_perm("core.view_hidden_user"):
return self
if user.has_perm("core.view_user"):
- return self.filter(is_viewable=True)
+ return self.filter(Q(is_viewable=True) | Q(whitelisted_users=user))
if user.is_anonymous:
return self.none()
return self.filter(id=user.id)
@@ -279,6 +279,16 @@ class User(AbstractUser):
),
default=True,
)
+ whitelisted_users = models.ManyToManyField(
+ "User",
+ related_name="visible_by_whitelist",
+ verbose_name=_("whitelisted users"),
+ help_text=_(
+ "Even if this profile is hidden, "
+ "the users in this list will still be able to see it."
+ ),
+ blank=True,
+ )
godfathers = models.ManyToManyField("User", related_name="godchildren", blank=True)
objects = CustomUserManager()
@@ -518,7 +528,7 @@ class User(AbstractUser):
self.username = user_name
return user_name
- def is_owner(self, obj):
+ def is_owner(self, obj: models.Model):
"""Determine if the object is owned by the user."""
if hasattr(obj, "is_owned_by") and obj.is_owned_by(self):
return True
@@ -526,7 +536,7 @@ class User(AbstractUser):
return True
return self.is_root
- def can_edit(self, obj):
+ def can_edit(self, obj: models.Model):
"""Determine if the object can be edited by the user."""
if hasattr(obj, "can_be_edited_by") and obj.can_be_edited_by(self):
return True
@@ -540,11 +550,9 @@ class User(AbstractUser):
pks = list(obj.edit_groups.values_list("id", flat=True))
if any(self.is_in_group(pk=pk) for pk in pks):
return True
- if isinstance(obj, User) and obj == self:
- return True
return self.is_owner(obj)
- def can_view(self, obj):
+ def can_view(self, obj: models.Model):
"""Determine if the object can be viewed by the user."""
if hasattr(obj, "can_be_viewed_by") and obj.can_be_viewed_by(self):
return True
@@ -563,14 +571,35 @@ class User(AbstractUser):
return True
return self.can_edit(obj)
- def can_be_edited_by(self, user):
- return user.is_root or user.is_board_member
+ def can_be_edited_by(self, user: User):
+ return user == self or user.is_root or user.is_board_member
def can_be_viewed_by(self, user: User) -> bool:
+ """Check if the given user can be viewed by this user.
+
+ Given users A and B. A can be viewed by B if :
+
+ - A and B are the same user
+ - or B has the permission to view hidden users
+ - or B can view users in general and A didn't hide its profile
+ - or B is in A's whitelist.
+ """
+
+ def is_in_whitelist(u: User):
+ if (
+ hasattr(self, "_prefetched_objects_cache")
+ and "whitelisted_users" in self._prefetched_objects_cache
+ ):
+ return u in self.whitelisted_users.all()
+ return self.whitelisted_users.contains(u)
+
return (
user.id == self.id
or user.has_perm("core.view_hidden_user")
- or (user.has_perm("core.view_user") and self.is_viewable)
+ or (
+ user.has_perm("core.view_user")
+ and (self.is_viewable or is_in_whitelist(user))
+ )
)
def get_mini_item(self):
@@ -750,7 +779,14 @@ class Preferences(models.Model):
User, related_name="_preferences", on_delete=models.CASCADE
)
receive_weekmail = models.BooleanField(_("receive the Weekmail"), default=False)
- show_my_stats = models.BooleanField(_("show your stats to others"), default=False)
+ show_my_stats = models.BooleanField(
+ _("show your stats to others"),
+ help_text=_(
+ "Allow subscribers (or whitelisted users "
+ "if your profile is hidden) to access your AE account stats."
+ ),
+ default=False,
+ )
notify_on_click = models.BooleanField(
_("get a notification for every click"), default=False
)
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 @@
messages.push(e?.detail)"
diff --git a/core/templates/core/fragment/user_visibility.jinja b/core/templates/core/fragment/user_visibility.jinja
new file mode 100644
index 00000000..ee78e907
--- /dev/null
+++ b/core/templates/core/fragment/user_visibility.jinja
@@ -0,0 +1,33 @@
+
\ 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..1aed4d94 100644
--- a/core/templates/core/user_preferences.jinja
+++ b/core/templates/core/user_preferences.jinja
@@ -1,7 +1,14 @@
{% extends "core/base.jinja" %}
+{%- block additional_js -%}
+
+{%- endblock -%}
+
{%- block additional_css -%}
+ {# importing ajax-select-index is necessary for it to be applied after HTMX reload #}
+
+
{%- endblock -%}
{% block title %}
@@ -11,30 +18,22 @@
{% block content %}
{% trans %}Preferences{% endtrans %}
-
{% trans %}General{% endtrans %}
-
-
{% trans %}Trombi{% endtrans %}
-
- {% if trombi_form %}
-
-
- {% 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 +42,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 %}
+
+ {% 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/tests/test_user.py b/core/tests/test_user.py
index 8714b4f1..5dd3e62f 100644
--- a/core/tests/test_user.py
+++ b/core/tests/test_user.py
@@ -399,13 +399,12 @@ class TestUserQuerySetViewableBy:
return [
baker.make(User),
subscriber_user.make(),
- subscriber_user.make(is_viewable=False),
+ *subscriber_user.make(is_viewable=False, _quantity=2),
]
def test_admin_user(self, users: list[User]):
user = baker.make(
- User,
- user_permissions=[Permission.objects.get(codename="view_hidden_user")],
+ User, user_permissions=[Permission.objects.get(codename="view_hidden_user")]
)
viewable = User.objects.filter(id__in=[u.id for u in users]).viewable_by(user)
assert set(viewable) == set(users)
@@ -418,6 +417,12 @@ class TestUserQuerySetViewableBy:
viewable = User.objects.filter(id__in=[u.id for u in users]).viewable_by(user)
assert set(viewable) == {users[0], users[1]}
+ def test_whitelist(self, users: list[User]):
+ user = subscriber_user.make()
+ users[3].whitelisted_users.add(user)
+ viewable = User.objects.filter(id__in=[u.id for u in users]).viewable_by(user)
+ assert set(viewable) == {users[0], users[1], users[3]}
+
@pytest.mark.parametrize("user_factory", [lambda: baker.make(User), AnonymousUser])
def test_not_subscriber(self, users: list[User], user_factory):
user = user_factory()
diff --git a/core/urls.py b/core/urls.py
index 71f76261..7e4b4e74 100644
--- a/core/urls.py
+++ b/core/urls.py
@@ -69,7 +69,6 @@ from core.views import (
UserCreationView,
UserGodfathersTreeView,
UserGodfathersView,
- UserListView,
UserMeRedirect,
UserMiniView,
UserPreferencesView,
@@ -78,6 +77,7 @@ from core.views import (
UserUpdateGroupView,
UserUpdateProfileView,
UserView,
+ UserVisibilityFormFragment,
delete_user_godfather,
logout,
notification,
@@ -136,7 +136,11 @@ urlpatterns = [
"group/
/detail/", GroupTemplateView.as_view(), name="group_detail"
),
# User views
- path("user/", UserListView.as_view(), name="user_list"),
+ 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 3d370b43..00d5610a 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
@@ -48,7 +50,6 @@ from django.views.generic import (
CreateView,
DeleteView,
DetailView,
- ListView,
RedirectView,
TemplateView,
)
@@ -65,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
@@ -411,13 +413,6 @@ class UserMiniView(CanViewMixin, DetailView):
template_name = "core/user_mini.jinja"
-class UserListView(ListView, CanEditPropMixin):
- """Displays the user list."""
-
- model = User
- template_name = "core/user_list.jinja"
-
-
# FIXME: the edit_once fields aren't displayed to the user (as expected).
# However, if the user re-add them manually in the form, they are saved.
class UserUpdateProfileView(UserTabsMixin, CanEditMixin, UpdateView):
@@ -475,6 +470,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."""
@@ -488,7 +507,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
@@ -498,6 +520,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
diff --git a/election/templates/election/election_detail.jinja b/election/templates/election/election_detail.jinja
index 6a491d9d..9f690cb0 100644
--- a/election/templates/election/election_detail.jinja
+++ b/election/templates/election/election_detail.jinja
@@ -146,7 +146,7 @@
{%- endif %}
- {%- if user.is_viewable %}
+ {%- if user.can_view(candidature.user) %}
{% if candidature.user.profile_pict %}
{% else %}
diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po
index 7ef7b81b..32f63042 100644
--- a/locale/fr/LC_MESSAGES/django.po
+++ b/locale/fr/LC_MESSAGES/django.po
@@ -6,7 +6,7 @@
msgid ""
msgstr ""
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2026-03-10 10:28+0100\n"
+"POT-Creation-Date: 2026-03-23 22:21+0100\n"
"PO-Revision-Date: 2016-07-18\n"
"Last-Translator: Maréchal \n"
@@ -551,8 +551,9 @@ msgstr ""
#: com/templates/com/news_edit.jinja com/templates/com/poster_edit.jinja
#: com/templates/com/screen_edit.jinja com/templates/com/weekmail.jinja
#: core/templates/core/create.jinja core/templates/core/edit.jinja
-#: core/templates/core/file_edit.jinja core/templates/core/page/edit.jinja
-#: core/templates/core/page/prop.jinja
+#: core/templates/core/file_edit.jinja
+#: core/templates/core/fragment/user_visibility.jinja
+#: core/templates/core/page/edit.jinja core/templates/core/page/prop.jinja
#: core/templates/core/user_godfathers.jinja
#: core/templates/core/user_godfathers_tree.jinja
#: core/templates/core/user_preferences.jinja
@@ -1547,6 +1548,18 @@ msgid ""
msgstr ""
"Si vous désactivez cette option, seuls les admins pourront voir votre profil."
+#: core/models.py
+msgid "whitelisted users"
+msgstr "utilisateurs whitelistés"
+
+#: core/models.py
+msgid ""
+"Even if this profile is hidden, the users in this list will still be able to "
+"see it."
+msgstr ""
+"Même si ce profil est caché, les utilisateurs sur cette liste pourront "
+"toujours le voir."
+
#: core/models.py
msgid "A user with that username already exists"
msgstr "Un utilisateur de ce nom d'utilisateur existe déjà"
@@ -1603,6 +1616,14 @@ msgstr "recevoir le Weekmail"
msgid "show your stats to others"
msgstr "montrez vos statistiques aux autres"
+#: core/models.py
+msgid ""
+"Allow subscribers (or whitelisted users if your profile is hidden) to access "
+"your AE account stats."
+msgstr ""
+"Autorise les cotisants (ou les personnes whitelistées, si votre profil est "
+"caché) à accéder aux statistiques de votre compte AE"
+
#: core/models.py
msgid "get a notification for every click"
msgstr "avoir une notification pour chaque click"
@@ -2612,21 +2633,12 @@ msgid "Preferences"
msgstr "Préférences"
#: core/templates/core/user_preferences.jinja
-msgid "General"
-msgstr "Général"
-
-#: core/templates/core/user_preferences.jinja trombi/views.py
-msgid "Trombi"
-msgstr "Trombi"
+msgid "Notifications"
+msgstr "Notifications"
#: core/templates/core/user_preferences.jinja
-#, python-format
-msgid "You already choose to be in that Trombi: %(trombi)s."
-msgstr "Vous avez déjà choisi ce Trombi: %(trombi)s."
-
-#: core/templates/core/user_preferences.jinja
-msgid "Go to my Trombi tools"
-msgstr "Allez à mes outils de Trombi"
+msgid "Visibility"
+msgstr "Visibilité"
#: core/templates/core/user_preferences.jinja
#: counter/templates/counter/counter_click.jinja
@@ -2645,6 +2657,19 @@ msgstr ""
"aurez besoin d'un lecteur NFC. Nous enregistrons l'UID de la carte qui fait "
"14 caractères de long."
+#: core/templates/core/user_preferences.jinja trombi/views.py
+msgid "Trombi"
+msgstr "Trombi"
+
+#: core/templates/core/user_preferences.jinja
+#, python-format
+msgid "You already choose to be in that Trombi: %(trombi)s."
+msgstr "Vous avez déjà choisi ce Trombi: %(trombi)s."
+
+#: core/templates/core/user_preferences.jinja
+msgid "Go to my Trombi tools"
+msgstr "Allez à mes outils de Trombi"
+
#: core/templates/core/user_stats.jinja
#, python-format
msgid "%(user_name)s's stats"
@@ -2925,6 +2950,10 @@ msgstr "Photos"
msgid "Account"
msgstr "Compte"
+#: core/views/user.py
+msgid "Visibility parameters updated."
+msgstr "Paramètres de visibilité mis à jour."
+
#: counter/apps.py counter/models.py
msgid "counter"
msgstr "comptoir"
diff --git a/sas/models.py b/sas/models.py
index 04061fa2..64f6c15b 100644
--- a/sas/models.py
+++ b/sas/models.py
@@ -270,7 +270,11 @@ class PeoplePictureRelationQuerySet(models.QuerySet):
if user.is_root or user.is_in_group(pk=settings.SITH_GROUP_SAS_ADMIN_ID):
return self
if user.was_subscribed:
- return self.filter(Q(user_id=user.id) | Q(user__is_viewable=True))
+ return self.filter(
+ Q(user_id=user.id)
+ | Q(user__is_viewable=True)
+ | Q(user__whitelisted_users=user)
+ )
return self.filter(user_id=user.id)