*** WIP ***
Migrating the complete_template script stuff to JinjaService. Having trouble with the tools stuff. Pulled back to spot where test pass using CompleteTemplate
This commit is contained in:
parent
da9b902c19
commit
0122029e30
|
@ -24,9 +24,7 @@ def render_markdown(data, template):
|
||||||
data structure. Useful for folks that are building these markdown templates.
|
data structure. Useful for folks that are building these markdown templates.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# template = Template(template)
|
|
||||||
data = json.loads(data)
|
data = json.loads(data)
|
||||||
# return template.render(**data)
|
|
||||||
return JinjaService.get_content(template, data)
|
return JinjaService.get_content(template, data)
|
||||||
except UndefinedError as ue:
|
except UndefinedError as ue:
|
||||||
raise ApiError(code="undefined_field", message=ue.message)
|
raise ApiError(code="undefined_field", message=ue.message)
|
||||||
|
@ -42,6 +40,12 @@ def render_docx():
|
||||||
try:
|
try:
|
||||||
file = connexion.request.files['file']
|
file = connexion.request.files['file']
|
||||||
data = connexion.request.form['data']
|
data = connexion.request.form['data']
|
||||||
|
# TODO: This bypasses the Jinja service and uses complete_template script
|
||||||
|
# content = JinjaService().get_word_document_content(file, json.loads(data), image_file_data=None)
|
||||||
|
#
|
||||||
|
# target_stream = io.BytesIO()
|
||||||
|
# content.save(target_stream)
|
||||||
|
# target_stream.seek(0) # move to the beginning of the stream.
|
||||||
target_stream = CompleteTemplate().make_template(file, json.loads(data))
|
target_stream = CompleteTemplate().make_template(file, json.loads(data))
|
||||||
return send_file(
|
return send_file(
|
||||||
io.BytesIO(target_stream.read()),
|
io.BytesIO(target_stream.read()),
|
||||||
|
|
|
@ -12,6 +12,7 @@ from crc.models.file import CONTENT_TYPES, FileModel, FileDataModel
|
||||||
from crc.models.workflow import WorkflowModel
|
from crc.models.workflow import WorkflowModel
|
||||||
from crc.scripts.script import Script
|
from crc.scripts.script import Script
|
||||||
from crc.services.file_service import FileService
|
from crc.services.file_service import FileService
|
||||||
|
from crc.services.jinja_service import JinjaService
|
||||||
from crc.services.workflow_processor import WorkflowProcessor
|
from crc.services.workflow_processor import WorkflowProcessor
|
||||||
|
|
||||||
|
|
||||||
|
@ -108,10 +109,10 @@ Takes two arguments:
|
||||||
|
|
||||||
return image_file_data
|
return image_file_data
|
||||||
|
|
||||||
def make_template(self, binary_stream, context, image_file_data=None):
|
def make_template(self, binary_stream, task_data, image_file_data=None):
|
||||||
# TODO: Move this into the jinja_service?
|
# TODO: Move this into the jinja_service?
|
||||||
doc = DocxTemplate(binary_stream)
|
doc = DocxTemplate(binary_stream)
|
||||||
doc_context = copy.deepcopy(context)
|
doc_context = copy.deepcopy(task_data)
|
||||||
doc_context = self.rich_text_update(doc_context)
|
doc_context = self.rich_text_update(doc_context)
|
||||||
doc_context = self.append_images(doc, doc_context, image_file_data)
|
doc_context = self.append_images(doc, doc_context, image_file_data)
|
||||||
jinja_env = jinja2.Environment(autoescape=True)
|
jinja_env = jinja2.Environment(autoescape=True)
|
||||||
|
@ -119,6 +120,15 @@ Takes two arguments:
|
||||||
doc.render(doc_context, jinja_env)
|
doc.render(doc_context, jinja_env)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print (e)
|
print (e)
|
||||||
|
# try:
|
||||||
|
# doc = JinjaService.get_word_document_content(binary_stream, task_data, image_file_data)
|
||||||
|
# except jinja2.exceptions.TemplateError as te:
|
||||||
|
# # raise ApiError.from_task(code="bad_template",
|
||||||
|
# # message="There was a problem compiling your template.",
|
||||||
|
# # task=self.task)
|
||||||
|
# print(te)
|
||||||
|
# except TypeError as te:
|
||||||
|
# print(te)
|
||||||
target_stream = BytesIO()
|
target_stream = BytesIO()
|
||||||
doc.save(target_stream)
|
doc.save(target_stream)
|
||||||
target_stream.seek(0) # move to the beginning of the stream.
|
target_stream.seek(0) # move to the beginning of the stream.
|
||||||
|
|
|
@ -1,5 +1,11 @@
|
||||||
|
from docxtpl import DocxTemplate, Listing, InlineImage
|
||||||
from jinja2 import Environment, DictLoader
|
from jinja2 import Environment, DictLoader
|
||||||
|
|
||||||
|
from docx.shared import Inches
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
|
import copy
|
||||||
|
|
||||||
|
|
||||||
class JinjaService:
|
class JinjaService:
|
||||||
"""Service for Jinja2 templates.
|
"""Service for Jinja2 templates.
|
||||||
|
@ -27,13 +33,52 @@ Cool Right?
|
||||||
templates['main_template'] = input_template
|
templates['main_template'] = input_template
|
||||||
jinja2_env = Environment(loader=DictLoader(templates))
|
jinja2_env = Environment(loader=DictLoader(templates))
|
||||||
|
|
||||||
try:
|
# We just make a call here and let any errors percolate up to the calling method
|
||||||
template = jinja2_env.get_template('main_template')
|
template = jinja2_env.get_template('main_template')
|
||||||
|
return template.render(**data)
|
||||||
|
|
||||||
except Exception:
|
@staticmethod
|
||||||
# TODO: Should we deal w/ specific exceptions here?
|
def get_word_document_content(binary_stream, task_data, image_file_data=None):
|
||||||
# i.e., the ones in workflow_service._process_documentation
|
doc = DocxTemplate(binary_stream)
|
||||||
raise
|
doc_context = copy.deepcopy(task_data)
|
||||||
|
doc_context = JinjaService().rich_text_update(doc_context)
|
||||||
|
# doc_context = JinjaService().append_images(doc, doc_context, image_file_data)
|
||||||
|
jinja2_env = Environment(autoescape=True)
|
||||||
|
return doc.render(doc_context, jinja2_env)
|
||||||
|
|
||||||
else:
|
def rich_text_update(self, context):
|
||||||
return template.render(**data)
|
"""This is a bit of a hack. If we find that /n characters exist in the data, we want
|
||||||
|
these to come out in the final document without requiring someone to predict it in the
|
||||||
|
template. Ideally we would use the 'RichText' feature of the python-docx library, but
|
||||||
|
that requires we both escape it here, and in the Docx template. There is a thing called
|
||||||
|
a 'listing' in python-docx library that only requires we use it on the way in, and the
|
||||||
|
template doesn't have to think about it. So running with that for now."""
|
||||||
|
# loop through the content, identify anything that has a newline character in it, and
|
||||||
|
# wrap that sucker in a 'listing' function.
|
||||||
|
if isinstance(context, dict):
|
||||||
|
for k, v in context.items():
|
||||||
|
context[k] = self.rich_text_update(v)
|
||||||
|
elif isinstance(context, list):
|
||||||
|
for i in range(len(context)):
|
||||||
|
context[i] = self.rich_text_update(context[i])
|
||||||
|
elif isinstance(context, str) and '\n' in context:
|
||||||
|
return Listing(context)
|
||||||
|
return context
|
||||||
|
|
||||||
|
def append_images(self, template, context, image_file_data):
|
||||||
|
context['images'] = {}
|
||||||
|
if image_file_data is not None:
|
||||||
|
for file_data_model in image_file_data:
|
||||||
|
fm = file_data_model.file_model
|
||||||
|
if fm is not None:
|
||||||
|
context['images'][fm.id] = {
|
||||||
|
'name': fm.name,
|
||||||
|
'url': '/v1.0/file/%s/data' % fm.id,
|
||||||
|
'image': self.make_image(file_data_model, template)
|
||||||
|
}
|
||||||
|
|
||||||
|
return context
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def make_image(file_data_model, template):
|
||||||
|
return InlineImage(template, BytesIO(file_data_model.data), width=Inches(6.5))
|
||||||
|
|
|
@ -16,9 +16,16 @@
|
||||||
<camunda:formData>
|
<camunda:formData>
|
||||||
<camunda:formField id="subject" label="Subject" type="string" />
|
<camunda:formField id="subject" label="Subject" type="string" />
|
||||||
<camunda:formField id="recipients" label="Recipients" type="string" />
|
<camunda:formField id="recipients" label="Recipients" type="string" />
|
||||||
<camunda:formField id="include_me" label="Included Template" type="string" />
|
<camunda:formField id="include_me" label="Included Template" type="string">
|
||||||
|
<camunda:properties>
|
||||||
|
<camunda:property id="Property_01o03tf" value="'My Value'" />
|
||||||
|
</camunda:properties>
|
||||||
|
</camunda:formField>
|
||||||
<camunda:formField id="name" label="Name" type="string" />
|
<camunda:formField id="name" label="Name" type="string" />
|
||||||
</camunda:formData>
|
</camunda:formData>
|
||||||
|
<camunda:properties>
|
||||||
|
<camunda:property name="my_extension" value="'My Value'" />
|
||||||
|
</camunda:properties>
|
||||||
</bpmn:extensionElements>
|
</bpmn:extensionElements>
|
||||||
<bpmn:incoming>Flow_0yh2coe</bpmn:incoming>
|
<bpmn:incoming>Flow_0yh2coe</bpmn:incoming>
|
||||||
<bpmn:outgoing>Flow_0ovknlg</bpmn:outgoing>
|
<bpmn:outgoing>Flow_0ovknlg</bpmn:outgoing>
|
||||||
|
@ -33,38 +40,35 @@ Thank you for supporting the cause!
|
||||||
{% include 'include_me' %}
|
{% include 'include_me' %}
|
||||||
|
|
||||||
|
|
||||||
Did we include the other template?
|
Did we include the other template?</bpmn:documentation>
|
||||||
|
|
||||||
</bpmn:documentation>
|
|
||||||
<bpmn:incoming>Flow_0ovknlg</bpmn:incoming>
|
<bpmn:incoming>Flow_0ovknlg</bpmn:incoming>
|
||||||
<bpmn:outgoing>Flow_103ct5a</bpmn:outgoing>
|
<bpmn:outgoing>Flow_103ct5a</bpmn:outgoing>
|
||||||
<bpmn:script>email_model = email(subject=subject, recipients=recipients)</bpmn:script>
|
<bpmn:script>email_model = email(subject=subject, recipients=recipients)</bpmn:script>
|
||||||
</bpmn:scriptTask>
|
</bpmn:scriptTask>
|
||||||
<bpmn:manualTask id="Activity_0170t0m" name="Display Email">
|
<bpmn:manualTask id="Activity_0170t0m" name="Display Email">
|
||||||
<bpmn:documentation># Email Model
|
<bpmn:documentation># Email Model
|
||||||
{{ email_model }}
|
{{ email_model }}</bpmn:documentation>
|
||||||
</bpmn:documentation>
|
|
||||||
<bpmn:incoming>Flow_103ct5a</bpmn:incoming>
|
<bpmn:incoming>Flow_103ct5a</bpmn:incoming>
|
||||||
<bpmn:outgoing>Flow_1nd1ian</bpmn:outgoing>
|
<bpmn:outgoing>Flow_1nd1ian</bpmn:outgoing>
|
||||||
</bpmn:manualTask>
|
</bpmn:manualTask>
|
||||||
</bpmn:process>
|
</bpmn:process>
|
||||||
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
||||||
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_098hnjo">
|
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_098hnjo">
|
||||||
<bpmndi:BPMNEdge id="Flow_0yh2coe_di" bpmnElement="Flow_0yh2coe">
|
<bpmndi:BPMNEdge id="Flow_1nd1ian_di" bpmnElement="Flow_1nd1ian">
|
||||||
<di:waypoint x="215" y="117" />
|
<di:waypoint x="690" y="117" />
|
||||||
<di:waypoint x="270" y="117" />
|
<di:waypoint x="752" y="117" />
|
||||||
</bpmndi:BPMNEdge>
|
|
||||||
<bpmndi:BPMNEdge id="Flow_0ovknlg_di" bpmnElement="Flow_0ovknlg">
|
|
||||||
<di:waypoint x="370" y="117" />
|
|
||||||
<di:waypoint x="430" y="117" />
|
|
||||||
</bpmndi:BPMNEdge>
|
</bpmndi:BPMNEdge>
|
||||||
<bpmndi:BPMNEdge id="Flow_103ct5a_di" bpmnElement="Flow_103ct5a">
|
<bpmndi:BPMNEdge id="Flow_103ct5a_di" bpmnElement="Flow_103ct5a">
|
||||||
<di:waypoint x="530" y="117" />
|
<di:waypoint x="530" y="117" />
|
||||||
<di:waypoint x="590" y="117" />
|
<di:waypoint x="590" y="117" />
|
||||||
</bpmndi:BPMNEdge>
|
</bpmndi:BPMNEdge>
|
||||||
<bpmndi:BPMNEdge id="Flow_1nd1ian_di" bpmnElement="Flow_1nd1ian">
|
<bpmndi:BPMNEdge id="Flow_0ovknlg_di" bpmnElement="Flow_0ovknlg">
|
||||||
<di:waypoint x="690" y="117" />
|
<di:waypoint x="370" y="117" />
|
||||||
<di:waypoint x="752" y="117" />
|
<di:waypoint x="430" y="117" />
|
||||||
|
</bpmndi:BPMNEdge>
|
||||||
|
<bpmndi:BPMNEdge id="Flow_0yh2coe_di" bpmnElement="Flow_0yh2coe">
|
||||||
|
<di:waypoint x="215" y="117" />
|
||||||
|
<di:waypoint x="270" y="117" />
|
||||||
</bpmndi:BPMNEdge>
|
</bpmndi:BPMNEdge>
|
||||||
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
|
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
|
||||||
<dc:Bounds x="179" y="99" width="36" height="36" />
|
<dc:Bounds x="179" y="99" width="36" height="36" />
|
||||||
|
|
|
@ -1,26 +1,27 @@
|
||||||
|
import unittest
|
||||||
|
from tests.base_test import BaseTest
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import unittest
|
|
||||||
import copy
|
import copy
|
||||||
|
|
||||||
from docxtpl import Listing
|
from docxtpl import Listing
|
||||||
|
|
||||||
from crc import app
|
from crc import app
|
||||||
from crc.scripts.complete_template import CompleteTemplate
|
from crc.scripts.complete_template import CompleteTemplate
|
||||||
from tests.base_test import BaseTest
|
from crc.services.jinja_service import JinjaService
|
||||||
|
|
||||||
|
|
||||||
class TestCompleteTemplate(unittest.TestCase):
|
class TestCompleteTemplate(unittest.TestCase):
|
||||||
|
|
||||||
def test_rich_text_update(self):
|
def test_rich_text_update(self):
|
||||||
script = CompleteTemplate()
|
script = JinjaService()
|
||||||
data = {"name": "Dan"}
|
data = {"name": "Dan"}
|
||||||
data_copy = copy.deepcopy(data)
|
data_copy = copy.deepcopy(data)
|
||||||
script.rich_text_update(data_copy)
|
script.rich_text_update(data_copy)
|
||||||
self.assertEqual(data, data_copy)
|
self.assertEqual(data, data_copy)
|
||||||
|
|
||||||
def test_rich_text_update_new_line(self):
|
def test_rich_text_update_new_line(self):
|
||||||
script = CompleteTemplate()
|
script = JinjaService()
|
||||||
data = {"name": "Dan\n Funk"}
|
data = {"name": "Dan\n Funk"}
|
||||||
data_copy = copy.deepcopy(data)
|
data_copy = copy.deepcopy(data)
|
||||||
script.rich_text_update(data_copy)
|
script.rich_text_update(data_copy)
|
||||||
|
@ -28,7 +29,7 @@ class TestCompleteTemplate(unittest.TestCase):
|
||||||
self.assertIsInstance(data_copy["name"], Listing)
|
self.assertIsInstance(data_copy["name"], Listing)
|
||||||
|
|
||||||
def test_rich_text_nested_new_line(self):
|
def test_rich_text_nested_new_line(self):
|
||||||
script = CompleteTemplate()
|
script = JinjaService()
|
||||||
data = {"names": [{"name": "Dan\n Funk"}]}
|
data = {"names": [{"name": "Dan\n Funk"}]}
|
||||||
data_copy = copy.deepcopy(data)
|
data_copy = copy.deepcopy(data)
|
||||||
script.rich_text_update(data_copy)
|
script.rich_text_update(data_copy)
|
||||||
|
|
|
@ -4,6 +4,8 @@ from crc.services.workflow_service import WorkflowService
|
||||||
|
|
||||||
from crc import mail
|
from crc import mail
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
class TestJinjaService(BaseTest):
|
class TestJinjaService(BaseTest):
|
||||||
|
|
||||||
|
@ -38,7 +40,13 @@ class TestJinjaService(BaseTest):
|
||||||
print(f'test_jinja_service_email: {workflow_api.next_task.data}')
|
print(f'test_jinja_service_email: {workflow_api.next_task.data}')
|
||||||
|
|
||||||
def test_jinja_service_tools(self):
|
def test_jinja_service_tools(self):
|
||||||
pass
|
template = "This is my template. {% include 'include_me' %} Was something included?"
|
||||||
|
data = {"name": "World",
|
||||||
|
"include_me": "Hello {{name}}!"}
|
||||||
|
rv = self.app.get('/v1.0/render_markdown?template=%s&data=%s' %
|
||||||
|
(template, json.dumps(data)))
|
||||||
|
self.assert_success(rv)
|
||||||
|
self.assertIn("Hello World", rv.get_data(as_text=True))
|
||||||
|
|
||||||
def test_jinja_service_documents(self):
|
def test_jinja_service_documents(self):
|
||||||
pass
|
pass
|
||||||
|
|
Loading…
Reference in New Issue