stc

a simple time card webapp
git clone _git@git.brennen.work:stc.git
Log | Files | Refs | README

commit 5a791de8cd934f3c0f1cf702c551fce58cd074a8
parent 951ec8c3ee0faf79c1d496c31f1425da1fdbf33c
Author: Youth Employment Program Production <youthemployment22@gmail.com>
Date:   Tue, 23 Apr 2024 12:38:16 -0600

Add branch collection mvc

Diffstat:
Mapp/__init__.py | 3+++
Aapp/branches/__init__.py | 5+++++
Aapp/branches/__pycache__/__init__.cpython-310.pyc | 0
Aapp/branches/__pycache__/forms.cpython-310.pyc | 0
Aapp/branches/__pycache__/routes.cpython-310.pyc | 0
Aapp/branches/forms.py | 19+++++++++++++++++++
Aapp/branches/routes.py | 160+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aapp/branches/templates/branch.html | 14++++++++++++++
Aapp/branches/templates/branches.html | 30++++++++++++++++++++++++++++++
Aapp/branches/templates/dev_branches.html | 13+++++++++++++
Aapp/branches/templates/error.html | 12++++++++++++
Aapp/branches/templates/form_branches.html | 22++++++++++++++++++++++
Aapp/branches/templates/new_branch.html | 22++++++++++++++++++++++
Aapp/branches/templates/update_branch.html | 25+++++++++++++++++++++++++
Mapp/forms.py | 5+++++
Mapp/routes.py | 69++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------
Mapp/templates/admin/agreements/projects/index.html | 1+
Mapp/templates/admin/agreements/projects/newproject.html | 1+
18 files changed, 384 insertions(+), 17 deletions(-)

diff --git a/app/__init__.py b/app/__init__.py @@ -13,4 +13,7 @@ app.register_blueprint(fleet_bp) #app.register_blueprint(fleet_bp, url_prefix='/ from app.equipment import bp as equipment_bp app.register_blueprint(equipment_bp) #app.register_blueprint(equipment_bp, url_prefix='/equipment') +from app.branches import bp as branch_bp +app.register_blueprint(branch_bp) #app.register_blueprint(equipment_bp, url_prefix='/equipment') + from app import routes, models diff --git a/app/branches/__init__.py b/app/branches/__init__.py @@ -0,0 +1,5 @@ +from flask import Blueprint + +bp = Blueprint('branches',__name__, template_folder='templates') + +from app.branches import routes diff --git a/app/branches/__pycache__/__init__.cpython-310.pyc b/app/branches/__pycache__/__init__.cpython-310.pyc Binary files differ. diff --git a/app/branches/__pycache__/forms.cpython-310.pyc b/app/branches/__pycache__/forms.cpython-310.pyc Binary files differ. diff --git a/app/branches/__pycache__/routes.cpython-310.pyc b/app/branches/__pycache__/routes.cpython-310.pyc Binary files differ. diff --git a/app/branches/forms.py b/app/branches/forms.py @@ -0,0 +1,19 @@ +from flask_wtf import FlaskForm +from wtforms import StringField, SubmitField, PasswordField, BooleanField, SelectField, TimeField, DateField, IntegerField +from wtforms.validators import DataRequired, optional, length, InputRequired, EqualTo + +class NewBranch(FlaskForm): + branch_name = StringField('Branch Name', validators=[DataRequired()]) + address = StringField('Address',validators=[DataRequired()]) + city = StringField('City',validators=[DataRequired()]) + state = StringField('State',validators=[DataRequired()]) + zipcode = StringField('Zipcode', validators=[DataRequired()]) + submit_branch = SubmitField('Add Branch') + +class UpdateBranch(FlaskForm): + branch_name = StringField('Branch Name') + address = StringField('Address') + city = StringField('City') + state = StringField('State') + zipcode = StringField('Zipcode') + update_branch= SubmitField('Update Branch') diff --git a/app/branches/routes.py b/app/branches/routes.py @@ -0,0 +1,160 @@ +from app import app +from flask_pymongo import PyMongo +from flask import render_template, redirect, url_for, flash, request +from flask_login import login_required +from app.branches import bp +from bson.objectid import ObjectId +import datetime, hashlib +import os +from app.branches.forms import NewBranch, UpdateBranch +#from app.meetings.update import +#from app.meetings.meeting import + +mongo = PyMongo(app) + +### Define fetch_meeting ### +#TRY TO MOVE TO app.meetings.meeting.py +class BranchNotFoundError(Exception): + pass + +def fetch_branch(branch_id): + branch = mongo.db.branch_collection.find_one({"_id":ObjectId(branch_id)}) + + if branch == None: + raise BranchNotFoundError(f'Branch Id {branch_id} returned None') + else: + return branch + +### BEGIN DEV ROUTES ### + +@bp.route('/branches/seed',methods=["GET","PUT"]) +@login_required +def branchesSeed(): + seeds = [ + { + "branch_name": "Dillon", + "address":"730 North Montana St", + "city":"Dillon", + "state":"MT", + "zipcode": "88888" + }, + { + "branch_name": "Salmon", + "address":"601 Lena St", + "city":"Salmon", + "state":"ID", + "zipcode": "99999" + }, + { + "branch_name":"BLM Internship", + "address":"730 North Montana St", + "city":"Dillon", + "state":"MT", + "zipcode": "777777" + } + ] + mongo.db.branch_collection.delete_many({}) + mongo.db.branch_collection.insert_many(seeds) + dev_branches = mongo.db.branch_collection.find() + return render_template('dev_branches.html',dev_branches=dev_branches) + +@bp.route('/branches/dev',methods=["GET"]) +@login_required +def allBranches(): + dev_branches = mongo.db.branch_collection.find({}) + return render_template('dev_branches.html',dev_branches=dev_branches) + +### END DEV ROUTES ### + +#### BEGIN ROUTES #### + +@bp.route('/branches',methods=["GET"]) +@bp.route('/branches/',methods=["GET"]) +@login_required +def branches(): + branches = mongo.db.branch_collection.find({}) + return render_template('branches.html',branches=branches) + +@bp.route('/branch/<branch_id>',methods=["GET"]) +@bp.route('/branch/<branch_id>/',methods=["GET"]) +def branch(branch_id): + try: + branch = fetch_branch(branch_id) + except BranchNotFoundError as e: + return render_template('error.html',error=e) + else: + return render_template('branch.html',branch=branch) + +@bp.route('/branch/new',methods=["GET","POST"]) +@login_required +def new_branch(): + form = NewBranch() + if form.validate_on_submit(): + try: + new_branch = {'branch_name':form.branch_name.data,'address':form.address.data,'city':form.city.data,'state':form.state.data,'zipcode':form.zipcode.data} + if form.branch_name.data == "": + new_branch['branch_name']=form.city.data + except Exception: + return "Error assigning form data" + else: + mongo.db.branch_collection.insert_one(new_branch) + flash("Created new branch!") + return redirect(url_for('branches.branches')) + +# if form.name.data != "": +# new_meeting['meeting_name']=form.name.data + return render_template('new_branch.html',form=form) + +@bp.route('/branch/<branch_id>/update',methods=["GET","POST"]) +@login_required +def update_branch(branch_id): + try: + branch = fetch_branch(branch_id) + except BranchNotFoundError as e: + return render_template('error.html',error=e) + else: + return render_template('update_branch.html',branch=branch) + +@bp.route('/branch/<branch_id>/<update>',methods=["GET","POST"]) +@login_required +def change_branch(branch_id,update): + form = UpdateBranch() + try: + branch = fetch_branch(branch_id) + except BranchNotFoundError as e: + return render_template('error.html',error=e) + else: + if form.validate_on_submit(): + if update == "branch_name": + mongo.db.branch_collection.update_one({'_id':branch['_id']},{'$set':{'branch_name':form.branch_name.data}}) + flash("Updated branch name from {} to {}".format(branch['branch_name'],form.branch_name.data)) + return redirect(url_for('branches.update_branch',branch_id=branch['_id'])) + if update == "address": + mongo.db.branch_collection.update_one({'_id':branch['_id']},{'$set':{'address':form.address.data}}) + flash("Updated address from {} to {}".format(branch['address'],form.address.data)) + return redirect(url_for('branches.update_branch',branch_id=branch['_id'])) + if update == "city": + mongo.db.branch_collection.update_one({'_id':branch['_id']},{'$set':{'city':form.city.data}}) + flash("Updated city from {} to {}".format(branch['city'],form.city.data)) + return redirect(url_for('branches.update_branch',branch_id=branch['_id'])) + if update == "state": + mongo.db.branch_collection.update_one({'_id':branch['_id']},{'$set':{'state':form.state.data}}) + flash("Updated state to {}".format(form.state.data)) + return redirect(url_for('branches.update_branch',branch_id=branch['_id'])) + if update == "zipcode": + mongo.db.branch_collection.update_one({'_id':branch['_id']},{'$set':{'zipcode':form.zipcode.data}}) + flash("Updated zipcode to {}".format(form.zipcode.data)) + return redirect(url_for('branches.update_branch',branch_id=branch['_id'])) + return render_template('form_branches.html',branch=branch,update=update,form=form) + +@bp.route('/branch/<branch_id>/remove',methods=["GET","POST"]) +@login_required +def remove_branch(branch_id): + try: + branch = fetch_branch(branch_id) + except BranchNotFoundError as e: + return render_template('error.html',error=e) + else: + mongo.db.branch_collection.delete_one(branch) + flash("Deleted branch {}".format(branch['branch_name'])) + return redirect(url_for('branches.branches')) diff --git a/app/branches/templates/branch.html b/app/branches/templates/branch.html @@ -0,0 +1,14 @@ +{% extends 'base.html' %} + +{% block title %}{{ branch['branch_name'] }} Branch{% endblock %} + +{% block content %} + {% if branch %} + <section class="hours-grid"> {# class="meeting">#} + <a href="{{ url_for('branches.branches') }}" class="action-button">back to branches</a> + <h2 style="color:red;align:left">{{ branch['branch_name'] }}</h2> + <div><p>{{ branch['address'] }}</p></div> + <a class="action-button" href="{{ url_for('branches.update_branch',branch_id=branch['_id']) }}">modify</a> + </section> + {% endif %} +{% endblock %} diff --git a/app/branches/templates/branches.html b/app/branches/templates/branches.html @@ -0,0 +1,30 @@ +{% extends 'base.html' %} + +{% block title %}Branches{% endblock %} + +{% block content %} + {% with messages = get_flashed_messages() %} + {% if messages %} + {% for message in messages %} + <div id="messagebanner"><p>{{ message }}</p></div> + {% endfor %} + {% endif %} + {% endwith %} + <section> + {% if branches %} + <section class="branches"> + <h2 style="color:red">Branches</h2> + {% for branch in branches %} + <a href="{{ url_for('branches.branch',branch_id=branch['_id']) }}"> + <h3>{{ branch.branch_name }}</h3> + <div>{{ branch.address }}</div> + <div>{{ branch['city'] }}</div> + <div>{{ branch['state'] }}</div> + <div>{{ branch['zipcode'] }}</div> + </a> + {% endfor %} + </section> + {% endif %} + <a href="{{ url_for('branches.new_branch') }}" class="action-button">Add new Branch</a> + </section> +{% endblock %} diff --git a/app/branches/templates/dev_branches.html b/app/branches/templates/dev_branches.html @@ -0,0 +1,13 @@ +{% extends 'base.html' %} + +{% block title %}DEV Seed Branches{% endblock %} + +{% block content %} + + {%- for x in dev_branches %} + {%- print(x) %} + </br> + </br> + {%- endfor %} + +{% endblock %} diff --git a/app/branches/templates/error.html b/app/branches/templates/error.html @@ -0,0 +1,12 @@ +{% extends 'base.html' %} + +{% block title %}Error{% endblock %} + +{% block content %} + +<div style="text-align:center;"> + <h3>{{ error }}</h3> + <a href="{{ url_for('branches.branches') }}">back to branches</a> +</div> + +{% endblock %} diff --git a/app/branches/templates/form_branches.html b/app/branches/templates/form_branches.html @@ -0,0 +1,22 @@ +{% extends 'base.html' %} + +{% block title %}Update Branch{% endblock %} + +{% block content %} +<section> + <a href="{{ url_for('branches.update_branch',branch_id=branch['branch_id']) }}">back to branch</a> + <h3>Update Branch</h3> +<form action="" method="POST" novalidate> + {{ form.hidden_tag() }} + {% for error in form.errors %} + <span style="color:red;">[{{ error }}]</span> + {% endfor %} + {% if update == "branch_name" %}{{ form.branch_name.label }}{{ form.branch_name() }}{% endif %} + {% if update == "address" %}{{ form.address.label }}{{ form.address() }}{% endif %} + {% if update == "city" %}{{ form.city.label }}{{ form.city() }}{% endif %} + {% if update == "state" %}{{ form.state.label }}{{ form.state() }}{% endif %} + {% if update == "zipcode" %}{{ form.zipcode.label }}{{ form.zipcode() }}{% endif %} + {{ form.update_branch() }} +</form> +</section> +{% endblock %} diff --git a/app/branches/templates/new_branch.html b/app/branches/templates/new_branch.html @@ -0,0 +1,22 @@ +{% extends 'base.html' %} + +{% block title %}New Branch{% endblock %} + +{% block content %} +<section> + <a href="{{ url_for('branches.branches') }}">back to branches</a> +<h3>Add new Branch</h3> +<form action="" method="POST" novalidate> + {{ form.hidden_tag() }} + {% for error in form.errors %} + <span style="color:red;">[{{ error }}]</span> + {% endfor %} + {{ form.branch_name.label }}{{ form.branch_name() }} + {{ form.address.label }}{{ form.address() }} + {{ form.city.label }}{{ form.city() }} + {{ form.state.label }}{{ form.state() }} + {{ form.zipcode.label }}{{ form.zipcode() }} + {{ form.submit_branch() }} +</form> +</section> +{% endblock %} diff --git a/app/branches/templates/update_branch.html b/app/branches/templates/update_branch.html @@ -0,0 +1,25 @@ +{% extends 'base.html' %} + +{% block title %}Update {{ branch.branch_name }}{% endblock %} + +{% block content %} + {% with messages = get_flashed_messages() %} + {% if messages %} + {% for message in messages %} + <div id="messagebanner"><p>{{ message }}</p></div> + {% endfor %} + {% endif %} + {% endwith %} + {% if branch %} + <section class="branch"> + <a href="{{ url_for('branches.branch',branch_id=branch['_id']) }}">back to branch</a> + <div style="display:flex"><h2>Date: {{ branch.branch_name }}</h2><a href="{{url_for('branches.change_branch',branch_id=branch["_id"],update="branch_name")}}"style="color:red">change</a></div> + <div style="display:flex"><h3>Address: {{ branch.address }}</h3><a href="{{url_for('branches.change_branch',branch_id=branch['_id'],update="address")}}"style="color:red">change</a></div> + <div style="display:flex"><h3>City: {{ branch.city }}</h3><a href="{{url_for('branches.change_branch',branch_id=branch['_id'],update="city")}}"style="color:red">change</a></div> + <div style="display:flex"><h3>State: {{ branch.state }}</h3><a href="{{url_for('branches.change_branch',branch_id=branch['_id'],update="state")}}"style="color:red">change</a></div> + <div style="display:flex"><h4 href="color:red">Zipcode: {{ branch.zipcode }}</h4><a href="{{ url_for('branches.change_branch',branch_id=branch['_id'],update="zipcode")}}" style="color:red">change</a></div> + <a href="{{ url_for('branches.branch',branch_id=branch['_id']) }}">back to branch</a> + <a href="{{ url_for('branches.remove_branch',branch_id=branch['_id']) }}">remove branch</a> + </section> + {% endif %} +{% endblock %} diff --git a/app/forms.py b/app/forms.py @@ -50,6 +50,10 @@ class ChangePasswordForm(FlaskForm): confpass = PasswordField('Confirm',validators=[DataRequired()]) changePassword = SubmitField('Change Password') +class ChangeBranchForm(FlaskForm): + branch = SelectField('Branch',validators=[DataRequired()]) + changeBranch = SubmitField('Change Branch') + class NewRoleForm(FlaskForm): rolename = StringField('Role Name', validators=[DataRequired()]) @@ -59,6 +63,7 @@ class ConfirmRemove(FlaskForm): class NewProjectForm(BudgetForm): projectName = StringField('Project Name', validators=[DataRequired()]) agreement = SelectField('Part of Agreement', validators=[DataRequired()]) + branch = SelectField('Organization Branch', validators=[DataRequired()]) createNewProject = SubmitField('Create New Project') class MoveProjectForm(FlaskForm): diff --git a/app/routes.py b/app/routes.py @@ -4,7 +4,7 @@ from app import app from flask_pymongo import PyMongo from flask_login import LoginManager from flask import render_template, url_for, request, flash, redirect -from app.forms import LoginForm, PunchclockinWidget, PunchclockoutWidget, FleetCheckoutForm, FleetCheckinForm, NewUserForm, AdmnPermissionsForm, DashPermissionsForm, NewHoursForm, ConfirmRemove, NewAgreementForm, RenameAgreementForm, NewProjectForm, MoveProjectForm, RenameProjectForm, CrewClockinWidget, NewUserHourForm, ChangePasswordForm, ChangeUserForm, upDate, updateTime, updateProject, newNote, dateRange +from app.forms import LoginForm, PunchclockinWidget, PunchclockoutWidget, FleetCheckoutForm, FleetCheckinForm, NewUserForm, AdmnPermissionsForm, DashPermissionsForm, NewHoursForm, ConfirmRemove, NewAgreementForm, RenameAgreementForm, NewProjectForm, MoveProjectForm, RenameProjectForm, CrewClockinWidget, NewUserHourForm, ChangePasswordForm, ChangeUserForm, upDate, updateTime, updateProject, newNote, dateRange, ChangeBranchForm from flask import request from werkzeug.urls import url_parse from werkzeug.security import generate_password_hash, check_password_hash @@ -541,27 +541,23 @@ def clockin_new_user(): @app.route("/newtime/<usernm>",methods=['GET','POST']) @login_required def new_time(usernm): -# clocked_in_users = mongo.db.time_collection.find({'clock_out': {'$exists':False}}) + user = mongo.db.user_collection.find_one({"username": usernm}) availableProjects = [] for project in mongo.db.projects_collection.find(): availableProjects.append((project['_id'],project['project_name'])) -# GET_CLOCKED out users -# clocked_out_active_users=[] -# clocked_in_active_users=[] -# for active in mongo.db.user_collection.find({'is_active':True},{'username':1,'fname':1,'mname':1,'lname':1}): -# funame = active['fname']+' '+active['lname'] -# alreadyin = [] -# for user in clocked_in_users: -# alreadyin.append(user['modified_by'][0]) -# if any(element in active['username'] for element in alreadyin): -# clocked_in_active_users.append((active['_id'],funame)) -# else: -# clocked_out_active_users.append((active['username'],funame)) +# availableProjects = [("","Select Project")] +# for project in mongo.db.projects_collection.find(): +# if 'branch' in project: +# if project['branch'] == 'Global' or project['branch'] == user['branch']: +# availableProjects.append((project['_id'],project['project_name'])) form=NewHoursForm() form.projectSel.choices = availableProjects if form.validate_on_submit(): # Possible bug iff user clocks in between page load and form submit... will create additional time_collection entry + # if form.projectSel.data == "" or form.projectSel.data == "Select Project": + # flash("You must Select a Project")# This part doesn't seem to function? + # return redirect(url_for('new_time',usernm=usernm)) dateentry = datetime.datetime.combine(form.dateSel.data,datetime.time()) starttime = datetime.datetime.combine(form.dateSel.data,form.startTime.data) endtime = datetime.datetime.combine(form.dateSel.data,form.endTime.data) @@ -581,9 +577,9 @@ def new_time(usernm): entryevent['note'] = form.note.data mongo.db.time_collection.insert_one(entryevent) - return redirect(url_for('hours',username=usrnm)) + return redirect(url_for('hours',username=user['username'])) - return render_template('dashboard/punchclock/otheruser.html',form=form,ORGNAME=OrganizationName) + return render_template('dashboard/punchclock/otheruser.html',user=user,form=form,ORGNAME=OrganizationName) @app.route("/newusertime",methods=['GET','POST']) @login_required @@ -994,6 +990,12 @@ def activeusers(): active = mongo.db.user_collection.find({'is_active':True}) return render_template('admin/users/active.html',activeusers=active,ORGNAME=OrganizationName) +@app.route("/user/<user_id>") +@login_required +def user(user_id): + usr = mongo.db.user_collection.find({"_id":ObjectId(user_id)}) + return render_template('admin/users/active.html',activeusers=usr,ORGNAME=OrganizationName) + @app.route("/admin/deactivate/<userid>",methods=['GET','POST']) @login_required def deactivate_user(userid): @@ -1216,8 +1218,14 @@ def newproject(): # END Available Agreements form = NewProjectForm() - # TODO If statement for optional newproject('argument') if new or none return all choices, else return (agreement_id, agreement_name) for new agreement ID passed + # TODO If statement for optional newproject('argument') if new or none return all choices, else return (agreement_id, agreement_name) for new agreement ID passed <-- What did I mean by this? OH It's a conditional to redirect to create a new agreement... this would require passing the form data to the next route as params? form.agreement.choices = availableAgreements + form.branch.choices = ['Dillon','Salmon'] + #TODO MAKE BELOW WORK!!! Apply to other branch dependent areas + #branches = [] + #for branch in mongo.db.branch_collection.find(): + # branches.append((branch['_id'],branch['location'])) + #form.branch.choices = branches if form.validate_on_submit(): # create deterministic agreement unique _id? Example being genpasswd in new user validate on submit @@ -1244,6 +1252,8 @@ def newproject(): mongo.db.projects_collection.insert_one({ 'project_name':form.projectName.data, 'agreement':ObjectId(form.agreement.data), + 'branch':form.branch.data, + #'branch':ObjectId(form.branch.data), 'budget':[{ 'labor':form.laborBudget.data, 'travel':form.travelBudget.data, @@ -1318,6 +1328,31 @@ def rename_project(project_id): return redirect(url_for('project', project_id=project_id)) return render_template('admin/agreements/projects/update/rename.html',form=form,ORGNAME=OrganizationName) +@app.route("/admin/update/branch/<collection>/<document_id>",methods=["GET","POST"]) +@login_required +def change_branch(collection,document_id): + form = ChangeBranchForm() + + #TODO MAKE BELOW WORK!!! Apply to other branch dependent areas + #branches = [] + #for branch in mongo.db.branch_collection.find(): + # branches.append((branch['_id'],branch['location'])) + #form.branch.choices = branches + form.branch.choices = ['Dillon','Salmon','Global'] + + if form.validate_on_submit(): + match collection: + case "project": + mongo.db.projects_collection.update_one({"_id":ObjectId(document_id)},{'$set':{'branch':form.branch.data}}) + flash("Changed Branch for Project {} to {}".format(document_id,form.branch.data)) + return redirect(url_for('project',project_id=document_id)) + case "user": + mongo.db.user_collection.update_one({"_id":ObjectId(document_id)},{'$set':{'branch':form.branch.data}}) + flash("Changed Branch for User {} to {}".format(document_id,form.branch.data)) + return redirect(url_for('user',user_id=document_id)) + + return render_template('admin/update/branch.html',form=form,ORGNAME=OrganizationName) + @app.route("/admin/move/project/<project_id>",methods=["GET","POST"]) @login_required def move_project(project_id): diff --git a/app/templates/admin/agreements/projects/index.html b/app/templates/admin/agreements/projects/index.html @@ -16,6 +16,7 @@ <dt>{{ project['project_name'] }}<a href="{{url_for('rename_project', project_id=project['_id'])}}"style="color:var(--accent);"> change</a></dt> <dd>ID: {{ project['_id'] }}</dd> <dd>A_ID:{{ project['agreement']}}<a href="{{url_for('move_project',project_id=project['_id'])}}"style="color:var(--accent);"> change</a></dd> + <dd>Branch:{{ project['branch']}}<a href="{{url_for('change_branch',collection='project',document_id=project['_id'])}}"style="color:var(--accent);"> change</a></dd> <dd>Budget:{{ project['budget']}}</dd> <dd>Costs:{{ project['costs']}}</dd> <dd><a href="{{url_for('remove_project',project_id=project['_id'])}}" style="color:var(--accent);">REMOVE</a></dd> diff --git a/app/templates/admin/agreements/projects/newproject.html b/app/templates/admin/agreements/projects/newproject.html @@ -15,6 +15,7 @@ {% endfor %} {{ form.projectName.label }}{{ form.projectName() }}<br> {{ form.agreement.label }}{{ form.agreement() }}<br> + {{ form.branch.label }}{{ form.branch() }}<br> <h4>Budget</h4> {{ form.laborBudget.label }}{{ form.laborBudget() }}<br> {{ form.travelBudget.label }}{{ form.travelBudget() }}<br>