commit
d0c8d0e519
@ -14,7 +14,7 @@ class CSPFileError(Exception):
|
|||||||
|
|
||||||
|
|
||||||
class FileProviderInterface:
|
class FileProviderInterface:
|
||||||
_PERMITTED_MIMETYPES = ["application/pdf", "image/png"]
|
_PERMITTED_MIMETYPES = ["application/pdf"]
|
||||||
|
|
||||||
def _enforce_mimetype(self, fyle):
|
def _enforce_mimetype(self, fyle):
|
||||||
# TODO: for hardening, we should probably use a better library for
|
# TODO: for hardening, we should probably use a better library for
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
from wtforms.fields import BooleanField, DecimalField, StringField
|
from wtforms.fields import BooleanField, DecimalField, FileField, StringField
|
||||||
from wtforms.fields.html5 import DateField
|
from wtforms.fields.html5 import DateField
|
||||||
from wtforms.validators import Required, Optional
|
from wtforms.validators import Required, Optional
|
||||||
|
from flask_wtf.file import FileAllowed
|
||||||
|
|
||||||
from .forms import BaseForm
|
from .forms import BaseForm
|
||||||
|
from atst.forms.validators import FileLength
|
||||||
from atst.utils.localization import translate
|
from atst.utils.localization import translate
|
||||||
|
|
||||||
|
|
||||||
@ -12,6 +14,14 @@ class TaskOrderForm(BaseForm):
|
|||||||
description=translate("forms.task_order.number_description"),
|
description=translate("forms.task_order.number_description"),
|
||||||
validators=[Required()],
|
validators=[Required()],
|
||||||
)
|
)
|
||||||
|
pdf = FileField(
|
||||||
|
None,
|
||||||
|
validators=[
|
||||||
|
FileAllowed(["pdf"], translate("forms.task_order.file_format_not_allowed")),
|
||||||
|
FileLength(message=translate("forms.validators.file_length")),
|
||||||
|
],
|
||||||
|
render_kw={"accept": ".pdf,application/pdf"},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class FundingForm(BaseForm):
|
class FundingForm(BaseForm):
|
||||||
|
@ -99,3 +99,17 @@ def RequiredIf(criteria_function, message=translate("forms.validators.is_require
|
|||||||
raise StopValidation()
|
raise StopValidation()
|
||||||
|
|
||||||
return _required_if
|
return _required_if
|
||||||
|
|
||||||
|
|
||||||
|
def FileLength(max_length=50000000, message=None):
|
||||||
|
def _file_length(_form, field):
|
||||||
|
if field.data is None:
|
||||||
|
return True
|
||||||
|
|
||||||
|
content = field.data.read()
|
||||||
|
if len(content) > max_length:
|
||||||
|
raise ValidationError(message)
|
||||||
|
else:
|
||||||
|
field.data.seek(0)
|
||||||
|
|
||||||
|
return _file_length
|
||||||
|
@ -8,25 +8,32 @@ from atst.models.permissions import Permissions
|
|||||||
from atst.utils.flash import formatted_flash as flash
|
from atst.utils.flash import formatted_flash as flash
|
||||||
|
|
||||||
|
|
||||||
@task_orders_bp.route("/portfolios/<portfolio_id>/task_orders/new")
|
def render_task_orders_edit(portfolio_id, task_order_id=None, form=None):
|
||||||
@task_orders_bp.route("/portfolios/<portfolio_id>/task_orders/<task_order_id>/edit")
|
render_args = {}
|
||||||
@user_can(Permissions.CREATE_TASK_ORDER, message="view new task order form")
|
|
||||||
def edit(portfolio_id, task_order_id=None):
|
|
||||||
form = None
|
|
||||||
|
|
||||||
if task_order_id:
|
if task_order_id:
|
||||||
task_order = TaskOrders.get(task_order_id)
|
task_order = TaskOrders.get(task_order_id)
|
||||||
form = TaskOrderForm(number=task_order.number)
|
render_args["form"] = form or TaskOrderForm(
|
||||||
|
number=task_order.number, pdf=task_order.pdf
|
||||||
|
)
|
||||||
|
render_args["task_order_id"] = task_order_id
|
||||||
else:
|
else:
|
||||||
form = TaskOrderForm()
|
render_args["form"] = form or TaskOrderForm()
|
||||||
|
|
||||||
cancel_url = (
|
render_args["cancel_url"] = (
|
||||||
http_request.referrer
|
http_request.referrer
|
||||||
if http_request.referrer
|
if http_request.referrer
|
||||||
else url_for("task_orders.portfolio_funding", portfolio_id=portfolio_id)
|
else url_for("task_orders.portfolio_funding", portfolio_id=portfolio_id)
|
||||||
)
|
)
|
||||||
|
|
||||||
return render_template("task_orders/edit.html", form=form, cancel_url=cancel_url)
|
return render_template("task_orders/edit.html", **render_args)
|
||||||
|
|
||||||
|
|
||||||
|
@task_orders_bp.route("/portfolios/<portfolio_id>/task_orders/new")
|
||||||
|
@task_orders_bp.route("/portfolios/<portfolio_id>/task_orders/<task_order_id>/edit")
|
||||||
|
@user_can(Permissions.CREATE_TASK_ORDER, message="view new task order form")
|
||||||
|
def edit(portfolio_id, task_order_id=None):
|
||||||
|
return render_task_orders_edit(portfolio_id, task_order_id)
|
||||||
|
|
||||||
|
|
||||||
@task_orders_bp.route("/portfolios/<portfolio_id>/task_orders/new", methods=["POST"])
|
@task_orders_bp.route("/portfolios/<portfolio_id>/task_orders/new", methods=["POST"])
|
||||||
@ -35,7 +42,8 @@ def edit(portfolio_id, task_order_id=None):
|
|||||||
)
|
)
|
||||||
@user_can(Permissions.CREATE_TASK_ORDER, message="create new task order")
|
@user_can(Permissions.CREATE_TASK_ORDER, message="create new task order")
|
||||||
def update(portfolio_id, task_order_id=None):
|
def update(portfolio_id, task_order_id=None):
|
||||||
form_data = http_request.form
|
form_data = {**http_request.form, **http_request.files}
|
||||||
|
|
||||||
form = TaskOrderForm(form_data)
|
form = TaskOrderForm(form_data)
|
||||||
|
|
||||||
if form.validate():
|
if form.validate():
|
||||||
@ -56,4 +64,4 @@ def update(portfolio_id, task_order_id=None):
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
flash("form_errors")
|
flash("form_errors")
|
||||||
return render_template("task_orders/edit.html", form=form)
|
return render_task_orders_edit(portfolio_id, task_order_id, form), 400
|
||||||
|
@ -9,6 +9,7 @@ import levelofwarrant from '../levelofwarrant'
|
|||||||
import multicheckboxinput from '../multi_checkbox_input'
|
import multicheckboxinput from '../multi_checkbox_input'
|
||||||
import optionsinput from '../options_input'
|
import optionsinput from '../options_input'
|
||||||
import textinput from '../text_input'
|
import textinput from '../text_input'
|
||||||
|
import uploadinput from '../upload_input'
|
||||||
import toggler from '../toggler'
|
import toggler from '../toggler'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@ -23,6 +24,7 @@ export default {
|
|||||||
optionsinput,
|
optionsinput,
|
||||||
textinput,
|
textinput,
|
||||||
toggler,
|
toggler,
|
||||||
|
uploadinput,
|
||||||
},
|
},
|
||||||
mixins: [FormMixin],
|
mixins: [FormMixin],
|
||||||
}
|
}
|
||||||
|
@ -19,17 +19,15 @@ export default {
|
|||||||
initialData: {
|
initialData: {
|
||||||
type: String,
|
type: String,
|
||||||
},
|
},
|
||||||
uploadErrors: {
|
initialErrors: {
|
||||||
type: Array,
|
type: Boolean,
|
||||||
default: () => [],
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
data: function() {
|
data: function() {
|
||||||
const pdf = this.initialData
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
showUpload: !pdf || this.uploadErrors.length > 0,
|
attachment: this.initialData || null,
|
||||||
|
showErrors: this.initialErrors,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -37,5 +35,26 @@ export default {
|
|||||||
showUploadInput: function() {
|
showUploadInput: function() {
|
||||||
this.showUpload = true
|
this.showUpload = true
|
||||||
},
|
},
|
||||||
|
addAttachment: function(e) {
|
||||||
|
this.attachment = e.target.value
|
||||||
|
this.showErrors = false
|
||||||
|
},
|
||||||
|
removeAttachment: function(e) {
|
||||||
|
e.preventDefault()
|
||||||
|
this.attachment = null
|
||||||
|
this.$refs.attachmentInput.value = null
|
||||||
|
this.showErrors = false
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
baseName: function() {
|
||||||
|
if (this.attachment) {
|
||||||
|
return this.attachment.split(/[\\/]/).pop()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
hasAttachment: function() {
|
||||||
|
return !!this.attachment
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
1
static/icons/check-circle-solid.svg
Normal file
1
static/icons/check-circle-solid.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="check-circle" class="svg-inline--fa fa-check-circle fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M504 256c0 136.967-111.033 248-248 248S8 392.967 8 256 119.033 8 256 8s248 111.033 248 248zM227.314 387.314l184-184c6.248-6.248 6.248-16.379 0-22.627l-22.627-22.627c-6.248-6.249-16.379-6.249-22.628 0L216 308.118l-70.059-70.059c-6.248-6.248-16.379-6.248-22.628 0l-22.627 22.627c-6.248 6.248-6.248 16.379 0 22.627l104 104c6.249 6.249 16.379 6.249 22.628.001z"></path></svg>
|
After Width: | Height: | Size: 600 B |
@ -23,6 +23,7 @@
|
|||||||
@import "elements/graphs";
|
@import "elements/graphs";
|
||||||
@import "elements/menu";
|
@import "elements/menu";
|
||||||
@import "elements/card";
|
@import "elements/card";
|
||||||
|
@import "elements/uploader";
|
||||||
|
|
||||||
@import "components/accordion_table";
|
@import "components/accordion_table";
|
||||||
@import "components/topbar";
|
@import "components/topbar";
|
||||||
|
71
styles/elements/_uploader.scss
Normal file
71
styles/elements/_uploader.scss
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
.upload-widget {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
label.upload-label {
|
||||||
|
text-align: right;
|
||||||
|
border: 1px solid black;
|
||||||
|
padding: 0;
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
.upload-button {
|
||||||
|
padding: 1rem 1.5rem;
|
||||||
|
display: inline-block;
|
||||||
|
background-color: $color-blue;
|
||||||
|
border: 1px solid $color-blue;
|
||||||
|
margin: -1px;
|
||||||
|
color: white;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: $color-blue-darker;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
opacity: 0;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.uploaded-file {
|
||||||
|
.icon {
|
||||||
|
vertical-align: middle;
|
||||||
|
|
||||||
|
svg * {
|
||||||
|
fill: $color-green;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.uploaded-file__name {
|
||||||
|
vertical-align: middle;
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
font-weight: $font-bold;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uploaded-file__remove {
|
||||||
|
vertical-align: middle;
|
||||||
|
margin-left: 2rem;
|
||||||
|
font-size: $small-font-size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.usa-input--error {
|
||||||
|
.upload-widget {
|
||||||
|
label.upload-label {
|
||||||
|
border: 1px solid $color-red;
|
||||||
|
box-shadow: inset 0 0 0 2px $color-red;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.upload-button {
|
||||||
|
margin-left: -3px;
|
||||||
|
border-left: 3px solid $color-red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,24 +1,47 @@
|
|||||||
|
{% from "components/icon.html" import Icon %}
|
||||||
|
|
||||||
{% macro UploadInput(field, show_label=False) -%}
|
{% macro UploadInput(field, show_label=False) -%}
|
||||||
<uploadinput inline-template v-bind:initial-data='{{ field.data | tojson }}' v-bind:upload-errors='{{ field.errors | list }}'>
|
<uploadinput
|
||||||
|
inline-template
|
||||||
|
{% if not field.errors %}
|
||||||
|
v-bind:initial-data='{{ field.data | tojson }}'
|
||||||
|
{% else %}
|
||||||
|
v-bind:initial-errors='true'
|
||||||
|
{% endif %}
|
||||||
|
>
|
||||||
<div>
|
<div>
|
||||||
<template v-if="showUpload">
|
<div v-show="hasAttachment" class="uploaded-file">
|
||||||
<div class="usa-input {% if field.errors %} usa-input--error {% endif %}">
|
{{ Icon("check-circle-solid") }}
|
||||||
{% if show_label %}
|
<span class="uploaded-file__name" v-html="baseName"></span>
|
||||||
{{ field.label }}
|
<a href="#" class="uploaded-file__remove" v-on:click="removeAttachment">Remove</a>
|
||||||
{% endif %}
|
</div>
|
||||||
{{ field.description }}
|
<div v-show="hasAttachment === false" v-bind:class='{ "usa-input": true, "usa-input--error": showErrors }'>
|
||||||
{{ field }}
|
{% if show_label %}
|
||||||
{% for error in field.errors %}
|
{{ field.label }}
|
||||||
<span class="usa-input__message">{{error}}</span>
|
{% endif %}
|
||||||
{% endfor %}
|
{{ field.description }}
|
||||||
|
<div class="upload-widget">
|
||||||
|
<label class="upload-label" for="{{ field.name }}">
|
||||||
|
<span class="upload-button">
|
||||||
|
Browse
|
||||||
|
</span>
|
||||||
|
{% if field.errors %}
|
||||||
|
<span v-show="showErrors">{{ Icon('alert',classes="icon-validation") }}</span>
|
||||||
|
{% endif %}
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
v-on:change="addAttachment"
|
||||||
|
ref="attachmentInput"
|
||||||
|
accept="{{ field.accept }}"
|
||||||
|
id="{{ field.name }}"
|
||||||
|
name="{{ field.name }}"
|
||||||
|
aria-label="Task Order Upload"
|
||||||
|
type="file">
|
||||||
</div>
|
</div>
|
||||||
</template>
|
{% for error in field.errors %}
|
||||||
<template v-else>
|
<span v-show="showErrors" class="usa-input__message">{{error}}</span>
|
||||||
<p>Uploaded {{ field.data.filename }}</p>
|
{% endfor %}
|
||||||
<div>
|
</div>
|
||||||
<button type="button" v-on:click="showUploadInput">Change</button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
</div>
|
||||||
</uploadinput>
|
</uploadinput>
|
||||||
{%- endmacro %}
|
{%- endmacro %}
|
||||||
|
@ -1,36 +1,36 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "portfolios/base.html" %}
|
||||||
|
|
||||||
{% from 'components/save_button.html' import SaveButton %}
|
{% from 'components/save_button.html' import SaveButton %}
|
||||||
{% from 'components/text_input.html' import TextInput %}
|
{% from 'components/text_input.html' import TextInput %}
|
||||||
|
{% from 'components/upload_input.html' import UploadInput %}
|
||||||
|
|
||||||
{% block content %}
|
{% block portfolio_content %}
|
||||||
<div class="col task-order-form">
|
<div class="col task-order-form">
|
||||||
{% include "fragments/flash.html" %}
|
{% include "fragments/flash.html" %}
|
||||||
<div class="panel">
|
<base-form inline-template>
|
||||||
{% block portfolio_header %}
|
{% if task_order_id %}
|
||||||
{% include "portfolios/header.html" %}
|
{% set action = url_for("task_orders.update", portfolio_id=portfolio.id, task_order_id=task_order_id) %}
|
||||||
{% endblock %}
|
{% else %}
|
||||||
<base-form inline-template>
|
{% set action = url_for("task_orders.update", portfolio_id=portfolio.id) %}
|
||||||
<form id="new-task-order" action='{{ url_for("task_orders.update", portfolio_id=portfolio.id) }}' method="POST" autocomplete="off">
|
{% endif %}
|
||||||
{{ form.csrf_token }}
|
<form id="new-task-order" action='{{ action }}' method="POST" autocomplete="off" enctype="multipart/form-data">
|
||||||
<div class="panel__content">
|
{{ form.csrf_token }}
|
||||||
<!-- TODO: implement save bar with component -->
|
<!-- TODO: implement save bar with component -->
|
||||||
<span class="h3">Add Funding</span>
|
<span class="h3">Add Funding</span>
|
||||||
<a
|
<a
|
||||||
href="{{ cancel_url }}"
|
href="{{ cancel_url }}"
|
||||||
class="action-group__action icon-link">
|
class="action-group__action icon-link">
|
||||||
<span class="icon icon--x"></span>
|
<span class="icon icon--x"></span>
|
||||||
{{ "common.cancel" | translate }}
|
{{ "common.cancel" | translate }}
|
||||||
</a>
|
</a>
|
||||||
{{ SaveButton(text=('common.save' | translate), element='input', form='new-task-order') }}
|
{{ SaveButton(text=('common.save' | translate), element='input', form='new-task-order') }}
|
||||||
</div>
|
<p>
|
||||||
<div class="panel__content">
|
{{ "task_orders.new.form_help_text" | translate }}
|
||||||
{{ "task_orders.new.form_help_text" | translate }}
|
</p>
|
||||||
<hr>
|
<hr>
|
||||||
{{ TextInput(form.number, validation='taskOrderNumber') }}
|
{{ TextInput(form.number, validation='taskOrderNumber') }}
|
||||||
</div>
|
{{ UploadInput(form.pdf) }}
|
||||||
</form>
|
</form>
|
||||||
</base-form>
|
</base-form>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -1,13 +1,7 @@
|
|||||||
from wtforms.validators import ValidationError, StopValidation
|
from wtforms.validators import ValidationError, StopValidation
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from atst.forms.validators import (
|
from atst.forms.validators import *
|
||||||
Name,
|
|
||||||
IsNumber,
|
|
||||||
PhoneNumber,
|
|
||||||
ListItemsUnique,
|
|
||||||
RequiredIf,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class TestIsNumber:
|
class TestIsNumber:
|
||||||
@ -97,3 +91,12 @@ class TestRequiredIf:
|
|||||||
|
|
||||||
with pytest.raises(StopValidation):
|
with pytest.raises(StopValidation):
|
||||||
validator(dummy_form, dummy_field)
|
validator(dummy_form, dummy_field)
|
||||||
|
|
||||||
|
|
||||||
|
class TestFileLength:
|
||||||
|
def test_FileLength(self, dummy_form, dummy_field, pdf_upload):
|
||||||
|
validator = FileLength(max_length=1)
|
||||||
|
dummy_field.data = pdf_upload
|
||||||
|
|
||||||
|
with pytest.raises(ValidationError):
|
||||||
|
validator(dummy_form, dummy_field)
|
||||||
|
@ -3,7 +3,7 @@ from flask import url_for
|
|||||||
|
|
||||||
from atst.domain.permission_sets import PermissionSets
|
from atst.domain.permission_sets import PermissionSets
|
||||||
from atst.domain.task_orders import TaskOrders
|
from atst.domain.task_orders import TaskOrders
|
||||||
from atst.models.attachment import Attachment
|
from atst.models import Attachment, TaskOrder
|
||||||
from atst.utils.localization import translate
|
from atst.utils.localization import translate
|
||||||
|
|
||||||
from tests.factories import (
|
from tests.factories import (
|
||||||
@ -39,13 +39,15 @@ def test_task_orders_new(client, user_session, portfolio):
|
|||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
def test_task_orders_create(client, user_session, portfolio):
|
def test_task_orders_create(client, user_session, portfolio, pdf_upload, session):
|
||||||
user_session(portfolio.owner)
|
user_session(portfolio.owner)
|
||||||
|
data = {"number": "0123456789", "pdf": pdf_upload}
|
||||||
response = client.post(
|
response = client.post(
|
||||||
url_for("task_orders.update", portfolio_id=portfolio.id),
|
url_for("task_orders.update", portfolio_id=portfolio.id), data=data
|
||||||
data={"number": "0123456789"},
|
|
||||||
)
|
)
|
||||||
assert response.status_code == 302
|
assert response.status_code == 302
|
||||||
|
task_order = session.query(TaskOrder).filter_by(number=data["number"]).one()
|
||||||
|
assert task_order.pdf.filename == pdf_upload.filename
|
||||||
|
|
||||||
|
|
||||||
def test_task_orders_create_invalid_data(client, user_session, portfolio):
|
def test_task_orders_create_invalid_data(client, user_session, portfolio):
|
||||||
@ -54,17 +56,53 @@ def test_task_orders_create_invalid_data(client, user_session, portfolio):
|
|||||||
response = client.post(
|
response = client.post(
|
||||||
url_for("task_orders.update", portfolio_id=portfolio.id), data={"number": ""}
|
url_for("task_orders.update", portfolio_id=portfolio.id), data={"number": ""}
|
||||||
)
|
)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 400
|
||||||
assert num_task_orders == len(portfolio.task_orders)
|
assert num_task_orders == len(portfolio.task_orders)
|
||||||
assert "There were some errors" in response.data.decode()
|
assert "There were some errors" in response.data.decode()
|
||||||
|
|
||||||
|
|
||||||
def test_task_orders_edit():
|
def test_task_orders_update(client, user_session, portfolio, pdf_upload):
|
||||||
pass
|
user_session(portfolio.owner)
|
||||||
|
data = {"number": "0123456789", "pdf": pdf_upload}
|
||||||
|
task_order = TaskOrderFactory.create(number="0987654321")
|
||||||
|
response = client.post(
|
||||||
|
url_for(
|
||||||
|
"task_orders.update", portfolio_id=portfolio.id, task_order_id=task_order.id
|
||||||
|
),
|
||||||
|
data=data,
|
||||||
|
)
|
||||||
|
assert response.status_code == 302
|
||||||
|
assert task_order.number == data["number"]
|
||||||
|
|
||||||
|
|
||||||
def test_task_orders_update():
|
def test_task_orders_update_pdf(
|
||||||
pass
|
client, user_session, portfolio, pdf_upload, pdf_upload2
|
||||||
|
):
|
||||||
|
user_session(portfolio.owner)
|
||||||
|
task_order = TaskOrderFactory.create(pdf=pdf_upload)
|
||||||
|
data = {"number": "0123456789", "pdf": pdf_upload2}
|
||||||
|
response = client.post(
|
||||||
|
url_for(
|
||||||
|
"task_orders.update", portfolio_id=portfolio.id, task_order_id=task_order.id
|
||||||
|
),
|
||||||
|
data=data,
|
||||||
|
)
|
||||||
|
assert response.status_code == 302
|
||||||
|
assert task_order.pdf.filename == pdf_upload2.filename
|
||||||
|
|
||||||
|
|
||||||
|
def test_task_orders_update_delete_pdf(client, user_session, portfolio, pdf_upload):
|
||||||
|
user_session(portfolio.owner)
|
||||||
|
task_order = TaskOrderFactory.create(pdf=pdf_upload)
|
||||||
|
data = {"number": "0123456789", "pdf": None}
|
||||||
|
response = client.post(
|
||||||
|
url_for(
|
||||||
|
"task_orders.update", portfolio_id=portfolio.id, task_order_id=task_order.id
|
||||||
|
),
|
||||||
|
data=data,
|
||||||
|
)
|
||||||
|
assert response.status_code == 302
|
||||||
|
assert task_order.pdf is None
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skip(reason="Update after implementing new TO form")
|
@pytest.mark.skip(reason="Update after implementing new TO form")
|
||||||
|
@ -336,6 +336,7 @@ forms:
|
|||||||
list_items_unique_message: Items must be unique
|
list_items_unique_message: Items must be unique
|
||||||
name_message: 'This field accepts letters, numbers, commas, apostrophes, hyphens, and periods.'
|
name_message: 'This field accepts letters, numbers, commas, apostrophes, hyphens, and periods.'
|
||||||
phone_number_message: Please enter a valid 5 or 10 digit phone number.
|
phone_number_message: Please enter a valid 5 or 10 digit phone number.
|
||||||
|
file_length: Your file may not exceed 50 MB.
|
||||||
fragments:
|
fragments:
|
||||||
edit_application_form:
|
edit_application_form:
|
||||||
explain: AT-AT allows you to create multiple applications within a portfolio. Each application can then be broken down into its own customizable environments.
|
explain: AT-AT allows you to create multiple applications within a portfolio. Each application can then be broken down into its own customizable environments.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user