Add pdf column and uploader

This commit is contained in:
Montana 2019-02-05 09:06:23 -05:00
parent d4fd3fb262
commit ce2b4b6ea1
9 changed files with 124 additions and 82 deletions

View File

@ -0,0 +1,36 @@
"""Add PDF to Task Order
Revision ID: 1f690989e38e
Revises: 0ff4c31c4d28
Create Date: 2019-02-04 15:56:57.642156
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision = '1f690989e38e'
down_revision = '0ff4c31c4d28'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('task_orders', sa.Column('pdf_attachment_id', postgresql.UUID(as_uuid=True), nullable=True))
op.drop_constraint('task_orders_attachments_attachment_id', 'task_orders', type_='foreignkey')
op.alter_column('task_orders', 'attachment_id', new_column_name='csp_attachment_id')
op.create_foreign_key(None, 'task_orders', 'attachments', ['pdf_attachment_id'], ['id'])
op.create_foreign_key(None, 'task_orders', 'attachments', ['csp_attachment_id'], ['id'])
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_constraint(None, 'task_orders', type_='foreignkey')
op.drop_constraint(None, 'task_orders', type_='foreignkey')
op.alter_column('task_orders', 'csp_attachment_id', new_column_name='attachment_id')
op.create_foreign_key('task_orders_attachments_attachment_id', 'task_orders', 'attachments', ['attachment_id'], ['id'])
op.drop_column('task_orders', 'pdf_attachment_id')
# ### end Alembic commands ###

View File

@ -36,15 +36,6 @@ class Authorization(object):
def is_ccpo(cls, user):
return user.atat_role.name == "ccpo"
@classmethod
def check_is_mo_or_cor(cls, user, task_order):
if (
task_order.contracting_officer_representative != user
and task_order.creator != user
):
message = "build Task Order {}".format(task_order.id)
raise UnauthorizedError(user, message)
@classmethod
def check_is_ko(cls, user, task_order):
if task_order.contracting_officer != user:

View File

@ -51,8 +51,8 @@ class TaskOrder(Base, mixins.TimestampsMixin):
start_date = Column(Date) # Period of Performance
end_date = Column(Date)
performance_length = Column(Integer)
attachment_id = Column(ForeignKey("attachments.id"))
_csp_estimate = relationship("Attachment")
csp_attachment_id = Column(ForeignKey("attachments.id"))
_csp_estimate = relationship("Attachment", foreign_keys=[csp_attachment_id])
clin_01 = Column(Numeric(scale=2))
clin_02 = Column(Numeric(scale=2))
clin_03 = Column(Numeric(scale=2))
@ -72,6 +72,8 @@ class TaskOrder(Base, mixins.TimestampsMixin):
so_email = Column(String) # Email
so_phone_number = Column(String) # Phone Number
so_dod_id = Column(String) # DOD ID
pdf_attachment_id = Column(ForeignKey("attachments.id"))
_pdf = relationship("Attachment", foreign_keys=[pdf_attachment_id])
number = Column(String, unique=True) # Task Order Number
loa = Column(String) # Line of Accounting (LOA)
custom_clauses = Column(String) # Custom Clauses
@ -93,6 +95,21 @@ class TaskOrder(Base, mixins.TimestampsMixin):
elif new_csp_estimate:
raise TypeError("Could not set csp_estimate with invalid type")
@hybrid_property
def pdf(self):
return self._pdf
@pdf.setter
def pdf(self, new_pdf):
if isinstance(new_pdf, Attachment):
self._pdf = new_pdf
elif isinstance(new_pdf, FileStorage):
self._pdf = Attachment.attach(new_pdf, "task_order", self.id)
elif not new_pdf and self._pdf:
self._pdf = None
elif new_pdf:
raise TypeError("Could not set pdf with invalid type")
@property
def is_submitted(self):

View File

@ -261,7 +261,6 @@ def get_started():
@task_orders_bp.route("/portfolios/<portfolio_id>/task_orders/new/<int:screen>")
def new(screen, task_order_id=None, portfolio_id=None):
workflow = ShowTaskOrderWorkflow(g.current_user, screen, task_order_id)
Authorization.check_is_mo_or_cor(g.current_user, task_order)
return render_template(
workflow.template,
current=screen,
@ -284,7 +283,6 @@ def update(screen, task_order_id=None, portfolio_id=None):
workflow = UpdateTaskOrderWorkflow(
g.current_user, form_data, screen, task_order_id, portfolio_id
)
Authorization.check_is_mo_or_cor(g.current_user, task_order)
if workflow.validate():
workflow.update()
return redirect(

42
js/components/upload.js Normal file
View File

@ -0,0 +1,42 @@
import createNumberMask from 'text-mask-addons/dist/createNumberMask'
import { conformToMask } from 'vue-text-mask'
import FormMixin from '../mixins/form'
import textinput from './text_input'
import optionsinput from './options_input'
export default {
name: 'upload',
mixins: [FormMixin],
components: {
textinput,
optionsinput,
},
props: {
initialData: {
type: Object,
default: () => ({}),
},
uploadErrors: {
type: Array,
default: () => [],
},
},
data: function() {
const { pdf } = this.initialData
return {
showUpload: !pdf || this.uploadErrors.length > 0,
}
},
methods: {
showUploadInput: function() {
this.showUpload = true
},
},
}

View File

@ -20,6 +20,7 @@ import NewApplication from './components/forms/new_application'
import EditEnvironmentRole from './components/forms/edit_environment_role'
import EditApplicationRoles from './components/forms/edit_application_roles'
import funding from './components/forms/funding'
import upload from './components/upload'
import Modal from './mixins/modal'
import selector from './components/selector'
import BudgetChart from './components/charts/budget_chart'
@ -64,6 +65,7 @@ const app = new Vue({
RequestsList,
ConfirmationPopover,
funding,
upload,
DateSelector,
EditOfficerForm,
},

View File

@ -1,50 +0,0 @@
{% from "components/icon.html" import Icon %}
{% macro DatePicker(field) -%}
<date-selector inline-template>
<div class="usa-input date-picker" v-bind:class="{ 'usa-input--success': isDateValid }">
<input v-bind:value="formattedDate" type="hidden" />
<div class="usa-form-group usa-form-group-month">
<label>Month</label>
<input
max="12"
maxlength="2"
min="1"
type="number"
v-bind:class="{ 'usa-input-error': !isMonthValid }"
v-model="month"
/>
</div>
<div class="usa-form-group usa-form-group-day">
<label>Day</label>
<input
maxlength="2"
min="1"
type="number"
v-bind:class="{ 'usa-input-error': !isDayValid }"
v-bind:max="daysMaxCalculation"
v-model="day"
/>
</div>
<div class="usa-form-group usa-form-group-year">
<label>Year</label>
<input
maxlength="2"
type="number"
v-model="year"
/>
</div>
<div class="usa-form-group-date-ok" v-if="isDateValid">
{{ Icon("ok", classes="icon--green") }}
</div>
</div>
</date-selector>
{%- endmacro %}

View File

@ -17,11 +17,11 @@
{% include "fragments/flash.html" %}
{% block form_action %}
{% if task_order_id %}
<form method='POST' action="{{ url_for('portfolios.submit_ko_review', portfolio_id=portfolio_id, task_order_id=task_order_id, form=form) }}" autocomplete="off" enctype="multipart/form-data">
{% endif %}
<form method='POST' action="{{ url_for('portfolios.submit_ko_review', portfolio_id=portfolio.id, task_order_id=task_order.id, form=form) }}" autocomplete="off" enctype="multipart/form-data">
{% endblock %}
{{ form.csrf_token }}
{% block form %}
{% set message = "task_orders.ko_review.submitted_by" | translate({"name": task_order.creator.full_name}) %}
@ -63,11 +63,28 @@
<div class="h2">{{ "task_orders.ko_review.task_order_information"| translate }}</div>
<div class="form__sub-fields">
<div class="usa-input">
<div class="usa-input__title">{{ form.pdf.label }}</div>
{{ form.pdf.description }}
{{ form.pdf }}
</div>
<upload inline-template v-bind:initial-data='{{ form.data | tojson }}' v-bind:upload-errors='{{ form.pdf.errors | list }}'>
<div>
<template v-if="showUpload">
<div class="usa-input {% if form.pdf.errors %} usa-input--error {% endif %}">
<div class="usa-input__title">{{ form.pdf.label }}</div>
{{ form.pdf.description }}
{{ form.pdf }}
{% for error in form.pdf.errors %}
<span class="usa-input__message">{{error}}</span>
{% endfor %}
</div>
</template>
<template v-else>
<p>Uploaded {{ form.pdf.data.filename }}</p>
<div>
<button type="button" v-on:click="showUploadInput">Change</button>
</div>
</template>
</div>
</upload>
{{ TextInput(form.number) }}
{{ TextInput(form.loa) }}
{{ TextInput(form.custom_clauses, paragraph=True) }}

View File

@ -4,7 +4,6 @@
{% from "components/options_input.html" import OptionsInput %}
{% from "components/date_input.html" import DateInput %}
{% from "components/multi_checkbox_input.html" import MultiCheckboxInput %}
{% from "components/datepicker.html" import DatePicker %}
{% block heading %}
{{ "task_orders.new.app_info.section_title"| translate }}
@ -12,16 +11,6 @@
{% block form %}
<div class="usa-input">
<label>
<div class="usa-input__title">Date picker</div>
</label>
<span class="usa-form-hint">For example: 04 28 1986</span>
{{ DatePicker() }}
</div>
<!-- App Info Section -->
<h3 class="task-order-form__heading subheading">{{ "task_orders.new.app_info.basic_info_title"| translate }}</h3>