mirror of
https://github.com/ae-utbm/sith.git
synced 2024-11-22 06:03:20 +00:00
Merge branch 'mailing' into 'master'
Enhance mailing list edition for clubs with brand new form See merge request ae/Sith!200
This commit is contained in:
commit
9f2a0deeb9
291
club/forms.py
Normal file
291
club/forms.py
Normal file
@ -0,0 +1,291 @@
|
|||||||
|
# -*- coding:utf-8 -*
|
||||||
|
#
|
||||||
|
# Copyright 2016,2017
|
||||||
|
# - Skia <skia@libskia.so>
|
||||||
|
# - Sli <antoine@bartuccio.fr>
|
||||||
|
#
|
||||||
|
# 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
|
@ -338,6 +338,8 @@ class Mailing(models.Model):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
|
if Mailing.objects.filter(email=self.email).exists():
|
||||||
|
raise ValidationError(_("This mailing list already exists."))
|
||||||
if self.can_moderate(self.moderator):
|
if self.can_moderate(self.moderator):
|
||||||
self.is_moderated = True
|
self.is_moderated = True
|
||||||
else:
|
else:
|
||||||
@ -446,18 +448,20 @@ class MailingSubscription(models.Model):
|
|||||||
def can_be_edited_by(self, user):
|
def can_be_edited_by(self, user):
|
||||||
return self.user is not None and user.id == self.user.id
|
return self.user is not None and user.id == self.user.id
|
||||||
|
|
||||||
@property
|
@cached_property
|
||||||
def get_email(self):
|
def get_email(self):
|
||||||
if self.user and not self.email:
|
if self.user and not self.email:
|
||||||
return self.user.email
|
return self.user.email
|
||||||
return self.email
|
return self.email
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def get_username(self):
|
||||||
|
if self.user:
|
||||||
|
return str(self.user)
|
||||||
|
return _("Unregistered user")
|
||||||
|
|
||||||
def fetch_format(self):
|
def fetch_format(self):
|
||||||
return self.get_email + " "
|
return self.get_email + " "
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
if self.user:
|
return "(%s) - %s : %s" % (self.mailing, self.get_username, self.email)
|
||||||
user = str(self.user)
|
|
||||||
else:
|
|
||||||
user = _("Unregistered user")
|
|
||||||
return "(%s) - %s : %s" % (self.mailing, user, self.email)
|
|
||||||
|
@ -1,17 +1,26 @@
|
|||||||
{% extends "core/base.jinja" %}
|
{% extends "core/base.jinja" %}
|
||||||
|
{% from 'core/macros.jinja' import select_all_checkbox %}
|
||||||
|
|
||||||
{% block title %}
|
{% block title %}
|
||||||
{% trans %}Mailing lists{% endtrans %}
|
{% trans %}Mailing lists{% endtrans %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% if has_objects %}
|
|
||||||
|
|
||||||
<b>{% trans %}Remember : mailing lists need to be moderated, if your new created list is not shown wait until moderation takes action{% endtrans %}</b>
|
<b>{% trans %}Remember : mailing lists need to be moderated, if your new created list is not shown wait until moderation takes action{% endtrans %}</b>
|
||||||
|
|
||||||
{% for mailing in object_list %}
|
{% if mailings_not_moderated %}
|
||||||
{% if mailing.is_moderated %}
|
<p>{% trans %}Mailing lists waiting for moderation{% endtrans %}</p>
|
||||||
<h2>{% trans %}Mailing{% endtrans %} {{ mailing.email_full }}
|
<ul>
|
||||||
|
{% for mailing in mailings_not_moderated %}
|
||||||
|
<li>{{ mailing.email_full }}<a href="{{ url('club:mailing_delete', mailing_id=mailing.id) }}"> - {% trans %}Delete{% endtrans %}</a></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if mailings_moderated %}
|
||||||
|
{% for mailing in mailings_moderated %}
|
||||||
|
<h2>{% trans %}Mailing{% endtrans %} {{ mailing.email_full }}
|
||||||
{%- if user.is_owner(mailing) -%}
|
{%- if user.is_owner(mailing) -%}
|
||||||
<a href="{{ url('club:mailing_delete', mailing_id=mailing.id) }}"> - {% trans %}Delete{% endtrans %}</a>
|
<a href="{{ url('club:mailing_delete', mailing_id=mailing.id) }}"> - {% trans %}Delete{% endtrans %}</a>
|
||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
@ -19,27 +28,38 @@
|
|||||||
<form method="GET" action="{{ url('club:mailing_generate', mailing_id=mailing.id) }}" style="display:inline-block;">
|
<form method="GET" action="{{ url('club:mailing_generate', mailing_id=mailing.id) }}" style="display:inline-block;">
|
||||||
<input type="submit" name="generateMalingList" value="{% trans %}Generate mailing list{% endtrans %}">
|
<input type="submit" name="generateMalingList" value="{% trans %}Generate mailing list{% endtrans %}">
|
||||||
</form>
|
</form>
|
||||||
<form method="GET" action="{{ url('club:mailing_clean', mailing_id=mailing.id) }}" style="display:inline-block;">
|
{% set form_mailing_removal = form["removal_" + mailing.id|string] %}
|
||||||
<input type="submit" name="cleanMailingList" value="{% trans %}Clean mailing list{% endtrans %}">
|
{% if form_mailing_removal.field.choices %}
|
||||||
|
{% set ms = dict(mailing.subscriptions.all() | groupby('id')) %}
|
||||||
|
<form action="{{ url('club:mailing', club_id=club.id) }}" id="{{ form_mailing_removal.auto_id }}" method="post" enctype="multipart/form-data">
|
||||||
|
<p style="margin-bottom: 1em;">{{ select_all_checkbox(form_mailing_removal.auto_id) }}</p>
|
||||||
|
{% csrf_token %}
|
||||||
|
<input hidden type="number" name="{{ form.action.name }}" value="{{ form_actions.REMOVE_SUBSCRIPTION }}" />
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<td>{% trans %}User{% endtrans %}</td>
|
||||||
|
<td>{% trans %}Email{% endtrans %}</td>
|
||||||
|
<td>{% trans %}Delete{% endtrans %}</td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for widget in form_mailing_removal.subwidgets %}
|
||||||
|
{% set user = ms[widget.data.value][0] %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ user.get_username }}</td>
|
||||||
|
<td>{{ user.get_email }}</td>
|
||||||
|
<td>{{ widget.tag() }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{{ form_mailing_removal.errors }}
|
||||||
|
<p><input type="submit" value="{% trans %}Remove from mailing list{% endtrans %}" /></p>
|
||||||
</form>
|
</form>
|
||||||
<hr>
|
|
||||||
<table>
|
{% else %}
|
||||||
<tr>
|
<p><b>{% trans %}There is no subscriber for this mailing list{% endtrans %}</b></p>
|
||||||
<th>{% trans %}User{% endtrans %}</th>
|
|
||||||
<th colspan="2">{% trans %}Email{%endtrans%}</th>
|
|
||||||
</tr>
|
|
||||||
{% for subscriber in mailing.subscriptions.all() %}
|
|
||||||
<tr>
|
|
||||||
{% if subscriber.user %}
|
|
||||||
<td>{{ subscriber.user }}</td>
|
|
||||||
{% else %}
|
|
||||||
<td>{% trans %}Unregistered user{% endtrans %}</td>
|
|
||||||
{% endif %}
|
|
||||||
<td>{{ subscriber.get_email }}</td>
|
|
||||||
<td><a href="{{ url('club:mailing_subscription_delete', mailing_subscription_id=subscriber.id) }}">{% trans %}Delete{% endtrans %}</a></td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</table>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
@ -47,19 +67,41 @@
|
|||||||
<p>{% trans %}No mailing list existing for this club{% endtrans %}</p>
|
<p>{% trans %}No mailing list existing for this club{% endtrans %}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if has_objects %}
|
<p>{{ form.non_field_errors() }}</p>
|
||||||
|
{% if mailings_moderated %}
|
||||||
<h2>{% trans %}New member{% endtrans %}</h2>
|
<h2>{% trans %}New member{% endtrans %}</h2>
|
||||||
<form action="{{ url('club:mailing_subscription_create', club_id=club.id) }}" method="post" enctype="multipart/form-data">
|
<form action="{{ url('club:mailing', club_id=club.id) }}" method="post" enctype="multipart/form-data">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{ add_member.as_p() }}
|
<p>
|
||||||
|
{{ form.subscription_mailing.errors }}
|
||||||
|
<label for="{{ form.subscription_mailing.id_for_label }}">{{ form.subscription_mailing.label }}</label>
|
||||||
|
{{ form.subscription_mailing }}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{{ form.subscription_users.errors }}
|
||||||
|
<label for="{{ form.subscription_users.id_for_label }}">{{ form.subscription_users.label }}</label>
|
||||||
|
{{ form.subscription_users }}
|
||||||
|
<span class="helptext">{{ form.subscription_users.help_text }}</span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{{ form.subscription_email.errors }}
|
||||||
|
<label for="{{ form.subscription_email.id_for_label }}">{{ form.subscription_email.label }}</label>
|
||||||
|
{{ form.subscription_email }}
|
||||||
|
</p>
|
||||||
|
<input hidden type="number" name="{{ form.action.name }}" value="{{ form_actions.NEW_SUBSCRIPTION }}" />
|
||||||
<p><input type="submit" value="{% trans %}Add to mailing list{% endtrans %}" /></p>
|
<p><input type="submit" value="{% trans %}Add to mailing list{% endtrans %}" /></p>
|
||||||
</form>
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<h2>{% trans %}New mailing{% endtrans %}</h2>
|
<h2>{% trans %}New mailing{% endtrans %}</h2>
|
||||||
<form action="{{ url('club:mailing_create', club_id=club.id) }}" method="post" enctype="multipart/form-data">
|
<form action="{{ url('club:mailing', club_id=club.id) }}" method="post" enctype="multipart/form-data">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{ add_mailing.as_p() }}
|
<p>
|
||||||
|
{{ form.mailing_email.errors }}
|
||||||
|
<label for="{{ form.mailing_email.id_for_label }}">{{ form.mailing_email.label }}</label>
|
||||||
|
{{ form.mailing_email }}
|
||||||
|
</p>
|
||||||
|
<input hidden type="number" name="{{ form.action.name }}" value="{{ form_actions.NEW_MALING }}" />
|
||||||
<p><input type="submit" value="{% trans %}Create mailing list{% endtrans %}" /></p>
|
<p><input type="submit" value="{% trans %}Create mailing list{% endtrans %}" /></p>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
320
club/tests.py
320
club/tests.py
@ -22,12 +22,17 @@
|
|||||||
#
|
#
|
||||||
#
|
#
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
from django.test import TestCase
|
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.urlresolvers import reverse
|
||||||
from django.core.management import call_command
|
from django.core.management import call_command
|
||||||
|
from django.core.exceptions import ValidationError, NON_FIELD_ERRORS
|
||||||
|
|
||||||
from core.models import User
|
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.
|
# Create your tests here.
|
||||||
|
|
||||||
@ -373,3 +378,316 @@ class ClubTest(TestCase):
|
|||||||
"S' Kia</a></td>\\n <td>Responsable info</td>"
|
"S' Kia</a></td>\\n <td>Responsable info</td>"
|
||||||
in content
|
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="<p>Listes de diffusions en attente de modération</p>"
|
||||||
|
)
|
||||||
|
self.assertContains(response, "<li>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")
|
||||||
|
21
club/urls.py
21
club/urls.py
@ -64,31 +64,12 @@ urlpatterns = [
|
|||||||
),
|
),
|
||||||
url(r"^(?P<club_id>[0-9]+)/prop$", ClubEditPropView.as_view(), name="club_prop"),
|
url(r"^(?P<club_id>[0-9]+)/prop$", ClubEditPropView.as_view(), name="club_prop"),
|
||||||
url(r"^(?P<club_id>[0-9]+)/tools$", ClubToolsView.as_view(), name="tools"),
|
url(r"^(?P<club_id>[0-9]+)/tools$", ClubToolsView.as_view(), name="tools"),
|
||||||
url(
|
url(r"^(?P<club_id>[0-9]+)/mailing$", ClubMailingView.as_view(), name="mailing"),
|
||||||
r"^(?P<club_id>[0-9]+)/mailing$",
|
|
||||||
ClubMailingView.as_view(action="display"),
|
|
||||||
name="mailing",
|
|
||||||
),
|
|
||||||
url(
|
|
||||||
r"^(?P<club_id>[0-9]+)/mailing/new/mailing$",
|
|
||||||
ClubMailingView.as_view(action="add_mailing"),
|
|
||||||
name="mailing_create",
|
|
||||||
),
|
|
||||||
url(
|
|
||||||
r"^(?P<club_id>[0-9]+)/mailing/new/subscription$",
|
|
||||||
ClubMailingView.as_view(action="add_member"),
|
|
||||||
name="mailing_subscription_create",
|
|
||||||
),
|
|
||||||
url(
|
url(
|
||||||
r"^(?P<mailing_id>[0-9]+)/mailing/generate$",
|
r"^(?P<mailing_id>[0-9]+)/mailing/generate$",
|
||||||
MailingAutoGenerationView.as_view(),
|
MailingAutoGenerationView.as_view(),
|
||||||
name="mailing_generate",
|
name="mailing_generate",
|
||||||
),
|
),
|
||||||
url(
|
|
||||||
r"^(?P<mailing_id>[0-9]+)/mailing/clean$",
|
|
||||||
MailingAutoCleanView.as_view(),
|
|
||||||
name="mailing_clean",
|
|
||||||
),
|
|
||||||
url(
|
url(
|
||||||
r"^(?P<mailing_id>[0-9]+)/mailing/delete$",
|
r"^(?P<mailing_id>[0-9]+)/mailing/delete$",
|
||||||
MailingDeleteView.as_view(),
|
MailingDeleteView.as_view(),
|
||||||
|
385
club/views.py
385
club/views.py
@ -23,6 +23,8 @@
|
|||||||
#
|
#
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.views.generic import ListView, DetailView, TemplateView, View
|
from django.views.generic import ListView, DetailView, TemplateView, View
|
||||||
from django.views.generic.edit import DeleteView
|
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 import timezone
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.utils.translation import ugettext as _t
|
from django.utils.translation import ugettext as _t
|
||||||
from ajax_select.fields import AutoCompleteSelectField, AutoCompleteSelectMultipleField
|
from django.core.exceptions import PermissionDenied, ValidationError, NON_FIELD_ERRORS
|
||||||
from django.core.exceptions import PermissionDenied
|
|
||||||
from django.shortcuts import get_object_or_404, redirect
|
from django.shortcuts import get_object_or_404, redirect
|
||||||
|
|
||||||
from core.views import (
|
from core.views import (
|
||||||
@ -46,71 +47,19 @@ from core.views import (
|
|||||||
PageEditViewBase,
|
PageEditViewBase,
|
||||||
DetailFormView,
|
DetailFormView,
|
||||||
)
|
)
|
||||||
from core.views.forms import SelectDate, SelectDateTime
|
from core.models import PageRev
|
||||||
from club.models import Club, Membership, Mailing, MailingSubscription
|
|
||||||
from sith.settings import SITH_MAXIMUM_FREE_ROLE
|
from counter.models import Selling
|
||||||
from counter.models import Selling, Counter
|
|
||||||
from core.models import User, PageRev
|
|
||||||
from com.views import (
|
from com.views import (
|
||||||
PosterListBaseView,
|
PosterListBaseView,
|
||||||
PosterCreateBaseView,
|
PosterCreateBaseView,
|
||||||
PosterEditBaseView,
|
PosterEditBaseView,
|
||||||
PosterDeleteBaseView,
|
PosterDeleteBaseView,
|
||||||
)
|
)
|
||||||
from com.models import Poster
|
|
||||||
|
|
||||||
from django.conf import settings
|
from club.models import Club, Membership, Mailing, MailingSubscription
|
||||||
|
from club.forms import MailingForm, ClubEditForm, ClubMemberForm
|
||||||
# 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
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class ClubTabsMixin(TabedViewMixin):
|
class ClubTabsMixin(TabedViewMixin):
|
||||||
@ -306,122 +255,6 @@ class ClubToolsView(ClubTabsMixin, CanEditMixin, DetailView):
|
|||||||
current_tab = "tools"
|
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):
|
class ClubMembersView(ClubTabsMixin, CanViewMixin, DetailFormView):
|
||||||
"""
|
"""
|
||||||
View of a club's members
|
View of a club's members
|
||||||
@ -485,24 +318,6 @@ class ClubOldMembersView(ClubTabsMixin, CanViewMixin, DetailView):
|
|||||||
current_tab = "elderlies"
|
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):
|
class ClubSellingView(ClubTabsMixin, CanEditMixin, DetailView):
|
||||||
"""
|
"""
|
||||||
Sellings of a club
|
Sellings of a club
|
||||||
@ -687,94 +502,134 @@ class ClubStatView(TemplateView):
|
|||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
|
|
||||||
class ClubMailingView(ClubTabsMixin, ListView):
|
class ClubMailingView(ClubTabsMixin, CanEditMixin, DetailFormView):
|
||||||
"""
|
"""
|
||||||
A list of mailing for a given club
|
A list of mailing for a given club
|
||||||
"""
|
"""
|
||||||
|
|
||||||
action = None
|
model = Club
|
||||||
model = Mailing
|
form_class = MailingForm
|
||||||
|
pk_url_kwarg = "club_id"
|
||||||
template_name = "club/mailing.jinja"
|
template_name = "club/mailing.jinja"
|
||||||
current_tab = "mailing"
|
current_tab = "mailing"
|
||||||
|
|
||||||
def authorized(self):
|
def get_form_kwargs(self):
|
||||||
return (
|
kwargs = super(ClubMailingView, self).get_form_kwargs()
|
||||||
self.club.has_rights_in_club(self.user)
|
kwargs["club_id"] = self.get_object().id
|
||||||
or self.user.is_root
|
kwargs["user_id"] = self.request.user.id
|
||||||
or self.user.is_in_group(settings.SITH_GROUP_COM_ADMIN_ID)
|
kwargs["mailings"] = self.mailings
|
||||||
)
|
return kwargs
|
||||||
|
|
||||||
def dispatch(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
self.club = get_object_or_404(Club, pk=kwargs["club_id"])
|
self.mailings = Mailing.objects.filter(club_id=self.get_object().id).all()
|
||||||
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)
|
|
||||||
return super(ClubMailingView, self).dispatch(request, *args, **kwargs)
|
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):
|
def get_context_data(self, **kwargs):
|
||||||
kwargs = super(ClubMailingView, self).get_context_data(**kwargs)
|
kwargs = super(ClubMailingView, self).get_context_data(**kwargs)
|
||||||
kwargs["add_member"] = self.member_form
|
kwargs["club"] = self.get_object()
|
||||||
kwargs["add_mailing"] = self.mailing_form
|
kwargs["user"] = self.request.user
|
||||||
kwargs["club"] = self.club
|
kwargs["mailings"] = self.mailings
|
||||||
kwargs["user"] = self.user
|
kwargs["mailings_moderated"] = (
|
||||||
kwargs["has_objects"] = len(kwargs["object_list"]) > 0
|
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
|
return kwargs
|
||||||
|
|
||||||
def get_object(self):
|
def add_new_mailing(self, cleaned_data) -> ValidationError:
|
||||||
return self.club
|
"""
|
||||||
|
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):
|
for user in cleaned_data["subscription_users"]:
|
||||||
"""
|
sub = MailingSubscription(
|
||||||
Create a new mailing list
|
mailing=cleaned_data["subscription_mailing"], user=user
|
||||||
"""
|
)
|
||||||
|
try:
|
||||||
|
sub.clean()
|
||||||
|
except ValidationError as validation_error:
|
||||||
|
return validation_error
|
||||||
|
|
||||||
list_view = None
|
users_to_save.append(sub.save())
|
||||||
form_class = None
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
if cleaned_data["subscription_email"]:
|
||||||
view_kwargs = self.list_view.get_context_data(**kwargs)
|
sub = MailingSubscription(
|
||||||
for key, data in (
|
mailing=cleaned_data["subscription_mailing"],
|
||||||
super(MailingGenericCreateView, self).get_context_data(**kwargs).items()
|
email=cleaned_data["subscription_email"],
|
||||||
):
|
)
|
||||||
view_kwargs[key] = data
|
|
||||||
view_kwargs[self.list_view.action] = view_kwargs["form"]
|
|
||||||
return view_kwargs
|
|
||||||
|
|
||||||
def get_form_kwargs(self):
|
try:
|
||||||
kwargs = super(MailingGenericCreateView, self).get_form_kwargs()
|
sub.clean()
|
||||||
kwargs["club_id"] = self.list_view.club.id
|
except ValidationError as validation_error:
|
||||||
kwargs["user_id"] = self.list_view.user.id
|
return validation_error
|
||||||
return kwargs
|
sub.save()
|
||||||
|
|
||||||
def dispatch(self, request, *args, **kwargs):
|
# Save users after we are sure there is no error
|
||||||
if not self.list_view.authorized():
|
for user in users_to_save:
|
||||||
raise PermissionDenied
|
user.save()
|
||||||
self.template_name = self.list_view.template_name
|
|
||||||
return super(MailingGenericCreateView, self).dispatch(request, *args, **kwargs)
|
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):
|
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):
|
class MailingDeleteView(CanEditMixin, DeleteView):
|
||||||
@ -829,18 +684,6 @@ class MailingAutoGenerationView(View):
|
|||||||
return redirect("club:mailing", club_id=club.id)
|
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):
|
class PosterListView(ClubTabsMixin, PosterListBaseView, CanViewMixin):
|
||||||
"""List communication posters"""
|
"""List communication posters"""
|
||||||
|
|
||||||
|
@ -837,6 +837,27 @@ Welcome to the wiki page!
|
|||||||
krophil_profile.save()
|
krophil_profile.save()
|
||||||
krophil.profile_pict = krophil_profile
|
krophil.profile_pict = krophil_profile
|
||||||
krophil.save()
|
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
|
# Adding subscription for sli
|
||||||
s = Subscription(
|
s = Subscription(
|
||||||
member=User.objects.filter(pk=sli.pk).first(),
|
member=User.objects.filter(pk=sli.pk).first(),
|
||||||
@ -861,6 +882,18 @@ Welcome to the wiki page!
|
|||||||
start=s.subscription_start,
|
start=s.subscription_start,
|
||||||
)
|
)
|
||||||
s.save()
|
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(
|
Selling(
|
||||||
label=dcons.name,
|
label=dcons.name,
|
||||||
|
@ -296,11 +296,6 @@ class DetailFormView(SingleObjectMixin, FormView):
|
|||||||
"""
|
"""
|
||||||
return super(DetailFormView, self).get_object()
|
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 .user import *
|
||||||
from .page import *
|
from .page import *
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"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"
|
"PO-Revision-Date: 2016-07-18\n"
|
||||||
"Last-Translator: Skia <skia@libskia.so>\n"
|
"Last-Translator: Skia <skia@libskia.so>\n"
|
||||||
"Language-Team: AE info <ae.info@utbm.fr>\n"
|
"Language-Team: AE info <ae.info@utbm.fr>\n"
|
||||||
@ -174,10 +174,10 @@ msgstr "étiquette"
|
|||||||
msgid "target type"
|
msgid "target type"
|
||||||
msgstr "type de cible"
|
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_members.jinja:16
|
||||||
#: club/templates/club/club_old_members.jinja:8
|
#: 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/cash_summary_list.jinja:32
|
||||||
#: counter/templates/counter/stats.jinja:15
|
#: counter/templates/counter/stats.jinja:15
|
||||||
#: counter/templates/counter/stats.jinja:52
|
#: 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/club_account_details.jinja:60
|
||||||
#: accounting/templates/accounting/label_list.jinja:26
|
#: accounting/templates/accounting/label_list.jinja:26
|
||||||
#: club/templates/club/club_sellings.jinja:50
|
#: 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/mailing_admin.jinja:19
|
||||||
#: com/templates/com/news_admin_list.jinja:41
|
#: com/templates/com/news_admin_list.jinja:41
|
||||||
#: com/templates/com/news_admin_list.jinja:70
|
#: com/templates/com/news_admin_list.jinja:70
|
||||||
@ -386,7 +386,7 @@ msgid "Delete"
|
|||||||
msgstr "Supprimer"
|
msgstr "Supprimer"
|
||||||
|
|
||||||
#: accounting/templates/accounting/bank_account_details.jinja:18
|
#: 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"
|
msgid "Infos"
|
||||||
msgstr "Infos"
|
msgstr "Infos"
|
||||||
|
|
||||||
@ -405,7 +405,7 @@ msgstr "Nouveau compte club"
|
|||||||
#: accounting/templates/accounting/bank_account_details.jinja:27
|
#: accounting/templates/accounting/bank_account_details.jinja:27
|
||||||
#: accounting/templates/accounting/bank_account_list.jinja:22
|
#: accounting/templates/accounting/bank_account_list.jinja:22
|
||||||
#: accounting/templates/accounting/club_account_details.jinja:58
|
#: 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:39
|
||||||
#: com/templates/com/news_admin_list.jinja:68
|
#: com/templates/com/news_admin_list.jinja:68
|
||||||
#: com/templates/com/news_admin_list.jinja:115
|
#: com/templates/com/news_admin_list.jinja:115
|
||||||
@ -893,6 +893,101 @@ msgstr "Opérations sans étiquette"
|
|||||||
msgid "Refound this account"
|
msgid "Refound this account"
|
||||||
msgstr "Rembourser ce compte"
|
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
|
#: club/models.py:51
|
||||||
msgid "unix name"
|
msgid "unix name"
|
||||||
msgstr "nom unix"
|
msgstr "nom unix"
|
||||||
@ -959,7 +1054,7 @@ msgstr "description"
|
|||||||
msgid "past member"
|
msgid "past member"
|
||||||
msgstr "Anciens membres"
|
msgstr "Anciens membres"
|
||||||
|
|
||||||
#: club/models.py:323 club/models.py:418
|
#: club/models.py:323 club/models.py:420
|
||||||
msgid "Email address"
|
msgid "Email address"
|
||||||
msgstr "Adresse email"
|
msgstr "Adresse email"
|
||||||
|
|
||||||
@ -976,21 +1071,25 @@ msgstr "est modéré"
|
|||||||
msgid "moderator"
|
msgid "moderator"
|
||||||
msgstr "modérateur"
|
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"
|
msgid "Mailing"
|
||||||
msgstr "Liste de diffusion"
|
msgstr "Liste de diffusion"
|
||||||
|
|
||||||
#: club/models.py:425
|
#: club/models.py:427
|
||||||
msgid "At least user or email is required"
|
msgid "At least user or email is required"
|
||||||
msgstr "Au moins un utilisateur ou un email est nécessaire"
|
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"
|
msgid "This email is already suscribed in this mailing"
|
||||||
msgstr "Cet email est déjà abonné à cette 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"
|
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
|
#: club/templates/club/club_list.jinja:4 club/templates/club/club_list.jinja:37
|
||||||
msgid "Club list"
|
msgid "Club list"
|
||||||
@ -1037,12 +1136,6 @@ msgstr "Description"
|
|||||||
msgid "Since"
|
msgid "Since"
|
||||||
msgstr "Depuis"
|
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
|
#: club/templates/club/club_members.jinja:50
|
||||||
msgid "There are no members in this club."
|
msgid "There are no members in this club."
|
||||||
msgstr "Il n'y a pas de membres dans ce club."
|
msgstr "Il n'y a pas de membres dans ce club."
|
||||||
@ -1067,8 +1160,8 @@ msgstr "Du"
|
|||||||
msgid "To"
|
msgid "To"
|
||||||
msgstr "Au"
|
msgstr "Au"
|
||||||
|
|
||||||
#: club/templates/club/club_sellings.jinja:5 club/views.py:195
|
#: club/templates/club/club_sellings.jinja:5 club/views.py:144
|
||||||
#: club/views.py:563 counter/templates/counter/counter_main.jinja:19
|
#: club/views.py:378 counter/templates/counter/counter_main.jinja:19
|
||||||
#: counter/templates/counter/last_ops.jinja:35
|
#: counter/templates/counter/last_ops.jinja:35
|
||||||
msgid "Sellings"
|
msgid "Sellings"
|
||||||
msgstr "Ventes"
|
msgstr "Ventes"
|
||||||
@ -1094,13 +1187,6 @@ msgstr "unités"
|
|||||||
msgid "Benefit: "
|
msgid "Benefit: "
|
||||||
msgstr "Bénéfice : "
|
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
|
#: club/templates/club/club_sellings.jinja:22
|
||||||
#: core/templates/core/user_account_detail.jinja:19
|
#: core/templates/core/user_account_detail.jinja:19
|
||||||
#: core/templates/core/user_account_detail.jinja:52
|
#: core/templates/core/user_account_detail.jinja:52
|
||||||
@ -1189,7 +1275,7 @@ msgstr "Comptabilité : "
|
|||||||
msgid "Manage launderettes"
|
msgid "Manage launderettes"
|
||||||
msgstr "Gestion des laveries"
|
msgstr "Gestion des laveries"
|
||||||
|
|
||||||
#: club/templates/club/mailing.jinja:4
|
#: club/templates/club/mailing.jinja:5
|
||||||
msgid "Mailing lists"
|
msgid "Mailing lists"
|
||||||
msgstr "Mailing listes"
|
msgstr "Mailing listes"
|
||||||
|
|
||||||
@ -1202,38 +1288,46 @@ msgstr ""
|
|||||||
"nouvellement créee n'est pas affichée, attendez jusqu'à qu'un modérateur "
|
"nouvellement créee n'est pas affichée, attendez jusqu'à qu'un modérateur "
|
||||||
"entre en action"
|
"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"
|
msgid "Generate mailing list"
|
||||||
msgstr "Générer la liste de diffusion"
|
msgstr "Générer la liste de diffusion"
|
||||||
|
|
||||||
#: club/templates/club/mailing.jinja:23
|
#: club/templates/club/mailing.jinja:42
|
||||||
msgid "Clean mailing list"
|
|
||||||
msgstr "Néttoyer la liste de diffusion"
|
|
||||||
|
|
||||||
#: club/templates/club/mailing.jinja:29
|
|
||||||
#: com/templates/com/mailing_admin.jinja:10
|
#: com/templates/com/mailing_admin.jinja:10
|
||||||
msgid "Email"
|
msgid "Email"
|
||||||
msgstr "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"
|
msgid "No mailing list existing for this club"
|
||||||
msgstr "Aucune mailing liste n'existe pour ce 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"
|
msgid "New member"
|
||||||
msgstr "Nouveau membre"
|
msgstr "Nouveau membre"
|
||||||
|
|
||||||
#: club/templates/club/mailing.jinja:55
|
#: club/templates/club/mailing.jinja:92
|
||||||
msgid "Add to mailing list"
|
msgid "Add to mailing list"
|
||||||
msgstr "Ajouter à la mailing liste"
|
msgstr "Ajouter à la mailing liste"
|
||||||
|
|
||||||
#: club/templates/club/mailing.jinja:59
|
#: club/templates/club/mailing.jinja:96
|
||||||
msgid "New mailing"
|
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"
|
msgid "Create mailing list"
|
||||||
msgstr "Créer une mailing liste"
|
msgstr "Créer une liste de diffusion"
|
||||||
|
|
||||||
#: club/templates/club/page_history.jinja:8
|
#: club/templates/club/page_history.jinja:8
|
||||||
msgid "No page existing for this club"
|
msgid "No page existing for this club"
|
||||||
@ -1243,79 +1337,42 @@ msgstr "Aucune page n'existe pour ce club"
|
|||||||
msgid "Club stats"
|
msgid "Club stats"
|
||||||
msgstr "Statistiques du club"
|
msgstr "Statistiques du club"
|
||||||
|
|
||||||
#: club/views.py:139
|
#: club/views.py:88
|
||||||
msgid "Members"
|
msgid "Members"
|
||||||
msgstr "Membres"
|
msgstr "Membres"
|
||||||
|
|
||||||
#: club/views.py:148
|
#: club/views.py:97
|
||||||
msgid "Old members"
|
msgid "Old members"
|
||||||
msgstr "Anciens membres"
|
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"
|
msgid "History"
|
||||||
msgstr "Historique"
|
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
|
#: sas/templates/sas/picture.jinja:95 trombi/views.py:60
|
||||||
msgid "Tools"
|
msgid "Tools"
|
||||||
msgstr "Outils"
|
msgstr "Outils"
|
||||||
|
|
||||||
#: club/views.py:186
|
#: club/views.py:135
|
||||||
msgid "Edit club page"
|
msgid "Edit club page"
|
||||||
msgstr "Éditer la page de club"
|
msgstr "Éditer la page de club"
|
||||||
|
|
||||||
#: club/views.py:202
|
#: club/views.py:151
|
||||||
msgid "Mailing list"
|
msgid "Mailing list"
|
||||||
msgstr "Listes de diffusion"
|
msgstr "Listes de diffusion"
|
||||||
|
|
||||||
#: club/views.py:211 com/views.py:141
|
#: club/views.py:160 com/views.py:141
|
||||||
msgid "Posters list"
|
msgid "Posters list"
|
||||||
msgstr "Liste d'affiches"
|
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:43
|
||||||
#: counter/templates/counter/counter_list.jinja:59
|
#: counter/templates/counter/counter_list.jinja:59
|
||||||
msgid "Props"
|
msgid "Props"
|
||||||
msgstr "Propriétés"
|
msgstr "Propriétés"
|
||||||
|
|
||||||
#: club/views.py:319
|
#: club/views.py:335 core/templates/core/user_stats.jinja:27
|
||||||
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
|
|
||||||
#: counter/views.py:1635
|
#: counter/views.py:1635
|
||||||
msgid "Product"
|
msgid "Product"
|
||||||
msgstr "Produit"
|
msgstr "Produit"
|
||||||
@ -1547,13 +1604,6 @@ msgstr "Auteur"
|
|||||||
msgid "Moderator"
|
msgid "Moderator"
|
||||||
msgstr "Modérateur"
|
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
|
#: com/templates/com/news_admin_list.jinja:47
|
||||||
msgid "Notices to moderate"
|
msgid "Notices to moderate"
|
||||||
msgstr "Informations à modérer"
|
msgstr "Informations à modérer"
|
||||||
@ -4612,10 +4662,6 @@ msgstr "Éditer la page de présentation"
|
|||||||
msgid "Book launderette slot"
|
msgid "Book launderette slot"
|
||||||
msgstr "Réserver un créneau de laverie"
|
msgstr "Réserver un créneau de laverie"
|
||||||
|
|
||||||
#: launderette/views.py:228
|
|
||||||
msgid "Action"
|
|
||||||
msgstr "Action"
|
|
||||||
|
|
||||||
#: launderette/views.py:240
|
#: launderette/views.py:240
|
||||||
msgid "Tokens, separated by spaces"
|
msgid "Tokens, separated by spaces"
|
||||||
msgstr "Jetons, séparés par des espaces"
|
msgstr "Jetons, séparés par des espaces"
|
||||||
|
Loading…
Reference in New Issue
Block a user