liminspace / django-mjml Goto Github PK
View Code? Open in Web Editor NEWThe simplest way to use MJML in Django templates.
License: MIT License
The simplest way to use MJML in Django templates.
License: MIT License
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?
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"
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"
Hello,
Django has released version 5.0, can we unlock django-mjml? https://docs.djangoproject.com/en/5.0/releases/5.0/
Thank you,
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.
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?
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)
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
`
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 ๐
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.
It would be amazing to have an admin widget for email templates stored in the database for a live preview.
I see 2 approaches:
Thoughts?
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!
Cannot use this anymore due to the (node:60710) [DEP0040] DeprecationWarning: The
punycode module is deprecated. Please use a userland alternative instead.
error. I tried working around this, but found out mjml
itself depends on libraries directly linked to punycode (see mjmlio/mjml#2843)
I have previously worked around this issue by downgrading node, but that's not really a fix; especially since basically a year later it's still the same.
Can we ignore stderr involving these lines to avoid crashing the wrapper?
(node:60953) [DEP0040] DeprecationWarning: The `punycode` module is deprecated. Please use a userland alternative instead.
(Use `node --trace-deprecation ...` to show where the warning was created)
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
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
Line 39 in 9dd6bf1
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
Hey I love to use this package in our project, but adding Node as a runtime dependency has me a bit scared. Are you interested in adding https://github.com/mgd020/mjml-python as an optional backend? It just wraps https://github.com/jdrouet/mrml (the Rust version of MJML) so no extra runtime dependency needed. Thanks!
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
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
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.
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!
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!)
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>
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.
BlockNotFound
block with name 'html' does not exist
It's the new LTS and I would like to upgrade.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.