Skip to content

Commit

Permalink
Release 0.9.0
Browse files Browse the repository at this point in the history
* Added supporting Django v3.0
* Added supporting render http-server (including official MJML API https://mjml.io/api)
* Added Python 3.8 in tests
* Added MJML 4.5.1 in tests
* Upgraded MJML to 4.5.1 in dockerfile
* Upgraded Node to v12 for tcp-server
* Reorganized tests
* Updated docs
  • Loading branch information
liminspace authored Dec 24, 2019
1 parent 2815a90 commit 6d15d86
Show file tree
Hide file tree
Showing 22 changed files with 698 additions and 407 deletions.
27 changes: 25 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,59 +7,82 @@ python:
- "3.5"
- "3.6"
- "3.7"
- "3.8"

env:
- DJANGO_VERSION="<1.9" MJML_VERSION=3.3.5
- DJANGO_VERSION="<1.9" MJML_VERSION=4.3.1
- DJANGO_VERSION="<1.9" MJML_VERSION=4.4.0
- DJANGO_VERSION="<1.9" MJML_VERSION=4.5.1

- DJANGO_VERSION="<1.10" MJML_VERSION=3.3.5
- DJANGO_VERSION="<1.10" MJML_VERSION=4.3.1
- DJANGO_VERSION="<1.10" MJML_VERSION=4.4.0
- DJANGO_VERSION="<1.10" MJML_VERSION=4.1.1

- DJANGO_VERSION="<1.11" MJML_VERSION=3.3.5
- DJANGO_VERSION="<1.11" MJML_VERSION=4.3.1
- DJANGO_VERSION="<1.11" MJML_VERSION=4.4.0
- DJANGO_VERSION="<1.11" MJML_VERSION=4.5.1

- DJANGO_VERSION="<1.12" MJML_VERSION=3.3.5
- DJANGO_VERSION="<1.12" MJML_VERSION=4.3.1
- DJANGO_VERSION="<1.12" MJML_VERSION=4.4.0
- DJANGO_VERSION="<1.12" MJML_VERSION=4.5.1

- DJANGO_VERSION="<2.1" MJML_VERSION=3.3.5
- DJANGO_VERSION="<2.1" MJML_VERSION=4.3.1
- DJANGO_VERSION="<2.1" MJML_VERSION=4.4.0
- DJANGO_VERSION="<2.1" MJML_VERSION=4.5.1

- DJANGO_VERSION="<2.2" MJML_VERSION=3.3.5
- DJANGO_VERSION="<2.2" MJML_VERSION=4.3.1
- DJANGO_VERSION="<2.2" MJML_VERSION=4.4.0
- DJANGO_VERSION="<2.2" MJML_VERSION=4.5.1

- DJANGO_VERSION="<2.3" MJML_VERSION=3.3.5
- DJANGO_VERSION="<2.3" MJML_VERSION=4.3.1
- DJANGO_VERSION="<2.3" MJML_VERSION=4.4.0
- DJANGO_VERSION="<2.3" MJML_VERSION=4.5.1

- DJANGO_VERSION="<3.1" MJML_VERSION=3.3.5
- DJANGO_VERSION="<3.1" MJML_VERSION=4.3.1
- DJANGO_VERSION="<3.1" MJML_VERSION=4.4.0
- DJANGO_VERSION="<3.1" MJML_VERSION=4.5.1

matrix:
exclude:
- { python: "2.7", env: DJANGO_VERSION="<2.1" MJML_VERSION=3.3.5 }
- { python: "2.7", env: DJANGO_VERSION="<2.1" MJML_VERSION=4.3.1 }
- { python: "2.7", env: DJANGO_VERSION="<2.1" MJML_VERSION=4.4.0 }
- { python: "2.7", env: DJANGO_VERSION="<2.1" MJML_VERSION=4.5.1 }

- { python: "2.7", env: DJANGO_VERSION="<2.2" MJML_VERSION=3.3.5 }
- { python: "2.7", env: DJANGO_VERSION="<2.2" MJML_VERSION=4.3.1 }
- { python: "2.7", env: DJANGO_VERSION="<2.2" MJML_VERSION=4.4.0 }
- { python: "2.7", env: DJANGO_VERSION="<2.2" MJML_VERSION=4.5.1 }

- { python: "2.7", env: DJANGO_VERSION="<2.3" MJML_VERSION=3.3.5 }
- { python: "2.7", env: DJANGO_VERSION="<2.3" MJML_VERSION=4.3.1 }
- { python: "2.7", env: DJANGO_VERSION="<2.3" MJML_VERSION=4.4.0 }
- { python: "2.7", env: DJANGO_VERSION="<2.3" MJML_VERSION=4.5.1 }

- { python: "2.7", env: DJANGO_VERSION="<3.1" MJML_VERSION=3.3.5 }
- { python: "2.7", env: DJANGO_VERSION="<3.1" MJML_VERSION=4.3.1 }
- { python: "2.7", env: DJANGO_VERSION="<3.1" MJML_VERSION=4.4.0 }
- { python: "2.7", env: DJANGO_VERSION="<3.1" MJML_VERSION=4.5.1 }

before_install:
- . $HOME/.nvm/nvm.sh
- nvm install 8
- nvm use 8
- nvm install 12
- nvm use 12

install:
- npm install -g mjml-http-server@0.0.3
- npm install mjml@$MJML_VERSION
- node_modules/.bin/mjml --version
- pip install "Django$DJANGO_VERSION"
- pip install "requests>=2.19.0,<=2.23.0"

script:
- python tools.py test
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
0.9.0 (2019-12-24)
==================
* Added supporting Django v3.0
* Added supporting render http-server (including official MJML API https://mjml.io/api)
* Added Python 3.8 in tests
* Added MJML 4.5.1 in tests
* Upgraded MJML to 4.5.1 in dockerfile
* Upgraded Node to v12 for tcp-server
* Reorganized tests
* Updated docs


0.8.0 (2019-07-29)
==================
* Fixed a trouble with unicode
Expand Down
44 changes: 31 additions & 13 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -24,25 +24,23 @@ Installation

**Requirements:**

* Django v1.8+
* mjml v2.3+ (under node v8)
* Django from 1.8 to 3.0
* requests from 2.19.0 (only if you are going to use API HTTP-server for rendering)
* mjml from 2.3 to 4.5.1

**\1\. Install** ``mjml``.

See https://github.com/mjmlio/mjml#installation and https://mjml.io/documentation/#installation

**\2\. Install** ``django-mjml``.

* Via pip::
**\2\. Install** ``django-mjml``. ::

$ pip install django-mjml

* Via setuptools::

$ easy_install django-mjml
To install development version use ``git+https://github.com/liminspace/django-mjml.git@develop`` instead ``django-mjml``.

If you want to use API HTTP-server, install ``requests``::

For install development version use ``git+https://github.com/liminspace/django-mjml.git@develop`` instead ``django-mjml``.
$ pip install requests

**\3\. Set up** ``settings.py`` **in your django project.** ::

Expand All @@ -56,7 +54,7 @@ See https://github.com/mjmlio/mjml#installation and https://mjml.io/documentatio
Usage
-----

Load ``mjml`` in your django template and use ``mjml`` tag that will compile mjml to html::
Load ``mjml`` in your django template and use ``mjml`` tag that will compile MJML to HTML::

{% load mjml %}

Expand All @@ -79,7 +77,7 @@ Load ``mjml`` in your django template and use ``mjml`` tag that will compile mjm
Advanced settings
-----------------

There are two backend modes for compiling: ``cmd`` and ``tcpserver``.
There are two backend modes for compiling: ``cmd``, ``tcpserver`` and ``httpserver``.

**cmd mode**

Expand All @@ -102,14 +100,14 @@ Once you have a working installation, you can skip the sanity check on startup t

**tcpserver mode**

This mode is faster than ``cmd`` but it needs run a server process in background. ::
This mode is faster than ``cmd`` but it needs run a separated server process which will render templates. ::

MJML_BACKEND_MODE = 'tcpserver'
MJML_TCPSERVERS = [
('127.0.0.1', 28101), # host and port
]

You can set several servers and it will be used random one::
You can set several servers and a random one will be used::

MJML_TCPSERVERS = [
('127.0.0.1', 28101),
Expand Down Expand Up @@ -161,3 +159,23 @@ Or you can use docker-compose::
ports:
- "28102:28102"


**httpserver mode**

don't forget to install ``requests`` to use this mode.

This mode is faster than ``cmd`` and similar to ``tcpserver`` but you can use official MJML API https://mjml.io/api
or run your own HTTP-server (for example https://github.com/danihodovic/mjml-server) to render templates. ::

MJML_BACKEND_MODE = 'httpserver'
MJML_HTTPSERVERS = [
{
'URL': 'https://api.mjml.io/v1/render', # official MJML API
'HTTP_AUTH': ('<Application ID>', '<Secret Key>'),
},
{
'URL': 'http://127.0.0.1:38101/v1/render', # your own HTTP-server
},
]

You can set one or more servers and a random one will be used.
4 changes: 2 additions & 2 deletions docker/mjml-tcpserver/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
FROM node:8-stretch
FROM node:12-stretch

ARG MJML_VERSION=4.4.0
ARG MJML_VERSION=4.5.1
ARG APP_DIR=/app

RUN npm install mjml@${MJML_VERSION}
Expand Down
2 changes: 1 addition & 1 deletion mjml/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
__version__ = '0.8.0'
__version__ = '0.9.0'

default_app_config = 'mjml.apps.MJMLConfig'
5 changes: 3 additions & 2 deletions mjml/apps.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from __future__ import absolute_import
from django.apps import AppConfig
from django.core.exceptions import ImproperlyConfigured
from .tools import mjml_render
from . import settings as mjml_settings
from mjml.tools import mjml_render
from mjml import settings as mjml_settings


def check_mjml_command():
Expand Down
18 changes: 17 additions & 1 deletion mjml/settings.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from __future__ import absolute_import
from django.conf import settings

MJML_BACKEND_MODE = getattr(settings, 'MJML_BACKEND_MODE', 'cmd')
assert MJML_BACKEND_MODE in ('cmd', 'tcpserver')
assert MJML_BACKEND_MODE in ('cmd', 'tcpserver', 'httpserver')

# cmd backend mode configs
MJML_EXEC_CMD = getattr(settings, 'MJML_EXEC_CMD', 'mjml')
Expand All @@ -12,3 +13,18 @@
assert isinstance(MJML_TCPSERVERS, (list, tuple))
for t in MJML_TCPSERVERS:
assert isinstance(t, (list, tuple)) and len(t) == 2 and isinstance(t[0], str) and isinstance(t[1], int)

# httpserver backend mode configs
MJML_HTTPSERVERS = getattr(settings, 'MJML_HTTPSERVERS', [{
'URL': 'https://api.mjml.io/v1/render',
'HTTP_AUTH': None, # None (default) or ('login', 'password')
}])
assert isinstance(MJML_HTTPSERVERS, (list, tuple))
for t in MJML_HTTPSERVERS:
assert isinstance(t, dict)
assert 'URL' in t and isinstance(t['URL'], str)
if 'HTTP_AUTH' in t:
http_auth = t['HTTP_AUTH']
assert isinstance(http_auth, (type(None), list, tuple))
if http_auth is not None:
assert len(http_auth) == 2 and isinstance(http_auth[0], str) and isinstance(http_auth[1], str)
3 changes: 2 additions & 1 deletion mjml/templatetags/mjml.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import absolute_import
from django import template
from ..tools import mjml_render
from mjml.tools import mjml_render


register = template.Library()
Expand Down
64 changes: 63 additions & 1 deletion mjml/tools.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
# coding=utf-8
from __future__ import absolute_import
import copy
import json
try:
from json import JSONDecodeError
except ImportError:
JSONDecodeError = ValueError
import socket
import random
import subprocess
import tempfile
import requests
import requests.auth
from django.utils.encoding import force_str, force_bytes
from . import settings as mjml_settings
from mjml import settings as mjml_settings


_cache = {}
Expand Down Expand Up @@ -93,9 +102,62 @@ def _mjml_render_by_tcpserver(mjml_code):
)


def _mjml_render_by_httpserver(mjml_code):
if len(mjml_settings.MJML_HTTPSERVERS) > 1:
servers = list(mjml_settings.MJML_HTTPSERVERS)[:]
random.shuffle(servers)
else:
servers = mjml_settings.MJML_HTTPSERVERS

timeouts = 0
for server_conf in servers:
http_auth = server_conf.get('HTTP_AUTH')
auth = requests.auth.HTTPBasicAuth(*http_auth) if http_auth else None

try:
response = requests.post(
url=server_conf['URL'],
auth=auth,
data=force_bytes(json.dumps({'mjml': mjml_code})),
headers={'Content-Type': 'application/json'},
timeout=25,
)
except requests.exceptions.Timeout:
timeouts += 1
continue

try:
data = response.json()
except (TypeError, JSONDecodeError):
data = {}

if response.status_code == 200:
errors = data.get('errors')
if errors:
msgs = ['Line: {e[line]} Tag: {e[tagName]} Message: {e[message]}'.format(e=e) for e in errors]
raise RuntimeError('MJML compile error (via MJML HTTP server): {}'.format('\n'.join(msgs)))

return force_str(data['html'])
else:
msg = '[code={}, request_id={}] {}'.format(
response.status_code,
data.get('request_id', ''),
data.get('message', 'Unknown error.'),
)
raise RuntimeError('MJML compile error (via MJML HTTP server): {}'.format(msg))

raise RuntimeError(
('MJML compile error (via MJML HTTP server): no working server\n'
'Number of servers: {total}\n'
'Timeouts: {timeouts}').format(total=len(servers), timeouts=timeouts)
)


def mjml_render(mjml_code):
if mjml_settings.MJML_BACKEND_MODE == 'cmd':
return _mjml_render_by_cmd(mjml_code)
elif mjml_settings.MJML_BACKEND_MODE == 'tcpserver':
return _mjml_render_by_tcpserver(mjml_code)
elif mjml_settings.MJML_BACKEND_MODE == 'httpserver':
return _mjml_render_by_httpserver(mjml_code)
raise RuntimeError('Invalid settings.MJML_BACKEND_MODE "{}"'.format(mjml_settings.MJML_BACKEND_MODE))
11 changes: 7 additions & 4 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
wheel==0.33.4
twine==1.13.0
wheel==0.33.6
twine==1.15.0; python_version < "3"
twine==3.1.1; python >= "3"

django<1.12; python_version < "3"
django>=1.8,<2.3; python_version >= "3"
django>=1.8,<3.1; python_version >= "3"
coverage==4.5.4
six==1.12.0
six==1.13.0
ndg-httpsclient>=0.5.1; python_version < "3"
mock<3.1; python_version < "3"
requests>=2.19.0
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
zip_safe=False, # because include static
platforms=['OS Independent'],
install_requires=[
'django>=1.8,<2.3',
'django>=1.8,<3.1',
],
keywords=[
'django', 'mjml', 'django-mjml', 'email', 'layout', 'template', 'templatetag',
Expand Down
File renamed without changes.
14 changes: 11 additions & 3 deletions tests/settings.py → testprj/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@

INSTALLED_APPS = (
'mjml',
'tests',
'testprj',
)

MIDDLEWARE_CLASSES = ()

ROOT_URLCONF = 'tests.urls'
ROOT_URLCONF = 'testprj.urls'

WSGI_APPLICATION = 'tests.wsgi.application'
WSGI_APPLICATION = 'testprj.wsgi.application'

DATABASES = {
'default': {
Expand Down Expand Up @@ -56,5 +56,13 @@
('127.0.0.1', 28102),
('127.0.0.1', 28103),
)
MJML_HTTPSERVERS = (
{
'URL': 'http://127.0.0.1:38101/v1/render',
},
{
'URL': 'http://127.0.0.1:38102/v1/render',
},
)

DEFAULT_MJML_VERSION = 4
Loading

0 comments on commit 6d15d86

Please sign in to comment.