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/'