1
0
mirror of https://gitlab.crans.org/bde/nk20 synced 2025-07-23 01:06:47 +02:00

Compare commits

..

2 Commits

Author SHA1 Message Date
f6ad6197de ListViews et templates 2025-07-05 19:47:35 +02:00
6c7d86185a Models 2025-07-03 14:34:04 +02:00
37 changed files with 642 additions and 617 deletions

View File

@ -21,6 +21,3 @@ EMAIL_PASSWORD=CHANGE_ME
# Wiki configuration # Wiki configuration
WIKI_USER=NoteKfet2020 WIKI_USER=NoteKfet2020
WIKI_PASSWORD= WIKI_PASSWORD=
# OIDC
OIDC_RSA_PRIVATE_KEY=CHANGE_ME

View File

@ -8,7 +8,7 @@ variables:
GIT_SUBMODULE_STRATEGY: recursive GIT_SUBMODULE_STRATEGY: recursive
# Ubuntu 22.04 # Ubuntu 22.04
py310-django52: py310-django42:
stage: test stage: test
image: ubuntu:22.04 image: ubuntu:22.04
before_script: before_script:
@ -22,10 +22,10 @@ py310-django52:
python3-djangorestframework python3-django-oauth-toolkit python3-psycopg2 python3-pil python3-djangorestframework python3-django-oauth-toolkit python3-psycopg2 python3-pil
python3-babel python3-lockfile python3-pip python3-phonenumbers python3-memcache python3-babel python3-lockfile python3-pip python3-phonenumbers python3-memcache
python3-bs4 python3-setuptools tox texlive-xetex python3-bs4 python3-setuptools tox texlive-xetex
script: tox -e py310-django52 script: tox -e py310-django42
# Debian Bookworm # Debian Bookworm
py311-django52: py311-django42:
stage: test stage: test
image: debian:bookworm image: debian:bookworm
before_script: before_script:
@ -37,7 +37,7 @@ py311-django52:
python3-djangorestframework python3-django-oauth-toolkit python3-psycopg2 python3-pil python3-djangorestframework python3-django-oauth-toolkit python3-psycopg2 python3-pil
python3-babel python3-lockfile python3-pip python3-phonenumbers python3-memcache python3-babel python3-lockfile python3-pip python3-phonenumbers python3-memcache
python3-bs4 python3-setuptools tox texlive-xetex python3-bs4 python3-setuptools tox texlive-xetex
script: tox -e py311-django52 script: tox -e py311-django42
linters: linters:
stage: quality-assurance stage: quality-assurance

View File

@ -61,8 +61,8 @@ Bien que cela permette de créer une instance sur toutes les distributions,
6. (Optionnel) **Création d'une clé privée OpenID Connect** 6. (Optionnel) **Création d'une clé privée OpenID Connect**
Pour activer le support d'OpenID Connect, il faut générer une clé privée, par Pour activer le support d'OpenID Connect, il faut générer une clé privée, par
exemple avec openssl (`openssl genrsa -out oidc.key 4096`), et copier la clé dans .env dans le champ exemple avec openssl (`openssl genrsa -out oidc.key 4096`), et renseigner son
`OIDC_RSA_PRIVATE_KEY`. emplacement dans `OIDC_RSA_PRIVATE_KEY` (par défaut `/var/secrets/oidc.key`).
7. Enjoy : 7. Enjoy :
@ -237,8 +237,8 @@ Sinon vous pouvez suivre les étapes décrites ci-dessous.
7. **Création d'une clé privée OpenID Connect** 7. **Création d'une clé privée OpenID Connect**
Pour activer le support d'OpenID Connect, il faut générer une clé privée, par Pour activer le support d'OpenID Connect, il faut générer une clé privée, par
exemple avec openssl (`openssl genrsa -out oidc.key 4096`), et renseigner le champ exemple avec openssl (`openssl genrsa -out oidc.key 4096`), et renseigner son
`OIDC_RSA_PRIVATE_KEY` dans le .env (par défaut `/var/secrets/oidc.key`). emplacement dans `OIDC_RSA_PRIVATE_KEY` (par défaut `/var/secrets/oidc.key`).
8. *Enjoy \o/* 8. *Enjoy \o/*

0
apps/family/__init__.py Normal file
View File

11
apps/family/apps.py Normal file
View File

@ -0,0 +1,11 @@
# Copyright (C) 2018-2025 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from django.utils.translation import gettext_lazy as _
from django.apps import AppConfig
class FamilyConfig(AppConfig):
name = 'family'
verbose_name = _('family')

View File

@ -0,0 +1,85 @@
# Generated by Django 4.2.21 on 2025-07-04 19:05
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='ChallengeCategory',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, unique=True, verbose_name='name')),
],
options={
'verbose_name': 'challenge category',
'verbose_name_plural': 'challenge categories',
},
),
migrations.CreateModel(
name='Family',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, unique=True, verbose_name='name')),
('description', models.CharField(max_length=255, verbose_name='description')),
('score', models.PositiveIntegerField(verbose_name='score')),
('rank', models.PositiveIntegerField(verbose_name='rank')),
],
options={
'verbose_name': 'Family',
'verbose_name_plural': 'Families',
},
),
migrations.CreateModel(
name='Challenge',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, verbose_name='name')),
('description', models.CharField(max_length=255, verbose_name='description')),
('points', models.PositiveIntegerField(verbose_name='points')),
('obtained', models.PositiveIntegerField(verbose_name='obtained')),
('category', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='family.challengecategory', verbose_name='category')),
],
options={
'verbose_name': 'challenge',
'verbose_name_plural': 'challenges',
},
),
migrations.CreateModel(
name='Achievement',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('obtained_at', models.DateTimeField(default=django.utils.timezone.now, verbose_name='obtained at')),
('challenge', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='family.challenge')),
('family', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='family.family', verbose_name='family')),
],
options={
'verbose_name': 'achievement',
'verbose_name_plural': 'achievements',
},
),
migrations.CreateModel(
name='FamilyMembership',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('year', models.PositiveIntegerField(default=2025, verbose_name='year')),
('family', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='members', to='family.family', verbose_name='family')),
('user', models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, related_name='family_memberships', to=settings.AUTH_USER_MODEL, verbose_name='user')),
],
options={
'verbose_name': 'family membership',
'verbose_name_plural': 'family memberships',
'unique_together': {('user', 'year')},
},
),
]

View File

202
apps/family/models.py Normal file
View File

@ -0,0 +1,202 @@
# Copyright (C) 2018-2025 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from django.db import models, transaction
from django.utils import timezone
from django.contrib.auth.models import User
from django.utils.translation import gettext_lazy as _
class Family(models.Model):
name = models.CharField(
max_length=255,
verbose_name=_('name'),
unique=True
)
description = models.CharField(
max_length=255,
verbose_name=_('description')
)
score = models.PositiveIntegerField(
verbose_name=_('score')
)
rank = models.PositiveIntegerField(
verbose_name=_('rank'),
)
class Meta:
verbose_name = _('Family')
verbose_name_plural = _('Families')
def __str__(self):
return self.name
class FamilyMembership(models.Model):
user = models.OneToOneField(
User,
on_delete=models.PROTECT,
related_name=_('family_memberships'),
verbose_name=_('user'),
)
family = models.ForeignKey(
Family,
on_delete=models.PROTECT,
related_name=_('members'),
verbose_name=_('family'),
)
year = models.PositiveIntegerField(
verbose_name=_('year'),
default=timezone.now().year,
)
class Meta:
unique_together = ('user', 'year',)
verbose_name = _('family membership')
verbose_name_plural = _('family memberships')
def __str__(self):
return _('Family membership of {user} to {family}').format(user=self.user.username, family=self.family.name, )
class ChallengeCategory(models.Model):
name = models.CharField(
max_length=255,
verbose_name=_('name'),
unique=True,
)
class Meta:
verbose_name = _('challenge category')
verbose_name_plural = _('challenge categories')
def __str__(self):
return self.name
class Challenge(models.Model):
name = models.CharField(
max_length=255,
verbose_name=_('name'),
)
description = models.CharField(
max_length=255,
verbose_name=_('description'),
)
points = models.PositiveIntegerField(
verbose_name=_('points'),
)
category = models.ForeignKey(
ChallengeCategory,
verbose_name=_('category'),
on_delete=models.PROTECT
)
obtained = models.PositiveIntegerField(
verbose_name=_('obtained')
)
class Meta:
verbose_name = _('challenge')
verbose_name_plural = _('challenges')
def __str__(self):
return self.name
class Achievement(models.Model):
challenge = models.ForeignKey(
Challenge,
on_delete=models.PROTECT,
)
family = models.ForeignKey(
Family,
on_delete=models.PROTECT,
verbose_name=_('family'),
)
obtained_at = models.DateTimeField(
verbose_name=_('obtained at'),
default=timezone.now,
)
class Meta:
verbose_name = _('achievement')
verbose_name_plural = _('achievements')
def __str__(self):
return _('Challenge {challenge} carried out by Family {family}').format(challenge=self.challenge.name, family=self.family.name, )
@classmethod
def update_ranking(cls, *args, **kwargs):
"""
Update ranking when adding or removing points
"""
family_set = cls.objects.select_for_update().all().order_by("-score")
for i in range(family_set.count()):
if i == 0 or family_set[i].score != family_set[i - 1].score:
new_rank = i + 1
family = family_set[i]
family.rank = new_rank
family._force_save = True
family.save()
@transaction.atomic
def save(self, *args, **kwargs):
"""
When saving, also grants points to the family
"""
self.family = Family.objects.select_for_update().get(pk=self.family_id)
self.challenge = Challenge.objects.select_for_update().get(pk=self.challenge_id)
challenge_points = self.challenge.points
is_new = self.pk is None
super.save(*args, **kwargs)
# Only grant points when getting a new achievement
if is_new:
self.family.refresh_from_db()
self.family.score += challenge_points
self.family._force_save = True
self.family.save()
self.challenge.refresh_from_db()
self.challenge.obtained += 1
self.challenge._force_save = True
self.challenge.save()
self.__class__.update_ranking()
@transaction.atomic
def delete(self, *args, **kwargs):
"""
When deleting, also removes points from the family
"""
# Get the family and challenge before deletion
self.family = Family.objects.select_for_update().get(pk=self.family_id)
challenge_points = self.challenge.points
# Delete the achievement
super().delete(*args, **kwargs)
# Remove points from the family
self.family.refresh_from_db()
self.family.score -= challenge_points
self.family._force_save = True
self.family.save()
self.challenge.refresh_from_db()
self.challenge.obtained -= 1
self.challenge._force_save = True
self.challenge.save()
self.__class__.update_ranking()

40
apps/family/tables.py Normal file
View File

@ -0,0 +1,40 @@
# Copyright (C) 2018-2025 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
import django_tables2 as tables
from django_tables2 import A
from .models import Family, Challenge
class FamilyTable(tables.Table):
"""
List all families
"""
name = tables.LinkColumn(
"family:family_detail",
args=[A("pk")],
)
class Meta:
attrs = {
'class': 'table table-condensed table-striped table-hover'
}
model = Family
template_name = 'django_tables2/bootstrap4.html'
fields = ('name', 'score', 'rank',)
order_by = ('rank',)
class ChallengeTable(tables.Table):
"""
List all challenges
"""
class Meta:
attrs = {
'class': 'table table-condensed table-striped table-hover'
}
order_by = ('id',)
model = Challenge
template_name = 'django_tables2/bootstrap4.html'
fields = ('name', 'points', 'category',)

View File

@ -0,0 +1,30 @@
{% extends "base.html" %}
{% comment %}
Copyright (C) 2018-2025 by BDE ENS Paris-Saclay
SPDX-License-Identifier: GPL-3.0-or-later
{% endcomment %}
{% load render_table from django_tables2 %}
{% load i18n %}
{% block content %}
<div class="row">
<div class="col-xl-12">
<div class="btn-group btn-group-toggle" style="width: 100%; padding: 0 0 2em 0">
<a href="{% url "family:family_list" %}" class="btn btn-sm btn-outline-primary">
{% trans "Families" %}
</a>
<a href="#" class="btn btn-sm btn-outline-primary active">
{% trans "Challenges" %}
</a>
</div>
</div>
</div>
<<div class="card bg-white mb-3">
<h3 class="card-header text-center">
{{ title }}
</h3>
{% render_table table %}
</div>
{% endblock %}

View File

@ -0,0 +1,30 @@
{% extends "base.html" %}
{% comment %}
Copyright (C) 2018-2025 by BDE ENS Paris-Saclay
SPDX-License-Identifier: GPL-3.0-or-later
{% endcomment %}
{% load render_table from django_tables2 %}
{% load i18n %}
{% block content %}
<div class="row">
<div class="col-xl-12">
<div class="btn-group btn-group-toggle" style="width: 100%; padding: 0 0 2em 0">
<a href="#" class="btn btn-sm btn-outline-primary active">
{% trans "Families" %}
</a>
<a href="{% url "family:challenge_list" %}" class="btn btn-sm btn-outline-primary">
{% trans "Challenges" %}
</a>
</div>
</div>
</div>
<<div class="card bg-white mb-3">
<h3 class="card-header text-center">
{{ title }}
</h3>
{% render_table table %}
</div>
{% endblock %}

12
apps/family/urls.py Normal file
View File

@ -0,0 +1,12 @@
# Copyright (C) 2018-2025 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from django.urls import path
from .views import FamilyListView, ChallengeListView
app_name = 'family'
urlpatterns = [
path('list/', FamilyListView.as_view(), name="family_list"),
path('challenge/list/', ChallengeListView.as_view(), name="challenge_list"),
]

63
apps/family/views.py Normal file
View File

@ -0,0 +1,63 @@
# Copyright (C) 2018-2025 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import DetailView, UpdateView
from django.utils.translation import gettext_lazy as _
from django_tables2 import SingleTableView
from permission.views import ProtectQuerysetMixin, ProtectedCreateView
from .models import Family, Challenge
from .tables import FamilyTable, ChallengeTable
class FamilyCreateView(ProtectQuerysetMixin, ProtectedCreateView):
"""
Create family
"""
model = Family
extra_context = {"title": _('Create family')}
def get_sample_object(self):
return Family(
name="",
description="Sample family",
score=0,
rank=0,
)
class FamilyListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
"""
List existing Families
"""
model = Family
table_class = FamilyTable
extra_context = {"title": _('Families list')}
class FamilyDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
"""
Display details of a family
"""
model = Family
context_object_name = "family"
extra_context = {"title": _('Family detail')}
class FamilyUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
"""
Update the information of a family.
"""
model = Family
context_object_name = "family"
extra_context = {"title": _('Update family')}
class ChallengeListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
"""
List all challenges
"""
model = Challenge
table_class = ChallengeTable
extra_context = {"title": _('Challenges list')}

View File

@ -7,52 +7,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
{% load i18n %} {% load i18n %}
{% block content %} {% block content %}
<div class="card bg-light"> {{ block.super }}
<h3 class="card-header text-center">
{{ title }}
</h3>
<div class="card-body">
<style>
input[type=number]::-webkit-inner-spin-button,
input[type=number]::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;
}
input[type=number] {
appearance: textfield;
padding: 6px;
border: 1px solid #ccc;
border-radius: 4px;
width: 100px;
}
</style>
<div class="d-flex align-items-center" style="max-width: 300px;">
<form method="get" action="{% url 'food:redirect_view' %}" class="d-flex w-100">
<input type="number" name="slug" placeholder="QR-code" required class="form-control form-control-sm" style="max-width: 120px;">
<button type="submit" class="btn btn-sm btn-primary">{% trans "View food" %}</button>
</form>
</div>
</div>
<div class="card-body">
<input id="searchbar" type="text" class="form-control"
placeholder="{% trans "Search by attribute such as name..." %}">
</div>
{% block extra_inside_card %}
{% endblock %}
<div id="dynamic-table">
{% if table.data %}
{% render_table table %}
{% else %}
<div class="card-body">
<div class="alert alert-warning">
{% trans "There is no results." %}
</div>
</div>
{% endif %}
</div>
</div>
<br> <br>
<div class="card bg-light mb-3"> <div class="card bg-light mb-3">
<h3 class="card-header text-center"> <h3 class="card-header text-center">
@ -113,20 +68,4 @@ SPDX-License-Identifier: GPL-3.0-or-later
{% endfor %} {% endfor %}
{% endif %} {% endif %}
</div> </div>
{% endblock %}
<script>
document.addEventListener('DOMContentLoaded', function() {
document.getElementById('goButton').addEventListener('click', function(event) {
event.preventDefault();
const slug = document.getElementById('slugInput').value;
if (slug && !isNaN(slug)) {
window.location.href = `/food/${slug}/`;
} else {
alert("Veuillez entrer un nombre valide.");
}
});
});
</script>
{% endblock %}

View File

@ -18,5 +18,4 @@ urlpatterns = [
path('detail/basic/<int:pk>', views.BasicFoodDetailView.as_view(), name='basicfood_view'), path('detail/basic/<int:pk>', views.BasicFoodDetailView.as_view(), name='basicfood_view'),
path('detail/transformed/<int:pk>', views.TransformedFoodDetailView.as_view(), name='transformedfood_view'), path('detail/transformed/<int:pk>', views.TransformedFoodDetailView.as_view(), name='transformedfood_view'),
path('add/ingredient/<int:pk>', views.AddIngredientView.as_view(), name='add_ingredient'), path('add/ingredient/<int:pk>', views.AddIngredientView.as_view(), name='add_ingredient'),
path('redirect/', views.QRCodeRedirectView.as_view(), name='redirect_view'),
] ]

View File

@ -10,7 +10,6 @@ from django.db.models import Q
from django.http import HttpResponseRedirect, Http404 from django.http import HttpResponseRedirect, Http404
from django.views.generic import DetailView, UpdateView, CreateView from django.views.generic import DetailView, UpdateView, CreateView
from django.views.generic.list import ListView from django.views.generic.list import ListView
from django.views.generic.base import RedirectView
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.utils import timezone from django.utils import timezone
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
@ -64,8 +63,7 @@ class FoodListView(ProtectQuerysetMixin, LoginRequiredMixin, MultiTableMixin, Li
valid_regex = is_regex(pattern) valid_regex = is_regex(pattern)
suffix = '__iregex' if valid_regex else '__istartswith' suffix = '__iregex' if valid_regex else '__istartswith'
prefix = '^' if valid_regex else '' prefix = '^' if valid_regex else ''
qs = qs.filter(Q(**{f'name{suffix}': prefix + pattern}) qs = qs.filter(Q(**{f'name{suffix}': prefix + pattern}))
| Q(**{f'owner__name{suffix}': prefix + pattern}))
else: else:
qs = qs.none() qs = qs.none()
search_table = qs.filter(PermissionBackend.filter_queryset(self.request, Food, 'view')) search_table = qs.filter(PermissionBackend.filter_queryset(self.request, Food, 'view'))
@ -508,14 +506,3 @@ class TransformedFoodDetailView(FoodDetailView):
if Food.objects.filter(pk=kwargs['pk']).count() == 1: if Food.objects.filter(pk=kwargs['pk']).count() == 1:
kwargs['stop_redirect'] = (Food.objects.get(pk=kwargs['pk']).polymorphic_ctype.model == 'transformedfood') kwargs['stop_redirect'] = (Food.objects.get(pk=kwargs['pk']).polymorphic_ctype.model == 'transformedfood')
return super().get(*args, **kwargs) return super().get(*args, **kwargs)
class QRCodeRedirectView(RedirectView):
"""
Redirects to the QR code creation page from Food List
"""
def get_redirect_url(self, *args, **kwargs):
slug = self.request.GET.get('slug')
if slug:
return reverse_lazy('food:qrcode_create', kwargs={'slug': slug})
return reverse_lazy('food:list')

View File

@ -44,7 +44,7 @@ class TemplateLoggedInTests(TestCase):
self.assertRedirects(response, settings.LOGIN_REDIRECT_URL, 302, 302) self.assertRedirects(response, settings.LOGIN_REDIRECT_URL, 302, 302)
def test_logout(self): def test_logout(self):
response = self.client.post(reverse("logout")) response = self.client.get(reverse("logout"))
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
def test_admin_index(self): def test_admin_index(self):

View File

@ -13,7 +13,7 @@ def register_note_urls(router, path):
router.register(path + '/note', NotePolymorphicViewSet) router.register(path + '/note', NotePolymorphicViewSet)
router.register(path + '/alias', AliasViewSet) router.register(path + '/alias', AliasViewSet)
router.register(path + '/trust', TrustViewSet) router.register(path + '/trust', TrustViewSet)
router.register(path + '/consumer', ConsumerViewSet, basename='alias2') router.register(path + '/consumer', ConsumerViewSet)
router.register(path + '/transaction/category', TemplateCategoryViewSet) router.register(path + '/transaction/category', TemplateCategoryViewSet)
router.register(path + '/transaction/transaction', TransactionViewSet) router.register(path + '/transaction/transaction', TransactionViewSet)

View File

@ -1,10 +1,8 @@
# Copyright (C) 2018-2025 by BDE ENS Paris-Saclay # Copyright (C) 2018-2025 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from oauth2_provider.oauth2_validators import OAuth2Validator from oauth2_provider.oauth2_validators import OAuth2Validator
from oauth2_provider.scopes import BaseScopes from oauth2_provider.scopes import BaseScopes
from member.models import Club from member.models import Club
from note.models import Alias
from note_kfet.middlewares import get_current_request from note_kfet.middlewares import get_current_request
from .backends import PermissionBackend from .backends import PermissionBackend
@ -18,58 +16,26 @@ class PermissionScopes(BaseScopes):
and can be useful to make queries through the API with limited privileges. and can be useful to make queries through the API with limited privileges.
""" """
def get_all_scopes(self, **kwargs): def get_all_scopes(self):
scopes = {} return {f"{p.id}_{club.id}": f"{p.description} (club {club.name})"
if 'scopes' in kwargs: for p in Permission.objects.all() for club in Club.objects.all()}
for scope in kwargs['scopes']:
if scope == 'openid':
scopes['openid'] = "OpenID Connect"
else:
p = Permission.objects.get(id=scope.split('_')[0])
club = Club.objects.get(id=scope.split('_')[1])
scopes[scope] = f"{p.description} (club {club.name})"
return scopes
scopes = {f"{p.id}_{club.id}": f"{p.description} (club {club.name})"
for p in Permission.objects.all() for club in Club.objects.all()}
scopes['openid'] = "OpenID Connect"
return scopes
def get_available_scopes(self, application=None, request=None, *args, **kwargs): def get_available_scopes(self, application=None, request=None, *args, **kwargs):
if not application: if not application:
return [] return []
scopes = [f"{p.id}_{p.membership.club.id}" return [f"{p.id}_{p.membership.club.id}"
for t in Permission.PERMISSION_TYPES for t in Permission.PERMISSION_TYPES
for p in PermissionBackend.get_raw_permissions(get_current_request(), t[0])] for p in PermissionBackend.get_raw_permissions(get_current_request(), t[0])]
scopes.append('openid')
return scopes
def get_default_scopes(self, application=None, request=None, *args, **kwargs): def get_default_scopes(self, application=None, request=None, *args, **kwargs):
if not application: if not application:
return [] return []
scopes = [f"{p.id}_{p.membership.club.id}" return [f"{p.id}_{p.membership.club.id}"
for p in PermissionBackend.get_raw_permissions(get_current_request(), 'view')] for p in PermissionBackend.get_raw_permissions(get_current_request(), 'view')]
scopes.append('openid')
return scopes
class PermissionOAuth2Validator(OAuth2Validator): class PermissionOAuth2Validator(OAuth2Validator):
oidc_claim_scope = OAuth2Validator.oidc_claim_scope oidc_claim_scope = None # fix breaking change of django-oauth-toolkit 2.0.0
oidc_claim_scope.update({"name": 'openid',
"normalized_name": 'openid',
"email": 'openid',
})
def get_additional_claims(self, request):
return {
"name": request.user.username,
"normalized_name": Alias.normalize(request.user.username),
"email": request.user.email,
}
def get_discovery_claims(self, request):
claims = super().get_discovery_claims(self)
return claims + ["name", "normalized_name", "email"]
def validate_scopes(self, client_id, scopes, client, request, *args, **kwargs): def validate_scopes(self, client_id, scopes, client, request, *args, **kwargs):
""" """
@ -88,8 +54,6 @@ class PermissionOAuth2Validator(OAuth2Validator):
if scope in scopes: if scope in scopes:
valid_scopes.add(scope) valid_scopes.add(scope)
if 'openid' in scopes:
valid_scopes.add('openid')
request.scopes = valid_scopes request.scopes = valid_scopes
return valid_scopes return valid_scopes

View File

@ -13,14 +13,12 @@ EXCLUDED = [
'cas_server.serviceticket', 'cas_server.serviceticket',
'cas_server.user', 'cas_server.user',
'cas_server.userattributes', 'cas_server.userattributes',
'constance.constance',
'contenttypes.contenttype', 'contenttypes.contenttype',
'logs.changelog', 'logs.changelog',
'migrations.migration', 'migrations.migration',
'oauth2_provider.accesstoken', 'oauth2_provider.accesstoken',
'oauth2_provider.grant', 'oauth2_provider.grant',
'oauth2_provider.refreshtoken', 'oauth2_provider.refreshtoken',
'oauth2_provider.idtoken',
'sessions.session', 'sessions.session',
] ]

View File

@ -164,24 +164,14 @@ class ScopesView(LoginRequiredMixin, TemplateView):
from oauth2_provider.models import Application from oauth2_provider.models import Application
from .scopes import PermissionScopes from .scopes import PermissionScopes
oidc = False scopes = PermissionScopes()
context["scopes"] = {} context["scopes"] = {}
all_scopes = scopes.get_all_scopes()
for app in Application.objects.filter(user=self.request.user).all(): for app in Application.objects.filter(user=self.request.user).all():
available_scopes = PermissionScopes().get_available_scopes(app) available_scopes = scopes.get_available_scopes(app)
context["scopes"][app] = OrderedDict() context["scopes"][app] = OrderedDict()
all_scopes = PermissionScopes().get_all_scopes(scopes=available_scopes) items = [(k, v) for (k, v) in all_scopes.items() if k in available_scopes]
scopes = {}
for scope in available_scopes:
scopes[scope] = all_scopes[scope]
# remove OIDC scope for sort
if 'openid' in scopes:
del scopes['openid']
oidc = True
items = [(k, v) for (k, v) in scopes.items()]
items.sort(key=lambda x: (int(x[0].split("_")[1]), int(x[0].split("_")[0]))) items.sort(key=lambda x: (int(x[0].split("_")[1]), int(x[0].split("_")[0])))
# add oidc if necessary
if oidc:
items.append(('openid', PermissionScopes().get_all_scopes(scopes=['openid'])['openid']))
for k, v in items: for k, v in items:
context["scopes"][app][k] = v context["scopes"][app][k] = v

View File

@ -5,7 +5,7 @@ from bootstrap_datepicker_plus.widgets import DatePickerInput
from django import forms from django import forms
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.db.models import Q from django.db.models import Q
from django.forms import CheckboxSelectMultiple, RadioSelect from django.forms import CheckboxSelectMultiple
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from note.models import NoteSpecial, NoteUser from note.models import NoteSpecial, NoteUser
from note_kfet.inputs import AmountInput, Autocomplete, ColorWidget from note_kfet.inputs import AmountInput, Autocomplete, ColorWidget
@ -140,19 +140,6 @@ class WEIMembershipForm(forms.ModelForm):
required=False, required=False,
) )
def __init__(self, *args, wei=None, **kwargs):
super().__init__(*args, **kwargs)
if 'bus' in self.fields:
if wei is not None:
self.fields['bus'].queryset = Bus.objects.filter(wei=wei)
else:
self.fields['bus'].queryset = Bus.objects.none()
if 'team' in self.fields:
if wei is not None:
self.fields['team'].queryset = BusTeam.objects.filter(bus__wei=wei)
else:
self.fields['team'].queryset = BusTeam.objects.none()
def clean(self): def clean(self):
cleaned_data = super().clean() cleaned_data = super().clean()
if 'team' in cleaned_data and cleaned_data["team"] is not None \ if 'team' in cleaned_data and cleaned_data["team"] is not None \
@ -164,8 +151,21 @@ class WEIMembershipForm(forms.ModelForm):
model = WEIMembership model = WEIMembership
fields = ('roles', 'bus', 'team',) fields = ('roles', 'bus', 'team',)
widgets = { widgets = {
"bus": RadioSelect(), "bus": Autocomplete(
"team": RadioSelect(), Bus,
attrs={
'api_url': '/api/wei/bus/',
'placeholder': 'Bus ...',
}
),
"team": Autocomplete(
BusTeam,
attrs={
'api_url': '/api/wei/team/',
'placeholder': 'Équipe ...',
},
resetable=True,
),
} }

View File

@ -210,27 +210,4 @@ SPDX-License-Identifier: GPL-3.0-or-later
} }
} }
</script> </script>
<script>
$(document).ready(function () {
function refreshTeams() {
let buses = [];
$("input[name='bus']:checked").each(function (ignored) {
buses.push($(this).parent().text().trim());
});
console.log(buses);
$("input[name='team']").each(function () {
let label = $(this).parent();
$(this).parent().addClass('d-none');
buses.forEach(function (bus) {
if (label.text().includes(bus))
label.removeClass('d-none');
});
});
}
$("input[name='bus']").change(refreshTeams);
refreshTeams();
});
</script>
{% endblock %} {% endblock %}

View File

@ -788,8 +788,7 @@ class WEIUpdateRegistrationView(ProtectQuerysetMixin, LoginRequiredMixin, Update
return form return form
def get_membership_form(self, data=None, instance=None): def get_membership_form(self, data=None, instance=None):
registration = self.get_object() membership_form = WEIMembershipForm(data if data else None, instance=instance)
membership_form = WEIMembershipForm(data if data else None, instance=instance, wei=registration.wei)
del membership_form.fields["credit_type"] del membership_form.fields["credit_type"]
del membership_form.fields["credit_amount"] del membership_form.fields["credit_amount"]
del membership_form.fields["first_name"] del membership_form.fields["first_name"]
@ -970,13 +969,6 @@ class WEIValidateRegistrationView(ProtectQuerysetMixin, ProtectedCreateView):
return WEIMembership1AForm return WEIMembership1AForm
return WEIMembershipForm return WEIMembershipForm
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
registration = WEIRegistration.objects.get(pk=self.kwargs["pk"])
wei = registration.wei
kwargs['wei'] = wei
return kwargs
def get_form(self, form_class=None): def get_form(self, form_class=None):
form = super().get_form(form_class) form = super().get_form(form_class)
registration = WEIRegistration.objects.get(pk=self.kwargs["pk"]) registration = WEIRegistration.objects.get(pk=self.kwargs["pk"])

View File

@ -136,7 +136,7 @@ de diffusion utiles.
Faîtes attention, donc où la sortie est stockée. Faîtes attention, donc où la sortie est stockée.
Il prend 4 options : Il prend 2 options :
* ``--type``, qui prend en argument ``members`` (défaut), ``clubs``, ``events``, ``art``, * ``--type``, qui prend en argument ``members`` (défaut), ``clubs``, ``events``, ``art``,
``sport``, qui permet respectivement de sortir la liste des adresses mails des adhérent⋅es ``sport``, qui permet respectivement de sortir la liste des adresses mails des adhérent⋅es
@ -149,10 +149,7 @@ Il prend 4 options :
pour la ML Adhérents, pour exporter les mails des adhérents au BDE pendant n'importe pour la ML Adhérents, pour exporter les mails des adhérents au BDE pendant n'importe
laquelle des ``n+1`` dernières années. laquelle des ``n+1`` dernières années.
* ``--email``, qui prend en argument une chaine de caractère contenant une adresse email. Le script sort sur la sortie standard la liste des adresses mails à inscrire.
Si aucun email n'est renseigné, le script sort sur la sortie standard la liste des adresses mails à inscrire.
Dans le cas contraire, la liste est envoyée à l'adresse passée en argument.
Attention : il y a parfois certains cas particuliers à prendre en compte, il n'est Attention : il y a parfois certains cas particuliers à prendre en compte, il n'est
malheureusement pas aussi simple que de simplement supposer que ces listes sont exhaustives. malheureusement pas aussi simple que de simplement supposer que ces listes sont exhaustives.

View File

@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: \n" "Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-07-11 16:10+0200\n" "POT-Creation-Date: 2025-06-27 19:15+0200\n"
"PO-Revision-Date: 2022-04-11 22:05+0200\n" "PO-Revision-Date: 2022-04-11 22:05+0200\n"
"Last-Translator: bleizi <bleizi@crans.org>\n" "Last-Translator: bleizi <bleizi@crans.org>\n"
"Language-Team: French <http://translate.ynerant.fr/projects/nk20/nk20/fr/>\n" "Language-Team: French <http://translate.ynerant.fr/projects/nk20/nk20/fr/>\n"
@ -357,7 +357,7 @@ msgstr "Détails de l'activité"
#: apps/note/models/transactions.py:261 #: apps/note/models/transactions.py:261
#: apps/note/templates/note/transaction_form.html:17 #: apps/note/templates/note/transaction_form.html:17
#: apps/note/templates/note/transaction_form.html:152 #: apps/note/templates/note/transaction_form.html:152
#: note_kfet/templates/base.html:79 #: note_kfet/templates/base.html:78
msgid "Transfer" msgid "Transfer"
msgstr "Virement" msgstr "Virement"
@ -474,7 +474,7 @@ msgstr "Inviter"
msgid "Create new activity" msgid "Create new activity"
msgstr "Créer une nouvelle activité" msgstr "Créer une nouvelle activité"
#: apps/activity/views.py:71 note_kfet/templates/base.html:97 #: apps/activity/views.py:71 note_kfet/templates/base.html:96
msgid "Activities" msgid "Activities"
msgstr "Activités" msgstr "Activités"
@ -563,7 +563,7 @@ msgstr "Nom"
#, fuzzy #, fuzzy
#| msgid "QR-code number" #| msgid "QR-code number"
msgid "QR code number" msgid "QR code number"
msgstr "Numéro de QR-code" msgstr "numéro de QR-code"
#: apps/food/models.py:23 #: apps/food/models.py:23
msgid "Allergen" msgid "Allergen"
@ -597,7 +597,7 @@ msgstr "est prêt"
msgid "order" msgid "order"
msgstr "consigne" msgstr "consigne"
#: apps/food/models.py:107 apps/food/views.py:35 #: apps/food/models.py:107 apps/food/views.py:34
#: note_kfet/templates/base.html:72 #: note_kfet/templates/base.html:72
msgid "Food" msgid "Food"
msgstr "Bouffe" msgstr "Bouffe"
@ -657,75 +657,61 @@ msgstr "QR-codes"
#: apps/food/models.py:286 #: apps/food/models.py:286
#: apps/food/templates/food/transformedfood_update.html:24 #: apps/food/templates/food/transformedfood_update.html:24
msgid "QR-code number" msgid "QR-code number"
msgstr "Numéro de QR-code" msgstr "numéro de QR-code"
#: apps/food/templates/food/food_detail.html:22 #: apps/food/templates/food/food_detail.html:19
msgid "Contained in" msgid "Contained in"
msgstr "Contenu dans" msgstr "Contenu dans"
#: apps/food/templates/food/food_detail.html:29 #: apps/food/templates/food/food_detail.html:26
msgid "Contain" msgid "Contain"
msgstr "Contient" msgstr "Contient"
#: apps/food/templates/food/food_detail.html:38 #: apps/food/templates/food/food_detail.html:35
msgid "Update" msgid "Update"
msgstr "Modifier" msgstr "Modifier"
#: apps/food/templates/food/food_detail.html:43 #: apps/food/templates/food/food_detail.html:40
msgid "Add to a meal" msgid "Add to a meal"
msgstr "Ajouter à un plat" msgstr "Ajouter à un plat"
#: apps/food/templates/food/food_detail.html:48 #: apps/food/templates/food/food_detail.html:45
msgid "Manage ingredients" msgid "Manage ingredients"
msgstr "Gérer les ingrédients" msgstr "Gérer les ingrédients"
#: apps/food/templates/food/food_detail.html:52 #: apps/food/templates/food/food_detail.html:49
msgid "Return to the food list" msgid "Return to the food list"
msgstr "Retour à la liste de nourriture" msgstr "Retour à la liste de nourriture"
#: apps/food/templates/food/food_list.html:32 #: apps/food/templates/food/food_list.html:14
msgid "View food"
msgstr "Voir l'aliment"
#: apps/food/templates/food/food_list.html:37
#: note_kfet/templates/base_search.html:15
msgid "Search by attribute such as name..."
msgstr "Chercher par un attribut tel que le nom..."
#: apps/food/templates/food/food_list.html:49
#: note_kfet/templates/base_search.html:23
msgid "There is no results."
msgstr "Il n'y a pas de résultat."
#: apps/food/templates/food/food_list.html:58
msgid "Meal served" msgid "Meal served"
msgstr "Plat servis" msgstr "Plat servis"
#: apps/food/templates/food/food_list.html:63 #: apps/food/templates/food/food_list.html:19
msgid "New meal" msgid "New meal"
msgstr "Nouveau plat" msgstr "Nouveau plat"
#: apps/food/templates/food/food_list.html:72 #: apps/food/templates/food/food_list.html:28
msgid "There is no meal served." msgid "There is no meal served."
msgstr "Il n'y a pas de plat servi." msgstr "Il n'y a pas de plat servi."
#: apps/food/templates/food/food_list.html:79 #: apps/food/templates/food/food_list.html:35
msgid "Free food" msgid "Free food"
msgstr "Open" msgstr "Open"
#: apps/food/templates/food/food_list.html:86 #: apps/food/templates/food/food_list.html:42
msgid "There is no free food." msgid "There is no free food."
msgstr "Il n'y a pas de bouffe en open" msgstr "Il n'y a pas de bouffe en open"
#: apps/food/templates/food/food_list.html:94 #: apps/food/templates/food/food_list.html:50
msgid "Food of your clubs" msgid "Food of your clubs"
msgstr "Bouffe de tes clubs" msgstr "Bouffe de tes clubs"
#: apps/food/templates/food/food_list.html:100 #: apps/food/templates/food/food_list.html:56
msgid "Food of club" msgid "Food of club"
msgstr "Bouffe du club" msgstr "Bouffe du club"
#: apps/food/templates/food/food_list.html:107 #: apps/food/templates/food/food_list.html:63
msgid "Yours club has not food yet." msgid "Yours club has not food yet."
msgstr "Ton club n'a pas de bouffe pour l'instant" msgstr "Ton club n'a pas de bouffe pour l'instant"
@ -799,49 +785,49 @@ msgstr "semaines"
msgid "and" msgid "and"
msgstr "et" msgstr "et"
#: apps/food/views.py:120 #: apps/food/views.py:118
msgid "Add a new QRCode" msgid "Add a new QRCode"
msgstr "Ajouter un nouveau QR-code" msgstr "Ajouter un nouveau QR-code"
#: apps/food/views.py:169 #: apps/food/views.py:167
msgid "Add an aliment" msgid "Add an aliment"
msgstr "Ajouter un nouvel aliment" msgstr "Ajouter un nouvel aliment"
#: apps/food/views.py:228 #: apps/food/views.py:235
msgid "Add a meal" msgid "Add a meal"
msgstr "Ajouter un plat" msgstr "Ajouter un plat"
#: apps/food/views.py:259 #: apps/food/views.py:275
msgid "Manage ingredients of:" msgid "Manage ingredients of:"
msgstr "Gestion des ingrédienrs de :" msgstr "Gestion des ingrédienrs de :"
#: apps/food/views.py:273 apps/food/views.py:281 #: apps/food/views.py:289 apps/food/views.py:297
#, python-brace-format #, python-brace-format
msgid "Fully used in {meal}" msgid "Fully used in {meal}"
msgstr "Aliment entièrement utilisé dans : {meal}" msgstr "Aliment entièrement utilisé dans : {meal}"
#: apps/food/views.py:320 #: apps/food/views.py:344
msgid "Add the ingredient:" msgid "Add the ingredient:"
msgstr "Ajouter l'ingrédient" msgstr "Ajouter l'ingrédient"
#: apps/food/views.py:346 #: apps/food/views.py:370
#, python-brace-format #, python-brace-format
msgid "Food fully used in : {meal.name}" msgid "Food fully used in : {meal.name}"
msgstr "Aliment entièrement utilisé dans : {meal.name}" msgstr "Aliment entièrement utilisé dans : {meal.name}"
#: apps/food/views.py:365 #: apps/food/views.py:389
msgid "Update an aliment" msgid "Update an aliment"
msgstr "Modifier un aliment" msgstr "Modifier un aliment"
#: apps/food/views.py:413 #: apps/food/views.py:437
msgid "Details of:" msgid "Details of:"
msgstr "Détails de :" msgstr "Détails de :"
#: apps/food/views.py:423 apps/treasury/tables.py:149 #: apps/food/views.py:447 apps/treasury/tables.py:149
msgid "Yes" msgid "Yes"
msgstr "Oui" msgstr "Oui"
#: apps/food/views.py:425 apps/member/models.py:99 apps/treasury/tables.py:149 #: apps/food/views.py:449 apps/member/models.py:99 apps/treasury/tables.py:149
msgid "No" msgid "No"
msgstr "Non" msgstr "Non"
@ -1976,8 +1962,8 @@ msgstr ""
"mode de paiement et un⋅e utilisateur⋅rice ou un club" "mode de paiement et un⋅e utilisateur⋅rice ou un club"
#: apps/note/models/transactions.py:357 apps/note/models/transactions.py:360 #: apps/note/models/transactions.py:357 apps/note/models/transactions.py:360
#: apps/note/models/transactions.py:363 apps/wei/views.py:1105 #: apps/note/models/transactions.py:363 apps/wei/views.py:1097
#: apps/wei/views.py:1109 #: apps/wei/views.py:1101
msgid "This field is required." msgid "This field is required."
msgstr "Ce champ est requis." msgstr "Ce champ est requis."
@ -2079,8 +2065,6 @@ msgstr "Historique des transactions récentes"
#: apps/note/templates/note/mails/weekly_report.txt:32 #: apps/note/templates/note/mails/weekly_report.txt:32
#: apps/registration/templates/registration/mails/email_validation_email.html:40 #: apps/registration/templates/registration/mails/email_validation_email.html:40
#: apps/registration/templates/registration/mails/email_validation_email.txt:16 #: apps/registration/templates/registration/mails/email_validation_email.txt:16
#: apps/scripts/templates/scripts/food_report.html:48
#: apps/scripts/templates/scripts/food_report.txt:14
msgid "Mail generated by the Note Kfet on the" msgid "Mail generated by the Note Kfet on the"
msgstr "Mail généré par la Note Kfet le" msgstr "Mail généré par la Note Kfet le"
@ -2192,7 +2176,7 @@ msgstr "Chercher un bouton"
msgid "Update button" msgid "Update button"
msgstr "Modifier le bouton" msgstr "Modifier le bouton"
#: apps/note/views.py:156 note_kfet/templates/base.html:67 #: apps/note/views.py:156 note_kfet/templates/base.html:66
msgid "Consumptions" msgid "Consumptions"
msgstr "Consommations" msgstr "Consommations"
@ -2285,7 +2269,7 @@ msgstr "s'applique au club"
msgid "role permissions" msgid "role permissions"
msgstr "permissions par rôles" msgstr "permissions par rôles"
#: apps/permission/signals.py:75 #: apps/permission/signals.py:73
#, python-brace-format #, python-brace-format
msgid "" msgid ""
"You don't have the permission to change the field {field} on this instance " "You don't have the permission to change the field {field} on this instance "
@ -2294,7 +2278,7 @@ msgstr ""
"Vous n'avez pas la permission de modifier le champ {field} sur l'instance du " "Vous n'avez pas la permission de modifier le champ {field} sur l'instance du "
"modèle {app_label}.{model_name}." "modèle {app_label}.{model_name}."
#: apps/permission/signals.py:85 apps/permission/views.py:104 #: apps/permission/signals.py:83 apps/permission/views.py:104
#, python-brace-format #, python-brace-format
msgid "" msgid ""
"You don't have the permission to add an instance of model {app_label}." "You don't have the permission to add an instance of model {app_label}."
@ -2303,7 +2287,7 @@ msgstr ""
"Vous n'avez pas la permission d'ajouter une instance du modèle {app_label}." "Vous n'avez pas la permission d'ajouter une instance du modèle {app_label}."
"{model_name}." "{model_name}."
#: apps/permission/signals.py:114 #: apps/permission/signals.py:112
#, python-brace-format #, python-brace-format
msgid "" msgid ""
"You don't have the permission to delete this instance of model {app_label}." "You don't have the permission to delete this instance of model {app_label}."
@ -2391,7 +2375,7 @@ msgstr ""
"Vous n'avez pas la permission d'ajouter une instance du modèle « {model} » " "Vous n'avez pas la permission d'ajouter une instance du modèle « {model} » "
"avec ces paramètres. Merci de les corriger et de réessayer." "avec ces paramètres. Merci de les corriger et de réessayer."
#: apps/permission/views.py:111 note_kfet/templates/base.html:121 #: apps/permission/views.py:111 note_kfet/templates/base.html:120
msgid "Rights" msgid "Rights"
msgstr "Droits" msgstr "Droits"
@ -2596,7 +2580,7 @@ msgstr ""
msgid "Invalidate pre-registration" msgid "Invalidate pre-registration"
msgstr "Invalider l'inscription" msgstr "Invalider l'inscription"
#: apps/treasury/apps.py:12 note_kfet/templates/base.html:103 #: apps/treasury/apps.py:12 note_kfet/templates/base.html:102
msgid "Treasury" msgid "Treasury"
msgstr "Trésorerie" msgstr "Trésorerie"
@ -3012,7 +2996,7 @@ msgstr "Gérer les crédits de la Société générale"
#: apps/wei/apps.py:10 apps/wei/models.py:42 apps/wei/models.py:43 #: apps/wei/apps.py:10 apps/wei/models.py:42 apps/wei/models.py:43
#: apps/wei/models.py:67 apps/wei/models.py:192 #: apps/wei/models.py:67 apps/wei/models.py:192
#: note_kfet/templates/base.html:109 #: note_kfet/templates/base.html:108
msgid "WEI" msgid "WEI"
msgstr "WEI" msgstr "WEI"
@ -3057,7 +3041,7 @@ msgstr "Rôles au WEI"
msgid "Select the roles that you are interested in." msgid "Select the roles that you are interested in."
msgstr "Sélectionnez les rôles qui vous intéressent." msgstr "Sélectionnez les rôles qui vous intéressent."
#: apps/wei/forms/registration.py:160 #: apps/wei/forms/registration.py:147
msgid "This team doesn't belong to the given bus." msgid "This team doesn't belong to the given bus."
msgstr "Cette équipe n'appartient pas à ce bus." msgstr "Cette équipe n'appartient pas à ce bus."
@ -3136,7 +3120,7 @@ msgstr "Rôle au WEI"
msgid "Credit from Société générale" msgid "Credit from Société générale"
msgstr "Crédit de la Société générale" msgstr "Crédit de la Société générale"
#: apps/wei/models.py:202 apps/wei/views.py:992 #: apps/wei/models.py:202 apps/wei/views.py:984
msgid "Caution check given" msgid "Caution check given"
msgstr "Chèque de caution donné" msgstr "Chèque de caution donné"
@ -3368,6 +3352,7 @@ msgid "View club"
msgstr "Voir le club" msgstr "Voir le club"
#: apps/wei/templates/wei/bus_detail.html:26 #: apps/wei/templates/wei/bus_detail.html:26
#| msgid "survey information"
msgid "Edit information" msgid "Edit information"
msgstr "Modifier les informations" msgstr "Modifier les informations"
@ -3388,8 +3373,8 @@ msgstr "Télécharger au format PDF"
#: apps/wei/templates/wei/survey.html:11 #: apps/wei/templates/wei/survey.html:11
#: apps/wei/templates/wei/survey_closed.html:11 #: apps/wei/templates/wei/survey_closed.html:11
#: apps/wei/templates/wei/survey_end.html:11 apps/wei/views.py:1167 #: apps/wei/templates/wei/survey_end.html:11 apps/wei/views.py:1159
#: apps/wei/views.py:1222 apps/wei/views.py:1269 #: apps/wei/views.py:1214 apps/wei/views.py:1261
msgid "Survey WEI" msgid "Survey WEI"
msgstr "Questionnaire WEI" msgstr "Questionnaire WEI"
@ -3669,51 +3654,51 @@ msgstr ""
msgid "Update WEI Registration" msgid "Update WEI Registration"
msgstr "Modifier l'inscription WEI" msgstr "Modifier l'inscription WEI"
#: apps/wei/views.py:811 #: apps/wei/views.py:810
msgid "No membership found for this registration" msgid "No membership found for this registration"
msgstr "Pas d'adhésion trouvée pour cette inscription" msgstr "Pas d'adhésion trouvée pour cette inscription"
#: apps/wei/views.py:820 #: apps/wei/views.py:819
msgid "You don't have the permission to update memberships" msgid "You don't have the permission to update memberships"
msgstr "" msgstr ""
"Vous n'avez pas la permission d'ajouter une instance du modèle {app_label}." "Vous n'avez pas la permission d'ajouter une instance du modèle {app_label}."
"{model_name}." "{model_name}."
#: apps/wei/views.py:826 #: apps/wei/views.py:825
#, python-format #, python-format
msgid "You don't have the permission to update the field %(field)s" msgid "You don't have the permission to update the field %(field)s"
msgstr "Vous n'avez pas la permission de modifier le champ %(field)s" msgstr "Vous n'avez pas la permission de modifier le champ %(field)s"
#: apps/wei/views.py:871 #: apps/wei/views.py:870
msgid "Delete WEI registration" msgid "Delete WEI registration"
msgstr "Supprimer l'inscription WEI" msgstr "Supprimer l'inscription WEI"
#: apps/wei/views.py:882 #: apps/wei/views.py:881
msgid "You don't have the right to delete this WEI registration." msgid "You don't have the right to delete this WEI registration."
msgstr "Vous n'avez pas la permission de supprimer cette inscription au WEI." msgstr "Vous n'avez pas la permission de supprimer cette inscription au WEI."
#: apps/wei/views.py:900 #: apps/wei/views.py:899
msgid "Validate WEI registration" msgid "Validate WEI registration"
msgstr "Valider l'inscription WEI" msgstr "Valider l'inscription WEI"
#: apps/wei/views.py:993 #: apps/wei/views.py:985
msgid "Please make sure the check is given before validating the registration" msgid "Please make sure the check is given before validating the registration"
msgstr "" msgstr ""
"Merci de vous assurer que le chèque a bien été donné avant de valider " "Merci de vous assurer que le chèque a bien été donné avant de valider "
"l'adhésion" "l'adhésion"
#: apps/wei/views.py:999 #: apps/wei/views.py:991
msgid "Create deposit transaction" msgid "Create deposit transaction"
msgstr "Créer une transaction de caution" msgstr "Créer une transaction de caution"
#: apps/wei/views.py:1000 #: apps/wei/views.py:992
#, python-format #, python-format
msgid "" msgid ""
"A transaction of %(amount).2f€ will be created from the user's Note account" "A transaction of %(amount).2f€ will be created from the user's Note account"
msgstr "" msgstr ""
"Un transaction de %(amount).2f€ va être créée depuis la note de l'utilisateur" "Un transaction de %(amount).2f€ va être créée depuis la note de l'utilisateur"
#: apps/wei/views.py:1095 #: apps/wei/views.py:1087
#, python-format #, python-format
msgid "" msgid ""
"This user doesn't have enough money to join this club and pay the deposit. " "This user doesn't have enough money to join this club and pay the deposit. "
@ -3723,21 +3708,21 @@ msgstr ""
"payer la cautionSolde actuel : %(balance)d€, crédit : %(credit)d€, requis : " "payer la cautionSolde actuel : %(balance)d€, crédit : %(credit)d€, requis : "
"%(needed)d€" "%(needed)d€"
#: apps/wei/views.py:1148 #: apps/wei/views.py:1140
#, fuzzy, python-format #, fuzzy, python-format
#| msgid "total amount" #| msgid "total amount"
msgid "Caution %(name)s" msgid "Caution %(name)s"
msgstr "montant total" msgstr "montant total"
#: apps/wei/views.py:1362 #: apps/wei/views.py:1354
msgid "Attribute buses to first year members" msgid "Attribute buses to first year members"
msgstr "Répartir les 1A dans les bus" msgstr "Répartir les 1A dans les bus"
#: apps/wei/views.py:1388 #: apps/wei/views.py:1380
msgid "Attribute bus" msgid "Attribute bus"
msgstr "Attribuer un bus" msgstr "Attribuer un bus"
#: apps/wei/views.py:1428 #: apps/wei/views.py:1420
msgid "" msgid ""
"No first year student without a bus found. Either all of them have a bus, or " "No first year student without a bus found. Either all of them have a bus, or "
"none has filled the survey yet." "none has filled the survey yet."
@ -3761,13 +3746,13 @@ msgstr "bde"
#: apps/wrapped/models.py:65 #: apps/wrapped/models.py:65
msgid "data json" msgid "data json"
msgstr "données json" msgstr "donnée json"
#: apps/wrapped/models.py:66 #: apps/wrapped/models.py:66
msgid "data in the wrapped and generated by the script generate_wrapped" msgid "data in the wrapped and generated by the script generate_wrapped"
msgstr "donnée dans le wrapped et générée par le script generate_wrapped" msgstr "donnée dans le wrapped et générée par le script generate_wrapped"
#: apps/wrapped/models.py:70 note_kfet/templates/base.html:115 #: apps/wrapped/models.py:70 note_kfet/templates/base.html:114
msgid "Wrapped" msgid "Wrapped"
msgstr "Wrapped" msgstr "Wrapped"
@ -3800,7 +3785,7 @@ msgid "Copy link"
msgstr "Copier le lien" msgstr "Copier le lien"
#: apps/wrapped/templates/wrapped/1/wrapped_base.html:16 #: apps/wrapped/templates/wrapped/1/wrapped_base.html:16
#: note_kfet/templates/base.html:15 #: note_kfet/templates/base.html:14
msgid "The ENS Paris-Saclay BDE note." msgid "The ENS Paris-Saclay BDE note."
msgstr "La note du BDE de l'ENS Paris-Saclay." msgstr "La note du BDE de l'ENS Paris-Saclay."
@ -3903,7 +3888,7 @@ msgid ""
"Do not forget to ask permission to people who are in your wrapped before to " "Do not forget to ask permission to people who are in your wrapped before to "
"make them public" "make them public"
msgstr "" msgstr ""
"N'oublie pas de demander la permission des personnes apparaissant dans un " "N'oublies pas de demander la permission des personnes apparaissant dans un "
"wrapped avant de le rendre public" "wrapped avant de le rendre public"
#: apps/wrapped/templates/wrapped/wrapped_list.html:40 #: apps/wrapped/templates/wrapped/wrapped_list.html:40
@ -3922,19 +3907,19 @@ msgstr "Le wrapped est public"
msgid "List of wrapped" msgid "List of wrapped"
msgstr "Liste des wrapped" msgstr "Liste des wrapped"
#: note_kfet/settings/base.py:180 #: note_kfet/settings/base.py:177
msgid "German" msgid "German"
msgstr "Allemand" msgstr "Allemand"
#: note_kfet/settings/base.py:181 #: note_kfet/settings/base.py:178
msgid "English" msgid "English"
msgstr "Anglais" msgstr "Anglais"
#: note_kfet/settings/base.py:182 #: note_kfet/settings/base.py:179
msgid "Spanish" msgid "Spanish"
msgstr "Espagnol" msgstr "Espagnol"
#: note_kfet/settings/base.py:183 #: note_kfet/settings/base.py:180
msgid "French" msgid "French"
msgstr "Français" msgstr "Français"
@ -3995,34 +3980,34 @@ msgstr ""
msgid "Reset" msgid "Reset"
msgstr "Réinitialiser" msgstr "Réinitialiser"
#: note_kfet/templates/base.html:85 #: note_kfet/templates/base.html:84
msgid "Users" msgid "Users"
msgstr "Utilisateur·rices" msgstr "Utilisateur·rices"
#: note_kfet/templates/base.html:91 #: note_kfet/templates/base.html:90
msgid "Clubs" msgid "Clubs"
msgstr "Clubs" msgstr "Clubs"
#: note_kfet/templates/base.html:126 #: note_kfet/templates/base.html:125
msgid "Admin" msgid "Admin"
msgstr "Admin" msgstr "Admin"
#: note_kfet/templates/base.html:140 #: note_kfet/templates/base.html:139
msgid "My account" msgid "My account"
msgstr "Mon compte" msgstr "Mon compte"
#: note_kfet/templates/base.html:145 #: note_kfet/templates/base.html:142
msgid "Log out" msgid "Log out"
msgstr "Se déconnecter" msgstr "Se déconnecter"
#: note_kfet/templates/base.html:154 #: note_kfet/templates/base.html:150
#: note_kfet/templates/registration/signup.html:6 #: note_kfet/templates/registration/signup.html:6
#: note_kfet/templates/registration/signup.html:11 #: note_kfet/templates/registration/signup.html:11
#: note_kfet/templates/registration/signup.html:28 #: note_kfet/templates/registration/signup.html:28
msgid "Sign up" msgid "Sign up"
msgstr "Inscription" msgstr "Inscription"
#: note_kfet/templates/base.html:161 #: note_kfet/templates/base.html:157
#: note_kfet/templates/registration/login.html:6 #: note_kfet/templates/registration/login.html:6
#: note_kfet/templates/registration/login.html:15 #: note_kfet/templates/registration/login.html:15
#: note_kfet/templates/registration/login.html:38 #: note_kfet/templates/registration/login.html:38
@ -4030,7 +4015,7 @@ msgstr "Inscription"
msgid "Log in" msgid "Log in"
msgstr "Se connecter" msgstr "Se connecter"
#: note_kfet/templates/base.html:175 #: note_kfet/templates/base.html:171
msgid "" msgid ""
"You are not a BDE member anymore. Please renew your membership if you want " "You are not a BDE member anymore. Please renew your membership if you want "
"to use the note." "to use the note."
@ -4038,7 +4023,7 @@ msgstr ""
"Vous n'êtes plus adhérent·e BDE. Merci de réadhérer si vous voulez profiter " "Vous n'êtes plus adhérent·e BDE. Merci de réadhérer si vous voulez profiter "
"de la note." "de la note."
#: note_kfet/templates/base.html:181 #: note_kfet/templates/base.html:177
msgid "" msgid ""
"Your e-mail address is not validated. Please check your mail inbox and click " "Your e-mail address is not validated. Please check your mail inbox and click "
"on the validation link." "on the validation link."
@ -4046,7 +4031,7 @@ msgstr ""
"Votre adresse e-mail n'est pas validée. Merci de vérifier votre boîte mail " "Votre adresse e-mail n'est pas validée. Merci de vérifier votre boîte mail "
"et de cliquer sur le lien de validation." "et de cliquer sur le lien de validation."
#: note_kfet/templates/base.html:187 #: note_kfet/templates/base.html:183
msgid "" msgid ""
"You declared that you opened a bank account in the Société générale. The " "You declared that you opened a bank account in the Société générale. The "
"bank did not validate the creation of the account to the BDE, so the " "bank did not validate the creation of the account to the BDE, so the "
@ -4060,38 +4045,22 @@ msgstr ""
"vérification peut durer quelques jours. Merci de vous assurer de bien aller " "vérification peut durer quelques jours. Merci de vous assurer de bien aller "
"au bout de vos démarches." "au bout de vos démarches."
#: note_kfet/templates/base.html:214 #: note_kfet/templates/base.html:206
msgid "Contact us" msgid "Contact us"
msgstr "Nous contacter" msgstr "Nous contacter"
#: note_kfet/templates/base.html:216 #: note_kfet/templates/base.html:208
msgid "Technical Support" msgid "Technical Support"
msgstr "Support technique" msgstr "Support technique"
#: note_kfet/templates/base.html:218 #: note_kfet/templates/base.html:210
msgid "Charte Info (FR)" msgid "Charte Info (FR)"
msgstr "Charte Info (FR)" msgstr "Charte Info (FR)"
#: note_kfet/templates/base.html:220 #: note_kfet/templates/base.html:212
msgid "FAQ (FR)" msgid "FAQ (FR)"
msgstr "FAQ (FR)" msgstr "FAQ (FR)"
#: note_kfet/templates/base.html:222
msgid "Managed by BDE"
msgstr "Géré par le BDE"
#: note_kfet/templates/base.html:224
msgid "Hosted by Cr@ns"
msgstr "Hébergé par le Cr@ans"
#: note_kfet/templates/base.html:266
msgid "The note is not available for now"
msgstr "La note est indisponible pour le moment"
#: note_kfet/templates/base.html:268
msgid "Thank you for your understanding -- The Respos Info of BDE"
msgstr "Merci de votre compréhension -- Les Respos Info du BDE"
#: note_kfet/templates/base_search.html:15 #: note_kfet/templates/base_search.html:15
msgid "Search by attribute such as name..." msgid "Search by attribute such as name..."
msgstr "Chercher par un attribut tel que le nom..." msgstr "Chercher par un attribut tel que le nom..."
@ -4100,41 +4069,6 @@ msgstr "Chercher par un attribut tel que le nom..."
msgid "There is no results." msgid "There is no results."
msgstr "Il n'y a pas de résultat." msgstr "Il n'y a pas de résultat."
#: note_kfet/templates/cas/logged.html:8
msgid ""
"<h3>Log In Successful</h3>You have successfully logged into the Central "
"Authentication Service.<br/>For security reasons, please Log Out and Exit "
"your web browser when you are done accessing services that require "
"authentication!"
msgstr ""
"<h3>Connection réussie</h3>Vous vous êtes bien connecté au Service Central d'Authentification."
"<br/>Pour des raisons de sécurité, veuillez vous déconnecter et fermer votre navigateur internet "
"une fois que vous aurez fini d'accéder aux services qui requiert une authentification !"
#: note_kfet/templates/cas/logged.html:14
msgid "Log me out from all my sessions"
msgstr "Me déconnecter de toutes mes sessions"
#: note_kfet/templates/cas/logged.html:20
msgid "Forget the identity provider"
msgstr "Oublier le fournisseur d'identité"
#: note_kfet/templates/cas/logged.html:24
msgid "Logout"
msgstr "Déconnexion"
#: note_kfet/templates/cas/login.html:11
msgid "Please log in"
msgstr "Veuillez vous connecter"
#: note_kfet/templates/cas/login.html:23
msgid "Login"
msgstr "Connexion"
#: note_kfet/templates/cas/warn.html:14
msgid "Connect to the service"
msgstr "Connexion au service"
#: note_kfet/templates/oauth2_provider/application_confirm_delete.html:8 #: note_kfet/templates/oauth2_provider/application_confirm_delete.html:8
msgid "Are you sure to delete the application" msgid "Are you sure to delete the application"
msgstr "Êtes-vous sûr⋅e de vouloir supprimer l'application" msgstr "Êtes-vous sûr⋅e de vouloir supprimer l'application"
@ -4355,86 +4289,10 @@ msgstr ""
"d'adhésion. Vous devez également valider votre adresse email en suivant le " "d'adhésion. Vous devez également valider votre adresse email en suivant le "
"lien que vous avez reçu." "lien que vous avez reçu."
#, fuzzy #, fuzzy, python-format
#~| msgid "QR-code" #~| msgid "Creation date"
#~ msgid "Go to QR-code" #~ msgid "Deposit %(name)s"
#~ msgstr "QR-code" #~ msgstr "Caution %(name)s"
#, python-brace-format
#~ msgid "QR-code number {qr_code_number}"
#~ msgstr "Numéro du QR-code {qr_code_number}"
#~ msgid "was eaten"
#~ msgstr "a été mangé"
#~ msgid "is active"
#~ msgstr "est en cours"
#~ msgid "foods"
#~ msgstr "bouffes"
#~ msgid "Arrival date"
#~ msgstr "Date d'arrivée"
#~ msgid "Active"
#~ msgstr "Actif"
#~ msgid "Eaten"
#~ msgstr "Mangé"
#~ msgid "number"
#~ msgstr "numéro"
#~ msgid "View details"
#~ msgstr "Voir plus"
#~ msgid "Ready"
#~ msgstr "Prêt"
#~ msgid "Creation date"
#~ msgstr "Date de création"
#~ msgid "Ingredients"
#~ msgstr "Ingrédients"
#~ msgid "Open"
#~ msgstr "Open"
#~ msgid "All meals"
#~ msgstr "Tout les plats"
#~ msgid "There is no meal."
#~ msgstr "Il n'y a pas de plat"
#~ msgid "The product is already prepared"
#~ msgstr "Le produit est déjà prêt"
#~ msgid "Add a new basic food with QRCode"
#~ msgstr "Ajouter un nouvel ingrédient avec un QR-code"
#~ msgid "QRCode"
#~ msgstr "QR-code"
#~ msgid "Add a new meal"
#~ msgstr "Ajouter un nouveau plat"
#~ msgid "Update a meal"
#~ msgstr "Modifier le plat"
#, fuzzy
#~| msgid "invalidate"
#~ msgid "Enter a valid color."
#~ msgstr "dévalider"
#, fuzzy
#~| msgid "invalidate"
#~ msgid "Enter a valid value."
#~ msgstr "dévalider"
#, fuzzy
#~| msgid "Invitation"
#~ msgid "Syndication"
#~ msgstr "Invitation"
#, fuzzy #, fuzzy
#~| msgid "There is no results." #~| msgid "There is no results."
@ -4848,7 +4706,7 @@ msgstr ""
#, python-brace-format #, python-brace-format
#~ msgid "QR-code number {qr_code_number}" #~ msgid "QR-code number {qr_code_number}"
#~ msgstr "Numéro du QR-code {qr_code_number}" #~ msgstr "numéro du QR-code {qr_code_number}"
#~ msgid "was eaten" #~ msgid "was eaten"
#~ msgstr "a été mangé" #~ msgstr "a été mangé"

View File

@ -27,6 +27,5 @@ MAILTO=notekfet2020@lists.crans.org
# Vider les tokens Oauth2 # Vider les tokens Oauth2
00 6 * * * root cd /var/www/note_kfet && env/bin/python manage.py cleartokens -v 0 00 6 * * * root cd /var/www/note_kfet && env/bin/python manage.py cleartokens -v 0
# Envoyer la liste des abonnés à la NL BDA # Envoyer la liste des abonnés à la NL BDA
00 10 * * 0 root cd /var/www/note_kfet && env/bin/python manage.py extract_ml_registrations -t art -e "bda.ensparissaclay@gmail.com" 00 10 * * 0 root cd /var/www/note_kfet && env/bin/python manage.py extract_ml_registrations -t art -e "bda.ensparissaclay@gmail.com"
# Envoyer la liste de la bouffe au club et aux GCKs
00 8 * * 1 root cd /var/www/note_kfet && env/bin/python manage.py send_mail_for_food --report --club

View File

@ -56,8 +56,3 @@ if "cas_server" in settings.INSTALLED_APPS:
from cas_server.models import * from cas_server.models import *
admin_site.register(ServicePattern, ServicePatternAdmin) admin_site.register(ServicePattern, ServicePatternAdmin)
admin_site.register(FederatedIendityProvider, FederatedIendityProviderAdmin) admin_site.register(FederatedIendityProvider, FederatedIendityProviderAdmin)
if "constance" in settings.INSTALLED_APPS:
from constance.admin import *
from constance.models import *
admin_site.register([Config], ConstanceAdmin)

View File

@ -39,9 +39,7 @@ SECURE_HSTS_PRELOAD = True
INSTALLED_APPS = [ INSTALLED_APPS = [
# External apps # External apps
'bootstrap_datepicker_plus', 'bootstrap_datepicker_plus',
'cas_server',
'colorfield', 'colorfield',
'constance',
'crispy_bootstrap4', 'crispy_bootstrap4',
'crispy_forms', 'crispy_forms',
# 'django_htcpcp_tea', # 'django_htcpcp_tea',
@ -72,6 +70,7 @@ INSTALLED_APPS = [
# Note apps # Note apps
'api', 'api',
'activity', 'activity',
'family',
'food', 'food',
'logs', 'logs',
'member', 'member',
@ -113,7 +112,6 @@ TEMPLATES = [
'APP_DIRS': True, 'APP_DIRS': True,
'OPTIONS': { 'OPTIONS': {
'context_processors': [ 'context_processors': [
'constance.context_processors.config',
'django.template.context_processors.debug', 'django.template.context_processors.debug',
'django.template.context_processors.request', 'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth', 'django.contrib.auth.context_processors.auth',
@ -310,30 +308,6 @@ PHONENUMBER_DEFAULT_REGION = 'FR'
# We add custom information to CAS, in order to give a normalized name to other services # We add custom information to CAS, in order to give a normalized name to other services
CAS_AUTH_CLASS = 'member.auth.CustomAuthUser' CAS_AUTH_CLASS = 'member.auth.CustomAuthUser'
CAS_LOGIN_TEMPLATE = 'cas/login.html'
CAS_LOGOUT_TEMPLATE = 'cas/logout.html'
CAS_WARN_TEMPLATE = 'cas/warn.html'
CAS_LOGGED_TEMPLATE = 'cas/logged.html'
# Default field for primary key # Default field for primary key
DEFAULT_AUTO_FIELD = "django.db.models.AutoField" DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
# Constance settings
CONSTANCE_ADDITIONAL_FIELDS = {
'banner_type': ['django.forms.fields.ChoiceField', {
'widget': 'django.forms.Select',
'choices': (('info', 'Info'), ('success', 'Success'), ('warning', 'Warning'), ('danger', 'Danger'))
}],
}
CONSTANCE_CONFIG = {
'BANNER_MESSAGE': ('', 'Some message', str),
'BANNER_TYPE': ('info', 'Banner type', 'banner_type'),
'MAINTENANCE': (False, 'check for mainteance mode', bool),
'MAINTENANCE_MESSAGE': ('', 'Some maintenance message', str),
}
CONSTANCE_CONFIG_FIELDSETS = {
'Maintenance': ('MAINTENANCE_MESSAGE', 'MAINTENANCE'),
'Banner': ('BANNER_MESSAGE', 'BANNER_TYPE'),
}
CONSTANCE_BACKEND = 'constance.backends.database.DatabaseBackend'
CONSTANCE_SUPERUSER_ONLY = True

View File

@ -5,7 +5,6 @@ SPDX-License-Identifier: GPL-3.0-or-later
<!DOCTYPE html> <!DOCTYPE html>
{% get_current_language as LANGUAGE_CODE %}{% get_current_language_bidi as LANGUAGE_BIDI %} {% get_current_language as LANGUAGE_CODE %}{% get_current_language_bidi as LANGUAGE_BIDI %}
<html lang="{{ LANGUAGE_CODE|default:"en" }}" {% if LANGUAGE_BIDI %}dir="rtl"{% endif %} class="position-relative h-100"> <html lang="{{ LANGUAGE_CODE|default:"en" }}" {% if LANGUAGE_BIDI %}dir="rtl"{% endif %} class="position-relative h-100">
{% if not config.MAINTENANCE %}
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
@ -79,6 +78,13 @@ SPDX-License-Identifier: GPL-3.0-or-later
<a class="nav-link {% if request.path_info == url %}active{% endif %}" href="{{ url }}"><i class="fa fa-exchange"></i> {% trans 'Transfer' %}</a> <a class="nav-link {% if request.path_info == url %}active{% endif %}" href="{{ url }}"><i class="fa fa-exchange"></i> {% trans 'Transfer' %}</a>
</li> </li>
{% endif %} {% endif %}
{% if user.is_authenticated %}
<li class="nav-item">
{% url 'family:family_list' as url %}
<a class="nav-link {% if request.path_info == url %}active{% endif %}" href="{{ url }}"><i class="fa fa-users"></i> {% trans 'Families' %}</a>
</li>
{% endif %}
{% if "auth.user"|model_list_length >= 2 %} {% if "auth.user"|model_list_length >= 2 %}
<li class="nav-item"> <li class="nav-item">
{% url 'member:user_list' as url %} {% url 'member:user_list' as url %}
@ -139,12 +145,9 @@ SPDX-License-Identifier: GPL-3.0-or-later
<a class="dropdown-item" href="{% url 'member:user_detail' pk=request.user.pk %}"> <a class="dropdown-item" href="{% url 'member:user_detail' pk=request.user.pk %}">
<i class="fa fa-user"></i> {% trans "My account" %} <i class="fa fa-user"></i> {% trans "My account" %}
</a> </a>
<form method="post" action="{% url 'logout' %}"> <a class="dropdown-item" href="{% url 'logout' %}">
{% csrf_token %} <i class="fa fa-sign-out"></i> {% trans "Log out" %}
<button class="dropdown-item" type=submit"> </a>
<i class="fa fa-sign-out"></i> {% trans "Log out" %}
</button>
</form>
</div> </div>
</li> </li>
{% else %} {% else %}
@ -192,11 +195,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
{% endblocktrans %} {% endblocktrans %}
</div> </div>
{% endif %} {% endif %}
{% if config.BANNER_MESSAGE and user.is_authenticated %} {# TODO Add banners #}
<div class="alert alert-{{ config.BANNER_TYPE }}">
{{ config.BANNER_MESSAGE }}
</div>
{% endif %}
</div> </div>
{% block content %} {% block content %}
<p>Default content...</p> <p>Default content...</p>
@ -218,10 +217,6 @@ SPDX-License-Identifier: GPL-3.0-or-later
class="text-muted">{% trans "Charte Info (FR)" %}</a> &mdash; class="text-muted">{% trans "Charte Info (FR)" %}</a> &mdash;
<a href="https://note.crans.org/doc/faq/" <a href="https://note.crans.org/doc/faq/"
class="text-muted">{% trans "FAQ (FR)" %}</a> &mdash; class="text-muted">{% trans "FAQ (FR)" %}</a> &mdash;
<a href="https://bde.ens-cachan.fr"
class="text-muted">{% trans "Managed by BDE" %}</a> &mdash;
<a href="https://crans.org"
class="text-muted">{% trans "Hosted by Cr@ns" %}</a> &mdash;
</span> </span>
{% csrf_token %} {% csrf_token %}
<select title="language" name="language" <select title="language" name="language"
@ -258,15 +253,4 @@ SPDX-License-Identifier: GPL-3.0-or-later
{% block extrajavascript %}{% endblock %} {% block extrajavascript %}{% endblock %}
</body> </body>
{% endif %}
{% if config.MAINTENANCE %}
<body>
<div style="text-align:center">
<br />
{% trans "The note is not available for now" %}<br /><br />
{{ config.MAINTENANCE_MESSAGE }}<br /><br />
{% trans "Thank you for your understanding -- The Respos Info of BDE" %}
</div>
</body>
{% endif %}
</html> </html>

View File

@ -1,28 +0,0 @@
{% extends "base.html" %}
{% comment %}
Copyright (C) by BDE ENS-Paris-Saclay
SPDX-License-Identifier: GPL-3.0-or-later
{% endcomment %}
{% load i18n %}
{% block content %}
<div class="alert alert-success" role="alert">{% blocktrans %}<h3>Log In Successful</h3>You have successfully logged into the Central Authentication Service.<br/>For security reasons, please Log Out and Exit your web browser when you are done accessing services that require authentication!{% endblocktrans %}</div>
<div class="card bg-light mx-auto" style="max-width:30rem;">
<div class="card-body">
<form class="form-signin" method="get" action="logout">
<div class="checkbox">
<label>
<input type="checkbox" name="all" value="1">{% trans "Log me out from all my sessions" %}
</label>
</div>
{% if settings.CAS_FEDERATE and request.COOKIES.remember_provider %}
<div class="checkbox">
<label>
<input type="checkbox" name="forget_provider" value="1">{% trans "Forget the identity provider" %}
</label>
</div>
{% endif %}
<button class="btn btn-danger btn-block btn-lg" type="submit">{% trans "Logout" %}</button>
</form>
</div>
</div>
{% endblock %}

View File

@ -1,42 +0,0 @@
{% extends "base.html" %}
{% comment %}
Copyright (C) by BDE ENS-Paris-Saclay
SPDX-License-Identifier: GPL-3.0-or-later
{% endcomment %}
{% load i18n %}
{% block ante_messages %}
{% if auto_submit %}<noscript>{% endif %}
<div class="card-header text-center">
<h2 class="form-signin-heading">{% trans "Please log in" %}</h2>
</div>
{% if auto_submit %}</noscript>{% endif %}
{% endblock %}
{% block content %}
<div class="card bg-light mx-auto" style="max-width: 30rem;">
<div class="card-body">
<form class="form-signin" method="post" id="login_form"{% if post_url %} action="{{post_url}}"{% endif %}>
{% csrf_token %}
{% include "cas_server/bs4/form.html" %}
{% if auto_submit %}<noscript>{% endif %}
<button class="btn btn-primary btn-block btn-lg" type="submit">{% trans "Login" %}</button>
{% if auto_submit %}</noscript>{% endif %}
</div>
</form>
</div>
</div>
{% endblock %}
{% block javascript_inline %}
jQuery(function( $ ){
$("#id_warn").click(function(e){
if($("#id_warn").is(':checked')){
createCookie("warn", "on", 10 * 365);
} else {
eraseCookie("warn");
}
});
});
{% if auto_submit %}document.getElementById('login_form').submit(); // SUBMIT FORM{% endif %}
{% endblock %}

View File

@ -1,10 +0,0 @@
{% extends "base.html" %}
{% comment %}
Copyright (C) by BDE ENS-Paris-Saclay
SPDX-License-Identifier: GPL-3.0-or-later
{% endcomment %}
{% load i18n static %}
{% block content %}
<div class="alert alert-success" role="alert">{{ logout_msg }}</div>
{% endblock %}

View File

@ -1,19 +0,0 @@
{% extends "base.html" %}
{% comment %}
Copyright (C) by BDE ENS-Paris-Saclay
SPDX-License-Identifier: GPL-3.0-or-later
{% endcomment %}
{% load i18n static %}
{% block content %}
<div class="card bg-light mx-auto" style="max-width: 30rem;">
<div class="card-body">
<form class="form-signin" method="post">
{% csrf_token %}
{% include "cas_server/bs4/form.html" %}
<button class="btn btn-primary btn-block btn-lg" type="submit">{% trans "Connect to the service" %}</button>
</form>
</div>
</div>
{% endblock %}

View File

@ -21,8 +21,9 @@ urlpatterns = [
path('activity/', include('activity.urls')), path('activity/', include('activity.urls')),
path('treasury/', include('treasury.urls')), path('treasury/', include('treasury.urls')),
path('wei/', include('wei.urls')), path('wei/', include('wei.urls')),
path('food/',include('food.urls')), path('food/', include('food.urls')),
path('wrapped/',include('wrapped.urls')), path('wrapped/', include('wrapped.urls')),
path('family/', include('family.urls')),
# Include Django Contrib and Core routers # Include Django Contrib and Core routers
path('i18n/', include('django.conf.urls.i18n')), path('i18n/', include('django.conf.urls.i18n')),

View File

@ -1,21 +1,20 @@
beautifulsoup4~=4.13.4 beautifulsoup4~=4.12.3
crispy-bootstrap4~=2025.6 crispy-bootstrap4~=2023.1
Django~=5.2.4 Django~=4.2.9
django-bootstrap-datepicker-plus~=5.0.5 django-bootstrap-datepicker-plus~=5.0.5
django-cas-server~=3.1.0 #django-cas-server~=2.0.0
django-colorfield~=0.14.0 django-colorfield~=0.11.0
django-constance~=4.3.2 django-crispy-forms~=2.1.0
django-crispy-forms~=2.4.0 django-extensions>=3.2.3
django-extensions>=4.1.0 django-filter~=23.5
django-filter~=25.1
#django-htcpcp-tea~=0.8.1 #django-htcpcp-tea~=0.8.1
django-mailer~=2.3.2 django-mailer~=2.3.1
django-oauth-toolkit~=3.0.1 django-oauth-toolkit~=2.3.0
django-phonenumber-field~=8.1.0 django-phonenumber-field~=7.3.0
django-polymorphic~=3.1.0 django-polymorphic~=3.1.0
djangorestframework~=3.16.0 djangorestframework~=3.14.0
django-rest-polymorphic~=0.1.10 django-rest-polymorphic~=0.1.10
django-tables2~=2.7.5 django-tables2~=2.7.0
python-memcached~=1.62 python-memcached~=1.62
phonenumbers~=9.0.8 phonenumbers~=8.13.28
Pillow>=11.3.0 Pillow>=10.2.0

View File

@ -1,13 +1,13 @@
[tox] [tox]
envlist = envlist =
# Ubuntu 22.04 Python # Ubuntu 22.04 Python
py310-django52 py310-django42
# Debian Bookworm Python # Debian Bookworm Python
py311-django52 py311-django42
# Ubuntu 24.04 Python # Ubuntu 24.04 Python
py312-django52 py312-django42
linters linters
skipsdist = True skipsdist = True
@ -32,7 +32,8 @@ deps =
pep8-naming pep8-naming
pyflakes pyflakes
commands = commands =
flake8 apps --extend-exclude apps/scripts flake8 apps --extend-exclude apps/scripts,apps/wrapped/management/commands
flake8 apps/wrapped/management/commands --extend-ignore=C901
[flake8] [flake8]
ignore = W503, I100, I101, B019 ignore = W503, I100, I101, B019