diff --git a/club/forms.py b/club/forms.py new file mode 100644 index 00000000..dca2b05c --- /dev/null +++ b/club/forms.py @@ -0,0 +1,291 @@ +# -*- coding:utf-8 -* +# +# Copyright 2016,2017 +# - Skia +# - Sli +# +# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM, +# http://ae.utbm.fr. +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of the GNU General Public License a published by the Free Software +# Foundation; either version 3 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple +# Place - Suite 330, Boston, MA 02111-1307, USA. +# +# + +from django.conf import settings +from django import forms +from django.utils.translation import ugettext_lazy as _ + +from ajax_select.fields import AutoCompleteSelectField, AutoCompleteSelectMultipleField + +from club.models import Mailing, MailingSubscription, Club, Membership + +from core.models import User +from core.views.forms import SelectDate, SelectDateTime +from counter.models import Counter + + +class ClubEditForm(forms.ModelForm): + class Meta: + model = Club + fields = ["address", "logo", "short_description"] + + def __init__(self, *args, **kwargs): + super(ClubEditForm, self).__init__(*args, **kwargs) + self.fields["short_description"].widget = forms.Textarea() + + +class MailingForm(forms.Form): + """ + Form handling mailing lists right + """ + + ACTION_NEW_MAILING = 1 + ACTION_NEW_SUBSCRIPTION = 2 + ACTION_REMOVE_SUBSCRIPTION = 3 + + subscription_users = AutoCompleteSelectMultipleField( + "users", + label=_("Users to add"), + help_text=_("Search users to add (one or more)."), + required=False, + ) + + def __init__(self, club_id, user_id, mailings, *args, **kwargs): + super(MailingForm, self).__init__(*args, **kwargs) + + self.fields["action"] = forms.TypedChoiceField( + ( + (self.ACTION_NEW_MAILING, _("New Mailing")), + (self.ACTION_NEW_SUBSCRIPTION, _("Subscribe")), + (self.ACTION_REMOVE_SUBSCRIPTION, _("Remove")), + ), + coerce=int, + label=_("Action"), + initial=1, + required=True, + widget=forms.HiddenInput(), + ) + + # Generate bulk removal forms, they are never required + for mailing in mailings: + self.fields["removal_" + str(mailing.id)] = forms.ModelMultipleChoiceField( + mailing.subscriptions.all(), + label=_("Remove"), + required=False, + widget=forms.CheckboxSelectMultiple, + ) + + # Include fields for handling mailing creation + mailing_fields = ("email",) + self.fields.update(forms.fields_for_model(Mailing, fields=mailing_fields)) + for field in mailing_fields: + self.fields["mailing_" + field] = self.fields.pop(field) + self.fields["mailing_" + field].required = False + + # Include fields for handling subscription creation + subscription_fields = ("mailing", "email") + self.fields.update( + forms.fields_for_model(MailingSubscription, fields=subscription_fields) + ) + for field in subscription_fields: + self.fields["subscription_" + field] = self.fields.pop(field) + self.fields["subscription_" + field].required = False + + self.fields["subscription_mailing"].queryset = Mailing.objects.filter( + club__id=club_id, is_moderated=True + ) + + def check_required(self, cleaned_data, field): + """ + If the given field doesn't exist or has no value, add a required error on it + """ + if not cleaned_data.get(field, None): + self.add_error(field, _("This field is required")) + + def clean_subscription_users(self): + """ + Convert given users into real users and check their validity + """ + cleaned_data = super(MailingForm, self).clean() + users = [] + for user in cleaned_data["subscription_users"]: + user = User.objects.filter(id=user).first() + if not user: + raise forms.ValidationError( + _("One of the selected users doesn't exist"), code="invalid" + ) + if not user.email: + raise forms.ValidationError( + _("One of the selected users doesn't have an email address"), + code="invalid", + ) + users.append(user) + return users + + def clean(self): + cleaned_data = super(MailingForm, self).clean() + + if not "action" in cleaned_data: + # If there is no action provided, we can stop here + raise forms.ValidationError(_("An action is required"), code="invalid") + + if cleaned_data["action"] == self.ACTION_NEW_MAILING: + self.check_required(cleaned_data, "mailing_email") + + if cleaned_data["action"] == self.ACTION_NEW_SUBSCRIPTION: + self.check_required(cleaned_data, "subscription_mailing") + if not cleaned_data.get( + "subscription_users", None + ) and not cleaned_data.get("subscription_email", None): + raise forms.ValidationError( + _("You must specify at least an user or an email address"), + code="invalid", + ) + + return cleaned_data + + +class SellingsFormBase(forms.Form): + begin_date = forms.DateTimeField( + ["%Y-%m-%d %H:%M:%S"], + label=_("Begin date"), + required=False, + widget=SelectDateTime, + ) + end_date = forms.DateTimeField( + ["%Y-%m-%d %H:%M:%S"], + label=_("End date"), + required=False, + widget=SelectDateTime, + ) + counter = forms.ModelChoiceField( + Counter.objects.order_by("name").all(), label=_("Counter"), required=False + ) + + +class ClubMemberForm(forms.Form): + """ + Form handling the members of a club + """ + + error_css_class = "error" + required_css_class = "required" + + users = AutoCompleteSelectMultipleField( + "users", + label=_("Users to add"), + help_text=_("Search users to add (one or more)."), + required=False, + ) + + 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): + """ + 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 + """ + 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"] <= settings.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 diff --git a/club/models.py b/club/models.py index d446c080..5f0b58dd 100644 --- a/club/models.py +++ b/club/models.py @@ -338,6 +338,8 @@ class Mailing(models.Model): ) def clean(self): + if Mailing.objects.filter(email=self.email).exists(): + raise ValidationError(_("This mailing list already exists.")) if self.can_moderate(self.moderator): self.is_moderated = True else: @@ -446,18 +448,20 @@ class MailingSubscription(models.Model): def can_be_edited_by(self, user): return self.user is not None and user.id == self.user.id - @property + @cached_property def get_email(self): if self.user and not self.email: return self.user.email return self.email + @cached_property + def get_username(self): + if self.user: + return str(self.user) + return _("Unregistered user") + def fetch_format(self): return self.get_email + " " def __str__(self): - if self.user: - user = str(self.user) - else: - user = _("Unregistered user") - return "(%s) - %s : %s" % (self.mailing, user, self.email) + return "(%s) - %s : %s" % (self.mailing, self.get_username, self.email) diff --git a/club/templates/club/mailing.jinja b/club/templates/club/mailing.jinja index 9c2af2b8..7aa88eed 100644 --- a/club/templates/club/mailing.jinja +++ b/club/templates/club/mailing.jinja @@ -1,17 +1,26 @@ {% extends "core/base.jinja" %} +{% from 'core/macros.jinja' import select_all_checkbox %} {% block title %} {% trans %}Mailing lists{% endtrans %} {% endblock %} {% block content %} - {% if has_objects %} {% trans %}Remember : mailing lists need to be moderated, if your new created list is not shown wait until moderation takes action{% endtrans %} - {% for mailing in object_list %} - {% if mailing.is_moderated %} -

{% trans %}Mailing{% endtrans %} {{ mailing.email_full }} + {% if mailings_not_moderated %} +

{% trans %}Mailing lists waiting for moderation{% endtrans %}

+ + {% endif %} + + {% if mailings_moderated %} + {% for mailing in mailings_moderated %} +

{% trans %}Mailing{% endtrans %} {{ mailing.email_full }} {%- if user.is_owner(mailing) -%} - {% trans %}Delete{% endtrans %} {%- endif -%} @@ -19,27 +28,38 @@
-
- + {% set form_mailing_removal = form["removal_" + mailing.id|string] %} + {% if form_mailing_removal.field.choices %} + {% set ms = dict(mailing.subscriptions.all() | groupby('id')) %} + +

{{ select_all_checkbox(form_mailing_removal.auto_id) }}

+ {% csrf_token %} + + + + + + + + + + + {% for widget in form_mailing_removal.subwidgets %} + {% set user = ms[widget.data.value][0] %} + + + + + + {% endfor %} + +
{% trans %}User{% endtrans %}{% trans %}Email{% endtrans %}{% trans %}Delete{% endtrans %}
{{ user.get_username }}{{ user.get_email }}{{ widget.tag() }}
+ {{ form_mailing_removal.errors }} +

-
- - - - - - {% for subscriber in mailing.subscriptions.all() %} - - {% if subscriber.user %} - - {% else %} - - {% endif %} - - - - {% endfor %} -
{% trans %}User{% endtrans %}{% trans %}Email{%endtrans%}
{{ subscriber.user }}{% trans %}Unregistered user{% endtrans %}{{ subscriber.get_email }}{% trans %}Delete{% endtrans %}
+ + {% else %} +

{% trans %}There is no subscriber for this mailing list{% endtrans %}

{% endif %} {% endfor %} @@ -47,19 +67,41 @@

{% trans %}No mailing list existing for this club{% endtrans %}

{% endif %} - {% if has_objects %} +

{{ form.non_field_errors() }}

+ {% if mailings_moderated %}

{% trans %}New member{% endtrans %}

-
+ {% csrf_token %} - {{ add_member.as_p() }} +

+ {{ form.subscription_mailing.errors }} + + {{ form.subscription_mailing }} +

+

+ {{ form.subscription_users.errors }} + + {{ form.subscription_users }} + {{ form.subscription_users.help_text }} +

+

+ {{ form.subscription_email.errors }} + + {{ form.subscription_email }} +

+

{% endif %}

{% trans %}New mailing{% endtrans %}

-
+ {% csrf_token %} - {{ add_mailing.as_p() }} +

+ {{ form.mailing_email.errors }} + + {{ form.mailing_email }} +

+

diff --git a/club/tests.py b/club/tests.py index 25f6e019..835e7f4c 100644 --- a/club/tests.py +++ b/club/tests.py @@ -22,12 +22,17 @@ # # +from django.conf import settings from django.test import TestCase +from django.utils import timezone, html +from django.utils.translation import ugettext as _ from django.core.urlresolvers import reverse from django.core.management import call_command +from django.core.exceptions import ValidationError, NON_FIELD_ERRORS from core.models import User -from club.models import Club +from club.models import Club, Membership, Mailing +from club.forms import MailingForm # Create your tests here. @@ -373,3 +378,316 @@ class ClubTest(TestCase): "S' Kia\\n Responsable info" in content ) + + +class MailingFormTest(TestCase): + """Perform validation tests for MailingForm""" + + def setUp(self): + call_command("populate") + self.skia = User.objects.filter(username="skia").first() + self.rbatsbak = User.objects.filter(username="rbatsbak").first() + self.krophil = User.objects.filter(username="krophil").first() + self.comunity = User.objects.filter(username="comunity").first() + self.bdf = Club.objects.filter(unix_name="bdf").first() + Membership( + user=self.rbatsbak, + club=self.bdf, + start_date=timezone.now(), + role=settings.SITH_CLUB_ROLES_ID["Board member"], + ).save() + + def test_mailing_list_add_no_moderation(self): + # Test with Communication admin + self.client.login(username="comunity", password="plop") + self.client.post( + reverse("club:mailing", kwargs={"club_id": self.bdf.id}), + {"action": MailingForm.ACTION_NEW_MAILING, "mailing_email": "foyer"}, + ) + response = self.client.get( + reverse("club:mailing", kwargs={"club_id": self.bdf.id}) + ) + self.assertContains(response, text="Liste de diffusion foyer@utbm.fr") + + # Test with Root + self.client.login(username="root", password="plop") + self.client.post( + reverse("club:mailing", kwargs={"club_id": self.bdf.id}), + {"action": MailingForm.ACTION_NEW_MAILING, "mailing_email": "mde"}, + ) + response = self.client.get( + reverse("club:mailing", kwargs={"club_id": self.bdf.id}) + ) + self.assertContains(response, text="Liste de diffusion mde@utbm.fr") + + def test_mailing_list_add_moderation(self): + self.client.login(username="rbatsbak", password="plop") + self.client.post( + reverse("club:mailing", kwargs={"club_id": self.bdf.id}), + {"action": MailingForm.ACTION_NEW_MAILING, "mailing_email": "mde"}, + ) + response = self.client.get( + reverse("club:mailing", kwargs={"club_id": self.bdf.id}) + ) + self.assertNotContains(response, text="Liste de diffusion mde@utbm.fr") + self.assertContains( + response, text="

Listes de diffusions en attente de modération

" + ) + self.assertContains(response, "
  • mde@utbm.fr") + + def test_mailing_list_forbidden(self): + # With anonymous user + response = self.client.get( + reverse("club:mailing", kwargs={"club_id": self.bdf.id}) + ) + self.assertContains(response, "", status_code=403) + + # With user not in club + self.client.login(username="krophil", password="plop") + response = self.client.get( + reverse("club:mailing", kwargs={"club_id": self.bdf.id}) + ) + self.assertContains(response, "", status_code=403) + + def test_add_new_subscription_fail_not_moderated(self): + self.client.login(username="rbatsbak", password="plop") + self.client.post( + reverse("club:mailing", kwargs={"club_id": self.bdf.id}), + {"action": MailingForm.ACTION_NEW_MAILING, "mailing_email": "mde"}, + ) + + self.client.post( + reverse("club:mailing", kwargs={"club_id": self.bdf.id}), + { + "action": MailingForm.ACTION_NEW_SUBSCRIPTION, + "subscription_users": self.skia.id, + "subscription_mailing": Mailing.objects.get(email="mde").id, + }, + ) + response = self.client.get( + reverse("club:mailing", kwargs={"club_id": self.bdf.id}) + ) + self.assertNotContains(response, "skia@git.an") + + def test_add_new_subscription_success(self): + # Prepare mailing list + self.client.login(username="comunity", password="plop") + self.client.post( + reverse("club:mailing", kwargs={"club_id": self.bdf.id}), + {"action": MailingForm.ACTION_NEW_MAILING, "mailing_email": "mde"}, + ) + + # Add single user + self.client.post( + reverse("club:mailing", kwargs={"club_id": self.bdf.id}), + { + "action": MailingForm.ACTION_NEW_SUBSCRIPTION, + "subscription_users": self.skia.id, + "subscription_mailing": Mailing.objects.get(email="mde").id, + }, + ) + response = self.client.get( + reverse("club:mailing", kwargs={"club_id": self.bdf.id}) + ) + self.assertContains(response, "skia@git.an") + + # Add multiple users + self.client.post( + reverse("club:mailing", kwargs={"club_id": self.bdf.id}), + { + "action": MailingForm.ACTION_NEW_SUBSCRIPTION, + "subscription_users": "|%s|%s|" % (self.comunity.id, self.rbatsbak.id), + "subscription_mailing": Mailing.objects.get(email="mde").id, + }, + ) + response = self.client.get( + reverse("club:mailing", kwargs={"club_id": self.bdf.id}) + ) + self.assertContains(response, "richard@git.an") + self.assertContains(response, "comunity@git.an") + self.assertContains(response, "skia@git.an") + + # Add arbitrary email + self.client.post( + reverse("club:mailing", kwargs={"club_id": self.bdf.id}), + { + "action": MailingForm.ACTION_NEW_SUBSCRIPTION, + "subscription_email": "arbitrary@git.an", + "subscription_mailing": Mailing.objects.get(email="mde").id, + }, + ) + response = self.client.get( + reverse("club:mailing", kwargs={"club_id": self.bdf.id}) + ) + self.assertContains(response, "richard@git.an") + self.assertContains(response, "comunity@git.an") + self.assertContains(response, "skia@git.an") + self.assertContains(response, "arbitrary@git.an") + + # Add user and arbitrary email + self.client.post( + reverse("club:mailing", kwargs={"club_id": self.bdf.id}), + { + "action": MailingForm.ACTION_NEW_SUBSCRIPTION, + "subscription_email": "more.arbitrary@git.an", + "subscription_users": self.krophil.id, + "subscription_mailing": Mailing.objects.get(email="mde").id, + }, + ) + response = self.client.get( + reverse("club:mailing", kwargs={"club_id": self.bdf.id}) + ) + self.assertContains(response, "richard@git.an") + self.assertContains(response, "comunity@git.an") + self.assertContains(response, "skia@git.an") + self.assertContains(response, "arbitrary@git.an") + self.assertContains(response, "more.arbitrary@git.an") + self.assertContains(response, "krophil@git.an") + + def test_add_new_subscription_fail_form_errors(self): + # Prepare mailing list + self.client.login(username="comunity", password="plop") + self.client.post( + reverse("club:mailing", kwargs={"club_id": self.bdf.id}), + {"action": MailingForm.ACTION_NEW_MAILING, "mailing_email": "mde"}, + ) + + # Neither email or email is specified + response = self.client.post( + reverse("club:mailing", kwargs={"club_id": self.bdf.id}), + { + "action": MailingForm.ACTION_NEW_SUBSCRIPTION, + "subscription_mailing": Mailing.objects.get(email="mde").id, + }, + ) + self.assertContains( + response, text=_("You must specify at least an user or an email address") + ) + + # No mailing specified + response = self.client.post( + reverse("club:mailing", kwargs={"club_id": self.bdf.id}), + { + "action": MailingForm.ACTION_NEW_SUBSCRIPTION, + "subscription_users": self.krophil.id, + }, + ) + self.assertContains(response, text=_("This field is required")) + + # One of the selected users doesn't exist + response = self.client.post( + reverse("club:mailing", kwargs={"club_id": self.bdf.id}), + { + "action": MailingForm.ACTION_NEW_SUBSCRIPTION, + "subscription_users": "|789|", + "subscription_mailing": Mailing.objects.get(email="mde").id, + }, + ) + self.assertContains( + response, text=html.escape(_("One of the selected users doesn't exist")) + ) + + # An user has no email adress + self.krophil.email = "" + self.krophil.save() + + response = self.client.post( + reverse("club:mailing", kwargs={"club_id": self.bdf.id}), + { + "action": MailingForm.ACTION_NEW_SUBSCRIPTION, + "subscription_users": self.krophil.id, + "subscription_mailing": Mailing.objects.get(email="mde").id, + }, + ) + self.assertContains( + response, + text=html.escape( + _("One of the selected users doesn't have an email address") + ), + ) + + self.krophil.email = "krophil@git.an" + self.krophil.save() + + # An user is added twice + + self.client.post( + reverse("club:mailing", kwargs={"club_id": self.bdf.id}), + { + "action": MailingForm.ACTION_NEW_SUBSCRIPTION, + "subscription_users": self.krophil.id, + "subscription_mailing": Mailing.objects.get(email="mde").id, + }, + ) + + response = self.client.post( + reverse("club:mailing", kwargs={"club_id": self.bdf.id}), + { + "action": MailingForm.ACTION_NEW_SUBSCRIPTION, + "subscription_users": self.krophil.id, + "subscription_mailing": Mailing.objects.get(email="mde").id, + }, + ) + self.assertContains( + response, + text=html.escape(_("This email is already suscribed in this mailing")), + ) + + def test_remove_subscription_success(self): + # Prepare mailing list + self.client.login(username="comunity", password="plop") + self.client.post( + reverse("club:mailing", kwargs={"club_id": self.bdf.id}), + {"action": MailingForm.ACTION_NEW_MAILING, "mailing_email": "mde"}, + ) + mde = Mailing.objects.get(email="mde") + response = self.client.post( + reverse("club:mailing", kwargs={"club_id": self.bdf.id}), + { + "action": MailingForm.ACTION_NEW_SUBSCRIPTION, + "subscription_users": "|%s|%s|%s|" + % (self.comunity.id, self.rbatsbak.id, self.krophil.id), + "subscription_mailing": mde.id, + }, + ) + self.assertContains(response, "comunity@git.an") + self.assertContains(response, "richard@git.an") + self.assertContains(response, "krophil@git.an") + + # Delete one user + self.client.post( + reverse("club:mailing", kwargs={"club_id": self.bdf.id}), + { + "action": MailingForm.ACTION_REMOVE_SUBSCRIPTION, + "removal_%d" % mde.id: mde.subscriptions.get(user=self.krophil).id, + }, + ) + response = self.client.get( + reverse("club:mailing", kwargs={"club_id": self.bdf.id}) + ) + + self.assertContains(response, "comunity@git.an") + self.assertContains(response, "richard@git.an") + self.assertNotContains(response, "krophil@git.an") + + # Delete multiple users + self.client.post( + reverse("club:mailing", kwargs={"club_id": self.bdf.id}), + { + "action": MailingForm.ACTION_REMOVE_SUBSCRIPTION, + "removal_%d" + % mde.id: [ + user.id + for user in mde.subscriptions.filter( + user__in=[self.rbatsbak, self.comunity] + ).all() + ], + }, + ) + response = self.client.get( + reverse("club:mailing", kwargs={"club_id": self.bdf.id}) + ) + + self.assertNotContains(response, "comunity@git.an") + self.assertNotContains(response, "richard@git.an") + self.assertNotContains(response, "krophil@git.an") diff --git a/club/urls.py b/club/urls.py index 8f446d65..c5a01e02 100644 --- a/club/urls.py +++ b/club/urls.py @@ -64,31 +64,12 @@ urlpatterns = [ ), url(r"^(?P[0-9]+)/prop$", ClubEditPropView.as_view(), name="club_prop"), url(r"^(?P[0-9]+)/tools$", ClubToolsView.as_view(), name="tools"), - url( - r"^(?P[0-9]+)/mailing$", - ClubMailingView.as_view(action="display"), - name="mailing", - ), - url( - r"^(?P[0-9]+)/mailing/new/mailing$", - ClubMailingView.as_view(action="add_mailing"), - name="mailing_create", - ), - url( - r"^(?P[0-9]+)/mailing/new/subscription$", - ClubMailingView.as_view(action="add_member"), - name="mailing_subscription_create", - ), + url(r"^(?P[0-9]+)/mailing$", ClubMailingView.as_view(), name="mailing"), url( r"^(?P[0-9]+)/mailing/generate$", MailingAutoGenerationView.as_view(), name="mailing_generate", ), - url( - r"^(?P[0-9]+)/mailing/clean$", - MailingAutoCleanView.as_view(), - name="mailing_clean", - ), url( r"^(?P[0-9]+)/mailing/delete$", MailingDeleteView.as_view(), diff --git a/club/views.py b/club/views.py index 7f50be06..1d2e145e 100644 --- a/club/views.py +++ b/club/views.py @@ -23,6 +23,8 @@ # # + +from django.conf import settings from django import forms from django.views.generic import ListView, DetailView, TemplateView, View from django.views.generic.edit import DeleteView @@ -33,8 +35,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, AutoCompleteSelectMultipleField -from django.core.exceptions import PermissionDenied +from django.core.exceptions import PermissionDenied, ValidationError, NON_FIELD_ERRORS from django.shortcuts import get_object_or_404, redirect from core.views import ( @@ -46,71 +47,19 @@ from core.views import ( PageEditViewBase, DetailFormView, ) -from core.views.forms import SelectDate, SelectDateTime -from club.models import Club, Membership, Mailing, MailingSubscription -from sith.settings import SITH_MAXIMUM_FREE_ROLE -from counter.models import Selling, Counter -from core.models import User, PageRev +from core.models import PageRev + +from counter.models import Selling + from com.views import ( PosterListBaseView, PosterCreateBaseView, PosterEditBaseView, PosterDeleteBaseView, ) -from com.models import Poster -from django.conf import settings - -# Custom forms - - -class ClubEditForm(forms.ModelForm): - class Meta: - model = Club - fields = ["address", "logo", "short_description"] - - def __init__(self, *args, **kwargs): - super(ClubEditForm, self).__init__(*args, **kwargs) - self.fields["short_description"].widget = forms.Textarea() - - -class MailingForm(forms.ModelForm): - class Meta: - model = Mailing - fields = ("email", "club", "moderator") - - def __init__(self, *args, **kwargs): - club_id = kwargs.pop("club_id", None) - user_id = kwargs.pop("user_id", -1) # Remember 0 is treated as None - super(MailingForm, self).__init__(*args, **kwargs) - if club_id: - self.fields["club"].queryset = Club.objects.filter(id=club_id) - self.fields["club"].initial = club_id - self.fields["club"].widget = forms.HiddenInput() - if user_id >= 0: - self.fields["moderator"].queryset = User.objects.filter(id=user_id) - self.fields["moderator"].initial = user_id - self.fields["moderator"].widget = forms.HiddenInput() - - -class MailingSubscriptionForm(forms.ModelForm): - class Meta: - model = MailingSubscription - fields = ("mailing", "user", "email") - - def __init__(self, *args, **kwargs): - kwargs.pop("user_id", None) # For standart interface - club_id = kwargs.pop("club_id", None) - super(MailingSubscriptionForm, self).__init__(*args, **kwargs) - self.fields["email"].required = False - if club_id: - self.fields["mailing"].queryset = Mailing.objects.filter( - club__id=club_id, is_moderated=True - ) - - user = AutoCompleteSelectField( - "users", label=_("User"), help_text=None, required=False - ) +from club.models import Club, Membership, Mailing, MailingSubscription +from club.forms import MailingForm, ClubEditForm, ClubMemberForm class ClubTabsMixin(TabedViewMixin): @@ -306,122 +255,6 @@ class ClubToolsView(ClubTabsMixin, CanEditMixin, DetailView): current_tab = "tools" -class ClubMemberForm(forms.Form): - """ - Form handling the members of a club - """ - - error_css_class = "error" - required_css_class = "required" - - users = AutoCompleteSelectMultipleField( - "users", - label=_("Users to add"), - help_text=_("Search users to add (one or more)."), - required=False, - ) - - 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): - """ - 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 - """ - 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, DetailFormView): """ View of a club's members @@ -485,24 +318,6 @@ class ClubOldMembersView(ClubTabsMixin, CanViewMixin, DetailView): current_tab = "elderlies" -class SellingsFormBase(forms.Form): - begin_date = forms.DateTimeField( - ["%Y-%m-%d %H:%M:%S"], - label=_("Begin date"), - required=False, - widget=SelectDateTime, - ) - end_date = forms.DateTimeField( - ["%Y-%m-%d %H:%M:%S"], - label=_("End date"), - required=False, - widget=SelectDateTime, - ) - counter = forms.ModelChoiceField( - Counter.objects.order_by("name").all(), label=_("Counter"), required=False - ) - - class ClubSellingView(ClubTabsMixin, CanEditMixin, DetailView): """ Sellings of a club @@ -687,94 +502,134 @@ class ClubStatView(TemplateView): return kwargs -class ClubMailingView(ClubTabsMixin, ListView): +class ClubMailingView(ClubTabsMixin, CanEditMixin, DetailFormView): """ A list of mailing for a given club """ - action = None - model = Mailing + model = Club + form_class = MailingForm + pk_url_kwarg = "club_id" template_name = "club/mailing.jinja" current_tab = "mailing" - def authorized(self): - return ( - self.club.has_rights_in_club(self.user) - or self.user.is_root - or self.user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID) - ) + def get_form_kwargs(self): + kwargs = super(ClubMailingView, self).get_form_kwargs() + kwargs["club_id"] = self.get_object().id + kwargs["user_id"] = self.request.user.id + kwargs["mailings"] = self.mailings + return kwargs def dispatch(self, request, *args, **kwargs): - self.club = get_object_or_404(Club, pk=kwargs["club_id"]) - self.user = request.user - self.object = self.club - if not self.authorized(): - raise PermissionDenied - self.member_form = MailingSubscriptionForm(club_id=self.club.id) - self.mailing_form = MailingForm(club_id=self.club.id, user_id=self.user.id) + self.mailings = Mailing.objects.filter(club_id=self.get_object().id).all() return super(ClubMailingView, self).dispatch(request, *args, **kwargs) - def post(self, request, *args, **kwargs): - res = super(ClubMailingView, self).get(request, *args, **kwargs) - if self.action != "display": - if self.action == "add_mailing": - form = MailingForm - model = Mailing - elif self.action == "add_member": - form = MailingSubscriptionForm - model = MailingSubscription - return MailingGenericCreateView.as_view( - model=model, list_view=self, form_class=form - )(request, *args, **kwargs) - return res - - def get_queryset(self): - return Mailing.objects.filter(club_id=self.club.id).all() - def get_context_data(self, **kwargs): kwargs = super(ClubMailingView, self).get_context_data(**kwargs) - kwargs["add_member"] = self.member_form - kwargs["add_mailing"] = self.mailing_form - kwargs["club"] = self.club - kwargs["user"] = self.user - kwargs["has_objects"] = len(kwargs["object_list"]) > 0 + kwargs["club"] = self.get_object() + kwargs["user"] = self.request.user + kwargs["mailings"] = self.mailings + kwargs["mailings_moderated"] = ( + kwargs["mailings"].exclude(is_moderated=False).all() + ) + kwargs["mailings_not_moderated"] = ( + kwargs["mailings"].exclude(is_moderated=True).all() + ) + kwargs["form_actions"] = { + "NEW_MALING": self.form_class.ACTION_NEW_MAILING, + "NEW_SUBSCRIPTION": self.form_class.ACTION_NEW_SUBSCRIPTION, + "REMOVE_SUBSCRIPTION": self.form_class.ACTION_REMOVE_SUBSCRIPTION, + } return kwargs - def get_object(self): - return self.club + def add_new_mailing(self, cleaned_data) -> ValidationError: + """ + Create a new mailing list from the form + """ + mailing = Mailing( + club=self.get_object(), + email=cleaned_data["mailing_email"], + moderator=self.request.user, + is_moderated=False, + ) + try: + mailing.clean() + except ValidationError as validation_error: + return validation_error + mailing.save() + return None + def add_new_subscription(self, cleaned_data) -> ValidationError: + """ + Add mailing subscriptions for each user given and/or for the specified email in form + """ + users_to_save = [] -class MailingGenericCreateView(CreateView, SingleObjectMixin): - """ - Create a new mailing list - """ + for user in cleaned_data["subscription_users"]: + sub = MailingSubscription( + mailing=cleaned_data["subscription_mailing"], user=user + ) + try: + sub.clean() + except ValidationError as validation_error: + return validation_error - list_view = None - form_class = None + users_to_save.append(sub.save()) - def get_context_data(self, **kwargs): - view_kwargs = self.list_view.get_context_data(**kwargs) - for key, data in ( - super(MailingGenericCreateView, self).get_context_data(**kwargs).items() - ): - view_kwargs[key] = data - view_kwargs[self.list_view.action] = view_kwargs["form"] - return view_kwargs + if cleaned_data["subscription_email"]: + sub = MailingSubscription( + mailing=cleaned_data["subscription_mailing"], + email=cleaned_data["subscription_email"], + ) - def get_form_kwargs(self): - kwargs = super(MailingGenericCreateView, self).get_form_kwargs() - kwargs["club_id"] = self.list_view.club.id - kwargs["user_id"] = self.list_view.user.id - return kwargs + try: + sub.clean() + except ValidationError as validation_error: + return validation_error + sub.save() - def dispatch(self, request, *args, **kwargs): - if not self.list_view.authorized(): - raise PermissionDenied - self.template_name = self.list_view.template_name - return super(MailingGenericCreateView, self).dispatch(request, *args, **kwargs) + # Save users after we are sure there is no error + for user in users_to_save: + user.save() + + return None + + def remove_subscription(self, cleaned_data): + """ + Remove specified users from a mailing list + """ + fields = [ + cleaned_data[key] + for key in cleaned_data.keys() + if key.startswith("removal_") + ] + for field in fields: + for sub in field: + sub.delete() + + def form_valid(self, form): + resp = super(ClubMailingView, self).form_valid(form) + + cleaned_data = form.clean() + error = None + + if cleaned_data["action"] == self.form_class.ACTION_NEW_MAILING: + error = self.add_new_mailing(cleaned_data) + + if cleaned_data["action"] == self.form_class.ACTION_NEW_SUBSCRIPTION: + error = self.add_new_subscription(cleaned_data) + + if cleaned_data["action"] == self.form_class.ACTION_REMOVE_SUBSCRIPTION: + self.remove_subscription(cleaned_data) + + if error: + form.add_error(NON_FIELD_ERRORS, error) + return self.form_invalid(form) + + return resp def get_success_url(self, **kwargs): - return reverse_lazy("club:mailing", kwargs={"club_id": self.list_view.club.id}) + return reverse_lazy("club:mailing", kwargs={"club_id": self.get_object().id}) class MailingDeleteView(CanEditMixin, DeleteView): @@ -829,18 +684,6 @@ class MailingAutoGenerationView(View): return redirect("club:mailing", club_id=club.id) -class MailingAutoCleanView(View): - def dispatch(self, request, *args, **kwargs): - self.mailing = get_object_or_404(Mailing, pk=kwargs["mailing_id"]) - if not request.user.can_edit(self.mailing): - raise PermissionDenied - return super(MailingAutoCleanView, self).dispatch(request, *args, **kwargs) - - def get(self, request, *args, **kwargs): - self.mailing.subscriptions.all().delete() - return redirect("club:mailing", club_id=self.mailing.club.id) - - class PosterListView(ClubTabsMixin, PosterListBaseView, CanViewMixin): """List communication posters""" diff --git a/core/management/commands/populate.py b/core/management/commands/populate.py index cccaf098..02bd3cd3 100644 --- a/core/management/commands/populate.py +++ b/core/management/commands/populate.py @@ -837,6 +837,27 @@ Welcome to the wiki page! krophil_profile.save() krophil.profile_pict = krophil_profile krophil.save() + # Adding user Com Unity + comunity = User( + username="comunity", + last_name="Unity", + first_name="Com", + email="comunity@git.an", + date_of_birth="1942-06-12", + ) + comunity.set_password("plop") + comunity.save() + comunity.groups = [ + Group.objects.filter(name="Communication admin").first().id + ] + comunity.save() + Membership( + user=comunity, + club=bar_club, + start_date=timezone.now(), + role=settings.SITH_CLUB_ROLES_ID["Board member"], + ).save() + # Adding subscription for sli s = Subscription( member=User.objects.filter(pk=sli.pk).first(), @@ -861,6 +882,18 @@ Welcome to the wiki page! start=s.subscription_start, ) s.save() + # Com Unity + s = Subscription( + member=comunity, + subscription_type=default_subscription, + payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0][0], + ) + s.subscription_start = s.compute_start() + s.subscription_end = s.compute_end( + duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]["duration"], + start=s.subscription_start, + ) + s.save() Selling( label=dcons.name, diff --git a/core/views/__init__.py b/core/views/__init__.py index c4972bd0..202b08fa 100644 --- a/core/views/__init__.py +++ b/core/views/__init__.py @@ -296,11 +296,6 @@ class DetailFormView(SingleObjectMixin, FormView): """ return super(DetailFormView, self).get_object() - def get_context_data(self, *args, **kwargs): - kwargs = super(DetailFormView, self).get_context_data() - kwargs["object"] = self.get_object() - return kwargs - from .user import * from .page import * diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index d5f73570..8eab8c71 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-28 15:00+0200\n" +"POT-Creation-Date: 2019-05-09 11:33+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:413 +#: accounting/models.py:313 club/models.py:415 #: club/templates/club/club_members.jinja:16 #: club/templates/club/club_old_members.jinja:8 -#: club/templates/club/mailing.jinja:28 club/views.py:112 +#: club/templates/club/mailing.jinja:41 #: counter/templates/counter/cash_summary_list.jinja:32 #: counter/templates/counter/stats.jinja:15 #: counter/templates/counter/stats.jinja:52 @@ -345,7 +345,7 @@ msgstr "Compte en banque : " #: accounting/templates/accounting/club_account_details.jinja:60 #: accounting/templates/accounting/label_list.jinja:26 #: club/templates/club/club_sellings.jinja:50 -#: club/templates/club/mailing.jinja:16 club/templates/club/mailing.jinja:39 +#: club/templates/club/mailing.jinja:25 club/templates/club/mailing.jinja:43 #: com/templates/com/mailing_admin.jinja:19 #: com/templates/com/news_admin_list.jinja:41 #: com/templates/com/news_admin_list.jinja:70 @@ -386,7 +386,7 @@ msgid "Delete" msgstr "Supprimer" #: accounting/templates/accounting/bank_account_details.jinja:18 -#: club/views.py:129 core/views/user.py:205 sas/templates/sas/picture.jinja:86 +#: club/views.py:78 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:175 +#: accounting/templates/accounting/journal_details.jinja:89 club/views.py:124 #: com/templates/com/news_admin_list.jinja:39 #: com/templates/com/news_admin_list.jinja:68 #: com/templates/com/news_admin_list.jinja:115 @@ -893,6 +893,101 @@ msgstr "Opérations sans étiquette" msgid "Refound this account" msgstr "Rembourser ce compte" +#: club/forms.py:60 club/forms.py:197 +msgid "Users to add" +msgstr "Utilisateurs à ajouter" + +#: club/forms.py:61 club/forms.py:198 core/views/group.py:63 +msgid "Search users to add (one or more)." +msgstr "Recherche les utilisateurs à ajouter (un ou plus)." + +#: club/forms.py:70 +#, fuzzy +#| msgid "New mailing" +msgid "New Mailing" +msgstr "Nouvelle mailing liste" + +#: club/forms.py:71 +#, fuzzy +#| msgid "Unsubscribe" +msgid "Subscribe" +msgstr "Se désabonner" + +#: club/forms.py:72 club/forms.py:85 com/templates/com/news_admin_list.jinja:40 +#: com/templates/com/news_admin_list.jinja:116 +#: com/templates/com/news_admin_list.jinja:198 +#: com/templates/com/news_admin_list.jinja:274 +msgid "Remove" +msgstr "Retirer" + +#: club/forms.py:75 launderette/views.py:228 +msgid "Action" +msgstr "Action" + +#: club/forms.py:122 +#, fuzzy +#| msgid "This field is required." +msgid "This field is required" +msgstr "Ce champ est obligatoire." + +#: club/forms.py:134 club/forms.py:259 +msgid "One of the selected users doesn't exist" +msgstr "Un des utilisateurs sélectionné n'existe pas" + +#: club/forms.py:138 +#, fuzzy +#| msgid "One of the selected users doesn't exist" +msgid "One of the selected users doesn't have an email address" +msgstr "Un des utilisateurs sélectionné n'existe pas" + +#: club/forms.py:149 +#, fuzzy +#| msgid "This field is required." +msgid "An action is required" +msgstr "Ce champ est obligatoire." + +#: club/forms.py:162 +msgid "You must specify at least an user or an email address" +msgstr "vous devez spécifier au moins un utilisateur ou une adresse email" + +#: club/forms.py:172 counter/views.py:1481 +msgid "Begin date" +msgstr "Date de début" + +#: club/forms.py:178 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/forms.py:183 club/templates/club/club_sellings.jinja:21 +#: 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 +msgid "Counter" +msgstr "Comptoir" + +#: club/forms.py:241 club/templates/club/club_members.jinja:21 +#: club/templates/club/club_members.jinja:46 +#: core/templates/core/user_clubs.jinja:29 +msgid "Mark as old" +msgstr "Marquer comme ancien" + +#: club/forms.py:263 +msgid "User must be subscriber to take part to a club" +msgstr "L'utilisateur doit être cotisant pour faire partie d'un club" + +#: club/forms.py:267 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/forms.py:288 +msgid "You should specify a role" +msgstr "Vous devez choisir un rôle" + +#: club/forms.py:299 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/models.py:51 msgid "unix name" msgstr "nom unix" @@ -959,7 +1054,7 @@ msgstr "description" msgid "past member" msgstr "Anciens membres" -#: club/models.py:323 club/models.py:418 +#: club/models.py:323 club/models.py:420 msgid "Email address" msgstr "Adresse email" @@ -976,21 +1071,25 @@ msgstr "est modéré" msgid "moderator" msgstr "modérateur" -#: club/models.py:406 club/templates/club/mailing.jinja:14 +#: club/models.py:342 +msgid "This mailing list already exists." +msgstr "Cette liste de diffusion existe déjà." + +#: club/models.py:408 club/templates/club/mailing.jinja:23 msgid "Mailing" msgstr "Liste de diffusion" -#: club/models.py:425 +#: club/models.py:427 msgid "At least user or email is required" msgstr "Au moins un utilisateur ou un email est nécessaire" -#: club/models.py:433 +#: club/models.py:435 msgid "This email is already suscribed in this mailing" msgstr "Cet email est déjà abonné à cette mailing" -#: club/models.py:462 club/templates/club/mailing.jinja:36 +#: club/models.py:461 msgid "Unregistered user" -msgstr "Désabonner un utilisateur" +msgstr "Utilisateur non enregistré" #: club/templates/club/club_list.jinja:4 club/templates/club/club_list.jinja:37 msgid "Club list" @@ -1037,12 +1136,6 @@ msgstr "Description" 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:50 msgid "There are no members in this club." msgstr "Il n'y a pas de membres dans ce club." @@ -1067,8 +1160,8 @@ msgstr "Du" msgid "To" msgstr "Au" -#: club/templates/club/club_sellings.jinja:5 club/views.py:195 -#: club/views.py:563 counter/templates/counter/counter_main.jinja:19 +#: club/templates/club/club_sellings.jinja:5 club/views.py:144 +#: club/views.py:378 counter/templates/counter/counter_main.jinja:19 #: counter/templates/counter/last_ops.jinja:35 msgid "Sellings" msgstr "Ventes" @@ -1094,13 +1187,6 @@ msgstr "unités" msgid "Benefit: " msgstr "Bénéfice : " -#: club/templates/club/club_sellings.jinja:21 club/views.py:502 -#: 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 -msgid "Counter" -msgstr "Comptoir" - #: club/templates/club/club_sellings.jinja:22 #: core/templates/core/user_account_detail.jinja:19 #: core/templates/core/user_account_detail.jinja:52 @@ -1189,7 +1275,7 @@ msgstr "Comptabilité : " msgid "Manage launderettes" msgstr "Gestion des laveries" -#: club/templates/club/mailing.jinja:4 +#: club/templates/club/mailing.jinja:5 msgid "Mailing lists" msgstr "Mailing listes" @@ -1202,38 +1288,46 @@ msgstr "" "nouvellement créee n'est pas affichée, attendez jusqu'à qu'un modérateur " "entre en action" -#: club/templates/club/mailing.jinja:20 +#: club/templates/club/mailing.jinja:13 +msgid "Mailing lists waiting for moderation" +msgstr "Listes de diffusions en attente de modération" + +#: club/templates/club/mailing.jinja:29 msgid "Generate mailing list" msgstr "Générer la liste de diffusion" -#: club/templates/club/mailing.jinja:23 -msgid "Clean mailing list" -msgstr "Néttoyer la liste de diffusion" - -#: club/templates/club/mailing.jinja:29 +#: club/templates/club/mailing.jinja:42 #: com/templates/com/mailing_admin.jinja:10 msgid "Email" msgstr "Email" -#: club/templates/club/mailing.jinja:47 +#: club/templates/club/mailing.jinja:58 +msgid "Remove from mailing list" +msgstr "Supprimer de la liste de diffusion" + +#: club/templates/club/mailing.jinja:62 +msgid "There is no subscriber for this mailing list" +msgstr "Il n'y a pas d'abonnés dans cette liste de diffusion" + +#: club/templates/club/mailing.jinja:67 msgid "No mailing list existing for this club" msgstr "Aucune mailing liste n'existe pour ce club" -#: club/templates/club/mailing.jinja:51 +#: club/templates/club/mailing.jinja:72 msgid "New member" msgstr "Nouveau membre" -#: club/templates/club/mailing.jinja:55 +#: club/templates/club/mailing.jinja:92 msgid "Add to mailing list" msgstr "Ajouter à la mailing liste" -#: club/templates/club/mailing.jinja:59 +#: club/templates/club/mailing.jinja:96 msgid "New mailing" -msgstr "Nouvelle mailing liste" +msgstr "Nouvelle liste de diffusion" -#: club/templates/club/mailing.jinja:63 +#: club/templates/club/mailing.jinja:109 msgid "Create mailing list" -msgstr "Créer une mailing liste" +msgstr "Créer une liste de diffusion" #: club/templates/club/page_history.jinja:8 msgid "No page existing for this club" @@ -1243,79 +1337,42 @@ msgstr "Aucune page n'existe pour ce club" msgid "Club stats" msgstr "Statistiques du club" -#: club/views.py:139 +#: club/views.py:88 msgid "Members" msgstr "Membres" -#: club/views.py:148 +#: club/views.py:97 msgid "Old members" msgstr "Anciens membres" -#: club/views.py:158 core/templates/core/page.jinja:33 +#: club/views.py:107 core/templates/core/page.jinja:33 msgid "History" msgstr "Historique" -#: club/views.py:166 core/templates/core/base.jinja:121 core/views/user.py:228 +#: club/views.py:115 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:186 +#: club/views.py:135 msgid "Edit club page" msgstr "Éditer la page de club" -#: club/views.py:202 +#: club/views.py:151 msgid "Mailing list" msgstr "Listes de diffusion" -#: club/views.py:211 com/views.py:141 +#: club/views.py:160 com/views.py:141 msgid "Posters list" msgstr "Liste d'affiches" -#: club/views.py:221 counter/templates/counter/counter_list.jinja:21 +#: club/views.py:170 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:319 -msgid "Users to add" -msgstr "Utilisateurs à ajouter" - -#: 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:491 counter/views.py:1481 -msgid "Begin date" -msgstr "Date de début" - -#: club/views.py:497 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:520 core/templates/core/user_stats.jinja:27 +#: club/views.py:335 core/templates/core/user_stats.jinja:27 #: counter/views.py:1635 msgid "Product" msgstr "Produit" @@ -1547,13 +1604,6 @@ msgstr "Auteur" msgid "Moderator" msgstr "Modérateur" -#: com/templates/com/news_admin_list.jinja:40 -#: com/templates/com/news_admin_list.jinja:116 -#: com/templates/com/news_admin_list.jinja:198 -#: com/templates/com/news_admin_list.jinja:274 -msgid "Remove" -msgstr "Retirer" - #: com/templates/com/news_admin_list.jinja:47 msgid "Notices to moderate" msgstr "Informations à modérer" @@ -4612,10 +4662,6 @@ msgstr "Éditer la page de présentation" msgid "Book launderette slot" msgstr "Réserver un créneau de laverie" -#: launderette/views.py:228 -msgid "Action" -msgstr "Action" - #: launderette/views.py:240 msgid "Tokens, separated by spaces" msgstr "Jetons, séparés par des espaces"