[Zope3-checkins] CVS: zopeproducts/bugtracker - I18N.txt:1.1 INSTALL.txt:1.1 LICENSE.txt:1.1 README.txt:1.1 TODO.txt:1.1 VERSION.txt:1.1 __init__.py:1.1 bug.py:1.1 comment.py:1.1 configure.zcml:1.1 interfaces.py:1.1 mail.py:1.1 tracker.py:1.1 vocabulary.py:1.1

Stephan Richter srichter@cosmos.phy.tufts.edu
Thu, 24 Jul 2003 14:08:15 -0400


Update of /cvs-repository/zopeproducts/bugtracker
In directory cvs.zope.org:/tmp/cvs-serv302

Added Files:
	I18N.txt INSTALL.txt LICENSE.txt README.txt TODO.txt 
	VERSION.txt __init__.py bug.py comment.py configure.zcml 
	interfaces.py mail.py tracker.py vocabulary.py 
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/I18N.txt ===
Internationalization (I18n) and Localalization (L10n)
=====================================================

  Crating/Updating Message Catalog Template (POT) Files
  -----------------------------------------------------

    0. Install 'bugtracker' in '<zope3>/src/zopeproducts'.

    1. Set the the Python path::
       
        export PYTHONPATH=<zope3>/src
  
    2. Go into the 'locales' directory and execute extract.py::

        python2.2 extract.py


  Updating Message Catalog (PO) Files
  -----------------------------------

    1. For each language do simply::

        msgmerge -U de/LC_MESSAGES/bugtracker.po bugtracker.pot

    2. Translate the updated PO file. 
       Note: KBabel is a great tool for this task!


  Compiling Message Catalogs (PO) to binary (MO) Files
  ----------------------------------------------------

    1. Go to the right directory, such as '<bugtracker>/locales/de/LC_MESSAGES'.

    2. Run the following command::

        msgfmt -o bugtracker.mo bugtracker.po


=== Added File zopeproducts/bugtracker/INSTALL.txt ===
Installation
============

  - create 'zopeproducts' inside your Zope 3 installation src directory

  - add an empty '__init__.py' to zopeproducts

  - copy the 'bugtracker' folder to 'zopeproducts'

  - add the following line to the 'products.zcml' file::

     <include package='zopeproducts.bugtracker' />

  - XXX: (Not yet) You need to define the following role declarations to your
    user in order to use the zwiki product effectively.

     <grant role="BugTrackerAdmin" principal="user" />
     <grant role="BugTrackerEditor" principal="user" />
     <grant role="BugTrackerUser" principal="user" />

     <grant role="BugTrackerUser" principal="anybody" />


Usage
=====

  1. To see a Bug Tracker in action, go into the management interface and add
     a "Bug Tracker" object named 'tracker'. Leave the preselected option and
     enter a title.

  2. To get to the end user interface, enter::

      http://localhost:8080/++skin++tracker/tracker


=== Added File zopeproducts/bugtracker/LICENSE.txt ===
Zope Public License (ZPL) Version 2.0
-----------------------------------------------

This software is Copyright (c) Zope Corporation (tm) and
Contributors. All rights reserved.

This license has been certified as open source. It has also
been designated as GPL compatible by the Free Software
Foundation (FSF).

Redistribution and use in source and binary forms, with or
without modification, are permitted provided that the
following conditions are met:

1. Redistributions in source code must retain the above
   copyright notice, this list of conditions, and the following
   disclaimer.

2. Redistributions in binary form must reproduce the above
   copyright notice, this list of conditions, and the following
   disclaimer in the documentation and/or other materials
   provided with the distribution.

3. The name Zope Corporation (tm) must not be used to
   endorse or promote products derived from this software
   without prior written permission from Zope Corporation.

4. The right to distribute this software or to use it for
   any purpose does not give you the right to use Servicemarks
   (sm) or Trademarks (tm) of Zope Corporation. Use of them is
   covered in a separate agreement (see
   http://www.zope.com/Marks).

5. If any files are modified, you must cause the modified
   files to carry prominent notices stating that you changed
   the files and the date of any change.

Disclaimer

  THIS SOFTWARE IS PROVIDED BY ZOPE CORPORATION ``AS IS''
  AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
  NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
  AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN
  NO EVENT SHALL ZOPE CORPORATION OR ITS CONTRIBUTORS BE
  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
  OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
  DAMAGE.


This software consists of contributions made by Zope
Corporation and many individuals on behalf of Zope
Corporation.  Specific attributions are listed in the
accompanying credits file.


=== Added File zopeproducts/bugtracker/README.txt ===
Bug Tracker Product for Zope 3
==============================

  This product is an implementation of a bug tracker in Zope 3. 

  Features
  --------

    Bug Tracker

      - View list of bugs

        o Filtering by status, type, release, priority and text

        o Batching, when list of bugs becomes too long.

        o Bug status and priority values are marked up based on value.

      - Settings

        o When creating a Bug Tracker, one can select the option to
          automatically create a set of status, type, release and priority
          choices.

        o The choices for the status, type, release and priority are flexible
          and can be changed.

      - Mail Subscriptions

        o These are Bug Tracker wide mail subscriptions that send the
          recipients an E-mail about additions, changes and deletions of bugs.

    Bug

      - Overview

        o This screen provides a comprehensive overview of all the available
          information about the bug.

        o The status and priority are marked up based on their value.

        o The description and the comments are rendered using STX.

        o Upload Files and Images

        o Add new comments

      - Edit

        o To provide a familiar interface, the edit form is layed out in the
          same way as the overview

      - Dependencies

        o One in my opinion major improvement over the current collector is
          the availability of a dependency feature, where I can say that this
          bug depends on that one.

        o Based on this information a dependency tree is generated using the 
          markup rules for status and priority, so that a user can quickly 
          recognize critical spots in the tree.

        o There is also a Statistics section that tells you how many bugs are
          completed, have not been viewed and are being fixed. 

      - Mail Subscriptions

        o These are specific bug mail subscriptions that send the recipients
          an E-mail about additions, changes and deletions of the bug.


=== Added File zopeproducts/bugtracker/TODO.txt ===
To Do
=====

  Tests

    - Write tests for View code

    - Write tests for the Batcher


  I18n and L10n

    - Internationalize all screens and code.

    - Create message catalogs.

    - Provide a sample translation (probably German). 


  Security

    - Declare permissions and roles

    - Assign correct permissions to objects and views


  UI

    - Display Term title instead of value in the drop-down elements.


  Other Features

    - Improve Mailings (use some sort of diff library)

    - XML Import and Export

=== Added File zopeproducts/bugtracker/VERSION.txt ===
0.1

=== Added File zopeproducts/bugtracker/__init__.py ===


=== Added File zopeproducts/bugtracker/bug.py ===
##############################################################################
#
# Copyright (c) 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.
# 
##############################################################################
"""A Container based Bug

$Id: bug.py,v 1.1 2003/07/24 18:08:03 srichter Exp $
"""
from zope.interface import implements
from zope.component import getAdapter, queryAdapter
from zope.app.interfaces.dublincore import IZopeDublinCore
from zope.app.interfaces.annotation import IAnnotations
from zope.app.interfaces.index.text import ISearchableText
from zope.app.traversing import getParent, getName
from zope.app.container.btree import BTreeContainer
from zope.app.context import ContextWrapper
from zope.proxy import removeAllProxies
from zopeproducts.bugtracker.interfaces import IBug, IComment
from zopeproducts.bugtracker.interfaces import IBugDependencies

DependencyKey = 'http://www.zope.org/bugtracker#1.0/Dependencies'


class Bug(BTreeContainer):

    implements(IBug)

    # See zopeproducts.bugtracker.interfaces.IBug
    status = u'new'

    # See zopeproducts.bugtracker.interfaces.IBug
    priority = u'normal'

    # See zopeproducts.bugtracker.interfaces.IBug
    type = u'bug'

    # See zopeproducts.bugtracker.interfaces.IBug
    release = u'None'

    def getOwners(self):
        return getattr(self, '_owners', [])
    
    def setOwners(self, owners):
        self._owners = removeAllProxies(owners)

    # See zopeproducts.bugtracker.interfaces.IBug
    owners = property(getOwners, setOwners)

    def setTitle(self, title):
        """Set bug title."""
        dc = queryAdapter(self, IZopeDublinCore)
        dc.title = title

    def getTitle(self):
        """Get bug title."""
        dc = queryAdapter(self, IZopeDublinCore)
        return dc.title

    # See zopeproducts.bugtracker.interfaces.IBug
    title = property(getTitle, setTitle)

    def setDescription(self, description):
        """Set bug description."""
        dc = queryAdapter(self, IZopeDublinCore)
        dc.description = description

    def getDescription(self):
        """Get bug description."""
        dc = queryAdapter(self, IZopeDublinCore)
        return dc.description

    # See zopeproducts.bugtracker.interfaces.IBug
    description = property(getDescription, setDescription)    

    def getSubmitter(self):
        """Get bug submitter."""
        dc = queryAdapter(self, IZopeDublinCore)
        if not dc.creators:
            return None
        return dc.creators[0]

    # See zopeproducts.bugtracker.interfaces.IBug
    submitter = property(getSubmitter)    


class BugDependencyAdapter(object):

    implements(IBugDependencies)
    __used_for__ = IBug

    def __init__(self, context):
        self.context = context
        self._annotations = getAdapter(context, IAnnotations)
        if not self._annotations.get(DependencyKey):
            self._annotations[DependencyKey] = ()

    def setDependencies(self, dependencies):
        self._annotations[DependencyKey] = tuple(dependencies)

    def getDependencies(self):
        return self._annotations[DependencyKey]

    dependencies = property(getDependencies, setDependencies)

    def findChildren(self, recursive=True, all=None):
        "See zopeproducts.bugtracker.interfaces.IBugDependencies"
        if all is None:
            all = []
        tracker = getParent(self.context)
        contextName = getName(self.context)
        deps = getAdapter(self.context, IBugDependencies)
        children = []
        for bugName in deps.dependencies:
            # Circle detection; if the bugName was processed before, skip it
            if bugName in all:
                continue
            else:
                all.append(bugName)

            wrapped = ContextWrapper(tracker[bugName], tracker, name=bugName)
            if recursive is True:
                deps = getAdapter(wrapped, IBugDependencies)
                subs = deps.findChildren(all=all)
            else:
                subs = ()

            children.append((wrapped, subs))

        return tuple(children)


class SearchableText:
    """This adapter allows us to get all searchable text at once.""" 

    implements(ISearchableText)
    __used_for__ = IBug

    def __init__(self, context):
        self.context = context

    def getSearchableText(self):
        return [self.context.title, self.context.description]


=== Added File zopeproducts/bugtracker/comment.py ===
##############################################################################
#
# Copyright (c) 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.
# 
##############################################################################
"""A simple Comment

$Id: comment.py,v 1.1 2003/07/24 18:08:03 srichter Exp $
"""
from zope.interface import implements
from persistence import Persistent

from zopeproducts.bugtracker.interfaces import IComment


class Comment(Persistent):

    implements(IComment)

    # See zopeproducts.bugtracker.interfaces.IComment
    body = u""


=== Added File zopeproducts/bugtracker/configure.zcml ===
<zopeConfigure
   xmlns="http://namespaces.zope.org/zope"
   xmlns:event="http://namespaces.zope.org/event"
   xmlns:mail="http://namespaces.zope.org/mail"
   xmlns:translate="http://namespaces.zope.org/gts">

   <!-- Setting up the vocabularies for the bug tracker -->  

   <vocabulary
      name="Stati"
      factory=".vocabulary.StatusVocabulary" />

   <vocabulary
      name="Releases"
      factory=".vocabulary.ReleaseVocabulary" />

   <vocabulary
      name="Priorities"
      factory=".vocabulary.PriorityVocabulary" />

   <vocabulary
      name="BugTypes"
      factory=".vocabulary.BugTypeVocabulary" />

   <vocabulary
      name="Users"
      factory=".vocabulary.UserVocabulary" />


  <content class=".vocabulary.ManagableVocabulary">
    <allow interface=".interfaces.IManagableVocabulary"/>
    <allow attributes="__contains__"/>
  </content>

  <content class=".vocabulary.StatusVocabulary">
    <require like_class=".vocabulary.ManagableVocabulary"/>    
  </content>

  <content class=".vocabulary.PriorityVocabulary">
    <require like_class=".vocabulary.ManagableVocabulary"/>    
  </content>

  <content class=".vocabulary.ReleaseVocabulary">
    <require like_class=".vocabulary.ManagableVocabulary"/>    
  </content>

  <content class=".vocabulary.BugTypeVocabulary">
    <require like_class=".vocabulary.ManagableVocabulary"/>    
  </content>

  <content class=".vocabulary.SimpleTerm">
    <allow interface="zope.schema.interfaces.ITokenizedTerm"/>
    <allow attributes="title"/>
  </content>

  <content class=".vocabulary.UserVocabulary">
    <allow interface="zope.schema.interfaces.IVocabulary"/>
    <allow interface="zope.schema.interfaces.IVocabularyTokenized"/>
    <allow attributes="__contains__"/>
  </content>

  <content class=".vocabulary.UserTerm">
    <allow
        interface="zope.schema.interfaces.ITokenizedTerm"/>
    <allow attributes="principal"/>
  </content>

  <!-- Bug Tracker related configuration -->

  <content class=".tracker.BugTracker">

    <implements
       interface="zope.app.interfaces.annotation.IAttributeAnnotatable" />

    <factory
        id="BugTracker"
        permission="zope.View"
	title="Bug Tracker"
        description="A Bug Tracker" />

    <allow interface="zope.app.interfaces.services.service.Read" />

    <require
        permission="zope.ManageServices"
        interface="zope.app.interfaces.services.service.Write" />

    <require
        permission="zope.View"
        interface="zope.app.interfaces.container.IReadContainer"/>

    <require
        permission="zope.View"
        interface="zope.app.interfaces.container.IWriteContainer"/>

    <require
        permission="zope.View"
        set_schema=".interfaces.IBugTracker" />

  </content>

  <adapter
      factory=".vocabulary.StatusVocabulary"
      provides=".interfaces.IStatusVocabulary"
      for=".interfaces.IBugTracker" />

  <adapter
      factory=".vocabulary.ReleaseVocabulary"
      provides=".interfaces.IReleaseVocabulary"
      for=".interfaces.IBugTracker" />

  <adapter
      factory=".vocabulary.PriorityVocabulary"
      provides=".interfaces.IPriorityVocabulary"
      for=".interfaces.IBugTracker" />

  <adapter
      factory=".vocabulary.BugTypeVocabulary"
      provides=".interfaces.IBugTypeVocabulary"
      for=".interfaces.IBugTracker" />

  <adapter
      factory=".mail.MailSubscriptions"
      provides=".interfaces.IMailSubscriptions"
      for=".interfaces.IBugTracker" />

  <!-- Bug related configuration -->

  <content class=".bug.Bug">

    <implements
       interface="zope.app.interfaces.annotation.IAttributeAnnotatable"/>

    <implements
       interface="zope.app.interfaces.container.IContentContainer" />

    <factory
        id="Bug"
        permission="zope.View"
        title="Bug"
        description="A Bug" />

    <require
        permission="zope.View"
        interface="zope.app.interfaces.container.IReadContainer"/>

    <require
        permission="zope.View"
        interface="zope.app.interfaces.container.IWriteContainer"/>

    <require
        permission="zope.View"
        interface=".interfaces.IBug"
        set_schema=".interfaces.IBug" />

  </content>

  <adapter 
      factory=".bug.SearchableText"
      provides="zope.app.interfaces.index.text.ISearchableText"
      for=".interfaces.IBug" />

  <adapter
      factory=".bug.BugDependencyAdapter"
      provides=".interfaces.IBugDependencies"
      for=".interfaces.IBug" />

  <adapter
      factory=".mail.MailSubscriptions"
      provides=".interfaces.IMailSubscriptions"
      for=".interfaces.IBug" />


  <!-- Comment related configuration -->

  <content class=".comment.Comment">

    <implements
       interface="zope.app.interfaces.annotation.IAttributeAnnotatable"/>

    <factory
        id="BugComment"
        permission="zope.View"
        title="Comment"
        description="A comment about the bug." />

    <require
        permission="zope.View"
        interface=".interfaces.IComment"
        set_schema=".interfaces.IComment" />

  </content>


  <!-- Register Mailer and Mail Service -->

  <mail:smtpMailer id="bugs-smtp" hostname="localhost" port="25" />

  <mail:queuedService permission="zope.SendMail"
                      queuePath="./src/zopeproducts/bugtracker/mail-queue"
                      mailer="bugs-smtp" />

  <!-- Register event listener for change mails -->
  <event:subscribe
      subscriber=".mail.mailer"
      event_types="zope.app.interfaces.event.IObjectAddedEvent
                   zope.app.interfaces.event.IObjectModifiedEvent
                   zope.app.interfaces.event.IObjectRemovedEvent
                   zope.app.interfaces.event.IObjectMovedEvent" />


  <!-- Register various browser related components, including all views -->
  <include package=".browser" />

</zopeConfigure>


=== Added File zopeproducts/bugtracker/interfaces.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.
#
##############################################################################
"""Bug Tracker Interfaces

Bag Tracker related interfaces.

$Id: interfaces.py,v 1.1 2003/07/24 18:08:03 srichter Exp $
"""
from zope.interface import Interface
from zope.schema import Text, TextLine, List, Dict
from zope.schema.vocabulary import VocabularyField, VocabularyListField

from zope.schema.interfaces import IVocabulary, IVocabularyTokenized
from zope.app.interfaces.container import IContainer


from zope.i18n import MessageIDFactory
_ = MessageIDFactory('bugtracker')

class IBugTracker(IContainer):
    """A Bug Tracker object represents a collection of bugs for a particular
    software or subject.

    Note that there is no specific interface for managing bugs, since the
    generic IContainer interface is sufficient.

    The status messages are a collection of possible stati a bug can be
    in. The reason we define the list here is that we can change and adjust it
    later for the particular instance.
    """

    title = TextLine(
        title = _(u"Title"),
        description = _(u"Title of the bug tracker."),
        required=True)


class IBug(Interface):
    """A bug is the content object containing all necessary information that
    are relevant to a bug report.

    Note: I also included title in the interface, since I think it is part of
    the data and not the meta data of the bug content object.
    """

    title = TextLine(
        title = _(u"Title"),
        description = _(u"Title/Summary of the bug."),
        required=True)

    description = Text(
        title = _(u"Description"),
        description = _(u"Detailed Description of the bug."),
        required=True)

    submitter = TextLine(
        title = _(u"Submitter"),
        description = _(u"Name of the person that submitted the bug."),
        required=False)

    status = VocabularyField(
        title = _(u"Status"),
        description = _(u"The current status of the bug."),
        default= 'new',
        required = True,
        vocabulary = "Stati")

    priority = VocabularyField(
        title = _(u"Priority"),
        description = _(u"Specifies how urgent this bug is."),
        default= 'normal',
        required = True,
        vocabulary = "Priorities")

    type = VocabularyField(
        title = _(u"Type"),
        description = _(u"Specifies of what nature the bug is."),
        default= 'bug',
        required = True,
        vocabulary = "BugTypes")

    release = VocabularyField(
        title = _(u"Release"),
        description = _(u"Defines the release for which the bug is scheduled."),
        default = 'None',
        required = True,
        vocabulary = "Releases")

    owners = VocabularyListField(
        title = _(u"Owners"),
        description = _(u"List of people assigned as owners of the bug."),
        required=False,
        vocabulary = "Users")


class IBugDependencies(Interface):
    """This object handles the dependencies of a bug."""
    
    dependencies = List(
        title = _(u"Dependencies"),
        description = _(u"Other bugs this bug depends on."),
        value_type = TextLine(title=_(u"Bug Id"),
                              description=_(u"Bug Id.")),
        required=False)


    def findChildren(recursive=True):
        """Returns a list of children for this bug.

        If the recursive is True, the method recurses into all children
        returning the entire sub-graph of this Bug. Is the recursive
        argument set to False, only the first level of children will be
        returned.

        While circular references are okay (since dependencies are general
        graphs, not trees), this method must be aware of this fact and cannot
        just implement a plain old recursive algorithm. When a circle is
        detected, then simply cut off the search at this point.
        """


class IComment(Interface):
    """Simple comment for Bug.

    For now we assume the body to be structured text.
    """

    body = Text(
        title=_(u"Body"),
        description=_(u"Renderable body of the Comment."),
        default=u"",
        required=True)


class IManagableVocabulary(IVocabulary, IVocabularyTokenized):
    """Vocabulary that can be modified by addign and deleting terms.

    Note that this is a simple interface, where vocabularies are simple
    value-title mappings. The values should be preferibly in ASCII, so that
    there is no problem with encoding them in HTML.
    """

    def add(value, title):
        """Add a new vocabulary entry."""

    def delete(value):
        """Delete an entry from the vocabulary."""

    
class IStatusVocabulary(IManagableVocabulary):
    """Manageable vocabulary that stores stati."""


class IReleaseVocabulary(IManagableVocabulary):
    """Manageable vocabulary that stores all releases."""


class IPriorityVocabulary(IManagableVocabulary):
    """Manageable vocabulary that stores all priority values."""


class IBugTypeVocabulary(IManagableVocabulary):
    """Manageable vocabulary that stores all type values."""


class IMailSubscriptions(Interface):
    """This interface allows you to retrieve a list of E-mails for
    mailings."""

    def getSubscriptions():
        """Return a list of E-mails."""

    def addSubscriptions(emails):
        """Add a bunch of subscriptions; one would be okay too."""

    def removeSubscriptions(emails):
        """Remove a set of subscriptions."""


=== Added File zopeproducts/bugtracker/mail.py ===
##############################################################################
#
# Copyright (c) 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.
# 
##############################################################################
"""Mail Support

$Id: mail.py,v 1.1 2003/07/24 18:08:03 srichter Exp $
"""
from zope.interface import implements
from zope.component import getAdapter, getService
from zope.app.interfaces.annotation import IAnnotations
from zope.app.interfaces.event import ISubscriber
from zope.app.interfaces.event import IObjectAddedEvent, IObjectModifiedEvent
from zope.app.interfaces.event import IObjectRemovedEvent, IObjectMovedEvent
from zope.app.traversing import getParent, getName

from interfaces import IBug, IBugTracker, IMailSubscriptions

SubscriberKey = 'http://www.zope.org/bugtracker#1.0/MailSubs/emails'

class MailSubscriptions:
    """An adapter for IBugTracker and IBug to provide an
    interface for collecting E-mails for sending out change notices."""

    implements(IMailSubscriptions)

    def __init__(self, context):
        self.context = context
        self._annotations = getAdapter(context, IAnnotations)
        if not self._annotations.get(SubscriberKey):
            self._annotations[SubscriberKey] = ()

    def getSubscriptions(self):
        "See zopeproducts.messageboard.interfaces.IMailSubscriptions"
        return self._annotations[SubscriberKey]
        
    def addSubscriptions(self, emails):
        "See zopeproducts.messageboard.interfaces.IMailSubscriptions"
        subscribers = list(self._annotations[SubscriberKey])
        for email in emails:
            if email not in subscribers:
                subscribers.append(email.strip())
        self._annotations[SubscriberKey] = tuple(subscribers)
                
    def removeSubscriptions(self, emails):
        "See zopeproducts.messageboard.interfaces.IMailSubscriptions"
        subscribers = list(self._annotations[SubscriberKey])
        for email in emails:
            if email in subscribers:
                subscribers.remove(email)
        self._annotations[SubscriberKey] = tuple(subscribers)


class Mailer:
    """Class to handle all outgoing mail."""

    implements(ISubscriber)

    def notify(self, event):
        """See zope.app.interfaces.event.ISubscriber"""
        if IBug.isImplementedBy(event.object):
            if IObjectAddedEvent.isImplementedBy(event):
                self.handleAdded(event.object)
            elif IObjectModifiedEvent.isImplementedBy(event):
                self.handleModified(event.object)
            elif IObjectRemovedEvent.isImplementedBy(event):
                self.handleRemoved(event.object)

    def handleAdded(self, object):
        subject = 'Added: %s (%s)' %(object.title, getName(object))
        emails = self.getAllSubscribers(object)
        body = object.description
        self.mail(emails, subject, body)        

    def handleModified(self, object):
        subject = 'Modified: %s (%s)' %(object.title, getName(object))
        emails = self.getAllSubscribers(object)
        body = object.description
        self.mail(emails, subject, body)

    def handleRemoved(self, object):
        subject = 'Removed: %s (%s)' %(object.title, getName(object))
        emails = self.getAllSubscribers(object)
        body = self.description
        self.mail(emails, subject, body)

    def getAllSubscribers(self, object):
        """Retrieves all email subscribers for this message and all above."""
        emails = ()
        obj = object
        while IBug.isImplementedBy(obj) or IBugTracker.isImplementedBy(obj):
            emails += tuple(getAdapter(
                obj, IMailSubscriptions).getSubscriptions())
            obj = getParent(obj)
        return emails

    def mail(self, toaddrs, subject, body):
        """Mail out the change message."""
        if not toaddrs:
            return
        msg = 'Subject: %s\n\n\n%s' %(subject, body)
        mail_service = getService(None, 'Mail')
        mail_service.send('bugtracker@zope3.org' , toaddrs, msg)


# Create a global mailer object.
mailer = Mailer()


=== Added File zopeproducts/bugtracker/tracker.py ===
##############################################################################
#
# Copyright (c) 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.
# 
##############################################################################
"""A Bug Tracker implementation

$Id: tracker.py,v 1.1 2003/07/24 18:08:03 srichter Exp $
"""
from zope.interface import implements
from zope.app.content.folder import Folder
from zopeproducts.bugtracker.interfaces import IBugTracker


class BugTracker(Folder):

    implements(IBugTracker)

    # See zopeproducts.bugtracker.interfaces.IBugTracker
    title = u''


=== Added File zopeproducts/bugtracker/vocabulary.py ===
##############################################################################
#
# Copyright (c) 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.
# 
##############################################################################
"""Vocabularies for the Bug Tracker

$Id: vocabulary.py,v 1.1 2003/07/24 18:08:03 srichter Exp $
"""
from zope.interface import implements
from zope.component import getAdapter, getService
from zope.app.traversing import getParents
from persistence import Persistent
from persistence.dict import PersistentDict

from zope.schema.interfaces import \
     ITokenizedTerm, IVocabulary, IVocabularyTokenized
from zope.app.interfaces.annotation import IAnnotatable, IAnnotations
from zope.app.interfaces.security import IAuthenticationService

from zope.app.services.servicenames import Authentication
from zopeproducts.bugtracker.interfaces import IManagableVocabulary, IBugTracker
from zopeproducts.bugtracker.interfaces import \
     IStatusVocabulary, IReleaseVocabulary, IPriorityVocabulary 
from zopeproducts.bugtracker.interfaces import \
     IBugTypeVocabulary 

class SimpleTerm(Persistent):

    implements(ITokenizedTerm)

    def __init__(self, value, title):
        self.value = value
        self.title = title

    def getToken(self):
        return self.value

    token = property(getToken)
    

class ManagableVocabulary(object):

    implements(IManagableVocabulary)
    __used_for__ = IAnnotatable

    key = None

    interface = None

    def __init__(self, context):
        self.context = self._getRealContext(context)
        self.annotations = getAdapter(self.context, IAnnotations)
        if not self.annotations.get(self.key):
            self.annotations[self.key] = PersistentDict()
    
    def __contains__(self, value):
        return value in self.annotations[self.key].keys()
    
    def __iter__(self):
        return iter(self.annotations[self.key].values())
    
    def __len__(self):
        return len(self.annotations[self.key])
    
    def getQuery(self):
        return None
    
    def getTerm(self, value):
        return self.annotations[self.key][value]

    def getTermByToken(self, token):
        return self.getTerm(token)
    
    def add(self, value, title):
        self.annotations[self.key][value] = SimpleTerm(value, title)

    def delete(self, value):
        del self.annotations[self.key][value]

    def _getRealContext(self, context):
        for obj in getParents(context):
            if self.interface.isImplementedBy(obj):
                return obj
        return context


class StatusVocabulary(ManagableVocabulary):

    implements(IStatusVocabulary)

    key = 'http://www.zope.org/bugtracker#1.0/status/values'
    interface = IBugTracker

    title = 'Status Definitions'


class ReleaseVocabulary(ManagableVocabulary):

    implements(IReleaseVocabulary)

    key = 'http://www.zope.org/bugtracker#1.0/release/values'
    interface = IBugTracker

    title = 'Release Definitions'


class PriorityVocabulary(ManagableVocabulary):

    implements(IPriorityVocabulary)

    key = 'http://www.zope.org/bugtracker#1.0/priority/values'
    interface = IBugTracker

    title = 'Priority Definitions'


class BugTypeVocabulary(ManagableVocabulary):

    implements(IBugTypeVocabulary)

    key = 'http://www.zope.org/bugtracker#1.0/bugtype/values'
    interface = IBugTracker

    title = 'Bug Type Definitions'


class UserTerm(Persistent):

    implements(ITokenizedTerm)

    def __init__(self, principal):
        self.principal = principal
        self.value = principal.getId()
        self.token = principal.getId()


class UserVocabulary(object):

    implements(IVocabulary, IVocabularyTokenized)

    def __init__(self, context):
        self.auth = getService(context, 'Authentication')
    
    def __contains__(self, value):
        ids = map(lambda p: p.getId(), self.auth.getPrincipals(''))
        return value in ids
    
    def __iter__(self):
        terms = map(lambda p: UserTerm(p), self.auth.getPrincipals(''))
        return iter(terms)
    
    def __len__(self):
        return len(self.auth.getPrincipals(''))
    
    def getQuery(self):
        return None
    
    def getTerm(self, value):
        return UserTerm(self.auth.getPrincipal(value))

    def getTermByToken(self, token):
        return self.getTerm(token)