# Kanbanara Projects Component
# Written by Rebecca Shalfield between 2013 and 2017
# Copyright (c) Rebecca Shalfield 2013-2017
# Released under the GNU AGPL v3

import datetime
import json
import logging
import os
import re
import urllib.parse
import zipfile

import cherrypy
from cherrypy.lib.static import serve_file
from kanbanara import Kanbanara
from pymongo import MongoClient


class Projects(Kanbanara):

    @cherrypy.expose
    def add_category(self, project, category, colour):
        """Allows a category to be added to a project"""
        Kanbanara.check_authentication(f'/{self.component}')
        Kanbanara.cookie_handling(self)
        if project and category:
            for project_document in self.projects_collection.find({'project': project}):
                if 'categories' in project_document and project_document['categories']:
                    project_document['categories'].append({'category': category, 'colour': colour})
                else:
                    project_document['categories'] = [{'category': category, 'colour': colour}]

                self.projects_collection.save(project_document)
                self.save_project_as_json(project_document)
                break

        raise cherrypy.HTTPRedirect("/projects/update_project?project="+project, 302)

    @cherrypy.expose
    def add_project_announcement(self, project, announcement, startdate, enddate):
        """Allows an announcement to be added to a project"""
        Kanbanara.check_authentication(f'/{self.component}')
        Kanbanara.cookie_handling(self)
        if project and announcement and startdate and enddate:
            for project_document in self.projects_collection.find({'project': project}):
                announcement_document = {'announcement': announcement}
                announcement_document['startdate'] = self.dashed_date_to_datetime_object(startdate)
                announcement_document['enddate'] = self.dashed_date_to_datetime_object(enddate)
                if 'announcements' in project_document and project_document['announcements']:
                    project_document['announcements'].append(announcement_document)
                else:
                    project_document['announcements'] = [announcement_document]

                self.projects_collection.save(project_document)
                self.save_project_as_json(project_document)
                break

        raise cherrypy.HTTPRedirect("/projects/update_project?project="+project, 302)
        
    @cherrypy.expose
    def add_custom_attribute(self, project, custom_attribute):
        """Allows a custom card attribute to be added to a project"""
        Kanbanara.check_authentication(f'/{self.component}')
        Kanbanara.cookie_handling(self)
        if custom_attribute:
            for project_document in self.projects_collection.find({'project': project}):
                custom_attributes = {}
                if 'customattributes' in project_document and project_document['customattributes']:
                    custom_attributes = project_document['customattributes']

                if custom_attribute not in custom_attributes:
                    custom_attributes[custom_attribute] = True

                project_document['customattributes'] = custom_attributes
                self.projects_collection.save(project_document)
                self.save_project_as_json(project_document)
                break

        raise cherrypy.HTTPRedirect("/projects/update_project?project="+project, 302)

    @cherrypy.expose
    def add_custom_state(self, project, custom_state, metastate):
        """Allows a custom state mapped onto a metastate to be added to a project"""
        Kanbanara.check_authentication(f'/{self.component}')
        Kanbanara.cookie_handling(self)
        if custom_state and metastate and custom_state not in self.metastates_list:
            for project_document in self.projects_collection.find({'project': project}):
                custom_states = {}
                if 'customstates' in project_document and project_document['customstates']:
                    custom_states = project_document['customstates']

                if custom_state not in custom_states:
                    custom_states[custom_state] = metastate

                project_document['customstates'] = custom_states
                self.projects_collection.save(project_document)
                self.save_project_as_json(project_document)
                break

        raise cherrypy.HTTPRedirect("/projects/update_project?project="+project, 302)

    @cherrypy.expose
    def add_release(self, project, release, start_date=0, end_date=0, budget=0):
        """Allows a release to be added to a project"""
        Kanbanara.check_authentication(f'/{self.component}')
        Kanbanara.cookie_handling(self)
        if project and release:
            if not self.projects_collection.find({"project": project, 'releases.release': release}).count():
                for project_document in self.projects_collection.find({"project": project}):
                    release_document = {'release': release}
                    if start_date:
                        release_document['start_date'] = self.dashed_date_to_datetime_object(start_date)

                    if end_date:
                        release_document['end_date'] = self.dashed_date_to_datetime_object(end_date)

                    if budget:
                        release_document['budget'] = int(budget)

                    if 'releases' in project_document:
                        project_document['releases'].append(release_document)
                    else:
                        project_document['releases'] = [release_document]

                    self.projects_collection.save(project_document)
                    self.save_project_as_json(project_document)
                    break

        raise cherrypy.HTTPRedirect("/projects/update_project?project="+project, 302)

    @cherrypy.expose
    def add_subteam(self, project, subteam):
        """Allows a subteam to be added to a project as a subset of its entire team"""
        Kanbanara.check_authentication(f'/{self.component}')
        Kanbanara.cookie_handling(self)
        if project and subteam:
            for project_document in self.projects_collection.find({'project': project}):
                if 'subteams' in project_document and project_document['subteams']:
                    project_document['subteams'].append({'subteam': subteam})
                else:
                    project_document['subteams'] = [{'subteam': subteam}]

                self.projects_collection.save(project_document)
                self.save_project_as_json(project_document)
                break

        raise cherrypy.HTTPRedirect("/projects/update_project?project="+project, 302)

    @cherrypy.expose
    def backup_project(self, project):
        """Allows a project to be backedup by being exported in JSON format"""
        username = Kanbanara.check_authentication(f'/{self.component}')
        session_id = Kanbanara.cookie_handling(self)
        session_document = self.sessions_collection.find_one({"session_id": session_id})
        zippath = os.path.join(self.current_dir, '..', 'downloads', username+'_project_'+project+'_backup.zip')
        epoch = datetime.datetime.utcnow()
        while zippath in self.file_locks and self.file_locks[zippath] > epoch - self.timedelta_minute:
            True

        with zipfile.ZipFile(zippath, 'w') as myzip:
            self.file_locks[zippath] = datetime.datetime.utcnow()

            projectjsonpath = os.path.join(self.current_dir, '..', 'downloads', username+'_project_'+project+'_project.json')
            with open(projectjsonpath, 'w') as outfile:
                tidied_documents = []
                for document in self.projects_collection.find({'project': project}):
                    for unwanted in ['_id']:
                        del document[unwanted]

                    tidied_documents.append(document)
                    break

                json.dump(tidied_documents, outfile, indent=4, default=self.json_type_handler)

            myzip.write(projectjsonpath, os.path.relpath(projectjsonpath, os.path.join(self.current_dir, '..', 'downloads')))

            cardsjsonpath = os.path.join(self.current_dir, '..', 'downloads', username+'_project_'+project+'_cards.json')
            with open(cardsjsonpath, 'w') as outfile:
                tidied_documents = []
                for document in self.cards_collection.find({'project': project}):
                    for unwanted in ['_id']:
                        del document[unwanted]

                    tidied_documents.append(document)

                json.dump(tidied_documents, outfile, indent=4, default=self.json_type_handler)

            myzip.write(cardsjsonpath, os.path.relpath(cardsjsonpath, os.path.join(self.current_dir, '..', 'downloads')))
            del self.file_locks[zippath]

        return serve_file(zippath, "application/x-download", "attachment")

    def global_work_in_progress(self, project_manager):
        """Allows a project's global Work In Progress (WIP) settings to be updated"""
        session_id = Kanbanara.cookie_handling(self)
        session_document = self.sessions_collection.find_one({"session_id": session_id})
        member_document = Kanbanara.get_member_document(self, session_document)
        project = member_document.get('project', '')
        project_document = self.projects_collection.find_one({'project': project})
        if project_document:
            workflow = project_document.get('workflow', [])
            workflow_index = project_document.get('workflow_index', {})
            global_wips = project_document.get('global_wips', {})
        else:
            workflow = []
            workflow_index = {}
            global_wips = {}

        condensed_column_states = workflow_index.get('condensed_column_states', [])
        wip_grouping = ""
        wip_scope = ""
        wip_limit = ""
        closedwip = "1 month"
        enforce_wip_limits = False

        if 'wip_grouping' in global_wips and global_wips['wip_grouping']:
            wip_grouping = global_wips['wip_grouping']

        if 'wip_scope' in global_wips and global_wips['wip_scope']:
            wip_scope = global_wips['wip_scope']

        if 'wip_limit' in global_wips and global_wips['wip_limit']:
            wip_limit = global_wips['wip_limit']

        if 'closedwip' in global_wips and global_wips['closedwip']:
            closedwip = global_wips['closedwip']

        if 'enforcewiplimits' in global_wips:
            enforce_wip_limits = global_wips['enforcewiplimits']

        stepbuffermaxwip = {}
        stepbufferminwip = {}
        stepcounterpartmaxwip = {}
        stepcounterpartminwip = {}
        stepmainmaxwip = {}
        stepmainminwip = {}
        stepsharedmaxwip = {}
        for step_no in range(12):
            stepsharedmaxwip[step_no] = 0
            stepmainminwip[step_no] = 0
            stepmainmaxwip[step_no] = -1
            stepcounterpartminwip[step_no] = 0
            stepcounterpartmaxwip[step_no] = -1
            stepbufferminwip[step_no] = 0
            stepbuffermaxwip[step_no] = -1
            if member_document:
                if 'step'+str(step_no)+'sharedmaxwip' in global_wips:
                    stepsharedmaxwip[step_no] = global_wips['step'+str(step_no)+'sharedmaxwip']

                if 'step'+str(step_no)+'mainminwip' in global_wips:
                    stepmainminwip[step_no] = global_wips['step'+str(step_no)+'mainminwip']

                if 'step'+str(step_no)+'mainmaxwip' in global_wips:
                    stepmainmaxwip[step_no] = global_wips['step'+str(step_no)+'mainmaxwip']

                if 'step'+str(step_no)+'counterpartminwip' in global_wips:
                    stepcounterpartminwip[step_no] = global_wips['step'+str(step_no)+'counterpartminwip']

                if 'step'+str(step_no)+'counterpartmaxwip' in global_wips:
                    stepcounterpartmaxwip[step_no] = global_wips['step'+str(step_no)+'counterpartmaxwip']

                if 'step'+str(step_no)+'bufferminwip' in global_wips:
                    stepbufferminwip[step_no] = global_wips['step'+str(step_no)+'bufferminwip']

                if 'step'+str(step_no)+'buffermaxwip' in global_wips:
                    stepbuffermaxwip[step_no] = global_wips['step'+str(step_no)+'buffermaxwip']

        content = []
        content.append('<table class="form"><tr><td>')
        if project_manager:
            content.append('<form action="/projects/update_global_wip" method="post"><input type="hidden" name="project" value="'+project+'">')

        content.append('<table><tr><th>Grouping</th><th>Scope</th><th>Limit</th><td></td></tr><tr>')

        if project_manager:
            content.append('<td><select name="grouping">')
            content.append('<option class="warning" value="">Please select...</option>')
            for grouping in ['Whole Team', 'Team Member']:
                if grouping == wip_grouping:
                    content.append('<option value="'+grouping+'" selected>'+grouping+'</option>')
                else:
                    content.append('<option value="'+grouping+'">'+grouping+'</option>')

            content.append('</select></td>')
        else:
            content.append('<td>'+wip_grouping+'</td>')

        if project_manager:
            content.append('<td><select name="scope">')
            content.append('<option class="warning" value="">Please select...</option>')
            for scope in ['Whole Board', 'Column', 'Estimate']:
                if scope == wip_scope:
                    content.append('<option value="'+scope+'" selected>'+scope+'</option>')
                else:
                    content.append('<option value="'+scope+'">'+scope+'</option>')

            content.append('</select></td>')
        else:
            content.append('<td>'+wip_scope+'</td>')

        if project_manager:
            content.append('<td>')
            if wip_limit:
                content.append(f'<input type="number" name="limit" size="4" min="1" value="{wip_limit}">')
            else:
                content.append('<input type="number" name="limit" size="4" min="1" value="2">')

            content.append('</td>')
        else:
            content.append(f'<td>{wip_limit}</td>')

        content.append('</tr></table>')

        content.append('<table><tr><td></td>')
        for step_document in workflow:
            step_name = step_document['step']
            if step_name:
                number_of_columns = 0
                metastate = ""
                for column in ['maincolumn', 'counterpartcolumn', 'buffercolumn']:
                    if column in step_document:
                        number_of_columns += 1
                        if not metastate:
                            variable_column = step_document[column]
                            state = variable_column['state']
                            if state in self.metastates_list:
                                metastate = state
                            else:
                                custom_states = project_document.get('customstates', {})
                                if state in custom_states:
                                    metastate = custom_states[state]

                if number_of_columns:
                    if number_of_columns > 1:
                        content.append(f'<th id="{metastate}step" colspan="{number_of_columns}">{step_name}</th>')
                    else:
                        content.append(f'<th id="{metastate}step">{step_name}</th>')

        content.append('</tr><tr><td></td>')
        for step_document in workflow:
            step_name = step_document['step']
            if step_name:
                if 'maincolumn' in step_document:
                    main_column = step_document['maincolumn']
                    column_name = main_column['name']
                    state = main_column['state']
                    if state in self.metastates_list:
                        metastate = state
                    else:
                        custom_states = project_document.get('customstates', {})
                        if state in custom_states:
                            metastate = custom_states[state]

                    content.append('<th id="'+metastate+'columnheader">'+column_name+'</th>')

                if 'counterpartcolumn' in step_document:
                    counterpart_column = step_document['counterpartcolumn']
                    column_name = counterpart_column['name']
                    state = counterpart_column['state']
                    if state in self.metastates_list:
                        metastate = state
                    else:
                        custom_states = project_document.get('customstates', {})
                        if state in custom_states:
                            metastate = custom_states[state]

                    content.append('<th id="'+metastate+'columnheader">'+column_name+'</th>')

                if 'buffercolumn' in step_document:
                    buffer_column = step_document['buffercolumn']
                    column_name = buffer_column['name']
                    state = buffer_column['state']
                    if state in self.metastates_list:
                        metastate = state
                    else:
                        custom_states = project_document.get('customstates', {})
                        if state in custom_states:
                            metastate = custom_states[state]

                    content.append('<th id="'+metastate+'columnheader">'+column_name+'</th>')

        content.append('</tr>')

        # Minimum Values
        content.append('<tr><th>Minimum</th>')
        for step_no, step_document in enumerate(workflow):
            step_name = step_document['step']
            if step_name:
                if 'maincolumn' in step_document:
                    main_column = step_document['maincolumn']
                    state = main_column['state']
                    if condensed_column_states and state == condensed_column_states[-1]:
                        if 'closedwip' in global_wips and global_wips['closedwip']:
                            closedwip = global_wips['closedwip']
                        else:
                            closedwip = '1 month'

                        content.append('<td rowspan="2" align="center">')
                        if project_manager:
                            content.append(self.create_html_select_block('closedwip',
                                                                         self.potential_closed_periods,
                                                                         default='Please select...',
                                                                         current_value=closedwip))
                        else:
                            content.append(closedwip)

                        content.append('</td>')
                    else:
                        if project_manager:
                            content.append(f'<td><input type="number" name="step{step_no}mainminwip" size="4" min="-1" value="{stepmainminwip[step_no]}"></td>')
                        else:
                            content.append(f'<td>{stepmainminwip[step_no]}</td>')

                if 'counterpartcolumn' in step_document:
                    counterpart_column = step_document['counterpartcolumn']
                    state = counterpart_column['state']
                    if condensed_column_states and state == condensed_column_states[-1]:
                        if 'closedwip' in global_wips and global_wips['closedwip']:
                            closedwip = global_wips['closedwip']
                        else:
                            closedwip = '1 month'

                        content.append('<td rowspan="2" align="center">')
                        if project_manager:
                            content.append(self.create_html_select_block('closedwip',
                                                                         self.potential_closed_periods,
                                                                         default='Please select...',
                                                                         current_value=closedwip))
                        else:
                            content.append(closedwip)

                        content.append('</td>')
                    else:
                        if project_manager:
                            content.append(f'<td><input type="number" name="step{step_no}counterpartminwip" size="4" min="-1" value="{stepcounterpartminwip[step_no]}"></td>')
                        else:
                            content.append(f'<td>{stepcounterpartminwip[step_no]}</td>')

                if 'buffercolumn' in step_document:
                    if project_manager:
                        content.append(f'<td><input type="number" name="step{step_no}bufferminwip" size="4" min="-1" value="{stepbufferminwip[step_no]}"></td>')
                    else:
                        content.append(f'<td>{stepbufferminwip[step_no]}</td>')

        content.append('</tr>')

        # Maximum Values
        content.append('<tr><th>Maximum</th>')
        for step_no, step_document in enumerate(workflow):
            step_name = step_document['step']
            if step_name:
                if 'maincolumn' in step_document:
                    main_column = step_document['maincolumn']
                    state = main_column['state']
                    if state == condensed_column_states[-1]:
                        continue
                    else:
                        if project_manager:
                            content.append(f'<td><input type="number" name="step{step_no}mainmaxwip" size="4" min="-1" value="{stepmainmaxwip[step_no]}"></td>')
                        else:
                            content.append(f'<td>{stepmainmaxwip[step_no]}</td>')

                if 'counterpartcolumn' in step_document:
                    counterpart_column = step_document['counterpartcolumn']
                    state = counterpart_column['state']
                    if state == condensed_column_states[-1]:
                        continue
                    else:
                        if project_manager:
                            content.append(f'<td><input type="number" name="step{step_no}counterpartmaxwip" size="4" min="-1" value="{stepcounterpartmaxwip[step_no]}"></td>')
                        else:
                            content.append(f'<td>{stepcounterpartmaxwip[step_no]}</td>')

                if 'buffercolumn' in step_document:
                    if project_manager:
                        content.append(f'<td><input type="number" name="step{step_no}buffermaxwip" size="4" min="-1" value="{stepbuffermaxwip[step_no]}"></td>')
                    else:
                        content.append(f'<td>{stepbuffermaxwip[step_no]}</td>')

        content.append('</tr>')

        content.append(f'<tr><td colspan="{len(condensed_column_states)+1}"><hr></td></tr>')

        content.append('<tr><th>Step&nbsp;Alternative<br>Shared&nbsp;Maximum</th>')
        for step_no, step_document in enumerate(workflow):
            if step_document['step']:
                number_of_columns = 0
                for column in ['maincolumn', 'counterpartcolumn', 'buffercolumn']:
                    if column in step_document:
                        number_of_columns += 1

                if number_of_columns:
                    if number_of_columns > 1:
                        content.append(f'<td colspan="{number_of_columns}" align="center">')
                        if project_manager:
                            content.append(f'<input type="number" name="step{step_no}sharedmaxwip" size="4" min="-1" value="{stepsharedmaxwip[step_no]}">')
                        else:
                            content.append(str(stepsharedmaxwip[step_no]))

                        content.append('</td>')
                    else:
                        content.append('<td></td>')

        content.append('</tr>')

        project_document = self.projects_collection.find_one({'project': project})
        workflow_index = project_document.get('workflow_index', {})
        condensed_column_states = workflow_index.get('condensed_column_states', [])
        if project_manager:
            content.append(f'<tr><td colspan="{len(condensed_column_states)+1}" align="center"><input name="enforcewiplimits" type="checkbox"')
            if enforce_wip_limits:
                content.append(' checked')

            content.append('> Enforce WIP Limits</td></tr>')
            content.append(f'<tr><td colspan="{len(condensed_column_states)+1}" align="center"><input type="submit" value="Update Global WIP Settings"></td></tr>')

        content.append('</table>')
        if project_manager:
            content.append('</form>')

        content.append('</td></tr></table>')
        return "".join(content)

    @cherrypy.expose
    def remove_category(self, project="", category=""):
        """Allows a project's category to be removed"""
        if project and category:
            for project_document in self.projects_collection.find({'project': project}):
                categories = project_document['categories']
                revised_categories = [category_document for category_document in categories
                                      if category_document['category'] != category]
                project_document['categories'] = revised_categories
                self.projects_collection.save(project_document)
                self.save_project_as_json(project_document)
                break

        raise cherrypy.HTTPRedirect("/projects/update_project?project="+project, 302)

    @cherrypy.expose
    def remove_custom_attribute(self, project="", custom_attribute=""):
        """Allows a project's custom attribute to be removed"""
        if project and custom_attribute:
            for project_document in self.projects_collection.find({'project': project}):
                custom_attributes = project_document['customattributes']
                if custom_attribute in custom_attributes:
                    del custom_attributes[custom_attribute]

                project_document['customattributes'] = custom_attributes
                self.projects_collection.save(project_document)
                self.save_project_as_json(project_document)
                break

        raise cherrypy.HTTPRedirect("/projects/update_project?project="+project, 302)
        
    @cherrypy.expose
    def remove_custom_state(self, project="", custom_state=""):
        """Allows a project's custom state to be removed"""
        if project and custom_state:
            for project_document in self.projects_collection.find({'project': project}):
                custom_states = project_document['customstates']
                if custom_state in custom_states:
                    del custom_states[custom_state]

                project_document['customstates'] = custom_states
                self.projects_collection.save(project_document)
                self.save_project_as_json(project_document)
                break

        raise cherrypy.HTTPRedirect("/projects/update_project?project="+project, 302)

    @cherrypy.expose
    def restore_project(self, project):
        """Allows an individual project to be restored from a backup"""
        if project:
            Kanbanara.check_authentication(f'/{self.component}')
            session_id = Kanbanara.cookie_handling(self)
            session_document = self.sessions_collection.find_one({"session_id": session_id})
            member_document = Kanbanara.get_member_document(self, session_document)
            content = []
            content.append(Kanbanara.header(self, "restore_project", "Restore Project"))
            content.append(Kanbanara.filter_bar(self, 'restore_project'))
            content.append(Kanbanara.menubar(self))
            content.append('<div align="center">')
            content.append(self.insert_page_title_and_online_help(session_document, "restore_project",
                                                                  "Restore Project"))
            project_manager = False
            for project_document in member_document['projects']:
                if 'project' in project_document and project_document['project'] == project:
                    if project_document['role'] in ['Project Manager', 'Deputy Project Manager']:
                        project_manager = True
                        break

            if project_manager and not self.cards_collection.find({'project': project}).count():
                content.append('<p>Sorry, not implemented yet!</p>')

            content.append('</div>')
            content.append(Kanbanara.footer(self))
            return "".join(content)
        else:
            raise cherrypy.HTTPRedirect('/kanban', 302)

    @cherrypy.expose
    def update_category(self, project, category, existingcolour='', newcolour=''):
        """Allows a project's category to be updated"""
        Kanbanara.check_authentication(f'/{self.component}')
        session_id = Kanbanara.cookie_handling(self)
        session_document = self.sessions_collection.find_one({"session_id": session_id})
        content = []
        content.append(Kanbanara.header(self, "update_category", "Update Category"))
        content.append(Kanbanara.filter_bar(self, 'update_category'))
        content.append(Kanbanara.menubar(self))
        content.append('<div align="center">')
        content.append(self.insert_page_title_and_online_help(session_document,
                                                              "update_category", "Update Category"))
        project_document = self.projects_collection.find_one({'project': project})
        categories = project_document['categories']
        if newcolour:
            revised_categories = []
            for category_document in categories:
                if 'category' in category_document and category_document['category'] == category:
                    category_document['colour'] = newcolour

                revised_categories.append(category_document)

            project_document['categories'] = revised_categories
            self.projects_collection.save(project_document)
            self.save_project_as_json(project_document)
            # TODO - Why can't I return directly to categories tab?
            raise cherrypy.HTTPRedirect("/projects/update_project?project="+project, 302)
        else:
            content.append('<form action="/projects/update_category" method="post">')
            content.append('<input type="hidden" name="project" value="'+project+'">')
            content.append('<input type="hidden" name="category" value="'+category+'"><table><tr><th>Category:</th>')
            content.append('<td><i>'+category+'</i></td></tr><tr><th>Colour:</th><td><select name="newcolour">')
            for candidate_colour in self.colours:
                content.append('<option value="'+candidate_colour+'" style="background-color:'+candidate_colour+'"')
                if existingcolour == candidate_colour:
                    content.append(' selected')

                content.append('>'+candidate_colour+'</option>')

            content.append('</select></td></tr><tr><td colspan="3" align="center">')
            content.append('<input type="submit" value="Update"></form></td></tr></table>')
            content.append(Kanbanara.footer(self))
            return "".join(content)

    @cherrypy.expose
    def update_custom_state(self, project, custom_state, existingmetastate='', newmetastate=''):
        """Allows a project's custom state to be assigned to a different metastate"""
        Kanbanara.check_authentication(f'/{self.component}')
        session_id = Kanbanara.cookie_handling(self)
        session_document = self.sessions_collection.find_one({"session_id": session_id})
        content = []
        content.append(Kanbanara.header(self, "update_custom_state", "Update Custom State"))
        content.append(Kanbanara.filter_bar(self, 'update_custom_state'))
        content.append(Kanbanara.menubar(self))
        content.append('<div align="center">')
        content.append(self.insert_page_title_and_online_help(session_document, "update_custom_state",
                                                              "Update Custom State"))
        project_document = self.projects_collection.find_one({'project': project})
        custom_states = project_document['customstates']
        if newmetastate:
            custom_states[custom_state] = newmetastate
            project_document['customstates'] = custom_states
            self.projects_collection.save(project_document)
            self.save_project_as_json(project_document)
            # TODO - Why can't I return directly to state mappings tab?
            raise cherrypy.HTTPRedirect("/projects/update_project?project="+project, 302)
        else:
            content.append('<form action="/projects/update_custom_state" method="post">')
            content.append('<input type="hidden" name="project" value="'+project+'">')
            content.append('<input type="hidden" name="custom_state" value="'+custom_state+'"><table><tr><th>Custom State:</th>')
            content.append('<td><i>'+custom_state+'</i></td></tr><tr><th>Metastate:</th><td><select name="newmetastate">')
            for candidate_metastate in self.metastates_list:
                content.append('<option value="'+candidate_metastate+'" style="background-color:'+candidate_metastate+'"')
                if existingmetastate == candidate_metastate:
                    content.append(' selected')

                content.append('>'+candidate_metastate+'</option>')

            content.append('</select></td></tr><tr><td colspan="3" align="center">')
            content.append('<input type="submit" value="Update"></form></td></tr></table>')
            content.append(Kanbanara.footer(self))
            return "".join(content)

    @cherrypy.expose
    def update_global_wip(self, project, grouping, scope, limit,
                          step0mainminwip="0", step0counterpartminwip="0", step0bufferminwip="0",
                          step1mainminwip="0", step1counterpartminwip="0", step1bufferminwip="0",
                          step2mainminwip="0", step2counterpartminwip="0", step2bufferminwip="0",
                          step3mainminwip="0", step3counterpartminwip="0", step3bufferminwip="0",
                          step4mainminwip="0", step4counterpartminwip="0", step4bufferminwip="0",
                          step5mainminwip="0", step5counterpartminwip="0", step5bufferminwip="0",
                          step6mainminwip="0", step6counterpartminwip="0", step6bufferminwip="0",
                          step7mainminwip="0", step7counterpartminwip="0", step7bufferminwip="0",
                          step8mainminwip="0", step8counterpartminwip="0", step8bufferminwip="0",
                          step9mainminwip="0", step9counterpartminwip="0", step9bufferminwip="0",
                          step10mainminwip="0", step10counterpartminwip="0", step10bufferminwip="0",
                          step11mainminwip="0", step11counterpartminwip="0", step11bufferminwip="0",
                          step0mainmaxwip="-1", step0counterpartmaxwip="-1", step0buffermaxwip="-1",
                          step1mainmaxwip="-1", step1counterpartmaxwip="-1", step1buffermaxwip="-1",
                          step2mainmaxwip="-1", step2counterpartmaxwip="-1", step2buffermaxwip="-1",
                          step3mainmaxwip="-1", step3counterpartmaxwip="-1", step3buffermaxwip="-1",
                          step4mainmaxwip="-1", step4counterpartmaxwip="-1", step4buffermaxwip="-1",
                          step5mainmaxwip="-1", step5counterpartmaxwip="-1", step5buffermaxwip="-1",
                          step6mainmaxwip="-1", step6counterpartmaxwip="-1", step6buffermaxwip="-1",
                          step7mainmaxwip="-1", step7counterpartmaxwip="-1", step7buffermaxwip="-1",
                          step8mainmaxwip="-1", step8counterpartmaxwip="-1", step8buffermaxwip="-1",
                          step9mainmaxwip="-1", step9counterpartmaxwip="-1", step9buffermaxwip="-1",
                          step10mainmaxwip="-1", step10counterpartmaxwip="-1", step10buffermaxwip="-1",
                          step11mainmaxwip="-1", step11counterpartmaxwip="-1", step11buffermaxwip="-1",
                          closedwip='1 month', step0sharedmaxwip="0", step1sharedmaxwip="0", step2sharedmaxwip="0",
                          step3sharedmaxwip="0", step4sharedmaxwip="0", step5sharedmaxwip="0", step6sharedmaxwip="0",
                          step7sharedmaxwip="0", step8sharedmaxwip="0", step9sharedmaxwip="0", step10sharedmaxwip="0",
                          step11sharedmaxwip="0", enforcewiplimits=False):
        """Updates a project's global WIP limits"""
        Kanbanara.check_authentication(f'/{self.component}')
        session_id = Kanbanara.cookie_handling(self)
        session_document = self.sessions_collection.find_one({"session_id": session_id})
        member_document = Kanbanara.get_member_document(self, session_document)
        project_document = self.projects_collection.find_one({'project': project})
        if 'global_wips' in project_document:
            global_wips = project_document['global_wips']
        else:
            global_wips = {}

        # Step through the local variables passed into the function
        # returning each as a tuple of its name as a string together with its value
        for (var_as_string, value) in locals().items():
            if var_as_string.startswith('step') and var_as_string.endswith('wip'):
                if (var_as_string.endswith('mainminwip') or var_as_string.endswith('counterpartminwip') or var_as_string.endswith('bufferminwip')) and value != '0':
                    global_wips[var_as_string] = int(value)
                elif (var_as_string.endswith('mainmaxwip') or var_as_string.endswith('counterpartmaxwip') or var_as_string.endswith('buffermaxwip')) and value != '-1':
                    global_wips[var_as_string] = int(value)
                elif var_as_string.endswith('sharedmaxwip') and value != '0':
                    global_wips[var_as_string] = int(value)

        global_wips['wip_grouping'] = grouping
        global_wips['wip_scope'] = scope
        # TODO - Should wip_limit be max_wip_limit?
        global_wips['wip_limit'] = int(limit)
        global_wips['closedwip'] = closedwip
        global_wips['enforcewiplimits'] = enforcewiplimits
        project_document['global_wips'] = global_wips
        self.projects_collection.save(project_document)
        self.save_project_as_json(project_document)
        raise cherrypy.HTTPRedirect("/projects/update_project?project="+project, 302)

    def __init__(self):
        """Initialisation"""
        self.component = 'projects'

        Kanbanara.__init__(self)

        self.current_dir = os.path.dirname(os.path.abspath(__file__))

        logging.basicConfig(level=logging.DEBUG,
                            format='%(asctime)s - %(levelname)s - %(message)s',
                            filename=os.path.join(self.current_dir, '..', 'logs', 'kanbanara.log'),
                            filemode='w'
                           )

        self.next_card_numbers = {}

        Kanbanara.read_administrator_ini_file(self)

        # Initialisation Settings
        self.mongodb_host, self.mongodb_port, self.mongodb_username, self.mongodb_password, self.mongodb_bindir = Kanbanara.read_mongodb_ini_file(self)

        # Connect to MongoDB on given host and port
        if self.mongodb_username and self.mongodb_password:
            modified_username = urllib.parse.quote_plus(self.mongodb_username)
            modified_password = urllib.parse.quote_plus(self.mongodb_password)
            connection = MongoClient('mongodb://' + modified_username + ':' + modified_password + '@' +
                                     self.mongodb_host + ':' + str(self.mongodb_port))
        else:
            connection = MongoClient(self.mongodb_host, self.mongodb_port)

        # Connect to 'kanbanara' database, creating if not already exists
        db = connection['kanbanara']

        # Connect to 'sessions' collection
        self.sessions_collection = db['sessions']
        for attribute in ['session_id']:
            self.sessions_collection.create_index(attribute, unique=True, background=True)

        for attribute in ['lastaccess']:
            self.sessions_collection.create_index(attribute, unique=False, background=True)

        # Connect to 'members' collection
        self.members_collection = db['members']
        for attribute in ['username']:
            # TODO - username attribute should be unique but get error when unique=true set
            self.members_collection.create_index(attribute, unique=False, background=True)

        # Connect to 'cards' collection
        self.cards_collection = db['cards']
        for attribute in ['id']:
            self.cards_collection.create_index(attribute, unique=True, background=True)

        for attribute in ['description', 'iteration', 'project', 'release', 'state']:
            self.cards_collection.create_index(attribute, unique=False, background=True)

        # Connect to 'vault' database, creating if not already exists
        vault_db = connection['vault']

        self.roles = ['Team Member', 'Guest', 'Deputy Project Manager', 'Project Manager',
                      'Product Manager', 'Product Owner', 'Business Analyst', 'Quality Assurance',
                      'Scrum Master', 'Developer', 'Tester', 'Executive', 'Customer', 'Sponsor',
                      'Architect', 'Stakeholder', 'Business Analyst', 'Database Administrator',
                      'Agile Coach']

    @cherrypy.expose
    def add_iteration(self, project, release, iteration, start_date=0, end_date=0, budget=0):
        """Allows an iteration to be added to the release of a project"""
        Kanbanara.check_authentication(f'/{self.component}')
        Kanbanara.cookie_handling(self)
        if all([project, release, iteration]):
            project_document = self.projects_collection.find_one({"project": project})
            if project_document:
                if 'releases' in project_document:
                    for release_document in project_document['releases']:
                        if release_document['release'] == release:
                            iteration_document = {'iteration': iteration}
                            if start_date:
                                iteration_document['start_date'] = self.dashed_date_to_datetime_object(start_date)

                            if end_date:
                                iteration_document['end_date'] = self.dashed_date_to_datetime_object(end_date)

                            if budget:
                                iteration_document['budget'] = int(budget)

                            if 'iterations' in release_document:
                                release_document['iterations'].append(iteration_document)
                            else:
                                release_document['iterations'] = [iteration_document]

                self.projects_collection.save(project_document)
                self.save_project_as_json(project_document)

        raise cherrypy.HTTPRedirect("/projects/update_project?project="+project, 302)

    def get_default_workflow(self):
        workflow = [{"step": "Planning",
                     "maincolumn":        {'name': 'Untriaged', 'centric': 'Owner', 'state': 'untriaged'},
                     "counterpartcolumn": {'name': 'Triaged',   'centric': 'Owner', 'state': 'triaged'}
                    },
                    {"step": "Backlog",
                     "maincolumn":   {'name': 'Backlog', 'centric': 'Owner', 'state': 'backlog'},
                     "buffercolumn": {'name': 'Defined', 'centric': 'Owner', 'state': 'defined'}
                    },
                    {"step": "Analysis",
                     "maincolumn":   {'name': 'Analysis', 'centric': 'Owner', 'state': 'analysis'},
                     "counterpartcolumn": {'name': 'Analysis Peer Review',   'centric': 'Reviewer', 'state': 'analysispeerreview'},
                     "buffercolumn": {'name': 'Analysed', 'centric': 'Owner', 'state': 'analysed'}
                    },
                    {"step": "Design",
                     "maincolumn":   {'name': 'Design',   'centric': 'Owner', 'state': 'design'},
                     "counterpartcolumn": {'name': 'Design Peer Review',   'centric': 'Reviewer', 'state': 'designpeerreview'},
                     "buffercolumn": {'name': 'Designed', 'centric': 'Owner', 'state': 'designed'}
                    },
                    {"step": "Implementation",
                     "maincolumn":   {'name': 'Development', 'centric': 'Owner',    'state': 'development'},
                     "counterpartcolumn": {'name': 'Development Peer Review',   'centric': 'Reviewer', 'state': 'developmentpeerreview'},
                     "buffercolumn": {'name': 'Developed',   'centric': 'Reviewer', 'state': 'developed'}
                    },
                    {"step": "Validation - Unit",
                     "maincolumn":   {'name': 'Unit Testing',          'centric': 'Reviewer', 'state': 'unittesting'},
                     "buffercolumn": {'name': 'Unit Testing Accepted', 'centric': 'Reviewer',
                                      'state': 'unittestingaccepted'}
                    },
                    {"step": "Validation - Integration",
                     "maincolumn":   {'name': 'Integration Testing',          'centric': 'Reviewer',
                                      'state': 'integrationtesting'},
                     "buffercolumn": {'name': 'Integration Testing Accepted', 'centric': 'Reviewer',
                                      'state': 'integrationtestingaccepted'}
                    },
                    {"step": "Validation - System",
                     "maincolumn":   {'name': 'System Testing', 'centric': 'Reviewer', 'state': 'systemtesting'},
                     "buffercolumn": {'name': 'System Testing Accepted', 'centric': 'Reviewer',
                                      'state': 'systemtestingaccepted'}
                    },
                    {"step": "Validation - Acceptance",
                     "maincolumn":   {'name': 'Acceptance Testing', 'centric': 'Reviewer',
                                      'state': 'acceptancetesting'},
                     "buffercolumn": {'name': 'Acceptance Testing Accepted', 'centric': 'Owner',
                                      'state': 'acceptancetestingaccepted'}
                    },
                    {"step": ""},
                    {"step": "Completion",
                     "maincolumn": {'name': 'Completed', 'centric': 'Owner', 'state': 'completed'},
                     "counterpartcolumn": {'name': 'Closed', 'centric': 'Owner', 'state': 'closed'}
                    },
                    {"step": ""}
                   ]
        return workflow

    @cherrypy.expose
    def add_project(self, project="", role="standalone", start_date=0, end_date=0, budget=0):
        """Allows a new project to be added"""
        Kanbanara.check_authentication(f'/{self.component}')
        session_id = Kanbanara.cookie_handling(self)
        session_document = self.sessions_collection.find_one({"session_id": session_id})
        member_document = Kanbanara.get_member_document(self, session_document)
        if project and not self.projects_collection.find({"project_normalised": project.lower()}).count():
            project_document = {"project":            project,
                                "project_normalised": project.lower(),
                                "creator":            member_document["username"],
                                "nextcardnumber":     1,
                                "workflow":           [],
                                "workflow_index":     {},
                                "releases":           [],
                                "customstates":       {}}

            if role:
                project_document["role"] = role

            if start_date:
                start_date = self.dashed_date_to_datetime_object(start_date)
                project_document["start_date"] = start_date

            if end_date:
                end_date = self.dashed_date_to_datetime_object(end_date)
                project_document["end_date"] = end_date

            if budget:
                project_document["budget"] = int(budget)

            project_document["members"] = [{'username': member_document['username'], 'role': 'Project Manager'}]
            self.projects_collection.insert(project_document)

            self.save_project_as_json(project_document)
            if member_document:
                if 'projects' in member_document and member_document["projects"]:
                    if not self.project_in_projects(project, member_document["projects"]):
                        member_document["projects"].append({'project': project, 'role': 'Project Manager'})

                else:
                    member_document["projects"] = [{'project': project, 'role': 'Project Manager'}]

                self.members_collection.save(member_document)
                self.save_member_as_json(member_document)

            raise cherrypy.HTTPRedirect("/kanban/index", 302)
        else:
            content = []
            page_title = "Add Project"
            content.append(Kanbanara.header(self, 'add_project', page_title))
            content.append(Kanbanara.filter_bar(self, 'add_project'))
            content.append(Kanbanara.menubar(self))
            content.append(self.insert_page_title_and_online_help(session_document, 'add_project', page_title))
            content.append('<div align="center">')
            content.append('<table class="form">')
            content.append('<tr><th>Project</th><th>Role</th><th>Start Date</th><th>End Date</th><th>Budget</th><td></td></tr>') # Inner table
            if project:
                content.append('<tr><td colspan="6" class="warning">Sorry, project \''+project+'\' is already in use!</td></tr>')

            content.append('<tr><td><form action="/projects/add_project" method="post"><input type="text" name="project" placeholder="Project"></td>')
            content.append('<td><select name="role">')
            for candidate_role in ['standalone', 'master', 'slave']:
                content.append('<option value="'+candidate_role+'">'+candidate_role.capitalize()+'</option>')

            content.append('</select></td>')
            content.append('<td><input class="startdate" type="text" name="start_date" placeholder="Start Date"></td><td><input class="enddate" type="text" name="end_date" placeholder="End Date"></td><td><input class="budget" type="number" name="budget" min="0" placeholder="Budget"></td><td><input type="submit" value="Add Project"></form><input type="button" value="Cancel" onclick="window.location=\'/kanban\'" /></td></tr>') # Inner table
            content.append('</table>')
            content.append('</div>')
            content.append(Kanbanara.footer(self))
            return "".join(content)

    @cherrypy.expose
    def create_complex_workflow(self, project):
        _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, condensed_column_states_dict = self.get_project_workflow(project)
        if not condensed_column_states_dict:
            for project_document in self.projects_collection.find({'project': project}):
                workflow = self.get_default_workflow()
                project_document['customstates'] = {}
                project_document['workflow'] = workflow
                project_document['workflow_index'] = self.create_workflow_index(workflow)
                self.projects_collection.save(project_document)
                self.save_project_as_json(project_document)
                break

        raise cherrypy.HTTPRedirect("/projects/update_project?project="+project, 302)
            
    @cherrypy.expose
    def create_simple_todo_doing_done_workflow(self, project):
        _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, condensed_column_states_dict = self.get_project_workflow(project)
        if not condensed_column_states_dict:
            for project_document in self.projects_collection.find({'project': project}):
                workflow = []
                custom_states = project_document.get('customstates', {})
                for (custom_state, metastate) in [('ToDo',  'backlog'),
                                                  ('Doing', 'development'),
                                                  ('Done',  'closed')]:
                    if custom_state not in custom_states:
                        custom_states[custom_state] = metastate

                workflow = [{'step':       ''},
                            {'step':       'To Do',
                             'maincolumn': {'name':    'To Do',
                                            'centric': 'Owner',
                                            'state':   'ToDo'
                                           }
                            },
                            {'step':       ''},
                            {'step':       'Doing',
                             'maincolumn': {'name':    'Doing',
                                            'centric': 'Owner',
                                            'state':   'Doing'
                                           }
                            },
                            {'step':       ''},
                            {'step':       'Done',
                             'maincolumn': {'name':    'Done',
                                            'centric': 'Owner',
                                            'state':   'Done'
                                           }
                            },
                            {'step':       ''},
                            {'step':       ''},
                            {'step':       ''},
                            {'step':       ''},
                            {'step':       ''},
                            {'step':       ''}
                           ]

                project_document['customstates'] = custom_states
                project_document['workflow'] = workflow
                project_document['workflow_index'] = self.create_workflow_index(workflow)
                self.projects_collection.save(project_document)
                self.save_project_as_json(project_document)
                break

        raise cherrypy.HTTPRedirect("/projects/update_project?project="+project, 302)

    def get_teammember_project_roles(self, projects, username):
        project_roles = []
        for member_document in self.members_collection.find({'username': username}):
            project_documents = member_document.get('projects', [])
            for project_document in project_documents:
                if project_document['project'] in projects:
                    project_roles.append((project_document['project'], project_document['role']))

        return project_roles

    @cherrypy.expose
    def delete_iteration(self, project, release, iteration, confirmation="no"):
        """comment"""
        Kanbanara.check_authentication(f'/{self.component}/delete_iteration')
        session_id = Kanbanara.cookie_handling(self)
        session_document = self.sessions_collection.find_one({"session_id": session_id})
        if confirmation == "yes":
            # TODO - IS projects attribute correct?
            project_document = self.projects_collection.find_one({'projects': project})
            if project_document and 'releases' in project_document:
                for release_document in project_document['releases']:
                    if release_document['release'] == release:
                        if 'iterations' in release_document:
                            subset_iteration_documents = [iteration_document for iteration_document
                                                          in release_document['iterations']
                                                          if iteration_document['iteration'] != iteration
                                                        ]
                            release_document['iterations'] = subset_iteration_documents
                            self.projects_collection.save(project_document)
                            self.save_project_as_json(project_document)
                            for card_document in self.cards_collection.find({'project': project,
                                                                             'release': release,
                                                                             'iteration': iteration
                                                                            }):
                                card_document['iteration'] = ""
                                self.cards_collection.save(card_document)
                                self.save_card_as_json(card_document)

            raise cherrypy.HTTPRedirect("/projects/add_project", 302)
        else:
            content = []
            content.append(Kanbanara.header(self, "delete_iteration", "Delete Iteration"))
            content.append(Kanbanara.filter_bar(self, 'add_project'))
            content.append(Kanbanara.menubar(self))
            content.append('<div align="center">')
            content.append(self.insert_page_title_and_online_help(session_document,
                                                                  "delete_iteration",
                                                                  "Delete Iteration"))
            content.append(f'<table><tr><td align="center">{iteration}</td></tr>')
            count = self.cards_collection.find({'project': project, 'release': release,
                                                'iteration': iteration}).count()
            if count:
                content.append(f'<tr><td class="warning">Warning: {count} documents will be affected!</td></tr>')

            content.append('<tr><td align="center">')
            content.append('<form action="/kanban/delete_iteration" method="post">')
            content.append(f'<input type="hidden" name="project" value="{project}">')
            content.append(f'<input type="hidden" name="release" value="{release}">')
            content.append(f'<input type="hidden" name="iteration" value="{iteration}">')
            content.append('<input type="hidden" name="confirmation" value="yes">')
            content.append('<input type="submit" value="Confirm Iteration Deletion"></form><input type="button" value="Cancel" onclick="window.location=\'/projects\'" />')
            content.append('</td></tr></table>')
            content.append('</div>')
            content.append(Kanbanara.footer(self))
            return "".join(content)

    @cherrypy.expose
    def delete_project(self, project, confirmation="no"):
        """ Allows a project that you are a member of to be deleted """
        username = Kanbanara.check_authentication(f'/{self.component}')
        session_id = Kanbanara.cookie_handling(self)
        session_document = self.sessions_collection.find_one({"session_id": session_id})
        member_document = Kanbanara.get_member_document(self, session_document)
        if not self.cards_collection.find({'project': project}).count():
            if confirmation == "yes":
                if 'projects' in member_document and member_document["projects"] and self.project_in_projects(project, member_document['projects']):
                    members = []
                    project_document = self.projects_collection.find_one({'project': project})
                    if project_document:
                        project_member_documents = project_document.get('members', [])
                        for project_member_document in project_member_documents:
                            members.append(project_member_document['username'])

                        self.projects_collection.remove(project_document)

                    if not members:
                        members = [username]

                    for member_document in self.members_collection.find({'username': {'$in': members}}):
                        if 'projects' in member_document and member_document['projects'] and self.project_in_projects(project, member_document['projects']):
                            revised_projects = []
                            for member_project_document in member_document['projects']:
                                if member_project_document['project'] != project:
                                    revised_projects.append(member_project_document)

                            member_document['projects'] = revised_projects
                            self.members_collection.save(member_document)
                            self.save_member_as_json(member_document)

                raise cherrypy.HTTPRedirect("/kanban/index", 302)
            else:
                content = []
                content.append(Kanbanara.header(self, "delete_project", "Delete Project"))
                content.append(Kanbanara.filter_bar(self, 'add_project'))
                content.append(Kanbanara.menubar(self))
                content.append('<div align="center">')
                content.append(self.insert_page_title_and_online_help(session_document,
                                                                      "delete_project",
                                                                      "Delete Project"))
                content.append('<table><tr><td align="center">'+project+'</td></tr>')
                count = self.cards_collection.find({'project': project}).count()
                if count:
                    content.append(f'<tr><td class="warning">Warning: {count} documents will be affected!</td></tr>')

                content.append('<tr><td align="center">')
                content.append('<form action="/projects/delete_project" method="post">')
                content.append('<input type="hidden" name="project" value="'+project+'">')
                content.append('<input type="hidden" name="confirmation" value="yes">')
                content.append('<input type="submit" value="Confirm Project Deletion"></form><input type="button" value="Cancel" onclick="window.location=\'/projects\'" />')
                content.append('</td></tr></table>')
                content.append('</div>')
                content.append(Kanbanara.footer(self))
                return "".join(content)

    @cherrypy.expose
    def delete_release(self, project, release, confirmation="no"):
        """ Allows a project's release to be deleted """
        Kanbanara.check_authentication(f'/{self.component}')
        session_id = Kanbanara.cookie_handling(self)
        session_document = self.sessions_collection.find_one({"session_id": session_id})
        member_document = Kanbanara.get_member_document(self, session_document)
        if confirmation == "yes":
            if member_document:
                if 'projects' in member_document and member_document["projects"] and self.project_in_projects(project, member_document['projects']):
                    project_document = self.projects_collection.find_one({'project': project,
                                                                          'releases.release': release})
                    subsetrelease_documents = [release_document for release_document
                                               in project_document['releases']
                                               if release_document['release'] != release]
                    project_document['releases'] = subsetrelease_documents
                    self.projects_collection.save(project_document)
                    self.save_project_as_json(project_document)
                    for card_document in self.cards_collection.find({'project': project,
                                                                     'release': release}):
                        card_document['release'] = ""
                        self.cards_collection.save(card_document)
                        self.save_card_as_json(card_document)

            raise cherrypy.HTTPRedirect("/projects/add_project", 302)
        else:
            content = []
            content.append(Kanbanara.header(self, "delete_release", "Delete Release"))
            content.append(Kanbanara.filter_bar(self, 'add_project'))
            content.append(Kanbanara.menubar(self))
            content.append('<div align="center">')
            content.append(self.insert_page_title_and_online_help(session_document,
                                                                  "delete_release",
                                                                  "Delete Release"))
            content.append('<table><tr><td align="center">'+release+'</td></tr>')
            count = self.cards_collection.find({'project': project, 'release': release}).count()
            if count:
                content.append(f'<tr><td class="warning">Warning: {count} documents will be affected!</td></tr>')

            content.append('<tr><td align="center">')
            content.append('<form action="/kanban/delete_release" method="post">')
            content.append(f'<input type="hidden" name="project" value="{project}">')
            content.append(f'<input type="hidden" name="release" value="{release}">')
            content.append('<input type="hidden" name="confirmation" value="yes">')
            content.append('<input type="submit" value="Confirm Release Deletion"></form>')
            content.append('<input type="button" value="Cancel" onclick="window.location=\'/update_project?project='+project+'\'" />')
            content.append('</td></tr></table>')
            content.append('</div>')
            content.append(Kanbanara.footer(self))
            return "".join(content)

    def populate_centric_menu(self, column_centric):
        content = '<option value=""></option>'
        for centric in ['Owner', 'Reviewer']:
            if centric == column_centric:
                content += '<option value="'+centric+'" selected>'+centric+'</option>'
            else:
                content += '<option value="'+centric+'">'+centric+'</option>'

        return content

    def populate_state_menu(self, project_document, step_role, selected_state):
        content = []
        content.append('<option value=""></option>')
        if step_role == 'main':
            metastates_list = self.metastates_main_list
        elif step_role == 'counterpart':
            metastates_list = self.metastates_list
        else:
            metastates_list = self.metastates_buffer_list

        for candidate_metastate in metastates_list:
            content.append('<optgroup label="'+candidate_metastate+'">')
            if 'customstates' in project_document and project_document['customstates']:
                for custom_state, metastate in project_document['customstates'].items():
                    if metastate == candidate_metastate:
                        if custom_state == selected_state:
                            content.append('<option value="'+custom_state+'" selected>'+custom_state+'</option>')
                        else:
                            content.append('<option value="'+custom_state+'">'+custom_state+'</option>')

            if candidate_metastate == selected_state:
                content.append('<option value="'+candidate_metastate+'" selected>'+candidate_metastate+'</option>')
            else:
                content.append('<option value="'+candidate_metastate+'">'+candidate_metastate+'</option>')

        return "".join(content)

    @cherrypy.expose
    def delete_project_announcement(self, project, announcement):
        project_document = self.projects_collection.find_one({'project': project})
        announcements = project_document.get('announcements', [])
        modified_announcements = [announcement_document for announcement_document in announcements
                                  if announcement_document['announcement'] != announcement]
        if announcements != modified_announcements:
            project_document['announcements'] = modified_announcements
            self.projects_collection.save(project_document)
            self.save_project_as_json(project_document)

        raise cherrypy.HTTPRedirect("/projects/update_project?project="+project, 302)

    @cherrypy.expose
    def invite_new_member(self, project, username, role="", colour=""):
        """ Allows a new team member to be added to a project """
        loggedon_username = Kanbanara.check_authentication(f'/{self.component}')
        session_id = Kanbanara.cookie_handling(self)
        session_document = self.sessions_collection.find_one({"session_id": session_id})
        loggedon_member_document = Kanbanara.get_member_document(self, session_document)
        if loggedon_member_document and 'projects' in loggedon_member_document and loggedon_member_document['projects'] and self.project_in_projects(project, loggedon_member_document['projects']):
            invited_member_document = ""
            project_document = ""
            if self.members_collection.find({"username": username}).count():
                invited_member_document = self.members_collection.find_one({"username": username})

            if self.projects_collection.find({"project": project}).count():
                project_document = self.projects_collection.find_one({"project": project})

            if invited_member_document and project_document:
                if 'projects' in invited_member_document and invited_member_document['projects']:
                    if not self.project_in_projects(project, invited_member_document['projects']):
                        invited_member_document['projects'].append({'project': project,
                                                                    'role': role,
                                                                    'colour': colour})

                else:
                    invited_member_document['projects'] = [{'project': project,
                                                            'role': role,
                                                            'colour': colour}]

                self.members_collection.save(invited_member_document)
                self.save_member_as_json(invited_member_document)
                if 'members' in project_document and project_document['members']:
                    if not self.username_in_members(username, project_document['members']):
                        project_document['members'].append({'username': username, 'role': role,
                                                            'colour': colour})

                else:
                    project_document['members'] = [{'username': username, 'role': role,
                                                    'colour': colour}]

                self.projects_collection.save(project_document)
                self.save_project_as_json(project_document)
                raise cherrypy.HTTPRedirect("/projects/update_project?project="+project, 302)

        raise cherrypy.HTTPRedirect('/kanban', 302)

    @cherrypy.expose
    def teammembers(self):
        """"""
        page = "teammembers"
        Kanbanara.check_authentication(f'/{self.component}/'+page)
        session_id = Kanbanara.cookie_handling(self)
        session_document = self.sessions_collection.find_one({"session_id": session_id})
        member_document = Kanbanara.get_member_document(self, session_document)
        content = []
        title = "Team Members"
        content.append(Kanbanara.header(self, page, title))
        content.append(Kanbanara.filter_bar(self, page))
        content.append(Kanbanara.menubar(self))
        content.append('<div align="center">')
        content.append(self.insert_page_title_and_online_help(session_document, page, title))
        project_distincts = self.get_member_projects(member_document)
        fullnames_and_usernames = self.get_project_members(project_distincts)
        for (fullname, teammember_username) in fullnames_and_usernames:
            content.append('<div class="teammember"><table border="0">')
            avatar = self.get_teammember_avatar(teammember_username)
            project_roles = self.get_teammember_project_roles(project_distincts, teammember_username)
            cols = len(project_roles)
            content.append(f'<tr><td colspan="{cols}">')
            if avatar:
                content.append('<img src="/images/avatars/'+avatar+'">')
            else:
                content.append('<img src="/images/avatars/default.jpg">')

            content.append(f'</td></tr><tr><td colspan="{cols}"><h2>{fullname}</h2></td></tr>')
            content.append(f'<tr><td colspan="{cols}">{teammember_username}</td></tr>')
            content.append('<tr>')
            for (project, role) in project_roles:
                content.append(f'<td class="projectrole">{project}<br>{role}</td>')

            content.append('</tr></table>')
            content.append('</div>')

        content.append('</div>')
        content.append(Kanbanara.footer(self))
        return "".join(content)

    @cherrypy.expose
    def projects(self, adminusername="", adminpassword=""):
        Kanbanara.check_authentication(f'/{self.component}/projects')
        session_id = Kanbanara.cookie_handling(self)
        session_document = self.sessions_collection.find_one({"session_id": session_id})
        content = []
        content.append(Kanbanara.header(self, "projects", "Projects"))
        content.append(Kanbanara.filter_bar(self, 'projects'))
        content.append(Kanbanara.menubar(self))
        content.append('<div align="center">')
        content.append(self.insert_page_title_and_online_help(session_document,
                                                              "projects", "Projects"))
        if adminusername and adminpassword:
            if adminusername == self.kanbanara_settings['admin_username'] and adminpassword == self.kanbanara_settings['admin_password']:
                attributes = [('project', 'Project'),
                              ('creator', 'Creator'),
                              ('members', 'Members'),
                              ('releases', 'Releases')
                             ]
                content.append('<table class="admin"><tr>')
                for (attribute, heading) in attributes:
                    content.append('<th>'+heading+'</th>')

                content.append('</tr>')
                for project_document in self.projects_collection.find():
                    content.append('<tr>')
                    for (attribute, heading) in attributes:
                        if attribute in project_document:
                            content.append(f'<td>{project_document[attribute]}</td>')
                        else:
                            content.append('<td>Not Present</td>')

                    content.append('</tr>')

                content.append('</table>')
            else:
                raise cherrypy.HTTPError(401, 'Unauthorised')

        else:
            content.append('<form action="/kanban/projects" method="post">')
            content.append('<table border="0"><tr><td align="right">Administrator Username</td><td>')
            content.append('<input type="password" size="40" name="adminusername" placeholder="Username">')
            content.append('</td></tr><tr><td align="right">Administrator Password</td><td><input type="password" size="40" name="adminpassword" placeholder="Password"></td></tr><tr><td align="center" colspan="2"><input class="button" type="submit" value="Enter"></td></tr></table></form>')

        content.append('</div>')
        content.append(Kanbanara.footer(self))
        return "".join(content)

    @cherrypy.expose
    def remove_team_member(self, project="", username=""):
        """comment"""
        if project and username:
            member_document = self.members_collection.find_one({"username": username})
            if member_document:
                if 'projects' in member_document and member_document['projects'] and self.project_in_projects(project, member_document['projects']):
                    modified_projects = []
                    for project_document in member_document['projects']:
                        if 'project' in project_document and project_document['project'] and project_document['project'] == project:
                            continue
                        else:
                            modified_projects.append(project_document)

                    if modified_projects != member_document['projects']:
                        member_document['projects'] = modified_projects
                        self.members_collection.save(member_document)
                        self.save_member_as_json(member_document)

            project_document = self.projects_collection.find_one({'project': project})
            if project_document:
                if 'members' in project_document and project_document['members']:
                    modified_members = []
                    for member_document in project_document['members']:
                        if 'username' in member_document and member_document['username'] and member_document['username'] == username:
                            continue
                        else:
                            modified_members.append(member_document)

                    if modified_members != project_document['members']:
                        project_document['members'] = modified_members
                        self.projects_collection.save(project_document)
                        self.save_project_as_json(project_document)

        raise cherrypy.HTTPRedirect("/projects/update_project?project="+project, 302)

    @cherrypy.expose
    def index(self):
        """Redirects you to the kanban board"""
        raise cherrypy.HTTPRedirect("/kanban", 302)

    @cherrypy.expose
    def project_as_json(self, project):
        """Displays a project in JSON format"""
        Kanbanara.check_authentication(f'/{self.component}')
        session_id = Kanbanara.cookie_handling(self)
        session_document = self.sessions_collection.find_one({"session_id": session_id})
        member_document = Kanbanara.get_member_document(self, session_document)
        if member_document and 'projects' in member_document and member_document["projects"] and self.project_in_projects(project, member_document['projects']):
            content = []
            content.append(Kanbanara.header(self, "project_as_json","Project '"+project+"' As JSON"))
            content.append(Kanbanara.filter_bar(self, 'index'))
            content.append(Kanbanara.menubar(self))
            content.append(self.insert_page_title_and_online_help(session_document,"project_as_json","Project '"+project+"' As JSON"))
            project_document = self.projects_collection.find_one({'project': project})
            if project_document:
                content.append('<p class="json">')
                content.append(self.dictionary_as_json('html', project_document, 0))
                content.append('</p>')

            content.append(self.footer())
            return "".join(content)
        else:
            raise cherrypy.HTTPRedirect("/kanban/index", 302)

    @cherrypy.expose
    def update_project_estimate_actual_cost_tab(self, project, currency):
        """Allows the estimated/actual cost attributes for a given project to be updated"""
        Kanbanara.check_authentication(f'/{self.component}')
        Kanbanara.cookie_handling(self)
        for project_document in self.projects_collection.find({'project': project}):
            project_document['currency'] = currency
            self.projects_collection.save(project_document)
            self.save_project_as_json(project_document)
            break

        raise cherrypy.HTTPRedirect("/projects/update_project?project="+project, 302)

    @cherrypy.expose
    def update_project_estimate_actual_time_tab(self, project, unit):
        """Allows the estimated/actual time attributes for a given project to be updated"""
        Kanbanara.check_authentication(f'/{self.component}')
        Kanbanara.cookie_handling(self)
        if project and unit:
            for project_document in self.projects_collection.find({'project': project}):
                project_document['unit'] = unit
                self.projects_collection.save(project_document)
                self.save_project_as_json(project_document)
                break

        raise cherrypy.HTTPRedirect("/projects/update_project?project="+project, 302)

    @cherrypy.expose
    def updateiteration(self, project='', release='', existing_iteration='', revised_iteration='', start_date='',
                        end_date='', budget=''):
        """ Allows the iteration of a project's release to be updated """
        Kanbanara.check_authentication(f'/{self.component}')
        session_id = Kanbanara.cookie_handling(self)
        session_document = self.sessions_collection.find_one({"session_id": session_id})
        if revised_iteration:
            if start_date:
                start_date = self.dashed_date_to_datetime_object(start_date)

            if end_date:
                end_date = self.dashed_date_to_datetime_object(end_date)

            project_document = self.projects_collection.find_one({'project': project})
            if 'releases' in project_document:
                for release_document in project_document['releases']:
                    if release_document['release'] == release:
                        for iteration_document in release_document['iterations']:
                            if iteration_document['iteration'] == existing_iteration:
                                iteration_document['iteration'] = revised_iteration
                                if start_date:
                                    iteration_document['start_date'] = start_date

                                if end_date:
                                    iteration_document['end_date'] = end_date

                                if budget:
                                    iteration_document['budget'] = int(budget)

                                self.projects_collection.save(project_document)
                                self.save_project_as_json(project_document)

            if revised_iteration != existing_iteration:
                for card_document in self.cards_collection.find({'project': project,
                                                                'release': release,
                                                                'iteration': existing_iteration}):
                    card_document['iteration'] = revised_iteration
                    self.cards_collection.save(card_document)

            raise cherrypy.HTTPRedirect("/projects/update_project?project="+project, 302)
        else:
            content = []
            content.append(Kanbanara.header(self, "updateiteration", "Update Iteration"))
            content.append(Kanbanara.filter_bar(self, 'add_project'))
            content.append(Kanbanara.menubar(self))
            content.append('<div align="center">')
            content.append(self.insert_page_title_and_online_help(session_document, "updateiteration", "Update Iteration"))
            content.append('<table><form action="/projects/updateiteration" method="post"><input type="hidden" name="project" value="'+project+'"><input type="hidden" name="release" value="'+release+'"><input type="hidden" name="existing_iteration" value="'+existing_iteration+'">')
            content.append('<tr><th>Project:</th><td><i>'+project+'</i></td></tr>')
            content.append('<tr><th>Release:</th><td><i>'+release+'</i></td></tr>')
            content.append('<tr><th>Iteration:</th><td><input type="text" name="revised_iteration" size="40" value="'+existing_iteration+'"></td></tr>')
            if start_date:
                date_format = self.convert_time_to_display_format(start_date)
                content.append('<tr><th>Start Date:</th><td><input class="startdate" type="text" name="start_date" value="'+date_format+'"></td></tr>')
            else:
                content.append('<tr><th>Start Date:</th><td><input class="startdate" type="text" name="start_date"></td></tr>')

            if end_date:
                date_format = self.convert_time_to_display_format(end_date)
                content.append('<tr><th>End Date:</th><td><input class="enddate" type="text" name="end_date" value="'+date_format+'"></td></tr>')
            else:
                content.append('<tr><th>End Date:</th><td><input class="enddate" type="text" name="end_date"></td></tr>')

            if budget:
                content.append(f'<tr><th>Budget:</th><td><input type="number" name="budget" value="{budget}"></td></tr>')
            else:
                content.append('<tr><th>Budget:</th><td><input type="number" name="budget"></td></tr>')

            content.append('<tr><td colspan="2" align="center"><input type="submit" value="Update"></form></td></tr>')
            content.append('</table>')
            content.append(Kanbanara.footer(self))
            return "".join(content)

    @cherrypy.expose
    def update_project(self, project=""):
        """Allows the attributes of a project to be altered and updated"""
        Kanbanara.check_authentication(f'/{self.component}')
        session_id = Kanbanara.cookie_handling(self)
        session_document = self.sessions_collection.find_one({"session_id": session_id})
        member_document = Kanbanara.get_member_document(self, session_document)
        epoch = datetime.datetime.utcnow()
        if not (member_document and 'projects' in member_document and member_document["projects"] and self.project_in_projects(project, member_document['projects'])):
            raise cherrypy.HTTPRedirect('/kanban', 302)

        project_manager = False
        for project_document in member_document['projects']:
            if 'project' in project_document and project_document['project'] == project:
                if project_document['role'] in ['Project Manager', 'Deputy Project Manager']:
                    project_manager = True
                    break

        if not self.projects_collection.find({'project': project}).count():
            raise cherrypy.HTTPRedirect('/kanban', 302)

        content = []
        page_title = "Update "+project+" Project"
        content.append(Kanbanara.header(self, 'update_project',page_title))
        content.append(Kanbanara.filter_bar(self, 'update_project'))
        content.append(Kanbanara.menubar(self))
        content.append(self.insert_page_title_and_online_help(session_document, 'update_project',
                                                              page_title))
        content.append('<div class="tabs" class="ui-tabs ui-widget ui-widget-content ui-corner-all">')

        content.append('<ul class="tabs" class="ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all">')
        content.append('<li class="ui-state-default ui-corner-top ui-tabs-selected ui-state-active"><a href="#basetab">Base</a></li>')
        content.append('<li class="ui-state-default ui-corner-top"><a href="#announcementstab">Announcements</a></li>')
        content.append('<li class="ui-state-default ui-corner-top"><a href="#timelinetab">Timeline</a></li>')
        content.append('<li class="ui-state-default ui-corner-top"><a href="#customstatestab">Custom States</a></li>')
        content.append('<li class="ui-state-default ui-corner-top"><a href="#workflowtab">Workflow</a></li>')
        content.append('<li class="ui-state-default ui-corner-top"><a href="#releasesiterationstab">Releases and Iterations</a></li>')
        content.append('<li class="ui-state-default ui-corner-top"><a href="#teammemberstab">Team Members</a></li>')
        content.append('<li class="ui-state-default ui-corner-top"><a href="#globalwiptab">Global Work In Progress (WIP)</a></li>')
        content.append('<li class="ui-state-default ui-corner-top"><a href="#customattributestab">Custom Attributes</a></li>')
        content.append('<li class="ui-state-default ui-corner-top"><a href="#subteamstab">Subteams</a></li>')
        content.append('<li class="ui-state-default ui-corner-top"><a href="#emailtab">Email</a></li>')
        content.append('<li class="ui-state-default ui-corner-top"><a href="#classesofservicetab">Classes Of Service</a></li>')
        content.append('<li class="ui-state-default ui-corner-top"><a href="#estimateactualcosttab">Estimate/Actual Cost</a></li>')
        content.append('<li class="ui-state-default ui-corner-top"><a href="#estimateactualtimetab">Estimate/Actual Time</a></li>')
        content.append('<li class="ui-state-default ui-corner-top"><a href="#categoriestab">Categories</a></li>')
        content.append('<li class="ui-state-default ui-corner-top"><a href="#entryexitcriteriatab">Entry/Exit Criteria</a></li>')
        content.append('<li class="ui-state-default ui-corner-top"><a href="#synchronisationtab">Synchronisation</a></li>')
        content.append('</ul>')

        content.append('<div class="tab_content" id="basetab" class="ui-tabs-panel ui-widget-content ui-corner-bottom">')
        content.append('<table class="form">')
        content.append('<tr><th>Project</th><th>Role</th><th>Start Date</th><th>End Date</th><th>Budget</th><td></td></tr>')
        project_start_date = 0
        project_role = 0
        project_end_date = 0
        project_budget = 0
        project_document = self.projects_collection.find_one({'project': project})
        content.append('<tr><td align="center">'+project+'</td>')
        if 'role' in project_document and project_document['role']:
            content.append('<td align="center">'+project_document['role'].capitalize()+'</td>')
            project_role = project_document['role']
        else:
            content.append('<td></td>')

        if 'start_date' in project_document and project_document['start_date']:
            date_format = self.convert_time_to_display_format(project_document['start_date'])
            content.append('<td align="center">'+date_format+'</td>')
            project_start_date = project_document['start_date']
        else:
            content.append('<td></td>')

        if 'end_date' in project_document and project_document['end_date']:
            date_format = self.convert_time_to_display_format(project_document['end_date'])
            content.append('<td align="center">'+date_format+'</td>')
            project_end_date = project_document['end_date']
        else:
            content.append('<td></td>')

        if 'budget' in project_document and project_document['budget']:
            content.append(f'<td align="center">{project_document["budget"]}</td>')
            project_budget = int(project_document['budget'])
        else:
            content.append('<td></td>')

        if project_manager:
            content.append('<td><form action="/projects/update_project_base_attributes" method="post"><input type="hidden" name="existingproject" value="'+project+'">')
            for attribute in ['role', 'start_date', 'end_date', 'budget']:
                if attribute in project_document and project_document[attribute]:
                    content.append(f'<input type="hidden" name="{attribute}" value="{project_document[attribute]}">')

            content.append('<input type="submit" value="Update"></form>')
            content.append('</td>')

        content.append('</tr></table>')
        if not project_manager:
            content.append('<p class="pmwarning">This tab can only be updated by your Project Manager</p>')

        content.append('</div>')
        content.append('<div class="tab_content" id="announcementstab" class="ui-tabs-panel ui-widget-content ui-corner-bottom">')
        content.append('<table class="form"><tr><th>Announcement</th><th>Start Date</th><th>End Date</th><th>Status</th><td></td></tr>')
        if 'announcements' in project_document and project_document['announcements']:
            for announcement_document in project_document['announcements']:
                announcement = announcement_document['announcement']
                content.append('<tr><td>'+announcement+'</td><td>')
                start_date = announcement_document.get('startdate', datetime.timedelta())
                end_date   = announcement_document.get('enddate',   datetime.timedelta())
                if start_date:
                    content.append(self.convert_time_to_display_format(start_date))

                content.append('</td><td>')
                if end_date:
                    content.append(self.convert_time_to_display_format(end_date))

                content.append('</td><td>')
                epoch = datetime.datetime.utcnow()
                status = 'Unknown'
                if start_date and start_date > epoch:
                    status = 'Pending'
                elif end_date and end_date < epoch:
                    status = 'Expired'
                elif start_date and end_date and start_date < epoch < end_date:
                    status = 'Current'

                content.append(status + '</td><td>')
                if project_manager:
                    content.append('<form action="/projects/delete_project_announcement" method="post"><input type="hidden" name="project" value="'+project+'"><input type="hidden" name="announcement" value="'+announcement+'"><input type="submit" value="Delete"></form>')

                content.append('</td></tr>')

        if project_manager:
            content.append('<form action="/projects/add_project_announcement" method="post"><input type="hidden" name="project" value="'+project+'"><tr><td><input type="text" name="announcement" size="80"></td><td><input class="startdate" type="text" name="startdate"></td><td><input class="enddate" type="text" name="enddate"></td><td>-</td><td><input type="submit" value="Add"></td></tr></form>')

        content.append('</table>')
        if not project_manager:
            content.append('<p class="pmwarning">This tab can only be updated by your Project Manager</p>')

        content.append('</div>')
        content.append('<div class="tab_content" id="timelinetab" class="ui-tabs-panel ui-widget-content ui-corner-bottom">')
        if not project_manager:
            content.append('<p class="pmwarning">This tab can only be updated by your Project Manager</p>')

        content.append('</div>')
        content.append('<div class="tab_content" id="customstatestab" class="ui-tabs-panel ui-widget-content ui-corner-bottom">')
        content.append('<table class="form"><tr><th>Custom State</th><th>Mapped Onto MetaState</th><th></th></tr>')
        used_category_colours = []
        if 'customstates' in project_document and project_document['customstates']:
            for candidate_metastate in self.metastates_list:
                for custom_state, metastate in project_document['customstates'].items():
                    if metastate == candidate_metastate:
                        content.append('<tr><td>')
                        content.append(custom_state)
                        content.append('</td><td>')
                        content.append(metastate)
                        content.append('</td><td>')
                        if project_manager:
                            content.append('<form action="/projects/update_custom_state" method="post"><input type="hidden" name="project" value="'+project+'"><input type="hidden" name="custom_state" value="'+custom_state+'">')
                            content.append('<input type="hidden" name="existingmetastate" value="'+metastate+'">')
                            content.append('<input type="submit" value="Update"></form>')
                            custom_state_record_count = self.cards_collection.find({'project': project,
                                                                                         'state': custom_state}).count()
                            if custom_state_record_count:
                                content.append(f'<button type="button" disabled title="This project still has {custom_state_record_count} cards associated with this custom state!">Remove</button>')
                            else:
                                content.append('<form action="/projects/remove_custom_state" method="post"><input type="hidden" name="project" value="'+project+'"><input type="hidden" name="custom_state" value="'+custom_state+'"><input type="submit" value="Remove"></form>')

                        content.append('</td></tr>')

        if project_manager:
            content.append('<form action="/projects/add_custom_state" method="post"><input type="hidden" name="project" value="'+project+'"><tr><td><input type="text" name="custom_state"></td><td>')
            content.append(self.create_html_select_block('metastate', self.metastates_list))
            content.append('</td><td><input type="submit" value="Add"></td></tr></form>')

        content.append('</table>')
        if not project_manager:
            content.append('<p class="pmwarning">This tab can only be updated by your Project Manager</p>')

        content.append('</div>')
        content.append('<div class="tab_content" id="workflowtab" class="ui-tabs-panel ui-widget-content ui-corner-bottom">')
        if project_manager:
            steps, main_column_names, counterpart_column_names, buffer_column_names, main_column_centrics, counterpart_column_centrics, buffer_column_centrics, main_column_states, counterpart_column_states, buffer_column_states, main_column_descs, counterpart_column_descs, buffer_column_descs, uncondensed_column_names, uncondensed_column_centrics, uncondensed_column_states, condensed_column_states_dict = self.get_project_workflow(project)
            [step1, step2, step3, step4, step5, step6,
             step7, step8, step9, step10, step11, step12] = steps
            [step1mainname, step2mainname, step3mainname, step4mainname,
             step5mainname, step6mainname, step7mainname, step8mainname,
             step9mainname, step10mainname, step11mainname, step12mainname] = main_column_names
            [step1counterpartname, step2counterpartname, step3counterpartname,
             step4counterpartname, step5counterpartname, step6counterpartname,
             step7counterpartname, step8counterpartname, step9counterpartname,
             step10counterpartname, step11counterpartname, step12counterpartname] = counterpart_column_names
            [step1buffername, step2buffername, step3buffername, step4buffername,
             step5buffername, step6buffername, step7buffername, step8buffername,
             step9buffername, step10buffername, step11buffername, step12buffername] = buffer_column_names
            [step1maincentric, step2maincentric, step3maincentric, step4maincentric,
             step5maincentric, step6maincentric, step7maincentric, step8maincentric,
             step9maincentric, step10maincentric, step11maincentric, step12maincentric] = main_column_centrics
            [step1counterpartcentric, step2counterpartcentric, step3counterpartcentric,
             step4counterpartcentric, step5counterpartcentric, step6counterpartcentric,
             step7counterpartcentric, step8counterpartcentric, step9counterpartcentric,
             step10counterpartcentric, step11counterpartcentric, step12counterpartcentric] = counterpart_column_centrics
            [step1buffercentric, step2buffercentric, step3buffercentric, step4buffercentric,
             step5buffercentric, step6buffercentric, step7buffercentric, step8buffercentric,
             step9buffercentric, step10buffercentric, step11buffercentric, step12buffercentric] = buffer_column_centrics
            [step1mainstate, step2mainstate, step3mainstate, step4mainstate,
             step5mainstate, step6mainstate, step7mainstate, step8mainstate,
             step9mainstate, step10mainstate, step11mainstate, step12mainstate] = main_column_states
            [step1counterpartstate, step2counterpartstate, step3counterpartstate,
             step4counterpartstate, step5counterpartstate, step6counterpartstate,
             step7counterpartstate, step8counterpartstate, step9counterpartstate,
             step10counterpartstate, step11counterpartstate, step12counterpartstate] = counterpart_column_states
            [step1bufferstate, step2bufferstate, step3bufferstate, step4bufferstate,
             step5bufferstate, step6bufferstate, step7bufferstate, step8bufferstate,
             step9bufferstate, step10bufferstate, step11bufferstate, step12bufferstate] = buffer_column_states
            [step1maindesc, step2maindesc, step3maindesc, step4maindesc,
             step5maindesc, step6maindesc, step7maindesc, step8maindesc,
             step9maindesc, step10maindesc, step11maindesc, step12maindesc] = main_column_descs
            [step1counterpartdesc, step2counterpartdesc, step3counterpartdesc,
             step4counterpartdesc, step5counterpartdesc, step6counterpartdesc,
             step7counterpartdesc, step8counterpartdesc, step9counterpartdesc,
             step10counterpartdesc, step11counterpartdesc, step12counterpartdesc] = counterpart_column_descs
            [step1bufferdesc, step2bufferdesc, step3bufferdesc, step4bufferdesc,
             step5bufferdesc, step6bufferdesc, step7bufferdesc, step8bufferdesc,
             step9bufferdesc, step10bufferdesc, step11bufferdesc, step12bufferdesc] = buffer_column_descs
            content.append('<form action="/projects/submit_workflow" method="post">')
            content.append('<input type="hidden" name="project" value="'+project+'">')
            content.append(f'<table class="admin"><tr><th>Step 1-3</th><th>Name</th><td align="center" colspan="3"><input type="text" name="step1" value="{step1}"></td><td align="center" colspan="3"><input type="text" name="step2" value="{step2}"></td><td align="center" colspan="3"><input type="text" name="step3" value="{step3}"></td></tr>')
            content.append('<tr><td colspan="2"></td><th>Main</th><th>Counterpart</th><th>Buffer</th><th>Main</th><th>Counterpart</th><th>Buffer</th><th>Main</th><th>Counterpart</th><th>Buffer</th></tr>')
            content.append(f'<tr><th rowspan="2">Column</th><th>Name</th><td><input type="text" name="step1mainname" value="{step1mainname}"></td><td><input type="text" name="step1counterpartname" value="{step1counterpartname}"></td><td><input type="text" name="step1buffername" value="{step1buffername}"></td><td><input type="text" name="step2mainname" value="{step2mainname}"></td><td><input type="text" name="step2counterpartname" value="{step2counterpartname}"></td><td><input type="text" name="step2buffername" value="{step2buffername}"></td><td><input type="text" name="step3mainname" value="{step3mainname}"></td><td><input type="text" name="step3counterpartname" value="{step3counterpartname}"></td><td><input type="text" name="step3buffername" value="{step3buffername}"></td></tr>')
            content.append('<tr><th>Centric</th><td><select name="step1maincentric">')
            content.append(self.populate_centric_menu(step1maincentric))
            content.append('</select></td><td><select name="step1counterpartcentric">')
            content.append(self.populate_centric_menu(step1counterpartcentric))
            content.append('</select></td><td><select name="step1buffercentric">')
            content.append(self.populate_centric_menu(step1buffercentric))
            content.append('</select></td><td><select name="step2maincentric">')
            content.append(self.populate_centric_menu(step2maincentric))
            content.append('</select></td><td><select name="step2counterpartcentric">')
            content.append(self.populate_centric_menu(step2counterpartcentric))
            content.append('</select></td><td><select name="step2buffercentric">')
            content.append(self.populate_centric_menu(step2buffercentric))
            content.append('</select></td><td><select name="step3maincentric">')
            content.append(self.populate_centric_menu(step3maincentric))
            content.append('</select></td><td><select name="step3counterpartcentric">')
            content.append(self.populate_centric_menu(step3counterpartcentric))
            content.append('</select></td><td><select name="step3buffercentric">')
            content.append(self.populate_centric_menu(step3buffercentric))
            content.append('</select></td></tr>')
            content.append('<tr><th>Metastate/State</th><th>Name</th><td><select name="step1mainstate">')
            content.append(self.populate_state_menu(project_document, 'main', step1mainstate))
            content.append('</select></td><td><select name="step1counterpartstate">')
            content.append(self.populate_state_menu(project_document, 'counterpart', step1counterpartstate))
            content.append('</select></td><td><select name="step1bufferstate">')
            content.append(self.populate_state_menu(project_document, 'buffer', step1bufferstate))
            content.append('</select></td><td><select name="step2mainstate">')
            content.append(self.populate_state_menu(project_document, 'main', step2mainstate))
            content.append('</select></td><td><select name="step2counterpartstate">')
            content.append(self.populate_state_menu(project_document, 'counterpart', step2counterpartstate))
            content.append('</select></td><td><select name="step2bufferstate">')
            content.append(self.populate_state_menu(project_document, 'buffer', step2bufferstate))
            content.append('</select></td><td><select name="step3mainstate">')
            content.append(self.populate_state_menu(project_document, 'main', step3mainstate))
            content.append('</select></td><td><select name="step3counterpartstate">')
            content.append(self.populate_state_menu(project_document, 'counterpart', step3counterpartstate))
            content.append('</select></td><td><select name="step3bufferstate">')
            content.append(self.populate_state_menu(project_document, 'buffer', step3bufferstate))
            content.append('</select></td></tr>')
            content.append(f'<tr><th colspan="2">Description</th><td><input type="text" name="step1maindesc" value="{step1maindesc}">')
            content.append(f'</td><td><input type="text" name="step1counterpartdesc" value="{step1counterpartdesc}">')
            content.append(f'</td><td><input type="text" name="step1bufferdesc" value="{step1bufferdesc}">')
            content.append(f'</td><td><input type="text" name="step2maindesc" value="{step2maindesc}">')
            content.append(f'</td><td><input type="text" name="step2counterpartdesc" value="{step2counterpartdesc}">')
            content.append(f'</td><td><input type="text" name="step2bufferdesc" value="{step2bufferdesc}">')
            content.append(f'</td><td><input type="text" name="step3maindesc" value="{step3maindesc}">')
            content.append(f'</td><td><input type="text" name="step3counterpartdesc" value="{step3counterpartdesc}">')
            content.append(f'</td><td><input type="text" name="step3bufferdesc" value="{step3bufferdesc}">')
            content.append('</td></tr>')
            content.append('<tr><td colspan="11" align="center"><hr></td></tr>')
            content.append(f'<tr><th>Step 4-6</th><th>Name</th><td align="center" colspan="3"><input type="text" name="step4" value="{step4}"></td><td align="center" colspan="3"><input type="text" name="step5" value="{step5}"></td><td align="center" colspan="3"><input type="text" name="step6" value="{step6}"></td></tr>')
            content.append('<tr><td colspan="2"></td><th>Main</th><th>Counterpart</th><th>Buffer</th><th>Main</th><th>Counterpart</th><th>Buffer</th><th>Main</th><th>Counterpart</th><th>Buffer</th></tr>')
            content.append(f'<tr><th rowspan="2">Column</th><th>Name</th><td><input type="text" name="step4mainname" value="{step4mainname}"></td><td><input type="text" name="step4counterpartname" value="{step4counterpartname}"></td><td><input type="text" name="step4buffername" value="{step4buffername}"></td><td><input type="text" name="step5mainname" value="{step5mainname}"></td><td><input type="text" name="step5counterpartname" value="{step5counterpartname}"></td><td><input type="text" name="step5buffername" value="{step5buffername}"></td><td><input type="text" name="step6mainname" value="{step6mainname}"></td><td><input type="text" name="step6counterpartname" value="{step6counterpartname}"></td><td><input type="text" name="step6buffername" value="{step6buffername}"></td></tr>')
            content.append('<tr><th>Centric</th><td><select name="step4maincentric">')
            content.append(self.populate_centric_menu(step4maincentric))
            content.append('</select></td><td><select name="step4counterpartcentric">')
            content.append(self.populate_centric_menu(step4counterpartcentric))
            content.append('</select></td><td><select name="step4buffercentric">')
            content.append(self.populate_centric_menu(step4buffercentric))
            content.append('</select></td><td><select name="step5maincentric">')
            content.append(self.populate_centric_menu(step5maincentric))
            content.append('</select></td><td><select name="step5counterpartcentric">')
            content.append(self.populate_centric_menu(step5counterpartcentric))
            content.append('</select></td><td><select name="step5buffercentric">')
            content.append(self.populate_centric_menu(step5buffercentric))
            content.append('</select></td><td><select name="step6maincentric">')
            content.append(self.populate_centric_menu(step6maincentric))
            content.append('</select></td><td><select name="step6counterpartcentric">')
            content.append(self.populate_centric_menu(step6counterpartcentric))
            content.append('</select></td><td><select name="step6buffercentric">')
            content.append(self.populate_centric_menu(step6buffercentric))
            content.append('</select></td></tr>')
            content.append('<tr><th>Metastate/State</th><th>Name</th><td><select name="step4mainstate">')
            content.append(self.populate_state_menu(project_document, 'main', step4mainstate))
            content.append('</select></td><td><select name="step4counterpartstate">')
            content.append(self.populate_state_menu(project_document, 'counterpart', step4counterpartstate))
            content.append('</select></td><td><select name="step4bufferstate">')
            content.append(self.populate_state_menu(project_document, 'buffer', step4bufferstate))
            content.append('</select></td><td><select name="step5mainstate">')
            content.append(self.populate_state_menu(project_document, 'main', step5mainstate))
            content.append('</select></td><td><select name="step5counterpartstate">')
            content.append(self.populate_state_menu(project_document, 'counterpart', step5counterpartstate))
            content.append('</select></td><td><select name="step5bufferstate">')
            content.append(self.populate_state_menu(project_document, 'buffer', step5bufferstate))
            content.append('</select></td><td><select name="step6mainstate">')
            content.append(self.populate_state_menu(project_document, 'main', step6mainstate))
            content.append('</select></td><td><select name="step6counterpartstate">')
            content.append(self.populate_state_menu(project_document, 'counterpart', step6counterpartstate))
            content.append('</select></td><td><select name="step6bufferstate">')
            content.append(self.populate_state_menu(project_document, 'buffer', step6bufferstate))
            content.append('</select></td></tr>')
            content.append(f'<tr><th colspan="2">Description</th><td><input type="text" name="step4maindesc" value="{step4maindesc}">')
            content.append(f'</td><td><input type="text" name="step4counterpartdesc" value="{step4counterpartdesc}">')
            content.append(f'</td><td><input type="text" name="step4bufferdesc" value="{step4bufferdesc}">')
            content.append(f'</td><td><input type="text" name="step5maindesc" value="{step5maindesc}">')
            content.append(f'</td><td><input type="text" name="step5counterpartdesc" value="{step5counterpartdesc}">')
            content.append(f'</td><td><input type="text" name="step5bufferdesc" value="{step5bufferdesc}">')
            content.append(f'</td><td><input type="text" name="step6maindesc" value="{step6maindesc}">')
            content.append(f'</td><td><input type="text" name="step6counterpartdesc" value="{step6counterpartdesc}">')
            content.append(f'</td><td><input type="text" name="step6bufferdesc" value="{step6bufferdesc}">')
            content.append('</td></tr>')
            content.append('<tr><td colspan="11" align="center"><hr></td></tr>')
            content.append(f'<tr><th>Step 7-9</th><th>Name</th><td align="center" colspan="3"><input type="text" name="step7" value="{step7}"></td><td align="center" colspan="3"><input type="text" name="step8" value="{step8}"></td><td align="center" colspan="3"><input type="text" name="step9" value="{step9}"></td></tr>')
            content.append('<tr><td colspan="2"></td><th>Main</th><th>Counterpart</th><th>Buffer</th><th>Main</th><th>Counterpart</th><th>Buffer</th><th>Main</th><th>Counterpart</th><th>Buffer</th></tr>')
            content.append(f'<tr><th rowspan="2">Column</th><th>Name</th><td><input type="text" name="step7mainname" value="{step7mainname}"></td><td><input type="text" name="step7counterpartname" value="{step7counterpartname}"></td><td><input type="text" name="step7buffername" value="{step7buffername}"></td><td><input type="text" name="step8mainname" value="{step8mainname}"></td><td><input type="text" name="step8counterpartname" value="{step8counterpartname}"></td><td><input type="text" name="step8buffername" value="{step8buffername}"></td><td><input type="text" name="step9mainname" value="{step9mainname}"></td><td><input type="text" name="step9counterpartname" value="{step9counterpartname}"></td><td><input type="text" name="step9buffername" value="{step9buffername}"></td></tr>')
            content.append('<tr><th>Centric</th><td><select name="step7maincentric">')
            content.append(self.populate_centric_menu(step7maincentric))
            content.append('</select></td><td><select name="step7counterpartcentric">')
            content.append(self.populate_centric_menu(step7counterpartcentric))
            content.append('</select></td><td><select name="step7buffercentric">')
            content.append(self.populate_centric_menu(step7buffercentric))
            content.append('</select></td><td><select name="step8maincentric">')
            content.append(self.populate_centric_menu(step8maincentric))
            content.append('</select></td><td><select name="step8counterpartcentric">')
            content.append(self.populate_centric_menu(step8counterpartcentric))
            content.append('</select></td><td><select name="step8buffercentric">')
            content.append(self.populate_centric_menu(step8buffercentric))
            content.append('</select></td><td><select name="step9maincentric">')
            content.append(self.populate_centric_menu(step9maincentric))
            content.append('</select></td><td><select name="step9counterpartcentric">')
            content.append(self.populate_centric_menu(step9counterpartcentric))
            content.append('</select></td><td><select name="step9buffercentric">')
            content.append(self.populate_centric_menu(step9buffercentric))
            content.append('</select></td></tr>')
            content.append('<tr><th>Metastate/State</th><th>Name</th><td><select name="step7mainstate">')
            content.append(self.populate_state_menu(project_document, 'main', step7mainstate))
            content.append('</select></td><td><select name="step7counterpartstate">')
            content.append(self.populate_state_menu(project_document, 'counterpart', step7counterpartstate))
            content.append('</select></td><td><select name="step7bufferstate">')
            content.append(self.populate_state_menu(project_document, 'buffer', step7bufferstate))
            content.append('</select></td><td><select name="step8mainstate">')
            content.append(self.populate_state_menu(project_document, 'main', step8mainstate))
            content.append('</select></td><td><select name="step8counterpartstate">')
            content.append(self.populate_state_menu(project_document, 'counterpart', step8counterpartstate))
            content.append('</select></td><td><select name="step8bufferstate">')
            content.append(self.populate_state_menu(project_document, 'buffer', step8bufferstate))
            content.append('</select></td><td><select name="step9mainstate">')
            content.append(self.populate_state_menu(project_document, 'main', step9mainstate))
            content.append('</select></td><td><select name="step9counterpartstate">')
            content.append(self.populate_state_menu(project_document, 'counterpart', step9counterpartstate))
            content.append('</select></td><td><select name="step9bufferstate">')
            content.append(self.populate_state_menu(project_document, 'buffer', step9bufferstate))
            content.append('</select></td></tr>')
            content.append(f'<tr><th colspan="2">Description</th><td><input type="text" name="step7maindesc" value="{step7maindesc}">')
            content.append(f'</td><td><input type="text" name="step7counterpartdesc" value="{step7counterpartdesc}">')
            content.append(f'</td><td><input type="text" name="step7bufferdesc" value="{step7bufferdesc}">')
            content.append(f'</td><td><input type="text" name="step8maindesc" value="{step8maindesc}">')
            content.append(f'</td><td><input type="text" name="step8counterpartdesc" value="{step8counterpartdesc}">')
            content.append(f'</td><td><input type="text" name="step8bufferdesc" value="{step8bufferdesc}">')
            content.append(f'</td><td><input type="text" name="step9maindesc" value="{step9maindesc}">')
            content.append(f'</td><td><input type="text" name="step9counterpartdesc" value="{step9counterpartdesc}">')
            content.append(f'</td><td><input type="text" name="step9bufferdesc" value="{step9bufferdesc}">')
            content.append('</td></tr>')
            content.append('<tr><td colspan="11" align="center"><hr></td></tr>')
            content.append(f'<tr><th>Step 10-12</th><th>Name</th><td align="center" colspan="3"><input type="text" name="step10" value="{step10}"></td><td align="center" colspan="3"><input type="text" name="step11" value="{step11}"></td><td align="center" colspan="3"><input type="text" name="step12" value="{step12}"></td></tr>')
            content.append('<tr><td colspan="2"></td><th>Main</th><th>Counterpart</th><th>Buffer</th><th>Main</th><th>Counterpart</th><th>Buffer</th><th>Main</th><th>Counterpart</th><th>Buffer</th></tr>')
            content.append(f'<tr><th rowspan="2">Column</th><th>Name</th><td><input type="text" name="step10mainname" value="{step10mainname}"></td><td><input type="text" name="step10counterpartname" value="{step10counterpartname}"></td><td><input type="text" name="step10buffername" value="{step10buffername}"></td><td><input type="text" name="step11mainname" value="{step11mainname}"></td><td><input type="text" name="step11counterpartname" value="{step11counterpartname}"></td><td><input type="text" name="step11buffername" value="{step11buffername}"></td><td><input type="text" name="step12mainname" value="{step12mainname}"></td><td><input type="text" name="step12counterpartname" value="{step12counterpartname}"></td><td><input type="text" name="step12buffername" value="{step12buffername}"></td></tr>')
            content.append('<tr><th>Centric</th><td><select name="step10maincentric">')
            content.append(self.populate_centric_menu(step10maincentric))
            content.append('</select></td><td><select name="step10counterpartcentric">')
            content.append(self.populate_centric_menu(step10counterpartcentric))
            content.append('</select></td><td><select name="step10buffercentric">')
            content.append(self.populate_centric_menu(step10buffercentric))
            content.append('</select></td><td><select name="step11maincentric">')
            content.append(self.populate_centric_menu(step11maincentric))
            content.append('</select></td><td><select name="step11counterpartcentric">')
            content.append(self.populate_centric_menu(step11counterpartcentric))
            content.append('</select></td><td><select name="step11buffercentric">')
            content.append(self.populate_centric_menu(step11buffercentric))
            content.append('</select></td><td><select name="step12maincentric">')
            content.append(self.populate_centric_menu(step12maincentric))
            content.append('</select></td><td><select name="step12counterpartcentric">')
            content.append(self.populate_centric_menu(step12counterpartcentric))
            content.append('</select></td><td><select name="step12buffercentric">')
            content.append(self.populate_centric_menu(step12buffercentric))
            content.append('</select></td></tr>')
            content.append('<tr><th>Metastate/State</th><th>Name</th><td><select name="step10mainstate">')
            content.append(self.populate_state_menu(project_document, 'main', step10mainstate))
            content.append('</select></td><td><select name="step10counterpartstate">')
            content.append(self.populate_state_menu(project_document, 'counterpart', step10counterpartstate))
            content.append('</select></td><td><select name="step10bufferstate">')
            content.append(self.populate_state_menu(project_document, 'buffer', step10bufferstate))
            content.append('</select></td><td><select name="step11mainstate">')
            content.append(self.populate_state_menu(project_document, 'main', step11mainstate))
            content.append('</select></td><td><select name="step11counterpartstate">')
            content.append(self.populate_state_menu(project_document, 'counterpart', step11counterpartstate))
            content.append('</select></td><td><select name="step11bufferstate">')
            content.append(self.populate_state_menu(project_document, 'buffer', step11bufferstate))
            content.append('</select></td><td><select name="step12mainstate">')
            content.append(self.populate_state_menu(project_document, 'main', step12mainstate))
            content.append('</select></td><td><select name="step12counterpartstate">')
            content.append(self.populate_state_menu(project_document, 'counterpart', step12counterpartstate))
            content.append('</select></td><td><select name="step12bufferstate">')
            content.append(self.populate_state_menu(project_document, 'buffer', step12bufferstate))
            content.append('</select></td></tr>')
            content.append(f'<tr><th colspan="2">Description</th><td><input type="text" name="step10maindesc" value="{step10maindesc}">')
            content.append(f'</td><td><input type="text" name="step10counterpartdesc" value="{step10counterpartdesc}">')
            content.append(f'</td><td><input type="text" name="step10bufferdesc" value="{step10bufferdesc}">')
            content.append(f'</td><td><input type="text" name="step11maindesc" value="{step11maindesc}">')
            content.append(f'</td><td><input type="text" name="step11counterpartdesc" value="{step11counterpartdesc}">')
            content.append(f'</td><td><input type="text" name="step11bufferdesc" value="{step11bufferdesc}">')
            content.append(f'</td><td><input type="text" name="step12maindesc" value="{step12maindesc}">')
            content.append(f'</td><td><input type="text" name="step12counterpartdesc" value="{step12counterpartdesc}">')
            content.append(f'</td><td><input type="text" name="step12bufferdesc" value="{step12bufferdesc}">')
            content.append('</td></tr>')
            content.append('<tr><td colspan="11" align="center"><input type="submit" value="Update Workflow"></td></tr></form>')
            if not condensed_column_states_dict:
                content.append('<tr><td colspan="11" align="right"><form action="/projects/create_simple_todo_doing_done_workflow" method="post"><input type="hidden" name="project" value="'+project+'"><input type="submit" value="Create Simple ToDo-Doing-Done Workflow"></form> <form action="/projects/create_complex_workflow" method="post"><input type="hidden" name="project" value="'+project+'"><input type="submit" value="Create Complex Workflow"></form></td></tr>')

            content.append('</table>')

            content.append('<table class="admin"><tr><th>Default Step Internal Name</th><th>Default State Internal Name</th><th>Alternative Display Name Suggestions</th></tr>')
            content.append('<tr><td></td><th id="backlogcolumnheader">Backlog</th><td><p>Backburner, Demand, Goals, Held Back, Icebox, Ideas, New, Options, Pool, Pool of Ideas, Problems, Proposed, Received, ThingsTo Do, To Do, Upcoming, Work Items</p></td></tr>')

            content.append('<tr><td></td><th id="definedcolumnheader">Defined (buffer)</th><td><p>Awaiting Production, Committed, Inbox, Input, Next, Not Started, On Deck, On Your Marks, Pending, Plan, Prioritised, Priority, Proposed, Queue, Scheduled, Selected, Things We\'ll Do Soon, Triaged, Waiting</p></td></tr>')

            content.append('<tr><th id="specifystep">Specify</th><td></td><td><p></p></td></tr>')

            content.append('<tr><td></td><th id="analysiscolumnheader">Analysis</th><td><p>Architecture, Definition, Design, Elaboration, Feature Preparation, Find Cause, Get Set, Investigate, Planning, Requirements, Research, Specification</p></td></tr>')

            content.append('<tr><td></td><th id="analysedcolumnheader">Analysed (buffer)</th><td><p>Cause Found, Feature Selected, Ready, Ready for Dev, Researched, Selected for Development</p></td></tr>')

            content.append('<tr><th id="implementstep">Implement</th><td></td><td><p>Things We\'re Actively Working On</p></td></tr>')

            content.append('<tr><td></td><th id="developmentcolumnheader">Development</th><td><p>Actively Working, Coding, Develop, Doing, Executing, Fix Cause, Focus, Go, Implement, In Process, In Progress, In Work, Being Sorted, Working</p></td></tr>')

            content.append('<tr><td></td><th id="developedcolumnheader">Developed (buffer)</th><td><p>Cause Fixed, Ready for Code Review, Ready for QA</p></td></tr>')

            content.append('<tr><th id="validatestep">Validate</th><td></td><td><p></p></td></tr>')

            content.append('<tr><td></td><th id="testingcolumnheader">Testing</th><td><p>Check, Code Review, In Code Review, In QA, Peer Review, Review, Sign Off, Test, Verification</p></td></tr>')

            content.append('<tr><td></td><th id="acceptedcolumnheader">Accepted</th><td><p>Complete, Completed Work, Crunched!, Customer Acceptance, Deliverable, Deploy, Deployable, Deployment, Enabled, Passed, Pending Done, Preparing for Deployment, Ready for Demo, Ready for Release, Ready To Deploy, Scheduled, To Be Deployed, To Release, User Acceptance, Validated</p></td></tr>')

            content.append('<tr><td></td><th id="closedcolumnheader">Closed</th><td><p>Archive, Archived, Delivered, Deployed, Done, Finish Line, In Production, Live To Site, Released, Sorted</p></td></tr>')
            content.append('</table>')

        else:
            content.append('<p class="pmwarning">This tab can only be updated by your Project Manager</p>')

        content.append('</div>')

        content.append('<div class="tab_content" id="releasesiterationstab" class="ui-tabs-panel ui-widget-content ui-corner-bottom">')
        content.append('<table class="form">')
        content.append('<tr><th>Release</th><th>Iteration</th><th>Start Date</th><th>End Date</th><th>Number of Days</th><th>Budget</th><th>Status</th><td></td></tr>')
        if 'projects' in member_document and member_document["projects"]:
            if not self.project_in_projects(project, member_document['projects']):
                raise cherrypy.HTTPRedirect("/projects/add_project", 302)

        if 'nextcardnumber' not in project_document:
            project_document['nextcardnumber'] = 1
            self.projects_collection.save(project_document)
            self.save_project_as_json(project_document)

        unit = ""
        currency = ""

        if 'unit' in project_document and project_document['unit']:
            unit = project_document['unit']

        if 'currency' in project_document and project_document['currency']:
            currency = project_document['currency']

        if 'releases' in project_document:
            for release_document in project_document['releases']:
                release = release_document['release']
                release_start_date = 0
                release_end_date = 0
                content.append('<tr><td align="center">'+release+'</td><td></td>')
                if 'start_date' in release_document and release_document['start_date']:
                    release_start_date = release_document['start_date']
                    date_format = self.convert_time_to_display_format(release_document['start_date'])
                    if project_start_date and project_start_date > release_start_date:
                        content.append('<td class="warning" title="This start date is before inherited start date!">'+date_format+'</td>')
                    else:
                        content.append('<td align="center">'+date_format+'</td>')

                else:
                    content.append('<td></td>')

                if 'end_date' in release_document and release_document['end_date']:
                    release_end_date = release_document['end_date']
                    date_format = self.convert_time_to_display_format(release_document['end_date'])
                    if project_end_date and project_end_date < release_end_date:
                        content.append('<td class="warning" title="This end date is after inherited end date!">'+date_format+'</td>')
                    else:
                        content.append('<td align="center">'+date_format+'</td>')

                else:
                    content.append('<td></td>')

                if 'start_date' in release_document and release_document['start_date'] and 'end_date' in release_document and release_document['end_date']:
                    no_of_days = int((release_document['end_date']-release_document['start_date'])/self.timedelta_day)+1
                    content.append(f'<td align="right">{no_of_days} Days</td>')
                else:
                    content.append('<td></td>')

                if 'budget' in release_document and release_document['budget']:
                    content.append(f'<td align="right">{release_document["budget"]}</td>')
                else:
                    content.append('<td></td>')

                content.append('<td>'+self.get_release_status(project, release, release_start_date, release_end_date)+'</td>')

                content.append('<td>')
                if project_manager:
                    content.append('<form action="/projects/updaterelease" method="post"><input type="hidden" name="project" value="'+project+'"><input type="hidden" name="existing_release" value="'+release+'">')

                    for attribute in ['start_date', 'end_date', 'budget']:
                        if attribute in release_document and release_document[attribute]:
                            content.append(f'<input type="hidden" name="{attribute}" value="{release_document[attribute]}">')

                    content.append('<input type="submit" value="Update"></form>')

                    if self.cards_collection.find({'project': project, 'release': release}).count():
                        content.append('<button type="button" disabled>Delete</button>')
                    else:
                        content.append(f'<form action="/kanban/delete_release" method="post"><input type="hidden" name="project" value="{project}"><input type="hidden" name="release" value="{release}"><input type="submit" value="Delete"></form>')

                content.append('</td></tr>')
                if 'iterations' in release_document:
                    for iteration_document in release_document['iterations']:
                        iteration = iteration_document['iteration']
                        iteration_start_date = 0
                        iteration_end_date = 0
                        content.append('<tr><td align="center">&#34;</td><td align="center">'+iteration+'</td>')
                        if 'start_date' in iteration_document and iteration_document['start_date']:
                            iteration_start_date = iteration_document['start_date']
                            date_format = self.convert_time_to_display_format(iteration_document['start_date'])
                            if (project_start_date and project_start_date > iteration_start_date) or (release_start_date and release_start_date > iteration_start_date):
                                content.append('<td class="warning" title="This start date is before inherited start date!">'+date_format+'</td>')
                            else:
                                content.append('<td align="center">'+date_format+'</td>')

                        else:
                            content.append('<td></td>')

                        if 'end_date' in iteration_document and iteration_document['end_date']:
                            iteration_end_date = iteration_document['end_date']
                            date_format = self.convert_time_to_display_format(iteration_document['end_date'])
                            if (project_end_date and project_end_date < iteration_end_date) or (release_end_date and release_end_date < iteration_end_date):
                                content.append('<td class="warning" title="This end date is after inherited end date!">'+date_format+'</td>')
                            else:
                                content.append('<td align="center">'+date_format+'</td>')

                        else:
                            content.append('<td></td>')

                        if 'start_date' in iteration_document and iteration_document['start_date'] and 'end_date' in iteration_document and iteration_document['end_date']:
                            no_of_days = int((iteration_document['end_date']-iteration_document['start_date'])/self.timedelta_day)
                            content.append(f'<td align="right">{no_of_days} Days</td>')
                        else:
                            content.append('<td></td>')

                        if 'budget' in iteration_document and iteration_document['budget']:
                            content.append(f'<td align="right">{iteration_document["budget"]}</td>')
                        else:
                            content.append('<td></td>')

                        content.append('<td>'+self.get_iteration_status(project, release, iteration, iteration_start_date, iteration_end_date)+'</td>')

                        content.append('<td>')
                        if project_manager:
                            content.append('<form action="/projects/updateiteration" method="post"><input type="hidden" name="project" value="'+project+'"><input type="hidden" name="release" value="'+release+'"><input type="hidden" name="existing_iteration" value="'+iteration+'">')

                            for attribute in ['start_date', 'end_date', 'budget']:
                                if attribute in iteration_document and iteration_document[attribute]:
                                    content.append(f'<input type="hidden" name="{attribute}" value="{iteration_document[attribute]}">')

                            content.append('<input type="submit" value="Update"></form>')

                            if self.cards_collection.find({'project': project, 'release': release, 'iteration': iteration}).count():
                                content.append('<button type="button" disabled>Delete</button>')
                            else:
                                content.append('<form action="/kanban/delete_iteration" method="post"><input type="hidden" name="project" value="'+project+'"><input type="hidden" name="release" value="'+release+'"><input type="hidden" name="iteration" value="'+iteration+'"><input type="submit" value="Delete"></form>')

                        content.append('</td></tr>')

        if project_manager:
            content.append('<tr><td colspan="7"><h2 class="section">Add Release</h2></td></tr>')
            content.append('<form action="/kanban/add_release" method="post"><input type="hidden" name="project" value="'+project+'"><tr><td><input type="text" name="release" placeholder="Release"></td><td></td><td><input class="startdate" type="text" name="start_date" placeholder="Start Date" size="10"></td><td><input class="enddate" type="text" name="end_date" placeholder="End Date" size="10"></td><td></td><td><input type="number" name="budget" placeholder="Budget"></td><td><input type="submit" value="Add Release"></td></tr></form>')
            content.append('<tr><td colspan="7"><h2 class="section">Add Iteration</h2></td></tr>')
            content.append('<form action="/projects/add_iteration" method="post"><input type="hidden" name="project" value="'+project+'"><tr><td><select name="release">')
            content.append('<option class="warning" value="">Please select...</option>')
            releases = self.projects_collection.find({'project': project}).distinct('releases.release')
            for release in releases:
                content.append('<option value="'+release+'">'+release+'</option>')

            content.append('</select></td><td><input type="text" name="iteration" placeholder="Iteration"></td><td><input class="startdate" type="text" name="start_date" placeholder="Start Date" size="10"></td><td><input class="enddate" type="text" name="end_date" placeholder="End Date" size="10"></td><td></td><td><input type="number" name="budget" placeholder="Budget"></td><td><input type="submit" value="Add Iteration"></td></tr></form>')

        content.append('</table>')
        if not project_manager:
            content.append('<p class="pmwarning">This tab can only be updated by your Project Manager</p>')

        content.append('</div>')
        content.append('<div class="tab_content" id="teammemberstab" class="ui-tabs-panel ui-widget-content ui-corner-bottom">')
        content.append('<table class="form"><tr><th>Team Member</th><th>Role</th><th>Colour</th><td></td></tr>')
        fullnames_and_usernames = self.get_project_members([project])
        used_teammember_colours = []
        for fullname, teammember_username in fullnames_and_usernames:
            for othermember_document in self.members_collection.find({'username': teammember_username,
                                                                     'projects': {'$exists': True,
                                                                                  '$nin': [[], '', None]}}):
                if self.project_in_projects(project, othermember_document['projects']):
                    # We can only remove members from a project we ourselves are a member of
                    content.append('<tr><td>'+fullname+'</td><td>')
                    for candidate_project in othermember_document['projects']:
                        if candidate_project['project'] == project and 'role' in candidate_project and candidate_project['role']:
                            content.append(candidate_project['role'])
                            break

                    content.append('</td>')
                    colour = ""
                    for candidate_project in othermember_document['projects']:
                        if candidate_project['project'] == project and 'colour' in candidate_project and candidate_project['colour']:
                            colour = candidate_project['colour']
                            break

                    if colour:
                        content.append('<td bgcolor="'+colour+'">'+colour+'</td>')
                    else:
                        content.append('<td></td>')

                    content.append('<td>')
                    if project_manager:
                        content.append('<form action="/projects/update_teammember" method="post"><input type="hidden" name="project" value="'+project+'"><input type="hidden" name="teammember_username" value="'+teammember_username+'"><input type="submit" value="Update"></form>')
                        project_member_record_count = self.cards_collection.find({'project': project,
                                                                                 'owner': teammember_username}).count()
                        if project_member_record_count:
                            content.append(f'<button type="button" disabled title="{fullname} still has {project_member_record_count} cards associated with this project!">Remove</button>')
                        else:
                            content.append('<form action="/projects/remove_team_member" method="post"><input type="hidden" name="project" value="'+project+'"><input type="hidden" name="username" value="'+teammember_username+'"><input type="submit" value="Remove"></form>')

                    content.append('</td></tr>')

        if project_manager:
            content.append('<form action="/projects/invite_new_member" method="post"><input type="hidden" name="project" value="'+project+'"><tr><td><input type="text" size="30" name="username" placeholder="Username"></td><td>')
            content.append(self.create_html_select_block('role', self.roles))
            content.append('</td><td><select name="colour">')
            content.append('<option value="">Please select...</option>')
            for candidate_colour in self.colours:
                if candidate_colour not in used_teammember_colours:
                    content.append('<option style="background-color:'+candidate_colour+'" value="'+candidate_colour+'">'+candidate_colour+'</option>')

            content.append('</select></td><td><input type="submit" value="Invite Team Member"></td></tr></form>')

        content.append('</table>')
        if not project_manager:
            content.append('<p class="pmwarning">This tab can only be updated by your Project Manager</p>')

        content.append('</div>')
        content.append('<div class="tab_content" id="globalwiptab" class="ui-tabs-panel ui-widget-content ui-corner-bottom">')
        content.append(self.global_work_in_progress(project_manager))
        if not project_manager:
            content.append('<p class="pmwarning">This tab can only be updated by your Project Manager</p>')

        content.append('</div>')
        
        content.append('<div class="tab_content" id="customattributestab" class="ui-tabs-panel ui-widget-content ui-corner-bottom">')
        content.append('<table class="form"><tr><th>Custom Attributes</th><th></th></tr>')
        if 'customattributes' in project_document and project_document['customattributes']:
            for custom_attribute, value in project_document['customattributes'].items():
                content.append('<tr><td>')
                content.append(custom_attribute)
                content.append('</td><td>')
                if project_manager:
                    custom_attribute_record_count = self.cards_collection.find({'project': project,
                                                                                custom_attribute: {"$exists": True, "$ne": ""}
                                                                               }).count()
                    if custom_attribute_record_count:
                        content.append(f'<button type="button" disabled title="This project still has {custom_attribute_record_count} cards using  this custom attribute!">Remove</button>')
                    else:
                        content.append('<form action="/projects/remove_custom_attribute" method="post"><input type="hidden" name="project" value="'+project+'"><input type="hidden" name="custom_attribute" value="'+custom_attribute+'"><input type="submit" value="Remove"></form>')

                content.append('</td></tr>')

        if project_manager:
            content.append('<form action="/projects/add_custom_attribute" method="post"><input type="hidden" name="project" value="'+project+'"><tr><td><input type="text" name="custom_attribute"></td><td><input type="submit" value="Add"></td></tr></form>')

        content.append('</table>')
        if not project_manager:
            content.append('<p class="pmwarning">This tab can only be updated by your Project Manager</p>')

        content.append('</div>')
        
        content.append('<div class="tab_content" id="subteamstab" class="ui-tabs-panel ui-widget-content ui-corner-bottom">')
        content.append('<table class="form"><tr><th>Subteam</th><th></th></tr>')
        if 'subteams' in project_document and project_document['subteams']:
            for subteam_document in project_document['subteams']:
                content.append('<tr><td>')
                if 'subteam' in subteam_document:
                    content.append(subteam_document['subteam'])

                content.append('</td><td>')
                if project_manager:
                    content.append('<form action="/projects/update_subteam" method="post"><input type="hidden" name="project" value="'+project+'"><input type="hidden" name="subteam" value="'+subteam_document['subteam']+'">')

                    content.append('<input type="submit" value="Update"></form>')
                    subteam_record_count = self.cards_collection.find({'project': project,
                                                                        'subteam': subteam_document['subteam']}).count()
                    if subteam_record_count:
                        content.append(f'<button type="button" disabled title="This project still has {subteam_record_count} cards associated with this subteam!">Remove</button>')
                    else:
                        content.append('<form action="/projects/remove_subteam" method="post"><input type="hidden" name="project" value="'+project+'"><input type="hidden" name="subteam" value="'+subteam_document['subteam']+'"><input type="submit" value="Remove"></form>')

                content.append('</td></tr>')

        if project_manager:
            content.append('<form action="/kanban/add_subteam" method="post"><input type="hidden" name="project" value="'+project+'"><tr><td><input type="text" name="subteam"></td><td><input type="submit" value="Add"></td></tr></form>')

        content.append('</table>')
        if not project_manager:
            content.append('<p class="pmwarning">This tab can only be updated by your Project Manager</p>')

        content.append('</div>')
        
        content.append('<div class="tab_content" id="emailtab" class="ui-tabs-panel ui-widget-content ui-corner-bottom">')
        if not project_manager:
            content.append('<p class="pmwarning">This tab can only be updated by your Project Manager</p>')

        content.append('</div>')

        content.append('<div class="tab_content" id="classesofservicetab" class="ui-tabs-panel ui-widget-content ui-corner-bottom">')
        if not project_manager:
            content.append('<p class="pmwarning">This tab can only be updated by your Project Manager</p>')

        content.append('</div>')

        content.append('<div class="tab_content" id="estimateactualcosttab" class="ui-tabs-panel ui-widget-content ui-corner-bottom">')
        if project_manager:
            content.append('<table class="form"><form action="/projects/update_project_estimate_actual_cost_tab" method="post"><input type="hidden" name="project" value="'+project+'"><tr><th>Currency</th><td></td></tr><tr><td><select name="currency">')
            for candidate_currency_key in self.currencies.keys():
                (candidatecurrency_textual, candidatecurrency_symbol) = self.currencies[candidate_currency_key]
                if candidate_currency_key == currency:
                    content.append('<option value="'+candidate_currency_key+'" selected>'+candidatecurrency_textual+' ('+candidatecurrency_symbol+')</option>')
                else:
                    content.append('<option value="'+candidate_currency_key+'">'+candidatecurrency_textual+' ('+candidatecurrency_symbol+')</option>')

            content.append('</select></td><td>')
            cost_record_count = self.cards_collection.find({'project': project, '$or': [{'estimatedcost': {'$gt': 0}}, {'actualcost': {'$gt': 0}}]}).count()
            if cost_record_count:
                content.append('<button type="button" disabled title="This project has already started using cost values!">Update Estimate/Actual Cost</button>')
            else:
                content.append('<input type="submit" value="Update Estimate/Actual Cost">')

            content.append('</td></tr></form></table>')
        else:
            content.append('<table class="form"><tr><th>Unit</th><td></td></tr><tr><td>')
            content.append(currency)
            content.append('</td></tr></table>')

        if not project_manager:
            content.append('<p class="pmwarning">This tab can only be updated by your Project Manager</p>')

        content.append('</div>')

        content.append('<div class="tab_content" id="estimateactualtimetab" class="ui-tabs-panel ui-widget-content ui-corner-bottom">')
        if project_manager:
            content.append('<table class="form"><form action="/projects/update_project_estimate_actual_time_tab" method="post"><input type="hidden" name="project" value="'+project+'"><tr><th>Unit</th><td></td></tr><tr><td>')
            content.append(self.create_html_select_block('unit', ['StoryPoint', 'Day', 'Hour'],
                                                         current_value=unit))
            content.append('</td><td>')
            time_record_count = self.cards_collection.find({'project': project, '$or': [{'estimatedtime': {'$gt': 0}}, {'actualtime': {'$gt': 0}}]}).count()
            if time_record_count:
                content.append('<button type="button" disabled title="This project has already started using time values!">Update Estimate/Actual Time</button>')
            else:
                content.append('<input type="submit" value="Update Estimate/Actual Time">')

            content.append('</td></tr></form></table>')
        else:
            content.append('<table class="form"><tr><th>Unit</th><td></td></tr><tr><td>')
            content.append(unit)
            content.append('</td></tr></table>')
            content.append('<p class="pmwarning">This tab can only be updated by your Project Manager</p>')

        content.append('</div>')

        content.append('<div class="tab_content" id="categoriestab" class="ui-tabs-panel ui-widget-content ui-corner-bottom">')
        content.append('<table class="form"><tr><th>Category</th><th>Colour</th><th></th></tr>')
        used_category_colours = []
        if 'categories' in project_document and project_document['categories']:
            for category_document in project_document['categories']:
                content.append('<tr>')
                if 'category' in category_document:
                    if 'colour'in category_document and category_document['colour']:
                        content.append('<td bgcolor="'+category_document['colour']+'">')
                    else:
                        content.append('<td>')

                    content.append(category_document['category'])
                else:
                    content.append('<td>')

                content.append('</td>')
                if 'colour' in category_document and category_document['colour']:
                    content.append('<td bgcolor="'+category_document['colour']+'">'+category_document['colour'])
                    used_category_colours.append(category_document['colour'])
                else:
                    content.append('<td>')

                content.append('</td><td>')
                if project_manager:
                    content.append('<form action="/projects/update_category" method="post"><input type="hidden" name="project" value="'+project+'"><input type="hidden" name="category" value="'+category_document['category']+'">')
                    if 'colour' in category_document:
                        content.append('<input type="hidden" name="existingcolour" value="'+category_document['colour']+'">')

                    content.append('<input type="submit" value="Update"></form>')
                    category_record_count = self.cards_collection.find({'project': project,
                                                                       'category': category_document['category']}).count()
                    if category_record_count:
                        content.append(f'<button type="button" disabled title="This project still has {category_record_count} cards associated with this category!">Remove</button>')
                    else:
                        content.append('<form action="/projects/remove_category" method="post"><input type="hidden" name="project" value="'+project+'"><input type="hidden" name="category" value="'+category_document['category']+'"><input type="submit" value="Remove"></form>')

                content.append('</td></tr>')

        if project_manager:
            content.append('<form action="/projects/add_category" method="post"><input type="hidden" name="project" value="'+project+'"><tr><td><input type="text" name="category"></td><td><select name="colour">')
            content.append('<option value="">Please select...</option>')
            for candidate_colour in self.colours:
                if candidate_colour not in used_category_colours:
                    content.append('<option style="background-color:'+candidate_colour+'" value="'+candidate_colour+'">'+candidate_colour+'</option>')

            content.append('</select></td><td><input type="submit" value="Add"></td></tr></form>')

        content.append('</table>')
        if not project_manager:
            content.append('<p class="pmwarning">This tab can only be updated by your Project Manager</p>')

        content.append('</div>')

        content.append('<div class="tab_content" id="entryexitcriteriatab" class="ui-tabs-panel ui-widget-content ui-corner-bottom">')
        if project_manager:
            all_entry_criteria = project_document.get('entrycriteria', {})
            all_exit_criteria = project_document.get('exitcriteria', {})
            content.append('<form action="/projects/submit_entry_exit_criteria" method="post">')
            content.append('<input type="hidden" name="project" value="'+project+'">')
            content.append('<table class="admin"><tr><th>Metastate</th><th>Custom State</th><th>Entry Criteria</th><th>Exit Criteria<br>(Done Rules / Pull Criteria / Definition of Done)</th></tr>')
            for metastate in condensed_column_states_dict:
                content.append(f'<tr><th>{metastate}</th><th>-</th>')
                entry_criteria = all_entry_criteria.get(metastate, '')
                content.append(f'<td><textarea name="{metastate}entrycriteria" rows="5" cols="80">{entry_criteria}</textarea></td>')
                exit_criteria = all_exit_criteria.get(metastate, '')
                content.append(f'<td><textarea name="{metastate}exitcriteria" rows="5" cols="80">{exit_criteria}</textarea></td>')
                content.append('</tr>')

            content.append('<tr><td colspan="4" align="center"><input type="submit" value="Update"></td></tr>')
            content.append('</table>')
            content.append('</form>')
        else:
            content.append('<p class="pmwarning">This tab can only be updated by your Project Manager</p>')

        content.append('</div>')

        content.append('<div class="tab_content" id="synchronisationtab" class="ui-tabs-panel ui-widget-content ui-corner-bottom">')
        if project_manager:
            master_host = ""
            master_port = ""
            synchronisation_period = ""
            if 'master_host' in project_document and project_document['master_host']:
                master_host = project_document['master_host']

            if 'master_port' in project_document and project_document['master_port']:
                master_port = project_document['master_port']

            if 'synchronisation_period' in project_document and project_document['synchronisation_period']:
                synchronisation_period = project_document['synchronisation_period']

            content.append('<form action="/projects/submit_synchronisation" method="post">')
            content.append('<input type="hidden" name="project" value="'+project+'">')
            content.append('<table class="admin">')
            content.append('<tr><th>Master Host</th><td><input type="text" name="master_host" value="'+master_host+'"></td></tr>')
            content.append('<tr><th>Master Port</th><td><input type="text" name="master_port" value="'+master_port+'"></td></tr>')
            content.append('<tr><th>Synchronisation Period</th><td>')
            content.append(self.create_html_select_block('synchronisation_period',
                                                         self.potential_synchronisation_periods,
                                                         current_value=synchronisation_period))
            content.append('</td></tr>')
            content.append('<tr><td colspan="2" align="center"><input type="submit" value="Update"></td></tr>')
            content.append('</table>')
        else:
            content.append('<p class="pmwarning">This tab can only be updated by your Project Manager</p>')

        content.append('</form>')
        content.append('</div>')

        content.append('</div>') # End of tab div
        content.append('<script type="text/javascript" src="/kanban/scripts/project.js"></script>')
        content.append(Kanbanara.footer(self))
        return "".join(content)

    @cherrypy.expose
    def update_project_base_attributes(self, existingproject, revisedproject='', role='standalone', start_date=0,
                                       end_date=0, budget=0):
        """Allows the base attributes of a project to be altered"""
        Kanbanara.check_authentication(f'/{self.component}')
        session_id = Kanbanara.cookie_handling(self)
        session_document = self.sessions_collection.find_one({"session_id": session_id})
        project_document = self.projects_collection.find_one({'project': existingproject})
        existing_role = project_document.get('role', 'standalone')
        existing_start_date = project_document.get('start_date', 0)
        existing_end_date = project_document.get('end_date', 0)
        existing_budget = project_document.get('budget', 0)
        if start_date:
            start_date = self.dashed_date_to_datetime_object(start_date)

        if end_date:
            end_date = self.dashed_date_to_datetime_object(end_date)

        if revisedproject or role != existing_role or start_date != existing_start_date or end_date != existing_end_date or budget != existing_budget:
            if revisedproject:
                project_document['project'] = revisedproject

            project_document['role'] = role
            project_document['start_date'] = start_date
            project_document['end_date'] = end_date
            if budget:
                project_document['budget'] = int(budget)
            elif 'budget' in project_document:
                del project_document['budget']

            self.projects_collection.save(project_document)
            self.save_project_as_json(project_document)

            for othersession_document in self.sessions_collection.find({"project": existingproject}):
                othersession_document['project'] = revisedproject
                self.sessions_collection.save(othersession_document)

            for card_document in self.cards_collection.find({'project': existingproject}):
                card_document['project'] = revisedproject
                self.cards_collection.save(card_document)

            raise cherrypy.HTTPRedirect("/projects/update_project?project="+revisedproject, 302)
        else:
            content = []
            content.append(Kanbanara.header(self, "update_project_base_attributes","Update Project"))
            content.append(Kanbanara.filter_bar(self, 'add_project'))
            content.append(Kanbanara.menubar(self))

            content.append('<div align="center">')
            content.append(self.insert_page_title_and_online_help(session_document,"update_project_base_attributes","Update Project"))
            content.append('<table class="project"><tr><th><form action="/projects/update_project_base_attributes" method="post"><input type="hidden" name="existingproject" value="'+existingproject+'">Project</th><td><input type="text" name="revisedproject" size="40" value="'+existingproject+'"></td></tr>')

            content.append('<tr><th>Role</th><td>')
            content.append(self.create_html_select_block('role',
                                                         ['standalone', 'master', 'slave'],
                                                         current_value=role,
                                                         specials=['capitalise']))
            content.append('</td></tr>')

            if start_date:
                date_format = self.convert_time_to_display_format(start_date)
                content.append('<tr><th>Start Date</th><td><input class="startdate" type="text" name="start_date" value="'+date_format+'"></td></tr>')
            else:
                content.append('<tr><th>Start Date</th><td><input class="startdate" type="text" name="start_date"></td></tr>')

            if end_date:
                date_format = self.convert_time_to_display_format(end_date)
                content.append('<tr><th>End Date</th><td><input class="enddate" type="text" name="end_date" value="'+date_format+'"></td></tr>')
            else:
                content.append('<tr><th>End Date</th><td><input class="enddate" type="text" name="end_date"></td></tr>')

            if budget:
                content.append(f'<tr><th>Budget</th><td><input type="number" name="budget" value="{budget}"></td></tr>')
            else:
                content.append('<tr><th>Budget</th><td><input type="number" name="budget"></td></tr>')

            content.append('<tr><td colspan="2" align="center"><input type="submit" value="Update"></form><input type="button" value="Cancel" onclick="window.location=\'/projects/add_project\'" /></td></tr></table>')
            content.append(Kanbanara.footer(self))
            return "".join(content)

    @cherrypy.expose
    def updaterelease(self, project='', existing_release='', revised_release='', start_date='', end_date='', budget=''):
        """ Allows a project's release to be updated """
        Kanbanara.check_authentication(f'/{self.component}')
        session_id = Kanbanara.cookie_handling(self)
        session_document = self.sessions_collection.find_one({"session_id": session_id})
        if revised_release:
            if start_date:
                start_date = self.dashed_date_to_datetime_object(start_date)

            if end_date:
                end_date = self.dashed_date_to_datetime_object(end_date)

            project_document = self.projects_collection.find_one({'project': project})
            if 'releases' in project_document:
                for release_document in project_document['releases']:
                    if release_document['release'] == existing_release:
                        release_document['release'] = revised_release
                        if start_date:
                            release_document['start_date'] = start_date

                        if end_date:
                            release_document['end_date'] = end_date

                        if budget:
                            release_document['budget'] = int(budget)

                        self.projects_collection.save(project_document)
                        self.save_project_as_json(project_document)

            if revised_release != existing_release:
                for card_document in self.cards_collection.find({'project': project, "release": existing_release}):
                    card_document['release'] = revised_release
                    self.cards_collection.save(card_document)

            raise cherrypy.HTTPRedirect("/projects/update_project?project="+project, 302)
        else:
            content = []
            content.append(Kanbanara.header(self, "updaterelease", "Update Release"))
            content.append(Kanbanara.filter_bar(self, 'add_project'))
            content.append(Kanbanara.menubar(self))
            content.append('<div align="center">')
            content.append(self.insert_page_title_and_online_help(session_document, "updaterelease", "Update Release"))
            content.append('<table><form action="/projects/updaterelease" method="post"><input type="hidden" name="project" value="'+project+'"><input type="hidden" name="existing_release" value="'+existing_release+'"><tr><th>Project:</th><td><i>'+project+'</i></td></tr><tr><th>Release:</th><td><input type="text" name="revised_release" size="40" value="'+existing_release+'"></td></tr>')
            if start_date:
                date_format = self.convert_time_to_display_format(start_date)
                content.append('<tr><th>Start Date:</th><td><input class="startdate" type="text" name="start_date" value="'+date_format+'"></td></tr>')
            else:
                content.append('<tr><th>Start Date:</th><td><input class="startdate" type="text" name="start_date"></td></tr>')

            if end_date:
                date_format = self.convert_time_to_display_format(end_date)
                content.append('<tr><th>End Date:</th><td><input class="enddate" type="text" name="end_date" value="'+date_format+'"></td></tr>')
            else:
                content.append('<tr><th>End Date:</th><td><input class="enddate" type="text" name="end_date"></td></tr>')

            if budget:
                content.append('<tr><th>Budget:</th><td><input type="number" name="budget" value="%s"></td></tr>' % budget)
            else:
                content.append('<tr><th>Budget:</th><td><input type="number" name="budget"></td></tr>')

            content.append('<tr><td colspan="2" align="center"><input type="submit" value="Update"></form></td></tr></table>')
            content.append(Kanbanara.footer(self))
            return "".join(content)

    @cherrypy.expose
    def remove_subteam(self, project="", subteam=""):
        """Allows a project's subteam to be deleted"""
        if project and subteam:
            for project_document in self.projects_collection.find({'project': project}):
                subteams = project_document['subteams']
                revised_subteams = [subteam_document for subteam_document in subteams
                                    if subteam_document['subteam'] != subteam]
                project_document['subteams'] = revised_subteams
                self.projects_collection.save(project_document)
                self.save_project_as_json(project_document)
                break

        raise cherrypy.HTTPRedirect("/projects/update_project?project="+project, 302)

    @cherrypy.expose
    def submit_synchronisation(self, project, master_host, master_port, synchronisation_period):
        """Allows the host and port of a project's master and its synchronisation period to be updated"""
        project_document = self.projects_collection.find_one({'project': project})
        if master_host:
            project_document['master_host'] = master_host
        elif 'master_host' in project_document:
            del project_document['master_host']

        if master_port:
            project_document['master_port'] = master_port
        elif 'master_port' in project_document:
            del project_document['master_port']

        if synchronisation_period:
            project_document['synchronisation_period'] = synchronisation_period
            project_document['next_synchronisation'] = datetime.datetime.utcnow() + self.synchronisation_periods[synchronisation_period]
            if 'synchronised_upto' not in project_document:
                project_document['synchronised_upto'] = 0

        elif 'synchronisation_period' in project_document:
            del project_document['synchronisation_period']
            if 'next_synchronisation' in project_document:
                del project_document['next_synchronisation']

        self.projects_collection.save(project_document)
        self.save_project_as_json(project_document)
        raise cherrypy.HTTPRedirect("/projects/update_project?project="+project, 302)

    @cherrypy.expose
    def submit_workflow(self, project, step1, step1mainname, step1counterpartname, step1buffername, step1maincentric,
                        step1counterpartcentric, step1buffercentric, step1mainstate, step1counterpartstate,
                        step1bufferstate, step1maindesc, step1counterpartdesc,
                        step1bufferdesc, step2, step2mainname, step2counterpartname, step2buffername, step2maincentric,
                        step2counterpartcentric, step2buffercentric, step2mainstate, step2counterpartstate,
                        step2bufferstate, step2maindesc, step2counterpartdesc,
                        step2bufferdesc, step3, step3mainname, step3counterpartname, step3buffername, step3maincentric,
                        step3counterpartcentric, step3buffercentric, step3mainstate, step3counterpartstate,
                        step3bufferstate, step3maindesc, step3counterpartdesc,
                        step3bufferdesc, step4, step4mainname, step4counterpartname, step4buffername, step4maincentric,
                        step4counterpartcentric, step4buffercentric, step4mainstate, step4counterpartstate,
                        step4bufferstate,  step4maindesc, step4counterpartdesc,
                        step4bufferdesc, step5, step5mainname, step5counterpartname, step5buffername, step5maincentric,
                        step5counterpartcentric, step5buffercentric, step5mainstate, step5counterpartstate,
                        step5bufferstate, step5maindesc, step5counterpartdesc,
                        step5bufferdesc, step6, step6mainname, step6counterpartname, step6buffername, step6maincentric,
                        step6counterpartcentric, step6buffercentric, step6mainstate, step6counterpartstate,
                        step6bufferstate, step6maindesc, step6counterpartdesc,
                        step6bufferdesc, step7, step7mainname, step7counterpartname, step7buffername, step7maincentric,
                        step7counterpartcentric, step7buffercentric, step7mainstate, step7counterpartstate,
                        step7bufferstate, step7maindesc, step7counterpartdesc,
                        step7bufferdesc, step8, step8mainname, step8counterpartname, step8buffername, step8maincentric,
                        step8counterpartcentric, step8buffercentric, step8mainstate, step8counterpartstate,
                        step8bufferstate, step8maindesc, step8counterpartdesc,
                        step8bufferdesc, step9, step9mainname, step9counterpartname, step9buffername, step9maincentric,
                        step9counterpartcentric, step9buffercentric, step9mainstate, step9counterpartstate,
                        step9bufferstate, step9maindesc, step9counterpartdesc,
                        step9bufferdesc, step10, step10mainname, step10counterpartname, step10buffername,
                        step10maincentric, step10counterpartcentric, step10buffercentric, step10mainstate,
                        step10counterpartstate, step10bufferstate,  step10maindesc, step10counterpartdesc,
                        step10bufferdesc, step11, step11mainname, step11counterpartname,
                        step11buffername, step11maincentric, step11counterpartcentric, step11buffercentric,
                        step11mainstate, step11counterpartstate, step11bufferstate, step11maindesc, step11counterpartdesc,
                        step11bufferdesc, step12, step12mainname,
                        step12counterpartname, step12buffername, step12maincentric, step12counterpartcentric,
                        step12buffercentric, step12mainstate, step12counterpartstate, step12bufferstate, step12maindesc, step12counterpartdesc,
                        step12bufferdesc):
        project_document = self.projects_collection.find_one({'project': project})
        workflow = []
        for step, (main_name, counterpart_name, buffer_name, main_centric, counterpart_centric,
                   buffer_centric, main_state, counterpart_state, buffer_state, main_desc,
                   counterpart_desc, buffer_desc) in zip([step1, step2, step3, step4, step5, step6,
                                                          step7, step8, step9, step10, step11,
                                                          step12],
                            [(step1mainname, step1counterpartname, step1buffername,
                              step1maincentric, step1counterpartcentric, step1buffercentric,
                              step1mainstate, step1counterpartstate, step1bufferstate,
                              step1maindesc, step1counterpartdesc, step1bufferdesc),
                             (step2mainname, step2counterpartname, step2buffername,
                              step2maincentric, step2counterpartcentric, step2buffercentric,
                              step2mainstate, step2counterpartstate, step2bufferstate,
                              step2maindesc, step2counterpartdesc, step2bufferdesc),
                             (step3mainname, step3counterpartname, step3buffername,
                              step3maincentric, step3counterpartcentric, step3buffercentric,
                              step3mainstate, step3counterpartstate, step3bufferstate,
                              step3maindesc, step3counterpartdesc, step3bufferdesc),
                             (step4mainname, step4counterpartname, step4buffername,
                              step4maincentric, step4counterpartcentric, step4buffercentric,
                              step4mainstate, step4counterpartstate, step4bufferstate,
                              step4maindesc, step4counterpartdesc, step4bufferdesc),
                             (step5mainname, step5counterpartname, step5buffername,
                              step5maincentric, step5counterpartcentric, step5buffercentric,
                              step5mainstate, step5counterpartstate, step5bufferstate,
                              step5maindesc, step5counterpartdesc, step5bufferdesc),
                             (step6mainname, step6counterpartname, step6buffername,
                              step6maincentric, step6counterpartcentric, step6buffercentric,
                              step6mainstate, step6counterpartstate, step6bufferstate,
                              step6maindesc, step6counterpartdesc, step6bufferdesc),
                             (step7mainname, step7counterpartname, step7buffername,
                              step7maincentric, step7counterpartcentric, step7buffercentric,
                              step7mainstate, step7counterpartstate, step7bufferstate,
                              step7maindesc, step7counterpartdesc, step7bufferdesc),
                             (step8mainname, step8counterpartname, step8buffername,
                              step8maincentric, step8counterpartcentric, step8buffercentric,
                              step8mainstate, step8counterpartstate, step8bufferstate,
                              step8maindesc, step8counterpartdesc, step8bufferdesc),
                             (step9mainname, step9counterpartname, step9buffername,
                              step9maincentric, step9counterpartcentric, step9buffercentric,
                              step9mainstate, step9counterpartstate, step9bufferstate,
                              step9maindesc, step9counterpartdesc, step9bufferdesc),
                             (step10mainname, step10counterpartname, step10buffername,
                              step10maincentric, step10counterpartcentric, step10buffercentric,
                              step10mainstate, step10counterpartstate, step10bufferstate,
                              step10maindesc, step10counterpartdesc, step10bufferdesc),
                             (step11mainname, step11counterpartname, step11buffername,
                              step11maincentric, step11counterpartcentric, step11buffercentric,
                              step11mainstate, step11counterpartstate, step11bufferstate,
                              step11maindesc, step11counterpartdesc, step11bufferdesc),
                             (step12mainname, step12counterpartname, step12buffername,
                              step12maincentric, step12counterpartcentric, step12buffercentric,
                              step12mainstate, step12counterpartstate, step12bufferstate,
                              step12maindesc, step12counterpartdesc, step12bufferdesc)]
                           ):
            # This creates dictionaries with deliberate blank steps so as to
            # maintain correct spacing on Project Workflow page
            step_dictionary = {'step': step}
            if main_name and main_centric and main_state:
                maincolumn = {'name':        main_name,
                              'centric':     main_centric,
                              'state':       main_state,
                              'description': main_desc
                             }
                step_dictionary['maincolumn'] = maincolumn

            if counterpart_name and counterpart_centric and counterpart_state:
                counterpartcolumn = {'name':        counterpart_name,
                                     'centric':     counterpart_centric,
                                     'state':       counterpart_state,
                                     'description': counterpart_desc
                                    }
                step_dictionary['counterpartcolumn'] = counterpartcolumn

            if buffer_name and buffer_centric and buffer_state:
                buffercolumn = {'name':        buffer_name,
                                'centric':     buffer_centric,
                                'state':       buffer_state,
                                'description': buffer_desc
                               }
                step_dictionary['buffercolumn'] = buffercolumn

            workflow.append(step_dictionary)

        project_document['workflow'] = workflow
        project_document['workflow_index'] = self.create_workflow_index(workflow)
        self.projects_collection.save(project_document)
        self.save_project_as_json(project_document)
        raise cherrypy.HTTPRedirect("/projects/update_project?project="+project, 302)

    def create_workflow_index(self, workflow):
        workflow_index              = {}
        steps                       = [''] * 12
        main_column_names           = [''] * 12
        counterpart_column_names    = [''] * 12
        buffer_column_names         = [''] * 12
        main_column_centrics        = [''] * 12
        counterpart_column_centrics = [''] * 12
        buffer_column_centrics      = [''] * 12
        main_column_states          = [''] * 12
        counterpart_column_states   = [''] * 12
        buffer_column_states        = [''] * 12
        main_column_descs           = [''] * 12
        counterpart_column_descs    = [''] * 12
        buffer_column_descs         = [''] * 12
        uncondensed_column_names    = []
        uncondensed_column_centrics = []
        uncondensed_column_states   = []
        uncondensed_column_descs    = []
        state                       = ""
        preceding_state             = ""
        for step_no, step_document in enumerate(workflow):
            steps[step_no] = step_document['step']
            if 'maincolumn' in step_document:
                if state:
                    preceding_state = state

                maincolumn_document                               = step_document['maincolumn']
                main_column_names[step_no]                        = maincolumn_document.get('name', '')
                main_column_centrics[step_no]                     = maincolumn_document.get('centric', '')
                state                                             = maincolumn_document.get('state', '')
                main_column_states[step_no]                       = state
                main_column_descs[step_no]                        = maincolumn_document.get('description', '')
                workflow_index['step'+str(step_no)+'mainname']    = maincolumn_document['name']
                workflow_index['step'+str(step_no)+'maincentric'] = maincolumn_document['centric']
                workflow_index['step'+str(step_no)+'mainstate']   = state
                workflow_index['step'+str(step_no)+'maindescription'] = maincolumn_document.get('description', '')
                if preceding_state and state:
                    workflow_index['preceding'+state] = preceding_state

            if 'counterpartcolumn' in step_document:
                if state:
                    preceding_state = state

                counterpartcolumn_document                               = step_document['counterpartcolumn']
                counterpart_column_names[step_no]                        = counterpartcolumn_document.get('name', '')
                counterpart_column_centrics[step_no]                     = counterpartcolumn_document.get('centric', '')
                state                                                    = counterpartcolumn_document.get('state', '')
                counterpart_column_states[step_no]                       = state
                counterpart_column_descs[step_no]                        = counterpartcolumn_document.get('description', '')
                workflow_index['step'+str(step_no)+'counterpartname']    = counterpartcolumn_document['name']
                workflow_index['step'+str(step_no)+'counterpartcentric'] = counterpartcolumn_document['centric']
                workflow_index['step'+str(step_no)+'counterpartstate']   = state
                workflow_index['step'+str(step_no)+'counterpartdescription'] = counterpartcolumn_document.get('description', '')
                if preceding_state and state:
                    workflow_index['preceding'+state] = preceding_state

            if 'buffercolumn' in step_document:
                if state:
                    preceding_state = state

                buffercolumn_document                               = step_document['buffercolumn']
                buffer_column_names[step_no]                        = buffercolumn_document.get('name', '')
                buffer_column_centrics[step_no]                     = buffercolumn_document.get('centric', '')
                state                                               = buffercolumn_document.get('state', '')
                buffer_column_states[step_no]                       = state
                buffer_column_descs[step_no]                        = buffercolumn_document.get('description', '')
                workflow_index['step'+str(step_no)+'buffername']    = buffercolumn_document['name']
                workflow_index['step'+str(step_no)+'buffercentric'] = buffercolumn_document['centric']
                workflow_index['step'+str(step_no)+'bufferstate']   = state
                workflow_index['step'+str(step_no)+'bufferdescription'] = buffercolumn_document.get('description', '')
                if preceding_state and state:
                    workflow_index['preceding'+state] = preceding_state

        workflow_index['steps'] = steps
        for main_column_name, counterpart_column_name, buffer_column_name in zip(main_column_names,
                                                                                 counterpart_column_names,
                                                                                 buffer_column_names):
            uncondensed_column_names.extend([main_column_name, counterpart_column_name, buffer_column_name])

        workflow_index['main_column_names']        = main_column_names
        workflow_index['counterpart_column_names'] = counterpart_column_names
        workflow_index['buffer_column_names']      = buffer_column_names
        workflow_index['uncondensed_column_names'] = uncondensed_column_names
        workflow_index['condensed_column_names'] = [x for x in uncondensed_column_names if x != '']

        for main_column_centric, counterpart_column_centric, buffer_column_centric in zip(main_column_centrics,
                                                                                          counterpart_column_centrics,
                                                                                          buffer_column_centrics):
            uncondensed_column_centrics.extend([main_column_centric, counterpart_column_centric, buffer_column_centric])

        workflow_index['main_column_centrics']        = main_column_centrics
        workflow_index['counterpart_column_centrics'] = counterpart_column_centrics
        workflow_index['buffer_column_centrics']      = buffer_column_centrics
        workflow_index['uncondensed_column_centrics'] = uncondensed_column_centrics

        for main_column_state, counterpart_column_state, buffer_column_state in zip(main_column_states,
                                                                                    counterpart_column_states,
                                                                                    buffer_column_states):
            uncondensed_column_states.extend([main_column_state, counterpart_column_state, buffer_column_state])

        workflow_index['main_column_states']        = main_column_states
        workflow_index['counterpart_column_states'] = counterpart_column_states
        workflow_index['buffer_column_states']      = buffer_column_states
        workflow_index['uncondensed_column_states'] = uncondensed_column_states
        condensed_column_states_dict = {}
        condensed_column_states = [x for x in uncondensed_column_states if x != '']
        for i, state in enumerate(condensed_column_states):
            if state:
                condensed_column_states_dict[state] = i

        workflow_index['condensed_column_states'] = condensed_column_states
        workflow_index['condensed_column_states_dict'] = condensed_column_states_dict

        for main_column_desc, counterpart_column_desc, buffer_column_desc in zip(main_column_descs,
                                                                                 counterpart_column_descs,
                                                                                 buffer_column_descs):
            uncondensed_column_descs.extend([main_column_desc, counterpart_column_desc, buffer_column_desc])

        workflow_index['main_column_desciptions']         = main_column_descs
        workflow_index['counterpart_column_descriptions'] = counterpart_column_descs
        workflow_index['buffer_column_descriptions']      = buffer_column_descs
        workflow_index['uncondensed_column_descriptions'] = uncondensed_column_descs        

        return workflow_index

    @cherrypy.expose
    def submit_entry_exit_criteria(self, project,
                                   untriagedentrycriteria="", untriagedexitcriteria="",
                                   triagedentrycriteria="", triagedexitcriteria="",
                                   backlogentrycriteria="", backlogexitcriteria="",
                                   definedentrycriteria="", definedexitcriteria="",
                                   analysisentrycriteria="", analysisexitcriteria="",
                                   analysispeerreviewentrycriteria="", analysispeerreviewexitcriteria="",
                                   analysedentrycriteria="", analysedexitcriteria="", 
                                   designentrycriteria="", designexitcriteria="",
                                   designpeerreviewentrycriteria="", designpeerreviewexitcriteria="",
                                   designedentrycriteria="", designedexitcriteria="", 
                                   developmententrycriteria="", developmentexitcriteria="",
                                   developmentpeerreviewentrycriteria="", developmentpeerreviewexitcriteria="",
                                   developedentrycriteria="", developedexitcriteria="",
                                   unittestingentrycriteria="", unittestingexitcriteria="",
                                   unittestingacceptedentrycriteria="", unittestingacceptedexitcriteria="",
                                   integrationtestingentrycriteria="", integrationtestingexitcriteria="",
                                   integrationtestingacceptedentrycriteria="", integrationtestingacceptedexitcriteria="",
                                   systemtestingentrycriteria="", systemtestingexitcriteria="",
                                   systemtestingacceptedentrycriteria="", systemtestingacceptedexitcriteria="",
                                   acceptancetestingentrycriteria="", acceptancetestingexitcriteria="",
                                   acceptancetestingacceptedentrycriteria="",  acceptancetestingacceptedexitcriteria="",
                                   completedentrycriteria="",  completedexitcriteria="",
                                   closedentrycriteria="",  closedexitcriteria=""):
        project_document = self.projects_collection.find_one({'project': project})
        steps, main_column_names, counterpart_column_names, buffer_column_names, main_column_centrics, counterpart_column_centrics, buffer_column_centrics, main_column_states, counterpart_column_states, buffer_column_states, main_column_descs, counterpart_column_descs, buffer_column_descs, uncondensed_column_names, uncondensed_column_centrics, uncondensed_column_states, condensed_column_states_dict = self.get_project_workflow(project)
        all_entry_criteria = {}
        all_exit_criteria = {}
        for (metastate, entry_criteria, exit_criteria) in [
                ('untriaged', untriagedentrycriteria, untriagedexitcriteria),
                ('triaged', triagedentrycriteria, triagedexitcriteria),
                ('backlog', backlogentrycriteria, backlogexitcriteria),
                ('defined', definedentrycriteria, definedexitcriteria),
                ('analysis', analysisentrycriteria, analysisexitcriteria),
                ('analysispeerreview', analysispeerreviewentrycriteria, analysispeerreviewexitcriteria),
                ('analysed', analysedentrycriteria, analysedexitcriteria), 
                ('design', designentrycriteria, designexitcriteria),
                ('designpeerreview', designpeerreviewentrycriteria, designpeerreviewexitcriteria),
                ('designed', designedentrycriteria, designedexitcriteria), 
                ('development', developmententrycriteria, developmentexitcriteria),
                ('developmentpeerreview', developmentpeerreviewentrycriteria, developmentpeerreviewexitcriteria),
                ('developed', developedentrycriteria, developedexitcriteria),
                ('unittesting', unittestingentrycriteria, unittestingexitcriteria),
                ('unittestingaccepted', unittestingacceptedentrycriteria, unittestingacceptedexitcriteria),
                ('integrationtesting', integrationtestingentrycriteria, integrationtestingexitcriteria),
                ('integrationtestingaccepted', integrationtestingacceptedentrycriteria, integrationtestingacceptedexitcriteria),
                ('systemtesting', systemtestingentrycriteria, systemtestingexitcriteria),
                ('systemtestingaccepted', systemtestingacceptedentrycriteria, systemtestingacceptedexitcriteria),
                ('acceptancetesting', acceptancetestingentrycriteria, acceptancetestingexitcriteria),
                ('acceptancetestingaccepted', acceptancetestingacceptedentrycriteria, acceptancetestingacceptedexitcriteria),
                ('completed', completedentrycriteria, completedexitcriteria),
                ('closed', closedentrycriteria, closedexitcriteria)]:
            if metastate in condensed_column_states_dict:
                all_entry_criteria[metastate] = entry_criteria
                all_exit_criteria[metastate] = exit_criteria

        project_document['entrycriteria'] = all_entry_criteria
        project_document['exitcriteria'] = all_exit_criteria
        self.projects_collection.save(project_document)
        self.save_project_as_json(project_document)
        raise cherrypy.HTTPRedirect("/projects/update_project?project="+project, 302)

    @cherrypy.expose
    def update_subteam(self, project='', subteam='', newsubteam=''):
        """Allows the name of a project's subteam be updated"""
        Kanbanara.check_authentication(f'/{self.component}')
        session_id = Kanbanara.cookie_handling(self)
        session_document = self.sessions_collection.find_one({"session_id": session_id})
        content = []
        content.append(Kanbanara.header(self, "update_subteam", "Update Subteam"))
        content.append(Kanbanara.filter_bar(self, 'update_subteam'))
        content.append(Kanbanara.menubar(self))
        content.append('<div align="center">')
        content.append(self.insert_page_title_and_online_help(session_document, "update_subteam", "Update Subteam"))
        if subteam and newsubteam:
            project_document = self.projects_collection.find_one({'project': project})
            subteams = project_document.get('subteams', [])
            revised_subteams = []
            for subteam_document in subteams:
                if 'subteam' in subteam_document and subteam_document['subteam'] == subteam:
                    subteam_document['subteam'] = newsubteam

                revised_subteams.append(subteam_document)

            project_document['subteams'] = revised_subteams
            self.projects_collection.save(project_document)
            self.save_project_as_json(project_document)
            raise cherrypy.HTTPRedirect("/projects/update_project?project="+project, 302)
        else:
            content.append('<form action="/projects/update_subteam" method="post"><input type="hidden" name="project" value="'+project+'"><input type="hidden" name="subteam" value="'+subteam+'"><table><tr><th>Subteam:</th><td><input type="text" name="newsubteam" value="'+subteam+'"></td></tr><tr><td colspan="3" align="center"><input type="submit" value="Update"></form></td></tr></table>')
            content.append(Kanbanara.footer(self))
            return "".join(content)

    def get_project_workflow(self, project):
        steps                       = [''] * 12
        main_column_names           = [''] * 12
        counterpart_column_names    = [''] * 12
        buffer_column_names         = [''] * 12
        main_column_centrics        = [''] * 12
        counterpart_column_centrics = [''] * 12
        buffer_column_centrics      = [''] * 12
        main_column_states          = [''] * 12
        counterpart_column_states   = [''] * 12
        buffer_column_states        = [''] * 12
        main_column_descs           = [''] * 12
        counterpart_column_descs    = [''] * 12
        buffer_column_descs         = [''] * 12
        uncondensed_column_names    = []
        uncondensed_column_centrics = []
        uncondensed_column_states   = []
        if self.projects_collection.find({'project': project, 'workflow': {'$exists': True}}):
            for project_document in self.projects_collection.find({'project': project, 'workflow': {'$exists': True}}):
                workflow = project_document['workflow']
                for i, step_document in enumerate(workflow):
                    steps[i] = step_document['step']
                    if 'maincolumn' in step_document:
                        maincolumn_document     = step_document['maincolumn']
                        main_column_names[i]    = maincolumn_document.get('name', '')
                        main_column_centrics[i] = maincolumn_document.get('centric', '')
                        main_column_states[i]   = maincolumn_document.get('state', '')
                        main_column_descs[i]    = maincolumn_document.get('description', '')

                    if 'counterpartcolumn' in step_document:
                        counterpartcolumn_document     = step_document['counterpartcolumn']
                        counterpart_column_names[i]    = counterpartcolumn_document.get('name', '')
                        counterpart_column_centrics[i] = counterpartcolumn_document.get('centric', '')
                        counterpart_column_states[i]   = counterpartcolumn_document.get('state', '')
                        counterpart_column_descs[i]    = counterpartcolumn_document.get('description', '')

                    if 'buffercolumn' in step_document:
                        buffercolumn_document     = step_document['buffercolumn']
                        buffer_column_names[i]    = buffercolumn_document.get('name', '')
                        buffer_column_centrics[i] = buffercolumn_document.get('centric', '')
                        buffer_column_states[i]   = buffercolumn_document.get('state', '')
                        buffer_column_descs[i]    = buffercolumn_document.get('description', '')

                break

        for main_column_name, counterpart_column_name, buffer_column_name in zip(main_column_names,
                                                                                 counterpart_column_names,
                                                                                 buffer_column_names):
            uncondensed_column_names.append(main_column_name)
            uncondensed_column_names.append(counterpart_column_name)
            uncondensed_column_names.append(buffer_column_name)

        for main_column_centric, counterpart_column_centric, buffer_column_centric in zip(main_column_centrics,
                                                                                          counterpart_column_centrics,
                                                                                          buffer_column_centrics):
            uncondensed_column_centrics.append(main_column_centric)
            uncondensed_column_centrics.append(counterpart_column_centric)
            uncondensed_column_centrics.append(buffer_column_centric)

        for main_column_state, counterpart_column_state, buffer_column_state in zip(main_column_states,
                                                                                    counterpart_column_states,
                                                                                    buffer_column_states):
            uncondensed_column_states.append(main_column_state)
            uncondensed_column_states.append(counterpart_column_state)
            uncondensed_column_states.append(buffer_column_state)

        condensed_column_states_dict = {}
        condensed_column_states = [x for x in uncondensed_column_states if x != '']
        for i, state in enumerate(condensed_column_states):
            if state:
                condensed_column_states_dict[state] = i

        return steps, main_column_names, counterpart_column_names, buffer_column_names, main_column_centrics, counterpart_column_centrics, buffer_column_centrics, main_column_states, counterpart_column_states, buffer_column_states, main_column_descs, counterpart_column_descs, buffer_column_descs, uncondensed_column_names, uncondensed_column_centrics, uncondensed_column_states, condensed_column_states_dict

    @cherrypy.expose
    def update_teammember(self, project='', teammember_username='', role='', colour=""):
        """comment"""
        Kanbanara.check_authentication(f'/{self.component}')
        session_id = Kanbanara.cookie_handling(self)
        session_document = self.sessions_collection.find_one({"session_id": session_id})
        content = []
        content.append(Kanbanara.header(self, "update_teammember", "Update Team Member"))
        content.append(Kanbanara.filter_bar(self, 'update_teammember'))
        content.append(Kanbanara.menubar(self))
        content.append('<div align="center">')
        content.append(self.insert_page_title_and_online_help(session_document, "update_teammember",
                                                              "Update Team Member"))
        project_document = self.projects_collection.find_one({'project': project})
        teammembers = project_document['members']
        teammember_document = ""
        potential_teammember_documents = self.members_collection.find({'username': teammember_username,
                                                                       'projects': {'$exists': True, '$nin': [[], '', None]}})
        for potential_teammember_document in potential_teammember_documents:
            if self.project_in_projects(project, potential_teammember_document['projects']):
                teammember_document = potential_teammember_document
                break

        if teammember_username and role:
            revised_members = []
            for teammember in teammembers:
                if 'username' in teammember and teammember['username'] == teammember_username:
                    teammember['role'] = role
                    teammember['colour'] = colour

                revised_members.append(teammember)

            project_document['members'] = revised_members
            self.projects_collection.save(project_document)
            self.save_project_as_json(project_document)
            revised_projects = []
            for project_member_of in teammember_document['projects']:
                if project_member_of['project'] == project:
                    project_member_of['role'] = role
                    project_member_of['colour'] = colour

                revised_projects.append(project_member_of)

            teammember_document['projects'] = revised_projects
            self.members_collection.save(teammember_document)
            self.save_member_as_json(teammember_document)
            raise cherrypy.HTTPRedirect("/projects/update_project?project="+project, 302)
        else:
            for teammember in teammembers:
                if 'username' in teammember and teammember['username'] == teammember_document['username']:
                    teammember_username = teammember['username']
                    role = teammember.get('role', '')
                    colour = teammember.get('colour', '')
                    break

            content.append('<table><form action="/projects/update_teammember" method="post"><input type="hidden" name="project" value="'+project+'"><input type="hidden" name="teammember_username" value="'+teammember_username+'"><tr><th>Username:</th><td><i>'+teammember_username+'</i></td></tr><tr><th>Role:</th><td>')
            content.append(self.create_html_select_block('role', self.roles, current_value=role))
            content.append('</td></tr><tr><th>Colour:</th><td><select name="colour">')
            for candidate_colour in self.colours:
                if colour == candidate_colour:
                    content.append(f'<option value="{candidate_colour}" selected>{candidate_colour}</option>')
                else:
                    content.append(f'<option value="{candidate_colour}">{candidate_colour}</option>')

            content.append('</select></td></tr><tr><td colspan="3" align="center"><input type="submit" value="Update"></form></td></tr></table>')
            content.append(Kanbanara.footer(self))
            return "".join(content)

CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
conf = {'/': {'tools.staticdir.root':   CURRENT_DIR,
              'tools.sessions.on':      True,
              'tools.sessions.locking': 'explicit'
             }}
for directory in ['css', 'images']:
    if os.path.exists(os.path.join(CURRENT_DIR, directory)):
        conf['/'+directory] = {'tools.staticdir.on':  True,
                               'tools.staticdir.dir': directory}

cherrypy.tree.mount(Projects(), '/projects', config=conf)
