3 Commits

Author SHA1 Message Date
dependabot[bot]
562b288df1 Bump requests from 2.32.5 to 2.33.0
Bumps [requests](https://github.com/psf/requests) from 2.32.5 to 2.33.0.
- [Release notes](https://github.com/psf/requests/releases)
- [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md)
- [Commits](https://github.com/psf/requests/compare/v2.32.5...v2.33.0)

---
updated-dependencies:
- dependency-name: requests
  dependency-version: 2.33.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-26 18:15:11 +00:00
Titouan
173311c1d5 Merge pull request #1315 from ae-utbm/taiste
Product history, formula management, test election
2026-03-12 11:33:45 +01:00
thomas girod
2995823d6e Merge pull request #1293 from ae-utbm/taiste
Refactors, updates and db optimisations
2026-02-13 15:25:04 +01:00
15 changed files with 1357 additions and 810 deletions

3
.gitignore vendored
View File

@@ -24,9 +24,6 @@ node_modules/
# compiled documentation
site/
# rollup-bundle-visualizer report
.bundle-size-report.html
### Redis ###
# Ignore redis binary dump (dump.rdb) files

View File

@@ -7,7 +7,7 @@
},
"files": {
"ignoreUnknown": false,
"includes": ["**/static/**", "vite.config.mts"]
"includes": ["**/static/**"]
},
"formatter": {
"enabled": true,

View File

@@ -244,8 +244,9 @@ class NewsListView(TemplateView):
.filter(
date_of_birth__month=localdate().month,
date_of_birth__day=localdate().day,
role__in=["STUDENT", "FORMER STUDENT"],
is_viewable=True,
)
.filter(role__in=["STUDENT", "FORMER STUDENT"])
.order_by("-date_of_birth"),
key=lambda u: u.date_of_birth.year,
)

View File

@@ -63,7 +63,6 @@ class UserAdmin(admin.ModelAdmin):
"scrub_pict",
"user_permissions",
"groups",
"whitelisted_users",
)
inlines = (UserBanInline,)
search_fields = ["first_name", "last_name", "username"]

View File

@@ -1,24 +0,0 @@
# 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",
),
),
]

View File

@@ -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(Q(is_viewable=True) | Q(whitelisted_users=user))
return self.filter(is_viewable=True)
if user.is_anonymous:
return self.none()
return self.filter(id=user.id)
@@ -279,15 +279,6 @@ class User(AbstractUser):
),
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)
objects = CustomUserManager()
@@ -576,31 +567,10 @@ class User(AbstractUser):
return 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 is_in_whitelist(user))
)
or (user.has_perm("core.view_user") and self.is_viewable)
)
def get_mini_item(self):

View File

@@ -26,6 +26,7 @@ export class NfcInput extends inheritHtmlElement("input") {
window.alert(gettext("Unsupported NFC card"));
});
// biome-ignore lint/correctness/noUndeclaredVariables: browser API
ndef.addEventListener("reading", (event: NDEFReadingEvent) => {
this.removeAttribute("scan");
this.node.value = event.serialNumber.replace(/:/g, "").toUpperCase();

View File

@@ -399,12 +399,13 @@ class TestUserQuerySetViewableBy:
return [
baker.make(User),
subscriber_user.make(),
*subscriber_user.make(is_viewable=False, _quantity=2),
subscriber_user.make(is_viewable=False),
]
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)
@@ -417,12 +418,6 @@ 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()

View File

@@ -69,6 +69,7 @@ from core.views import (
UserCreationView,
UserGodfathersTreeView,
UserGodfathersView,
UserListView,
UserMeRedirect,
UserMiniView,
UserPreferencesView,
@@ -135,6 +136,7 @@ urlpatterns = [
"group/<int:group_id>/detail/", GroupTemplateView.as_view(), name="group_detail"
),
# User views
path("user/", UserListView.as_view(), name="user_list"),
path(
"user/me/<path:remaining_path>/",
UserMeRedirect.as_view(),

View File

@@ -48,6 +48,7 @@ from django.views.generic import (
CreateView,
DeleteView,
DetailView,
ListView,
RedirectView,
TemplateView,
)
@@ -403,6 +404,13 @@ 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):

View File

@@ -146,7 +146,7 @@
<label for="{{ input_id }}">
{%- endif %}
<figure>
{%- if user.can_view(candidature.user) %}
{%- if user.is_viewable %}
{% if candidature.user.profile_pict %}
<img class="candidate__picture" src="{{ candidature.user.profile_pict.get_download_url() }}" alt="{% trans %}Profile{% endtrans %}">
{% else %}

2047
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -8,6 +8,8 @@
"compile-dev": "vite build --mode development",
"serve": "vite build --mode development --watch --minify false",
"openapi": "openapi-ts",
"analyse-dev": "vite-bundle-visualizer --mode development",
"analyse-prod": "vite-bundle-visualizer --mode production",
"check": "tsc && biome check --write"
},
"keywords": [],
@@ -33,9 +35,10 @@
"@types/cytoscape-cxtmenu": "^3.4.5",
"@types/cytoscape-klay": "^3.1.5",
"@types/js-cookie": "^3.0.6",
"rollup-plugin-visualizer": "^7.0.1",
"typescript": "^5.9.3",
"vite": "^8.0.0"
"vite": "^7.3.1",
"vite-bundle-visualizer": "^1.2.1",
"vite-plugin-static-copy": "^3.2.0"
},
"dependencies": {
"@alpinejs/sort": "^3.15.8",

7
uv.lock generated
View File

@@ -817,6 +817,7 @@ wheels = [
name = "griffelib"
version = "2.0.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/ad/06/eccbd311c9e2b3ca45dbc063b93134c57a1ccc7607c5e545264ad092c4a9/griffelib-2.0.0.tar.gz", hash = "sha256:e504d637a089f5cab9b5daf18f7645970509bf4f53eda8d79ed71cce8bd97934", size = 166312, upload-time = "2026-03-23T21:06:55.954Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/4d/51/c936033e16d12b627ea334aaaaf42229c37620d0f15593456ab69ab48161/griffelib-2.0.0-py3-none-any.whl", hash = "sha256:01284878c966508b6d6f1dbff9b6fa607bc062d8261c5c7253cb285b06422a7f", size = 142004, upload-time = "2026-02-09T19:09:40.561Z" },
]
@@ -1925,7 +1926,7 @@ wheels = [
[[package]]
name = "requests"
version = "2.32.5"
version = "2.33.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "certifi" },
@@ -1933,9 +1934,9 @@ dependencies = [
{ name = "idna" },
{ name = "urllib3" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" }
sdist = { url = "https://files.pythonhosted.org/packages/34/64/8860370b167a9721e8956ae116825caff829224fbca0ca6e7bf8ddef8430/requests-2.33.0.tar.gz", hash = "sha256:c7ebc5e8b0f21837386ad0e1c8fe8b829fa5f544d8df3b2253bff14ef29d7652", size = 134232, upload-time = "2026-03-25T15:10:41.586Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" },
{ url = "https://files.pythonhosted.org/packages/56/5d/c814546c2333ceea4ba42262d8c4d55763003e767fa169adc693bd524478/requests-2.33.0-py3-none-any.whl", hash = "sha256:3324635456fa185245e24865e810cecec7b4caf933d7eb133dcde67d48cee69b", size = 65017, upload-time = "2026-03-25T15:10:40.382Z" },
]
[[package]]

View File

@@ -1,17 +1,14 @@
// biome-ignore lint/correctness/noNodejsModules: this is backend side
import { parse, resolve } from "node:path";
import inject from "@rollup/plugin-inject";
import { glob } from "glob";
import { visualizer } from "rollup-plugin-visualizer";
import {
type AliasOptions,
defineConfig,
type PluginOption,
type Rollup,
type UserConfig,
} from "vite";
import type { Rollup } from "vite";
import { type AliasOptions, defineConfig, type UserConfig } from "vite";
import tsconfig from "./tsconfig.json";
const outDir = resolve(__dirname, "./staticfiles/generated/bundled");
const vendored = resolve(outDir, "vendored");
const nodeModules = resolve(__dirname, "node_modules");
const collectedFiles = glob.sync(
"./!(static)/static/bundled/**/*?(-)index.?(m)[j|t]s?(x)",
);
@@ -45,6 +42,7 @@ function getRelativeAssetPath(path: string): string {
return relativePath.join("/");
}
// biome-ignore lint/style/noDefaultExport: this is recommended by documentation
export default defineConfig((config: UserConfig) => {
return {
base: "/static/bundled/",
@@ -88,7 +86,6 @@ export default defineConfig((config: UserConfig) => {
Alpine: "alpinejs",
htmx: "htmx.org",
}),
visualizer({ filename: ".bundle-size-report.html" }) as PluginOption,
],
} satisfies UserConfig;
});