mirror of
https://github.com/ae-utbm/sith.git
synced 2026-03-25 04:55:01 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f08e343e17 |
@@ -244,9 +244,8 @@ class NewsListView(TemplateView):
|
|||||||
.filter(
|
.filter(
|
||||||
date_of_birth__month=localdate().month,
|
date_of_birth__month=localdate().month,
|
||||||
date_of_birth__day=localdate().day,
|
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"),
|
.order_by("-date_of_birth"),
|
||||||
key=lambda u: u.date_of_birth.year,
|
key=lambda u: u.date_of_birth.year,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ class UserAdmin(admin.ModelAdmin):
|
|||||||
"scrub_pict",
|
"scrub_pict",
|
||||||
"user_permissions",
|
"user_permissions",
|
||||||
"groups",
|
"groups",
|
||||||
|
"whitelisted_users",
|
||||||
)
|
)
|
||||||
inlines = (UserBanInline,)
|
inlines = (UserBanInline,)
|
||||||
search_fields = ["first_name", "last_name", "username"]
|
search_fields = ["first_name", "last_name", "username"]
|
||||||
|
|||||||
24
core/migrations/0049_user_whitelisted_users.py
Normal file
24
core/migrations/0049_user_whitelisted_users.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# 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=(
|
||||||
|
"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",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -131,7 +131,7 @@ class UserQuerySet(models.QuerySet):
|
|||||||
if user.has_perm("core.view_hidden_user"):
|
if user.has_perm("core.view_hidden_user"):
|
||||||
return self
|
return self
|
||||||
if user.has_perm("core.view_user"):
|
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:
|
if user.is_anonymous:
|
||||||
return self.none()
|
return self.none()
|
||||||
return self.filter(id=user.id)
|
return self.filter(id=user.id)
|
||||||
@@ -279,6 +279,15 @@ class User(AbstractUser):
|
|||||||
),
|
),
|
||||||
default=True,
|
default=True,
|
||||||
)
|
)
|
||||||
|
whitelisted_users = models.ManyToManyField(
|
||||||
|
"User",
|
||||||
|
related_name="visible_by_whitelist",
|
||||||
|
verbose_name=_("whitelisted users"),
|
||||||
|
help_text=_(
|
||||||
|
"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)
|
godfathers = models.ManyToManyField("User", related_name="godchildren", blank=True)
|
||||||
|
|
||||||
objects = CustomUserManager()
|
objects = CustomUserManager()
|
||||||
@@ -567,10 +576,31 @@ class User(AbstractUser):
|
|||||||
return user.is_root or user.is_board_member
|
return user.is_root or user.is_board_member
|
||||||
|
|
||||||
def can_be_viewed_by(self, user: User) -> bool:
|
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 (
|
return (
|
||||||
user.id == self.id
|
user.id == self.id
|
||||||
or user.has_perm("core.view_hidden_user")
|
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):
|
def get_mini_item(self):
|
||||||
|
|||||||
@@ -399,13 +399,12 @@ class TestUserQuerySetViewableBy:
|
|||||||
return [
|
return [
|
||||||
baker.make(User),
|
baker.make(User),
|
||||||
subscriber_user.make(),
|
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]):
|
def test_admin_user(self, users: list[User]):
|
||||||
user = baker.make(
|
user = baker.make(
|
||||||
User,
|
User, user_permissions=[Permission.objects.get(codename="view_hidden_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)
|
viewable = User.objects.filter(id__in=[u.id for u in users]).viewable_by(user)
|
||||||
assert set(viewable) == set(users)
|
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)
|
viewable = User.objects.filter(id__in=[u.id for u in users]).viewable_by(user)
|
||||||
assert set(viewable) == {users[0], users[1]}
|
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])
|
@pytest.mark.parametrize("user_factory", [lambda: baker.make(User), AnonymousUser])
|
||||||
def test_not_subscriber(self, users: list[User], user_factory):
|
def test_not_subscriber(self, users: list[User], user_factory):
|
||||||
user = user_factory()
|
user = user_factory()
|
||||||
|
|||||||
@@ -69,7 +69,6 @@ from core.views import (
|
|||||||
UserCreationView,
|
UserCreationView,
|
||||||
UserGodfathersTreeView,
|
UserGodfathersTreeView,
|
||||||
UserGodfathersView,
|
UserGodfathersView,
|
||||||
UserListView,
|
|
||||||
UserMeRedirect,
|
UserMeRedirect,
|
||||||
UserMiniView,
|
UserMiniView,
|
||||||
UserPreferencesView,
|
UserPreferencesView,
|
||||||
@@ -136,7 +135,6 @@ urlpatterns = [
|
|||||||
"group/<int:group_id>/detail/", GroupTemplateView.as_view(), name="group_detail"
|
"group/<int:group_id>/detail/", GroupTemplateView.as_view(), name="group_detail"
|
||||||
),
|
),
|
||||||
# User views
|
# User views
|
||||||
path("user/", UserListView.as_view(), name="user_list"),
|
|
||||||
path(
|
path(
|
||||||
"user/me/<path:remaining_path>/",
|
"user/me/<path:remaining_path>/",
|
||||||
UserMeRedirect.as_view(),
|
UserMeRedirect.as_view(),
|
||||||
|
|||||||
@@ -48,7 +48,6 @@ from django.views.generic import (
|
|||||||
CreateView,
|
CreateView,
|
||||||
DeleteView,
|
DeleteView,
|
||||||
DetailView,
|
DetailView,
|
||||||
ListView,
|
|
||||||
RedirectView,
|
RedirectView,
|
||||||
TemplateView,
|
TemplateView,
|
||||||
)
|
)
|
||||||
@@ -404,13 +403,6 @@ class UserMiniView(CanViewMixin, DetailView):
|
|||||||
template_name = "core/user_mini.jinja"
|
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).
|
# 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.
|
# However, if the user re-add them manually in the form, they are saved.
|
||||||
class UserUpdateProfileView(UserTabsMixin, CanEditMixin, UpdateView):
|
class UserUpdateProfileView(UserTabsMixin, CanEditMixin, UpdateView):
|
||||||
|
|||||||
@@ -116,56 +116,6 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<section>
|
|
||||||
<div class="category-header">
|
|
||||||
<h3 class="margin-bottom">{% trans %}Eurockéennes 2025 partnership{% endtrans %}</h3>
|
|
||||||
{% if user.is_subscribed %}
|
|
||||||
<div id="eurock-partner" style="
|
|
||||||
min-height: 600px;
|
|
||||||
background-color: lightgrey;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 10px;
|
|
||||||
">
|
|
||||||
<p style="text-align: center;">
|
|
||||||
{% trans trimmed %}
|
|
||||||
Our partner uses Weezevent to sell tickets.
|
|
||||||
Weezevent may collect user info according to
|
|
||||||
its own privacy policy.
|
|
||||||
By clicking the accept button you consent to
|
|
||||||
their terms of services.
|
|
||||||
{% endtrans %}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<a href="https://weezevent.com/fr/politique-de-confidentialite/">{% trans %}Privacy policy{% endtrans %}</a>
|
|
||||||
|
|
||||||
<button
|
|
||||||
hx-get="{{ url("eboutic:eurock") }}"
|
|
||||||
hx-target="#eurock-partner"
|
|
||||||
hx-swap="outerHTML"
|
|
||||||
hx-trigger="click, load[document.cookie.includes('weezevent_accept=true')]"
|
|
||||||
@htmx:after-request="document.cookie = 'weezevent_accept=true'"
|
|
||||||
>{% trans %}Accept{% endtrans %}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
{% else %}
|
|
||||||
<p>
|
|
||||||
{%- trans trimmed %}
|
|
||||||
You must be subscribed to benefit from the partnership with the Eurockéennes.
|
|
||||||
{% endtrans -%}
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
{%- trans trimmed %}
|
|
||||||
This partnership offers a discount of up to 33%
|
|
||||||
on tickets for Friday, Saturday and Sunday,
|
|
||||||
as well as the 3-day package from Friday to Sunday.
|
|
||||||
{% endtrans -%}
|
|
||||||
</p>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
{% for priority_groups in products|groupby('order') %}
|
{% for priority_groups in products|groupby('order') %}
|
||||||
{% for category, items in priority_groups.list|groupby('category') %}
|
{% for category, items in priority_groups.list|groupby('category') %}
|
||||||
{% if items|count > 0 %}
|
{% if items|count > 0 %}
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
<a title="Logiciel billetterie en ligne"
|
|
||||||
href="https://www.weezevent.com?c=sys_widget"
|
|
||||||
class="weezevent-widget-integration"
|
|
||||||
target="_blank"
|
|
||||||
data-src="https://widget.weezevent.com/ticket/8aaba226-f7a3-4192-a64e-72ff8f5b35b7?id_evenement=1419869&locale=fr-FR&code=28747"
|
|
||||||
data-width="650"
|
|
||||||
data-height="600"
|
|
||||||
data-resize="1"
|
|
||||||
data-nopb="0"
|
|
||||||
data-type="neo"
|
|
||||||
data-width_auto="1"
|
|
||||||
data-noscroll="0"
|
|
||||||
data-id="1419869">
|
|
||||||
Billetterie Weezevent
|
|
||||||
</a>
|
|
||||||
<script type="text/javascript" src="https://widget.weezevent.com/weez.js" async defer></script>
|
|
||||||
17
eboutic/templates/eboutic/eurok_fragment.jinja
Normal file
17
eboutic/templates/eboutic/eurok_fragment.jinja
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<a
|
||||||
|
title="Logiciel billetterie en ligne"
|
||||||
|
href="https://widget.weezevent.com/ticket/6ef65533-f5b0-4571-9d21-1f1bc63921f0?id_evenement=1211855&locale=fr-FR&code=34146"
|
||||||
|
class="weezevent-widget-integration"
|
||||||
|
target="_blank"
|
||||||
|
data-src="https://widget.weezevent.com/ticket/6ef65533-f5b0-4571-9d21-1f1bc63921f0?id_evenement=1211855&locale=fr-FR&code=34146"
|
||||||
|
data-width="650"
|
||||||
|
data-height="600"
|
||||||
|
data-resize="1"
|
||||||
|
data-nopb="0"
|
||||||
|
data-type="neo"
|
||||||
|
data-width_auto="1"
|
||||||
|
data-noscroll="0"
|
||||||
|
data-id="1211855">
|
||||||
|
Billetterie Weezevent
|
||||||
|
</a>
|
||||||
|
<script type="text/javascript" src="https://widget.weezevent.com/weez.js" async defer></script>
|
||||||
@@ -31,7 +31,6 @@ from eboutic.views import (
|
|||||||
EbouticMainView,
|
EbouticMainView,
|
||||||
EbouticPayWithSith,
|
EbouticPayWithSith,
|
||||||
EtransactionAutoAnswer,
|
EtransactionAutoAnswer,
|
||||||
EurockPartnerFragment,
|
|
||||||
payment_result,
|
payment_result,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -51,5 +50,4 @@ urlpatterns = [
|
|||||||
EtransactionAutoAnswer.as_view(),
|
EtransactionAutoAnswer.as_view(),
|
||||||
name="etransation_autoanswer",
|
name="etransation_autoanswer",
|
||||||
),
|
),
|
||||||
path("eurock/", EurockPartnerFragment.as_view(), name="eurock"),
|
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -42,11 +42,11 @@ from django.shortcuts import redirect, render
|
|||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.views.decorators.http import require_GET
|
from django.views.decorators.http import require_GET
|
||||||
from django.views.generic import DetailView, FormView, TemplateView, UpdateView, View
|
from django.views.generic import DetailView, FormView, UpdateView, View
|
||||||
from django.views.generic.edit import SingleObjectMixin
|
from django.views.generic.edit import SingleObjectMixin
|
||||||
from django_countries.fields import Country
|
from django_countries.fields import Country
|
||||||
|
|
||||||
from core.auth.mixins import CanViewMixin, IsSubscriberMixin
|
from core.auth.mixins import CanViewMixin
|
||||||
from core.views.mixins import FragmentMixin, UseFragmentsMixin
|
from core.views.mixins import FragmentMixin, UseFragmentsMixin
|
||||||
from counter.forms import BaseBasketForm, BasketProductForm, BillingInfoForm
|
from counter.forms import BaseBasketForm, BasketProductForm, BillingInfoForm
|
||||||
from counter.models import (
|
from counter.models import (
|
||||||
@@ -350,7 +350,3 @@ class EtransactionAutoAnswer(View):
|
|||||||
return HttpResponse(
|
return HttpResponse(
|
||||||
"Payment failed with error: " + request.GET["Error"], status=202
|
"Payment failed with error: " + request.GET["Error"], status=202
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class EurockPartnerFragment(IsSubscriberMixin, TemplateView):
|
|
||||||
template_name = "eboutic/eurock_fragment.jinja"
|
|
||||||
|
|||||||
@@ -146,7 +146,7 @@
|
|||||||
<label for="{{ input_id }}">
|
<label for="{{ input_id }}">
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
<figure>
|
<figure>
|
||||||
{%- if user.is_viewable %}
|
{%- if user.can_view(candidature.user) %}
|
||||||
{% if candidature.user.profile_pict %}
|
{% if candidature.user.profile_pict %}
|
||||||
<img class="candidate__picture" src="{{ candidature.user.profile_pict.get_download_url() }}" alt="{% trans %}Profile{% endtrans %}">
|
<img class="candidate__picture" src="{{ candidature.user.profile_pict.get_download_url() }}" alt="{% trans %}Profile{% endtrans %}">
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|||||||
@@ -5838,39 +5838,3 @@ msgstr "Vous ne pouvez plus écrire de commentaires, la date est passée."
|
|||||||
#, python-format
|
#, python-format
|
||||||
msgid "Maximum characters: %(max_length)s"
|
msgid "Maximum characters: %(max_length)s"
|
||||||
msgstr "Nombre de caractères max: %(max_length)s"
|
msgstr "Nombre de caractères max: %(max_length)s"
|
||||||
|
|
||||||
#: eboutic/templates/eboutic/eboutic_main.jinja
|
|
||||||
msgid "Eurockéennes 2025 partnership"
|
|
||||||
msgstr "Partenariat Eurockéennes 2025"
|
|
||||||
|
|
||||||
#: eboutic/templates/eboutic/eboutic_main.jinja
|
|
||||||
msgid ""
|
|
||||||
"Our partner uses Weezevent to sell tickets. Weezevent may collect user info "
|
|
||||||
"according to its own privacy policy. By clicking the accept button you "
|
|
||||||
"consent to their terms of services."
|
|
||||||
msgstr ""
|
|
||||||
"Notre partenaire utilises Wezevent pour vendre ses billets. Weezevent peut "
|
|
||||||
"collecter des informations utilisateur conformément à sa propre politique de "
|
|
||||||
"confidentialité. En cliquant sur le bouton d'acceptation vous consentez à "
|
|
||||||
"leurs termes de service."
|
|
||||||
|
|
||||||
#: eboutic/templates/eboutic/eboutic_main.jinja
|
|
||||||
msgid "Privacy policy"
|
|
||||||
msgstr "Politique de confidentialité"
|
|
||||||
|
|
||||||
#: eboutic/templates/eboutic/eboutic_main.jinja
|
|
||||||
msgid ""
|
|
||||||
"You must be subscribed to benefit from the partnership with the Eurockéennes."
|
|
||||||
msgstr ""
|
|
||||||
"Vous devez être cotisant pour bénéficier du partenariat avec les "
|
|
||||||
"Eurockéennes."
|
|
||||||
|
|
||||||
#: eboutic/templates/eboutic/eboutic_main.jinja
|
|
||||||
#, python-format
|
|
||||||
msgid ""
|
|
||||||
"This partnership offers a discount of up to 33%% on tickets for Friday, "
|
|
||||||
"Saturday and Sunday, as well as the 3-day package from Friday to Sunday."
|
|
||||||
msgstr ""
|
|
||||||
"Ce partenariat permet de profiter d'une réduction jusqu'à 33%% sur les "
|
|
||||||
"billets du vendredi, du samedi et du dimanche, ainsi qu'au forfait 3 jours, "
|
|
||||||
"du vendredi au dimanche."
|
|
||||||
|
|||||||
Reference in New Issue
Block a user