From 7cb869809e2c1be25dcbdf385535723bbf4db19b Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Sun, 19 Apr 2020 20:06:34 +0200 Subject: [PATCH 1/8] Make emails throw timeout errors, and gracefully handle them --- cotisations/models.py | 11 ++++++++--- cotisations/utils.py | 27 +++++++++++++++++++++++---- cotisations/views.py | 2 +- re2o/settings.py | 3 +++ re2o/utils.py | 18 ++++++++++++++++++ tickets/models.py | 9 ++++++--- tickets/views.py | 2 ++ users/models.py | 32 +++++++++++++++++++++++--------- users/views.py | 8 ++++++++ 9 files changed, 92 insertions(+), 20 deletions(-) diff --git a/cotisations/models.py b/cotisations/models.py index 215fcdb5..503ad244 100644 --- a/cotisations/models.py +++ b/cotisations/models.py @@ -330,9 +330,14 @@ class Facture(BaseInvoice): def save(self, *args, **kwargs): super(Facture, self).save(*args, **kwargs) + + request = None + if "request" in kwargs: + request = kwargs["request"] + if not self.__original_valid and self.valid: self.reorder_purchases() - send_mail_invoice(self) + send_mail_invoice(self, request) if ( self.is_subscription() and not self.__original_control @@ -340,7 +345,7 @@ class Facture(BaseInvoice): and CotisationsOption.get_cached_value("send_voucher_mail") and self.user.is_adherent() ): - send_mail_voucher(self) + send_mail_voucher(self, request) def __str__(self): return str(self.user) + " " + str(self.date) @@ -870,7 +875,7 @@ class Paiement(RevMixin, AclMixin, models.Model): # So make this invoice valid, trigger send mail invoice.valid = True - invoice.save() + invoice.save(request) # In case a cotisation was bought, inform the user, the # cotisation time has been extended too diff --git a/cotisations/utils.py b/cotisations/utils.py index 95672dd4..c370fe59 100644 --- a/cotisations/utils.py +++ b/cotisations/utils.py @@ -23,6 +23,9 @@ import os from django.template.loader import get_template from django.core.mail import EmailMessage +from django.utils.translation import ugettext_lazy as _ +from django.contrib import messages +from smtplib import SMTPException from .tex import create_pdf from preferences.models import AssoOption, GeneralOption, CotisationsOption, Mandate @@ -43,7 +46,21 @@ def find_payment_method(payment): return None -def send_mail_invoice(invoice): +def send_mail(mail, request): + """Wrapper for Django's EmailMessage.send which handles errors""" + try: + mail.send() + except SMTPException as e: + if request: + messages.error( + request, + _("Failed to send email: %(error)s.") % { + "error": e, + }, + ) + + +def send_mail_invoice(invoice, request=None): """Creates the pdf of the invoice and sends it by email to the client""" purchases_info = [] for purchase in invoice.vente_set.all(): @@ -90,10 +107,11 @@ def send_mail_invoice(invoice): [invoice.user.get_mail], attachments=[("invoice.pdf", pdf, "application/pdf")], ) - mail.send() + + send_mail(mail, request) -def send_mail_voucher(invoice): +def send_mail_voucher(invoice, request=None): """Creates a voucher from an invoice and sends it by email to the client""" president = Mandate.get_mandate(invoice.date).president ctx = { @@ -126,4 +144,5 @@ def send_mail_voucher(invoice): [invoice.user.get_mail], attachments=[("voucher.pdf", pdf, "application/pdf")], ) - mail.send() + + send_mail(mail, request) diff --git a/cotisations/views.py b/cotisations/views.py index cc80e22d..def26cda 100644 --- a/cotisations/views.py +++ b/cotisations/views.py @@ -1008,7 +1008,7 @@ def credit_solde(request, user, **_kwargs): else: price_ok = True if price_ok: - invoice.save() + invoice.save(request) Vente.objects.create( facture=invoice, name="solde", diff --git a/re2o/settings.py b/re2o/settings.py index 3d883615..c6df1a69 100644 --- a/re2o/settings.py +++ b/re2o/settings.py @@ -181,6 +181,9 @@ MEDIA_URL = os.path.join(BASE_DIR, "/media/") # Models to use for graphs GRAPH_MODELS = {"all_applications": True, "group_models": True} +# Timeout when sending emails through Django (in seconds) +EMAIL_TIMEOUT = 10 + # Activate API if "api" in INSTALLED_APPS: from api.settings import * diff --git a/re2o/utils.py b/re2o/utils.py index 7c49ff0a..0e735720 100644 --- a/re2o/utils.py +++ b/re2o/utils.py @@ -39,6 +39,10 @@ from __future__ import unicode_literals from django.utils import timezone from django.db.models import Q from django.contrib.auth.models import Permission +from django.utils.translation import ugettext_lazy as _ +from django.core.mail import send_mail as django_send_mail +from django.contrib import messages +from smtplib import SMTPException from cotisations.models import Cotisation, Facture, Vente from machines.models import Interface, Machine @@ -213,3 +217,17 @@ def remove_user_room(room, force=True): if force or not user.has_access(): user.room = None user.save() + + +def send_mail(request, *args, **kwargs): + """Wrapper for Django's send_mail which handles errors""" + try: + kwargs["fail_silently"] = request is None + django_send_mail(*args, **kwargs) + except SMTPException as e: + messages.error( + request, + _("Failed to send email: %(error)s.") % { + "error": e, + }, + ) diff --git a/tickets/models.py b/tickets/models.py index 55827097..5b74248c 100644 --- a/tickets/models.py +++ b/tickets/models.py @@ -1,11 +1,11 @@ from django.db import models from django.utils.translation import ugettext_lazy as _ -from django.core.mail import send_mail from django.template import loader from django.db.models.signals import post_save from django.dispatch import receiver from re2o.mixins import AclMixin +from re2o.utils import send_mail from preferences.models import GeneralOption @@ -38,6 +38,7 @@ class Ticket(AclMixin, models.Model): help_text=_("An email address to get back to you."), max_length=100, null=True ) solved = models.BooleanField(default=False) + request = None class Meta: permissions = (("view_tickets", _("Can view a ticket object")),) @@ -50,7 +51,7 @@ class Ticket(AclMixin, models.Model): else: return _("Anonymous ticket. Date: %s.") % (self.date) - def publish_mail(self): + def publish_mail(self, request=None): site_url = GeneralOption.objects.first().main_site_url to_addr = Preferences.objects.first().publish_address context = {"ticket": self, "site_url": site_url} @@ -62,7 +63,9 @@ class Ticket(AclMixin, models.Model): else: obj = "New ticket opened" template = loader.get_template("tickets/publication_mail_en") + send_mail( + request, obj, template.render(context), GeneralOption.get_cached_value("email_from"), @@ -108,4 +111,4 @@ def ticket_post_save(**kwargs): if kwargs["created"]: if Preferences.objects.first().publish_address: ticket = kwargs["instance"] - ticket.publish_mail() + ticket.publish_mail(ticket.request) diff --git a/tickets/views.py b/tickets/views.py index d330719a..03cc535c 100644 --- a/tickets/views.py +++ b/tickets/views.py @@ -60,6 +60,8 @@ def new_ticket(request): if ticketform.is_valid(): email = ticketform.cleaned_data.get("email") ticket = ticketform.save(commit=False) + ticket.request = request + if request.user.is_authenticated: ticket.user = request.user ticket.save() diff --git a/users/models.py b/users/models.py index 6b260170..55acd051 100755 --- a/users/models.py +++ b/users/models.py @@ -60,7 +60,6 @@ from django.db.models.signals import post_save, post_delete, m2m_changed from django.dispatch import receiver from django.utils.functional import cached_property from django.template import loader -from django.core.mail import send_mail from django.core.urlresolvers import reverse from django.db import transaction from django.utils import timezone @@ -84,6 +83,7 @@ from re2o.settings import LDAP, GID_RANGES, UID_RANGES from re2o.field_permissions import FieldPermissionModelMixin from re2o.mixins import AclMixin, RevMixin from re2o.base import smtp_check +from re2o.utils import send_mail from cotisations.models import Cotisation, Facture, Paiement, Vente from machines.models import Domain, Interface, Machine, regen @@ -244,6 +244,7 @@ class User( REQUIRED_FIELDS = ["surname", "email"] objects = UserManager() + request = None class Meta: permissions = ( @@ -749,7 +750,7 @@ class User( name__in=list(queryset_users.values_list("pseudo", flat=True)) ) - def notif_inscription(self): + def notif_inscription(self, request=None): """ Prend en argument un objet user, envoie un mail de bienvenue """ template = loader.get_template("users/email_welcome") mailmessageoptions, _created = MailMessageOption.objects.get_or_create() @@ -761,7 +762,9 @@ class User( "welcome_mail_en": mailmessageoptions.welcome_mail_en, "pseudo": self.pseudo, } + send_mail( + request, "Bienvenue au %(name)s / Welcome to %(name)s" % {"name": AssoOption.get_cached_value("name")}, "", @@ -769,7 +772,6 @@ class User( [self.email], html_message=template.render(context), ) - return def reset_passwd_mail(self, request): """ Prend en argument un request, envoie un mail de @@ -789,7 +791,9 @@ class User( ), "expire_in": str(GeneralOption.get_cached_value("req_expire_hrs")), } + send_mail( + request, "Changement de mot de passe de %(name)s / Password change for " "%(name)s" % {"name": AssoOption.get_cached_value("name")}, template.render(context), @@ -797,7 +801,6 @@ class User( [req.user.email], fail_silently=False, ) - return def send_confirm_email_if_necessary(self, request): """Update the user's email state: @@ -873,7 +876,9 @@ class User( "confirm_before_fr": self.confirm_email_before_date().strftime("%d/%m/%Y"), "confirm_before_en": self.confirm_email_before_date().strftime("%Y-%m-%d"), } + send_mail( + request, "Confirmation du mail de %(name)s / Email confirmation for " "%(name)s" % {"name": AssoOption.get_cached_value("name")}, template.render(context), @@ -883,7 +888,7 @@ class User( ) return - def autoregister_machine(self, mac_address, nas_type): + def autoregister_machine(self, mac_address, nas_type, request=None): """ Fonction appellée par freeradius. Enregistre la mac pour une machine inconnue sur le compte de l'user""" allowed, _message, _rights = Machine.can_create(self, self.id) @@ -927,7 +932,9 @@ class User( "asso_email": AssoOption.get_cached_value("contact"), "pseudo": self.pseudo, } + send_mail( + None, "Ajout automatique d'une machine / New machine autoregistered", "", GeneralOption.get_cached_value("email_from"), @@ -936,7 +943,7 @@ class User( ) return - def notif_disable(self): + def notif_disable(self, request=None): """Envoi un mail de notification informant que l'adresse mail n'a pas été confirmée""" template = loader.get_template("users/email_disable_notif") context = { @@ -945,7 +952,9 @@ class User( "asso_email": AssoOption.get_cached_value("contact"), "site_name": GeneralOption.get_cached_value("site_name"), } + send_mail( + request, "Suspension automatique / Automatic suspension", template.render(context), GeneralOption.get_cached_value("email_from"), @@ -1509,8 +1518,10 @@ def user_post_save(**kwargs): is_created = kwargs["created"] user = kwargs["instance"] EMailAddress.objects.get_or_create(local_part=user.pseudo.lower(), user=user) + if is_created: - user.notif_inscription() + user.notif_inscription(user.request) + user.state_sync() user.ldap_sync( base=True, access_refresh=True, mac_refresh=False, group_refresh=True @@ -1737,13 +1748,14 @@ class Ban(RevMixin, AclMixin, models.Model): date_start = models.DateTimeField(auto_now_add=True) date_end = models.DateTimeField() state = models.IntegerField(choices=STATES, default=STATE_HARD) + request = None class Meta: permissions = (("view_ban", _("Can view a ban object")),) verbose_name = _("ban") verbose_name_plural = _("bans") - def notif_ban(self): + def notif_ban(self, request=None): """ Prend en argument un objet ban, envoie un mail de notification """ template = loader.get_template("users/email_ban_notif") context = { @@ -1752,7 +1764,9 @@ class Ban(RevMixin, AclMixin, models.Model): "date_end": self.date_end, "asso_name": AssoOption.get_cached_value("name"), } + send_mail( + request, "Déconnexion disciplinaire / Disciplinary disconnection", template.render(context), GeneralOption.get_cached_value("email_from"), @@ -1795,7 +1809,7 @@ def ban_post_save(**kwargs): user.ldap_sync(base=False, access_refresh=True, mac_refresh=False) regen("mailing") if is_created: - ban.notif_ban() + ban.notif_ban(ban.request) regen("dhcp") regen("mac_ip_list") if user.has_access(): diff --git a/users/views.py b/users/views.py index 28fb107c..1c544ba6 100644 --- a/users/views.py +++ b/users/views.py @@ -119,6 +119,8 @@ def new_user(request): """ Vue de création d'un nouvel utilisateur, envoie un mail pour le mot de passe""" user = AdherentCreationForm(request.POST or None, user=request.user) + user.request = request + GTU_sum_up = GeneralOption.get_cached_value("GTU_sum_up") GTU = GeneralOption.get_cached_value("GTU") is_set_password_allowed = OptionalUser.get_cached_value("allow_set_password_during_user_creation") @@ -165,6 +167,8 @@ def new_club(request): """ Vue de création d'un nouveau club, envoie un mail pour le mot de passe""" club = ClubForm(request.POST or None, user=request.user) + club.request = request + if club.is_valid(): club = club.save(commit=False) club.save() @@ -368,6 +372,8 @@ def add_ban(request, user, userid): Syntaxe : JJ/MM/AAAA , heure optionnelle, prend effet immédiatement""" ban_instance = Ban(user=user) ban = BanForm(request.POST or None, instance=ban_instance) + ban.request = request + if ban.is_valid(): ban.save() messages.success(request, _("The ban was added.")) @@ -386,6 +392,8 @@ def edit_ban(request, ban_instance, **_kwargs): (a fortiori bureau) Syntaxe : JJ/MM/AAAA , heure optionnelle, prend effet immédiatement""" ban = BanForm(request.POST or None, instance=ban_instance) + ban.request = request + if ban.is_valid(): if ban.changed_data: ban.save() From 28c64c4eab20ec8b883ef558f2ecc6ba59db58ea Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Sun, 19 Apr 2020 20:11:38 +0200 Subject: [PATCH 2/8] Fix circular import of utils --- tickets/models.py | 4 ++-- users/models.py | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/tickets/models.py b/tickets/models.py index 5b74248c..17736978 100644 --- a/tickets/models.py +++ b/tickets/models.py @@ -5,7 +5,7 @@ from django.db.models.signals import post_save from django.dispatch import receiver from re2o.mixins import AclMixin -from re2o.utils import send_mail +import re2o.utils from preferences.models import GeneralOption @@ -64,7 +64,7 @@ class Ticket(AclMixin, models.Model): obj = "New ticket opened" template = loader.get_template("tickets/publication_mail_en") - send_mail( + re2o.utils.send_mail( request, obj, template.render(context), diff --git a/users/models.py b/users/models.py index 55acd051..9fb6a4b8 100755 --- a/users/models.py +++ b/users/models.py @@ -83,7 +83,7 @@ from re2o.settings import LDAP, GID_RANGES, UID_RANGES from re2o.field_permissions import FieldPermissionModelMixin from re2o.mixins import AclMixin, RevMixin from re2o.base import smtp_check -from re2o.utils import send_mail +import re2o.utils from cotisations.models import Cotisation, Facture, Paiement, Vente from machines.models import Domain, Interface, Machine, regen @@ -763,7 +763,7 @@ class User( "pseudo": self.pseudo, } - send_mail( + re2o.utils.send_mail( request, "Bienvenue au %(name)s / Welcome to %(name)s" % {"name": AssoOption.get_cached_value("name")}, @@ -792,7 +792,7 @@ class User( "expire_in": str(GeneralOption.get_cached_value("req_expire_hrs")), } - send_mail( + re2o.utils.send_mail( request, "Changement de mot de passe de %(name)s / Password change for " "%(name)s" % {"name": AssoOption.get_cached_value("name")}, @@ -877,7 +877,7 @@ class User( "confirm_before_en": self.confirm_email_before_date().strftime("%Y-%m-%d"), } - send_mail( + re2o.utils.send_mail( request, "Confirmation du mail de %(name)s / Email confirmation for " "%(name)s" % {"name": AssoOption.get_cached_value("name")}, @@ -933,7 +933,7 @@ class User( "pseudo": self.pseudo, } - send_mail( + re2o.utils.send_mail( None, "Ajout automatique d'une machine / New machine autoregistered", "", @@ -953,7 +953,7 @@ class User( "site_name": GeneralOption.get_cached_value("site_name"), } - send_mail( + re2o.utils.send_mail( request, "Suspension automatique / Automatic suspension", template.render(context), @@ -1765,7 +1765,7 @@ class Ban(RevMixin, AclMixin, models.Model): "asso_name": AssoOption.get_cached_value("name"), } - send_mail( + re2o.utils.send_mail( request, "Déconnexion disciplinaire / Disciplinary disconnection", template.render(context), From 561315541e18417dc432c96173bc88db3f6b5230 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Sun, 19 Apr 2020 20:15:23 +0200 Subject: [PATCH 3/8] Move mail util function to seperate file --- re2o/mail_utils.py | 45 +++++++++++++++++++++++++++++++++++++++++++++ re2o/utils.py | 18 ------------------ tickets/models.py | 4 ++-- users/models.py | 14 +++++++------- 4 files changed, 54 insertions(+), 27 deletions(-) create mode 100644 re2o/mail_utils.py diff --git a/re2o/mail_utils.py b/re2o/mail_utils.py new file mode 100644 index 00000000..065a9506 --- /dev/null +++ b/re2o/mail_utils.py @@ -0,0 +1,45 @@ +# -*- mode: python; coding: utf-8 -*- +# Re2o est un logiciel d'administration développé initiallement au rezometz. Il +# se veut agnostique au réseau considéré, de manière à être installable en +# quelques clics. +# +# Copyright © 2020 Jean-Romain Garnier +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 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 Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +# -*- coding: utf-8 -*- +# Jean-Romain Garnier +""" +Regroupe les fonctions en lien avec les mails +""" + +from django.utils.translation import ugettext_lazy as _ +from django.core.mail import send_mail as django_send_mail +from django.contrib import messages +from smtplib import SMTPException + + +def send_mail(request, *args, **kwargs): + """Wrapper for Django's send_mail which handles errors""" + try: + kwargs["fail_silently"] = request is None + django_send_mail(*args, **kwargs) + except SMTPException as e: + messages.error( + request, + _("Failed to send email: %(error)s.") % { + "error": e, + }, + ) diff --git a/re2o/utils.py b/re2o/utils.py index 0e735720..7c49ff0a 100644 --- a/re2o/utils.py +++ b/re2o/utils.py @@ -39,10 +39,6 @@ from __future__ import unicode_literals from django.utils import timezone from django.db.models import Q from django.contrib.auth.models import Permission -from django.utils.translation import ugettext_lazy as _ -from django.core.mail import send_mail as django_send_mail -from django.contrib import messages -from smtplib import SMTPException from cotisations.models import Cotisation, Facture, Vente from machines.models import Interface, Machine @@ -217,17 +213,3 @@ def remove_user_room(room, force=True): if force or not user.has_access(): user.room = None user.save() - - -def send_mail(request, *args, **kwargs): - """Wrapper for Django's send_mail which handles errors""" - try: - kwargs["fail_silently"] = request is None - django_send_mail(*args, **kwargs) - except SMTPException as e: - messages.error( - request, - _("Failed to send email: %(error)s.") % { - "error": e, - }, - ) diff --git a/tickets/models.py b/tickets/models.py index 17736978..a8adbe87 100644 --- a/tickets/models.py +++ b/tickets/models.py @@ -5,7 +5,7 @@ from django.db.models.signals import post_save from django.dispatch import receiver from re2o.mixins import AclMixin -import re2o.utils +from re2o.mail_utils import send_mail from preferences.models import GeneralOption @@ -64,7 +64,7 @@ class Ticket(AclMixin, models.Model): obj = "New ticket opened" template = loader.get_template("tickets/publication_mail_en") - re2o.utils.send_mail( + send_mail( request, obj, template.render(context), diff --git a/users/models.py b/users/models.py index 9fb6a4b8..42df3f5a 100755 --- a/users/models.py +++ b/users/models.py @@ -83,7 +83,7 @@ from re2o.settings import LDAP, GID_RANGES, UID_RANGES from re2o.field_permissions import FieldPermissionModelMixin from re2o.mixins import AclMixin, RevMixin from re2o.base import smtp_check -import re2o.utils +from re2o.mail_utils import send_mail from cotisations.models import Cotisation, Facture, Paiement, Vente from machines.models import Domain, Interface, Machine, regen @@ -763,7 +763,7 @@ class User( "pseudo": self.pseudo, } - re2o.utils.send_mail( + send_mail( request, "Bienvenue au %(name)s / Welcome to %(name)s" % {"name": AssoOption.get_cached_value("name")}, @@ -792,7 +792,7 @@ class User( "expire_in": str(GeneralOption.get_cached_value("req_expire_hrs")), } - re2o.utils.send_mail( + send_mail( request, "Changement de mot de passe de %(name)s / Password change for " "%(name)s" % {"name": AssoOption.get_cached_value("name")}, @@ -877,7 +877,7 @@ class User( "confirm_before_en": self.confirm_email_before_date().strftime("%Y-%m-%d"), } - re2o.utils.send_mail( + send_mail( request, "Confirmation du mail de %(name)s / Email confirmation for " "%(name)s" % {"name": AssoOption.get_cached_value("name")}, @@ -933,7 +933,7 @@ class User( "pseudo": self.pseudo, } - re2o.utils.send_mail( + send_mail( None, "Ajout automatique d'une machine / New machine autoregistered", "", @@ -953,7 +953,7 @@ class User( "site_name": GeneralOption.get_cached_value("site_name"), } - re2o.utils.send_mail( + send_mail( request, "Suspension automatique / Automatic suspension", template.render(context), @@ -1765,7 +1765,7 @@ class Ban(RevMixin, AclMixin, models.Model): "asso_name": AssoOption.get_cached_value("name"), } - re2o.utils.send_mail( + send_mail( request, "Déconnexion disciplinaire / Disciplinary disconnection", template.render(context), From 1b64c8f95b05864ca930aa987afe72dc101500ba Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Sun, 19 Apr 2020 18:21:38 +0000 Subject: [PATCH 4/8] Add translation for email error message --- api/locale/fr/LC_MESSAGES/django.po | 2 +- cotisations/locale/fr/LC_MESSAGES/django.po | 149 +++++----- logs/locale/fr/LC_MESSAGES/django.po | 2 +- machines/locale/fr/LC_MESSAGES/django.po | 2 +- multi_op/locale/fr/LC_MESSAGES/django.po | 2 +- preferences/locale/fr/LC_MESSAGES/django.po | 2 +- re2o/locale/fr/LC_MESSAGES/django.po | 7 +- search/locale/fr/LC_MESSAGES/django.po | 2 +- templates/locale/fr/LC_MESSAGES/django.po | 2 +- tickets/locale/fr/LC_MESSAGES/django.po | 26 +- topologie/locale/fr/LC_MESSAGES/django.po | 2 +- users/locale/fr/LC_MESSAGES/django.po | 300 ++++++++++---------- 12 files changed, 254 insertions(+), 244 deletions(-) diff --git a/api/locale/fr/LC_MESSAGES/django.po b/api/locale/fr/LC_MESSAGES/django.po index 8d2012f7..fb747f3e 100644 --- a/api/locale/fr/LC_MESSAGES/django.po +++ b/api/locale/fr/LC_MESSAGES/django.po @@ -21,7 +21,7 @@ msgid "" msgstr "" "Project-Id-Version: 2.5\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-04-19 17:12+0200\n" +"POT-Creation-Date: 2020-04-19 20:16+0200\n" "PO-Revision-Date: 2019-01-07 01:37+0100\n" "Last-Translator: Laouen Fernet \n" "Language-Team: \n" diff --git a/cotisations/locale/fr/LC_MESSAGES/django.po b/cotisations/locale/fr/LC_MESSAGES/django.po index bc915638..ec0098c2 100644 --- a/cotisations/locale/fr/LC_MESSAGES/django.po +++ b/cotisations/locale/fr/LC_MESSAGES/django.po @@ -21,7 +21,7 @@ msgid "" msgstr "" "Project-Id-Version: 2.5\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-04-19 17:12+0200\n" +"POT-Creation-Date: 2020-04-19 20:16+0200\n" "PO-Revision-Date: 2018-03-31 16:09+0002\n" "Last-Translator: Laouen Fernet \n" "Language: fr_FR\n" @@ -37,7 +37,7 @@ msgstr "Vous n'avez pas le droit de voir cette application." msgid "Select a payment method" msgstr "Sélectionnez un moyen de paiement" -#: cotisations/forms.py:73 cotisations/models.py:682 +#: cotisations/forms.py:73 cotisations/models.py:687 msgid "Member" msgstr "Adhérent" @@ -154,7 +154,7 @@ msgstr "Peut voir un objet facture" msgid "Can edit all the previous invoices" msgstr "Peut modifier toutes les factures précédentes" -#: cotisations/models.py:145 cotisations/models.py:456 cotisations/views.py:376 +#: cotisations/models.py:145 cotisations/models.py:461 cotisations/views.py:376 #: cotisations/views.py:571 msgid "invoice" msgstr "facture" @@ -217,115 +217,115 @@ msgstr "Il n'y a pas de moyens de paiement que vous puissiez utiliser." msgid "There are no articles that you can buy." msgstr "Il n'y a pas d'articles que vous puissiez acheter." -#: cotisations/models.py:372 +#: cotisations/models.py:377 msgid "Can view a custom invoice object" msgstr "Peut voir un objet facture personnalisée" -#: cotisations/models.py:374 +#: cotisations/models.py:379 msgid "recipient" msgstr "destinataire" -#: cotisations/models.py:375 +#: cotisations/models.py:380 msgid "payment type" msgstr "type de paiement" -#: cotisations/models.py:376 +#: cotisations/models.py:381 msgid "address" msgstr "adresse" -#: cotisations/models.py:377 +#: cotisations/models.py:382 msgid "paid" msgstr "payé" -#: cotisations/models.py:378 +#: cotisations/models.py:383 msgid "remark" msgstr "remarque" -#: cotisations/models.py:383 +#: cotisations/models.py:388 msgid "Can view a cost estimate object" msgstr "Peut voir un objet devis" -#: cotisations/models.py:386 +#: cotisations/models.py:391 msgid "period of validity" msgstr "période de validité" -#: cotisations/models.py:421 +#: cotisations/models.py:426 msgid "You don't have the right to delete a cost estimate." msgstr "Vous n'avez pas le droit de supprimer un devis." -#: cotisations/models.py:427 +#: cotisations/models.py:432 msgid "The cost estimate has an invoice and can't be deleted." msgstr "Le devis a une facture et ne peut pas être supprimé." -#: cotisations/models.py:449 cotisations/models.py:688 -#: cotisations/models.py:946 +#: cotisations/models.py:454 cotisations/models.py:693 +#: cotisations/models.py:951 msgid "Connection" msgstr "Connexion" -#: cotisations/models.py:450 cotisations/models.py:689 -#: cotisations/models.py:947 +#: cotisations/models.py:455 cotisations/models.py:694 +#: cotisations/models.py:952 msgid "Membership" msgstr "Adhésion" -#: cotisations/models.py:451 cotisations/models.py:684 -#: cotisations/models.py:690 cotisations/models.py:948 +#: cotisations/models.py:456 cotisations/models.py:689 +#: cotisations/models.py:695 cotisations/models.py:953 msgid "Both of them" msgstr "Les deux" -#: cotisations/models.py:460 +#: cotisations/models.py:465 msgid "amount" msgstr "montant" -#: cotisations/models.py:463 +#: cotisations/models.py:468 msgid "article" msgstr "article" -#: cotisations/models.py:466 +#: cotisations/models.py:471 msgid "price" msgstr "prix" -#: cotisations/models.py:469 cotisations/models.py:702 +#: cotisations/models.py:474 cotisations/models.py:707 msgid "duration (in months)" msgstr "durée (en mois)" -#: cotisations/models.py:475 cotisations/models.py:708 +#: cotisations/models.py:480 cotisations/models.py:713 msgid "duration (in days, will be added to duration in months)" msgstr "durée (en jours, sera ajoutée à la durée en mois)" -#: cotisations/models.py:483 cotisations/models.py:722 -#: cotisations/models.py:959 +#: cotisations/models.py:488 cotisations/models.py:727 +#: cotisations/models.py:964 msgid "subscription type" msgstr "type de cotisation" -#: cotisations/models.py:488 +#: cotisations/models.py:493 msgid "Can view a purchase object" msgstr "Peut voir un objet achat" -#: cotisations/models.py:489 +#: cotisations/models.py:494 msgid "Can edit all the previous purchases" msgstr "Peut modifier tous les achats précédents" -#: cotisations/models.py:491 cotisations/models.py:953 +#: cotisations/models.py:496 cotisations/models.py:958 msgid "purchase" msgstr "achat" -#: cotisations/models.py:492 +#: cotisations/models.py:497 msgid "purchases" msgstr "achats" -#: cotisations/models.py:545 cotisations/models.py:742 +#: cotisations/models.py:550 cotisations/models.py:747 msgid "Duration must be specified for a subscription." msgstr "La durée doit être renseignée pour une cotisation." -#: cotisations/models.py:556 +#: cotisations/models.py:561 msgid "You don't have the right to edit a purchase." msgstr "Vous n'avez pas le droit de modifier un achat." -#: cotisations/models.py:562 +#: cotisations/models.py:567 msgid "You don't have the right to edit this user's purchases." msgstr "Vous n'avez pas le droit de modifier les achats de cet utilisateur." -#: cotisations/models.py:571 +#: cotisations/models.py:576 msgid "" "You don't have the right to edit a purchase already controlled or " "invalidated." @@ -333,15 +333,15 @@ msgstr "" "Vous n'avez pas le droit de modifier un achat précédemment contrôlé ou " "invalidé." -#: cotisations/models.py:586 +#: cotisations/models.py:591 msgid "You don't have the right to delete a purchase." msgstr "Vous n'avez pas le droit de supprimer un achat." -#: cotisations/models.py:592 +#: cotisations/models.py:597 msgid "You don't have the right to delete this user's purchases." msgstr "Vous n'avez pas le droit de supprimer les achats de cet utilisateur." -#: cotisations/models.py:599 +#: cotisations/models.py:604 msgid "" "You don't have the right to delete a purchase already controlled or " "invalidated." @@ -349,134 +349,134 @@ msgstr "" "Vous n'avez pas le droit de supprimer un achat précédemment contrôlé ou " "invalidé." -#: cotisations/models.py:615 +#: cotisations/models.py:620 msgid "You don't have the right to view someone else's purchase history." msgstr "" "Vous n'avez pas le droit de voir l'historique des achats d'un autre " "utilisateur." -#: cotisations/models.py:683 +#: cotisations/models.py:688 msgid "Club" msgstr "Club" -#: cotisations/models.py:693 +#: cotisations/models.py:698 msgid "designation" msgstr "désignation" -#: cotisations/models.py:696 +#: cotisations/models.py:701 msgid "unit price" msgstr "prix unitaire" -#: cotisations/models.py:714 +#: cotisations/models.py:719 msgid "type of users concerned" msgstr "type d'utilisateurs concernés" -#: cotisations/models.py:725 cotisations/models.py:826 +#: cotisations/models.py:730 cotisations/models.py:831 msgid "is available for every user" msgstr "est disponible pour chaque utilisateur" -#: cotisations/models.py:732 +#: cotisations/models.py:737 msgid "Can view an article object" msgstr "Peut voir un objet article" -#: cotisations/models.py:733 +#: cotisations/models.py:738 msgid "Can buy every article" msgstr "Peut acheter chaque article" -#: cotisations/models.py:740 +#: cotisations/models.py:745 msgid "Solde is a reserved article name." msgstr "Solde est un nom d'article réservé." -#: cotisations/models.py:765 +#: cotisations/models.py:770 msgid "You can't buy this article." msgstr "Vous ne pouvez pas acheter cet article." -#: cotisations/models.py:806 +#: cotisations/models.py:811 msgid "Can view a bank object" msgstr "Peut voir un objet banque" -#: cotisations/models.py:807 +#: cotisations/models.py:812 msgid "bank" msgstr "banque" -#: cotisations/models.py:808 +#: cotisations/models.py:813 msgid "banks" msgstr "banques" -#: cotisations/models.py:824 +#: cotisations/models.py:829 msgid "method" msgstr "moyen" -#: cotisations/models.py:831 +#: cotisations/models.py:836 msgid "is user balance" msgstr "est solde utilisateur" -#: cotisations/models.py:832 +#: cotisations/models.py:837 msgid "There should be only one balance payment method." msgstr "Il ne devrait y avoir qu'un moyen de paiement solde." -#: cotisations/models.py:838 +#: cotisations/models.py:843 msgid "Can view a payment method object" msgstr "Peut voir un objet moyen de paiement" -#: cotisations/models.py:839 +#: cotisations/models.py:844 msgid "Can use every payment method" msgstr "Peut utiliser chaque moyen de paiement" -#: cotisations/models.py:841 +#: cotisations/models.py:846 msgid "payment method" msgstr "moyen de paiement" -#: cotisations/models.py:842 +#: cotisations/models.py:847 msgid "payment methods" msgstr "moyens de paiement" -#: cotisations/models.py:881 cotisations/payment_methods/comnpay/views.py:62 +#: cotisations/models.py:886 cotisations/payment_methods/comnpay/views.py:62 #, python-format msgid "The subscription of %(member_name)s was extended to %(end_date)s." msgstr "La cotisation de %(member_name)s a été étendue au %(end_date)s." -#: cotisations/models.py:891 +#: cotisations/models.py:896 msgid "The invoice was created." msgstr "La facture a été créée." -#: cotisations/models.py:911 +#: cotisations/models.py:916 msgid "You can't use this payment method." msgstr "Vous ne pouvez pas utiliser ce moyen de paiement." -#: cotisations/models.py:930 +#: cotisations/models.py:935 msgid "No custom payment methods." msgstr "Pas de moyens de paiement personnalisés." -#: cotisations/models.py:961 +#: cotisations/models.py:966 msgid "start date" msgstr "date de début" -#: cotisations/models.py:962 +#: cotisations/models.py:967 msgid "end date" msgstr "date de fin" -#: cotisations/models.py:966 +#: cotisations/models.py:971 msgid "Can view a subscription object" msgstr "Peut voir un objet cotisation" -#: cotisations/models.py:967 +#: cotisations/models.py:972 msgid "Can edit the previous subscriptions" msgstr "Peut modifier les cotisations précédentes" -#: cotisations/models.py:969 +#: cotisations/models.py:974 msgid "subscription" msgstr "cotisation" -#: cotisations/models.py:970 +#: cotisations/models.py:975 msgid "subscriptions" msgstr "cotisations" -#: cotisations/models.py:976 +#: cotisations/models.py:981 msgid "You don't have the right to edit a subscription." msgstr "Vous n'avez pas le droit de modifier une cotisation." -#: cotisations/models.py:985 +#: cotisations/models.py:990 msgid "" "You don't have the right to edit a subscription already controlled or " "invalidated." @@ -484,11 +484,11 @@ msgstr "" "Vous n'avez pas le droit de modifier une cotisation précédemment contrôlée " "ou invalidée." -#: cotisations/models.py:997 +#: cotisations/models.py:1002 msgid "You don't have the right to delete a subscription." msgstr "Vous n'avez pas le droit de supprimer une cotisation." -#: cotisations/models.py:1004 +#: cotisations/models.py:1009 msgid "" "You don't have the right to delete a subscription already controlled or " "invalidated." @@ -496,7 +496,7 @@ msgstr "" "Vous n'avez pas le droit de supprimer une cotisation précédemment contrôlée " "ou invalidée." -#: cotisations/models.py:1020 +#: cotisations/models.py:1025 msgid "You don't have the right to view someone else's subscription history." msgstr "" "Vous n'avez pas le droit de voir l'historique des cotisations d'un autre " @@ -933,6 +933,11 @@ msgstr "Créer une facture" msgid "Control the invoices" msgstr "Contrôler les factures" +#: cotisations/utils.py:57 +#, python-format +msgid "Failed to send email: %(error)s." +msgstr "" + #: cotisations/views.py:157 msgid "You need to choose at least one article." msgstr "Vous devez choisir au moins un article." diff --git a/logs/locale/fr/LC_MESSAGES/django.po b/logs/locale/fr/LC_MESSAGES/django.po index aceb2172..22de19e5 100644 --- a/logs/locale/fr/LC_MESSAGES/django.po +++ b/logs/locale/fr/LC_MESSAGES/django.po @@ -21,7 +21,7 @@ msgid "" msgstr "" "Project-Id-Version: 2.5\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-04-19 17:12+0200\n" +"POT-Creation-Date: 2020-04-19 20:16+0200\n" "PO-Revision-Date: 2018-06-23 16:01+0200\n" "Last-Translator: Laouen Fernet \n" "Language-Team: \n" diff --git a/machines/locale/fr/LC_MESSAGES/django.po b/machines/locale/fr/LC_MESSAGES/django.po index 8bd28bf3..5ec18685 100644 --- a/machines/locale/fr/LC_MESSAGES/django.po +++ b/machines/locale/fr/LC_MESSAGES/django.po @@ -21,7 +21,7 @@ msgid "" msgstr "" "Project-Id-Version: 2.5\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-04-19 17:12+0200\n" +"POT-Creation-Date: 2020-04-19 20:16+0200\n" "PO-Revision-Date: 2018-06-23 16:35+0200\n" "Last-Translator: Laouen Fernet \n" "Language-Team: \n" diff --git a/multi_op/locale/fr/LC_MESSAGES/django.po b/multi_op/locale/fr/LC_MESSAGES/django.po index 9a3def0b..31245d63 100644 --- a/multi_op/locale/fr/LC_MESSAGES/django.po +++ b/multi_op/locale/fr/LC_MESSAGES/django.po @@ -21,7 +21,7 @@ msgid "" msgstr "" "Project-Id-Version: 2.5\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-04-19 17:12+0200\n" +"POT-Creation-Date: 2020-04-19 20:16+0200\n" "PO-Revision-Date: 2019-11-16 00:22+0100\n" "Last-Translator: Laouen Fernet \n" "Language-Team: \n" diff --git a/preferences/locale/fr/LC_MESSAGES/django.po b/preferences/locale/fr/LC_MESSAGES/django.po index f3ca1867..bf0387b0 100644 --- a/preferences/locale/fr/LC_MESSAGES/django.po +++ b/preferences/locale/fr/LC_MESSAGES/django.po @@ -21,7 +21,7 @@ msgid "" msgstr "" "Project-Id-Version: 2.5\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-04-19 17:12+0200\n" +"POT-Creation-Date: 2020-04-19 20:16+0200\n" "PO-Revision-Date: 2018-06-24 15:54+0200\n" "Last-Translator: Laouen Fernet \n" "Language-Team: \n" diff --git a/re2o/locale/fr/LC_MESSAGES/django.po b/re2o/locale/fr/LC_MESSAGES/django.po index e2db4702..1c408790 100644 --- a/re2o/locale/fr/LC_MESSAGES/django.po +++ b/re2o/locale/fr/LC_MESSAGES/django.po @@ -21,7 +21,7 @@ msgid "" msgstr "" "Project-Id-Version: 2.5\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-04-19 17:12+0200\n" +"POT-Creation-Date: 2020-04-19 20:16+0200\n" "PO-Revision-Date: 2018-03-31 16:09+0002\n" "Last-Translator: Laouen Fernet \n" "Language-Team: \n" @@ -74,6 +74,11 @@ msgstr "Format : {main} {more}" msgid "Format: {main}" msgstr "Format : {main}" +#: re2o/mail_utils.py:42 +#, python-format +msgid "Failed to send email: %(error)s." +msgstr "Échec de l'envoi du mail : %(error)s." + #: re2o/mixins.py:113 #, python-format msgid "You don't have the right to create a %s object." diff --git a/search/locale/fr/LC_MESSAGES/django.po b/search/locale/fr/LC_MESSAGES/django.po index 1420cc69..bf54fb7d 100644 --- a/search/locale/fr/LC_MESSAGES/django.po +++ b/search/locale/fr/LC_MESSAGES/django.po @@ -21,7 +21,7 @@ msgid "" msgstr "" "Project-Id-Version: 2.5\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-04-19 17:12+0200\n" +"POT-Creation-Date: 2020-04-19 20:16+0200\n" "PO-Revision-Date: 2018-06-24 20:10+0200\n" "Last-Translator: Laouen Fernet \n" "Language-Team: \n" diff --git a/templates/locale/fr/LC_MESSAGES/django.po b/templates/locale/fr/LC_MESSAGES/django.po index 1d00e8e8..3935e92a 100644 --- a/templates/locale/fr/LC_MESSAGES/django.po +++ b/templates/locale/fr/LC_MESSAGES/django.po @@ -21,7 +21,7 @@ msgid "" msgstr "" "Project-Id-Version: 2.5\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-04-19 17:12+0200\n" +"POT-Creation-Date: 2020-04-19 20:16+0200\n" "PO-Revision-Date: 2018-03-31 16:09+0002\n" "Last-Translator: Laouen Fernet \n" "Language-Team: \n" diff --git a/tickets/locale/fr/LC_MESSAGES/django.po b/tickets/locale/fr/LC_MESSAGES/django.po index 2cb96239..166576ba 100644 --- a/tickets/locale/fr/LC_MESSAGES/django.po +++ b/tickets/locale/fr/LC_MESSAGES/django.po @@ -21,7 +21,7 @@ msgid "" msgstr "" "Project-Id-Version: 2.5\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-04-19 17:12+0200\n" +"POT-Creation-Date: 2020-04-19 20:16+0200\n" "PO-Revision-Date: 2019-11-16 00:35+0100\n" "Last-Translator: Laouen Fernet \n" "Language-Team: \n" @@ -42,33 +42,33 @@ msgstr "Description du ticket." msgid "An email address to get back to you." msgstr "Une adresse mail pour vous recontacter." -#: tickets/models.py:43 +#: tickets/models.py:44 msgid "Can view a ticket object" msgstr "Peut voir un objet ticket" -#: tickets/models.py:44 +#: tickets/models.py:45 msgid "ticket" msgstr "ticket" -#: tickets/models.py:45 +#: tickets/models.py:46 msgid "tickets" msgstr "tickets" -#: tickets/models.py:49 +#: tickets/models.py:50 #, python-format msgid "Ticket from %(name)s. Date: %(date)s." msgstr "Ticket de %(name)s. Date : %(date)s." -#: tickets/models.py:51 +#: tickets/models.py:52 #, python-format msgid "Anonymous ticket. Date: %s." msgstr "Ticket anonyme. Date : %s." -#: tickets/models.py:82 +#: tickets/models.py:85 msgid "You don't have the right to view other tickets than yours." msgstr "Vous n'avez pas le droit de voir d'autres tickets que les vôtres." -#: tickets/models.py:94 +#: tickets/models.py:97 msgid "You don't have the right to view the list of tickets." msgstr "Vous n'avez pas le droit de voir la liste des tickets." @@ -214,7 +214,7 @@ msgstr "Modifier" msgid "Ticket opening" msgstr "Ouverture de ticket" -#: tickets/templates/tickets/form_ticket.html:39 tickets/views.py:88 +#: tickets/templates/tickets/form_ticket.html:39 tickets/views.py:90 msgid "" "You are not authenticated. Please log in or provide an email address so we " "can get back to you." @@ -280,21 +280,21 @@ msgstr "Langue du mail" msgid "No tickets" msgstr "Pas de tickets" -#: tickets/views.py:69 tickets/views.py:80 +#: tickets/views.py:71 tickets/views.py:82 msgid "" "Your ticket has been succesfully opened. We will take care of it as soon as " "possible." msgstr "" "Votre ticket a bien été ouvert. Nous nous en occuperons dès que possible." -#: tickets/views.py:125 tickets/views.py:175 +#: tickets/views.py:127 tickets/views.py:177 msgid "Never" msgstr "Jamais" -#: tickets/views.py:152 +#: tickets/views.py:154 msgid "The tickets preferences were edited." msgstr "Les préférences de tickets ont été modifiées." -#: tickets/views.py:155 +#: tickets/views.py:157 msgid "Invalid form." msgstr "Formulaire invalide." diff --git a/topologie/locale/fr/LC_MESSAGES/django.po b/topologie/locale/fr/LC_MESSAGES/django.po index 76a26f28..37a56a9d 100644 --- a/topologie/locale/fr/LC_MESSAGES/django.po +++ b/topologie/locale/fr/LC_MESSAGES/django.po @@ -21,7 +21,7 @@ msgid "" msgstr "" "Project-Id-Version: 2.5\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-04-19 17:12+0200\n" +"POT-Creation-Date: 2020-04-19 20:16+0200\n" "PO-Revision-Date: 2018-06-25 14:53+0200\n" "Last-Translator: Laouen Fernet \n" "Language-Team: \n" diff --git a/users/locale/fr/LC_MESSAGES/django.po b/users/locale/fr/LC_MESSAGES/django.po index 5b91a471..35774bd3 100644 --- a/users/locale/fr/LC_MESSAGES/django.po +++ b/users/locale/fr/LC_MESSAGES/django.po @@ -21,7 +21,7 @@ msgid "" msgstr "" "Project-Id-Version: 2.5\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-04-19 17:12+0200\n" +"POT-Creation-Date: 2020-04-19 20:16+0200\n" "PO-Revision-Date: 2018-06-27 23:35+0200\n" "Last-Translator: Laouen Fernet \n" "Language-Team: \n" @@ -56,7 +56,7 @@ msgid "The current password is incorrect." msgstr "Le mot de passe actuel est incorrect." #: users/forms.py:132 users/forms.py:189 users/forms.py:425 -#: users/models.py:1894 +#: users/models.py:1908 msgid "Password" msgstr "Mot de passe" @@ -114,7 +114,7 @@ msgstr "Prénom" msgid "Surname" msgstr "Nom" -#: users/forms.py:329 users/forms.py:566 users/models.py:1894 +#: users/forms.py:329 users/forms.py:566 users/models.py:1908 #: users/templates/users/aff_emailaddress.html:36 #: users/templates/users/profil.html:209 msgid "Email address" @@ -336,7 +336,7 @@ msgstr "Non confirmé" msgid "Waiting for email confirmation" msgstr "En attente de confirmation" -#: users/models.py:204 users/models.py:1550 +#: users/models.py:204 users/models.py:1561 msgid "Must only contain letters, numerals or dashes." msgstr "Doit seulement contenir des lettres, chiffres ou tirets." @@ -362,82 +362,82 @@ msgstr "Commentaire, promotion." msgid "enable shortcuts on Re2o website" msgstr "activer les raccourcis sur le site de Re2o" -#: users/models.py:250 +#: users/models.py:251 msgid "Can change the password of a user" msgstr "Peut changer le mot de passe d'un utilisateur" -#: users/models.py:251 +#: users/models.py:252 msgid "Can edit the state of a user" msgstr "Peut changer l'état d'un utilisateur" -#: users/models.py:252 +#: users/models.py:253 msgid "Can force the move" msgstr "Peut forcer le déménagement" -#: users/models.py:253 +#: users/models.py:254 msgid "Can edit the shell of a user" msgstr "Peut modifier l'interface en ligne de commande d'un utilisateur" -#: users/models.py:256 +#: users/models.py:257 msgid "Can edit the groups of rights of a user (critical permission)" msgstr "" "Peut modifier les groupes de droits d'un utilisateur (permission critique)" -#: users/models.py:258 +#: users/models.py:259 msgid "Can edit all users, including those with rights" msgstr "" "Peut modifier tous les utilisateurs, y compris ceux possédant des droits" -#: users/models.py:259 +#: users/models.py:260 msgid "Can view a user object" msgstr "Peut voir un objet utilisateur" -#: users/models.py:261 +#: users/models.py:262 msgid "user (member or club)" msgstr "utilisateur (adhérent ou club)" -#: users/models.py:262 +#: users/models.py:263 msgid "users (members or clubs)" msgstr "utilisateurs (adhérents ou clubs)" -#: users/models.py:280 users/models.py:308 users/models.py:318 +#: users/models.py:281 users/models.py:309 users/models.py:319 msgid "Unknown type." msgstr "Type inconnu." -#: users/models.py:314 users/templates/users/aff_listright.html:75 +#: users/models.py:315 users/templates/users/aff_listright.html:75 #: users/templates/users/aff_listright.html:180 msgid "Member" msgstr "Adhérent" -#: users/models.py:316 +#: users/models.py:317 msgid "Club" msgstr "Club" -#: users/models.py:891 +#: users/models.py:896 msgid "Maximum number of registered machines reached." msgstr "Nombre maximum de machines enregistrées atteint." -#: users/models.py:893 +#: users/models.py:898 msgid "Re2o doesn't know wich machine type to assign." msgstr "Re2o ne sait pas quel type de machine attribuer." -#: users/models.py:916 users/templates/users/user_autocapture.html:64 +#: users/models.py:921 users/templates/users/user_autocapture.html:64 msgid "OK" msgstr "OK" -#: users/models.py:1010 +#: users/models.py:1019 msgid "This user is archived." msgstr "Cet utilisateur est archivé." -#: users/models.py:1024 users/models.py:1078 +#: users/models.py:1033 users/models.py:1087 msgid "You don't have the right to edit this club." msgstr "Vous n'avez pas le droit de modifier ce club." -#: users/models.py:1036 +#: users/models.py:1045 msgid "User with critical rights, can't be edited." msgstr "Utilisateur avec des droits critiques, ne peut être modifié." -#: users/models.py:1043 +#: users/models.py:1052 msgid "" "Impossible to edit the organisation's user without the \"change_all_users\" " "right." @@ -445,61 +445,61 @@ msgstr "" "Impossible de modifier l'utilisateur de l'association sans le droit « " "change_all_users »." -#: users/models.py:1055 users/models.py:1093 +#: users/models.py:1064 users/models.py:1102 msgid "You don't have the right to edit another user." msgstr "Vous n'avez pas le droit de modifier un autre utilisateur." -#: users/models.py:1119 +#: users/models.py:1128 msgid "You don't have the right to change the room." msgstr "Vous n'avez pas le droit de changer la chambre." -#: users/models.py:1136 +#: users/models.py:1145 msgid "You don't have the right to change the state." msgstr "Vous n'avez pas le droit de changer l'état." -#: users/models.py:1156 +#: users/models.py:1165 msgid "You don't have the right to change the shell." msgstr "Vous n'avez pas le droit de changer l'interface en ligne de commande." -#: users/models.py:1173 users/models.py:1188 +#: users/models.py:1182 users/models.py:1197 msgid "Local email accounts must be enabled." msgstr "Les comptes mail locaux doivent être activés." -#: users/models.py:1203 +#: users/models.py:1212 msgid "You don't have the right to force the move." msgstr "Vous n'avez pas le droit de forcer le déménagement." -#: users/models.py:1218 +#: users/models.py:1227 msgid "You don't have the right to edit the user's groups of rights." msgstr "" "Vous n'avez pas le droit de modifier les groupes de droits d'un autre " "utilisateur." -#: users/models.py:1234 +#: users/models.py:1243 msgid "\"superuser\" right required to edit the superuser flag." msgstr "Droit « superuser » requis pour modifier le signalement superuser." -#: users/models.py:1259 +#: users/models.py:1268 msgid "You don't have the right to view this club." msgstr "Vous n'avez pas le droit de voir ce club." -#: users/models.py:1268 +#: users/models.py:1277 msgid "You don't have the right to view another user." msgstr "Vous n'avez pas le droit de voir un autre utilisateur." -#: users/models.py:1283 users/models.py:1488 +#: users/models.py:1292 users/models.py:1497 msgid "You don't have the right to view the list of users." msgstr "Vous n'avez pas le droit de voir la liste des utilisateurs." -#: users/models.py:1300 +#: users/models.py:1309 msgid "You don't have the right to delete this user." msgstr "Vous n'avez pas le droit de supprimer cet utilisateur." -#: users/models.py:1323 +#: users/models.py:1332 msgid "This username is already used." msgstr "Ce pseudo est déjà utilisé." -#: users/models.py:1331 +#: users/models.py:1340 msgid "" "There is neither a local email address nor an external email address for " "this user." @@ -507,7 +507,7 @@ msgstr "" "Il n'y a pas d'adresse mail locale ni d'adresse mail externe pour cet " "utilisateur." -#: users/models.py:1338 +#: users/models.py:1347 msgid "" "You can't redirect your local emails if no external email address has been " "set." @@ -515,195 +515,195 @@ msgstr "" "Vous ne pouvez pas rediriger vos mails locaux si aucune adresse mail externe " "n'a été définie." -#: users/models.py:1358 +#: users/models.py:1367 msgid "member" msgstr "adhérent" -#: users/models.py:1359 +#: users/models.py:1368 msgid "members" msgstr "adhérents" -#: users/models.py:1376 +#: users/models.py:1385 msgid "A GPG fingerprint must contain 40 hexadecimal characters." msgstr "Une empreinte GPG doit contenir 40 caractères hexadécimaux." -#: users/models.py:1401 +#: users/models.py:1410 msgid "Self registration is disabled." msgstr "L'auto inscription est désactivée." -#: users/models.py:1411 +#: users/models.py:1420 msgid "You don't have the right to create a user." msgstr "Vous n'avez pas le droit de créer un utilisateur." -#: users/models.py:1441 +#: users/models.py:1450 msgid "club" msgstr "club" -#: users/models.py:1442 +#: users/models.py:1451 msgid "clubs" msgstr "clubs" -#: users/models.py:1453 +#: users/models.py:1462 msgid "You must be authenticated." msgstr "Vous devez être authentifié." -#: users/models.py:1461 +#: users/models.py:1470 msgid "You don't have the right to create a club." msgstr "Vous n'avez pas le droit de créer un club." -#: users/models.py:1554 +#: users/models.py:1565 msgid "Comment." msgstr "Commentaire." -#: users/models.py:1560 +#: users/models.py:1571 msgid "Can view a service user object" msgstr "Peut voir un objet utilisateur service" -#: users/models.py:1561 users/views.py:356 +#: users/models.py:1572 users/views.py:360 msgid "service user" msgstr "utilisateur service" -#: users/models.py:1562 +#: users/models.py:1573 msgid "service users" msgstr "utilisateurs service" -#: users/models.py:1566 +#: users/models.py:1577 #, python-brace-format msgid "Service user <{name}>" msgstr "Utilisateur service <{name}>" -#: users/models.py:1633 +#: users/models.py:1644 msgid "Can view a school object" msgstr "Peut voir un objet établissement" -#: users/models.py:1634 +#: users/models.py:1645 msgid "school" msgstr "établissement" -#: users/models.py:1635 +#: users/models.py:1646 msgid "schools" msgstr "établissements" -#: users/models.py:1654 +#: users/models.py:1665 msgid "UNIX group names can only contain lower case letters." msgstr "" "Les noms de groupe UNIX peuvent seulement contenir des lettres minuscules." -#: users/models.py:1660 +#: users/models.py:1671 msgid "Description." msgstr "Description." -#: users/models.py:1663 +#: users/models.py:1674 msgid "Can view a group of rights object" msgstr "Peut voir un objet groupe de droits" -#: users/models.py:1664 +#: users/models.py:1675 msgid "group of rights" msgstr "groupe de droits" -#: users/models.py:1665 +#: users/models.py:1676 msgid "groups of rights" msgstr "groupes de droits" -#: users/models.py:1710 +#: users/models.py:1721 msgid "Can view a shell object" msgstr "Peut voir un objet interface en ligne de commande" -#: users/models.py:1711 users/views.py:671 +#: users/models.py:1722 users/views.py:679 msgid "shell" msgstr "interface en ligne de commande" -#: users/models.py:1712 +#: users/models.py:1723 msgid "shells" msgstr "interfaces en ligne de commande" -#: users/models.py:1730 +#: users/models.py:1741 msgid "HARD (no access)" msgstr "HARD (pas d'accès)" -#: users/models.py:1731 +#: users/models.py:1742 msgid "SOFT (local access only)" msgstr "SOFT (accès local uniquement)" -#: users/models.py:1732 +#: users/models.py:1743 msgid "RESTRICTED (speed limitation)" msgstr "RESTRICTED (limitation de vitesse)" -#: users/models.py:1742 +#: users/models.py:1754 msgid "Can view a ban object" msgstr "Peut voir un objet bannissement" -#: users/models.py:1743 users/views.py:407 +#: users/models.py:1755 users/views.py:415 msgid "ban" msgstr "bannissement" -#: users/models.py:1744 +#: users/models.py:1756 msgid "bans" msgstr "bannissements" -#: users/models.py:1779 +#: users/models.py:1793 msgid "You don't have the right to view other bans than yours." msgstr "" "Vous n'avez pas le droit de voir d'autres bannissements que les vôtres." -#: users/models.py:1827 +#: users/models.py:1841 msgid "Can view a whitelist object" msgstr "Peut voir un objet accès gracieux" -#: users/models.py:1828 +#: users/models.py:1842 msgid "whitelist (free of charge access)" msgstr "Accès gracieux" -#: users/models.py:1829 +#: users/models.py:1843 msgid "whitelists (free of charge access)" msgstr "Accès gracieux" -#: users/models.py:1849 +#: users/models.py:1863 msgid "You don't have the right to view other whitelists than yours." msgstr "" "Vous n'avez pas le droit de voir d'autres accès gracieux que les vôtres." -#: users/models.py:2047 +#: users/models.py:2061 msgid "User of the local email account." msgstr "Utilisateur du compte mail local." -#: users/models.py:2050 +#: users/models.py:2064 msgid "Local part of the email address." msgstr "Partie locale de l'adresse mail." -#: users/models.py:2055 +#: users/models.py:2069 msgid "Can view a local email account object" msgstr "Peut voir un objet compte mail local" -#: users/models.py:2057 +#: users/models.py:2071 msgid "local email account" msgstr "compte mail local" -#: users/models.py:2058 +#: users/models.py:2072 msgid "local email accounts" msgstr "comptes mail locaux" -#: users/models.py:2086 users/models.py:2121 users/models.py:2155 -#: users/models.py:2189 +#: users/models.py:2100 users/models.py:2135 users/models.py:2169 +#: users/models.py:2203 msgid "The local email accounts are not enabled." msgstr "Les comptes mail locaux ne sont pas activés." -#: users/models.py:2091 +#: users/models.py:2105 msgid "You don't have the right to add a local email account to another user." msgstr "" "Vous n'avez pas le droit d'ajouter un compte mail local à un autre " "utilisateur." -#: users/models.py:2101 +#: users/models.py:2115 msgid "You reached the limit of {} local email accounts." msgstr "Vous avez atteint la limite de {} comptes mail locaux." -#: users/models.py:2127 +#: users/models.py:2141 msgid "You don't have the right to view another user's local email account." msgstr "" "Vous n'avez pas le droit de voir le compte mail local d'un autre utilisateur." -#: users/models.py:2147 +#: users/models.py:2161 msgid "" "You can't delete a local email account whose local part is the same as the " "username." @@ -711,13 +711,13 @@ msgstr "" "Vous ne pouvez pas supprimer un compte mail local dont la partie locale est " "la même que le pseudo." -#: users/models.py:2161 +#: users/models.py:2175 msgid "You don't have the right to delete another user's local email account." msgstr "" "Vous n'avez pas le droit de supprimer le compte mail local d'un autre " "utilisateur." -#: users/models.py:2181 +#: users/models.py:2195 msgid "" "You can't edit a local email account whose local part is the same as the " "username." @@ -725,13 +725,13 @@ msgstr "" "Vous ne pouvez pas modifier un compte mail local dont la partie locale est " "la même que le pseudo." -#: users/models.py:2195 +#: users/models.py:2209 msgid "You don't have the right to edit another user's local email account." msgstr "" "Vous n'avez pas le droit de modifier le compte mail local d'un autre " "utilisateur." -#: users/models.py:2204 +#: users/models.py:2218 msgid "The local part must not contain @ or +." msgstr "La partie locale ne doit pas contenir @ ou +." @@ -888,8 +888,8 @@ msgstr "pour %(name)s." #: users/templates/users/confirm_email.html:39 #: users/templates/users/delete.html:36 #: users/templates/users/mass_archive.html:36 -#: users/templates/users/resend_confirmation_email.html:35 users/views.py:628 -#: users/views.py:732 +#: users/templates/users/resend_confirmation_email.html:35 users/views.py:636 +#: users/views.py:740 msgid "Confirm" msgstr "Confirmer" @@ -1086,14 +1086,14 @@ msgstr "Pas de machine" msgid "Detailed information" msgstr "Informations détaillées" -#: users/templates/users/profil.html:161 users/views.py:202 users/views.py:233 -#: users/views.py:252 users/views.py:269 users/views.py:341 users/views.py:395 -#: users/views.py:449 users/views.py:514 users/views.py:561 users/views.py:597 -#: users/views.py:657 users/views.py:703 +#: users/templates/users/profil.html:161 users/views.py:206 users/views.py:237 +#: users/views.py:256 users/views.py:273 users/views.py:345 users/views.py:403 +#: users/views.py:457 users/views.py:522 users/views.py:569 users/views.py:605 +#: users/views.py:665 users/views.py:711 msgid "Edit" msgstr "Modifier" -#: users/templates/users/profil.html:165 users/views.py:288 users/views.py:1035 +#: users/templates/users/profil.html:165 users/views.py:292 users/views.py:1043 msgid "Change the password" msgstr "Changer le mot de passe" @@ -1333,160 +1333,160 @@ msgstr "Connecté avec l'appareil :" msgid "MAC address %(mac)s" msgstr "Adresse MAC %(mac)s" -#: users/views.py:133 +#: users/views.py:135 #, python-format msgid "The user %s was created, a confirmation email was sent." msgstr "L'utilisateur %s a été créé, un mail de confirmation a été envoyé." -#: users/views.py:140 +#: users/views.py:142 #, python-format msgid "The user %s was created, an email to set the password was sent." msgstr "" "L'utilisateur %s a été créé, un mail pour initialiser le mot de passe a été " "envoyé." -#: users/views.py:153 +#: users/views.py:155 msgid "Commit" msgstr "Valider" -#: users/views.py:174 +#: users/views.py:178 #, python-format msgid "The club %s was created, an email to set the password was sent." msgstr "" "Le club %s a été créé, un mail pour initialiser le mot de passe a été envoyé." -#: users/views.py:179 +#: users/views.py:183 msgid "Create a club" msgstr "Créer un club" -#: users/views.py:194 +#: users/views.py:198 msgid "The club was edited." msgstr "Le club a été modifié." -#: users/views.py:226 +#: users/views.py:230 msgid "The user was edited." msgstr "L'utilisateur a été modifié." -#: users/views.py:229 +#: users/views.py:233 msgid "Sent a new confirmation email." msgstr "Un nouveau mail de confirmation a été envoyé." -#: users/views.py:247 +#: users/views.py:251 msgid "The states were edited." msgstr "Les états ont été modifié." -#: users/views.py:249 +#: users/views.py:253 msgid "An email to confirm the address was sent." msgstr "Un mail pour confirmer l'adresse a été envoyé." -#: users/views.py:266 +#: users/views.py:270 msgid "The groups were edited." msgstr "Les groupes ont été modifiés." -#: users/views.py:285 users/views.py:1032 +#: users/views.py:289 users/views.py:1040 msgid "The password was changed." msgstr "Le mot de passe a été changé." -#: users/views.py:300 +#: users/views.py:304 #, python-format msgid "%s was removed from the group." msgstr "%s a été retiré du groupe." -#: users/views.py:310 +#: users/views.py:314 #, python-format msgid "%s is no longer superuser." msgstr "%s n'est plus superutilisateur." -#: users/views.py:321 +#: users/views.py:325 msgid "The service user was created." msgstr "L'utilisateur service a été créé." -#: users/views.py:324 users/views.py:378 users/views.py:429 users/views.py:487 -#: users/views.py:579 users/views.py:642 users/views.py:685 +#: users/views.py:328 users/views.py:384 users/views.py:437 users/views.py:495 +#: users/views.py:587 users/views.py:650 users/views.py:693 msgid "Add" msgstr "Ajouter" -#: users/views.py:338 +#: users/views.py:342 msgid "The service user was edited." msgstr "L'utilisateur service a été modifié." -#: users/views.py:353 +#: users/views.py:357 msgid "The service user was deleted." msgstr "L'utilisateur service a été supprimé." -#: users/views.py:373 +#: users/views.py:379 msgid "The ban was added." msgstr "Le bannissement a été ajouté." -#: users/views.py:376 +#: users/views.py:382 msgid "Warning: this user already has an active ban." msgstr "Attention : cet utilisateur a déjà un bannissement actif." -#: users/views.py:392 +#: users/views.py:400 msgid "The ban was edited." msgstr "Le bannissement a été modifié." -#: users/views.py:405 +#: users/views.py:413 msgid "The ban was deleted." msgstr "Le bannissement a été supprimé." -#: users/views.py:422 +#: users/views.py:430 msgid "The whitelist was added." msgstr "L'accès gracieux a été ajouté." -#: users/views.py:426 +#: users/views.py:434 msgid "Warning: this user already has an active whitelist." msgstr "Attention : cet utilisateur a déjà un accès gracieux actif." -#: users/views.py:446 +#: users/views.py:454 msgid "The whitelist was edited." msgstr "L'accès gracieux a été ajouté." -#: users/views.py:461 +#: users/views.py:469 msgid "The whitelist was deleted." msgstr "L'accès gracieux a été supprimé." -#: users/views.py:466 +#: users/views.py:474 msgid "whitelist" msgstr "accès gracieux" -#: users/views.py:481 +#: users/views.py:489 msgid "The local email account was created." msgstr "Le compte mail local a été créé." -#: users/views.py:504 +#: users/views.py:512 msgid "The local email account was edited." msgstr "Le compte mail local a été modifié." -#: users/views.py:527 +#: users/views.py:535 msgid "The local email account was deleted." msgstr "Le compte mail local a été supprimé." -#: users/views.py:532 +#: users/views.py:540 msgid "email address" msgstr "adresse mail" -#: users/views.py:548 +#: users/views.py:556 msgid "The email settings were edited." msgstr "Les paramètres mail ont été modifiés." -#: users/views.py:551 users/views.py:1077 +#: users/views.py:559 users/views.py:1085 msgid "An email to confirm your address was sent." msgstr "Un mail pour confirmer votre adresse a été envoyé." -#: users/views.py:576 +#: users/views.py:584 msgid "The school was added." msgstr "L'établissement a été ajouté." -#: users/views.py:594 +#: users/views.py:602 msgid "The school was edited." msgstr "L'établissement a été modifié." -#: users/views.py:616 +#: users/views.py:624 msgid "The school was deleted." msgstr "L'établissement a été supprimé." -#: users/views.py:621 +#: users/views.py:629 #, python-format msgid "" "The school %s is assigned to at least one user, impossible to delete it." @@ -1494,31 +1494,31 @@ msgstr "" "L'établissement %s est assigné à au moins un utilisateur, impossible de le " "supprimer." -#: users/views.py:639 +#: users/views.py:647 msgid "The shell was added." msgstr "L'interface en ligne de commande a été ajoutée." -#: users/views.py:654 +#: users/views.py:662 msgid "The shell was edited." msgstr "L'interface en ligne de commande a été modifiée." -#: users/views.py:669 +#: users/views.py:677 msgid "The shell was deleted." msgstr "L'interface en ligne de commande a été supprimée." -#: users/views.py:682 +#: users/views.py:690 msgid "The group of rights was added." msgstr "Le groupe de droits a été ajouté." -#: users/views.py:700 +#: users/views.py:708 msgid "The group of rights was edited." msgstr "Le groupe de droits a été modifié." -#: users/views.py:720 +#: users/views.py:728 msgid "The group of rights was deleted." msgstr "Le groupe de droits a été supprimé." -#: users/views.py:725 +#: users/views.py:733 #, python-format msgid "" "The group of rights %s is assigned to at least one user, impossible to " @@ -1527,37 +1527,37 @@ msgstr "" "Le groupe de droits %s est assigné à au moins un utilisateur, impossible de " "le supprimer." -#: users/views.py:761 +#: users/views.py:769 #, python-format msgid "%s users were archived." msgstr "%s utilisateurs ont été archivés." -#: users/views.py:990 users/views.py:1073 +#: users/views.py:998 users/views.py:1081 msgid "The user doesn't exist." msgstr "L'utilisateur n'existe pas." -#: users/views.py:992 users/views.py:1000 +#: users/views.py:1000 users/views.py:1008 msgid "Reset" msgstr "Réinitialiser" -#: users/views.py:997 +#: users/views.py:1005 msgid "An email to reset the password was sent." msgstr "Un mail pour réinitialiser le mot de passe a été envoyé." -#: users/views.py:1015 +#: users/views.py:1023 msgid "Error: please contact an admin." msgstr "Erreur : veuillez contacter un admin." -#: users/views.py:1053 +#: users/views.py:1061 #, python-format msgid "The %s address was confirmed." msgstr "L'adresse mail %s a été confirmée." -#: users/views.py:1100 +#: users/views.py:1108 msgid "Incorrect URL, or already registered device." msgstr "URL incorrect, ou appareil déjà enregistré." -#: users/views.py:1112 +#: users/views.py:1120 msgid "" "Successful registration! Please disconnect and reconnect your Ethernet cable " "to get Internet access." @@ -1565,7 +1565,7 @@ msgstr "" "Enregistrement réussi ! Veuillez débrancher et rebrancher votre câble " "Ethernet pour avoir accès à Internet." -#: users/views.py:1152 users/views.py:1176 users/views.py:1191 +#: users/views.py:1160 users/views.py:1184 users/views.py:1199 msgid "The mailing list doesn't exist." msgstr "La liste de diffusion n'existe pas." From c0ae68490c48cce3f35e5c730b2d10ee0b852f64 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Sun, 19 Apr 2020 21:13:31 +0200 Subject: [PATCH 5/8] Also catch ConnectionError when sending mails --- cotisations/utils.py | 2 +- re2o/mail_utils.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cotisations/utils.py b/cotisations/utils.py index c370fe59..5da8eee0 100644 --- a/cotisations/utils.py +++ b/cotisations/utils.py @@ -50,7 +50,7 @@ def send_mail(mail, request): """Wrapper for Django's EmailMessage.send which handles errors""" try: mail.send() - except SMTPException as e: + except (SMTPException, ConnectionError) as e: if request: messages.error( request, diff --git a/re2o/mail_utils.py b/re2o/mail_utils.py index 065a9506..86d446fb 100644 --- a/re2o/mail_utils.py +++ b/re2o/mail_utils.py @@ -36,7 +36,7 @@ def send_mail(request, *args, **kwargs): try: kwargs["fail_silently"] = request is None django_send_mail(*args, **kwargs) - except SMTPException as e: + except (SMTPException, ConnectionError) as e: messages.error( request, _("Failed to send email: %(error)s.") % { From d0777187f79bd0ec96b746e0fc41021548430cd5 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Sun, 19 Apr 2020 21:15:07 +0200 Subject: [PATCH 6/8] Move both send_mail utils to same file --- cotisations/utils.py | 22 +++------------------- re2o/mail_utils.py | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/cotisations/utils.py b/cotisations/utils.py index 5da8eee0..c7ca6187 100644 --- a/cotisations/utils.py +++ b/cotisations/utils.py @@ -23,9 +23,7 @@ import os from django.template.loader import get_template from django.core.mail import EmailMessage -from django.utils.translation import ugettext_lazy as _ -from django.contrib import messages -from smtplib import SMTPException +from re2o.mail_utils import send_mail_object from .tex import create_pdf from preferences.models import AssoOption, GeneralOption, CotisationsOption, Mandate @@ -46,20 +44,6 @@ def find_payment_method(payment): return None -def send_mail(mail, request): - """Wrapper for Django's EmailMessage.send which handles errors""" - try: - mail.send() - except (SMTPException, ConnectionError) as e: - if request: - messages.error( - request, - _("Failed to send email: %(error)s.") % { - "error": e, - }, - ) - - def send_mail_invoice(invoice, request=None): """Creates the pdf of the invoice and sends it by email to the client""" purchases_info = [] @@ -108,7 +92,7 @@ def send_mail_invoice(invoice, request=None): attachments=[("invoice.pdf", pdf, "application/pdf")], ) - send_mail(mail, request) + send_mail_object(mail, request) def send_mail_voucher(invoice, request=None): @@ -145,4 +129,4 @@ def send_mail_voucher(invoice, request=None): attachments=[("voucher.pdf", pdf, "application/pdf")], ) - send_mail(mail, request) + send_mail_object(mail, request) diff --git a/re2o/mail_utils.py b/re2o/mail_utils.py index 86d446fb..72dbac81 100644 --- a/re2o/mail_utils.py +++ b/re2o/mail_utils.py @@ -43,3 +43,17 @@ def send_mail(request, *args, **kwargs): "error": e, }, ) + + +def send_mail_object(mail, request): + """Wrapper for Django's EmailMessage.send which handles errors""" + try: + mail.send() + except (SMTPException, ConnectionError) as e: + if request: + messages.error( + request, + _("Failed to send email: %(error)s.") % { + "error": e, + }, + ) From 528bfcdef391a7b9beb6169ba1c2679c43feaa88 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Sun, 19 Apr 2020 19:19:06 +0000 Subject: [PATCH 7/8] Fix calls to invoice.save --- cotisations/models.py | 11 ++++++----- cotisations/views.py | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/cotisations/models.py b/cotisations/models.py index 503ad244..dcb62408 100644 --- a/cotisations/models.py +++ b/cotisations/models.py @@ -329,11 +329,12 @@ class Facture(BaseInvoice): purchase.save() def save(self, *args, **kwargs): - super(Facture, self).save(*args, **kwargs) + try: + request = kwargs.pop("request") + except: + request = None - request = None - if "request" in kwargs: - request = kwargs["request"] + super(Facture, self).save(*args, **kwargs) if not self.__original_valid and self.valid: self.reorder_purchases() @@ -875,7 +876,7 @@ class Paiement(RevMixin, AclMixin, models.Model): # So make this invoice valid, trigger send mail invoice.valid = True - invoice.save(request) + invoice.save(request=request) # In case a cotisation was bought, inform the user, the # cotisation time has been extended too diff --git a/cotisations/views.py b/cotisations/views.py index def26cda..62eabef6 100644 --- a/cotisations/views.py +++ b/cotisations/views.py @@ -1008,7 +1008,7 @@ def credit_solde(request, user, **_kwargs): else: price_ok = True if price_ok: - invoice.save(request) + invoice.save(request=request) Vente.objects.create( facture=invoice, name="solde", From f3ae1973a185857c786ce9b1ad70b43b93c8b030 Mon Sep 17 00:00:00 2001 From: Jean-Romain Garnier Date: Sun, 19 Apr 2020 20:06:33 +0000 Subject: [PATCH 8/8] Catch socket exceptions in mail_send --- re2o/mail_utils.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/re2o/mail_utils.py b/re2o/mail_utils.py index 72dbac81..69d0cfff 100644 --- a/re2o/mail_utils.py +++ b/re2o/mail_utils.py @@ -29,14 +29,14 @@ from django.utils.translation import ugettext_lazy as _ from django.core.mail import send_mail as django_send_mail from django.contrib import messages from smtplib import SMTPException - +from socket import herror, gaierror def send_mail(request, *args, **kwargs): """Wrapper for Django's send_mail which handles errors""" try: kwargs["fail_silently"] = request is None django_send_mail(*args, **kwargs) - except (SMTPException, ConnectionError) as e: + except (SMTPException, ConnectionError, herror, gaierror) as e: messages.error( request, _("Failed to send email: %(error)s.") % { @@ -49,7 +49,7 @@ def send_mail_object(mail, request): """Wrapper for Django's EmailMessage.send which handles errors""" try: mail.send() - except (SMTPException, ConnectionError) as e: + except (SMTPException, ConnectionError, herror, gaierror) as e: if request: messages.error( request,