diff --git a/club/models.py b/club/models.py
index 00c9239d..d446c080 100644
--- a/club/models.py
+++ b/club/models.py
@@ -276,18 +276,6 @@ class Membership(models.Model):
_("description"), max_length=128, null=False, blank=True
)
- def clean(self):
- sub = User.objects.filter(pk=self.user.pk).first()
- if sub is None or not sub.is_subscribed:
- raise ValidationError(_("User must be subscriber to take part to a club"))
- if (
- Membership.objects.filter(user=self.user)
- .filter(club=self.club)
- .filter(end_date=None)
- .exists()
- ):
- raise ValidationError(_("User is already member of that club"))
-
def __str__(self):
return (
self.club.name
@@ -304,12 +292,15 @@ class Membership(models.Model):
"""
return user.is_in_group(settings.SITH_MAIN_BOARD_GROUP)
- def can_be_edited_by(self, user):
+ def can_be_edited_by(self, user, membership=None):
"""
Method to see if that object can be edited by the given user
"""
if user.memberships:
- ms = user.memberships.filter(club=self.club, end_date=None).first()
+ if membership: # This is for optimisation purpose
+ ms = membership
+ else:
+ ms = user.memberships.filter(club=self.club, end_date=None).first()
return (ms and ms.role >= self.role) or user.is_in_group(
settings.SITH_MAIN_BOARD_GROUP
)
diff --git a/club/templates/club/club_members.jinja b/club/templates/club/club_members.jinja
index 5f68a9df..7c378a7d 100644
--- a/club/templates/club/club_members.jinja
+++ b/club/templates/club/club_members.jinja
@@ -1,35 +1,80 @@
{% extends "core/base.jinja" %}
-{% from 'core/macros.jinja' import user_profile_link %}
+{% from 'core/macros.jinja' import user_profile_link, select_all_checkbox %}
{% block content %}
{% trans %}Club members{% endtrans %}
-
-
- {% trans %}User{% endtrans %} |
- {% trans %}Role{% endtrans %} |
- {% trans %}Description{% endtrans %} |
- {% trans %}Since{% endtrans %} |
-
-
- {% for m in club.members.filter(end_date=None).order_by('-role').all() %}
-
- {{ user_profile_link(m.user) }} |
- {{ settings.SITH_CLUB_ROLES[m.role] }} |
- {{ m.description }} |
- {{ m.start_date }} |
- {% if m.can_be_edited_by(user) %}
- {% trans %}Mark as old{% endtrans %} |
- {% endif %}
-
- {% endfor %}
-
-
-
+ {% else %}
+ {% trans %}There are no members in this club.{% endtrans %}
+ {% endif %}
+
{% endblock %}
-
-
-
diff --git a/club/tests.py b/club/tests.py
index c0409a9f..25f6e019 100644
--- a/club/tests.py
+++ b/club/tests.py
@@ -44,30 +44,54 @@ class ClubTest(TestCase):
self.client.login(username="root", password="plop")
self.client.post(
reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
- {"user": self.skia.id, "start_date": "12/06/2016", "role": 3},
+ {"users": self.skia.id, "start_date": "12/06/2016", "role": 3},
)
response = self.client.get(
reverse("club:club_members", kwargs={"club_id": self.bdf.id})
)
self.assertTrue(response.status_code == 200)
self.assertTrue(
- "S' Kia\\n Responsable info | "
+ "S' Kia\\n Responsable info | "
in str(response.content)
)
+ def test_create_add_multiple_user_to_club_from_root_ok(self):
+ self.client.login(username="root", password="plop")
+ self.client.post(
+ reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
+ {
+ "users": "|%d|%d|" % (self.skia.id, self.rbatsbak.id),
+ "start_date": "12/06/2016",
+ "role": 3,
+ },
+ )
+ response = self.client.get(
+ reverse("club:club_members", kwargs={"club_id": self.bdf.id})
+ )
+ self.assertTrue(response.status_code == 200)
+ content = str(response.content)
+ self.assertTrue(
+ "S' Kia\\n Responsable info | "
+ in content
+ )
+ self.assertTrue(
+ "Richard Batsbak\\n Responsable info | "
+ in content
+ )
+
def test_create_add_user_to_club_from_root_fail_not_subscriber(self):
self.client.login(username="root", password="plop")
response = self.client.post(
reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
- {"user": self.guy.id, "start_date": "12/06/2016", "role": 3},
+ {"users": self.guy.id, "start_date": "12/06/2016", "role": 3},
)
self.assertTrue(response.status_code == 200)
- self.assertTrue('- ' in str(response.content))
+ self.assertTrue('
- ' in str(response.content))
response = self.client.get(
reverse("club:club_members", kwargs={"club_id": self.bdf.id})
)
self.assertFalse(
- "Guy Carlier\\n
Responsable info | "
+ "Guy Carlier\\n Responsable info | "
in str(response.content)
)
@@ -75,18 +99,18 @@ class ClubTest(TestCase):
self.client.login(username="root", password="plop")
self.client.post(
reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
- {"user": self.skia.id, "start_date": "12/06/2016", "role": 3},
+ {"users": self.skia.id, "start_date": "12/06/2016", "role": 3},
)
response = self.client.get(
reverse("club:club_members", kwargs={"club_id": self.bdf.id})
)
self.assertTrue(
- "S' Kia\\n Responsable info | "
+ "S' Kia\\n Responsable info | "
in str(response.content)
)
response = self.client.post(
reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
- {"user": self.skia.id, "start_date": "12/06/2016", "role": 4},
+ {"users": self.skia.id, "start_date": "12/06/2016", "role": 4},
)
self.assertTrue(response.status_code == 200)
self.assertFalse(
@@ -94,23 +118,47 @@ class ClubTest(TestCase):
in str(response.content)
)
+ def test_create_add_user_non_existent_to_club_from_root_fail(self):
+ self.client.login(username="root", password="plop")
+ response = self.client.post(
+ reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
+ {"users": [9999], "start_date": "12/06/2016", "role": 3},
+ )
+ self.assertTrue(response.status_code == 200)
+ content = str(response.content)
+ self.assertTrue('- ' in content)
+ self.assertFalse("
Responsable info | " in content)
+ self.client.login(username="root", password="plop")
+ response = self.client.post(
+ reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
+ {
+ "users": "|%d|%d|" % (self.skia.id, 9999),
+ "start_date": "12/06/2016",
+ "role": 3,
+ },
+ )
+ self.assertTrue(response.status_code == 200)
+ content = str(response.content)
+ self.assertTrue('- ' in content)
+ self.assertFalse("
Responsable info | " in content)
+
def test_create_add_user_to_club_from_skia_ok(self):
self.client.login(username="root", password="plop")
self.client.post(
reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
- {"user": self.skia.id, "start_date": "12/06/2016", "role": 10},
+ {"users": self.skia.id, "start_date": "12/06/2016", "role": 10},
)
self.client.login(username="skia", password="plop")
self.client.post(
reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
- {"user": self.rbatsbak.id, "start_date": "12/06/2016", "role": 9},
+ {"users": self.rbatsbak.id, "start_date": "12/06/2016", "role": 9},
)
response = self.client.get(
reverse("club:club_members", kwargs={"club_id": self.bdf.id})
)
self.assertTrue(response.status_code == 200)
self.assertTrue(
- """Richard Batsbak\\n Vice-Pr\\xc3\\xa9sident | """
+ """Richard Batsbak\\n Vice-Pr\\xc3\\xa9sident | """
in str(response.content)
)
@@ -118,15 +166,210 @@ class ClubTest(TestCase):
self.client.login(username="root", password="plop")
self.client.post(
reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
- {"user": self.rbatsbak.id, "start_date": "12/06/2016", "role": 3},
+ {"users": self.rbatsbak.id, "start_date": "12/06/2016", "role": 3},
)
self.client.login(username="rbatsbak", password="plop")
response = self.client.post(
reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
- {"user": self.skia.id, "start_date": "12/06/2016", "role": 10},
+ {"users": self.skia.id, "start_date": "12/06/2016", "role": 10},
)
self.assertTrue(response.status_code == 200)
self.assertTrue(
" - Vous n'avez pas la permission de faire cela
"
in str(response.content)
)
+
+ def test_role_required_if_users_specified(self):
+ self.client.login(username="root", password="plop")
+ response = self.client.post(
+ reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
+ {"users": self.rbatsbak.id, "start_date": "12/06/2016"},
+ )
+ self.assertTrue(
+ '- Vous devez choisir un r' in str(response.content)
+ )
+
+ def test_mark_old_user_to_club_from_skia_ok(self):
+ self.client.login(username="root", password="plop")
+ self.client.post(
+ reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
+ {
+ "users": "|%d|%d|" % (self.skia.id, self.rbatsbak.id),
+ "start_date": "12/06/2016",
+ "role": 3,
+ },
+ )
+ self.client.login(username="skia", password="plop")
+ response = self.client.post(
+ reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
+ {"users_old": self.rbatsbak.id},
+ )
+ self.assertTrue(response.status_code == 302)
+
+ response = self.client.get(
+ reverse("club:club_members", kwargs={"club_id": self.bdf.id})
+ )
+ self.assertTrue(response.status_code == 200)
+ content = str(response.content)
+ self.assertFalse(
+ "Richard Batsbak\\n
Responsable info | "
+ in content
+ )
+ self.assertTrue(
+ "S' Kia\\n Responsable info | "
+ in content
+ )
+
+ # Skia is board member so he should be able to mark as old even without being in the club
+ response = self.client.post(
+ reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
+ {"users_old": self.skia.id},
+ )
+ self.client.login(username="root", password="plop")
+ self.client.post(
+ reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
+ {"users": self.rbatsbak.id, "start_date": "12/06/2016", "role": 3},
+ )
+ self.client.login(username="skia", password="plop")
+ response = self.client.post(
+ reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
+ {"users_old": self.rbatsbak.id},
+ )
+ self.assertFalse(
+ "Richard Batsbak\\n Responsable info | "
+ in str(response.content)
+ )
+
+ def test_mark_old_multiple_users_from_skia_ok(self):
+ self.client.login(username="root", password="plop")
+ self.client.post(
+ reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
+ {
+ "users": "|%d|%d|" % (self.skia.id, self.rbatsbak.id),
+ "start_date": "12/06/2016",
+ "role": 3,
+ },
+ )
+ self.client.login(username="skia", password="plop")
+ response = self.client.post(
+ reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
+ {"users_old": [self.rbatsbak.id, self.skia.id]},
+ )
+ self.assertTrue(response.status_code == 302)
+
+ response = self.client.get(
+ reverse("club:club_members", kwargs={"club_id": self.bdf.id})
+ )
+ self.assertTrue(response.status_code == 200)
+ content = str(response.content)
+ self.assertFalse(
+ "Richard Batsbak\\n Responsable info | "
+ in content
+ )
+ self.assertFalse(
+ "S' Kia\\n Responsable info | "
+ in content
+ )
+
+ def test_mark_old_user_to_club_from_richard_ok(self):
+ self.client.login(username="root", password="plop")
+ self.client.post(
+ reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
+ {
+ "users": "|%d|%d|" % (self.skia.id, self.rbatsbak.id),
+ "start_date": "12/06/2016",
+ "role": 3,
+ },
+ )
+
+ # Test with equal rights
+ self.client.login(username="rbatsbak", password="plop")
+ response = self.client.post(
+ reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
+ {"users_old": self.skia.id},
+ )
+ self.assertTrue(response.status_code == 302)
+
+ response = self.client.get(
+ reverse("club:club_members", kwargs={"club_id": self.bdf.id})
+ )
+ self.assertTrue(response.status_code == 200)
+ content = str(response.content)
+ self.assertTrue(
+ "Richard Batsbak\\n Responsable info | "
+ in content
+ )
+ self.assertFalse(
+ "S' Kia\\n Responsable info | "
+ in content
+ )
+
+ # Test with lower rights
+ self.client.post(
+ reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
+ {"users": self.skia.id, "start_date": "12/06/2016", "role": 0},
+ )
+
+ self.client.post(
+ reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
+ {"users_old": self.skia.id},
+ )
+ response = self.client.get(
+ reverse("club:club_members", kwargs={"club_id": self.bdf.id})
+ )
+ self.assertTrue(response.status_code == 200)
+ content = str(response.content)
+ self.assertTrue(
+ "Richard Batsbak\\n Responsable info | "
+ in content
+ )
+ self.assertFalse(
+ "S' Kia\\n Curieux | " in content
+ )
+
+ def test_mark_old_user_to_club_from_richard_fail(self):
+ self.client.login(username="root", password="plop")
+ self.client.post(
+ reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
+ {"users": self.skia.id, "start_date": "12/06/2016", "role": 3},
+ )
+
+ # Test with richard outside of the club
+ self.client.login(username="rbatsbak", password="plop")
+ response = self.client.post(
+ reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
+ {"users_old": self.skia.id},
+ )
+ self.assertTrue(response.status_code == 200)
+
+ response = self.client.get(
+ reverse("club:club_members", kwargs={"club_id": self.bdf.id})
+ )
+ self.assertTrue(response.status_code == 200)
+ self.assertTrue(
+ "S' Kia\\n Responsable info | "
+ in str(response.content)
+ )
+
+ # Test with lower rights
+ self.client.post(
+ reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
+ {"users": self.rbatsbak.id, "start_date": "12/06/2016", "role": 0},
+ )
+
+ self.client.post(
+ reverse("club:club_members", kwargs={"club_id": self.bdf.id}),
+ {"users_old": self.skia.id},
+ )
+ response = self.client.get(
+ reverse("club:club_members", kwargs={"club_id": self.bdf.id})
+ )
+ self.assertTrue(response.status_code == 200)
+ content = str(response.content)
+ self.assertTrue(
+ "Richard Batsbak\\n Curieux | " in content
+ )
+ self.assertTrue(
+ "S' Kia\\n Responsable info | "
+ in content
+ )
diff --git a/club/views.py b/club/views.py
index 1b460b2c..7f50be06 100644
--- a/club/views.py
+++ b/club/views.py
@@ -33,7 +33,7 @@ from django.core.urlresolvers import reverse, reverse_lazy
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ugettext as _t
-from ajax_select.fields import AutoCompleteSelectField
+from ajax_select.fields import AutoCompleteSelectField, AutoCompleteSelectMultipleField
from django.core.exceptions import PermissionDenied
from django.shortcuts import get_object_or_404, redirect
@@ -44,6 +44,7 @@ from core.views import (
CanEditPropMixin,
TabedViewMixin,
PageEditViewBase,
+ DetailFormView,
)
from core.views.forms import SelectDate, SelectDateTime
from club.models import Club, Membership, Mailing, MailingSubscription
@@ -305,7 +306,7 @@ class ClubToolsView(ClubTabsMixin, CanEditMixin, DetailView):
current_tab = "tools"
-class ClubMemberForm(forms.ModelForm):
+class ClubMemberForm(forms.Form):
"""
Form handling the members of a club
"""
@@ -313,24 +314,115 @@ class ClubMemberForm(forms.ModelForm):
error_css_class = "error"
required_css_class = "required"
- class Meta:
- model = Membership
- fields = ["user", "role", "start_date", "description"]
- widgets = {"start_date": SelectDate}
-
- user = AutoCompleteSelectField(
- "users", required=True, label=_("Select user"), help_text=None
+ users = AutoCompleteSelectMultipleField(
+ "users",
+ label=_("Users to add"),
+ help_text=_("Search users to add (one or more)."),
+ required=False,
)
- def save(self, *args, **kwargs):
+ def __init__(self, *args, **kwargs):
+ self.club = kwargs.pop("club")
+ self.request_user = kwargs.pop("request_user")
+ self.club_members = kwargs.pop("club_members", None)
+ if not self.club_members:
+ self.club_members = (
+ self.club.members.filter(end_date=None).order_by("-role").all()
+ )
+ self.request_user_membership = self.club.get_membership_for(self.request_user)
+ super(ClubMemberForm, self).__init__(*args, **kwargs)
+
+ # Using a ModelForm binds too much the form with the model and we don't want that
+ # We want the view to process the model creation since they are multiple users
+ # We also want the form to handle bulk deletion
+ self.fields.update(
+ forms.fields_for_model(
+ Membership,
+ fields=("role", "start_date", "description"),
+ widgets={"start_date": SelectDate},
+ )
+ )
+
+ # Role is required only if users is specified
+ self.fields["role"].required = False
+
+ # Start date and description are never really required
+ self.fields["start_date"].required = False
+ self.fields["description"].required = False
+
+ self.fields["users_old"] = forms.ModelMultipleChoiceField(
+ User.objects.filter(
+ id__in=[
+ ms.user.id
+ for ms in self.club_members
+ if ms.can_be_edited_by(
+ self.request_user, self.request_user_membership
+ )
+ ]
+ ).all(),
+ label=_("Mark as old"),
+ required=False,
+ widget=forms.CheckboxSelectMultiple,
+ )
+ if not self.request_user.is_root:
+ self.fields.pop("start_date")
+
+ def clean_users(self):
"""
- Overloaded to return the club, and not to a Membership object that has no view
+ Check that the user is not trying to add an user already in the club
+ Also check that the user is valid and has a valid subscription
"""
- super(ClubMemberForm, self).save(*args, **kwargs)
- return self.instance.club
+ cleaned_data = super(ClubMemberForm, self).clean()
+ users = []
+ for user_id in cleaned_data["users"]:
+ user = User.objects.filter(id=user_id).first()
+ if not user:
+ raise forms.ValidationError(
+ _("One of the selected users doesn't exist"), code="invalid"
+ )
+ if not user.is_subscribed:
+ raise forms.ValidationError(
+ _("User must be subscriber to take part to a club"), code="invalid"
+ )
+ if self.club.get_membership_for(user):
+ raise forms.ValidationError(
+ _("You can not add the same user twice"), code="invalid"
+ )
+ users.append(user)
+ return users
+
+ def clean(self):
+ """
+ Check user rights for adding an user
+ """
+ cleaned_data = super(ClubMemberForm, self).clean()
+
+ if "start_date" in cleaned_data and not cleaned_data["start_date"]:
+ # Drop start_date if allowed to edition but not specified
+ cleaned_data.pop("start_date")
+
+ if not cleaned_data.get("users"):
+ # No user to add equals no check needed
+ return cleaned_data
+
+ if cleaned_data.get("role", "") == "":
+ # Role is required if users exists
+ self.add_error("role", _("You should specify a role"))
+ return cleaned_data
+
+ request_user = self.request_user
+ membership = self.request_user_membership
+ if not (
+ cleaned_data["role"] <= SITH_MAXIMUM_FREE_ROLE
+ or (membership is not None and membership.role >= cleaned_data["role"])
+ or request_user.is_board_member
+ or request_user.is_root
+ ):
+ raise forms.ValidationError(_("You do not have the permission to do that"))
+ return cleaned_data
-class ClubMembersView(ClubTabsMixin, CanViewMixin, UpdateView):
+class ClubMembersView(ClubTabsMixin, CanViewMixin, DetailFormView):
"""
View of a club's members
"""
@@ -341,52 +433,45 @@ class ClubMembersView(ClubTabsMixin, CanViewMixin, UpdateView):
template_name = "club/club_members.jinja"
current_tab = "members"
- def get_form(self):
- """
- Here we get a Membership object, but the view handles Club object.
- That's why the save method of ClubMemberForm is overridden.
- """
- form = super(ClubMembersView, self).get_form()
- if (
- "user" in form.data and form.data.get("user") != ""
- ): # Load an existing membership if possible
- form.instance = (
- Membership.objects.filter(club=self.object)
- .filter(user=form.data.get("user"))
- .filter(end_date=None)
- .first()
- )
- if form.instance is None: # Instanciate a new membership
- form.instance = Membership(club=self.object, user=self.request.user)
- if not self.request.user.is_root:
- form.fields.pop("start_date", None)
- return form
+ def get_form_kwargs(self):
+ kwargs = super(ClubMembersView, self).get_form_kwargs()
+ kwargs["request_user"] = self.request.user
+ kwargs["club"] = self.get_object()
+ kwargs["club_members"] = self.members
+ return kwargs
+
+ def get_context_data(self, *args, **kwargs):
+ kwargs = super(ClubMembersView, self).get_context_data(*args, **kwargs)
+ kwargs["members"] = self.members
+ return kwargs
def form_valid(self, form):
"""
Check user rights
"""
- user = self.request.user
- ms = self.object.get_membership_for(user)
- if (
- form.cleaned_data["role"] <= SITH_MAXIMUM_FREE_ROLE
- or (ms is not None and ms.role >= form.cleaned_data["role"])
- or user.is_board_member
- or user.is_root
- ):
- form.save()
- form = self.form_class()
- return super(ModelFormMixin, self).form_valid(form)
- else:
- form.add_error(None, _("You do not have the permission to do that"))
- return self.form_invalid(form)
+ resp = super(ClubMembersView, self).form_valid(form)
+
+ data = form.clean()
+ users = data.pop("users", [])
+ users_old = data.pop("users_old", [])
+ for user in users:
+ Membership(club=self.get_object(), user=user, **data).save()
+ for user in users_old:
+ membership = self.get_object().get_membership_for(user)
+ membership.end_date = timezone.now()
+ membership.save()
+ return resp
def dispatch(self, request, *args, **kwargs):
- self.request = request
+ self.members = (
+ self.get_object().members.filter(end_date=None).order_by("-role").all()
+ )
return super(ClubMembersView, self).dispatch(request, *args, **kwargs)
def get_success_url(self, **kwargs):
- return reverse_lazy("club:club_members", kwargs={"club_id": self.club.id})
+ return reverse_lazy(
+ "club:club_members", kwargs={"club_id": self.get_object().id}
+ )
class ClubOldMembersView(ClubTabsMixin, CanViewMixin, DetailView):
diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po
index a62e1c94..98409cfc 100644
--- a/locale/fr/LC_MESSAGES/django.po
+++ b/locale/fr/LC_MESSAGES/django.po
@@ -6,7 +6,7 @@
msgid ""
msgstr ""
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2019-04-22 14:57+0200\n"
+"POT-Creation-Date: 2019-04-25 17:35+0200\n"
"PO-Revision-Date: 2016-07-18\n"
"Last-Translator: Skia \n"
"Language-Team: AE info \n"
@@ -174,10 +174,10 @@ msgstr "étiquette"
msgid "target type"
msgstr "type de cible"
-#: accounting/models.py:313 club/models.py:422
-#: club/templates/club/club_members.jinja:8
+#: accounting/models.py:313 club/models.py:413
+#: club/templates/club/club_members.jinja:16
#: club/templates/club/club_old_members.jinja:8
-#: club/templates/club/mailing.jinja:28 club/views.py:111
+#: club/templates/club/mailing.jinja:28 club/views.py:112
#: counter/templates/counter/cash_summary_list.jinja:32
#: counter/templates/counter/stats.jinja:15
#: counter/templates/counter/stats.jinja:52
@@ -186,7 +186,7 @@ msgstr "type de cible"
msgid "User"
msgstr "Utilisateur"
-#: accounting/models.py:314 club/models.py:329
+#: accounting/models.py:314 club/models.py:320
#: club/templates/club/club_detail.jinja:12
#: com/templates/com/mailing_admin.jinja:11
#: com/templates/com/news_admin_list.jinja:23
@@ -386,7 +386,7 @@ msgid "Delete"
msgstr "Supprimer"
#: accounting/templates/accounting/bank_account_details.jinja:18
-#: club/views.py:128 core/views/user.py:205 sas/templates/sas/picture.jinja:86
+#: club/views.py:129 core/views/user.py:205 sas/templates/sas/picture.jinja:86
msgid "Infos"
msgstr "Infos"
@@ -405,7 +405,7 @@ msgstr "Nouveau compte club"
#: accounting/templates/accounting/bank_account_details.jinja:27
#: accounting/templates/accounting/bank_account_list.jinja:22
#: accounting/templates/accounting/club_account_details.jinja:58
-#: accounting/templates/accounting/journal_details.jinja:89 club/views.py:174
+#: accounting/templates/accounting/journal_details.jinja:89 club/views.py:175
#: com/templates/com/news_admin_list.jinja:39
#: com/templates/com/news_admin_list.jinja:68
#: com/templates/com/news_admin_list.jinja:115
@@ -955,48 +955,40 @@ msgstr "rôle"
msgid "description"
msgstr "description"
-#: club/models.py:282
-msgid "User must be subscriber to take part to a club"
-msgstr "L'utilisateur doit être cotisant pour faire partie d'un club"
-
-#: club/models.py:289
-msgid "User is already member of that club"
-msgstr "L'utilisateur est déjà membre de ce club"
-
-#: club/models.py:298
+#: club/models.py:286
msgid "past member"
msgstr "Anciens membres"
-#: club/models.py:332 club/models.py:427
+#: club/models.py:323 club/models.py:418
msgid "Email address"
msgstr "Adresse email"
-#: club/models.py:340
+#: club/models.py:331
msgid "Enter a valid address. Only the root of the address is needed."
msgstr ""
"Entrez une adresse valide. Seule la racine de l'adresse est nécessaire."
-#: club/models.py:344 com/models.py:79 com/models.py:260 core/models.py:810
+#: club/models.py:335 com/models.py:79 com/models.py:260 core/models.py:810
msgid "is moderated"
msgstr "est modéré"
-#: club/models.py:346 com/models.py:81 com/models.py:264
+#: club/models.py:337 com/models.py:81 com/models.py:264
msgid "moderator"
msgstr "modérateur"
-#: club/models.py:415 club/templates/club/mailing.jinja:14
+#: club/models.py:406 club/templates/club/mailing.jinja:14
msgid "Mailing"
msgstr "Liste de diffusion"
-#: club/models.py:434
+#: club/models.py:425
msgid "At least user or email is required"
msgstr "Au moins un utilisateur ou un email est nécessaire"
-#: club/models.py:442
+#: club/models.py:433
msgid "This email is already suscribed in this mailing"
msgstr "Cet email est déjà abonné à cette mailing"
-#: club/models.py:471 club/templates/club/mailing.jinja:36
+#: club/models.py:462 club/templates/club/mailing.jinja:36
msgid "Unregistered user"
msgstr "Désabonner un utilisateur"
@@ -1021,7 +1013,7 @@ msgstr "Il n'y a pas de club dans ce site web."
msgid "Club members"
msgstr "Membres du club"
-#: club/templates/club/club_members.jinja:9
+#: club/templates/club/club_members.jinja:17
#: club/templates/club/club_old_members.jinja:9
#: core/templates/core/user_clubs.jinja:16
#: core/templates/core/user_clubs.jinja:42
@@ -1031,7 +1023,7 @@ msgstr "Membres du club"
msgid "Role"
msgstr "Rôle"
-#: club/templates/club/club_members.jinja:10
+#: club/templates/club/club_members.jinja:18
#: club/templates/club/club_old_members.jinja:10
#: core/templates/core/group_list.jinja:15
#: core/templates/core/user_clubs.jinja:17
@@ -1039,18 +1031,23 @@ msgstr "Rôle"
msgid "Description"
msgstr "Description"
-#: club/templates/club/club_members.jinja:11
+#: club/templates/club/club_members.jinja:19
#: core/templates/core/user_clubs.jinja:18
#: launderette/templates/launderette/launderette_admin.jinja:45
msgid "Since"
msgstr "Depuis"
#: club/templates/club/club_members.jinja:21
+#: club/templates/club/club_members.jinja:46 club/views.py:363
#: core/templates/core/user_clubs.jinja:29
msgid "Mark as old"
msgstr "Marquer comme ancien"
-#: club/templates/club/club_members.jinja:30
+#: club/templates/club/club_members.jinja:50
+msgid "There are no members in this club."
+msgstr "Il n'y a pas de membres dans ce club."
+
+#: club/templates/club/club_members.jinja:78
#: core/templates/core/file_detail.jinja:19 core/views/forms.py:355
#: launderette/views.py:226 trombi/templates/trombi/detail.jinja:19
msgid "Add"
@@ -1070,8 +1067,8 @@ msgstr "Du"
msgid "To"
msgstr "Au"
-#: club/templates/club/club_sellings.jinja:5 club/views.py:194
-#: club/views.py:478 counter/templates/counter/counter_main.jinja:19
+#: club/templates/club/club_sellings.jinja:5 club/views.py:195
+#: club/views.py:564 counter/templates/counter/counter_main.jinja:19
#: counter/templates/counter/last_ops.jinja:35
msgid "Sellings"
msgstr "Ventes"
@@ -1097,7 +1094,7 @@ msgstr "unités"
msgid "Benefit: "
msgstr "Bénéfice : "
-#: club/templates/club/club_sellings.jinja:21 club/views.py:417
+#: club/templates/club/club_sellings.jinja:21 club/views.py:503
#: core/templates/core/user_account_detail.jinja:18
#: core/templates/core/user_account_detail.jinja:51
#: counter/templates/counter/cash_summary_list.jinja:33 counter/views.py:168
@@ -1246,60 +1243,79 @@ msgstr "Aucune page n'existe pour ce club"
msgid "Club stats"
msgstr "Statistiques du club"
-#: club/views.py:138
+#: club/views.py:139
msgid "Members"
msgstr "Membres"
-#: club/views.py:147
+#: club/views.py:148
msgid "Old members"
msgstr "Anciens membres"
-#: club/views.py:157 core/templates/core/page.jinja:33
+#: club/views.py:158 core/templates/core/page.jinja:33
msgid "History"
msgstr "Historique"
-#: club/views.py:165 core/templates/core/base.jinja:121 core/views/user.py:228
+#: club/views.py:166 core/templates/core/base.jinja:121 core/views/user.py:228
#: sas/templates/sas/picture.jinja:95 trombi/views.py:60
msgid "Tools"
msgstr "Outils"
-#: club/views.py:185
+#: club/views.py:186
msgid "Edit club page"
msgstr "Éditer la page de club"
-#: club/views.py:201
+#: club/views.py:202
msgid "Mailing list"
msgstr "Listes de diffusion"
-#: club/views.py:210 com/views.py:141
+#: club/views.py:211 com/views.py:141
msgid "Posters list"
msgstr "Liste d'affiches"
-#: club/views.py:220 counter/templates/counter/counter_list.jinja:21
+#: club/views.py:221 counter/templates/counter/counter_list.jinja:21
#: counter/templates/counter/counter_list.jinja:43
#: counter/templates/counter/counter_list.jinja:59
msgid "Props"
msgstr "Propriétés"
-#: club/views.py:322 core/views/forms.py:358 counter/views.py:113
-#: trombi/views.py:141
-msgid "Select user"
-msgstr "Choisir un utilisateur"
+#: club/views.py:319
+msgid "Users to add"
+msgstr "Utilisateurs à ajouter"
-#: club/views.py:381 sas/views.py:129 sas/views.py:195 sas/views.py:286
+#: club/views.py:320 core/views/group.py:63
+msgid "Search users to add (one or more)."
+msgstr "Recherche les utilisateurs à ajouter (un ou plus)."
+
+#: club/views.py:381
+msgid "One of the selected users doesn't exist"
+msgstr "Un des utilisateurs sélectionné n'existe pas"
+
+#: club/views.py:385
+msgid "User must be subscriber to take part to a club"
+msgstr "L'utilisateur doit être cotisant pour faire partie d'un club"
+
+#: club/views.py:389 core/views/group.py:82
+msgid "You can not add the same user twice"
+msgstr "Vous ne pouvez pas ajouter deux fois le même utilisateur"
+
+#: club/views.py:410
+msgid "You should specify a role"
+msgstr "Vous devez choisir un rôle"
+
+#: club/views.py:421 sas/views.py:129 sas/views.py:195 sas/views.py:286
msgid "You do not have the permission to do that"
msgstr "Vous n'avez pas la permission de faire cela"
-#: club/views.py:406 counter/views.py:1481
+#: club/views.py:492 counter/views.py:1481
msgid "Begin date"
msgstr "Date de début"
-#: club/views.py:412 com/views.py:85 com/views.py:221 counter/views.py:1487
+#: club/views.py:498 com/views.py:85 com/views.py:221 counter/views.py:1487
#: election/views.py:190 subscription/views.py:52
msgid "End date"
msgstr "Date de fin"
-#: club/views.py:435 core/templates/core/user_stats.jinja:27
+#: club/views.py:521 core/templates/core/user_stats.jinja:27
#: counter/views.py:1635
msgid "Product"
msgstr "Produit"
@@ -3507,6 +3523,10 @@ msgstr "Parrain"
msgid "Godchild"
msgstr "Fillot"
+#: core/views/forms.py:358 counter/views.py:113 trombi/views.py:141
+msgid "Select user"
+msgstr "Choisir un utilisateur"
+
#: core/views/forms.py:371 core/views/forms.py:389 election/models.py:24
#: election/views.py:167
msgid "edit groups"
@@ -3525,14 +3545,6 @@ msgstr "Utilisateurs à retirer du groupe"
msgid "Users to add to group"
msgstr "Utilisateurs à ajouter au groupe"
-#: core/views/group.py:63
-msgid "Search users to add (one or more)."
-msgstr "Recherche les utilisateurs à ajouter (un ou plus)."
-
-#: core/views/group.py:82
-msgid "You can not add the same user twice"
-msgstr "Vous ne pouvez pas ajouter deux fois le même utilisateur"
-
#: core/views/user.py:223 trombi/templates/trombi/export.jinja:25
#: trombi/templates/trombi/user_profile.jinja:11
msgid "Pictures"