From d4f29224f69707f951e4cd2e073a03a86a95d128 Mon Sep 17 00:00:00 2001 From: Matias Varela Date: Sun, 24 Mar 2019 12:05:08 -0300 Subject: [PATCH] fix #50 custom thankyou page and send application letter Signed-off-by: Matias Varela --- .gitignore | 1 + Dockerfile | 1 + website/members/forms.py | 16 +- .../0021_member_application_letter.py | 18 ++ .../migrations/0024_merge_20190602_1955.py | 14 ++ website/members/models.py | 71 ++++++ ...html => signup_organization_thankyou.html} | 0 .../members/signup_person_thankyou.html | 119 +++++++++++ website/members/tests.py | 19 +- website/members/urls.py | 5 +- website/members/utils.py | 98 +++++++++ website/members/views.py | 202 ++++-------------- website/website/settings.py | 2 +- 13 files changed, 392 insertions(+), 174 deletions(-) create mode 100644 website/members/migrations/0021_member_application_letter.py create mode 100644 website/members/migrations/0024_merge_20190602_1955.py rename website/members/templates/members/{signup_thankyou.html => signup_organization_thankyou.html} (100%) create mode 100644 website/members/templates/members/signup_person_thankyou.html create mode 100644 website/members/utils.py diff --git a/.gitignore b/.gitignore index 0a72685..56ebcde 100644 --- a/.gitignore +++ b/.gitignore @@ -110,3 +110,4 @@ scripts/*.csv scripts/credentials.json .idea .vscode +/media/ diff --git a/Dockerfile b/Dockerfile index df031de..4d57365 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,6 +6,7 @@ RUN mkdir /code RUN mkdir /config # Install dependencies +RUN printf "deb http://archive.debian.org/debian/ jessie main\ndeb-src http://archive.debian.org/debian/ jessie main\ndeb http://security.debian.org jessie/updates main\ndeb-src http://security.debian.org jessie/updates main" > /etc/apt/sources.list RUN apt-get update && apt-get install -y inkscape && apt-get clean COPY /config/requirements.txt /config/ RUN pip install --no-cache-dir -r /config/requirements.txt diff --git a/website/members/forms.py b/website/members/forms.py index 238fae3..981ccfd 100644 --- a/website/members/forms.py +++ b/website/members/forms.py @@ -1,4 +1,5 @@ from django import forms +from django.db.transaction import atomic from django.conf import settings from django.utils.translation import ugettext_lazy as _ from django.utils.timezone import now @@ -70,19 +71,24 @@ def __init__(self, *args, **kwargs): ), ) + @atomic def save(self, commit=True): # We need remove category before save person. category = self.cleaned_data.pop('category', '') person = super(SignupPersonForm, self).save(commit=False) person.comments = ( "Se cargó a través del sitio web. Categoria seleccionada: %s." % category.name) - patron = Patron( - name=f"{person.first_name} {person.last_name}", - email=person.email, comments="Se cargó a través del sitio web") + patron, created = Patron.objects.get_or_create( + email=person.email, + defaults={ + 'name': f"{person.first_name} {person.last_name}", + 'comments': "Se cargó a través del sitio web" + }) member = Member(registration_date=now(), category=category) + member.patron = patron if commit: - patron.save() - member.patron = patron + file, filename = member._generate_letter(person) + member.application_letter.save(filename, file, save=True) member.save() person.membership = member person.save() diff --git a/website/members/migrations/0021_member_application_letter.py b/website/members/migrations/0021_member_application_letter.py new file mode 100644 index 0000000..3757ac4 --- /dev/null +++ b/website/members/migrations/0021_member_application_letter.py @@ -0,0 +1,18 @@ +# Generated by Django 2.0.4 on 2019-03-05 03:41 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('members', '0020_auto_20190209_1148'), + ] + + operations = [ + migrations.AddField( + model_name='member', + name='application_letter', + field=models.FileField(blank=True, null=True, upload_to='application_letter', verbose_name='carta de afiliación'), + ), + ] diff --git a/website/members/migrations/0024_merge_20190602_1955.py b/website/members/migrations/0024_merge_20190602_1955.py new file mode 100644 index 0000000..9d7a5a9 --- /dev/null +++ b/website/members/migrations/0024_merge_20190602_1955.py @@ -0,0 +1,14 @@ +# Generated by Django 2.0.4 on 2019-06-02 19:55 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('members', '0021_member_application_letter'), + ('members', '0023_auto_20190407_1624'), + ] + + operations = [ + ] diff --git a/website/members/models.py b/website/members/models.py index e31c907..b46ce1d 100644 --- a/website/members/models.py +++ b/website/members/models.py @@ -1,8 +1,14 @@ +import os + +import certg + from django.core.validators import MaxValueValidator, MinValueValidator +from django.core.files import File from django.db import models from django.utils.translation import ugettext_lazy as _ from django_extensions.db.models import TimeStampedModel + DEFAULT_MAX_LEN = 317 # Almost random LONG_MAX_LEN = 2048 # Random but bigger @@ -60,6 +66,8 @@ class Member(TimeStampedModel): null=True, blank=True) first_payment_year = models.PositiveSmallIntegerField( _('primer pago año'), validators=[MinValueValidator(2015)], null=True, blank=True) + application_letter = models.FileField( + _('carta de afiliación'), upload_to='application_letter', null=True, blank=True) # Flags has_student_certificate = models.BooleanField( @@ -86,6 +94,69 @@ def __str__(self): shutdown = "" if self.shutdown_date is None else ", DADO DE BAJA" return f"{legal_id} - [{self.category}{shutdown}] {self.entity}" + def _generate_letter(self, person): + """Generate the letter to be signed.""" + letter_svg_template = os.path.join( + os.path.abspath('.'), 'members', 'templates', 'members', 'carta.svg') + path_prefix = "/tmp/letter" + person_info = { + 'tiposocie': self.category.name, + 'nombre': person.first_name, + 'apellido': person.last_name, + 'dni': person.document_number, + 'email': person.email, + 'nacionalidad': person.nationality, + 'estadocivil': person.marital_status, + 'profesion': person.occupation, + 'fechanacimiento': person.birth_date.strftime("%Y-%m-%d"), + 'domicilio': person.street_address, + 'ciudad': person.city, + 'codpostal': person.zip_code, + 'provincia': person.province, + 'pais': person.country, + } + + # this could be optimized to generate all PDFs at once, but we're fine so far + (letter_filepath, ) = certg.process( + letter_svg_template, path_prefix, "dni", [person_info], images=None) + f_opened = open(letter_filepath, 'rb') + file = File(f_opened) + return file, letter_filepath.split('/')[-1] + + def _analyze(self): + """Analyze and indicate in which categories the member is missing somethhing.""" + cat_student = Category.objects.get(name=Category.STUDENT) + cat_collab = Category.objects.get(name=Category.COLLABORATOR) + + # simple flags with "Not Applicable" situation + missing_student_certif = ( + self.category == cat_student and not self.has_student_certificate) + missing_collab_accept = ( + self.category == cat_collab and not self.has_collaborator_acceptance) + + # info from Person + missing_nickname = self.person.nickname == "" + # picture is complicated, bool() is used to check if the Image field has an associated + # filename, and False itself is used as the "dont want a picture!" flag + missing_picture = not self.person.picture and self.person.picture is not False + + # info from Member itself + missing_payment = self.first_payment_month is None and self.category.fee > 0 + missing_signed_letter = not self.has_subscription_letter + + missing_info = { + 'missing_signed_letter': missing_signed_letter, + 'missing_student_certif': missing_student_certif, + 'missing_payment': missing_payment, + 'missing_nickname': missing_nickname, + 'missing_picture': missing_picture, + 'missing_collab_accept': missing_collab_accept, + } + missing_info['annual_fee'] = self.category.fee * 12 + missing_info['member'] = self + missing_info['on_purpose_missing_var'] = "ERROR" + return missing_info + def picture_upload_path(instance, filename): """Customize the picture's upload path to MEDIA_ROOT/pictures/lastname_document.ext.""" diff --git a/website/members/templates/members/signup_thankyou.html b/website/members/templates/members/signup_organization_thankyou.html similarity index 100% rename from website/members/templates/members/signup_thankyou.html rename to website/members/templates/members/signup_organization_thankyou.html diff --git a/website/members/templates/members/signup_person_thankyou.html b/website/members/templates/members/signup_person_thankyou.html new file mode 100644 index 0000000..5374231 --- /dev/null +++ b/website/members/templates/members/signup_person_thankyou.html @@ -0,0 +1,119 @@ +{% extends "members/signup_initial.html" %} +{% load crispy_forms_tags %} +{% block inner-content %} +
+

Su solicitud fue enviada con éxito

+

Próximos pasos

+ {% if missing_signed_letter %} +

+ Tenés que mandar una carta en papel, firmada, expresando tu intención de querer asociarte (es un paso + necesario desde el punto de vista legal). + + Nosotros te la generamos automáticamente (descargar), + revisá los datos, imprimila y a mano poné lugar y fecha, firmá, aclará y poné tu DNI. +

+

+ Para acelerar el trámite, sacale una foto y mandánosla, pero también la vas a tener que mandar por correo postal + o llevarla en mano a la sede de la Asociación Civil ** (o traerla a algún evento de PyAr y + dársela a cualquiera de la Comisión Directiva). +

+ {% endif %} + + {% if missing_student_certif %} +

+ Como sos socia/o "Estudiante", necesitamos un Certificado de Alumna/o Regular. + + Cuando lo consigas, para acelerar el trámite, sacale una foto y mandánosla, pero también la + vas a tener que mandar por correo postal o llevarla en mano a la sede de la Asociación Civil ** (o + traerla a algún evento de PyAr y dársela a cualquiera de la Comisión Directiva). +

+ {% endif %} + + {% if missing_payment %} +

+ Tenés que pagar la cuota social. En tu caso, para Socia/o tipo {{ object.category }}, + son ${{ object.category.fee }} por mes, y lo podés abonar de distintas maneras:

+

+ Débito mensual, usando una tarjeta de crédito: + {% if object.category == object.category.ACTIVE %} + http://mpago.la/1WDPgB + {% elif object.category == object.category.SUPPORTER %} + http://mpago.la/3c4foF + {% else %} + {{ on_purpose_missing_var }} + {% endif %} +

+ {% endif %} + +

+ También podés pagar el año entero (un total de ${{ annual_fee }}) directamente por transferencia o depósito a... +
+

+ Asociación Civil Python Argentina
+ Banco Credicoop
+ Cuenta Corriente en pesos
+ Nro. 191-153-009748/3
+ CBU 19101530-55015300974832
+ Alias python +
+ + ... o con tarjetas de crédito, débito, pagofácil, rapipago, etc: + + {% if object.category == object.category.ACTIVE %} + Pagar con TodoPago + + {% elif object.category == object.category.SUPPORTER %} + Pagar con Todo + + {% else %} + {{ on_purpose_missing_var }} + {% endif %} +

+ {% if missing_nickname %} +

+ Pasanos un nick o sobrenombre, para el carnet de asociada/o, totalmente opcional (pero si no + querés avisanos así dejamos de pedirte). +

+ {% endif %} + + {% if missing_picture %} +

+ Pasanos una foto cuadrada o cualquier imagen cuadrada, para el carnet de asociada/o, totalmente opcional + (pero si no querés avisanos así dejamos de pedirte). +

+ {% endif %} + + {% if missing_collab_accept %} +

+ Como sos socia/o "Colaborador/a", tenés que anotarte para colaborar.
+ + La dinámica es la siguiente: tenemos un grupo de Telegram donde distintos responsables de la + Asociación Civil podemos pedir cosas y vos u otro/a socio/a lo pueden agarrar para hacer. Ejemplo de + un pedido posible: "hay que arreglar un bug en el sistema de gestión de la Asoc Civil (hecho en Django) + que no muestra tal cosa en tal lugar".
+ + Obvio, cada uno agarrará lo que pueda resolver, pero la idea es que colaboren Participar en ese canal y + realmente colaborar con lo que la Asociación Civil necesite es condición necesaria para que al año se + renueve la posibilidad de ser socio/a Colaborador/a.
+ + Entonces, pasame por favor tu handle de Telegram así te invito al grupo. +

+ {% endif %} + + + {% if missing_signed_letter or missing_student_certif %} +

+ ** La dirección de la sede: +

+ Asociación Civil Python Argentina
+ Chile 1155, piso 1
+ CABA (C1098AAW)
+ Buenos Aires, Argentina
+
+

+ {% endif %} +

+ Ir a la página de la asociación +

+
+{% endblock %} diff --git a/website/members/tests.py b/website/members/tests.py index 9d611a5..48b18ce 100644 --- a/website/members/tests.py +++ b/website/members/tests.py @@ -8,7 +8,7 @@ from django.utils.timezone import now, make_aware from django.urls import reverse -from members import logic, views +from members import logic, utils from members.models import ( Member, Patron, Category, PaymentStrategy, Quota, Person, Organization) @@ -99,13 +99,14 @@ def test_signup_submit_success(self): response = self.client.get(reverse('signup_person')) response = self.client.post(reverse('signup_person'), data=data) self.assertEqual(response.status_code, 302) - self.assertEqual(response.url, reverse('signup_thankyou')) person = Person.objects.get(nickname='pepepin') + self.assertEqual(response.url, reverse('signup_person_thankyou', args=[person.membership_id])) self.assertEqual(person.first_name, 'Pepe') self.assertEqual(person.email, 'pepe@pomp.in') self.assertEqual(person.birth_date, datetime.date(1999, 12, 11)) self.assertEqual(person.membership.category.pk, cat.pk) self.assertEqual(person.membership.patron.email, person.email) + self.assertIsNotNone(person.membership.application_letter) def test_signup_submit_success_without_optionals(self): # crear categoria @@ -130,8 +131,8 @@ def test_signup_submit_success_without_optionals(self): response = self.client.get(reverse('signup_person')) response = self.client.post(reverse('signup_person'), data=data) self.assertEqual(response.status_code, 302) - self.assertEqual(response.url, reverse('signup_thankyou')) person = Person.objects.get(document_number='124354656') + self.assertEqual(response.url, reverse('signup_person_thankyou', args=[person.membership_id])) self.assertEqual(person.first_name, 'Pepe') self.assertEqual(person.email, 'pepe@pomp.in') self.assertEqual(person.birth_date, datetime.date(1999, 12, 11)) @@ -175,7 +176,7 @@ def test_signup_org_submit_success(self): response = self.client.get(reverse('signup_organization')) response = self.client.post(reverse('signup_organization'), data=data) self.assertEqual(response.status_code, 302) - self.assertEqual(response.url, reverse('signup_thankyou')) + self.assertEqual(response.url, reverse('signup_organization_thankyou')) orga = Organization.objects.get(name='Orga') self.assertEqual(orga.contact_info, random_text) @@ -576,21 +577,21 @@ class BuildDebtStringTestCase(TestCase): """Tests for the string debt building utility.""" def test_empty(self): - result = views._build_debt_string([]) + result = utils.build_debt_string([]) self.assertEqual(result, "-") def test_1(self): - result = views._build_debt_string([(2018, 8)]) + result = utils.build_debt_string([(2018, 8)]) self.assertEqual(result, "1 (2018-08)") def test_2(self): - result = views._build_debt_string([(2018, 8), (2018, 9)]) + result = utils.build_debt_string([(2018, 8), (2018, 9)]) self.assertEqual(result, "2 (2018-08, 2018-09)") def test_3(self): - result = views._build_debt_string([(2018, 8), (2018, 9), (2018, 10)]) + result = utils.build_debt_string([(2018, 8), (2018, 9), (2018, 10)]) self.assertEqual(result, "3 (2018-08, 2018-09, 2018-10)") def test_exceeding(self): - result = views._build_debt_string([(2018, 8), (2018, 9), (2018, 10), (2018, 11)]) + result = utils.build_debt_string([(2018, 8), (2018, 9), (2018, 10), (2018, 11)]) self.assertEqual(result, "4 (2018-08, 2018-09, 2018-10, ...)") diff --git a/website/members/urls.py b/website/members/urls.py index bdf7aa7..20931cc 100644 --- a/website/members/urls.py +++ b/website/members/urls.py @@ -7,9 +7,12 @@ urlpatterns = [ path('solicitud-alta/', views.signup_initial, name='signup'), path('solicitud-alta/persona/', views.signup_form_person, name='signup_person'), + path('solicitud-alta/persona//gracias', + views.signup_thankyou, name='signup_person_thankyou'), path('solicitud-alta/organizacion', views.signup_form_organization, name='signup_organization'), - path('solicitud-alta/gracias', views.signup_thankyou, name='signup_thankyou'), + path('solicitud-alta/organizacion/gracias', + views.signup_organization_thankyou, name='signup_organization_thankyou'), path('reportes/', views.reports_main, name='reports_main'), path('reportes/deudas', views.report_debts, name='report_debts'), diff --git a/website/members/utils.py b/website/members/utils.py new file mode 100644 index 0000000..64e3a22 --- /dev/null +++ b/website/members/utils.py @@ -0,0 +1,98 @@ +import os +import re +import logging + +from django.conf import settings +from django.core.mail import EmailMessage +from django.template.loader import render_to_string +from django.utils.timezone import now + +from members import logic + +logger = logging.getLogger(__name__) + + +def clean_double_empty_lines(oldtext): + while True: + newtext = re.sub("\n *?\n *?\n", "\n\n", oldtext) + if newtext == oldtext: + return newtext + oldtext = newtext + + +def build_debt_string(debt): + """Build a nice string to represent the debt.""" + if not debt: + return "-" + + # convert tuples to nice string format (only first 3, the used ones) + debt_nicer = ["{}-{:02d}".format(*d) for d in debt[:3]] + exceeding = "" if len(debt) <= 3 else ", ..." + result = "{} ({}{})".format(len(debt), ", ".join(debt_nicer), exceeding) + return result + + +def get_yearmonth(request): + try: + year = int(request.GET['limit_year']) + month = int(request.GET['limit_month']) + except (KeyError, ValueError): + # get by default one month before now, as it's the first month not really + # paid (current month is not yet finished) + currently = now() + year = currently.year + month = currently.month - 1 + if month <= 0: + year -= 1 + month += 12 + return year, month + + +def sendmail_missing_info(member): + """ + Generate email with application letter and sent it. + """ + EMAIL_SUBJECT = "Continuación del trámite de inscripción a la Asociación Civil Python Argentina" + + missing_info = member._analyze() + text = render_to_string('members/mail_missing.txt', missing_info) + text = clean_double_empty_lines(text) + if 'ERROR' in text: + # badly built template + text_err = "Error when building the report missing mail result, info: %s" % missing_info + logger.error(text_err) + raise Exception(text_err) + + # build the mail + recipient = f"{member.entity.full_name} <{member.entity.email}>" + mail = EmailMessage(EMAIL_SUBJECT, text, settings.EMAIL_FROM, [recipient]) + # if missing the signed letter, produce it and attach it + if missing_info['missing_signed_letter']: + mail.attach_file(member.application_letter.path) + mail.send() + + +def sendmail_about_debts(member, limit_year, limit_month): + """ + Generate email with debts information. + """ + EMAIL_SUBJECT = "Cuotas adeudadas a la Asociación Civil Python Argentina" + + debt = logic.get_debt_state(member, limit_year, limit_month) + debt_info = { + 'debt': build_debt_string(debt), + 'member': member, + 'annual_fee': member.category.fee * 12, + 'on_purpose_missing_var': "ERROR", + } + text = render_to_string('members/mail_indebt.txt', debt_info) + text = clean_double_empty_lines(text) + if 'ERROR' in text: + # badly built template + text_ext = "Error when building the report missing mail result, info: %s" % debt_info + logger.error(text_ext) + raise Exception(text_ext) + + recipient = f"{member.entity.full_name} <{member.entity.email}>" + mail = EmailMessage(EMAIL_SUBJECT, text, settings.EMAIL_FROM, [recipient]) + mail.send() diff --git a/website/members/views.py b/website/members/views.py index 94c71d1..f5c8283 100644 --- a/website/members/views.py +++ b/website/members/views.py @@ -10,7 +10,6 @@ from django.conf import settings from django.contrib import messages from django.contrib.auth.mixins import LoginRequiredMixin -from django.core.mail import EmailMessage from django.db.models import Q, Sum from django.http import HttpResponse from django.shortcuts import render @@ -19,34 +18,18 @@ from django.utils.timezone import now from django.utils.translation import ugettext as _ from django.views import View -from django.views.generic import TemplateView, CreateView +from django.views.generic import TemplateView, CreateView, DetailView import members from members import logic from members.forms import SignupPersonForm, SignupOrganizationForm from members.models import Person, Organization, Category, Member, Quota, Payment - -logger = logging.getLogger(__name__) - - -def _clean_double_empty_lines(oldtext): - while True: - newtext = re.sub("\n *?\n *?\n", "\n\n", oldtext) - if newtext == oldtext: - return newtext - oldtext = newtext +from members.utils import ( + sendmail_missing_info, sendmail_about_debts, build_debt_string, + clean_double_empty_lines, get_yearmonth) -def _build_debt_string(debt): - """Build a nice string to represent the debt.""" - if not debt: - return "-" - - # convert tuples to nice string format (only first 3, the used ones) - debt_nicer = ["{}-{:02d}".format(*d) for d in debt[:3]] - exceeding = "" if len(debt) <= 3 else ", ..." - result = "{} ({}{})".format(len(debt), ", ".join(debt_nicer), exceeding) - return result +logger = logging.getLogger(__name__) class OnlyAdminsViewMixin(LoginRequiredMixin): @@ -64,7 +47,6 @@ class SignupPersonFormView(CreateView): model = Person form_class = SignupPersonForm template_name = 'members/signup_form.html' - success_url = reverse_lazy('signup_thankyou') def get_context_data(self, **kwargs): context = super(SignupPersonFormView, self).get_context_data(**kwargs) @@ -75,16 +57,40 @@ def form_invalid(self, form): messages.error(self.request, _("Por favor, revise los campos.")) return super(SignupPersonFormView, self).form_invalid(form) + def form_valid(self, form): + response = super().form_valid(form) + try: + sendmail_missing_info(form.instance.membership) + except ConnectionError: + messages.warning( + self.request, _( + "No pudimos enviarte el email con la carta de afiliación." + " Puedes descargarla más abajo")) + return response + + def get_success_url(self): + return reverse_lazy('signup_person_thankyou', args=(self.object.membership_id, )) + class SignupOrganizationsFormView(CreateView): form_class = SignupOrganizationForm model = Organization template_name = 'members/signup_org_form.html' - success_url = reverse_lazy('signup_thankyou') + success_url = reverse_lazy('signup_organization_thankyou') + +class SignupThankyouView(DetailView): + model = Member + template_name = 'members/signup_person_thankyou.html' + + def get_context_data(self, **kwargs): + ctx = super().get_context_data(**kwargs) + ctx.update(ctx["object"]._analyze()) + return ctx -class SignupThankyouView(TemplateView): - template_name = 'members/signup_thankyou.html' + +class SignupOrganizationThankyouView(TemplateView): + template_name = 'members/signup_organization_thankyou.html' class ReportsInitialView(OnlyAdminsViewMixin, TemplateView): @@ -93,42 +99,23 @@ class ReportsInitialView(OnlyAdminsViewMixin, TemplateView): class ReportDebts(OnlyAdminsViewMixin, View): """Handle the report about debts.""" - MAIL_SUBJECT = "Cuotas adeudadas a la Asociación Civil Python Argentina" def post(self, request): raw_sendmail = parse.parse_qs(request.body)[b'sendmail'] to_send_mail_ids = map(int, raw_sendmail) - limit_year, limit_month = self._get_yearmonth(request) - + limit_year, limit_month = get_yearmonth(request) sent_error = 0 sent_ok = 0 tini = time.time() errors_code = str(uuid.uuid4()) for member_id in to_send_mail_ids: member = Member.objects.get(id=member_id) - - debt = logic.get_debt_state(member, limit_year, limit_month) - debt_info = { - 'debt': _build_debt_string(debt), - 'member': member, - 'annual_fee': member.category.fee * 12, - 'on_purpose_missing_var': "ERROR", - } - text = render_to_string('members/mail_indebt.txt', debt_info) - text = _clean_double_empty_lines(text) - if 'ERROR' in text: - # badly built template - logger.error( - "Error when building the report missing mail result, info: %s", debt_info) - return HttpResponse("Error al armar la página") - recipient = f"{member.entity.full_name} <{member.entity.email}>" - mail = EmailMessage(self.MAIL_SUBJECT, text, settings.EMAIL_FROM, [recipient]) try: - mail.send() - except Exception as err: + sendmail_about_debts(member, limit_year, limit_month) + except Exception as exc: sent_error += 1 logger.error( - "Problems sending email [%s] to member %s: %r", errors_code, member, err) + "Problems sending email [%s] to member %s: %r", errors_code, member, exc) else: sent_ok += 1 deltat = time.time() - tini @@ -141,29 +128,15 @@ def post(self, request): } return render(request, 'members/mail_sent.html', context) - def _get_yearmonth(self, request): - try: - year = int(request.GET['limit_year']) - month = int(request.GET['limit_month']) - except (KeyError, ValueError): - # get by default one month before now, as it's the first month not really - # paid (current month is not yet finished) - currently = now() - year = currently.year - month = currently.month - 1 - if month <= 0: - year -= 1 - month += 12 - return year, month def get(self, request): """Produce the report with the given year/month limits.""" - limit_year, limit_month = self._get_yearmonth(request) + limit_year, limit_month = get_yearmonth(request) # get those already confirmed members - members = Member.objects\ - .filter(legal_id__isnull=False, category__fee__gt=0, shutdown_date__isnull=True)\ - .order_by('legal_id').all() + members = Member.objects.filter( + legal_id__isnull=False, category__fee__gt=0, + shutdown_date__isnull=True).order_by('legal_id').all() debts = [] for member in members: @@ -171,7 +144,7 @@ def get(self, request): if debt: debts.append({ 'member': member, - 'debt': _build_debt_string(debt), + 'debt': build_debt_string(debt), }) context = { @@ -184,35 +157,6 @@ def get(self, request): class ReportMissing(OnlyAdminsViewMixin, View): """Handle the report about what different people miss to get approved as a member.""" - MAIL_SUBJECT = "Continuación del trámite de inscripción a la Asociación Civil Python Argentina" - - def _generate_letter(self, member): - """Generate the letter to be signed.""" - letter_svg_template = os.path.join( - os.path.dirname(members.__file__), 'templates', 'members', 'carta.svg') - path_prefix = "/tmp/letter" - person = member.person - person_info = { - 'tiposocie': member.category.name, - 'nombre': person.first_name, - 'apellido': person.last_name, - 'dni': person.document_number, - 'email': person.email, - 'nacionalidad': person.nationality, - 'estadocivil': person.marital_status, - 'profesion': person.occupation, - 'fechanacimiento': person.birth_date.strftime("%Y-%m-%d"), - 'domicilio': person.street_address, - 'ciudad': person.city, - 'codpostal': person.zip_code, - 'provincia': person.province, - 'pais': person.country, - } - - # this could be optimized to generate all PDFs at once, but we're fine so far - (letter_filepath,) = certg.process( - letter_svg_template, path_prefix, "dni", [person_info], images=None) - return letter_filepath def post(self, request): raw_sendmail = parse.parse_qs(request.body)[b'sendmail'] @@ -225,43 +169,14 @@ def post(self, request): member = Member.objects.get(id=member_id) # create the mail text from the template - missing_info = self._analyze_member(member) - missing_info['annual_fee'] = member.category.fee * 12 - missing_info['member'] = member - missing_info['on_purpose_missing_var'] = "ERROR" - text = render_to_string('members/mail_missing.txt', missing_info) - text = _clean_double_empty_lines(text) - if 'ERROR' in text: - # badly built template - logger.error( - "Error when building the report missing mail result, info: %s", missing_info) - return HttpResponse("Error al armar la página") - - # if missing the signed letter, produce it and attach it - if missing_info['missing_signed_letter']: - letter_filepath = self._generate_letter(member) - - # build the mail - recipient = f"{member.entity.full_name} <{member.entity.email}>" - mail = EmailMessage(self.MAIL_SUBJECT, text, settings.EMAIL_FROM, [recipient]) - if missing_info['missing_signed_letter']: - mail.attach_file(letter_filepath) - - # actually send it try: - mail.send() + sendmail_missing_info(member) except Exception as err: sent_error += 1 logger.error( "Problems sending email [%s] to member %s: %r", errors_code, member, err) else: sent_ok += 1 - finally: - if missing_info['missing_signed_letter']: - try: - os.unlink(letter_filepath) - except Exception as exc: - logger.warning("Couldn't remove letter file %r: %r", letter_filepath, exc) deltat = time.time() - tini context = { @@ -272,42 +187,12 @@ def post(self, request): } return render(request, 'members/mail_sent.html', context) - def _analyze_member(self, member): - """Analyze and indicate in which categories the member is missing somethhing.""" - cat_student = Category.objects.get(name=Category.STUDENT) - cat_collab = Category.objects.get(name=Category.COLLABORATOR) - - # simple flags with "Not Applicable" situation - missing_student_certif = ( - member.category == cat_student and not member.has_student_certificate) - missing_collab_accept = ( - member.category == cat_collab and not member.has_collaborator_acceptance) - - # info from Person - missing_nickname = member.person.nickname == "" - # picture is complicated, bool() is used to check if the Image field has an associated - # filename, and False itself is used as the "dont want a picture!" flag - missing_picture = not member.person.picture and member.person.picture is not False - - # info from Member itself - missing_payment = member.first_payment_month is None and member.category.fee > 0 - missing_signed_letter = not member.has_subscription_letter - - return { - 'missing_signed_letter': missing_signed_letter, - 'missing_student_certif': missing_student_certif, - 'missing_payment': missing_payment, - 'missing_nickname': missing_nickname, - 'missing_picture': missing_picture, - 'missing_collab_accept': missing_collab_accept, - } - def get(self, request): not_yet_members = Member.objects.filter(legal_id=None).order_by('created').all() incompletes = [] for member in not_yet_members: - missing_info = self._analyze_member(member) + missing_info = member._analyze() # convert missing info to proper strings to show for k, v in missing_info.items(): @@ -388,6 +273,7 @@ def get(self, request): signup_form_person = SignupPersonFormView.as_view() signup_form_organization = SignupOrganizationsFormView.as_view() signup_thankyou = SignupThankyouView.as_view() +signup_organization_thankyou = SignupOrganizationThankyouView.as_view() # only admins reports_main = ReportsInitialView.as_view() report_debts = ReportDebts.as_view() diff --git a/website/website/settings.py b/website/website/settings.py index 9ec676a..68bb5da 100644 --- a/website/website/settings.py +++ b/website/website/settings.py @@ -143,7 +143,7 @@ class Base(Configuration): CRISPY_TEMPLATE_PACK = 'bootstrap3' MEDIA_URL = '/media/' - MEDIA_ROOT = BASE_DIR + MEDIA_ROOT = os.path.join(BASE_DIR, '..', 'media') LOGIN_URL = '/cuentas/login/'