Giter Site home page Giter Site logo

django-mjml's Introduction

test

pypi

Django + MJML

django-mjml

The simplest way to use MJML in Django templates.

Installation

Requirements:

  • Django from 2.2 to 5.0
  • requests from 2.24.0 (only if you are going to use API HTTP-server for rendering)
  • mjml from 3.6.3 to 4.14.1

1. Install mjml.

Follow https://github.com/mjmlio/mjml#installation and https://documentation.mjml.io/#installation to get more info.

2. Install django-mjml. :

$ pip install django-mjml

If you want to use API HTTP-server you also need requests (at least version 2.24):

$ pip install django-mjml[requests]

To install development version use git+https://github.com/liminspace/django-mjml.git@main instead django-mjml.

3. Set up settings.py in your django project. :

INSTALLED_APPS = (
  ...,
  'mjml',
)

Usage

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

{% load mjml %}

{% mjml %}
    <mjml>
        <mj-body>
            <mj-section>
                <mj-column>
                    <mj-text>Hello world!</mj-text>
                </mj-column>
            </mj-section>
        </mj-body>
    </mjml>
{% endmjml %}

Advanced settings

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

cmd mode

This mode is very simple, slow and used by default.

Configure your Django:

MJML_BACKEND_MODE = 'cmd'
MJML_EXEC_CMD = 'mjml'

You can change MJML_EXEC_CMD and set path to executable mjml file, for example:

MJML_EXEC_CMD = '/home/user/node_modules/.bin/mjml'

Also you can pass addition cmd arguments, for example:

MJML_EXEC_CMD = ['node_modules/.bin/mjml', '--config.minify', 'true', '--config.validationLevel', 'strict']

Once you have a working installation, you can skip the sanity check on startup to speed things up:

MJML_CHECK_CMD_ON_STARTUP = False

tcpserver mode

This mode is faster than cmd but it needs the MJML TCP-Server.

Configure your Django:

MJML_BACKEND_MODE = 'tcpserver'
MJML_TCPSERVERS = [
    ('127.0.0.1', 28101),  # the host and port of MJML TCP-Server
]

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

MJML_TCPSERVERS = [
    ('127.0.0.1', 28101),
    ('127.0.0.1', 28102),
    ('127.0.0.1', 28103),
]

httpserver mode

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

This mode is faster than cmd and a bit slower than 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.

Configure your Django:

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.

django-mjml's People

Contributors

liminspace avatar pyup-bot avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

django-mjml's Issues

tcpserver.js does not propagate mjml compile errors for mjml >= 3

mjml >= 3 returns its results in a {errors: [], html: ""} object. 086ffdc added support for mjml >= 3 by using the result.html for the output; however, the errors key in the mjml compilation result is never checked.

If mjml has a compile error, this behavior can result in blank emails going out, which is problematic. It would be much better to loudly throw an error if mjml fails to compile. Throwing errors would help to prevent that kind of reputation-harming email noise.

This alternative handleConnection function throws a RuntimeError if mjml encounters an mjml compile error:

function handleConnection(conn) {
    conn.setEncoding('utf8');
    conn.on('data', function(d) {
        var result, html;
        try {
            result = mjml.mjml2html(d.toString());
            if (result.errors.length) {
              html = JSON.stringify(result.errors, null, 2);
              conn.write('1');
            } else {
              html = result.html;
              conn.write('0');
            }
        } catch (err) {
            html = err.message;
            conn.write('1');
        }
        conn.write(('000000000' + Buffer.byteLength(html).toString()).slice(-9));
        conn.write(html);
    });
    conn.once('close', function() {});
    conn.on('error', function(err) {});
}

If you wanted to preserve backwards compatibility to older versions of mjml as the current tcpserver.js has, this would need to be modified.

Custom backend feature

As I understand right now, this package supports only 3 MJML backends: cmd, tcpserver, and httpserver. Also, there is no way to extend this list because all variants are hardcoded.

https://github.com/liminspace/django-mjml/blob/master/mjml/tools.py#L156

I think it would be very nice to have a custom option to pass any path to some callable. This way, anyone can build their backend if needed.

For example:

# settings.py
MJML_BACKEND_MODE = 'myproject.mjml.backends.custom_renderer'

# myproject.mjml.backends.py
def custom_renderer(mjml):
    return requests.get('http://mjml-server.cluster.local', json={'mjml': mjml}).json()['html']

Now rendering part can be easily delegated anywhere we want.

I can create pull request, but I think I will need some help with testing ๐Ÿ˜„

Allow `check_mjml_command()` to be skipped on app start

Creating this issue in response to performance issues observed when the mjml app was included in installed apps.

We observed a slowdown of several seconds for app starts, restarts/reloads, and test runs, due to the check_mjml_command() invocation in MJMLConfig.ready(). As best as we can tell there is currently no way to avoid this performance hit on every app start/test run/reload.

Once a server has been configured in a stable environment, the administrator should have some level of control over whether this command is invoked every time a django process starts.

Our suggestion is to include a simple boolean setting (MJML_SKIP_CMD_CHECK?) which will allow an administrator to have fine-grained control over this startup behavior.

(PS: Thanks for creating this great library!)

Message in stderr is breaking django-mjml

Mjml v4.2.1 was released with a change that moves all messages from stdout to stderr to prevent these being mixed with the resulting html.
This is breaking django-mjml command here

if stderr:

For example, if you are still using v3 of mjml syntax, with version 4.2.1 you will get the following error in stderr:

MJML v3 syntax detected, migrating to MJML v4 syntax. Use mjml -m to get the migrated MJML.

This will make _mjml_render_by_cmd raise a RuntimeError

mj-include doesn't work

Importing other mj files like in this example doesn't work via the django implementation. Do we need to change the path?

<mjml>
  <mj-head>
    <mj-attributes>
      <mj-preview>Please confirm your e-mail</mj-preview>
      <mj-include path="components/css.mjml" />
    </mj-attributes>
  </mj-head>
...
      <mj-column>

        <mj-include path="components/header.mjml" />

    ...
      <mj-include path="components/footer_social_media_links.mjml" />
    </mj-section>
    <mj-include path="components/footer_bottom.mjml" />

  </mj-body>
</mjml>

MJML tags not being recognized

I have installed [email protected] as a global package using npm and getting these errors when running the project.

lib/python3.7/site-packages/mjml/tools.py", line 49, in _mjml_render_by_cmd
    raise RuntimeError('MJML stderr is not empty: {}.'.format(force_str(stderr))) ******
*****
Element mj-section doesn't exist or is not registered
Element mj-text doesn't exist or is not registered
Element mj-body doesn't exist or is not registered

Its working fine if I use cmd to directly convert a mjml file. mjml test.mjml -o test.html produces output without Element mj-text doesn't exist or is not registered errors.

Packages:
python_version = "3.7.5"
django-mjml = "==0.10.1"
Django = "==2.2.13"

MJML server received too many data error firing and not sure why

Hi. I'm not sure if this is due to me running python3 and the latest Django but when i try and send an email the following lines are triggered in tcpserver.js

} else if (total_data.length > data_size) {
            result = 'MJML server received too many data';
            conn.write('1');
        } else {

I'm guessing the check here is simply to see if data sizes match? I was racking my brains as to how to resolve this but couldnt come up with anything. So i have just commented out that condition and the emails are now sending.

So firstly - have I created a bigger issue by letting this error go unchecked? And secondly, do you have any ideas how I should fix this?

Thanks!

MJML compiler in TCP mode failing in CI on bitbucket

I'm having some issues getting our CI builds to pass using MJML-django in TCP mode as the renderer. It was working and then quite consistently when we started rendering more emails they started failing:

RuntimeError: MJML compile error (via MJML TCP server): no working server
Number of servers: 1
Timeouts: 0

I'm a bit baffled by the error. It appears that there are working servers but the compilation step is failing immediately.

Bitbucket YAML looks as follows for docker config:

image: python:3.6-jessie

definitions:
  services:
    mysql:
      image: mysql:5.7
      environment:
        MYSQL_DATABASE: pipelines
        MYSQL_ROOT_PASSWORD: *********
    redis:
      image: redis
    mjml:
      image: liminspace/mjml-tcpserver:0.10.2
      environment:
        HOST: "0.0.0.0"
        PORT: "28102"
      expose:
          - "28102"
      ports:
          - "28102:28102"

Getting timeouts since 0.8.0

Hi, i've upgraded django-mjml to version to 0.8.0 while trying to fix the issue I had. But now i'm getting timeouts. I don't really know why. Im using the docker-compose image.

MJML compile error (via MJML TCP server): no working server
Number of servers: 1
Timeouts: 1

Setting necessary for allowing custom authentication/transport for http backend

Current architecture does not allow for any other authentication than 'static' HTTPBasicAuth user+password. It is impossible to authenticate MJML server via API Keys, OAuth2, custom auth header without monkey-patching or forking the code.

Simplest idea: there could be provided a new MJML_HTTP_TRANSPORT setting with a string being dotted module path to a transport function, accepting kwargs: url, auth, data, headers, timeouts.
Such a config would allow users to procure custom authentication inside and call requests.post or any other transport from user's code. If not provided, requests.post could be used as-is.

Using dotted path string will avoid importing this function in settings.py - this follows typical django pattern. The transport function can be imported lazily before use through django.utils.module_loading.import_string

How to use with blocks?

Not sure if I'm using this wrong or if this is simply unsupported, I'm trying to use the django template extensioning...

<mj-body background-color="#eee">

    {% include './components/mjml_header.mjml' %}

    {% block outerhero %}
    <mj-section css-class="wrapper-title" background-color="#616E8E">
      <mj-column>
        <mj-text>
          <h1>
            {% block hero %}
            {% endblock %}
          </h1>
        </mj-text>
      </mj-column>
    </mj-section>
    {% endblock %}
    {% block content %}
    {% endblock %}

    {% include './components/mjml_contact.mjml' %}

    {% include './components/mjml_footer.mjml' %}

  </mj-body>

When I try and put mjml in an area like outerhero the mjml is just coming through as tags. Suggestions?

Update to support Django 4.0

Hey @liminspace, I'd like to use this with Django 4.0. Is there any particular reason you have it set to use versions of django < 3.3?

If I opened up a PR to update it, would you be interested in helping me get it merged in?

Thanks,
-Paul

Dont start the server with django-mjml.

Hi,

today i begin to use the django-mjml. Following the instructions I can't start the server. I detected a problem in app.py on the function check_mjml_command(). The issues is around the test, the test_mjml is on version 3 of mjml, and with the new version 4 can pass the test and dont start the server. If I change the string test_mjml to '' the test pass without problem.

With this little change I can start the server without problems.

tcpserver.js removed in 1.0 release but is still in docs

We upgraded from 0.9 to 1.0 and our supervisor script to start the mjml tcpserver failed. The node/ directory doesn't exist in the 1.0 release. Was this on purpose or a mistake? Is the tcpserver.js script available in a new way?

Mock httpserver

I added the httpserver config, but running my test fails because of the RuntimeError: MJML compile error (via MJML HTTP server):

Is there a way to mock the MJML calls? I'm doing a mock on the actual email sending

with patch("requests.post") as mock_post:
     mock_post.return_value(ok=True, status=status.HTTP_201_CREATED)

django.core.exceptions.ImproperlyConfigured: Problem to run command "mjml -i -s"

l have mjml installed propely, but when l add it in my installed app in django it returns the following error below. and if I re-run pip install django-mjml it returns requirements already satisfied. where do l went wrong?

`

(venv) c:\N2G-PROJECT\my_site>py manage.py runserver
Watching for file changes with StatReloader
Exception in thread django-main-thread:
Traceback (most recent call last):
File "C:\N2G-PROJECT\venv\Lib\site-packages\mjml\tools.py", line 30, in _mjml_render_by_cmd
p = subprocess.Popen(cmd_args, stdin=subprocess.PIPE, stdout=stdout_tmp_f, stderr=subprocess.PIPE)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\sb\AppData\Local\Programs\Python\Python311\Lib\subprocess.py", line 1026, in init
self._execute_child(args, executable, preexec_fn, close_fds,
File "C:\Users\sb\AppData\Local\Programs\Python\Python311\Lib\subprocess.py", line 1538, in _execute_child
hp, ht, pid, tid = _winapi.CreateProcess(executable, args,
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
FileNotFoundError: [WinError 2] The system cannot find the file specified

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
File "C:\N2G-PROJECT\venv\Lib\site-packages\mjml\apps.py", line 10, in check_mjml_command
html = mjml_render(
^^^^^^^^^^^^
File "C:\N2G-PROJECT\venv\Lib\site-packages\mjml\tools.py", line 158, in mjml_render
return _mjml_render_by_cmd(mjml_source)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\N2G-PROJECT\venv\Lib\site-packages\mjml\tools.py", line 34, in _mjml_render_by_cmd
raise RuntimeError(
RuntimeError: Problem to run command "mjml -i -s"
[WinError 2] The system cannot find the file specified
Check that mjml is installed and allow permissions to execute.
See https://github.com/mjmlio/mjml#installation

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
File "C:\N2G-PROJECT\venv\Lib\site-packages\mjml\tools.py", line 30, in _mjml_render_by_cmd
p = subprocess.Popen(cmd_args, stdin=subprocess.PIPE, stdout=stdout_tmp_f, stderr=subprocess.PIPE)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\sb\AppData\Local\Programs\Python\Python311\Lib\subprocess.py", line 1026, in init
self._execute_child(args, executable, preexec_fn, close_fds,
File "C:\Users\sb\AppData\Local\Programs\Python\Python311\Lib\subprocess.py", line 1538, in _execute_child
hp, ht, pid, tid = _winapi.CreateProcess(executable, args,
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
FileNotFoundError: [WinError 2] The system cannot find the file specified

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
File "C:\N2G-PROJECT\venv\Lib\site-packages\mjml\apps.py", line 17, in check_mjml_command
html = mjml_render(
^^^^^^^^^^^^
File "C:\N2G-PROJECT\venv\Lib\site-packages\mjml\tools.py", line 158, in mjml_render
return _mjml_render_by_cmd(mjml_source)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\N2G-PROJECT\venv\Lib\site-packages\mjml\tools.py", line 34, in _mjml_render_by_cmd
raise RuntimeError(
RuntimeError: Problem to run command "mjml -i -s"
[WinError 2] The system cannot find the file specified
Check that mjml is installed and allow permissions to execute.
See https://github.com/mjmlio/mjml#installation

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
File "C:\Users\sb\AppData\Local\Programs\Python\Python311\Lib\threading.py", line 1038, in _bootstrap_inner
self.run()
File "C:\Users\sb\AppData\Local\Programs\Python\Python311\Lib\threading.py", line 975, in run
self._target(*self._args, **self.kwargs)
File "C:\N2G-PROJECT\venv\Lib\site-packages\django\utils\autoreload.py", line 64, in wrapper
fn(*args, **kwargs)
File "C:\N2G-PROJECT\venv\Lib\site-packages\django\core\management\commands\runserver.py", line 125, in inner_run
autoreload.raise_last_exception()
File "C:\N2G-PROJECT\venv\Lib\site-packages\django\utils\autoreload.py", line 87, in raise_last_exception
raise exception[1]
File "C:\N2G-PROJECT\venv\Lib\site-packages\django\core\management_init
.py", line 394, in execute
autoreload.check_errors(django.setup)()
File "C:\N2G-PROJECT\venv\Lib\site-packages\django\utils\autoreload.py", line 64, in wrapper
fn(*args, **kwargs)
File "C:\N2G-PROJECT\venv\Lib\site-packages\django_init
.py", line 24, in setup
apps.populate(settings.INSTALLED_APPS)
File "C:\N2G-PROJECT\venv\Lib\site-packages\django\apps\registry.py", line 124, in populate
app_config.ready()
File "C:\N2G-PROJECT\venv\Lib\site-packages\mjml\apps.py", line 37, in ready
check_mjml_command()
File "C:\N2G-PROJECT\venv\Lib\site-packages\mjml\apps.py", line 23, in check_mjml_command
raise ImproperlyConfigured(e) from e
django.core.exceptions.ImproperlyConfigured: Problem to run command "mjml -i -s"
[WinError 2] The system cannot find the file specified
Check that mjml is installed and allow permissions to execute.
See https://github.com/mjmlio/mjml#installation

`

Django Admin Widget

It would be amazing to have an admin widget for email templates stored in the database for a live preview.

I see 2 approaches:

  • Auto send a template render request (to a provided URL) on an interval or manually by pressing a render template button.
  • Since the MJML was written in nodeJS, I would think it's possible to load this package as JavaScript in a browser for live preview functionality.

Thoughts?

MJML compile error (via MJML TCP server): no working server

Python: 3.9
django-mjml: 0.11.0
mjml: 4.10.1

I saw a similar one #119 but that is not solving my issue.

docker-compose.yml:

service:
  ...
  mjml:
    image: liminspace/mjml-tcpserver:0.11.0

settings.py:

MJML_BACKEND_MODE = 'tcpserver'
MJML_TCPSERVERS = [
    ('mjml', 28101),
]

I also tried different settings mentioned in the README none of them works. I can see the image is running on 28101 in docker.

Possibility to render *.mjml templates to *.html templates

Rendering templates by a template tag every time the mjml template is accessed might require too much computing power or network bandwidth, especially, if you are trying to send a mass email referring each user personally.

My proposal is to add a management command that goes through the template folder and searches for *.mjml files with whatever Django template variables and template tags. Then the command would render *.html template files with the same template variables and template tags and place them in the same folders.

Django email sending commands could use those Django HTML templates to send emails, whereas the mjml conversion would happen only when there are changes in the template structure.

Is there a way to compile to HTML and Cache Results

Would be great, if there isn't already, to have a way to translate MJML to HTML and keep the results as a typical HTML template (ie cache the compiled results) which would be an obvious performance gain for production.

Perhaps a management command
python manage.py mjml-compile

which finds all .mjml templates and produces an .html template or finds all mjml template tags and compiles a pure HTML equivalent template.

Point is there is little reason in production to have to translate in realtime and from the docs there doesn't seem to be a consideration for this.

Nice work / project!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.