[Zope3-checkins] CVS: zopeproducts/bugtracker/browser - __init__.py:1.1 add.pt:1.1 attachments.py:1.1 branchentry.pt:1.1 bug.py:1.1 bug_edit.pt:1.1 bug_icon.png:1.1 bug_overview.pt:1.1 comment.py:1.1 configure.zcml:1.1 dependencies.pt:1.1 legend.pt:1.1 mail.py:1.1 subscriptions.pt:1.1 tracker.css:1.1 tracker.py:1.1 tracker_add.pt:1.1 tracker_icon.png:1.1 tracker_overview.pt:1.1 tracker_settings.pt:1.1
Stephan Richter
srichter@cosmos.phy.tufts.edu
Thu, 24 Jul 2003 14:08:26 -0400
Update of /cvs-repository/zopeproducts/bugtracker/browser
In directory cvs.zope.org:/tmp/cvs-serv302/browser
Added Files:
__init__.py add.pt attachments.py branchentry.pt bug.py
bug_edit.pt bug_icon.png bug_overview.pt comment.py
configure.zcml dependencies.pt legend.pt mail.py
subscriptions.pt tracker.css tracker.py tracker_add.pt
tracker_icon.png tracker_overview.pt tracker_settings.pt
Log Message:
First Checkin of the Bug Tracker. A list of features is the README.txt file
and a to-do list is in TODO.txt.
The code features the use of vocabularies and vocabulary fields.
There is still a bit of work to do, but I am pretty close to make it usable
for us.
=== Added File zopeproducts/bugtracker/browser/__init__.py ===
=== Added File zopeproducts/bugtracker/browser/add.pt ===
<html metal:use-macro="views/standard_macros/dialog">
<body>
<div metal:fill-slot="body">
<form action="action.html" method="POST">
<table class="TypeListing" cellpadding="3">
<caption>Add Content</caption>
<tbody tal:repeat="info view/addingInfo">
<tr>
<td class="Selector">
<input type="radio" name="type_name"
tal:attributes="value info/action; id info/action" />
</td>
<td class="TypeName">
<label style="font-weight: bold;"
tal:attributes="for info/action">
<span tal:replace="info/title" >Folder</span>
</label>
<div class="TypeDescription" tal:content="info/description">
Folders are generic containers for content, including other
folders.
</div>
</td>
</tr>
</tbody>
<tbody tal:condition="nothing">
<tr>
<td class="Selector">
<input type="radio" name="type_name" value="" />
</td>
<td class="TypeName">
<img alt="Folder" src="../../ZMI/www/document_icon.gif" />
Document
</td>
</tr>
<tr>
<td class="Selector"><br /></td>
<td class="TypeDescription">
Documents are simple textual content.
</td>
</tr>
</tbody>
<tr>
<td><br/></td>
<td>
<input type="text" name="id"
tal:condition="view/namesAccepted"
tal:attributes="value request/id | nothing"
/>
<input type="submit" value=" Add " />
</td>
</tr>
</table>
</form>
</div>
</body>
</html>
=== Added File zopeproducts/bugtracker/browser/attachments.py ===
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Attachment Views
$Id: attachments.py,v 1.1 2003/07/24 18:08:10 srichter Exp $
"""
from zope.app.browser.form.widget import FileWidget
from zope.app.form.widget import CustomWidget
class FileUpload:
"""File editing mix-in that uses a file-upload widget."""
data_widget = CustomWidget(FileWidget)
=== Added File zopeproducts/bugtracker/browser/branchentry.pt ===
<li>
<a class="" href=""
tal:attributes="href string:../${view/name};
class string:node ${context/status} ${context/priority}">
<span tal:replace="string:${context/title} (${view/name})">
Bug Title (bugid)
</span>
</a>
</li>
=== Added File zopeproducts/bugtracker/browser/bug.py ===
##############################################################################
#
# Copyright (c) 2003 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Browser View Components for Bugs
$Id: bug.py,v 1.1 2003/07/24 18:08:10 srichter Exp $
"""
import re
from zope.component import getService, getAdapter
from zope.component.interfaces import IViewFactory
from zope.interface import implements
from zope.proxy import removeAllProxies
from zope.schema.vocabulary import getVocabularyRegistry
from zope.app.browser.form.vocabularywidget import VocabularyFieldEditWidget
from zope.app.browser.form.widget import TextWidget
from zope.app.context import ContextWrapper
from zope.app.form.widget import CustomWidget
from zope.app.services.servicenames import Authentication
from zope.app.traversing import getParent, getName
from zope.app.interfaces.dublincore import IZopeDublinCore
from zope.app.interfaces.size import ISized
from zope.app.pagetemplate.viewpagetemplatefile import ViewPageTemplateFile
from zope.app.browser.form.widget import MultiListWidget, ListWidget
from zope.app.browser.container.adding import Adding
from zopeproducts.bugtracker.interfaces import IComment
from zopeproducts.bugtracker.interfaces import IBugDependencies
from zopeproducts.bugtracker.browser.comment import CommentViewBase
from zopeproducts.bugtracker.browser.StructuredText import HTML
class BugAdding(Adding):
"""Custom adding view for NewsSite objects."""
menu_id = "add_bug"
def add(self, content):
container = self.context
if IComment.isImplementedBy(content):
names = filter(lambda n: n.startswith('comment'), container.keys())
if not container.keys():
self.contentName = 'comment1'
else:
self.contentName = 'comment' + str(int(max(names)[7:])+1)
else:
# we have a file or an image
self.contentName = self.request['field.data'].filename
name = container.setObject(self.contentName, content)
return container[name]
def nextURL(self):
return '../@@overview.html'
class BugBaseView(object):
"""Get's all the fancy expressions for the attribute values."""
def created(self):
dc = getAdapter(self.context, IZopeDublinCore)
formatter = self.request.locale.getDateTimeFormatter('short')
return formatter.format(dc.created)
def modified(self):
dc = getAdapter(self.context, IZopeDublinCore)
formatter = self.request.locale.getDateTimeFormatter('short')
if dc.modified is None:
return self.created()
return formatter.format(dc.modified)
def description(self):
html = HTML(str(self.context.description), level=3)
html = re.sub(
r'(?sm)^<html.*<body.*?>\n(.*)</body>\n</html>\n',r'\1', html)
return html
def status(self):
registry = getVocabularyRegistry()
types = registry.get(getParent(self.context), 'Stati')
return types.getTerm(self.context.status)
def type(self):
registry = getVocabularyRegistry()
types = registry.get(getParent(self.context), 'BugTypes')
return types.getTerm(self.context.type)
def release(self):
if self.context.release is None:
return u'not specified'
registry = getVocabularyRegistry()
types = registry.get(getParent(self.context), 'Releases')
return types.getTerm(self.context.release)
def priority(self):
registry = getVocabularyRegistry()
types = registry.get(getParent(self.context), 'Priorities')
return types.getTerm(self.context.priority)
def owners(self):
registry = getVocabularyRegistry()
users = registry.get(getParent(self.context), 'Users')
return map(lambda owner: users.getTerm(owner), self.context.owners)
class AddBug(object):
def nextURL(self):
return '../'+self.context.contentName
class CustomVocabularyWidget:
implements(IViewFactory)
def __init__(self, *args, **kw):
self._widget_factory = args[0]
if len(args) > 1:
self.args = args[1:]
else:
self.args = ()
self.kw = kw
def __call__(self, context, request):
field = context
context = field.vocabulary
args = (context, request) + self.args
instance = self._widget_factory(*args)
instance.setField(field)
for item in self.kw.items():
setattr(instance, item[0], item[1])
return instance
class EditBug(BugBaseView):
type_widget = CustomWidget(VocabularyFieldEditWidget, size=1)
status_widget = CustomWidget(VocabularyFieldEditWidget, size=1)
priority_widget = CustomWidget(VocabularyFieldEditWidget, size=1)
release_widget = CustomWidget(VocabularyFieldEditWidget, size=1)
title_widget = CustomWidget(TextWidget, size=40)
class Overview(BugBaseView):
"""View class providing necessary methods for the bug overview."""
def comments(self):
"""Get a list of all comments."""
comments = []
for name, obj in self.context.items():
if IComment.isImplementedBy(obj):
wrapped = ContextWrapper(obj, self.context, name=name)
comments.append(CommentViewBase(wrapped, self.request))
return comments
def attachments(self):
"""Get a list of all attachments."""
attchs = []
for name, obj in self.context.items():
if not IComment.isImplementedBy(obj):
size = getAdapter(obj, ISized)
attchs.append({'name': name, 'size': size.sizeForDisplay()})
return attchs
def dependencies(self):
deps = getAdapter(self.context, IBugDependencies)
return deps.dependencies
class Dependencies(object):
def dependencies(self):
deps = getAdapter(self.context, IBugDependencies)
return deps.dependencies
def availableBugs(self):
tracker = getParent(self.context)
bugs = []
for name, bug in tracker.items():
# Make sure we do not list the bug itself
if name != getName(self.context):
bugs.append({'name': name, 'title': bug.title})
return bugs
def setDependencies(self, dependencies=()):
deps = getAdapter(self.context, IBugDependencies)
deps.setDependencies(dependencies)
return self.request.response.redirect('./@@dependencies.html')
def _branchHTML(self, children):
html = '<ul>\n'
for child, subs in children:
html += DependencyEntry(child, self.request)()
if subs:
html += self._branchHTML(subs)
html += '</ul>\n'
return html
def branch(self):
deps = getAdapter(self.context, IBugDependencies)
children = deps.findChildren()
return self._branchHTML(children)
def _getAllSubs(self, children):
all = map(lambda c: c[0], children)
for child, subs in children:
all += self._getAllSubs(subs)
return all
def getStatistics(self):
deps = getAdapter(self.context, IBugDependencies)
children = deps.findChildren()
all = self._getAllSubs(children)
all_num = len(all)
if not all_num:
return {}
closed = len(filter(lambda b: b.status in ('closed', 'deferred'), all))
new = len(filter(lambda b: b.status == 'new', all))
open = len(filter(lambda b: b.status in ('open', 'assigned'), all))
stats = {'total': all_num,
'closed': closed,
'closed_perc': '%.2f%%' %(closed*100.0/all_num),
'new': new,
'new_perc': '%.2f%%' %(new*100.0/all_num),
'open': open,
'open_perc': '%.2f%%' %(open*100.0/all_num),
}
return stats
legend = ViewPageTemplateFile('legend.pt')
class DependencyEntry(object):
def __init__(self, context, request):
self.context = context
self.request = request
def name(self):
return getName(self.context)
__call__ = ViewPageTemplateFile('branchentry.pt')
=== Added File zopeproducts/bugtracker/browser/bug_edit.pt ===
<html metal:use-macro="views/standard_macros/page">
<head>
<style metal:fill-slot="style_slot"
type="text/css" media="all"
tal:content=
"string: @import url(${context/++resource++tracker.css});">
@import url(tracker.css);>
</style>
</head>
<body>
<div metal:fill-slot="body">
<form action="." tal:attributes="action request/URL" method="POST"
enctype="multipart/form-data"
>
<p tal:define="status view/update"
tal:condition="status"
tal:content="status" />
<p tal:condition="view/errors">
<span i18n:translate="">There are</span>
<strong tal:content="python:len(view.errors)">6</strong>
<span i18n:translate="">input errors.</span>
</p>
<h1 tal:content="structure view/title_widget">
<input type="text" name="title" value="title" />
</h1>
From <b tal:content="context/submitter">user</b> at
<b tal:content="view/created">2001/01/01 12:00</b>
<div>
<div class="row">
<div class="label md_label">Type:</div>
<div class="field md_field"
tal:content="structure view/type_widget" />
<div class="label md_label">Status:</div>
<div class="field md_field"
tal:content="structure view/status_widget" />
</div>
<div class="row">
<div class="label md_label">Priority:</div>
<div class="field md_field"
tal:content="structure view/priority_widget" />
<div class="label md_label">Release Target:</div>
<div class="field md_field"
tal:content="structure view/release_widget" />
</div>
<div class="row">
<div class="label">Owners:</div>
<div class="field"
tal:content="structure view/owners_widget" />
</div>
<div class="row">
<div class="label">Last modified:</div>
<div class="field" tal:content="view/modified"></div>
</div>
</div>
<h4>Description</h4>
<div tal:content="structure view/description_widget" />
<div class="row">
<div class="controls">
<input type="submit" value="Refresh"
i18n:attributes="value refresh-button" />
<input type="submit" name="UPDATE_SUBMIT" value="Submit"
i18n:attributes="value submit-button"/>
</div>
</div>
</form>
</div>
</body>
</html>
=== Added File zopeproducts/bugtracker/browser/bug_icon.png ===
<Binary-ish file>
=== Added File zopeproducts/bugtracker/browser/bug_overview.pt ===
<html metal:use-macro="views/standard_macros/page">
<head>
<style metal:fill-slot="style_slot"
type="text/css" media="all"
tal:content=
"string: @import url(${context/++resource++tracker.css});">
@import url(tracker.css);>
</style>
</head>
<body>
<div metal:fill-slot="body">
<h1 tal:content="context/title">Bug Number 1</h1>
From <b tal:content="context/submitter">user</b> at
<b tal:content="view/created">2001/01/01 12:00</b>
<div>
<div class="row">
<div class="label md_label">Type:</div>
<div class="field md_field" tal:content="view/type/title"></div>
<div class="label md_label">Status:</div>
<div class="field md_field">
<span tal:attributes="class context/status"
tal:content="view/status/title" />
</div>
</div>
<div class="row">
<div class="label md_label">Priority:</div>
<div class="field md_field">
<span tal:attributes="class context/priority"
tal:content="view/priority/title" />
</div>
<div class="label md_label">Release Target:</div>
<div class="field md_field" tal:content="view/release/title"></div>
</div>
<div class="row">
<div class="label">Owners:</div>
<div class="field">
<tal:block repeat="owner view/owners">
<d tal:content="owner/principal/getTitle" tal:omit-tag="" />
<d tal:condition="not:repeat/owner/end" tal:omit-tag="">,</d>
</tal:block>
</div>
</div>
<div class="row">
<div class="label">Last modified:</div>
<div class="field" tal:content="view/modified"></div>
</div>
</div>
<h4>Description</h4>
<div class="single_p" id="description"
tal:content="structure view/description">Bug Description</div>
<h4>Direct Dependencies</h4>
<div class="single_p">
<tal:block repeat="dep view/dependencies" tal:omit-tag="">
<a href="" tal:attributes="href string:../$dep"
tal:content="dep">Dep Bug</a>
<d tal:condition="not:repeat/dep/end" tal:replace="string:," />
</tal:block>
</div>
<h4>Attachments</h4>
<ul id="attachments" tal:condition="view/attachments">
<li tal:repeat="attch view/attachments">
<a href="" tal:attributes="href string: ./${attch/name}"
tal:content="attch/name">Attachment 1</a>
(<div tal:replace="attch/size">Size here</div>)
</li>
</ul>
<div class="action">
<a href="./@@+/UploadFile=">Add File</a> |
<a href="./@@+/UploadImage=">Add Image</a>
</div>
<h4>Comments</h4>
<div tal:repeat="comment view/comments">
<h5 style="margin: 0em 0.6em">
Entry #<d tal:replace="repeat/comment/number" /> by
<d tal:replace="comment/creator" /> on
<d tal:replace="comment/modified" />
</h5>
<div class="comment"
tal:content="structure comment/body">Comment body</div>
</div>
<div class="action">
<a href="./@@+/AddBugComment=">Add Comment</a>
</div>
</div>
</body>
</html>
=== Added File zopeproducts/bugtracker/browser/comment.py ===
##############################################################################
#
# Copyright (c) 2003 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Structured Text Renderer Classes
$Id: comment.py,v 1.1 2003/07/24 18:08:10 srichter Exp $
"""
import re
from zope.component import getAdapter
from zope.app.interfaces.dublincore import IZopeDublinCore
from StructuredText import HTML
class CommentViewBase(object):
def __init__(self, context, request):
self.context = context
self.request = request
def creator(self):
dc = getAdapter(self.context, IZopeDublinCore)
return dc.creators[0]
def modified(self):
dc = getAdapter(self.context, IZopeDublinCore)
formatter = self.request.locale.getDateTimeFormatter('short')
if dc.modified is None:
return formatter.format(dc.created)
return formatter.format(dc.modified)
def body(self):
html = HTML(str(self.context.body), level=3)
# strip html & body added by some zope versions
html = re.sub(
r'(?sm)^<html.*<body.*?>\n(.*)</body>\n</html>\n',r'\1', html)
return html
=== Added File zopeproducts/bugtracker/browser/configure.zcml ===
<zopeConfigure
xmlns="http://namespaces.zope.org/zope"
xmlns:browser="http://namespaces.zope.org/browser">
<include package=".skin" />
<browser:resource
name="tracker.css" file="tracker.css" layer="rotterdam" />
<browser:menu
id="add_bugtracker"
title="Menu of objects to be added to Bug Trackers."/>
<browser:menu
id="add_bug"
title="Menu of objects to be added to Bugs."/>
<browser:icon
name="zmi_icon"
for="zopeproducts.bugtracker.interfaces.IBugTracker"
file="tracker_icon.png"
/>
<!-- Custom adding views. -->
<browser:view
for="zopeproducts.bugtracker.interfaces.IBugTracker"
name="+"
class=".tracker.BugTrackerAdding"
permission="zope.View"
allowed_attributes="addingInfo"
menu="zmi_actions"
title="Add">
<browser:page name="index.html" template="add.pt" />
<browser:page name="action.html" attribute="action" />
</browser:view>
<browser:view
for="zopeproducts.bugtracker.interfaces.IBug"
name="+"
class=".bug.BugAdding"
permission="zope.View"
allowed_attributes="addingInfo"
menu="zmi_actions"
title="Add">
<browser:page name="index.html" template="add.pt" />
<browser:page name="action.html" attribute="action" />
</browser:view>
<!-- Bug tracker configuration -->
<browser:addform
label="Add Bug Tracker"
name="AddBugTracker"
schema="zopeproducts.bugtracker.interfaces.IBugTracker"
content_factory="zopeproducts.bugtracker.tracker.BugTracker"
permission="zope.View"
class=".tracker.AddBugTracker"
template="tracker_add.pt"
menu="add_content" title="Bug Tracker"/>
<browser:pages
for="zopeproducts.bugtracker.interfaces.IBugTracker"
class=".tracker.Overview"
permission="zope.View">
<browser:page name="overview.html" template="tracker_overview.pt"
menu="zmi_views" title="Overview" />
<browser:page name="updateOverviewSettings.html"
attribute="updateSettings" />
</browser:pages>
<browser:pages
for="zopeproducts.bugtracker.interfaces.IBugTracker"
class=".tracker.Settings"
permission="zope.View">
<browser:page name="settings.html" template="tracker_settings.pt"
menu="zmi_views" title="Settings" />
<browser:page name="addValue.html" attribute="addValue" />
<browser:page name="deleteValues.html" attribute="deleteValues" />
</browser:pages>
<browser:pages
for="zopeproducts.bugtracker.interfaces.IBugTracker"
class=".mail.MailSubscriptions"
permission="zope.View">
<browser:page name="subscriptions.html" template="subscriptions.pt"
menu="zmi_views" title="Subscriptions" />
<browser:page name="changeSubscriptions.html" attribute="change" />
</browser:pages>
<browser:defaultView
name="overview.html"
for="zopeproducts.bugtracker.interfaces.IBugTracker"/>
<!-- Bug configuration -->
<browser:addform
label="Add Bug"
name="AddBug"
schema="zopeproducts.bugtracker.interfaces.IBug"
content_factory="zopeproducts.bugtracker.bug.Bug"
permission="zope.View"
fields="title description type owners status priority release"
class=".bug.AddBug"
menu="add_bugtracker"
title="Bug"/>
<browser:editform
schema="zopeproducts.bugtracker.interfaces.IBug"
for="zopeproducts.bugtracker.interfaces.IBug"
label="Change Bug"
name="edit.html"
permission="zope.View"
fields="title description type owners status priority release"
template="bug_edit.pt"
class=".bug.EditBug"
menu="zmi_views" title="Edit" />
<browser:pages
for="zopeproducts.bugtracker.interfaces.IBug"
class=".bug.Overview"
permission="zope.View">
<browser:page name="overview.html" template="bug_overview.pt"
menu="zmi_views" title="Overview" />
</browser:pages>
<browser:pages
for="zopeproducts.bugtracker.interfaces.IBug"
class=".bug.Dependencies"
permission="zope.View">
<browser:page name="dependencies.html" template="dependencies.pt"
menu="zmi_views" title="Dependencies" />
<browser:page name="setDependencies.html" attribute="setDependencies" />
</browser:pages>
<browser:pages
for="zopeproducts.bugtracker.interfaces.IBug"
class=".mail.MailSubscriptions"
permission="zope.View">
<browser:page name="subscriptions.html" template="subscriptions.pt"
menu="zmi_views" title="Subscriptions" />
<browser:page name="changeSubscriptions.html" attribute="change" />
</browser:pages>
<browser:page
name="contents.html"
for="zopeproducts.bugtracker.interfaces.IBug"
permission="zope.ManageContent"
class="zope.app.browser.container.contents.Contents"
attribute="contents"
menu="zmi_views" title="Contents"/>
<browser:defaultView
name="overview.html"
for="zopeproducts.bugtracker.interfaces.IBug"/>
<browser:icon
name="zmi_icon"
for="zopeproducts.bugtracker.interfaces.IBug"
file="bug_icon.png"/>
<!-- Comment configuration -->
<browser:addform
label="Add Comment"
name="AddBugComment"
schema="zopeproducts.bugtracker.interfaces.IComment"
content_factory="zopeproducts.bugtracker.comment.Comment"
permission="zope.View"
menu="add_bug" title="Comment"/>
<browser:editform
schema="zopeproducts.bugtracker.interfaces.IComment"
for="zopeproducts.bugtracker.interfaces.IComment"
label="Change Comment"
name="edit.html"
permission="zope.View"
menu="zmi_views" title="Edit" />
<browser:defaultView
name="edit.html"
for="zopeproducts.bugtracker.interfaces.IComment"/>
<!-- Attachments configuration -->
<browser:addform
label="Upload File Attachment"
name="UploadFile"
schema="zope.app.interfaces.content.file.IFile"
content_factory="zope.app.content.file.File"
permission="zope.View"
fields="data"
class=".attachments.FileUpload"
menu="add_bug" title="File Attachment"/>
<browser:addform
label="Upload Image Attachment"
name="UploadImage"
schema="zope.app.interfaces.content.image.IImage"
content_factory="zope.app.content.image.Image"
permission="zope.View"
fields="data"
class=".attachments.FileUpload"
menu="add_bug" title="Image Attachment"/>
</zopeConfigure>
=== Added File zopeproducts/bugtracker/browser/dependencies.pt ===
<html metal:use-macro="views/standard_macros/page">
<head>
<style metal:fill-slot="style_slot" tal:define="global pagetip view/legend"
type="text/css" media="all"
tal:content=
"string: @import url(${context/++resource++tracker.css});">
@import url(tracker.css);>
</style>
</head>
<body i18n:domain="bugtracker">
<div metal:fill-slot="body">
<form action="setDependencies.html" method="post">
<div id="explanation">
In the selection box below you can select the bugs that have to be
completed <em>before</em> this bug can be completed.
</div>
<div>
<div class="row">
<div class="label" i18n:translate="">Dependencies</div>
<div class="field">
<select name="dependencies:list" size="5" multiple="">
<div tal:repeat="bug view/availableBugs" tal:omit-tag="">
<b tal:content="bug/name" />
<b tal:content="bug/title" />
<option
value="" selected=""
tal:content="string: ${bug/title} (${bug/name})"
tal:attributes="value bug/name"
tal:condition="python: bug['name'] in view.dependencies()">
Bug1
</option>
<option
tal:content="string: ${bug/title} (${bug/name})"
tal:attributes="value bug/name"
tal:condition="python: bug['name'] not in view.dependencies()">
Bug1
</option>
</div>
</select>
</div>
</div>
</div>
<div class="row">
<input type="submit" name="submit" value="Change"
i18n:attributes="value change-button" />
</div>
</form>
<h4 i18n:translate="">Dependency Statistics</h4>
<div class="stats"
tal:define="stats view/getStatistics"
tal:condition="view/getStatistics">
<div class="row">
<div class="label" i18n:translate="">Closed/Deferred Bugs:</div>
<div class="field">
<b tal:content="stats/closed_perc">75%</b>
(<d tal:replace="stats/closed" /> /
<d tal:replace="stats/total" />)
</div>
</div>
<div class="row">
<div class="label" i18n:translate="">New (unseen) Bugs:</div>
<div class="field">
<b tal:content="stats/new_perc">75%</b>
(<d tal:replace="stats/new" /> /
<d tal:replace="stats/total" />)
</div>
</div>
<div class="row">
<div class="label" i18n:translate="">Opened/Assigned Bugs:</div>
<div class="field">
<b tal:content="stats/open_perc">75%</b>
(<d tal:replace="stats/open" /> /
<d tal:replace="stats/total" />)
</div>
</div>
</div>
<h4 i18n:translate="">Dependency Tree</h4>
<p tal:replace="structure view/branch" />
</div>
</body>
</html>
=== Added File zopeproducts/bugtracker/browser/legend.pt ===
<h6>Status Markup</h6>
<a href="" class="node new">New</a><br/>
<a href="" class="node open">Open</a><br/>
<a href="" class="node assigned">Assigned</a><br/>
<a href="" class="node deferred">Deferred</a><br/>
<a href="" class="node closed">Closed</a><br/>
<h6>Priority Markup</h6>
<a href="" class="node low">Low</a><br/>
<a href="" class="node normal">Normal</a><br/>
<a href="" class="node urgent">Urgent</a><br/>
<a href="" class="node critical">Critial</a><br/>
=== Added File zopeproducts/bugtracker/browser/mail.py ===
##############################################################################
#
# Copyright (c) 2003 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Browser Views for IMessage
$Id: mail.py,v 1.1 2003/07/24 18:08:10 srichter Exp $
"""
from zope.component import getAdapter
from zopeproducts.bugtracker.interfaces import IBug, IMailSubscriptions
class MailSubscriptions:
def subscriptions(self):
return getAdapter(self.context, IMailSubscriptions).getSubscriptions()
def change(self):
if 'ADD' in self.request:
emails = self.request['emails'].split('\n')
getAdapter(self.context,
IMailSubscriptions).addSubscriptions(emails)
elif 'REMOVE' in self.request:
emails = self.request['remails']
print emails
if isinstance(emails, (str, unicode)):
emails = [emails]
getAdapter(self.context,
IMailSubscriptions).removeSubscriptions(emails)
self.request.response.redirect('.')
=== Added File zopeproducts/bugtracker/browser/subscriptions.pt ===
<html metal:use-macro="views/standard_macros/page">
<head>
<style metal:fill-slot="style_slot">
</style>
</head>
<body>
<div metal:fill-slot="body" i18n:domain="bugtracker">
<form action="changeSubscriptions.html" method="post">
<div class="row">
<div class="label" i18n:translate="">Current Subscriptions</div>
<div class="field">
<div tal:repeat="email view/subscriptions">
<input type="checkbox" name="remails:list"
value="" tal:attributes="value email">
<div tal:replace="email">zope3@zope3.org</div>
</div>
<input type="submit" name="REMOVE" value="Remove"
i18n:attributes="value remove-button">
</div>
</div>
<div class="row">
<div class="label" i18n:translate="">
Enter new Users (separate by 'Return')
</div>
<div class="field">
<textarea name="emails" cols="40" rows="10"></textarea>
</div>
</div>
<div class="row">
<div class="controls">
<input type="submit" value="Refresh"
i18n:attributes="value refresh-button" />
<input type="submit" name="ADD" value="Add"
i18n:attributes="value add-button" />
</div>
</div>
</form>
</div>
</body>
</html>
=== Added File zopeproducts/bugtracker/browser/tracker.css ===
/* Tracker Overview */
.summary_content {
padding-left: 0.5em;
}
.summary_title {
font-weight: bold;
}
.summary_body {
font-size: 100%;
}
.summary_condition {
}
.summary_metadata {
font-size: 80%;
color: #808080;
}
div.even {
padding: 0.3em;
background-color: #F8F8F8;
}
div.odd {
padding: 0.3em;
background-color: White;
}
div.batch {
background-color: #F0F0F0;
border: 1px solid #808080;
padding: 2px;
clear: both;
}
div.batch div.prev_batch {
text-align: left;
float: left;
width: 20%;
}
div.batch div.curr_batch {
text-align: center;
float: left;
width: 59%;
}
div.batch div.next_batch {
text-align: right;
float: right;
width: 20%;
}
div.clear {
clear: both;
}
/* Bug Overview */
.single_p {
padding-top: 0.5em;
}
.comment {
margin: 0.6em;
padding: 0.6em;
background-color: #F0F0F0;
border: 1pt solid #AAAAAA;
}
#description {
font-size: 120%;
padding: 0.6em;
margin: 0.6em;
background-color: #F8F8F8;
border: 1pt solid #EEEEEE;
}
p {
margin: 0px;
}
div.action {
padding-top: 1em;
}
#attachments {
margin-top: 4px;
margin-bottom: 0px;
}
div.md_label {
width: 25%;
}
div.md_field {
width: 25%;
}
/* Bug Dependencies */
#explanation {
font-weight: bold;
}
a.node {
text-decoration: none;
}
/* Status */
.new {
font-style: italic;
}
.open {
font-variant: small-caps;
}
.assigned {
}
.closed {
text-decoration: line-through;
}
.deferred {
font-style: italic;
text-decoration: line-through;
}
/* Priorities */
.low {
color: black;
}
.normal {
}
.urgent {
font-weight: bold;
color: #FFCE7B;
}
.critical {
font-weight: bold;
color: #FFA500;
}
/* Statistics-specific options */
div.stats {
padding-top: 0.5em;
padding-bottom: 1.5em;
}
div.stats div.row {
margin: 2pt;
padding: 0pt;
width: 100%;
}
div.stats div.label {
width: 30%;
}
=== Added File zopeproducts/bugtracker/browser/tracker.py ===
##############################################################################
#
# Copyright (c) 2003 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Browser View Components for Bug Trackers
$Id: tracker.py,v 1.1 2003/07/24 18:08:10 srichter Exp $
"""
from zope.component import getAdapter
from zope.schema.vocabulary import getVocabularyRegistry
from zope.app.browser.container.adding import Adding
from zope.app.interfaces.dublincore import IZopeDublinCore
from zopeproducts.bugtracker.interfaces import \
IStatusVocabulary, IReleaseVocabulary
from zopeproducts.bugtracker.interfaces import \
IPriorityVocabulary, IBugTypeVocabulary
class BugTrackerAdding(Adding):
"""Custom adding view for NewsSite objects."""
menu_id = "add_bugtracker"
def add(self, content):
container = self.context
if not container.keys():
self.contentName = '1'
else:
self.contentName = str(int(max(container.keys()))+1)
name = container.setObject(self.contentName, content)
return container[name]
class AddBugTracker(object):
"""Add a bug tracker."""
def createAndAdd(self, data):
content = super(AddBugTracker, self).createAndAdd(data)
if self.request.get('setup_vocabs'):
vocab = getAdapter(content, IStatusVocabulary)
vocab.add('new', u'New')
vocab.add('open', u'Open')
vocab.add('assigned', u'Assigned')
vocab.add('deferred', u'Deferred')
vocab.add('closed', u'Closed')
vocab = getAdapter(content, IBugTypeVocabulary)
vocab.add('bug', u'Bug')
vocab.add('feature', u'Feature')
vocab.add('release', u'Release')
vocab = getAdapter(content, IReleaseVocabulary)
vocab.add('None', u'(not specified)')
vocab = getAdapter(content, IPriorityVocabulary)
vocab.add('low', u'Low')
vocab.add('normal', u'Normal')
vocab.add('urgent', u'Urgent')
vocab.add('critical', u'Critical')
return content
class Settings(object):
"""Change the settings of the Bug Tracker."""
# list of managable vocabulary interfaces that we want to have
ifaces = [IStatusVocabulary, IReleaseVocabulary,
IPriorityVocabulary, IBugTypeVocabulary]
def getManagableVocabularyViews(self):
return map(lambda iface:
ManagableVocabularyView(self.context, self.request, iface),
self.ifaces)
def addValue(self, iface, value, title):
iface = filter(lambda i: i.getName() == iface, self.ifaces)[0]
vocab = ManagableVocabularyView(self.context, self.request, iface)
vocab.addValue(value, title)
def deleteValues(self, iface, values):
iface = filter(lambda i: i.getName() == iface, self.ifaces)[0]
vocab = ManagableVocabularyView(self.context, self.request, iface)
vocab.deleteValues(values)
class ManagableVocabularyView(object):
def __init__(self, context, request, vocab_iface):
self.context = context
self.request = request
self.vocab_iface = vocab_iface
def getExistingValues(self):
vocab = getAdapter(self.context, self.vocab_iface)
return iter(vocab)
def addValue(self, value, title):
vocab = getAdapter(self.context, self.vocab_iface)
vocab.add(value, title)
return self.request.response.redirect('./@@settings.html')
def deleteValues(self, values):
vocab = getAdapter(self.context, self.vocab_iface)
for value in values:
vocab.delete(value)
return self.request.response.redirect('./@@settings.html')
def title(self):
vocab = getAdapter(self.context, self.vocab_iface)
return vocab.title
from zope.app.interfaces.index.text import ISearchableText
def checkBug(bug, criteria, search_text):
for name, values in criteria:
if values and not getattr(bug, name) in values:
return False
# XXX: Extremely crude text search; should use indexes
text = ' '.join(getAdapter(bug, ISearchableText).getSearchableText())
if search_text != '':
terms = search_text.split(' ')
for term in terms:
if not term in text:
return False
return True
from bug import BugBaseView
from zopeproducts.bugtracker.interfaces import IComment
from zope.app.traversing import getName
class BugView(BugBaseView):
def __init__(self, context, request):
self.context = context
self.request = request
def numberOfComments(self):
return len(filter(IComment.isImplementedBy, self.context.values()))
def name(self):
return getName(self.context)
def descriptionPreview(self):
if len(self.context.description) < 200:
return self.context.description
else:
return self.context.description[:200] + u'...'
class Overview(object):
"""Overview of all the bugs."""
# Tuple values:
# - collection name
# - Vocabulary Registry name
# - Display Title
# - bug attribute name
filter_vars = (('stati', 'Stati', 'Status', 'status'),
('types', 'BugTypes', 'Type', 'type'),
('releases', 'Releases', 'Release', 'release'),
('priorities', 'Priorities', 'Priority', 'priority'),
)
def getBugs(self):
"""Return a list of all bugs having a status listed in the parameter.
If the parameter is an empty list/tuple, then show all bugs."""
criteria = []
for collName, dummy1, dummy2, name in self.filter_vars:
raw = self.request.cookies.get(collName, "")
criteria.append((name, raw.split(", ")))
formatter = self.request.locale.getDateTimeFormatter('short')
result = []
for name, bug in self.context.items():
if checkBug(bug, criteria, self.getSearchText()):
result.append(BugView(bug, self.request))
start = int(self.request.get('start', 0))
size = int(self.request.get('size', 20))
return Batch(result, start, size)
def updateSettings(self):
for collName, dummy1, dummy2, dummy3 in self.filter_vars:
values = self.request.get(collName, ())
self.request.response.setCookie(collName, ', '.join(values))
self.setSearchText()
return self.request.response.redirect('./overview.html')
def getSettingsInfo(self):
registry = getVocabularyRegistry()
info = []
for varname, vocname, title, dummy in self.filter_vars:
raw = self.request.cookies.get(varname, "")
info.append({'setting': raw.split(", "),
'all': iter(registry.get(self.context, vocname)),
'title': title,
'name': varname})
return info
def getSearchText(self):
return self.request.cookies.get('search_text', '')
def setSearchText(self):
value = self.request.get('search_text', '')
self.request.response.setCookie('search_text', value)
def numberOfBugs(self):
return len(self.context)
class Batch(object):
""" """
def __init__(self, list, start=0, size=20):
self.list = list
self.start = start
if len(list) == 0:
self.start = -1
else:
assert start < len(list)
self.size = size
self.trueSize = size
if start+size >= len(list):
self.trueSize = len(list)-start
self.end = start+self.trueSize-1
def __len__(self):
return self.trueSize
def __getitem__(self, key):
assert key < trueSize
return self.list[self.start+key]
def __iter__(self):
return iter(self.list[self.start:self.end+1])
def __contains__(self, key):
return key >= 0 and key < self.__len__()
def nextBatch(self):
start = self.start + self.size
if start >= len(self.list):
return None
return Batch(self.list, start, self.size)
def prevBatch(self):
start = self.start - self.size
if start < 0:
return None
return Batch(self.list, start, self.size)
def first(self):
return self.list[self.start]
def last(self):
return self.list[self.end]
def total(self):
return len(self.list)
def startNumber(self):
return self.start+1
def endNumber(self):
return self.end+1
=== Added File zopeproducts/bugtracker/browser/tracker_add.pt ===
<html metal:use-macro="views/standard_macros/page">
<head>
<style metal:fill-slot="style_slot">
</style>
</head>
<body>
<div metal:fill-slot="body" i18n:domain="bugtracker">
<p tal:define="status view/update"
tal:condition="status"
tal:content="status" />
<form action="." tal:attributes="action request/URL" method="post">
<div class="row">
<div class="field">
<h3><input type="checkbox" name="setup_vocabs:int" value="1"
checked=""/>
<span i18n:translate="">Create Initial Vocabulary Entries</span>
</h3>
<span i18n:translate=''>To make your life easier, when this
option is selected, it creates vocabulary entries for the
status, release, priority and type fields of a bug. This
will save you some time with the setup.</span>
</div>
</div>
<h3 i18n:translate="">Bug Tracker Fields</h3>
<div class="row"
metal:define-macro="widget_rows" tal:repeat="widget view/widgets"
tal:content="structure widget/row">
<div class="label">Name</div>
<div class="field"><input type="text" style="width:100%" /></div>
</div>
<div class="row">
<div class="controls">
<input type="submit" name="UPDATE_SUBMIT" value="Submit"
i18n:attributes="value submit-button" />
</div>
</div>
</form>
</div>
</body>
</html>
=== Added File zopeproducts/bugtracker/browser/tracker_icon.png ===
<Binary-ish file>
=== Added File zopeproducts/bugtracker/browser/tracker_overview.pt ===
<html metal:use-macro="views/standard_macros/page">
<head>
<style metal:fill-slot="style_slot"
type="text/css" media="all"
tal:content=
"string: @import url(${context/++resource++tracker.css});">
@import url(tracker.css);>
</style>
</head>
<body>
<div metal:fill-slot="body" tal:define="bugs view/getBugs">
<div class="batch" tal:condition="bugs/startNumber">
<div class="prev_batch" tal:define="prev bugs/prevBatch">
<a href=""
tal:condition="prev"
tal:attributes="href
string:./@@overview.html?start=${prev/start}&size=${prev/size}">
Previous
(<d tal:replace="prev/startNumber" /> to
<d tal:replace="prev/endNumber" />)
</a>
</div>
<div class="curr_batch">
<d tal:replace="bugs/startNumber" /> to
<d tal:replace="bugs/endNumber" />
of <d tal:replace="bugs/total" /> found
(<d tal:replace="view/numberOfBugs" /> total)
</div>
<div class="next_batch" tal:define="next bugs/nextBatch">
<a href=""
tal:condition="next"
tal:attributes="href
string:./@@overview.html?start=${next/start}&size=${next/size}">
Next
(<d tal:replace="next/startNumber" /> to
<d tal:replace="next/endNumber" />)
</a>
</div>
<div class="clear"></div>
</div>
<tal:block repeat="bug bugs">
<div class=""
tal:define="oddrow repeat/bug/odd"
tal:attributes="class python:oddrow and 'even' or 'odd'">
<h5 class="summary_title" >
<a href=""
tal:attributes="href string:./${bug/name}/@@overview.html">
Bug #<d tal:replace="bug/name">1</d> -
<d tal:replace="bug/context/title">Bug Title</d>
</a>
</h5>
<div class="summary_content">
<div class="summary_condition">
Status:
<span tal:attributes="class bug/context/status"
tal:content="bug/status/title">New</span> -
Priority:
<span tal:attributes="class bug/context/priority"
tal:content="bug/priority/title">Normal</span> -
Type: <b tal:replace="bug/type/title">Bug</b>
</div>
<div class="summary_body" tal:content="bug/descriptionPreview">
Message Description Sneak Preview goes here...
</div>
<div class="summary_metadata">
Posted by <b tal:content="bug/context/submitter">Submitter</b>
on <b tal:replace="bug/created">2003/01/01</b>
- <b tal:replace="bug/numberOfComments">3</b> comments
</div>
</div>
</div>
</tal:block>
<div class="batch" tal:condition="bugs/startNumber">
<div class="prev_batch" tal:define="prev bugs/prevBatch">
<a href=""
tal:condition="prev"
tal:attributes="href
string:./@@overview.html?start=${prev/start}&size=${prev/size}">
Previous
(<d tal:replace="prev/startNumber" /> to
<d tal:replace="prev/endNumber" />)
</a>
</div>
<div class="curr_batch">
<d tal:replace="bugs/startNumber" /> to
<d tal:replace="bugs/endNumber" />
of <d tal:replace="bugs/total" /> found
(<d tal:replace="view/numberOfBugs" /> total)
</div>
<div class="next_batch" tal:define="next bugs/nextBatch">
<a href=""
tal:condition="next"
tal:attributes="href
string:./@@overview.html?start=${next/start}&size=${next/size}">
Next
(<d tal:replace="next/startNumber" /> to
<d tal:replace="next/endNumber" />)
</a>
</div>
<div class="clear"></div>
</div>
<form action="./@@+/AddBug=" method="post">
<input type="submit" name="add" value="Add Bug" />
</form>
<form action="updateOverviewSettings.html" method="post"
tal:define="settings view/getSettingsInfo">
<div class="row">
<div class="field">
Search Text:
<input type="text" name="search_text" value=""
tal:attributes="value view/getSearchText">
</div>
</div>
<div class="row">
<div class="field" tal:repeat="var settings">
<tal:block replace="var/title">Status</tal:block>:<br>
<select size="5" name="stati:list" multiple="yes"
tal:attributes="name string:${var/name}:list">
<tal:block repeat="entry var/all">
<option value=""
tal:condition="python: entry.value in var['setting']"
tal:attributes="value entry/value"
tal:content="entry/title" selected="">New</option>
<option value=""
tal:condition="python: entry.value not in var['setting']"
tal:attributes="value entry/value"
tal:content="entry/title">New</option>
</tal:block>
</select>
</div>
<div class="field">
<input type="submit" value="Change"
i18n:attributes="value change-button" />
</div>
</div>
</form>
</div>
</body>
</html>
=== Added File zopeproducts/bugtracker/browser/tracker_settings.pt ===
<html metal:use-macro="views/standard_macros/page">
<head>
<style metal:fill-slot="style_slot">
</style>
</head>
<body>
<div metal:fill-slot="body">
<div tal:repeat="vocab view/getManagableVocabularyViews">
<h2 tal:content="vocab/title">Some Variable Definitions</h2>
<br />
<form action="." method="post">
<input type="hidden" name="iface" value=""
tal:attributes="value vocab/vocab_iface/getName" />
<b>Existing Values:</b>
<select size="5" name="values:list" multiple="yes">
<tal:block repeat="entry vocab/getExistingValues">
<option value=""
tal:attributes="value entry/value"
tal:content="string: ${entry/title} (${entry/value})">New</option>
</tal:block>
</select>
<input type="submit" name="deleteValues.html:method" value="Delete" />
<br>
Value: <input type="text" size="10" name="value" />
Title: <input type="text" size="10" name="title" />
<input type="submit" name="addValue.html:method" value="Add" />
</form>
</div>
</div>
</body>
</html>