web.py
1 from datetime import datetime 2 from urllib import urlencode 3 4 from jinja2 import Environment, FileSystemLoader 5 import cherrypy 6 7 from libbe import storage 8 from libbe import bugdir 9 from libbe.command.depend import get_blocked_by, get_blocks 10 from libbe.command.target import add_target, remove_target 11 from libbe.command.target import bug_from_target_summary, bug_target 12 from libbe.command.util import bugdir_bug_comment_from_user_id 13 from libbe.storage.util import settings_object 14 import libbe.command.tag 15 16 17 EMPTY = settings_object.EMPTY 18 19 def datetimeformat(value, format='%B %d, %Y at %I:%M %p'): 20 """Takes a timestamp and revormats it into a human-readable string.""" 21 return datetime.fromtimestamp(value).strftime(format) 22 23 24 class WebInterface: 25 """The web interface to CFBE.""" 26 27 def __init__(self, bug_root, template_root): 28 """Initialize the bug repository for this web interface.""" 29 self.bug_root = bug_root 30 store = storage.get_storage(self.bug_root) 31 store.connect() 32 version = store.storage_version() 33 print version 34 self.bd = bugdir.BugDir(store, from_storage=True) 35 self.repository_name = self.bug_root.split('/')[-1] 36 self.env = Environment(loader=FileSystemLoader(template_root)) 37 self.env.filters['datetimeformat'] = datetimeformat 38 39 def get_common_information(self): 40 """Returns a dict of common information that most pages will need.""" 41 possible_assignees = list(set( 42 [unicode(bug.assigned) for bug in self.bd if bug.assigned != EMPTY])) 43 possible_assignees.sort(key=unicode.lower) 44 45 possible_targets = list(set( 46 [unicode(bug.summary.rstrip("\n")) for bug in self.bd \ 47 if bug.severity == u"target"])) 48 49 possible_targets.sort(key=unicode.lower) 50 51 possible_statuses = [u'open', u'assigned', u'test', u'unconfirmed', 52 u'closed', u'disabled', u'fixed', u'wontfix'] 53 54 possible_severities = [u'minor', u'serious', u'critical', u'fatal', 55 u'wishlist'] 56 57 return {'possible_assignees': possible_assignees, 58 'possible_targets': possible_targets, 59 'possible_statuses': possible_statuses, 60 'possible_severities': possible_severities, 61 'tags': libbe.command.tag.get_all_tags(self.bd), 62 'repository_name': self.repository_name,} 63 64 def filter_bugs(self, status, assignee, target, tag): 65 """Filter the list of bugs to return only those desired.""" 66 bugs = [bug for bug in self.bd if bug.status in status] 67 68 if assignee != '': 69 assignee = None if assignee == 'None' else assignee 70 bugs = [bug for bug in bugs if bug.assigned == assignee] 71 72 if tag != '' and tag != 'None': 73 bugs = [bug for bug in bugs 74 if tag in libbe.command.tag.get_tags(bug)] 75 76 if target != '': 77 target = None if target == 'None' else target 78 if target == None: 79 # Return all bugs that don't block any targets. 80 return [bug for bug in bugs if not bug_target(self.bd, bug)] 81 else: 82 # Return all bugs that block the supplied target. 83 targetbug = bug_from_target_summary(self.bd, target) 84 if targetbug == None: 85 return [] 86 bugs = [bug for bug in get_blocked_by(self.bd, targetbug) if 87 bug.active] 88 89 return bugs 90 91 92 @cherrypy.expose 93 def index(self, status='open', assignee='', target='', tag=''): 94 """The main bug page. 95 Bugs can be filtered by assignee or target. 96 The bug database will be reloaded on each visit.""" 97 98 self.bd.load_all_bugs() 99 100 if status == 'open': 101 status = ['open', 'assigned', 'test', 'unconfirmed', 'wishlist'] 102 label = 'All Open Bugs' 103 elif status == 'closed': 104 status = ['closed', 'disabled', 'fixed', 'wontfix'] 105 label = 'All Closed Bugs' 106 107 if assignee != '': 108 label += ' Currently Unassigned' if assignee == 'None' \ 109 else ' Assigned to %s' % (assignee,) 110 if target != '': 111 label += ' Currently Unscheduled' if target == 'None' \ 112 else ' Scheduled for %s' % (target,) 113 if tag != '' and tag != 'None': 114 label += ' Tagged %s' % (tag,) 115 116 bugs = self.filter_bugs(status, assignee, target, tag) 117 if len(bugs) == 0: 118 template = self.env.get_template('empty-list.html') 119 else: 120 template = self.env.get_template('list.html') 121 122 common_info = self.get_common_information() 123 return template.render(bugs=bugs, bd=self.bd, label=label, 124 assignees=common_info['possible_assignees'], 125 targets=common_info['possible_targets'], 126 statuses=common_info['possible_statuses'], 127 severities=common_info['possible_severities'], 128 repository_name=common_info['repository_name'], 129 tags=common_info['tags'], 130 urlencode=urlencode) 131 132 133 @cherrypy.expose 134 def bug(self, id=''): 135 """The page for viewing a single bug.""" 136 137 self.bd.load_all_bugs() 138 139 bugdir, bug, comment = bugdir_bug_comment_from_user_id( 140 {self.bd.uuid: self.bd}, id) 141 142 template = self.env.get_template('bug.html') 143 common_info = self.get_common_information() 144 145 # Determine which targets a bug has. 146 # First, is this bug blocking any other bugs? 147 targets = '' 148 blocks = get_blocks(self.bd, bug) 149 for targetbug in blocks: 150 # Are any of those blocked bugs targets? 151 blocker = self.bd.bug_from_uuid(targetbug.uuid) 152 if blocker.severity == "target": 153 targets += "%s " % blocker.summary 154 155 return template.render(bug=bug, bd=self.bd, 156 assignee='' if bug.assigned == EMPTY else bug.assigned, 157 target=targets, 158 assignees=common_info['possible_assignees'], 159 targets=common_info['possible_targets'], 160 statuses=common_info['possible_statuses'], 161 severities=common_info['possible_severities'], 162 repository_name=common_info['repository_name']) 163 164 165 @cherrypy.expose 166 def create(self, summary): 167 """The view that handles the creation of a new bug.""" 168 if summary.strip() != '': 169 self.bd.new_bug(summary=summary).save() 170 raise cherrypy.HTTPRedirect('/', status=302) 171 172 173 @cherrypy.expose 174 def comment(self, id, body): 175 """The view that handles adding a comment.""" 176 bug = self.bd.bug_from_uuid(id) 177 178 if body.strip() != '': 179 bug.comment_root.new_reply(body=body) 180 bug.save() 181 182 raise cherrypy.HTTPRedirect( 183 '/bug?%s' % urlencode({'id':bug.id.long_user()}), 184 status=302) 185 186 @cherrypy.expose 187 def edit(self, id, status=None, target=None, assignee=None, severity=None, summary=None): 188 """The view that handles editing bug details.""" 189 bug = self.bd.bug_from_uuid(id) 190 191 if summary != None: 192 bug.summary = summary 193 else: 194 bug.status = status if status != 'None' else None 195 bug.assigned = assignee if assignee != 'None' else None 196 bug.severity = severity if severity != 'None' else None 197 198 if target: 199 current_target = bug_target(self.bd, bug) 200 if current_target: 201 remove_target(self.bd, bug) 202 if target != "None": 203 add_target(self.bd, bug, target) 204 else: 205 add_target(self.bd, bug, target) 206 207 bug.save() 208 209 raise cherrypy.HTTPRedirect( 210 '/bug?%s' % urlencode({'id':bug.id.long_user()}), 211 status=302)