Merge remote-tracking branch 'origin/master'
This commit is contained in:
commit
f7416065da
|
@ -727,7 +727,7 @@
|
|||
"spiffworkflow": {
|
||||
"editable": true,
|
||||
"git": "https://github.com/sartography/SpiffWorkflow.git",
|
||||
"ref": "0da57a83bfa0edaf1cfd5500f87757553621c412"
|
||||
"ref": "d8dbbb4e0575e8346e3342f314b76a0bce140530"
|
||||
},
|
||||
"sqlalchemy": {
|
||||
"hashes": [
|
||||
|
|
63
crc/api.yml
63
crc/api.yml
|
@ -585,12 +585,6 @@ paths:
|
|||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Task"
|
||||
default:
|
||||
description: unexpected error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
/workflow/{workflow_id}/task/{task_id}/data:
|
||||
parameters:
|
||||
- name: workflow_id
|
||||
|
@ -629,6 +623,63 @@ paths:
|
|||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Workflow"
|
||||
/render_markdown:
|
||||
parameters:
|
||||
- name: data
|
||||
in: query
|
||||
required: true
|
||||
description: The json data to use in populating the template
|
||||
schema:
|
||||
type: string
|
||||
- name: template
|
||||
in: query
|
||||
required: true
|
||||
description: The markdown template to process.
|
||||
schema:
|
||||
type: string
|
||||
get:
|
||||
operationId: crc.api.tools.render_markdown
|
||||
summary: Processes the markdown template using the data provided.
|
||||
tags:
|
||||
- Configurator Tools
|
||||
responses:
|
||||
'201':
|
||||
description: Returns the updated workflow with the task completed.
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
/render_docx:
|
||||
parameters:
|
||||
- name: data
|
||||
in: query
|
||||
required: true
|
||||
description: The json data to use in populating the template
|
||||
schema:
|
||||
type: string
|
||||
put:
|
||||
operationId: crc.api.tools.render_docx
|
||||
summary: Renders a docx template with embedded jinja2 markup.
|
||||
tags:
|
||||
- Configurator Tools
|
||||
requestBody:
|
||||
content:
|
||||
multipart/form-data:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
file:
|
||||
type: string
|
||||
format: binary
|
||||
responses:
|
||||
'200':
|
||||
description: Returns the generated document.
|
||||
content:
|
||||
application/octet-stream:
|
||||
schema:
|
||||
type: string
|
||||
format: binary
|
||||
example: '<?xml version="1.0" encoding="UTF-8"?><bpmn:definitions></bpmn:definitions>'
|
||||
|
||||
components:
|
||||
schemas:
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
from crc import ma
|
||||
from crc import ma, app
|
||||
|
||||
|
||||
class ApiError(Exception):
|
||||
def __init__(self, code, message):
|
||||
def __init__(self, code, message, status_code=400):
|
||||
self.status_code = status_code
|
||||
self.code = code
|
||||
self.message = message
|
||||
|
||||
|
@ -11,3 +12,8 @@ class ApiErrorSchema(ma.Schema):
|
|||
class Meta:
|
||||
fields = ("code", "message")
|
||||
|
||||
|
||||
@app.errorhandler(ApiError)
|
||||
def handle_invalid_usage(error):
|
||||
response = ApiErrorSchema().dump(error)
|
||||
return response, error.status_code
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
import io
|
||||
import json
|
||||
import uuid
|
||||
from io import BytesIO
|
||||
|
||||
import connexion
|
||||
import jinja2
|
||||
from docxtpl import DocxTemplate
|
||||
from flask import send_file
|
||||
from jinja2 import Template, UndefinedError
|
||||
|
||||
from crc.api.common import ApiError, ApiErrorSchema
|
||||
from crc.scripts.CompleteTemplate import CompleteTemplate
|
||||
from crc.services.file_service import FileService
|
||||
|
||||
|
||||
def render_markdown(data, template):
|
||||
"""
|
||||
Provides a quick way to very that a Jinja markdown template will work properly on a given json
|
||||
data structure. Useful for folks that are building these markdown templates.
|
||||
"""
|
||||
try:
|
||||
template = Template(template)
|
||||
data = json.loads(data)
|
||||
return template.render(**data)
|
||||
except UndefinedError as ue:
|
||||
raise ApiError(code="undefined field", message=ue.message)
|
||||
except Exception as e:
|
||||
raise ApiError(code="invalid", message=str(e))
|
||||
|
||||
|
||||
def render_docx(data):
|
||||
"""
|
||||
Provides a quick way to verify that a Jinja docx template will work properly on a given json
|
||||
data structure. Useful for folks that are building these templates.
|
||||
"""
|
||||
try:
|
||||
file = connexion.request.files['file']
|
||||
target_stream = CompleteTemplate().make_template(file, json.loads(data))
|
||||
return send_file(
|
||||
io.BytesIO(target_stream.read()),
|
||||
as_attachment=True,
|
||||
attachment_filename="output.docx",
|
||||
mimetype="application/octet-stream",
|
||||
cache_timeout=-1 # Don't cache these files on the browser.
|
||||
)
|
||||
except ValueError as e:
|
||||
raise ApiError(code="invalid", message=str(e))
|
||||
except Exception as e:
|
||||
raise ApiError(code="invalid", message=str(e))
|
|
@ -1,7 +1,7 @@
|
|||
import enum
|
||||
|
||||
import marshmallow
|
||||
from jinja2 import Environment, BaseLoader
|
||||
from jinja2 import Environment, BaseLoader, Undefined, Template
|
||||
from marshmallow import INCLUDE
|
||||
from marshmallow_enum import EnumField
|
||||
from marshmallow_sqlalchemy import ModelSchema
|
||||
|
@ -38,7 +38,6 @@ class WorkflowModel(db.Model):
|
|||
study_id = db.Column(db.Integer, db.ForeignKey('study.id'))
|
||||
workflow_spec_id = db.Column(db.String, db.ForeignKey('workflow_spec.id'))
|
||||
|
||||
|
||||
class Task(object):
|
||||
def __init__(self, id, name, title, type, state, form, documentation, data):
|
||||
self.id = id
|
||||
|
@ -64,14 +63,16 @@ class Task(object):
|
|||
if hasattr(spiff_task.task_spec, "form"):
|
||||
instance.form = spiff_task.task_spec.form
|
||||
if documentation != "" and documentation is not None:
|
||||
|
||||
instance.process_documentation(documentation)
|
||||
return instance
|
||||
|
||||
def process_documentation(self, documentation):
|
||||
'''Runs markdown documentation through the Jinja2 processor to inject data
|
||||
create loops, etc...'''
|
||||
rtemplate = Environment(autoescape=True, loader=BaseLoader).from_string(documentation)
|
||||
self.documentation = rtemplate.render(**self.data)
|
||||
|
||||
template = Template(documentation)
|
||||
self.documentation = template.render(**self.data)
|
||||
|
||||
|
||||
class OptionSchema(ma.Schema):
|
||||
|
|
|
@ -42,7 +42,7 @@ class CompleteTemplate(object):
|
|||
message="Can not find a file called '%s' "
|
||||
"within workflow specification '%s'") % (args[0], workflow_spec_model.id)
|
||||
|
||||
final_document_stream = self.make_template(file_data_model, task.data)
|
||||
final_document_stream = self.make_template(BytesIO(file_data_model.data), task.data)
|
||||
study_id = task.workflow.data[WorkflowProcessor.STUDY_ID_KEY]
|
||||
workflow_id = task.workflow.data[WorkflowProcessor.WORKFLOW_ID_KEY]
|
||||
FileService.add_task_file(study_id=study_id, workflow_id=workflow_id, task_id=task.id,
|
||||
|
@ -52,8 +52,8 @@ class CompleteTemplate(object):
|
|||
|
||||
print("Complete Task was called with %s" % str(args))
|
||||
|
||||
def make_template(self, file_data_model, context):
|
||||
doc = DocxTemplate(BytesIO(file_data_model.data))
|
||||
def make_template(self, binary_stream, context):
|
||||
doc = DocxTemplate(binary_stream)
|
||||
jinja_env = jinja2.Environment(autoescape=True)
|
||||
doc.render(context, jinja_env)
|
||||
target_stream = BytesIO()
|
||||
|
|
Binary file not shown.
|
@ -175,6 +175,19 @@ class TestTasksApi(BaseTest):
|
|||
task.process_documentation(documentation)
|
||||
self.assertEqual(expected, task.documentation)
|
||||
|
||||
def test_documentation_processing_handles_conditionals(self):
|
||||
|
||||
docs = "This test {% if works == 'yes' %}works{% endif %}"
|
||||
task = Task(1, "bill", "bill", "", "started", {}, docs, {})
|
||||
task.process_documentation(docs)
|
||||
self.assertEqual("This test ", task.documentation)
|
||||
|
||||
task.data = {"works": 'yes'}
|
||||
task.process_documentation(docs)
|
||||
self.assertEqual("This test works", task.documentation)
|
||||
|
||||
|
||||
|
||||
def test_get_documentation_populated_in_end(self):
|
||||
self.load_example_data()
|
||||
workflow = self.create_workflow('random_fact')
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
import json
|
||||
import os
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from crc import session, app
|
||||
from crc.models.study import StudyModel, StudyModelSchema
|
||||
from crc.models.protocol_builder import ProtocolBuilderStatus
|
||||
from crc.models.workflow import WorkflowSpecModel, WorkflowSpecModelSchema, WorkflowModel, WorkflowStatus, \
|
||||
WorkflowApiSchema
|
||||
from tests.base_test import BaseTest
|
||||
|
||||
|
||||
class TestStudyApi(BaseTest):
|
||||
|
||||
def test_render_markdown(self):
|
||||
template = "My name is {{name}}"
|
||||
data = {"name": "Dan"}
|
||||
rv = self.app.get('/v1.0/render_markdown?template=%s&data=%s' %
|
||||
(template, json.dumps(data)))
|
||||
self.assert_success(rv)
|
||||
self.assertEqual("My name is Dan", rv.get_data(as_text=True))
|
||||
|
||||
def test_render_docx(self):
|
||||
filepath = os.path.join(app.root_path, '..', 'tests', 'data', 'table.docx')
|
||||
template_data = {"hippa": [{"option": "Name", "selected": True, "stored": ["Record at UVA", "Stored Long Term"]},
|
||||
{"option": "Address", "selected": False},
|
||||
{"option": "Phone", "selected": True, "stored": ["Send or Transmit outside of UVA"]}]}
|
||||
with open(filepath, 'rb') as f:
|
||||
file_data = {'file': (f, 'my_new_file.bpmn')}
|
||||
rv = self.app.put('/v1.0/render_docx?data=%s' % json.dumps(template_data),
|
||||
data=file_data, follow_redirects=True,
|
||||
content_type='multipart/form-data')
|
||||
self.assert_success(rv)
|
Loading…
Reference in New Issue