mirror of
https://github.com/ae-utbm/sith.git
synced 2026-03-14 23:55:04 +00:00
Compare commits
3 Commits
taiste
...
user-white
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8dd6cefad5 | ||
|
|
ad4f7fb765 | ||
|
|
1d672a5fc2 |
@@ -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,
|
||||
)
|
||||
|
||||
@@ -63,6 +63,7 @@ class UserAdmin(admin.ModelAdmin):
|
||||
"scrub_pict",
|
||||
"user_permissions",
|
||||
"groups",
|
||||
"whitelisted_users",
|
||||
)
|
||||
inlines = (UserBanInline,)
|
||||
search_fields = ["first_name", "last_name", "username"]
|
||||
|
||||
33
core/migrations/0049_user_whitelisted_users.py
Normal file
33
core/migrations/0049_user_whitelisted_users.py
Normal file
@@ -0,0 +1,33 @@
|
||||
# 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(
|
||||
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 to access your AE account stats.",
|
||||
verbose_name="show your stats to others",
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -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,15 @@ 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."
|
||||
),
|
||||
)
|
||||
godfathers = models.ManyToManyField("User", related_name="godchildren", blank=True)
|
||||
|
||||
objects = CustomUserManager()
|
||||
@@ -518,7 +527,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 +535,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 +549,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 +570,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 +778,11 @@ 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 to access your AE account stats."),
|
||||
default=False,
|
||||
)
|
||||
notify_on_click = models.BooleanField(
|
||||
_("get a notification for every click"), default=False
|
||||
)
|
||||
|
||||
@@ -157,6 +157,7 @@ form {
|
||||
margin-bottom: .25rem;
|
||||
font-size: 80%;
|
||||
display: block;
|
||||
max-width: calc(100% - calc(var(--nf-input-size) * 2))
|
||||
}
|
||||
|
||||
fieldset {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
<div id="quick-notifications"
|
||||
x-data="{
|
||||
messages: [
|
||||
{% if messages %}
|
||||
{% for message in messages %}
|
||||
{
|
||||
tag: '{{ message.tags }}',
|
||||
text: '{{ message }}',
|
||||
},
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{%- for message in messages -%}
|
||||
{%- if not message.extra_tags -%}
|
||||
{ tag: '{{ message.tags }}', text: '{{ message }}' },
|
||||
{%- endif -%}
|
||||
{%- endfor -%}
|
||||
]
|
||||
}"
|
||||
@quick-notification-add="(e) => messages.push(e?.detail)"
|
||||
|
||||
33
core/templates/core/fragment/user_visibility.jinja
Normal file
33
core/templates/core/fragment/user_visibility.jinja
Normal file
@@ -0,0 +1,33 @@
|
||||
<form
|
||||
hx-post="{{ url("core:user_visibility_fragment", user_id=form.instance.id) }}"
|
||||
hx-disabled-elt="find input[type='submit']"
|
||||
hx-swap="outerHTML" x-data="{ isViewable: {{ form.is_viewable.value()|tojson }} }"
|
||||
>
|
||||
{% for message in messages %}
|
||||
{% if message.extra_tags=="visibility" %}
|
||||
<div class="alert alert-success">
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% csrf_token %}
|
||||
{{ form.non_field_errors() }}
|
||||
<fieldset class="form-group">
|
||||
{{ form.is_viewable|add_attr("x-model=isViewable") }}
|
||||
{{ form.is_viewable.label_tag() }}
|
||||
<span class="helptext">{{ form.is_viewable.help_text }}</span>
|
||||
{{ form.is_viewable.errors }}
|
||||
</fieldset>
|
||||
<fieldset class="form-group" x-show="!isViewable">
|
||||
{{ form.whitelisted_users.as_field_group() }}
|
||||
</fieldset>
|
||||
<fieldset class="form-group" x-show="isViewable">
|
||||
{{ form.show_my_stats }}
|
||||
{{ form.show_my_stats.label_tag() }}
|
||||
<span class="helptext">
|
||||
{{ form.show_my_stats.help_text }}
|
||||
</span>
|
||||
{{ form.show_my_stats.errors }}
|
||||
</fieldset>
|
||||
<input type="submit" class="btn btn-blue" value="{% trans %}Save{% endtrans %}">
|
||||
</form>
|
||||
@@ -147,18 +147,7 @@
|
||||
{%- endfor -%}
|
||||
</div>
|
||||
|
||||
{# Checkboxes #}
|
||||
<div class="profile-visible">
|
||||
<div class="row">
|
||||
{{ form.is_viewable }}
|
||||
{{ form.is_viewable.label_tag() }}
|
||||
</div>
|
||||
<span class="helptext">
|
||||
{{ form.is_viewable.help_text }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="final-actions">
|
||||
|
||||
{%- if form.instance == user -%}
|
||||
<p>
|
||||
<a href="{{ url('core:password_change') }}">{%- trans -%}Change my password{%- endtrans -%}</a>
|
||||
@@ -170,7 +159,6 @@
|
||||
</a>
|
||||
</p>
|
||||
{%- endif -%}
|
||||
|
||||
<p>
|
||||
<input type="submit" value="{%- trans -%}Update{%- endtrans -%}" />
|
||||
</p>
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
{% extends "core/base.jinja" %}
|
||||
|
||||
{%- block additional_js -%}
|
||||
<script type="module" src="{{ static("bundled/core/components/ajax-select-index.ts") }}"></script>
|
||||
{%- endblock -%}
|
||||
|
||||
{%- block additional_css -%}
|
||||
<link rel="stylesheet" href="{{ static('user/user_preferences.scss') }}">
|
||||
<link rel="stylesheet" href="{{ static("bundled/core/components/ajax-select-index.css") }}">
|
||||
<link rel="stylesheet" href="{{ static("core/components/ajax-select.scss") }}">
|
||||
{%- endblock -%}
|
||||
|
||||
{% block title %}
|
||||
@@ -11,30 +17,22 @@
|
||||
{% block content %}
|
||||
<div class="main">
|
||||
<h2>{% trans %}Preferences{% endtrans %}</h2>
|
||||
<h3>{% trans %}General{% endtrans %}</h3>
|
||||
<form class="form form-general" action="" method="post" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p() }}
|
||||
<input class="form-submit-btn" type="submit" value="{% trans %}Save{% endtrans %}" />
|
||||
</form>
|
||||
|
||||
<h3>{% trans %}Trombi{% endtrans %}</h3>
|
||||
|
||||
{% if trombi_form %}
|
||||
<form class="form form-trombi" action="{{ url('trombi:user_tools') }}" method="post" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
{{ trombi_form.as_p() }}
|
||||
<input class="form-submit-btn" type="submit" value="{% trans %}Save{% endtrans %}" />
|
||||
</form>
|
||||
|
||||
{% else %}
|
||||
<p>{% trans trombi=profile.trombi_user.trombi %}You already choose to be in that Trombi: {{ trombi }}.{% endtrans %}
|
||||
<br />
|
||||
<a href="{{ url('trombi:user_tools') }}">{% trans %}Go to my Trombi tools{% endtrans %}</a>
|
||||
</p>
|
||||
{% endif %}
|
||||
<h3>{% trans %}Notifications{% endtrans %}</h3>
|
||||
<form action="" method="post" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
<div class="form form-general">
|
||||
{{ form.as_p() }}
|
||||
</div>
|
||||
<input class="btn btn-blue" type="submit" value="{% trans %}Save{% endtrans %}" />
|
||||
</form>
|
||||
|
||||
<br />
|
||||
<h3>{% trans %}Visibility{% endtrans %}</h3>
|
||||
|
||||
{{ user_visibility_fragment }}
|
||||
|
||||
<br />
|
||||
{% if student_card_fragment %}
|
||||
<h3>{% trans %}Student card{% endtrans %}</h3>
|
||||
{{ 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 %}
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
<br />
|
||||
<h3>{% trans %}Trombi{% endtrans %}</h3>
|
||||
|
||||
{% if trombi_form %}
|
||||
<form action="{{ url('trombi:user_tools') }}" method="post" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
{{ trombi_form.as_p() }}
|
||||
<input class="btn btn-blue" type="submit" value="{% trans %}Save{% endtrans %}" />
|
||||
</form>
|
||||
{% else %}
|
||||
<p>{% trans trombi=profile.trombi_user.trombi %}You already choose to be in that Trombi: {{ trombi }}.{% endtrans %}
|
||||
<br />
|
||||
<a href="{{ url('trombi:user_tools') }}">{% trans %}Go to my Trombi tools{% endtrans %}</a>
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -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()
|
||||
|
||||
@@ -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/<int:group_id>/detail/", GroupTemplateView.as_view(), name="group_detail"
|
||||
),
|
||||
# User views
|
||||
path("user/", UserListView.as_view(), name="user_list"),
|
||||
path(
|
||||
"fragment/user/<int:user_id>/",
|
||||
UserVisibilityFormFragment.as_view(),
|
||||
name="user_visibility_fragment",
|
||||
),
|
||||
path(
|
||||
"user/me/<path:remaining_path>/",
|
||||
UserMeRedirect.as_view(),
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
@@ -404,13 +406,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):
|
||||
@@ -468,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."""
|
||||
|
||||
@@ -481,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
|
||||
@@ -491,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
|
||||
|
||||
@@ -146,7 +146,7 @@
|
||||
<label for="{{ input_id }}">
|
||||
{%- endif %}
|
||||
<figure>
|
||||
{%- if user.is_viewable %}
|
||||
{%- if user.can_view(candidature.user) %}
|
||||
{% if candidature.user.profile_pict %}
|
||||
<img class="candidate__picture" src="{{ candidature.user.profile_pict.get_download_url() }}" alt="{% trans %}Profile{% endtrans %}">
|
||||
{% else %}
|
||||
|
||||
@@ -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-14 23:09+0100\n"
|
||||
"PO-Revision-Date: 2016-07-18\n"
|
||||
"Last-Translator: Maréchal <thomas.girod@utbm.fr\n"
|
||||
"Language-Team: AE info <ae.info@utbm.fr>\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,17 @@ 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 +1615,10 @@ msgstr "recevoir le Weekmail"
|
||||
msgid "show your stats to others"
|
||||
msgstr "montrez vos statistiques aux autres"
|
||||
|
||||
#: core/models.py
|
||||
msgid "Allow subscribers to access your AE account stats."
|
||||
msgstr "Autoriser les cotisants à 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 +2628,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 +2652,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 +2945,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"
|
||||
|
||||
Reference in New Issue
Block a user