Merge pull request #508 from dod-ccpo/task-order-download
Task Order review and download
This commit is contained in:
@@ -57,9 +57,13 @@ class TaskOrder(Base, mixins.TimestampsMixin):
|
||||
self.number, self.budget, self.end_date, self.id
|
||||
)
|
||||
|
||||
@property
|
||||
def portfolio_name(self):
|
||||
return self.workspace.name
|
||||
|
||||
def to_dictionary(self):
|
||||
return {
|
||||
"portfolio_name": self.workspace.name,
|
||||
"portfolio_name": self.portfolio_name,
|
||||
**{
|
||||
c.name: getattr(self, c.name)
|
||||
for c in self.__table__.columns
|
||||
|
@@ -3,3 +3,5 @@ from flask import Blueprint
|
||||
task_orders_bp = Blueprint("task_orders", __name__)
|
||||
|
||||
from . import new
|
||||
from . import index
|
||||
from . import invite
|
||||
|
19
atst/routes/task_orders/index.py
Normal file
19
atst/routes/task_orders/index.py
Normal file
@@ -0,0 +1,19 @@
|
||||
from io import BytesIO
|
||||
from flask import Response
|
||||
|
||||
from . import task_orders_bp
|
||||
from atst.domain.task_orders import TaskOrders
|
||||
from atst.utils.docx import Docx
|
||||
|
||||
|
||||
@task_orders_bp.route("/task_orders/download_summary/<task_order_id>")
|
||||
def download_summary(task_order_id):
|
||||
task_order = TaskOrders.get(task_order_id)
|
||||
byte_str = BytesIO()
|
||||
Docx.render(byte_str, data=task_order.to_dictionary())
|
||||
filename = "{}.docx".format(task_order.portfolio_name)
|
||||
return Response(
|
||||
byte_str,
|
||||
headers={"Content-Disposition": "attachment; filename={}".format(filename)},
|
||||
mimetype="application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||
)
|
15
atst/routes/task_orders/invite.py
Normal file
15
atst/routes/task_orders/invite.py
Normal file
@@ -0,0 +1,15 @@
|
||||
from flask import redirect, url_for
|
||||
|
||||
from . import task_orders_bp
|
||||
from atst.domain.task_orders import TaskOrders
|
||||
from atst.utils.flash import formatted_flash as flash
|
||||
|
||||
|
||||
# TODO: add a real implementation for this
|
||||
@task_orders_bp.route("/task_orders/invite/<task_order_id>", methods=["POST"])
|
||||
def invite(task_order_id):
|
||||
task_order = TaskOrders.get(task_order_id)
|
||||
flash("task_order_submitted", task_order=task_order)
|
||||
return redirect(
|
||||
url_for("workspaces.workspace_members", workspace_id=task_order.workspace.id)
|
||||
)
|
@@ -113,6 +113,7 @@ def new(screen, task_order_id=None):
|
||||
workflow.template,
|
||||
current=screen,
|
||||
task_order_id=task_order_id,
|
||||
task_order=workflow.task_order,
|
||||
screens=workflow.display_screens,
|
||||
form=workflow.form,
|
||||
)
|
||||
|
57
atst/utils/docx.py
Normal file
57
atst/utils/docx.py
Normal file
@@ -0,0 +1,57 @@
|
||||
import os
|
||||
from zipfile import ZipFile
|
||||
from flask import render_template, current_app as app
|
||||
|
||||
|
||||
class Docx:
|
||||
DOCUMENT_FILE = "word/document.xml"
|
||||
|
||||
@classmethod
|
||||
def _template_path(cls, docx_file):
|
||||
return os.path.join(app.root_path, "..", "templates", docx_file)
|
||||
|
||||
@classmethod
|
||||
def _template(cls, docx_file):
|
||||
return ZipFile(Docx._template_path(docx_file), mode="r")
|
||||
|
||||
@classmethod
|
||||
def _write(cls, docx_template, docx_file, document_content):
|
||||
"""
|
||||
This method takes an existing docx as its starting
|
||||
point and copies over every file from it to a new zip
|
||||
file, overwriting the document.xml file with new
|
||||
document content.
|
||||
|
||||
zipfile.ZipFile does not provide a way to replace file
|
||||
contents in a zip in-place, so we copy over the entire
|
||||
zip archive instead.
|
||||
|
||||
docx_template: The source docx file we harvest from.
|
||||
docx_file: A ZipFile instance that content from the docx_template is copied to
|
||||
document_content: The new content for the document.xml file
|
||||
"""
|
||||
with docx_template as template:
|
||||
for item in template.infolist():
|
||||
if item.filename != Docx.DOCUMENT_FILE:
|
||||
content = template.read(item.filename).decode()
|
||||
else:
|
||||
content = document_content
|
||||
|
||||
docx_file.writestr(item, content)
|
||||
|
||||
return docx_file
|
||||
|
||||
@classmethod
|
||||
def render(
|
||||
cls,
|
||||
file_like,
|
||||
doc_template="docx/document.xml",
|
||||
file_template="docx/template.docx",
|
||||
**args,
|
||||
):
|
||||
document = render_template(doc_template, **args)
|
||||
with ZipFile(file_like, mode="w") as docx_file:
|
||||
docx_template = Docx._template(file_template)
|
||||
Docx._write(docx_template, docx_file, document)
|
||||
file_like.seek(0)
|
||||
return file_like
|
@@ -101,6 +101,13 @@ MESSAGES = {
|
||||
"message_template": "",
|
||||
"category": "success",
|
||||
},
|
||||
"task_order_submitted": {
|
||||
"title_template": "Task Order Form Submitted",
|
||||
"message_template": """
|
||||
Your task order form for {{ task_order.portfolio_name }} has been submitted.
|
||||
""",
|
||||
"category": "success",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user