*** 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:
mike cullerton 2021-10-18 16:56:03 -04:00
parent da9b902c19
commit 0122029e30
6 changed files with 106 additions and 34 deletions

View File

@ -24,9 +24,7 @@ def render_markdown(data, template):
data structure. Useful for folks that are building these markdown templates.
"""
try:
# template = Template(template)
data = json.loads(data)
# return template.render(**data)
return JinjaService.get_content(template, data)
except UndefinedError as ue:
raise ApiError(code="undefined_field", message=ue.message)
@ -42,6 +40,12 @@ def render_docx():
try:
file = connexion.request.files['file']
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))
return send_file(
io.BytesIO(target_stream.read()),

View File

@ -12,6 +12,7 @@ from crc.models.file import CONTENT_TYPES, FileModel, FileDataModel
from crc.models.workflow import WorkflowModel
from crc.scripts.script import Script
from crc.services.file_service import FileService
from crc.services.jinja_service import JinjaService
from crc.services.workflow_processor import WorkflowProcessor
@ -108,10 +109,10 @@ Takes two arguments:
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?
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.append_images(doc, doc_context, image_file_data)
jinja_env = jinja2.Environment(autoescape=True)
@ -119,6 +120,15 @@ Takes two arguments:
doc.render(doc_context, jinja_env)
except Exception as 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()
doc.save(target_stream)
target_stream.seek(0) # move to the beginning of the stream.

View File

@ -1,5 +1,11 @@
from docxtpl import DocxTemplate, Listing, InlineImage
from jinja2 import Environment, DictLoader
from docx.shared import Inches
from io import BytesIO
import copy
class JinjaService:
"""Service for Jinja2 templates.
@ -27,13 +33,52 @@ Cool Right?
templates['main_template'] = input_template
jinja2_env = Environment(loader=DictLoader(templates))
try:
template = jinja2_env.get_template('main_template')
# We just make a call here and let any errors percolate up to the calling method
template = jinja2_env.get_template('main_template')
return template.render(**data)
except Exception:
# TODO: Should we deal w/ specific exceptions here?
# i.e., the ones in workflow_service._process_documentation
raise
@staticmethod
def get_word_document_content(binary_stream, task_data, image_file_data=None):
doc = DocxTemplate(binary_stream)
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:
return template.render(**data)
def rich_text_update(self, context):
"""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))

View File

@ -16,9 +16,16 @@
<camunda:formData>
<camunda:formField id="subject" label="Subject" 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="&#39;My Value&#39;" />
</camunda:properties>
</camunda:formField>
<camunda:formField id="name" label="Name" type="string" />
</camunda:formData>
<camunda:properties>
<camunda:property name="my_extension" value="&#39;My Value&#39;" />
</camunda:properties>
</bpmn:extensionElements>
<bpmn:incoming>Flow_0yh2coe</bpmn:incoming>
<bpmn:outgoing>Flow_0ovknlg</bpmn:outgoing>
@ -33,38 +40,35 @@ Thank you for supporting the cause!
{% include 'include_me' %}
Did we include the other template?
</bpmn:documentation>
Did we include the other template?</bpmn:documentation>
<bpmn:incoming>Flow_0ovknlg</bpmn:incoming>
<bpmn:outgoing>Flow_103ct5a</bpmn:outgoing>
<bpmn:script>email_model = email(subject=subject, recipients=recipients)</bpmn:script>
</bpmn:scriptTask>
<bpmn:manualTask id="Activity_0170t0m" name="Display Email">
<bpmn:documentation># Email Model
{{ email_model }}
</bpmn:documentation>
{{ email_model }}</bpmn:documentation>
<bpmn:incoming>Flow_103ct5a</bpmn:incoming>
<bpmn:outgoing>Flow_1nd1ian</bpmn:outgoing>
</bpmn:manualTask>
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_098hnjo">
<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 id="Flow_0ovknlg_di" bpmnElement="Flow_0ovknlg">
<di:waypoint x="370" y="117" />
<di:waypoint x="430" y="117" />
<bpmndi:BPMNEdge id="Flow_1nd1ian_di" bpmnElement="Flow_1nd1ian">
<di:waypoint x="690" y="117" />
<di:waypoint x="752" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_103ct5a_di" bpmnElement="Flow_103ct5a">
<di:waypoint x="530" y="117" />
<di:waypoint x="590" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1nd1ian_di" bpmnElement="Flow_1nd1ian">
<di:waypoint x="690" y="117" />
<di:waypoint x="752" y="117" />
<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 id="Flow_0yh2coe_di" bpmnElement="Flow_0yh2coe">
<di:waypoint x="215" y="117" />
<di:waypoint x="270" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
<dc:Bounds x="179" y="99" width="36" height="36" />

View File

@ -1,26 +1,27 @@
import unittest
from tests.base_test import BaseTest
import json
import os
import unittest
import copy
from docxtpl import Listing
from crc import app
from crc.scripts.complete_template import CompleteTemplate
from tests.base_test import BaseTest
from crc.services.jinja_service import JinjaService
class TestCompleteTemplate(unittest.TestCase):
def test_rich_text_update(self):
script = CompleteTemplate()
script = JinjaService()
data = {"name": "Dan"}
data_copy = copy.deepcopy(data)
script.rich_text_update(data_copy)
self.assertEqual(data, data_copy)
def test_rich_text_update_new_line(self):
script = CompleteTemplate()
script = JinjaService()
data = {"name": "Dan\n Funk"}
data_copy = copy.deepcopy(data)
script.rich_text_update(data_copy)
@ -28,7 +29,7 @@ class TestCompleteTemplate(unittest.TestCase):
self.assertIsInstance(data_copy["name"], Listing)
def test_rich_text_nested_new_line(self):
script = CompleteTemplate()
script = JinjaService()
data = {"names": [{"name": "Dan\n Funk"}]}
data_copy = copy.deepcopy(data)
script.rich_text_update(data_copy)

View File

@ -4,6 +4,8 @@ from crc.services.workflow_service import WorkflowService
from crc import mail
import json
class TestJinjaService(BaseTest):
@ -38,7 +40,13 @@ class TestJinjaService(BaseTest):
print(f'test_jinja_service_email: {workflow_api.next_task.data}')
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):
pass