Merge pull request #19 from dod-ccpo/save_data
Form objects and validation
This commit is contained in:
commit
373998f42d
1
Pipfile
1
Pipfile
@ -7,6 +7,7 @@ name = "pypi"
|
|||||||
tornado = "==5.0.2"
|
tornado = "==5.0.2"
|
||||||
webassets = "==0.12.1"
|
webassets = "==0.12.1"
|
||||||
Unipath = "==1.1"
|
Unipath = "==1.1"
|
||||||
|
wtforms-tornado = "*"
|
||||||
|
|
||||||
[dev-packages]
|
[dev-packages]
|
||||||
pytest = "==3.6.0"
|
pytest = "==3.6.0"
|
||||||
|
16
Pipfile.lock
generated
16
Pipfile.lock
generated
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"_meta": {
|
"_meta": {
|
||||||
"hash": {
|
"hash": {
|
||||||
"sha256": "391e254ddb902877afca9c07aa2306710ce6d1e207b029c1a8b5dc0115ee99a5"
|
"sha256": "2299d6143992989417d01bd11d7d99a8a239a388321dc1546815d8b49c8151b8"
|
||||||
},
|
},
|
||||||
"pipfile-spec": 6,
|
"pipfile-spec": 6,
|
||||||
"requires": {
|
"requires": {
|
||||||
@ -41,6 +41,20 @@
|
|||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==0.12.1"
|
"version": "==0.12.1"
|
||||||
|
},
|
||||||
|
"wtforms": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:0cdbac3e7f6878086c334aa25dc5a33869a3954e9d1e015130d65a69309b3b61",
|
||||||
|
"sha256:e3ee092c827582c50877cdbd49e9ce6d2c5c1f6561f849b3b068c1b8029626f1"
|
||||||
|
],
|
||||||
|
"version": "==2.2.1"
|
||||||
|
},
|
||||||
|
"wtforms-tornado": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:dadb5e504d01f14bf75900f592888bb402ada6b8f8235fe583359f562d351a3a"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==0.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"develop": {
|
"develop": {
|
||||||
|
1
app.py
1
app.py
@ -1,6 +1,5 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
|
||||||
import os
|
|
||||||
import tornado.ioloop
|
import tornado.ioloop
|
||||||
|
|
||||||
from atst.app import make_app, make_config
|
from atst.app import make_app, make_config
|
||||||
|
3
atst.ini.example
Normal file
3
atst.ini.example
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
[server]
|
||||||
|
secret = change_me_into_something_secret
|
||||||
|
cac_url = http://localhost:8888/home
|
@ -16,7 +16,7 @@ from atst.api_client import ApiClient
|
|||||||
ENV = os.getenv("TORNADO_ENV", "dev")
|
ENV = os.getenv("TORNADO_ENV", "dev")
|
||||||
|
|
||||||
|
|
||||||
def make_app(config):
|
def make_app(config,**kwargs):
|
||||||
|
|
||||||
authz_client = ApiClient(config["default"]["AUTHZ_BASE_URL"])
|
authz_client = ApiClient(config["default"]["AUTHZ_BASE_URL"])
|
||||||
authnid_client = ApiClient(config["default"]["AUTHNID_BASE_URL"])
|
authnid_client = ApiClient(config["default"]["AUTHNID_BASE_URL"])
|
||||||
@ -60,8 +60,10 @@ def make_app(config):
|
|||||||
template_path = home.child('templates'),
|
template_path = home.child('templates'),
|
||||||
static_path = home.child('static'),
|
static_path = home.child('static'),
|
||||||
cookie_secret=config["default"]["COOKIE_SECRET"],
|
cookie_secret=config["default"]["COOKIE_SECRET"],
|
||||||
debug=config['default'].getboolean('DEBUG')
|
debug=config['default'].getboolean('DEBUG'),
|
||||||
|
**kwargs
|
||||||
)
|
)
|
||||||
|
app.config = config
|
||||||
return app
|
return app
|
||||||
|
|
||||||
|
|
||||||
|
8
atst/forms/date.py
Normal file
8
atst/forms/date.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
from wtforms.fields.html5 import IntegerField
|
||||||
|
from wtforms.validators import Required, ValidationError
|
||||||
|
from wtforms_tornado import Form
|
||||||
|
|
||||||
|
class DateForm(Form):
|
||||||
|
month = IntegerField('Month', validators=[Required()])
|
||||||
|
day = IntegerField('Day', validators=[Required()])
|
||||||
|
year = IntegerField('Year', validators=[Required()])
|
4
atst/forms/funding.py
Normal file
4
atst/forms/funding.py
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
from wtforms_tornado import Form
|
||||||
|
|
||||||
|
class FundingForm(Form):
|
||||||
|
pass
|
4
atst/forms/organization_info.py
Normal file
4
atst/forms/organization_info.py
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
from wtforms_tornado import Form
|
||||||
|
|
||||||
|
class OrganizationInfoForm(Form):
|
||||||
|
pass
|
4
atst/forms/readiness.py
Normal file
4
atst/forms/readiness.py
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
from wtforms_tornado import Form
|
||||||
|
|
||||||
|
class ReadinessForm(Form):
|
||||||
|
pass
|
38
atst/forms/request.py
Normal file
38
atst/forms/request.py
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
from wtforms.fields.html5 import IntegerField
|
||||||
|
from wtforms.fields import RadioField, StringField, SelectField, TextAreaField
|
||||||
|
from wtforms.validators import Required, ValidationError
|
||||||
|
from wtforms_tornado import Form
|
||||||
|
from .date import DateForm
|
||||||
|
|
||||||
|
class RequestForm(Form):
|
||||||
|
application_name = StringField('Application name', validators=[Required()])
|
||||||
|
application_description = TextAreaField('Application description', validators=[Required()])
|
||||||
|
dollar_value = IntegerField('Estimated dollar value of use', validators=[Required()])
|
||||||
|
input_estimate = SelectField('How did you arrive at this estimate?', validators=[Required()],
|
||||||
|
choices=[('','- Select -'),
|
||||||
|
('calculator','CSP usage calculator'),
|
||||||
|
('B','Option B'),
|
||||||
|
('C','Option C') ])
|
||||||
|
# no way to apply a label to a whole nested form like this
|
||||||
|
date_start = DateForm()
|
||||||
|
period_of_performance = SelectField('Desired period of performance', validators=[Required()],
|
||||||
|
choices=[('','- Select -'),
|
||||||
|
('value1','30 days'),
|
||||||
|
('value2','60 days'),
|
||||||
|
('value3','90 days') ])
|
||||||
|
classification_level = RadioField('Classification level', validators=[Required()],
|
||||||
|
choices=[('unclassified', 'Unclassified'),
|
||||||
|
('secret', 'Secret'),
|
||||||
|
('top-secret', 'Top Secret') ])
|
||||||
|
primary_service_branch = StringField('Primary service branch usage', validators=[Required()])
|
||||||
|
cloud_model = RadioField('Cloud model service', validators=[Required()],
|
||||||
|
choices=[('iaas', 'IaaS'),
|
||||||
|
('paas', 'PaaS'),
|
||||||
|
('both', 'Both') ])
|
||||||
|
number_of_cores = IntegerField('Number of cores', validators=[Required()])
|
||||||
|
total_ram = IntegerField('Total RAM', validators=[Required()])
|
||||||
|
|
||||||
|
# this is just an example validation; obviously this is wrong.
|
||||||
|
def validate_total_ram(self,field):
|
||||||
|
if (field.data % 2) != 0:
|
||||||
|
raise ValidationError("RAM must be in increments of 2.")
|
4
atst/forms/review.py
Normal file
4
atst/forms/review.py
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
from wtforms_tornado import Form
|
||||||
|
|
||||||
|
class ReviewForm(Form):
|
||||||
|
pass
|
@ -3,8 +3,6 @@ from webassets import Environment, Bundle
|
|||||||
import tornado.web
|
import tornado.web
|
||||||
from atst.home import home
|
from atst.home import home
|
||||||
|
|
||||||
# module variables used by the handlers
|
|
||||||
|
|
||||||
assets = Environment(
|
assets = Environment(
|
||||||
directory = home.child('scss'),
|
directory = home.child('scss'),
|
||||||
url = '/static')
|
url = '/static')
|
||||||
@ -16,13 +14,14 @@ css = Bundle(
|
|||||||
|
|
||||||
assets.register( 'css', css )
|
assets.register( 'css', css )
|
||||||
helpers = {
|
helpers = {
|
||||||
'assets': assets
|
'assets': assets,
|
||||||
}
|
}
|
||||||
|
|
||||||
class BaseHandler(tornado.web.RequestHandler):
|
|
||||||
|
|
||||||
|
class BaseHandler(tornado.web.RequestHandler):
|
||||||
def get_template_namespace(self):
|
def get_template_namespace(self):
|
||||||
ns = super(BaseHandler, self).get_template_namespace()
|
ns = super(BaseHandler, self).get_template_namespace()
|
||||||
|
helpers['config'] = self.application.config
|
||||||
ns.update(helpers)
|
ns.update(helpers)
|
||||||
return ns
|
return ns
|
||||||
|
|
||||||
|
@ -1,9 +1,16 @@
|
|||||||
import tornado
|
import tornado
|
||||||
from atst.handler import BaseHandler
|
from atst.handler import BaseHandler
|
||||||
|
from atst.forms.request import RequestForm
|
||||||
|
from atst.forms.organization_info import OrganizationInfoForm
|
||||||
|
from atst.forms.funding import FundingForm
|
||||||
|
from atst.forms.readiness import ReadinessForm
|
||||||
|
from atst.forms.review import ReviewForm
|
||||||
|
import tornado.httputil
|
||||||
|
|
||||||
class RequestNew(BaseHandler):
|
class RequestNew(BaseHandler):
|
||||||
screens = [
|
screens = [
|
||||||
{ 'title' : 'Details of Use',
|
{ 'title' : 'Details of Use',
|
||||||
|
'form' : RequestForm,
|
||||||
'subitems' : [
|
'subitems' : [
|
||||||
{'title' : 'Application Details',
|
{'title' : 'Application Details',
|
||||||
'id' : 'application-details'},
|
'id' : 'application-details'},
|
||||||
@ -14,20 +21,48 @@ class RequestNew(BaseHandler):
|
|||||||
{'title' : 'Usage',
|
{'title' : 'Usage',
|
||||||
'id' : 'usage' },
|
'id' : 'usage' },
|
||||||
]},
|
]},
|
||||||
{ 'title' : 'Organizational Info', },
|
{
|
||||||
{ 'title' : 'Funding/Contracting', },
|
'title' : 'Organizational Info',
|
||||||
{ 'title' : 'Readiness Survey', },
|
'form' : OrganizationInfoForm,
|
||||||
{ 'title' : 'Review & Submit', }
|
},
|
||||||
|
{
|
||||||
|
'title' : 'Funding/Contracting',
|
||||||
|
'form' : FundingForm,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'title' : 'Readiness Survey',
|
||||||
|
'form' : ReadinessForm,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'title' : 'Review & Submit',
|
||||||
|
'form' : ReviewForm,
|
||||||
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
def initialize(self, page):
|
def initialize(self, page):
|
||||||
self.page = page
|
self.page = page
|
||||||
|
|
||||||
|
@tornado.web.authenticated
|
||||||
|
def post(self, screen = 1):
|
||||||
|
self.check_xsrf_cookie()
|
||||||
|
screen = int(screen)
|
||||||
|
form = self.screens[ screen - 1 ]['form'](self.request.arguments)
|
||||||
|
if form.validate():
|
||||||
|
where = self.application.default_router.reverse_url('request_form', str(screen + 1))
|
||||||
|
self.redirect(where)
|
||||||
|
else:
|
||||||
|
self.show_form(screen, form)
|
||||||
|
|
||||||
@tornado.web.authenticated
|
@tornado.web.authenticated
|
||||||
def get(self, screen = 1):
|
def get(self, screen = 1):
|
||||||
|
self.show_form(screen=screen)
|
||||||
|
|
||||||
|
def show_form(self, screen = 1, form = None):
|
||||||
|
if not form:
|
||||||
|
form = self.screens[ int(screen) - 1 ]['form'](self.request.arguments)
|
||||||
self.render( 'requests/screen-%d.html.to' % int(screen),
|
self.render( 'requests/screen-%d.html.to' % int(screen),
|
||||||
|
f = form,
|
||||||
page = self.page,
|
page = self.page,
|
||||||
screens = self.screens,
|
screens = self.screens,
|
||||||
current = int(screen),
|
current = int(screen),
|
||||||
next_screen = int(screen) + 1 )
|
next_screen = int(screen) + 1 )
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
from atst.handler import BaseHandler
|
from atst.handler import BaseHandler
|
||||||
import tornado
|
import tornado
|
||||||
import tornado.gen
|
|
||||||
|
|
||||||
mock_workspaces = [
|
mock_workspaces = [
|
||||||
{
|
{
|
||||||
|
@ -5,3 +5,5 @@ DEBUG = true
|
|||||||
AUTHZ_BASE_URL = http://localhost
|
AUTHZ_BASE_URL = http://localhost
|
||||||
AUTHNID_BASE_URL= http://localhost
|
AUTHNID_BASE_URL= http://localhost
|
||||||
COOKIE_SECRET = some-secret-please-replace
|
COOKIE_SECRET = some-secret-please-replace
|
||||||
|
SECRET = change_me_into_something_secret
|
||||||
|
CAC_URL = http://localhost:8888/home
|
||||||
|
2878
package-lock.json
generated
2878
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -25,4 +25,5 @@ fi
|
|||||||
script/bootstrap
|
script/bootstrap
|
||||||
|
|
||||||
# Symlink uswds fonts into the /static directory
|
# Symlink uswds fonts into the /static directory
|
||||||
|
rm -f ./static/fonts
|
||||||
ln -s ../node_modules/uswds/src/fonts ./static/fonts
|
ln -s ../node_modules/uswds/src/fonts ./static/fonts
|
||||||
|
@ -7,4 +7,4 @@ set -e
|
|||||||
cd "$(dirname "${0}")/.."
|
cd "$(dirname "${0}")/.."
|
||||||
|
|
||||||
# Run unit tests
|
# Run unit tests
|
||||||
pipenv run python -m pytest
|
pipenv run python -m pytest -s $*
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
<h1 class="usa-display">JEDI</h1>
|
<h1 class="usa-display">JEDI</h1>
|
||||||
|
|
||||||
<a class="usa-button" href='https://cac.atat.codes'><span>Sign In with CAC</span></a>
|
<a class="usa-button" href='{{ config['default'].get('cac_url','https://cac.atat.codes') }}'><span>Sign In with CAC</span></a>
|
||||||
<button class="usa-button" disabled>Sign In via MFA</button>
|
<button class="usa-button" disabled>Sign In via MFA</button>
|
||||||
|
|
||||||
</main>
|
</main>
|
||||||
|
24
templates/requests/screen-1-test.html.to
Normal file
24
templates/requests/screen-1-test.html.to
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
{% extends '../requests_new.html.to' %}
|
||||||
|
|
||||||
|
{% block form %}
|
||||||
|
|
||||||
|
form goes here
|
||||||
|
<br>
|
||||||
|
{% if f.errors %}
|
||||||
|
<b>There were some errors</b>
|
||||||
|
{% end %}
|
||||||
|
<hr>
|
||||||
|
{% autoescape None %}
|
||||||
|
{% for e in f.a.errors %}
|
||||||
|
<b>{{ e }}</b>
|
||||||
|
{% end %}
|
||||||
|
<br>
|
||||||
|
a: {{ f.a }}
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
b: {{ f.b }}
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
{% end %}
|
||||||
|
|
@ -2,102 +2,70 @@
|
|||||||
|
|
||||||
{% block form %}
|
{% block form %}
|
||||||
<h2>Details of Use</h2>
|
<h2>Details of Use</h2>
|
||||||
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Doloremque placeat distinctio accusamus quo temporibus facilis, dicta delectus asperiores. Nihil aut quod quibusdam id fugit, officia dolorum laudantium! Quidem tempora, aliquam.</p>
|
|
||||||
<form>
|
{% autoescape None %}
|
||||||
|
{% if f.errors %}
|
||||||
|
<b>There were some errors, see below.</b>
|
||||||
|
{% end %}
|
||||||
|
|
||||||
|
|
||||||
<h3 id="application-details">Application Details</h3>
|
<h3 id="application-details">Application Details</h3>
|
||||||
<p>These headings introduce, respectively, sections and subsections within your body copy. As you create these headings, follow the same guidelines that you use when writing section headings: Be succinct, descriptive, and precise.</p>
|
|
||||||
|
|
||||||
<button class="usa-button-secondary usa-button-active">New Application</button>
|
<button class="usa-button-secondary usa-button-active">New Application</button>
|
||||||
<button class="usa-button-secondary" disabled>Existing Application</button>
|
<button class="usa-button-secondary" disabled>Existing Application</button>
|
||||||
<button class="usa-button-secondary" disabled>Sandbox Application</button>
|
<button class="usa-button-secondary" disabled>Sandbox Application</button>
|
||||||
|
|
||||||
|
|
||||||
<label for="input-application-name">Application name</label>
|
{{ f.application_name.label }}
|
||||||
<input id="input-application-name" name="input-application-name" type="text" placeholder="What is the application name?">
|
{{ f.application_name(placeholder="What is the application name?") }}
|
||||||
|
|
||||||
<label for="input-application-description">Application description</label>
|
{{ f.application_description.label }}
|
||||||
<textarea id="input-application-description" name="input-application-description" placeholder="Describe the application"></textarea>
|
{{ f.application_description(placeholder="Describe the application") }}
|
||||||
|
|
||||||
<label for="input-type-textarea">Estimated dollar value of use</label>
|
{{ f.dollar_value.label }}
|
||||||
<input id="dollar-value" name="dollar-value" type="text" placeholder="$">
|
{{ f.dollar_value(placeholder="$") }}
|
||||||
|
|
||||||
<label for="input-estimate">How did you arrive at this estimate?</label>
|
{{ f.input_estimate.label }}
|
||||||
<select name="input-estimate" id="input-estimate">
|
{{ f.input_estimate }}
|
||||||
<option value="">- Select -</option>
|
|
||||||
<option value="value1">CSP usage calculator</option>
|
|
||||||
<option value="value2">Option B</option>
|
|
||||||
<option value="value3">Option C</option>
|
|
||||||
</select>
|
|
||||||
|
|
||||||
|
<b>NEW</b>
|
||||||
|
<hr>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<label for="input-start-date">Expected start date</label>
|
<label for="input-start-date">Expected start date</label>
|
||||||
<div class="usa-date-of-birth">
|
<div class="usa-date-of-birth">
|
||||||
<div class="usa-form-group usa-form-group-month">
|
<div class="usa-form-group usa-form-group-month">
|
||||||
<label for="date_start_1">Month</label>
|
{{ f.date_start.month.label }}
|
||||||
<input class="usa-input-inline" aria-describedby="dobHint" id="date_start_1" name="date_start_1" type="number" min="1" max="12" value="">
|
{{ f.date_start.month(min="1", max="12") }}
|
||||||
</div>
|
</div>
|
||||||
<div class="usa-form-group usa-form-group-day">
|
<div class="usa-form-group usa-form-group-day">
|
||||||
<label for="date_start_2">Day</label>
|
{{ f.date_start.day.label }}
|
||||||
<input class="usa-input-inline" aria-describedby="dobHint" id="date_start_2" name="date_start_2" type="number" min="1" max="31" value="">
|
{{ f.date_start.day(min="1", max="31") }}
|
||||||
</div>
|
</div>
|
||||||
<div class="usa-form-group usa-form-group-year">
|
<div class="usa-form-group usa-form-group-year">
|
||||||
<label for="date_start_3">Year</label>
|
{{ f.date_start.year.label }}
|
||||||
<input class="usa-input-inline" aria-describedby="dobHint" id="date_start_3" name="date_start_3" type="number" min="1900" max="2000" value="">
|
{{ f.date_start.year(min="2000", max="2040") }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
|
{{ f.period_of_performance.label }}
|
||||||
<label for="input-period-performance">Desired period of performance</label>
|
{{ f.period_of_performance }}
|
||||||
<select name="input-period-performance" id="input-period-performance">
|
|
||||||
<option value="">- Select -</option>
|
|
||||||
<option value="value1">30 days</option>
|
|
||||||
<option value="value2">60 days</option>
|
|
||||||
<option value="value3">90 days</option>
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
<fieldset class="usa-fieldset-inputs usa-sans">
|
<fieldset class="usa-fieldset-inputs usa-sans">
|
||||||
<label for="input-period-performance">Classification level</label>
|
{{ f.classification_level.label }}
|
||||||
<ul class="usa-unstyled-list">
|
{{ f.classification_level(class_="usa-unstyled-list") }}
|
||||||
<li>
|
|
||||||
<input id="unclassified" type="radio" checked name="classification-level" value="unclassified">
|
|
||||||
<label for="unclassified">Unclassified</label>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<input id="secret" type="radio" name="classification-level" value="secret">
|
|
||||||
<label for="secret">Secret</label>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<input id="top-secret" type="radio" name="classification-level" value="top-secret">
|
|
||||||
<label for="top-secret">Top Secret</label>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
|
{{ f.primary_service_branch.label }}
|
||||||
<label for="input-service-branch">Primary service branch usage</label>
|
{{ f.primary_service_branch(placeholder="Add tags associated with service branches") }}
|
||||||
<input id="service-branch" name="service-branch" type="text" placeholder="Add tags associated with service branches">
|
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
<fieldset class="usa-fieldset-inputs usa-sans">
|
<fieldset class="usa-fieldset-inputs usa-sans">
|
||||||
<label for="input-cloud-model">Cloud model service</label>
|
{{ f.cloud_model.label }}
|
||||||
<ul class="usa-unstyled-list">
|
{{ f.cloud_model(class_="usa-unstyled-list") }}
|
||||||
<li>
|
|
||||||
<input id="iaas" type="radio" name="cloud-level" value="iaas">
|
|
||||||
<label for="iaas">IaaS</label>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<input id="paas" type="radio" name="cloud-level" value="paas">
|
|
||||||
<label for="paas">PaaS</label>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<input id="iass-pass" type="radio" checked name="cloud-level" value="iass-pass">
|
|
||||||
<label for="iass-pass">Both</label>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
|
|
||||||
@ -106,12 +74,18 @@
|
|||||||
<h4 id="application-details">Computation</h4>
|
<h4 id="application-details">Computation</h4>
|
||||||
<p>These headings introduce, respectively, sections and subsections within your body copy. As you create these headings, follow the same guidelines that you use when writing section headings: Be succinct, descriptive, and precise.</p>
|
<p>These headings introduce, respectively, sections and subsections within your body copy. As you create these headings, follow the same guidelines that you use when writing section headings: Be succinct, descriptive, and precise.</p>
|
||||||
|
|
||||||
<label for="">Number of cores</label>
|
{{ f.number_of_cores.label }}
|
||||||
<input id="" name="" type="text" placeholder="Total cores">
|
{{ f.number_of_cores(placeholder="Total cores", min="1", max="32") }}
|
||||||
|
|
||||||
<label for="">Total RAM</label>
|
|
||||||
<input id="" name="" type="text" placeholder="Amount of RAM">
|
|
||||||
|
|
||||||
|
<!-- example field with custom validation -->
|
||||||
|
{{ f.total_ram.label }}
|
||||||
|
{{ f.total_ram(placeholder="Total RAM", min="1", max="32") }}
|
||||||
|
<!-- example validation errors -->
|
||||||
|
{% for e in f.total_ram.errors %}
|
||||||
|
<div class="usa-input-error-message">
|
||||||
|
{{ e }}
|
||||||
|
</div>
|
||||||
|
{% end %}
|
||||||
|
|
||||||
|
|
||||||
<!-- Storage -->
|
<!-- Storage -->
|
||||||
@ -143,15 +117,4 @@
|
|||||||
<input id="" name="" type="text" placeholder="Total number of environments">
|
<input id="" name="" type="text" placeholder="Total number of environments">
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<br><br>
|
|
||||||
|
|
||||||
<button class="usa-button-secondary">Create Application</button>
|
|
||||||
<br>
|
|
||||||
<button class="usa-button-secondary" disabled>Save & Continue</button>
|
|
||||||
|
|
||||||
</form>
|
|
||||||
|
|
||||||
|
|
||||||
{% end %}
|
{% end %}
|
||||||
|
@ -8,19 +8,21 @@
|
|||||||
|
|
||||||
<h1>New Request</h1>
|
<h1>New Request</h1>
|
||||||
|
|
||||||
|
|
||||||
<aside class="sidenav usa-width-one-third">
|
<aside class="sidenav usa-width-one-third">
|
||||||
{% include 'requests/sidebar.html.to' %}
|
{% include 'requests/sidebar.html.to' %}
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
<main class="main-content usa-width-two-thirds">
|
<main class="main-content usa-width-two-thirds">
|
||||||
|
|
||||||
|
<form method='POST' action='{{ reverse_url('request_form', current) }}'>
|
||||||
|
{% module xsrf_form_html() %}
|
||||||
{% block form %}
|
{% block form %}
|
||||||
form goes here
|
form goes here
|
||||||
{% end %}
|
{% end %}
|
||||||
{% block next %}
|
{% block next %}
|
||||||
<a class='usa-button usa-button-primary' href='{{ reverse_url('request_form',next_screen) }}'>Save & Continue</a>
|
<input type='submit' class='usa-button usa-button-primary' value='Save & Continue' />
|
||||||
{% end %}
|
{% end %}
|
||||||
|
</form>
|
||||||
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
14
tests/forms/test_request.py
Normal file
14
tests/forms/test_request.py
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import wtforms
|
||||||
|
import pytest
|
||||||
|
from atst.forms.request import RequestForm
|
||||||
|
|
||||||
|
form = RequestForm()
|
||||||
|
|
||||||
|
def test_form_has_expected_fields():
|
||||||
|
label = form.application_name.label
|
||||||
|
assert label.text == 'Application name'
|
||||||
|
|
||||||
|
def test_form_can_validate_total_ram():
|
||||||
|
form.application_name.data = 5
|
||||||
|
with pytest.raises(wtforms.validators.ValidationError):
|
||||||
|
form.validate_total_ram(form.application_name)
|
33
tests/handlers/test_request_new.py
Normal file
33
tests/handlers/test_request_new.py
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import re
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
ERROR_CLASS = 'usa-input-error-message'
|
||||||
|
|
||||||
|
@pytest.mark.gen_test
|
||||||
|
def test_submit_invalid_request_form(monkeypatch, http_client, base_url):
|
||||||
|
monkeypatch.setattr('atst.handlers.request_new.RequestNew.get_current_user', lambda s: True)
|
||||||
|
monkeypatch.setattr('atst.handlers.request_new.RequestNew.check_xsrf_cookie', lambda s: True)
|
||||||
|
# this just needs to send a known invalid form value
|
||||||
|
response = yield http_client.fetch(
|
||||||
|
base_url + "/requests/new",
|
||||||
|
method="POST",
|
||||||
|
headers={"Content-Type": "application/x-www-form-urlencoded"},
|
||||||
|
body="total_ram=5",
|
||||||
|
)
|
||||||
|
assert response.effective_url == base_url + '/requests/new'
|
||||||
|
assert re.search(ERROR_CLASS, response.body.decode())
|
||||||
|
|
||||||
|
@pytest.mark.gen_test
|
||||||
|
def test_submit_valid_request_form(monkeypatch, http_client, base_url):
|
||||||
|
monkeypatch.setattr('atst.handlers.request_new.RequestNew.get_current_user', lambda s: True)
|
||||||
|
monkeypatch.setattr('atst.handlers.request_new.RequestNew.check_xsrf_cookie', lambda s: True)
|
||||||
|
monkeypatch.setattr('atst.forms.request.RequestForm.validate', lambda s: True)
|
||||||
|
# this just needs to send a known invalid form value
|
||||||
|
response = yield http_client.fetch(
|
||||||
|
base_url + "/requests/new",
|
||||||
|
method="POST",
|
||||||
|
headers={"Content-Type": "application/x-www-form-urlencoded"},
|
||||||
|
body="meaning=42",
|
||||||
|
)
|
||||||
|
assert response.effective_url == base_url + '/requests/new/2'
|
Loading…
x
Reference in New Issue
Block a user