Friday, May 13, 2016

Problems with sending email from a VPS

On both lino-framework.org and mylino.net I have currently problems when sending emails from my Lino applications.

To explore these problems, I used the following sendmail.py script:

from __future__ import print_function
from django.conf import settings
from django.core.mail import EmailMessage
for k in """EMAIL_HOST EMAIL_HOST_USER
 DEFAULT_FROM_EMAIL SERVER_EMAIL EMAIL_PORT EMAIL_USE_TLS
 """.split():
    print("{0} : {1}".format(k, getattr(settings, k)))

email = EmailMessage('Subject', 'Body', to=['luc.saffre@gmx.net'])
email.send()

This script is to be invoked via run as follows:

$ python manage.py  run sendmail.py

On mylino.net (which is hosted in France at OVH), my first attempt takes about 1 minute to complete, and then I get:

EMAIL_HOST : mx3.ovh.net
EMAIL_HOST_USER :
DEFAULT_FROM_EMAIL : noreply@mylino.net
SERVER_EMAIL : noreply@mylino.net
EMAIL_PORT : 25
EMAIL_USE_TLS : True
...
Traceback (most recent call last):
...
smtplib.SMTPRecipientsRefused: {u'luc.saffre@gmx.net': (553, "sorry, that domain isn't allowed to be relayed thru this MTA [mail497] (#5.7.1)")}

According to this page their STMP server is ns0.ovh.net. When I try this, I get another error message:

smtplib.SMTPRecipientsRefused: {u'luc.saffre@gmx.net': (554, '5.7.1 <133.ip-167-114-252.eu[167.114.252.133]>: Client host rejected: Access denied')}

While on lino-framework.org (hosted in Estonia by Elisa) I get another error:

EMAIL_HOST : smtp.elisa.ee
EMAIL_HOST_USER :
DEFAULT_FROM_EMAIL : noreply@lino-framework.org
SERVER_EMAIL : noreply@lino-framework.org
EMAIL_PORT : 25
EMAIL_USE_TLS : False
Traceback (most recent call last):
...
socket.gaierror: [Errno -2] Name or service not known

Adapting test suites

I adapted the test suites of Lino Così and Lino Welfare (#881).

As a side-effect, I changed the output format of the fab summary command to be shorter. On my machine it is now:

========= ========================================== ========= ========================
 Project   URL                                        Version   doctrees
--------- ------------------------------------------ --------- ------------------------
 atelier   http://atelier.lino-framework.org          1.0.0     docs
 cd        https://github.com/lsaffre/commondata      0.0.1     docs
 be        https://github.com/lsaffre/commondata-be   0.0.1
 ee        https://github.com/lsaffre/commondata-ee   0.0.1
 lino      http://www.lino-framework.org              1.7.0     docs
 xl        http://www.lino-framework.org              1.0.0     docs
 noi       http://noi.lino-framework.org              0.0.2     docs
 cosi      http://cosi.lino-framework.org             0.0.2     docs
 welfare   http://welfare.lino-framework.org          1.1.26    docs, docs_de, docs_fr
 presto    http://presto.lino-framework.org           0.0.1     docs
 voga      http://voga.lino-framework.org             0.0.4     docs
 ext6      http://www.lino-framework.org              0.0.1     docs
 patrols   http://patrols.lino-framework.org          0.0.2     docs
 logos     http://logos.lino-framework.org            0.0.1     docs
 dblog     None                                                 docs
 blog      None                                                 docs
 jsvv      None                                                 docs
 lucde     None                                                 docs
 lucet     None                                                 docs
 vv        None                                                 docs
========= ========================================== ========= ========================

Generating invoices for a given course

I fixed #917. Until now, in Lino Voga it was a bit complicated to generate invoices for a given course. Alexa had to set the state of all other courses to Draft. This was of course just a workaround. Now we have a good solution: lino_voga.lib.invoicing. This is an application-specific extension of lino_xl.lib.invoicing which, in short, adds a new field course to invoicing plans and a “basket” button to the Course model.

Side effect: When Lino generates invoices (by executing a plan), then these are now automatically registered.

A blocking bug

Alexa reported #918, her first blocking problem: a bug which made it impossible to create new enrolments (at least in courses whose max_places field was non-empty):

...
  File "/cosi/lino_xl.lib.courses/models.py", line 510, in run_from_ui
    msg = obj.get_confirm_veto(ar)
  File "/cosi/lino_xl.lib.courses/models.py", line 609, in get_confirm_veto
    free = self.course.get_free_places(self)
  File "/cosi/lino_xl.lib.courses/models.py", line 366, in get_free_places
    qs = PeriodEvents.active.add_filter(qs, rng)
  File "/lino/lino/modlib/system/choicelists.py", line 87, in add_filter
    Q(end_date__gte=obj.start_date))
  File "/site-packages/django/db/models/query.py", line 790, in filter
    return self._filter_or_exclude(False, *args, **kwargs)
  File "/site-packages/django/db/models/query.py", line 808, in _filter_or_exclude
    clone.query.add_q(Q(*args, **kwargs))
  File "/site-packages/django/db/models/sql/query.py", line 1243, in add_q
    clause, _ = self._add_q(q_object, self.used_aliases)
  File "/site-packages/django/db/models/sql/query.py", line 1263, in _add_q
    current_negated, allow_joins, split_subq)
  File "/site-packages/django/db/models/sql/query.py", line 1269, in _add_q
    allow_joins=allow_joins, split_subq=split_subq,
  File "/site-packages/django/db/models/sql/query.py", line 1203, in build_filter
    condition = self.build_lookup(lookups, col, value)
  File "/site-packages/django/db/models/sql/query.py", line 1099, in build_lookup
    return final_lookup(lhs, rhs)
  File "/site-packages/django/db/models/lookups.py", line 19, in __init__
    self.rhs = self.get_prep_lookup()
  File "/site-packages/django/db/models/lookups.py", line 57, in get_prep_lookup
    return self.lhs.output_field.get_prep_lookup(self.lookup_name, self.rhs)
  File "/site-packages/django/db/models/fields/__init__.py", line 744, in get_prep_lookup
    return self.get_prep_value(value)
  File "/site-packages/django/db/models/fields/__init__.py", line 1296, in get_prep_value
    return self.to_python(value)
  File "/site-packages/django/db/models/fields/__init__.py", line 1260, in to_python
    parsed = parse_date(value)
  File "/site-packages/django/utils/dateparse.py", line 60, in parse_date
    match = date_re.match(value)
TypeError: expected string or buffer