mirror of
https://github.com/ae-utbm/sith.git
synced 2026-09-18 02:04:20 +00:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8dbb472865 | ||
|
|
246909376f |
@@ -18,7 +18,7 @@ class Migration(migrations.Migration):
|
|||||||
"Groups that are automatically given or removed "
|
"Groups that are automatically given or removed "
|
||||||
"to user receiving or losing this club role"
|
"to user receiving or losing this club role"
|
||||||
),
|
),
|
||||||
related_name="club_roles",
|
related_name="linked_roles",
|
||||||
to="core.group",
|
to="core.group",
|
||||||
verbose_name="Linked groups",
|
verbose_name="Linked groups",
|
||||||
),
|
),
|
||||||
|
|||||||
+4
-55
@@ -1,16 +1,15 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import typing
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.backends import ModelBackend
|
from django.contrib.auth.backends import ModelBackend
|
||||||
from django.contrib.auth.models import Permission
|
from django.contrib.auth.models import Permission
|
||||||
from django.db.models import Exists, OuterRef, Q, QuerySet
|
|
||||||
|
|
||||||
from core.models import Group, User
|
from core.models import Group
|
||||||
|
|
||||||
if typing.TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from django.db.models.base import Model
|
from core.models import User
|
||||||
|
|
||||||
|
|
||||||
class SithModelBackend(ModelBackend):
|
class SithModelBackend(ModelBackend):
|
||||||
@@ -41,53 +40,3 @@ class SithModelBackend(ModelBackend):
|
|||||||
return Permission.objects.filter(
|
return Permission.objects.filter(
|
||||||
group__group__in=groups.values_list("pk", flat=True)
|
group__group__in=groups.values_list("pk", flat=True)
|
||||||
)
|
)
|
||||||
|
|
||||||
@typing.override
|
|
||||||
def with_perm(
|
|
||||||
self,
|
|
||||||
perm: str | Permission,
|
|
||||||
is_active: bool | None = True,
|
|
||||||
include_superusers: bool = False,
|
|
||||||
obj: Model | None = None,
|
|
||||||
) -> QuerySet[User]:
|
|
||||||
"""Return users that have permission "perm".
|
|
||||||
|
|
||||||
Contrary to the base django method, superusers aren't included in the
|
|
||||||
result.
|
|
||||||
This is because the OR operation to include superusers in the query result
|
|
||||||
utterly destroy the query performances on postgres
|
|
||||||
(it makes it like 1000x slower, and I'm not even kidding).
|
|
||||||
To overcome that, we could use a UNION instead, but then we wouldn't
|
|
||||||
be able to perform further filter operations on the queryset.
|
|
||||||
Thus, the `include_superusers` argument is not used at all.
|
|
||||||
|
|
||||||
Because of that, it is useless to set `include_superusers`,
|
|
||||||
as it will be silently ignored.
|
|
||||||
The only reason it's still there is not to break the interface
|
|
||||||
of the base class.
|
|
||||||
"""
|
|
||||||
if isinstance(perm, str):
|
|
||||||
try:
|
|
||||||
app_label, codename = perm.split(".")
|
|
||||||
except ValueError as e:
|
|
||||||
raise ValueError(
|
|
||||||
"Permission name should be in the form "
|
|
||||||
"app_label.permission_codename."
|
|
||||||
) from e
|
|
||||||
permission_q = Q(codename=codename, content_type__app_label=app_label)
|
|
||||||
elif isinstance(perm, Permission):
|
|
||||||
permission_q = Q(pk=perm.pk)
|
|
||||||
else:
|
|
||||||
raise TypeError(
|
|
||||||
"The `perm` argument must be a string or a permission instance."
|
|
||||||
)
|
|
||||||
|
|
||||||
user_q = Exists(
|
|
||||||
Permission.objects.filter(
|
|
||||||
Q(group__group__users=OuterRef("pk")) | Q(user=OuterRef("pk")),
|
|
||||||
permission_q,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if is_active is not None:
|
|
||||||
user_q &= Q(is_active=is_active)
|
|
||||||
return User.objects.filter(user_q)
|
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ from typing import ClassVar, NamedTuple
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.models import Permission
|
from django.contrib.auth.models import Permission
|
||||||
from django.contrib.sites.models import Site
|
from django.contrib.sites.models import Site
|
||||||
|
from django.core.files.base import ContentFile
|
||||||
from django.core.management import call_command
|
from django.core.management import call_command
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
from django.db import connection
|
from django.db import connection
|
||||||
@@ -120,15 +121,21 @@ class Command(BaseCommand):
|
|||||||
)
|
)
|
||||||
self.profiles_root = SithFile.objects.create(name="profiles", owner=root)
|
self.profiles_root = SithFile.objects.create(name="profiles", owner=root)
|
||||||
home_root = SithFile.objects.create(name="users", owner=root)
|
home_root = SithFile.objects.create(name="users", owner=root)
|
||||||
|
|
||||||
# Page needed for club creation
|
|
||||||
p = Page(name=settings.SITH_CLUB_ROOT_PAGE)
|
|
||||||
p.save(force_lock=True)
|
|
||||||
|
|
||||||
club_root = SithFile.objects.create(name="clubs", owner=root)
|
club_root = SithFile.objects.create(name="clubs", owner=root)
|
||||||
sas = SithFile.objects.create(
|
sas = SithFile.objects.create(
|
||||||
name="SAS", owner=root, id=settings.SITH_SAS_ROOT_DIR_ID
|
name="SAS", owner=root, id=settings.SITH_SAS_ROOT_DIR_ID
|
||||||
)
|
)
|
||||||
|
SithFile.objects.create(
|
||||||
|
name="CGU",
|
||||||
|
is_folder=False,
|
||||||
|
file=ContentFile(
|
||||||
|
content="Conditions générales d'utilisation", name="cgu.txt"
|
||||||
|
),
|
||||||
|
owner=root,
|
||||||
|
)
|
||||||
|
# Page needed for club creation
|
||||||
|
p = Page(name=settings.SITH_CLUB_ROOT_PAGE)
|
||||||
|
p.save(force_lock=True)
|
||||||
clubs = self._create_clubs()
|
clubs = self._create_clubs()
|
||||||
|
|
||||||
self.reset_index("club")
|
self.reset_index("club")
|
||||||
@@ -747,7 +754,6 @@ class Command(BaseCommand):
|
|||||||
"add_subscription",
|
"add_subscription",
|
||||||
"add_membership",
|
"add_membership",
|
||||||
"view_hidden_user",
|
"view_hidden_user",
|
||||||
"add_refilling",
|
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# Generated by Django 5.2.17 on 2026-09-15 11:02
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [("core", "0050_alter_sithfile_moderator")]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="user",
|
||||||
|
name="cgu_approved",
|
||||||
|
field=models.BooleanField(default=False, verbose_name="EULA approved"),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -291,6 +291,7 @@ class User(AbstractUser):
|
|||||||
),
|
),
|
||||||
blank=True,
|
blank=True,
|
||||||
)
|
)
|
||||||
|
cgu_approved = models.BooleanField(_("EULA approved"), default=False)
|
||||||
godfathers = models.ManyToManyField("User", related_name="godchildren", blank=True)
|
godfathers = models.ManyToManyField("User", related_name="godchildren", blank=True)
|
||||||
|
|
||||||
objects = CustomUserManager()
|
objects = CustomUserManager()
|
||||||
|
|||||||
@@ -49,67 +49,29 @@ body {
|
|||||||
max-width: 500px;
|
max-width: 500px;
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
|
|
||||||
>p,
|
input[type="submit"] {
|
||||||
>div {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 0;
|
|
||||||
|
|
||||||
>label {
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
@media (min-width: 500px) {
|
|
||||||
width: 300px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
>input,
|
|
||||||
>p>input,
|
|
||||||
>div>input {
|
|
||||||
box-sizing: border-box;
|
|
||||||
width: 100%;
|
|
||||||
max-width: 500px;
|
|
||||||
|
|
||||||
@media (min-width: 500px) {
|
|
||||||
max-width: 300px;
|
max-width: 300px;
|
||||||
}
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
>.errorlist {
|
.errorlist {
|
||||||
color: red;
|
color: red;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin: 10px 0 0 0;
|
margin: 10px 0 0 0;
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
>.required>.helptext {
|
div, fieldset {
|
||||||
text-align: center;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
>.required:last-of-type {
|
|
||||||
box-sizing: border-box;
|
|
||||||
max-width: 300px;
|
max-width: 300px;
|
||||||
flex-direction: row;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: space-between;
|
|
||||||
|
|
||||||
>label {
|
|
||||||
width: 100%;
|
|
||||||
}
|
}
|
||||||
|
.captcha {
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
>img {
|
>img {
|
||||||
width: 70px;
|
width: 70px;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
}
|
}
|
||||||
|
|
||||||
>input {
|
|
||||||
width: 200px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,13 +50,12 @@
|
|||||||
<input type="hidden" name="next" value="{{ next }}">
|
<input type="hidden" name="next" value="{{ next }}">
|
||||||
<input type="submit" value="{% trans %}Login{% endtrans %}">
|
<input type="submit" value="{% trans %}Login{% endtrans %}">
|
||||||
|
|
||||||
|
<div>
|
||||||
{# Assumes you setup the password_reset view in your URLconf #}
|
|
||||||
<p>
|
|
||||||
<a href="{{ url('core:password_reset') }}">{% trans %}Lost password?{% endtrans %}</a>
|
<a href="{{ url('core:password_reset') }}">{% trans %}Lost password?{% endtrans %}</a>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
<a href="{{ url('core:register') }}">{% trans %}Create account{% endtrans %}</a>
|
<a href="{{ url('core:register') }}">{% trans %}Create account{% endtrans %}</a>
|
||||||
</p>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,17 @@
|
|||||||
<form action="{{ url('core:register') }}" method="post">
|
<form action="{{ url('core:register') }}" method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{% render_honeypot_field %}
|
{% render_honeypot_field %}
|
||||||
{{ form.as_p() }}
|
{% for field in form %}
|
||||||
<input type="submit" value="{% trans %}Register{% endtrans %}" />
|
{% if field.name not in ["cgu_approved", "captcha"] %}
|
||||||
|
<div>{{ field.as_field_group() }}</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
<div class="captcha">{{ form.captcha.as_field_group() }}</div>
|
||||||
|
<fieldset>
|
||||||
|
{{ form.cgu_approved.errors }}
|
||||||
|
{{ form.cgu_approved }}
|
||||||
|
{{ form.cgu_approved.label_tag() }}
|
||||||
|
</fieldset>
|
||||||
|
<input type="submit" class="btn btn-blue" value="{% trans %}Register{% endtrans %}" />
|
||||||
</form>
|
</form>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
import pytest
|
|
||||||
from django.contrib.auth.models import Permission
|
|
||||||
from model_bakery import baker
|
|
||||||
|
|
||||||
from core.models import Group, User
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
|
||||||
def test_with_perm():
|
|
||||||
"""Test that `SithModelBackend.with_perm` works as intended."""
|
|
||||||
perms = baker.make(Permission, _quantity=4)
|
|
||||||
groups = baker.make(Group, _quantity=2)
|
|
||||||
groups[0].permissions.set(perms[0:2])
|
|
||||||
groups[1].permissions.set(perms[2:4])
|
|
||||||
users = [
|
|
||||||
baker.make(User),
|
|
||||||
baker.make(User, groups=[groups[0]]),
|
|
||||||
baker.make(User, groups=[groups[1]]),
|
|
||||||
baker.make(User, user_permissions=[perms[0]]),
|
|
||||||
baker.make(User, user_permissions=[perms[2]]),
|
|
||||||
baker.make(User, groups=[groups[1]], user_permissions=[perms[0]]),
|
|
||||||
]
|
|
||||||
|
|
||||||
expected = [users[1], users[3], users[5]]
|
|
||||||
assert list(User.objects.with_perm(perms[0])) == expected
|
|
||||||
str_repr = f"{perms[0].content_type.app_label}.{perms[0].codename}"
|
|
||||||
assert list(User.objects.with_perm(str_repr)) == expected
|
|
||||||
@@ -55,6 +55,7 @@ class TestUserRegistration:
|
|||||||
"password2": "plop",
|
"password2": "plop",
|
||||||
"captcha_0": "dummy-value",
|
"captcha_0": "dummy-value",
|
||||||
"captcha_1": "PASSED",
|
"captcha_1": "PASSED",
|
||||||
|
"cgu_approved": True,
|
||||||
}
|
}
|
||||||
|
|
||||||
@pytest.fixture()
|
@pytest.fixture()
|
||||||
@@ -92,6 +93,7 @@ class TestUserRegistration:
|
|||||||
({"first_name": ""}, "Ce champ est obligatoire."),
|
({"first_name": ""}, "Ce champ est obligatoire."),
|
||||||
({"last_name": ""}, "Ce champ est obligatoire."),
|
({"last_name": ""}, "Ce champ est obligatoire."),
|
||||||
({"captcha_1": "WRONG_CAPTCHA"}, "CAPTCHA invalide"),
|
({"captcha_1": "WRONG_CAPTCHA"}, "CAPTCHA invalide"),
|
||||||
|
({"cgu_approved": ""}, "Ce champ est obligatoire."),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_register_user_form_fail(
|
def test_register_user_form_fail(
|
||||||
|
|||||||
+19
-1
@@ -42,6 +42,8 @@ from django.forms import (
|
|||||||
TextInput,
|
TextInput,
|
||||||
Widget,
|
Widget,
|
||||||
)
|
)
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.utils.safestring import mark_safe
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from phonenumber_field.widgets import RegionalPhoneNumberWidget
|
from phonenumber_field.widgets import RegionalPhoneNumberWidget
|
||||||
@@ -146,9 +148,25 @@ class RegisteringForm(UserCreationForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = User
|
model = User
|
||||||
fields = ("first_name", "last_name", "email")
|
fields = ("first_name", "last_name", "email", "cgu_approved")
|
||||||
field_classes = {"email": AntiSpamEmailField}
|
field_classes = {"email": AntiSpamEmailField}
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.fields["cgu_approved"].required = True
|
||||||
|
self.fields["cgu_approved"].label_suffix = ""
|
||||||
|
self.fields["cgu_approved"].label = mark_safe(
|
||||||
|
_(
|
||||||
|
"I have read and I approve the "
|
||||||
|
'<a href="%(url)s" target="_blank">End User License Agreement</a>'
|
||||||
|
)
|
||||||
|
% {
|
||||||
|
"url": reverse(
|
||||||
|
"core:download", kwargs={"file_id": settings.SITH_CGU_FILE_ID}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class UserProfileForm(forms.ModelForm):
|
class UserProfileForm(forms.ModelForm):
|
||||||
"""Form handling the user profile, managing the files"""
|
"""Form handling the user profile, managing the files"""
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ class Migration(migrations.Migration):
|
|||||||
model_name="permanency",
|
model_name="permanency",
|
||||||
name="end",
|
name="end",
|
||||||
field=models.DateTimeField(
|
field=models.DateTimeField(
|
||||||
db_index=True, verbose_name="end date", null=True
|
db_index=True, verbose_name="end date", null=True, blank=True
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -6,9 +6,7 @@ from django.db import migrations, models
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
dependencies = [
|
dependencies = [("counter", "0018_producttype_priority")]
|
||||||
("counter", "0018_producttype_priority"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AlterModelOptions(
|
migrations.AlterModelOptions(
|
||||||
|
|||||||
@@ -6,9 +6,7 @@ import counter.fields
|
|||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
dependencies = [
|
dependencies = [("counter", "0019_billinginfo")]
|
||||||
("counter", "0019_billinginfo"),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
|
|||||||
+6
-11
@@ -669,18 +669,13 @@ class Counter(models.Model):
|
|||||||
"""Update the barman activity to prevent timeout."""
|
"""Update the barman activity to prevent timeout."""
|
||||||
self.permanencies.filter(end=None).update(activity=timezone.now())
|
self.permanencies.filter(end=None).update(activity=timezone.now())
|
||||||
|
|
||||||
@cached_property
|
|
||||||
def can_refill(self) -> bool:
|
def can_refill(self) -> bool:
|
||||||
"""Show if the counter authorize the refilling with physic money.
|
"""Show if the counter authorize the refilling with physic money."""
|
||||||
|
if self.type != "BAR":
|
||||||
Refills are authorized if a user having the required permission
|
return False
|
||||||
is currently logged in.
|
# at least one of the barmen is in the AE board
|
||||||
"""
|
ae = Club.objects.get(id=settings.SITH_MAIN_CLUB_ID)
|
||||||
return self.type == "BAR" and (
|
return any(ae.get_membership_for(barman) for barman in self.barmen_list)
|
||||||
User.objects.with_perm("counter.add_refilling")
|
|
||||||
.filter(id__in=[u.id for u in self.barmen_list])
|
|
||||||
.exists()
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_top_barmen(self) -> QuerySet:
|
def get_top_barmen(self) -> QuerySet:
|
||||||
"""Return a QuerySet querying the office hours stats of all the barmen of all time
|
"""Return a QuerySet querying the office hours stats of all the barmen of all time
|
||||||
|
|||||||
@@ -108,13 +108,6 @@ class TestFullClickBase(TestCase):
|
|||||||
|
|
||||||
|
|
||||||
class TestRefilling(TestFullClickBase):
|
class TestRefilling(TestFullClickBase):
|
||||||
@classmethod
|
|
||||||
def setUpTestData(cls):
|
|
||||||
super().setUpTestData()
|
|
||||||
cls.board_admin.user_permissions.add(
|
|
||||||
Permission.objects.get(codename="add_refilling")
|
|
||||||
)
|
|
||||||
|
|
||||||
def login_in_bar(self, barmen: User | None = None):
|
def login_in_bar(self, barmen: User | None = None):
|
||||||
used_barman = barmen if barmen is not None else self.board_admin
|
used_barman = barmen if barmen is not None else self.board_admin
|
||||||
self.client.post(
|
self.client.post(
|
||||||
@@ -154,7 +147,7 @@ class TestRefilling(TestFullClickBase):
|
|||||||
assert self.updated_amount(self.customer) == 0
|
assert self.updated_amount(self.customer) == 0
|
||||||
|
|
||||||
def test_refilling_no_refer_fail(self):
|
def test_refilling_no_refer_fail(self):
|
||||||
"""Check that the refill fails if the HTTP_REFERER header is missing"""
|
"""Check that the refill fails is the HTTP_REFERER header is missing"""
|
||||||
|
|
||||||
def refill():
|
def refill():
|
||||||
return self.client.post(
|
return self.client.post(
|
||||||
|
|||||||
@@ -204,7 +204,7 @@ class CounterClick(
|
|||||||
res["student_card_fragment"] = StudentCardFormFragment.as_fragment()(
|
res["student_card_fragment"] = StudentCardFormFragment.as_fragment()(
|
||||||
self.request, customer=self.customer
|
self.request, customer=self.customer
|
||||||
)
|
)
|
||||||
if self.object.can_refill:
|
if self.object.can_refill():
|
||||||
res["refilling_fragment"] = RefillingCreateView.as_fragment()(
|
res["refilling_fragment"] = RefillingCreateView.as_fragment()(
|
||||||
self.request, customer=self.customer, counter=self.object
|
self.request, customer=self.customer, counter=self.object
|
||||||
)
|
)
|
||||||
@@ -250,7 +250,7 @@ class RefillingCreateView(FragmentMixin, CreateView):
|
|||||||
if not (
|
if not (
|
||||||
request.barmen
|
request.barmen
|
||||||
and request.barmen.issubset(self.counter.barmen_list)
|
and request.barmen.issubset(self.counter.barmen_list)
|
||||||
and self.counter.can_refill
|
and self.counter.can_refill()
|
||||||
):
|
):
|
||||||
raise PermissionDenied
|
raise PermissionDenied
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-09-09 07:21+0200\n"
|
"POT-Creation-Date: 2026-09-15 23:49+0200\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"
|
||||||
@@ -1835,6 +1835,10 @@ msgstr ""
|
|||||||
"Même si ce profil est caché, les utilisateurs sur cette liste pourront "
|
"Même si ce profil est caché, les utilisateurs sur cette liste pourront "
|
||||||
"toujours le voir."
|
"toujours le voir."
|
||||||
|
|
||||||
|
#: core/models.py
|
||||||
|
msgid "EULA approved"
|
||||||
|
msgstr "CGU approuvées"
|
||||||
|
|
||||||
#: 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à"
|
||||||
@@ -3137,6 +3141,15 @@ msgstr "Assurez-vous que cet horodatage est dans le futur"
|
|||||||
msgid "Username, email, or account number"
|
msgid "Username, email, or account number"
|
||||||
msgstr "Nom d'utilisateur, email, ou numéro de compte AE"
|
msgstr "Nom d'utilisateur, email, ou numéro de compte AE"
|
||||||
|
|
||||||
|
#: core/views/forms.py
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"I have read and I approve the <a href=\"%(url)s\" target=\"_blank\">End User "
|
||||||
|
"License Agreement</a>"
|
||||||
|
msgstr ""
|
||||||
|
"J'ai lu et j'approuve les <a href=\"%(url)s\" target=\"_blank\">Conditions "
|
||||||
|
"Générales d'utilisation</a>"
|
||||||
|
|
||||||
#: core/views/forms.py
|
#: core/views/forms.py
|
||||||
msgid ""
|
msgid ""
|
||||||
"Profile: you need to be visible on the picture, in order to be recognized "
|
"Profile: you need to be visible on the picture, in order to be recognized "
|
||||||
@@ -3778,8 +3791,8 @@ msgid "Emptied"
|
|||||||
msgstr "Coffre vidé"
|
msgstr "Coffre vidé"
|
||||||
|
|
||||||
#: counter/templates/counter/cash_summary_list.jinja counter/views/cash.py
|
#: counter/templates/counter/cash_summary_list.jinja counter/views/cash.py
|
||||||
|
#: pedagogy/templates/pedagogy/fragments/ue_comment_form.jinja
|
||||||
#: pedagogy/templates/pedagogy/moderation.jinja
|
#: pedagogy/templates/pedagogy/moderation.jinja
|
||||||
#: pedagogy/templates/pedagogy/ue_detail.jinja
|
|
||||||
#: trombi/templates/trombi/comment.jinja
|
#: trombi/templates/trombi/comment.jinja
|
||||||
#: trombi/templates/trombi/user_tools.jinja
|
#: trombi/templates/trombi/user_tools.jinja
|
||||||
msgid "Comment"
|
msgid "Comment"
|
||||||
|
|||||||
@@ -417,6 +417,8 @@ SITH_FORUM_PAGE_LENGTH = 30
|
|||||||
SITH_SAS_ROOT_DIR_ID = env.int("SITH_SAS_ROOT_DIR_ID", default=4)
|
SITH_SAS_ROOT_DIR_ID = env.int("SITH_SAS_ROOT_DIR_ID", default=4)
|
||||||
SITH_SAS_IMAGES_PER_PAGE = 60
|
SITH_SAS_IMAGES_PER_PAGE = 60
|
||||||
|
|
||||||
|
SITH_CGU_FILE_ID = env.int("SITH_CGU_FILE_ID", default=5)
|
||||||
|
|
||||||
SITH_PROFILE_DEPARTMENTS = [
|
SITH_PROFILE_DEPARTMENTS = [
|
||||||
("TC", _("TC")),
|
("TC", _("TC")),
|
||||||
("IMSI", _("IMSI")),
|
("IMSI", _("IMSI")),
|
||||||
|
|||||||
Reference in New Issue
Block a user