mirror of
https://github.com/ae-utbm/sith.git
synced 2026-03-14 23:55:04 +00:00
Compare commits
5 Commits
master
...
user-white
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8dd6cefad5 | ||
|
|
ad4f7fb765 | ||
|
|
1d672a5fc2 | ||
|
|
d374ea9651 | ||
|
|
10a4e71b7a |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -24,6 +24,9 @@ node_modules/
|
|||||||
# compiled documentation
|
# compiled documentation
|
||||||
site/
|
site/
|
||||||
|
|
||||||
|
# rollup-bundle-visualizer report
|
||||||
|
.bundle-size-report.html
|
||||||
|
|
||||||
### Redis ###
|
### Redis ###
|
||||||
|
|
||||||
# Ignore redis binary dump (dump.rdb) files
|
# Ignore redis binary dump (dump.rdb) files
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
},
|
},
|
||||||
"files": {
|
"files": {
|
||||||
"ignoreUnknown": false,
|
"ignoreUnknown": false,
|
||||||
"includes": ["**/static/**"]
|
"includes": ["**/static/**", "vite.config.mts"]
|
||||||
},
|
},
|
||||||
"formatter": {
|
"formatter": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
|
|||||||
@@ -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"]
|
||||||
|
|||||||
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"):
|
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=_(
|
||||||
|
"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)
|
godfathers = models.ManyToManyField("User", related_name="godchildren", blank=True)
|
||||||
|
|
||||||
objects = CustomUserManager()
|
objects = CustomUserManager()
|
||||||
@@ -518,7 +527,7 @@ class User(AbstractUser):
|
|||||||
self.username = user_name
|
self.username = user_name
|
||||||
return 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."""
|
"""Determine if the object is owned by the user."""
|
||||||
if hasattr(obj, "is_owned_by") and obj.is_owned_by(self):
|
if hasattr(obj, "is_owned_by") and obj.is_owned_by(self):
|
||||||
return True
|
return True
|
||||||
@@ -526,7 +535,7 @@ class User(AbstractUser):
|
|||||||
return True
|
return True
|
||||||
return self.is_root
|
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."""
|
"""Determine if the object can be edited by the user."""
|
||||||
if hasattr(obj, "can_be_edited_by") and obj.can_be_edited_by(self):
|
if hasattr(obj, "can_be_edited_by") and obj.can_be_edited_by(self):
|
||||||
return True
|
return True
|
||||||
@@ -540,11 +549,9 @@ class User(AbstractUser):
|
|||||||
pks = list(obj.edit_groups.values_list("id", flat=True))
|
pks = list(obj.edit_groups.values_list("id", flat=True))
|
||||||
if any(self.is_in_group(pk=pk) for pk in pks):
|
if any(self.is_in_group(pk=pk) for pk in pks):
|
||||||
return True
|
return True
|
||||||
if isinstance(obj, User) and obj == self:
|
|
||||||
return True
|
|
||||||
return self.is_owner(obj)
|
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."""
|
"""Determine if the object can be viewed by the user."""
|
||||||
if hasattr(obj, "can_be_viewed_by") and obj.can_be_viewed_by(self):
|
if hasattr(obj, "can_be_viewed_by") and obj.can_be_viewed_by(self):
|
||||||
return True
|
return True
|
||||||
@@ -563,14 +570,35 @@ class User(AbstractUser):
|
|||||||
return True
|
return True
|
||||||
return self.can_edit(obj)
|
return self.can_edit(obj)
|
||||||
|
|
||||||
def can_be_edited_by(self, user):
|
def can_be_edited_by(self, user: User):
|
||||||
return user.is_root or user.is_board_member
|
return user == self or 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):
|
||||||
@@ -750,7 +778,11 @@ class Preferences(models.Model):
|
|||||||
User, related_name="_preferences", on_delete=models.CASCADE
|
User, related_name="_preferences", on_delete=models.CASCADE
|
||||||
)
|
)
|
||||||
receive_weekmail = models.BooleanField(_("receive the Weekmail"), default=False)
|
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(
|
notify_on_click = models.BooleanField(
|
||||||
_("get a notification for every click"), default=False
|
_("get a notification for every click"), default=False
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ export class NfcInput extends inheritHtmlElement("input") {
|
|||||||
window.alert(gettext("Unsupported NFC card"));
|
window.alert(gettext("Unsupported NFC card"));
|
||||||
});
|
});
|
||||||
|
|
||||||
// biome-ignore lint/correctness/noUndeclaredVariables: browser API
|
|
||||||
ndef.addEventListener("reading", (event: NDEFReadingEvent) => {
|
ndef.addEventListener("reading", (event: NDEFReadingEvent) => {
|
||||||
this.removeAttribute("scan");
|
this.removeAttribute("scan");
|
||||||
this.node.value = event.serialNumber.replace(/:/g, "").toUpperCase();
|
this.node.value = event.serialNumber.replace(/:/g, "").toUpperCase();
|
||||||
|
|||||||
@@ -157,6 +157,7 @@ form {
|
|||||||
margin-bottom: .25rem;
|
margin-bottom: .25rem;
|
||||||
font-size: 80%;
|
font-size: 80%;
|
||||||
display: block;
|
display: block;
|
||||||
|
max-width: calc(100% - calc(var(--nf-input-size) * 2))
|
||||||
}
|
}
|
||||||
|
|
||||||
fieldset {
|
fieldset {
|
||||||
|
|||||||
@@ -5,17 +5,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.profile {
|
.profile {
|
||||||
&-visible {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
gap: 5px;
|
|
||||||
padding-top: 10px;
|
|
||||||
input[type="checkbox"]+label {
|
|
||||||
max-width: unset;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&-pictures {
|
&-pictures {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
display: flex;
|
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 {
|
.justify {
|
||||||
|
|||||||
@@ -1,14 +1,11 @@
|
|||||||
<div id="quick-notifications"
|
<div id="quick-notifications"
|
||||||
x-data="{
|
x-data="{
|
||||||
messages: [
|
messages: [
|
||||||
{% if messages %}
|
{%- for message in messages -%}
|
||||||
{% for message in messages %}
|
{%- if not message.extra_tags -%}
|
||||||
{
|
{ tag: '{{ message.tags }}', text: '{{ message }}' },
|
||||||
tag: '{{ message.tags }}',
|
{%- endif -%}
|
||||||
text: '{{ message }}',
|
{%- endfor -%}
|
||||||
},
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
]
|
]
|
||||||
}"
|
}"
|
||||||
@quick-notification-add="(e) => messages.push(e?.detail)"
|
@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 -%}
|
{%- endfor -%}
|
||||||
</div>
|
</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">
|
<div class="final-actions">
|
||||||
|
|
||||||
{%- if form.instance == user -%}
|
{%- if form.instance == user -%}
|
||||||
<p>
|
<p>
|
||||||
<a href="{{ url('core:password_change') }}">{%- trans -%}Change my password{%- endtrans -%}</a>
|
<a href="{{ url('core:password_change') }}">{%- trans -%}Change my password{%- endtrans -%}</a>
|
||||||
@@ -170,7 +159,6 @@
|
|||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<input type="submit" value="{%- trans -%}Update{%- endtrans -%}" />
|
<input type="submit" value="{%- trans -%}Update{%- endtrans -%}" />
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -1,7 +1,13 @@
|
|||||||
{% extends "core/base.jinja" %}
|
{% extends "core/base.jinja" %}
|
||||||
|
|
||||||
|
{%- block additional_js -%}
|
||||||
|
<script type="module" src="{{ static("bundled/core/components/ajax-select-index.ts") }}"></script>
|
||||||
|
{%- endblock -%}
|
||||||
|
|
||||||
{%- block additional_css -%}
|
{%- block additional_css -%}
|
||||||
<link rel="stylesheet" href="{{ static('user/user_preferences.scss') }}">
|
<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 -%}
|
{%- endblock -%}
|
||||||
|
|
||||||
{% block title %}
|
{% block title %}
|
||||||
@@ -11,30 +17,22 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="main">
|
<div class="main">
|
||||||
<h2>{% trans %}Preferences{% endtrans %}</h2>
|
<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 />
|
<br />
|
||||||
<a href="{{ url('trombi:user_tools') }}">{% trans %}Go to my Trombi tools{% endtrans %}</a>
|
<h3>{% trans %}Notifications{% endtrans %}</h3>
|
||||||
</p>
|
<form action="" method="post" enctype="multipart/form-data">
|
||||||
{% endif %}
|
{% 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 %}
|
{% if student_card_fragment %}
|
||||||
<h3>{% trans %}Student card{% endtrans %}</h3>
|
<h3>{% trans %}Student card{% endtrans %}</h3>
|
||||||
{{ student_card_fragment }}
|
{{ 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 %}
|
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>
|
</p>
|
||||||
{% endif %}
|
{% 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>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -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,
|
||||||
@@ -78,6 +77,7 @@ from core.views import (
|
|||||||
UserUpdateGroupView,
|
UserUpdateGroupView,
|
||||||
UserUpdateProfileView,
|
UserUpdateProfileView,
|
||||||
UserView,
|
UserView,
|
||||||
|
UserVisibilityFormFragment,
|
||||||
delete_user_godfather,
|
delete_user_godfather,
|
||||||
logout,
|
logout,
|
||||||
notification,
|
notification,
|
||||||
@@ -136,7 +136,11 @@ 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(
|
||||||
|
"fragment/user/<int:user_id>/",
|
||||||
|
UserVisibilityFormFragment.as_view(),
|
||||||
|
name="user_visibility_fragment",
|
||||||
|
),
|
||||||
path(
|
path(
|
||||||
"user/me/<path:remaining_path>/",
|
"user/me/<path:remaining_path>/",
|
||||||
UserMeRedirect.as_view(),
|
UserMeRedirect.as_view(),
|
||||||
|
|||||||
@@ -48,12 +48,13 @@ from phonenumber_field.widgets import RegionalPhoneNumberWidget
|
|||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
from antispam.forms import AntiSpamEmailField
|
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.utils import resize_image
|
||||||
from core.views.widgets.ajax_select import (
|
from core.views.widgets.ajax_select import (
|
||||||
AutoCompleteSelect,
|
AutoCompleteSelect,
|
||||||
AutoCompleteSelectGroup,
|
AutoCompleteSelectGroup,
|
||||||
AutoCompleteSelectMultipleGroup,
|
AutoCompleteSelectMultipleGroup,
|
||||||
|
AutoCompleteSelectMultipleUser,
|
||||||
AutoCompleteSelectUser,
|
AutoCompleteSelectUser,
|
||||||
)
|
)
|
||||||
from core.views.widgets.markdown import MarkdownInput
|
from core.views.widgets.markdown import MarkdownInput
|
||||||
@@ -179,7 +180,6 @@ class UserProfileForm(forms.ModelForm):
|
|||||||
"school",
|
"school",
|
||||||
"promo",
|
"promo",
|
||||||
"forum_signature",
|
"forum_signature",
|
||||||
"is_viewable",
|
|
||||||
]
|
]
|
||||||
widgets = {
|
widgets = {
|
||||||
"date_of_birth": SelectDate,
|
"date_of_birth": SelectDate,
|
||||||
@@ -264,6 +264,38 @@ class UserProfileForm(forms.ModelForm):
|
|||||||
self._post_clean()
|
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):
|
class UserGroupsForm(forms.ModelForm):
|
||||||
error_css_class = "error"
|
error_css_class = "error"
|
||||||
required_css_class = "required"
|
required_css_class = "required"
|
||||||
|
|||||||
@@ -28,10 +28,12 @@ from datetime import timedelta
|
|||||||
from operator import itemgetter
|
from operator import itemgetter
|
||||||
from smtplib import SMTPException
|
from smtplib import SMTPException
|
||||||
|
|
||||||
|
from django.contrib import messages
|
||||||
from django.contrib.auth import login, views
|
from django.contrib.auth import login, views
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.contrib.auth.forms import PasswordChangeForm, SetPasswordForm
|
from django.contrib.auth.forms import PasswordChangeForm, SetPasswordForm
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
|
||||||
|
from django.contrib.messages.views import SuccessMessageMixin
|
||||||
from django.core.exceptions import PermissionDenied
|
from django.core.exceptions import PermissionDenied
|
||||||
from django.db.models import DateField, F, QuerySet, Sum
|
from django.db.models import DateField, F, QuerySet, Sum
|
||||||
from django.db.models.functions import Trunc
|
from django.db.models.functions import Trunc
|
||||||
@@ -48,7 +50,6 @@ from django.views.generic import (
|
|||||||
CreateView,
|
CreateView,
|
||||||
DeleteView,
|
DeleteView,
|
||||||
DetailView,
|
DetailView,
|
||||||
ListView,
|
|
||||||
RedirectView,
|
RedirectView,
|
||||||
TemplateView,
|
TemplateView,
|
||||||
)
|
)
|
||||||
@@ -65,8 +66,9 @@ from core.views.forms import (
|
|||||||
UserGodfathersForm,
|
UserGodfathersForm,
|
||||||
UserGroupsForm,
|
UserGroupsForm,
|
||||||
UserProfileForm,
|
UserProfileForm,
|
||||||
|
UserVisibilityForm,
|
||||||
)
|
)
|
||||||
from core.views.mixins import TabedViewMixin, UseFragmentsMixin
|
from core.views.mixins import FragmentMixin, TabedViewMixin, UseFragmentsMixin
|
||||||
from counter.models import Refilling, Selling
|
from counter.models import Refilling, Selling
|
||||||
from eboutic.models import Invoice
|
from eboutic.models import Invoice
|
||||||
from trombi.views import UserTrombiForm
|
from trombi.views import UserTrombiForm
|
||||||
@@ -404,13 +406,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):
|
||||||
@@ -468,6 +463,30 @@ class UserClubView(UserTabsMixin, CanViewMixin, DetailView):
|
|||||||
current_tab = "clubs"
|
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):
|
class UserPreferencesView(UserTabsMixin, UseFragmentsMixin, CanEditMixin, UpdateView):
|
||||||
"""Edit a user's preferences."""
|
"""Edit a user's preferences."""
|
||||||
|
|
||||||
@@ -481,7 +500,10 @@ class UserPreferencesView(UserTabsMixin, UseFragmentsMixin, CanEditMixin, Update
|
|||||||
current_tab = "prefs"
|
current_tab = "prefs"
|
||||||
|
|
||||||
def get_form_kwargs(self):
|
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):
|
def get_success_url(self):
|
||||||
return self.request.path
|
return self.request.path
|
||||||
@@ -491,6 +513,9 @@ class UserPreferencesView(UserTabsMixin, UseFragmentsMixin, CanEditMixin, Update
|
|||||||
from counter.views.student_card import StudentCardFormFragment
|
from counter.views.student_card import StudentCardFormFragment
|
||||||
|
|
||||||
res = super().get_fragment_context_data()
|
res = super().get_fragment_context_data()
|
||||||
|
res["user_visibility_fragment"] = UserVisibilityFormFragment.as_fragment()(
|
||||||
|
self.request, user=self.object
|
||||||
|
)
|
||||||
if hasattr(self.object, "customer"):
|
if hasattr(self.object, "customer"):
|
||||||
res["student_card_fragment"] = StudentCardFormFragment.as_fragment()(
|
res["student_card_fragment"] = StudentCardFormFragment.as_fragment()(
|
||||||
self.request, customer=self.object.customer
|
self.request, customer=self.object.customer
|
||||||
|
|||||||
@@ -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 %}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"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"
|
"PO-Revision-Date: 2016-07-18\n"
|
||||||
"Last-Translator: Maréchal <thomas.girod@utbm.fr\n"
|
"Last-Translator: Maréchal <thomas.girod@utbm.fr\n"
|
||||||
"Language-Team: AE info <ae.info@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/news_edit.jinja com/templates/com/poster_edit.jinja
|
||||||
#: com/templates/com/screen_edit.jinja com/templates/com/weekmail.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/create.jinja core/templates/core/edit.jinja
|
||||||
#: core/templates/core/file_edit.jinja core/templates/core/page/edit.jinja
|
#: core/templates/core/file_edit.jinja
|
||||||
#: core/templates/core/page/prop.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.jinja
|
||||||
#: core/templates/core/user_godfathers_tree.jinja
|
#: core/templates/core/user_godfathers_tree.jinja
|
||||||
#: core/templates/core/user_preferences.jinja
|
#: core/templates/core/user_preferences.jinja
|
||||||
@@ -1547,6 +1548,17 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Si vous désactivez cette option, seuls les admins pourront voir votre profil."
|
"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
|
#: core/models.py
|
||||||
msgid "A user with that username already exists"
|
msgid "A user with that username already exists"
|
||||||
msgstr "Un utilisateur de ce nom d'utilisateur existe déjà"
|
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"
|
msgid "show your stats to others"
|
||||||
msgstr "montrez vos statistiques aux autres"
|
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
|
#: core/models.py
|
||||||
msgid "get a notification for every click"
|
msgid "get a notification for every click"
|
||||||
msgstr "avoir une notification pour chaque click"
|
msgstr "avoir une notification pour chaque click"
|
||||||
@@ -2612,21 +2628,12 @@ msgid "Preferences"
|
|||||||
msgstr "Préférences"
|
msgstr "Préférences"
|
||||||
|
|
||||||
#: core/templates/core/user_preferences.jinja
|
#: core/templates/core/user_preferences.jinja
|
||||||
msgid "General"
|
msgid "Notifications"
|
||||||
msgstr "Général"
|
msgstr "Notifications"
|
||||||
|
|
||||||
#: core/templates/core/user_preferences.jinja trombi/views.py
|
|
||||||
msgid "Trombi"
|
|
||||||
msgstr "Trombi"
|
|
||||||
|
|
||||||
#: core/templates/core/user_preferences.jinja
|
#: core/templates/core/user_preferences.jinja
|
||||||
#, python-format
|
msgid "Visibility"
|
||||||
msgid "You already choose to be in that Trombi: %(trombi)s."
|
msgstr "Visibilité"
|
||||||
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_preferences.jinja
|
#: core/templates/core/user_preferences.jinja
|
||||||
#: counter/templates/counter/counter_click.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 "
|
"aurez besoin d'un lecteur NFC. Nous enregistrons l'UID de la carte qui fait "
|
||||||
"14 caractères de long."
|
"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
|
#: core/templates/core/user_stats.jinja
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "%(user_name)s's stats"
|
msgid "%(user_name)s's stats"
|
||||||
@@ -2925,6 +2945,10 @@ msgstr "Photos"
|
|||||||
msgid "Account"
|
msgid "Account"
|
||||||
msgstr "Compte"
|
msgstr "Compte"
|
||||||
|
|
||||||
|
#: core/views/user.py
|
||||||
|
msgid "Visibility parameters updated."
|
||||||
|
msgstr "Paramètres de visibilité mis à jour."
|
||||||
|
|
||||||
#: counter/apps.py counter/models.py
|
#: counter/apps.py counter/models.py
|
||||||
msgid "counter"
|
msgid "counter"
|
||||||
msgstr "comptoir"
|
msgstr "comptoir"
|
||||||
|
|||||||
2047
package-lock.json
generated
2047
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -8,8 +8,6 @@
|
|||||||
"compile-dev": "vite build --mode development",
|
"compile-dev": "vite build --mode development",
|
||||||
"serve": "vite build --mode development --watch --minify false",
|
"serve": "vite build --mode development --watch --minify false",
|
||||||
"openapi": "openapi-ts",
|
"openapi": "openapi-ts",
|
||||||
"analyse-dev": "vite-bundle-visualizer --mode development",
|
|
||||||
"analyse-prod": "vite-bundle-visualizer --mode production",
|
|
||||||
"check": "tsc && biome check --write"
|
"check": "tsc && biome check --write"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
@@ -35,10 +33,9 @@
|
|||||||
"@types/cytoscape-cxtmenu": "^3.4.5",
|
"@types/cytoscape-cxtmenu": "^3.4.5",
|
||||||
"@types/cytoscape-klay": "^3.1.5",
|
"@types/cytoscape-klay": "^3.1.5",
|
||||||
"@types/js-cookie": "^3.0.6",
|
"@types/js-cookie": "^3.0.6",
|
||||||
|
"rollup-plugin-visualizer": "^7.0.1",
|
||||||
"typescript": "^5.9.3",
|
"typescript": "^5.9.3",
|
||||||
"vite": "^7.3.1",
|
"vite": "^8.0.0"
|
||||||
"vite-bundle-visualizer": "^1.2.1",
|
|
||||||
"vite-plugin-static-copy": "^3.2.0"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@alpinejs/sort": "^3.15.8",
|
"@alpinejs/sort": "^3.15.8",
|
||||||
|
|||||||
@@ -1,14 +1,17 @@
|
|||||||
// biome-ignore lint/correctness/noNodejsModules: this is backend side
|
|
||||||
import { parse, resolve } from "node:path";
|
import { parse, resolve } from "node:path";
|
||||||
import inject from "@rollup/plugin-inject";
|
import inject from "@rollup/plugin-inject";
|
||||||
import { glob } from "glob";
|
import { glob } from "glob";
|
||||||
import type { Rollup } from "vite";
|
import { visualizer } from "rollup-plugin-visualizer";
|
||||||
import { type AliasOptions, defineConfig, type UserConfig } from "vite";
|
import {
|
||||||
|
type AliasOptions,
|
||||||
|
defineConfig,
|
||||||
|
type PluginOption,
|
||||||
|
type Rollup,
|
||||||
|
type UserConfig,
|
||||||
|
} from "vite";
|
||||||
import tsconfig from "./tsconfig.json";
|
import tsconfig from "./tsconfig.json";
|
||||||
|
|
||||||
const outDir = resolve(__dirname, "./staticfiles/generated/bundled");
|
const outDir = resolve(__dirname, "./staticfiles/generated/bundled");
|
||||||
const vendored = resolve(outDir, "vendored");
|
|
||||||
const nodeModules = resolve(__dirname, "node_modules");
|
|
||||||
const collectedFiles = glob.sync(
|
const collectedFiles = glob.sync(
|
||||||
"./!(static)/static/bundled/**/*?(-)index.?(m)[j|t]s?(x)",
|
"./!(static)/static/bundled/**/*?(-)index.?(m)[j|t]s?(x)",
|
||||||
);
|
);
|
||||||
@@ -42,7 +45,6 @@ function getRelativeAssetPath(path: string): string {
|
|||||||
return relativePath.join("/");
|
return relativePath.join("/");
|
||||||
}
|
}
|
||||||
|
|
||||||
// biome-ignore lint/style/noDefaultExport: this is recommended by documentation
|
|
||||||
export default defineConfig((config: UserConfig) => {
|
export default defineConfig((config: UserConfig) => {
|
||||||
return {
|
return {
|
||||||
base: "/static/bundled/",
|
base: "/static/bundled/",
|
||||||
@@ -86,6 +88,7 @@ export default defineConfig((config: UserConfig) => {
|
|||||||
Alpine: "alpinejs",
|
Alpine: "alpinejs",
|
||||||
htmx: "htmx.org",
|
htmx: "htmx.org",
|
||||||
}),
|
}),
|
||||||
|
visualizer({ filename: ".bundle-size-report.html" }) as PluginOption,
|
||||||
],
|
],
|
||||||
} satisfies UserConfig;
|
} satisfies UserConfig;
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user