Merge pull request #566 from dod-ccpo/upload-csp-estimate
Upload proof of CSP estimate
This commit is contained in:
@@ -8,7 +8,7 @@ from atst.domain.exceptions import UploadError
|
||||
|
||||
|
||||
class FileProviderInterface:
|
||||
_PERMITTED_MIMETYPES = ["application/pdf"]
|
||||
_PERMITTED_MIMETYPES = ["application/pdf", "image/png"]
|
||||
|
||||
def _enforce_mimetype(self, fyle):
|
||||
# TODO: for hardening, we should probably use a better library for
|
||||
@@ -57,6 +57,7 @@ class RackspaceFileProvider(FileProviderInterface):
|
||||
object_name = uuid4().hex
|
||||
with NamedTemporaryFile() as tempfile:
|
||||
tempfile.write(fyle.stream.read())
|
||||
tempfile.seek(0)
|
||||
self.container.upload_object(
|
||||
file_path=tempfile.name,
|
||||
object_name=object_name,
|
||||
|
@@ -25,7 +25,7 @@ class TaskOrders(object):
|
||||
],
|
||||
"funding": [
|
||||
"performance_length",
|
||||
# "pdf",
|
||||
"csp_estimate",
|
||||
"clin_01",
|
||||
"clin_02",
|
||||
"clin_03",
|
||||
|
@@ -54,12 +54,11 @@ def mixedContentToJson(value):
|
||||
This coerces the file upload in form data to its filename
|
||||
so that the data can be JSON serialized.
|
||||
"""
|
||||
if (
|
||||
isinstance(value, dict)
|
||||
and "legacy_task_order" in value
|
||||
and hasattr(value["legacy_task_order"]["pdf"], "filename")
|
||||
):
|
||||
value["legacy_task_order"]["pdf"] = value["legacy_task_order"]["pdf"].filename
|
||||
if isinstance(value, dict):
|
||||
for k, v in value.items():
|
||||
if hasattr(v, "filename"):
|
||||
value[k] = v.filename
|
||||
|
||||
return app.jinja_env.filters["tojson"](value)
|
||||
|
||||
|
||||
|
@@ -11,6 +11,7 @@ from wtforms.fields import (
|
||||
from wtforms.fields.html5 import DateField, TelField
|
||||
from wtforms.widgets import ListWidget, CheckboxInput
|
||||
from wtforms.validators import Length
|
||||
from flask_wtf.file import FileAllowed
|
||||
|
||||
from atst.forms.validators import IsNumber, PhoneNumber, RequiredIf
|
||||
|
||||
@@ -86,9 +87,15 @@ class FundingForm(CacheableForm):
|
||||
end_date = DateField(
|
||||
translate("forms.task_order.end_date_label"), format="%m/%d/%Y"
|
||||
)
|
||||
pdf = FileField(
|
||||
translate("forms.task_order.pdf_label"),
|
||||
description=translate("forms.task_order.pdf_description"),
|
||||
csp_estimate = FileField(
|
||||
translate("forms.task_order.csp_estimate_label"),
|
||||
description=translate("forms.task_order.csp_estimate_description"),
|
||||
validators=[
|
||||
FileAllowed(
|
||||
["pdf", "png"], translate("forms.task_order.file_format_not_allowed")
|
||||
)
|
||||
],
|
||||
render_kw={"accept": ".pdf,.png,application/pdf,image/png"},
|
||||
)
|
||||
clin_01 = IntegerField(translate("forms.task_order.clin_01_label"))
|
||||
clin_02 = IntegerField(translate("forms.task_order.clin_02_label"))
|
||||
|
@@ -2,10 +2,12 @@ from enum import Enum
|
||||
|
||||
import pendulum
|
||||
from sqlalchemy import Column, Numeric, String, ForeignKey, Date, Integer
|
||||
from sqlalchemy.ext.hybrid import hybrid_property
|
||||
from sqlalchemy.types import ARRAY
|
||||
from sqlalchemy.orm import relationship
|
||||
from werkzeug.datastructures import FileStorage
|
||||
|
||||
from atst.models import Base, types, mixins
|
||||
from atst.models import Attachment, Base, types, mixins
|
||||
|
||||
|
||||
class Status(Enum):
|
||||
@@ -49,7 +51,7 @@ class TaskOrder(Base, mixins.TimestampsMixin):
|
||||
end_date = Column(Date)
|
||||
performance_length = Column(Integer)
|
||||
attachment_id = Column(ForeignKey("attachments.id"))
|
||||
pdf = relationship("Attachment")
|
||||
_csp_estimate = relationship("Attachment")
|
||||
clin_01 = Column(Numeric(scale=2))
|
||||
clin_02 = Column(Numeric(scale=2))
|
||||
clin_03 = Column(Numeric(scale=2))
|
||||
@@ -72,6 +74,23 @@ class TaskOrder(Base, mixins.TimestampsMixin):
|
||||
number = Column(String, unique=True) # Task Order Number
|
||||
loa = Column(ARRAY(String)) # Line of Accounting (LOA)
|
||||
|
||||
@hybrid_property
|
||||
def csp_estimate(self):
|
||||
return self._csp_estimate
|
||||
|
||||
@csp_estimate.setter
|
||||
def csp_estimate(self, new_csp_estimate):
|
||||
if isinstance(new_csp_estimate, Attachment):
|
||||
self._csp_estimate = new_csp_estimate
|
||||
elif isinstance(new_csp_estimate, FileStorage):
|
||||
self._csp_estimate = Attachment.attach(
|
||||
new_csp_estimate, "task_order", self.id
|
||||
)
|
||||
elif not new_csp_estimate and self._csp_estimate:
|
||||
self._csp_estimate = None
|
||||
else:
|
||||
raise TypeError("Could not set csp_estimate with invalid type")
|
||||
|
||||
@property
|
||||
def is_submitted(self):
|
||||
return self.number is not None
|
||||
|
@@ -1,8 +1,9 @@
|
||||
from io import BytesIO
|
||||
from flask import g, Response
|
||||
from flask import g, Response, current_app as app
|
||||
|
||||
from . import task_orders_bp
|
||||
from atst.domain.task_orders import TaskOrders
|
||||
from atst.domain.exceptions import NotFoundError
|
||||
from atst.utils.docx import Docx
|
||||
|
||||
|
||||
@@ -17,3 +18,22 @@ def download_summary(task_order_id):
|
||||
headers={"Content-Disposition": "attachment; filename={}".format(filename)},
|
||||
mimetype="application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||
)
|
||||
|
||||
|
||||
@task_orders_bp.route("/task_orders/csp_estimate/<task_order_id>")
|
||||
def download_csp_estimate(task_order_id):
|
||||
task_order = TaskOrders.get(g.current_user, task_order_id)
|
||||
if task_order.csp_estimate:
|
||||
estimate = task_order.csp_estimate
|
||||
generator = app.csp.files.download(estimate.object_name)
|
||||
return Response(
|
||||
generator,
|
||||
headers={
|
||||
"Content-Disposition": "attachment; filename={}".format(
|
||||
estimate.filename
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
else:
|
||||
raise NotFoundError("task_order CSP estimate")
|
||||
|
@@ -272,8 +272,9 @@ def new(screen, task_order_id=None, portfolio_id=None):
|
||||
"/portfolios/<portfolio_id>/task_orders/new/<int:screen>", methods=["POST"]
|
||||
)
|
||||
def update(screen, task_order_id=None, portfolio_id=None):
|
||||
form_data = {**http_request.form, **http_request.files}
|
||||
workflow = UpdateTaskOrderWorkflow(
|
||||
g.current_user, http_request.form, screen, task_order_id, portfolio_id
|
||||
g.current_user, form_data, screen, task_order_id, portfolio_id
|
||||
)
|
||||
|
||||
if workflow.validate():
|
||||
|
Reference in New Issue
Block a user