Author SHA1 Message Date
imperosol 15c5a37c99 docs: fix nushell scripts 2026-10-12 14:08:12 +02:00
imperosol 07648f596e docs: describe populated user accounts 2026-10-12 13:51:18 +02:00
9 changed files with 171 additions and 177 deletions

No files matched your search

+14 -26
View File
@@ -83,17 +83,8 @@
{% endmacro %}
{% block content %}
<div class="alert alert-yellow">
<div class="alert-main">
{%- trans trimmed -%}
You can see product details.
However, you cannot edit it, thus you won't be able
to submit this form.
{%- endtrans -%}
</div>
</div>
{% if object %}
<h2>{{ object }}</h2>
<h2>{% trans name=object %}Edit product {{ name }}{% endtrans %}</h2>
<p><i>{% trans %}Creation date{% endtrans %} : {{ object.created_at|date }}</i></p>
<p><i>{% trans %}Last update{% endtrans %} : {{ object.updated_at|date }}</i></p>
{% else %}
@@ -136,17 +127,19 @@
{{ form.price_formset.management_form }}
<div x-ref="formContainer">
{%- for form in form.price_formset.forms -%}
<div>{{ price_form(form) }}</div>
<div>
{{ price_form(form) }}
</div>
{%- endfor -%}
</div>
<template x-ref="formTemplate">
<div>{{ price_form(form.price_formset.empty_form) }}</div>
<div>
{{ price_form(form.price_formset.empty_form) }}
</div>
</template>
{% if user.has_perm("counter.change_product") %}
<button class="btn btn-grey" @click.prevent="addForm()">
<i class="fa fa-plus"></i> {% trans %}Add a price{% endtrans %}
</button>
{% endif %}
<button class="btn btn-grey" @click.prevent="addForm()">
<i class="fa fa-plus"></i> {% trans %}Add a price{% endtrans %}
</button>
</div>
<br />
@@ -172,16 +165,11 @@
<template x-ref="formTemplate">
{{ action_form(form.action_formset.empty_form) }}
</template>
{% if user.has_perm("counter.change_product") %}
<button @click.prevent="addForm()" class="btn btn-grey">
<i class="fa fa-plus"></i>{% trans %}Add action{% endtrans %}
</button>
{% endif %}
<button @click.prevent="addForm()" class="btn btn-grey">
<i class="fa fa-plus"></i>{% trans %}Add action{% endtrans %}
</button>
</div>
<div class="row gap margin-bottom">{{ form.archived.as_field_group() }}</div>
{% if user.has_perm("counter.change_product") %}
<p><input class="btn btn-blue" type="submit" value="{% trans %}Save{% endtrans %}" /></p>
{% endif %}
<p><input class="btn btn-blue" type="submit" value="{% trans %}Save{% endtrans %}" /></p>
</form>
{% endblock %}
+3 -5
View File
@@ -80,11 +80,9 @@
<h3 class="margin-bottom">{% trans %}Product list{% endtrans %}</h3>
<div class="row margin-bottom">
{% if user.has_perm("counter.add_product") %}
<a href="{{ url('counter:new_product') }}" class="btn btn-blue">
{% trans %}New product{% endtrans %} <i class="fa fa-plus"></i>
</a>
{% endif %}
<a href="{{ url('counter:new_product') }}" class="btn btn-blue">
{% trans %}New product{% endtrans %} <i class="fa fa-plus"></i>
</a>
<button
class="btn btn-blue"
@click="downloadCsv()"
@@ -13,12 +13,12 @@
{% endblock %}
{% block content %}
{% if user.has_perm("counter.add_producttype") %}
<a href="{{ url('counter:new_product_type') }}" class="btn btn-blue margin-bottom">
<p>
<a href="{{ url('counter:new_product_type') }}" class="btn btn-blue">
{% trans %}New product type{% endtrans %}
<i class="fa fa-plus"></i>
</a>
{% endif %}
</p>
{% if product_types %}
<aside>
<p>
+21 -34
View File
@@ -48,7 +48,7 @@ from counter.models import (
Selling,
)
from counter.utils import is_logged_in_counter
from counter.views.mixins import CounterAdminTabsMixin
from counter.views.mixins import CounterAdminMixin, CounterAdminTabsMixin
class CounterListView(CounterAdminTabsMixin, CanViewMixin, ListView):
@@ -84,18 +84,17 @@ class CounterEditView(
return reverse_lazy("counter:admin", kwargs={"counter_id": self.object.id})
class CounterEditPropView(CounterAdminTabsMixin, PermissionRequiredMixin, UpdateView):
"""Edit a counter's main infos."""
class CounterEditPropView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView):
"""Edit a counter's main informations (for the counter's admin)."""
model = Counter
form_class = modelform_factory(Counter, fields=["name", "club", "type"])
pk_url_kwarg = "counter_id"
template_name = "core/edit.jinja"
current_tab = "counters"
permission_required = "counter.change_counter"
class CounterCreateView(CounterAdminTabsMixin, PermissionRequiredMixin, CreateView):
class CounterCreateView(CounterAdminTabsMixin, CounterAdminMixin, CreateView):
"""Create a counter (for the admins)."""
model = Counter
@@ -106,10 +105,9 @@ class CounterCreateView(CounterAdminTabsMixin, PermissionRequiredMixin, CreateVi
)
template_name = "core/create.jinja"
current_tab = "counters"
permission_required = "counter.add_counter"
class CounterDeleteView(CounterAdminTabsMixin, PermissionRequiredMixin, DeleteView):
class CounterDeleteView(CounterAdminTabsMixin, CounterAdminMixin, DeleteView):
"""Delete a counter (for the admins)."""
model = Counter
@@ -117,33 +115,30 @@ class CounterDeleteView(CounterAdminTabsMixin, PermissionRequiredMixin, DeleteVi
template_name = "core/delete_confirm.jinja"
success_url = reverse_lazy("counter:admin_list")
current_tab = "counters"
permission_required = "counter.delete_counter"
# Product management
class ProductTypeListView(CounterAdminTabsMixin, PermissionRequiredMixin, ListView):
class ProductTypeListView(CounterAdminTabsMixin, CounterAdminMixin, ListView):
"""A list view for the admins."""
model = ProductType
template_name = "counter/product_type_list.jinja"
current_tab = "product_types"
context_object_name = "product_types"
permission_required = "counter.view_producttype"
class ProductTypeCreateView(CounterAdminTabsMixin, PermissionRequiredMixin, CreateView):
class ProductTypeCreateView(CounterAdminTabsMixin, CounterAdminMixin, CreateView):
"""A create view for the admins."""
model = ProductType
fields = ["name", "description", "comment", "icon"]
template_name = "core/create.jinja"
current_tab = "products"
permission_required = "counter.add_producttype"
class ProductTypeEditView(CounterAdminTabsMixin, PermissionRequiredMixin, UpdateView):
class ProductTypeEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView):
"""An edit view for the admins."""
model = ProductType
@@ -151,26 +146,23 @@ class ProductTypeEditView(CounterAdminTabsMixin, PermissionRequiredMixin, Update
fields = ["name", "description", "comment", "icon"]
pk_url_kwarg = "type_id"
current_tab = "products"
permission_required = "counter.change_producttype"
class ProductListView(CounterAdminTabsMixin, PermissionRequiredMixin, TemplateView):
class ProductListView(CounterAdminTabsMixin, CounterAdminMixin, TemplateView):
current_tab = "products"
template_name = "counter/product_list.jinja"
permission_required = "counter.view_product"
class ProductCreateView(CounterAdminTabsMixin, PermissionRequiredMixin, CreateView):
class ProductCreateView(CounterAdminTabsMixin, CounterAdminMixin, CreateView):
"""A create view for the admins."""
model = Product
form_class = ProductForm
template_name = "counter/product_form.jinja"
current_tab = "products"
permission_required = "counter.add_product"
class ProductEditView(CounterAdminTabsMixin, PermissionRequiredMixin, UpdateView):
class ProductEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView):
"""An edit view for the admins."""
model = Product
@@ -178,13 +170,6 @@ class ProductEditView(CounterAdminTabsMixin, PermissionRequiredMixin, UpdateView
pk_url_kwarg = "product_id"
template_name = "counter/product_form.jinja"
current_tab = "products"
permission_required = "counter.change_product"
def has_permission(self):
return self.request.user.has_perm("counter.change_product") or (
self.request.method == "GET"
and self.request.user.has_perm("counter.view_product")
)
class ProductFormulaListView(CounterAdminTabsMixin, PermissionRequiredMixin, ListView):
@@ -314,6 +299,7 @@ class RefillingDeleteView(DeleteView):
template_name = "core/delete_confirm.jinja"
def dispatch(self, request, *args, **kwargs):
"""We have here a very particular right handling, we can't inherit from CanEditPropMixin."""
self.object = self.get_object()
if timezone.now() - self.object.date <= timedelta(
minutes=settings.SITH_LAST_OPERATIONS_LIMIT
@@ -338,6 +324,7 @@ class SellingDeleteView(DeleteView):
template_name = "core/delete_confirm.jinja"
def dispatch(self, request, *args, **kwargs):
"""We have here a very particular right handling, we can't inherit from CanEditPropMixin."""
self.object = self.get_object()
if timezone.now() - self.object.date <= timedelta(
minutes=settings.SITH_LAST_OPERATIONS_LIMIT
@@ -389,23 +376,23 @@ class CounterStatView(PermissionRequiredMixin, DetailView):
return kwargs
class CounterRefillingListView(
CounterAdminTabsMixin, PermissionRequiredMixin, ListView
):
class CounterRefillingListView(CounterAdminTabsMixin, CounterAdminMixin, ListView):
"""List of refillings on a counter."""
model = Refilling
template_name = "counter/refilling_list.jinja"
current_tab = "counters"
paginate_by = 30
permission_required = "counter.view_refilling"
def get_queryset(self):
self.counter = get_object_or_404(Counter, pk=self.kwargs["counter_id"])
return Refilling.objects.filter(counter=self.counter).order_by("-date")
def dispatch(self, request, *args, **kwargs):
self.counter = get_object_or_404(Counter, pk=kwargs["counter_id"])
self.queryset = Refilling.objects.filter(counter__id=self.counter.id)
return super().dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
return super().get_context_data(**kwargs) | {"counter": self.counter}
kwargs = super().get_context_data(**kwargs)
kwargs["counter"] = self.counter
return kwargs
class RefoundAccountView(UserPassesTestMixin, FormView):
+5 -8
View File
@@ -12,7 +12,7 @@
# OR WITHIN THE LOCAL FILE "LICENSE"
#
#
from django.contrib.auth.mixins import PermissionRequiredMixin
from django.http import Http404, HttpResponse
from django.utils.translation import gettext_lazy as _
from django.views.generic import DetailView, ListView
@@ -21,30 +21,28 @@ from django.views.generic.edit import CreateView, UpdateView
from core.auth.mixins import CanViewMixin
from counter.forms import EticketForm
from counter.models import Eticket, Selling
from counter.views.mixins import CounterAdminTabsMixin
from counter.views.mixins import CounterAdminMixin, CounterAdminTabsMixin
class EticketListView(CounterAdminTabsMixin, PermissionRequiredMixin, ListView):
class EticketListView(CounterAdminTabsMixin, CounterAdminMixin, ListView):
"""A list view for the admins."""
model = Eticket
template_name = "counter/eticket_list.jinja"
ordering = ["id"]
current_tab = "etickets"
permission_required = "counter.view_eticket"
class EticketCreateView(CounterAdminTabsMixin, PermissionRequiredMixin, CreateView):
class EticketCreateView(CounterAdminTabsMixin, CounterAdminMixin, CreateView):
"""Create an eticket."""
model = Eticket
template_name = "core/create.jinja"
form_class = EticketForm
current_tab = "etickets"
permission_required = "counter.add_eticket"
class EticketEditView(CounterAdminTabsMixin, PermissionRequiredMixin, UpdateView):
class EticketEditView(CounterAdminTabsMixin, CounterAdminMixin, UpdateView):
"""Edit an eticket."""
model = Eticket
@@ -52,7 +50,6 @@ class EticketEditView(CounterAdminTabsMixin, PermissionRequiredMixin, UpdateView
form_class = EticketForm
pk_url_kwarg = "eticket_id"
current_tab = "etickets"
permission_required = "counter.change_eticket"
class EticketPDFView(CanViewMixin, DetailView):
+67 -67
View File
@@ -13,13 +13,38 @@
#
#
from django.conf import settings
from django.core.exceptions import PermissionDenied
from django.urls import reverse, reverse_lazy
from django.utils.translation import gettext_lazy as _
from django.views.generic.base import View
from core.views.mixins import TabedViewMixin
from counter.utils import is_logged_in_counter
class CounterAdminMixin(View):
"""Protect counter admin section."""
edit_group = [settings.SITH_GROUP_COUNTER_ADMIN_ID]
edit_club = []
def _test_group(self, user):
return any(user.is_in_group(pk=grp_id) for grp_id in self.edit_group)
def _test_club(self, user):
return any(c.can_be_edited_by(user) for c in self.edit_club)
def dispatch(self, request, *args, **kwargs):
if not (
request.user.is_root
or self._test_group(request.user)
or self._test_club(request.user)
):
raise PermissionDenied
return super().dispatch(request, *args, **kwargs)
class CounterTabsMixin(TabedViewMixin):
def get_tabs_title(self):
return self.object
@@ -64,70 +89,45 @@ class CounterTabsMixin(TabedViewMixin):
class CounterAdminTabsMixin(TabedViewMixin):
tabs_title = _("Counter administration")
def get_list_of_tabs(self):
user = self.request.user
res = [
{
"url": reverse_lazy("counter:admin_list"),
"slug": "counters",
"name": _("Counters"),
}
]
if user.has_perm("counter.view_product"):
res.append(
{
"url": reverse_lazy("counter:product_list"),
"slug": "products",
"name": _("Products"),
}
)
if user.has_perm("counter.view_productformula"):
res.append(
{
"url": reverse_lazy("counter:product_formula_list"),
"slug": "formulas",
"name": _("Formulas"),
}
)
if user.has_perm("counter.view_producttype"):
res.append(
{
"url": reverse_lazy("counter:product_type_list"),
"slug": "product_types",
"name": _("Product types"),
}
)
if user.has_perm("counter.view_returnableproduct"):
res.append(
{
"url": reverse_lazy("counter:returnable_list"),
"slug": "returnable_products",
"name": _("Returnable products"),
}
)
if user.has_perm("counter.view_cashregistersummary"):
res.append(
{
"url": reverse_lazy("counter:cash_summary_list"),
"slug": "cash_summary",
"name": _("Cash register summaries"),
}
)
if user.has_perm("counter.view_invoicecall"):
res.append(
{
"url": reverse_lazy("counter:invoices_call"),
"slug": "invoices_call",
"name": _("Invoices call"),
}
)
if user.has_perm("counter.view_eticket"):
res.append(
{
"url": reverse_lazy("counter:eticket_list"),
"slug": "etickets",
"name": _("Etickets"),
}
)
return res
list_of_tabs = [
{
"url": reverse_lazy("counter:admin_list"),
"slug": "counters",
"name": _("Counters"),
},
{
"url": reverse_lazy("counter:product_list"),
"slug": "products",
"name": _("Products"),
},
{
"url": reverse_lazy("counter:product_formula_list"),
"slug": "formulas",
"name": _("Formulas"),
},
{
"url": reverse_lazy("counter:product_type_list"),
"slug": "product_types",
"name": _("Product types"),
},
{
"url": reverse_lazy("counter:returnable_list"),
"slug": "returnable_products",
"name": _("Returnable products"),
},
{
"url": reverse_lazy("counter:cash_summary_list"),
"slug": "cash_summary",
"name": _("Cash register summaries"),
},
{
"url": reverse_lazy("counter:invoices_call"),
"slug": "invoices_call",
"name": _("Invoices call"),
},
{
"url": reverse_lazy("counter:eticket_list"),
"slug": "etickets",
"name": _("Etickets"),
},
]
+5 -7
View File
@@ -48,7 +48,7 @@ des scripts complets mais qui restent lisibles,
```
Ok, c'est de la triche, on installe un package externe.
Mais bon, ça marche, et l'équivalent pur bash
serait carrément plus moche.
serait un pur enfer.
=== "nu"
@@ -59,20 +59,18 @@ des scripts complets mais qui restent lisibles,
Nombre de lignes total :
```nu
ls **/*.py | insert linecount { get name | open | lines | length } | math sum
ls **/*.py | par-each { open $in.name | lines | length } | math sum
```
Vous pouvez aussi exlure les lignes vides
et les les lignes de commentaire :
```nu
ls **/*.py |
insert linecount {
get name |
open |
par-each {
open $in.name |
lines |
each { str trim } |
filter { |l| not ($l | str starts-with "#") } | # commentaires
filter { |l| ($l | str length) > 0 } | # lignes vides
where { |l| ($l | str length) > 0 and not ($l | str starts-with "#") } |
length
} |
math sum
+40 -16
View File
@@ -18,7 +18,8 @@ il n'est pas possible de développer le site sur ce système d'exploitation.
Heureusement, il existe une alternative qui ne requiert pas de désinstaller votre
OS ni de mettre un dual boot sur votre ordinateur : `WSL`.
- **Prérequis:** vous devez être sur Windows 10 version 2004 ou ultérieure (build 19041 & versions ultérieures) ou Windows 11.
- **Prérequis:** vous devez être sur Windows 10 version 2004 ou ultérieure (build 19041 & versions ultérieures) ou
Windows 11.
- **Plus d'info:** [docs.microsoft.com](https://docs.microsoft.com/fr-fr/windows/wsl/install)
```shell
@@ -43,7 +44,7 @@ il suffit d'utiliser la commande suivante :
cd /mnt/<la_lettre_du_disque>/vos/fichiers/comme/dhab
```
!!!note
!!!success
À ce stade, si vous avez réussi votre installation de `WSL` ou bien qu'il
était déjà installé, vous pouvez effectuer la mise en place du projet en suivant
@@ -101,10 +102,10 @@ cd /mnt/<la_lettre_du_disque>/vos/fichiers/comme/dhab
ln -s /usr/bin/valkey-server /usr/bin/redis-server
```
=== "macOS"
Pour installer les dépendances, il est fortement recommandé d'installer le gestionnaire de paquets `homebrew <https://brew.sh/index_fr>`_.
Pour installer les dépendances, il est fortement recommandé
d'installer le gestionnaire de paquets `homebrew <https://brew.sh/index_fr>`_.
Il est également nécessaire d'avoir installé xcode
```bash
@@ -116,12 +117,15 @@ cd /mnt/<la_lettre_du_disque>/vos/fichiers/comme/dhab
!!!note
Si vous rencontrez des erreurs lors de votre configuration, n'hésitez pas à vérifier l'état de votre installation homebrew avec :code:`brew doctor`
Si vous rencontrez des erreurs lors de votre configuration,
n'hésitez pas à vérifier l'état de votre installation homebrew
avec :code:`brew doctor`
!!!note
Python ne fait pas parti des dépendances puisqu'il est automatiquement
installé par uv. Il est cependant parfois nécessaire d'installer les headers Python nécessaire à la compilation de certains paquets.
installé par uv. Il est cependant parfois nécessaire d'installer
les headers Python nécessaire à la compilation de certains paquets.
## Finaliser l'installation
@@ -145,7 +149,6 @@ uv run ./manage.py install_xapian
de texte à l'écran.
C'est normal, il ne faut pas avoir peur.
La modification de la configuration passe par la modification de variables
d'environnement.
Ces variables sont stockées dans un fichier `.env`.
@@ -174,8 +177,8 @@ uv run ./manage.py compilemessages
## Démarrer le serveur de développement
Il faut toujours avoir préalablement activé
l'environnement virtuel comme fait plus haut
Il faut toujours avoir préalablement activé
l'environnement virtuel comme fait plus haut
et se placer à la racine du projet.
Il suffit ensuite d'utiliser cette commande :
@@ -183,11 +186,32 @@ Il suffit ensuite d'utiliser cette commande :
uv run ./manage.py runserver
```
!!!note
Le serveur est alors accessible à l'adresse
[http://localhost:8000](http://localhost:8000)
ou bien [http://127.0.0.1:8000/](http://127.0.0.1:8000/).
Le serveur est alors accessible à l'adresse
[http://localhost:8000](http://localhost:8000)
ou bien [http://127.0.0.1:8000/](http://127.0.0.1:8000/).
Pour vous connecter, vous pouvez utiliser un des comptes utilisateur
créés durant l'installation :
| username | description |
|------------------|----------------------------------------------------------------------------|
| `root` | super-utilisateur<br/>il peut tout faire et tout voir |
| `subscriber` | simple cotisant |
| `old_subscriber` | ancien cotisant |
| `public` | non-cotisant |
| `krophil` | cotisant<br/>barman Foyer<br/>président du Troll Penché |
| `sli` | cotisant<br/>VP Troll |
| `skia` | cotisant<br/>barman MDE<br/>membre du bureau AE<br/>ancien membre du Troll |
| `comunity` | cotisant<br/>membre du bureau PdF<br/>admin communication |
| `counter` | cotisant<br/>admin comptoirs |
| `comptable` | cotisant<br/>admin comptabilité |
| `rbatsbak` | cotisant<br/>membre du Troll |
En plus de ces dernier, quelques centaines d'utilisateurs
supplémentaires sont générés procéduralement,
afin de rajouter un peu de volume de données.
Tous les utilisateurs ont le mot de passe `plop`.
!!!note
@@ -228,10 +252,10 @@ uv run ./manage.py runserver
La documentation est automatiquement mise en ligne à chaque envoi de code sur GitHub.
Pour l'utiliser en local ou globalement pour la modifier,
il existe une commande du site qui génère
il existe une commande du site qui génère
la documentation et lance un serveur la rendant
accessible à l'adresse [http://localhost:8080](http://localhost:8000).
Cette commande génère la documentation à
Cette commande génère la documentation à
chacune de ses modifications,
inutile de relancer le serveur à chaque fois.
@@ -241,7 +265,7 @@ uv run mkdocs serve
## Lancer les tests
Pour lancer les tests, il suffit d'utiliser
Pour lancer les tests, il suffit d'utiliser
la commande suivante :
```bash
+13 -11
View File
@@ -6,7 +6,7 @@
msgid ""
msgstr ""
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-10-09 21:44+0200\n"
"POT-Creation-Date: 2026-10-02 12:56+0200\n"
"PO-Revision-Date: 2016-07-18\n"
"Last-Translator: Maréchal <thomas.girod@utbm.fr\n"
"Language-Team: AE info <ae.info@utbm.fr>\n"
@@ -236,7 +236,7 @@ msgstr "vous devez spécifier au moins un utilisateur ou une adresse email"
msgid "Begin date"
msgstr "Date de début"
#: club/forms.py com/forms.py counter/forms.py
#: club/forms.py com/forms.py counter/forms.py subscription/forms.py
msgid "End date"
msgstr "Date de fin"
@@ -1093,7 +1093,7 @@ msgstr "Prix d'achat"
msgid "Format: 16:9 | Resolution: 1920x1080"
msgstr "Format : 16:9 | Résolution : 1920x1080"
#: com/forms.py
#: com/forms.py subscription/forms.py
msgid "Start date"
msgstr "Date de début"
@@ -4102,6 +4102,7 @@ msgstr "Nouvelle formule"
#: counter/templates/counter/fragments/create_student_card.jinja
#: counter/templates/counter/invoices_call.jinja
#: sas/templates/sas/picture.jinja
#: subscription/templates/subscription/stats.jinja
msgid "Go"
msgstr "Valider"
@@ -4236,12 +4237,9 @@ msgid "Remove price"
msgstr "Retirer le prix"
#: counter/templates/counter/product_form.jinja
msgid ""
"You can see product details. However, you cannot edit it, thus you won't be "
"able to submit this form."
msgstr ""
"Vous pouvez voir ce produit. Cependant, vous ne pouvez pas l'éditer ; vous "
"ne serez donc pas en mesure de soumettre le formulaire."
#, python-format
msgid "Edit product %(name)s"
msgstr "Édition du produit %(name)s"
#: counter/templates/counter/product_form.jinja
msgid "Creation date"
@@ -5987,8 +5985,12 @@ msgid "Create another subscription"
msgstr "Créer une nouvelle cotisation"
#: subscription/templates/subscription/stats.jinja
msgid "All subscriptions"
msgstr "Toutes les cotisations"
msgid "Total subscriptions"
msgstr "Cotisations totales"
#: subscription/templates/subscription/stats.jinja
msgid "Subscriptions by type"
msgstr "Cotisations par type"
#: subscription/templates/subscription/subscription.jinja
msgid "Existing member"