[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>&nbsp;
        </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>&nbsp;
        </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>&nbsp;
        </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>&nbsp;
        </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>