[CMF-checkins] CVS: CMF/CMFCollector - Collector.py:1.1 CollectorIssue.py:1.1 CollectorPermissions.py:1.1 INSTALL.txt:1.1 README.txt:1.1 TODO.txt:1.1 VERSION.txt:1.1 __init__.py:1.1 util.py:1.1
Ken Manheimer
klm@zope.com
Wed, 10 Oct 2001 15:14:59 -0400
Update of /cvs-repository/CMF/CMFCollector
In directory cvs.zope.org:/tmp/cvs-serv7316
Added Files:
Collector.py CollectorIssue.py CollectorPermissions.py
INSTALL.txt README.txt TODO.txt VERSION.txt __init__.py
util.py
Log Message:
Initial baseline.
- Content types: Collector, CollectorIssue, (virtual)
CollectorIssueTranscript, with python and skins. (Almost all
skins methods are ZPT or python scripts).
- External method install script (Extensions/InstallCollector.py).
- Install instructions (INSTALL.txt)
- Pending issues (TODO.txt)
=== Added File CMF/CMFCollector/Collector.py ===
##############################################################################
# Copyright (c) 2001 Zope Corporation. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 1.1 (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.
##############################################################################
"""Implement the Collector issue-container content type."""
import os, urllib
from DateTime import DateTime
from Globals import InitializeClass, DTMLFile, package_home
from AccessControl import ClassSecurityInfo, ModuleSecurityInfo
from AccessControl import getSecurityManager
from Products.CMFDefault.DublinCore import DefaultDublinCoreImpl
from Products.CMFCore.PortalContent import PortalContent
from Products.CMFCore.WorkflowCore import WorkflowAction
from Products.CMFDefault.SkinnedFolder import SkinnedFolder
# Import permission names
from Products.CMFCore import CMFCorePermissions
from CollectorPermissions import *
from CollectorIssue import addCollectorIssue
# Factory type information -- makes Events objects play nicely
# with the Types Tool (portal_types)
factory_type_information = (
{'id': 'Collector',
# 'content_icon': 'event_icon.gif',
'meta_type': 'CMF Collector',
'description': ('A Collector is a facility for tracking bug reports and'
' other issues.'),
'product': 'CMFCollector',
'factory': 'addCollector',
'allowed_content_types': ('CollectorIssue',),
'immediate_view': 'collector_edit_form',
'actions': ({'id': 'view',
'name': 'Browse',
'action': 'collector_contents',
'permissions': (ViewCollector,)},
{'id': 'addissue',
'name': 'New Issue',
'action': 'collector_add_issue_form',
'permissions': (AddCollectorIssue,)},
{'id': 'edit',
'name': 'Configure',
'action': 'collector_edit_form',
'permissions': (ManageCollector,)},
),
},
)
_dtmldir = os.path.join(package_home(globals()), 'dtml')
addCollectorForm = DTMLFile('addCollectorForm', _dtmldir, Kind='CMF Collector')
class Collector(SkinnedFolder):
"""Collection of IssueBundles."""
meta_type = 'CMF Collector'
effective_date = expiration_date = None
DEFAULT_IMPORTANCES = ['medium', 'high', 'low']
DEFAULT_SEVERITIES = ['normal', 'critical', 'major', 'minor']
DEFAULT_CLASSIFICATIONS = ['bug', 'bug+solution', 'feature', 'doc',
'test']
DEFAULT_VERSIONS = ['current', 'development', 'old', 'unique']
DEFAULT_OTHER_VERSIONS_SPIEL = (
"Pertinent other-system details, eg browser, webserver,"
" database, python, OS, etc.")
security = ClassSecurityInfo()
def __init__(self, id, title='', description='',
topics=None, classifications=None,
importances=None, severities=None,
supporters=None,
versions=None, other_versions_spiel=None):
SkinnedFolder.__init__(self, id, title)
self.last_issue_id = 0
self.description = description
if supporters is None:
username = str(getSecurityManager().getUser())
if username: supporters = [username]
else: supporters = []
else:
self._adjust_supporters_roster(supporters)
self.supporters = supporters
if topics is None:
self.topics = ['Zope', 'Collector', 'Database',
'Catalog', 'ZServer']
else: self.topics = topics
if classifications is None:
self.classifications = self.DEFAULT_CLASSIFICATIONS
else: self.classifications = classifications
if importances is None:
self.importances = self.DEFAULT_IMPORTANCES
else: self.importances = importances
if severities is None:
self.severities = self.DEFAULT_SEVERITIES
else: self.severities = severities
if versions is None:
self.versions = self.DEFAULT_VERSIONS
else: self.versions = versions
if other_versions_spiel is None:
self.other_versions_spiel = self.DEFAULT_OTHER_VERSIONS_SPIEL
else: self.other_versions_spiel = other_versions_spiel
return self
security.declareProtected(AddCollectorIssue, 'new_issue_id')
def new_issue_id(self):
"""Return a new issue id, incrementing the internal counter."""
lastid = self.last_issue_id = self.last_issue_id + 1
return str(lastid)
security.declareProtected(AddCollectorIssue, 'add_issue')
def add_issue(self,
title=None,
description=None,
submitter=None,
email=None,
security_related=None,
kibitzers=None,
topic=None,
importance=None,
classification=None,
severity=None,
assigned_to=None,
reported_version=None,
other_version_info=None):
"""Instigate a new collector issue."""
id = self.new_issue_id()
submitter_id = str(getSecurityManager().getUser())
addCollectorIssue(self,
id,
title=title,
description=description,
submitter_id=submitter_id,
submitter_name=submitter,
submitter_email=email,
kibitzers=kibitzers,
topic=topic,
classification=classification,
security_related=security_related,
importance=importance,
severity=severity,
assigned_to=assigned_to,
reported_version=reported_version,
other_version_info=other_version_info)
return id
security.declareProtected(ManageCollector, 'edit')
def edit(self, title=None, description=None, supporters=None,
topics=None, classifications=None,
importances=None, severities=None,
versions=None, other_versions_spiel=None):
changed = 0
if title is not None:
if title != self.title:
self.title = title
changed = 1
if description is not None:
if self.description != description:
self.description = description
changed = 1
if supporters is not None:
# XXX Vette supporters - they must exist, etc.
x = filter(None, supporters)
if self.supporters != x:
self._adjust_supporters_roster(x)
self.supporters = x
changed = 1
if topics is not None:
x = filter(None, topics)
if self.topics != x:
self.topics = x
changed = 1
if classifications is not None:
x = filter(None, classifications)
if self.classifications != x:
self.classifications = x
changed = 1
if importances is not None:
x = filter(None, importances)
if self.importances != x:
self.importances = x
changed = 1
if versions is not None:
x = filter(None, versions)
if self.versions != x:
self.versions = x
changed = 1
if versions is not None:
x = filter(None, versions)
if self.versions != x:
self.versions = x
changed = 1
if other_versions_spiel is not None:
if self.other_versions_spiel != other_versions_spiel:
self.other_versions_spiel = other_versions_spiel
changed = 1
return changed
def _adjust_supporters_roster(self, new_roster):
"""Adjust supporters local-role assignments to track roster changes.
Ie, ensure all and only designated supporters have 'Reviewer' local
role."""
already = []
# Remove 'Reviewer' local role from anyone having it not on new_roster:
for u in self.users_with_local_role('Reviewer'):
if u in new_roster:
already.append(u)
else:
# Remove the 'Reviewer' local role:
roles = list(self.get_local_roles_for_userid(u))
roles.remove('Reviewer')
if roles:
self.manage_setLocalRoles(u, roles)
else:
self.manage_delLocalRoles([u])
# Add 'Reviewer' local role to anyone on new_roster that lacks it:
for u in new_roster:
if u not in already:
roles = list(self.get_local_roles_for_userid(u))
roles.append('Reviewer')
self.manage_setLocalRoles(u, roles)
security.declareProtected(CMFCorePermissions.View, 'length')
def length(self):
"""Use length protocol."""
return self.__len__()
def __len__(self):
"""Implement length protocol method."""
return len(self.objectIds())
def __repr__(self):
return ("<%s %s (%d issues) at 0x%s>"
% (self.__class__.__name__, `self.id`, len(self),
hex(id(self))[2:]))
InitializeClass(Collector)
# XXX Enable use of pdb.set_trace() in python scripts
ModuleSecurityInfo('pdb').declarePublic('set_trace')
def addCollector(self, id, title=None, description=None,
topics=None, classifications=None,
importances=None, severities=None,
supporters=None,
versions=None, other_versions_spiel=None,
REQUEST=None):
"""
Create a collector.
"""
it = Collector(id, title=title, description=description,
topics=topics, classifications=classifications,
supporters=supporters,
versions=versions,
other_versions_spiel=other_versions_spiel)
self._setObject(id, it)
it = self._getOb(id)
it._setPortalTypeName('Collector')
it.manage_permission(ManageCollector, roles=['Owner'], acquire=1)
it.manage_permission(EditCollectorIssue,
roles=['Reviewer'],
acquire=1)
it.manage_permission(AddCollectorIssueComment,
roles=['Reviewer', 'Owner'],
acquire=1)
it.manage_permission(AddCollectorIssueArtifact,
roles=['Reviewer', 'Owner'],
acquire=1)
if REQUEST is not None:
try: url=self.DestinationURL()
except: url=REQUEST['URL1']
REQUEST.RESPONSE.redirect('%s/manage_main' % url)
return id
=== Added File CMF/CMFCollector/CollectorIssue.py ===
##############################################################################
# Copyright (c) 2001 Zope Corporation. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 1.1 (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.
##############################################################################
"""Implement the Collector Issue content type - a bundle containing the
collector transcript and various parts."""
import os, urllib, string, re
from DateTime import DateTime
from Globals import InitializeClass
from AccessControl import ClassSecurityInfo, getSecurityManager
from Acquisition import aq_base
import util # Collector utilities.
from Products.CMFDefault.DublinCore import DefaultDublinCoreImpl
from Products.CMFCore.PortalContent import PortalContent
from Products.CMFCore.WorkflowCore import WorkflowAction
from Products.CMFCore.utils import getToolByName
from Products.CMFDefault.SkinnedFolder import SkinnedFolder
from Products.CMFDefault.Document import addDocument
# Import permission names
from Products.CMFCore import CMFCorePermissions
from CollectorPermissions import *
DEFAULT_TRANSCRIPT_FORMAT = 'stx'
factory_type_information = (
{'id': 'Collector Issue',
#XXX 'content_icon': 'event_icon.gif',
'meta_type': 'CMF Collector Issue',
'description': ('A Collector Issue represents a bug report or'
' other support request.'),
'product': 'CMFCollector',
'factory': None, # So not included in 'New' add form
'allowed_content_types': ('Collector Issue Transcript', 'File', 'Image'),
'immediate_view': 'collector_edit_form',
'actions': ({'id': 'view',
'name': 'Transcript',
'action': 'collector_issue_contents',
'permissions': (ViewCollector,)},
{'id': 'followup',
'name': 'Followup',
'action': 'collector_issue_followup_form',
'permissions': (AddCollectorIssueComment,)},
{'id': 'artifacts',
'name': 'Add Artifacts',
'action': 'collector_issue_add_artifact_form',
'permissions': (AddCollectorIssueArtifact,)},
{'id': 'edit',
'name': 'Edit Issue',
'action': 'collector_issue_edit_form',
'permissions': (EditCollectorIssue,)},
{'id': 'browse',
'name': 'Browse Collector',
'action': 'collector_issue_up',
'permissions': (ViewCollector,)},
{'id': 'addIssue',
'name': 'New Issue',
'action': 'collector_issue_add_issue',
'permissions': (ViewCollector,)},
),
},
)
TRANSCRIPT_NAME = "ISSUE_TRANSCRIPT"
class CollectorIssue(SkinnedFolder, DefaultDublinCoreImpl):
"""An individual support request in the CMF Collector."""
meta_type = 'CMF Collector Issue'
effective_date = expiration_date = None
security = ClassSecurityInfo()
comment_delimiter = "<hr solid id=comment_delim>"
comment_number = 0
def __init__(self,
id, container,
title='', description='',
submitter_id=None, submitter_name=None, submitter_email=None,
kibitzers=None,
topic=None, classification=None,
security_related=0,
importance=None, severity=None,
assigned_to=None, current_status='pending',
resolution=None,
reported_version=None, other_version_info=None,
creation_date=None, modification_date=None,
effective_date=None, expiration_date=None):
""" """
SkinnedFolder.__init__(self, id, title)
# Take care of standard metadata:
DefaultDublinCoreImpl.__init__(self,
title=title, description=description,
effective_date=effective_date,
expiration_date=expiration_date)
if modification_date is None:
modification_date = self.creation_date
self.modification_date = modification_date
self._create_transcript(description, container)
user = getSecurityManager().getUser()
if submitter_id is None:
self.submitter_id = str(user)
self.submitter_id = submitter_id
if submitter_name is None:
if hasattr(user, 'full_name'):
submitter_name = user.full_name
elif (submitter_name
and (getattr(user, 'full_name', None) != submitter_name)):
# XXX We're being cavalier about stashing the full_name.
user.full_name = submitter_name
self.submitter_name = submitter_name
if submitter_email is None and hasattr(user, 'email'):
submitter_email = user.email
self.submitter_email = submitter_email
if kibitzers is None:
kibitzers = ()
self.kibitzers = kibitzers
self.topic = topic
self.classification = classification
self.security_related = security_related
self.importance = importance
self.severity = severity
self.assigned_to = assigned_to
self.current_status = current_status
self.resolution = resolution
self.reported_version = reported_version
self.other_version_info = other_version_info
self.edited = 0
return self
security.declareProtected(EditCollectorIssue, 'edit')
def edit(self, comment=None,
text=None,
status=None,
submitter_name=None,
title=None,
description=None,
security_related=None,
topic=None,
importance=None,
classification=None,
severity=None,
reported_version=None,
other_version_info=None):
"""Update the explicitly passed fields."""
if text is not None:
transcript = self.get_transcript()
transcript._edit(text_format=DEFAULT_TRANSCRIPT_FORMAT,
text=text)
if comment is not None:
self.do_action('edit', comment)
if submitter_name is not None:
self.submitter_name = submitter_name
if title is not None:
self.title = title
if description is not None:
self.description = description
if security_related is not None:
self.security_related = security_related
if topic is not None:
self.topic = topic
if importance is not None:
self.importance = importance
if classification is not None:
self.classification = classification
if severity is not None:
self.severity = severity
if reported_version is not None:
self.reported_version = reported_version
if other_version_info is not None:
self.other_version_info = other_version_info
self.edited = 1
security.declareProtected(CMFCorePermissions.View, 'get_transcript')
def get_transcript(self):
return self._getOb(TRANSCRIPT_NAME)
security.declareProtected(AddCollectorIssueComment, 'do_action')
def do_action(self, action, comment, attachments=None):
"""Execute an action, adding comment to the transcript."""
transcript = self.get_transcript()
self.comment_number = self.comment_number + 1
entry_leader = "\n\n" + self._entry_header(action) + "\n\n"
transcript._edit('stx',
transcript.EditableBody()
+ entry_leader
+ util.process_comment(comment))
security.declareProtected(AddCollectorIssueArtifact, 'add_artifact')
def add_artifact(self, id, type, description, file):
"""Add new artifact, and note in transcript."""
self.invokeFactory(type, id)
it = self._getOb(id)
it.description = description
it.manage_upload(file)
transcript = self.get_transcript()
entry_leader = ("\n\n"
+ self._entry_header("New Artifact '%s'" % id)
+ "\n\n")
transcript._edit('stx',
transcript.EditableBody()
+ entry_leader
+ util.process_comment(description))
def _create_transcript(self, description, container,
text_format=DEFAULT_TRANSCRIPT_FORMAT):
"""Create events and comments transcript, with initial entry."""
addDocument(self, TRANSCRIPT_NAME, description=description)
it = self.get_transcript()
it._setPortalTypeName('Collector Issue Transcript')
text = "%s\n\n %s " % (self._entry_header('Request', prefix="== "),
description)
it._edit(text_format=text_format, text=text)
it.title = self.title
def _entry_header(self, type, prefix="<hr> == ", suffix=" =="):
"""Return text for the header of a new transcript entry."""
# Ideally this would be a skin method (probly python script), but i
# don't know how to call it from the product, sigh.
t = string.capitalize(type)
if self.comment_number:
lead = t + " - Entry #" + str(self.comment_number)
else:
lead = t
user = getSecurityManager().getUser()
return ("%s%s by %s on %s%s" %
(prefix, lead, str(user), DateTime().aCommon(), suffix))
security.declareProtected(CMFCorePermissions.View, 'cited_text')
def cited_text(self):
"""Quote text for use in literal citations."""
return util.cited_text(self.get_transcript().text)
#################################################
# Dublin Core and search provisions
# The transcript indexes itself, we just need to index the salient
# attribute-style issue data/metadata...
security.declareProtected(CMFCorePermissions.ModifyPortalContent,
'indexObject')
def indexObject(self):
catalog = getToolByName(self, 'portal_catalog', None)
if catalog is not None:
catalog.indexObject(self)
security.declareProtected(CMFCorePermissions.ModifyPortalContent,
'unindexObject')
def unindexObject(self):
catalog = getToolByName(self, 'portal_catalog', None)
if catalog is not None:
catalog.unindexObject(self)
security.declareProtected(CMFCorePermissions.ModifyPortalContent,
'reindexObject')
def reindexObject(self):
catalog = getToolByName(self, 'portal_catalog', None)
if catalog is not None:
catalog.reindexObject(self)
def manage_afterAdd(self, item, container):
"""Add self to the workflow and catalog."""
# Are we being added (or moved)?
if aq_base(container) is not aq_base(self):
wf = getToolByName(self, 'portal_workflow', None)
if wf is not None:
wf.notifyCreated(self)
self.indexObject()
def manage_beforeDelete(self, item, container):
"""Remove self from the catalog."""
# Are we going away?
if aq_base(container) is not aq_base(self):
self.unindexObject()
# Now let our "aspects" know we are going away.
for it, subitem in self.objectItems():
si_m_bD = getattr(subitem, 'manage_beforeDelete', None)
if si_m_bD is not None:
si_m_bD(item, container)
def SearchableText(self):
"""Consolidate all text and structured fields for catalog search."""
# Make this a composite of the text and structured fields.
return (self.title + ' '
+ self.description + ' '
+ self.topic + ' '
+ self.classification + ' '
+ self.importance + ' '
+ self.severity + ' '
+ self.current_status + ' '
+ self.resolution + ' '
+ self.reported_version + ' '
+ self.other_version_info + ' '
+ ((self.security_related and 'security_related') or ''))
def Subject(self):
"""The structured attrs, combined w/field names for targeted search."""
return ('topic:' + self.topic,
'classification:' + self.classification,
'security_related:' + ((self.security_related and '1') or '0'),
'importance:' + self.importance,
'severity:' + self.severity,
'assigned_to:' + (self.assigned_to or ''),
'current_status:' + (self.current_status or ''),
'resolution:' + (self.resolution or ''),
'reported_version:' + self.reported_version)
def __repr__(self):
return ("<%s %s \"%s\" at 0x%s>"
% (self.__class__.__name__,
self.id, self.title,
hex(id(self))[2:]))
InitializeClass(CollectorIssue)
def addCollectorIssue(self,
id,
title='',
description='',
submitter_id=None,
submitter_name=None,
submitter_email=None,
kibitzers=None,
topic=None,
classification=None,
security_related=0,
importance=None,
severity=None,
assigned_to=None,
reported_version=None,
other_version_info=None,
REQUEST=None):
"""
Create a new issue in the collector.
"""
it = CollectorIssue(id=id,
container=self,
title=title,
description=description,
submitter_id=submitter_id,
submitter_name=submitter_name,
submitter_email=submitter_email,
kibitzers=kibitzers,
topic=topic,
classification=classification,
security_related=security_related,
importance=importance,
severity=severity,
assigned_to=assigned_to,
reported_version=reported_version,
other_version_info=other_version_info)
it._setPortalTypeName('Collector Issue')
self._setObject(id, it)
return id
=== Added File CMF/CMFCollector/CollectorPermissions.py ===
from Products.CMFCore import CMFCorePermissions
from Products.CMFCore.CMFCorePermissions import setDefaultRoles
# Gathering Event Related Permissions into one place
ViewCollector = CMFCorePermissions.View
AddCollector = 'Add portal collector'
ManageCollector = 'Add portal collector'
AddCollectorIssue = 'Add collector issue'
AddCollectorIssueComment = 'Add collector issue comment'
AddCollectorIssueArtifact = 'Add collector issue artifact'
EditCollectorIssue = 'Edit collector issue'
SupportIssue = 'Support collector issue'
# Set up default roles for permissions
setDefaultRoles(AddCollector, CMFCorePermissions.AddPortalContent)
setDefaultRoles(ManageCollector,
('Manager', 'Owner'))
setDefaultRoles(AddCollectorIssue,
('Anonymous', 'Manager', 'Reviewer', 'Owner'))
setDefaultRoles(AddCollectorIssueComment,
('Manager', 'Reviewer', 'Owner'))
setDefaultRoles(AddCollectorIssueArtifact,
('Manager', 'Reviewer', 'Owner'))
setDefaultRoles(EditCollectorIssue,
('Manager', 'Reviewer'))
setDefaultRoles(SupportIssue,
('Manager', 'Reviewer'))
=== Added File CMF/CMFCollector/INSTALL.txt ===
Installing CMFCollector
The CMFCollector is an issue collector for Zope.
Prerequisites:
- Zope, 2.4 or better (with page templates, Python 2.x)
- The CMF ( http://cmf.zope.org ), recent (as of 10/10/2001) CVS
checkout, or (not yet released) 1.2 or better.
- Zope Page templates (ZPT, http://www.zope.org/Wikis/DevSite/Projects/ZPT )
- CMFDecor provisions for ZPT, including use of the ZPT skin as the
default skin (settable from the portal_skins 'Properties' tab).
To install CMFCollector, uncompress the CMFCollector product into
your zope/Products directory or link it there, e.g.::
ln -s /path/to/installation /path/to/zope/Products
In the root of your CMFSite installation (within the ZMI):
1. Add an external method to the root of the CMF Site.
2. Use the following configuration values for the external
method:
o id: install_collector
o title (optional): Install Collector Content Types
o module name: CMFCollector.InstallCollector
o function name: install_collector
3. Go to the management screen for the newly added external
method and click the 'Try it' tab.
The install function will execute and give information about the
steps it took to register and install the CMF Events into the CMF
Site instance.
=== Added File CMF/CMFCollector/README.txt ===
CMFCollector README
The CMFCollector is starting out as a rudimentary issue tracker, to
replace the equally rudimentary, ancient collector we've been using
on zope.org for a long time. It is being implemented as CMF content
to enable evolution to a more comprehensive solution with time.
See INSTALL.txt for instructions about installing in your CMF site.
=== Added File CMF/CMFCollector/TODO.txt ===
To-do:
o 10/10/2001 klm: Basic content types implemented. Working on
workflow (prototyping with through-the-web DCWorkflow, but not yet
packaging that), searching, email, and assignment, but checking in
the base pieces.
o 09/24/2001 klm: Implementation. (Designs at
http://dev.zope.org/Wikis/DevSite/Projects/CollectorReplacement/FrontPage .)
=== Added File CMF/CMFCollector/VERSION.txt ===
CMFCollector Product 0.1
=== Added File CMF/CMFCollector/__init__.py ===
# Copyright (c) 2001 Zope Corporation. All Rights Reserved.
# This software is subject to the provisions of the Zope Public License,
# Version 1.1 (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.
from Products.CMFDefault import Portal
import Collector, CollectorIssue
import Products.CMFCore
from Products.CMFCore import utils, CMFCorePermissions
from Products.CMFCore.DirectoryView import registerDirectory
import CollectorPermissions
import sys
this_module = sys.modules[ __name__ ]
factory_type_information = (
(Collector.factory_type_information
+ CollectorIssue.factory_type_information
+ ({'id': 'Collector Issue Transcript',
# 'content_icon': 'event_icon.gif',
'meta_type': 'Document',
'description': ('A transcript of issue activity, including comments,'
' state changes, and so forth.'),
'product': 'CMFDefault',
'factory': None, # So not included in 'New' add form
'allowed_content_types': None,
'immediate_view': 'collector_transcript_view',
'actions': ({'id': 'view',
'name': 'View',
'action': '../',
'permissions': (CMFCorePermissions.View,)},
{'id': 'addcomment',
'name': 'Add Comment',
'action': 'collector_transcript_comment_form',
'permissions':
(CollectorPermissions.AddCollectorIssueComment,)},
{'id': 'edittranscript',
'name': 'Edit Transcript',
'action': 'collector_transcript_edit_form',
'permissions':
(CollectorPermissions.EditCollectorIssue,)},
),
},
)
)
)
contentClasses = (Collector.Collector, CollectorIssue.CollectorIssue)
contentConstructors = (Collector.addCollector,
CollectorIssue.addCollectorIssue)
z_bases = utils.initializeBasesPhase1(contentClasses, this_module)
# This is used by a script (external method) that can be run
# to set up collector in an existing CMF Site instance.
collector_globals = globals()
# Make the skins available as DirectoryViews
registerDirectory('skins', globals())
registerDirectory('skins/collector', globals())
def initialize(context):
utils.initializeBasesPhase2(z_bases, context)
context.registerHelp(directory='help')
context.registerHelpTitle('CMF Collector Help')
context.registerClass(Collector.Collector,
constructors = (Collector.addCollector,),
permission = CMFCorePermissions.AddPortalContent)
context.registerClass(CollectorIssue.CollectorIssue,
constructors = (CollectorIssue.addCollectorIssue,),
permission = CollectorPermissions.AddCollectorIssue)
=== Added File CMF/CMFCollector/util.py ===
##############################################################################
# Copyright (c) 2001 Zope Corporation. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 1.1 (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.
##############################################################################
"""Sundry collector utilities."""
import string, re
preexp = re.compile(r'<pre>')
unpreexp = re.compile(r'</pre>')
citedexp = re.compile(r'^\s*>')
# Match group 1 is citation prefix, group 2 is leading whitespace:
cite_prefixexp = re.compile('([\s>]*>)?([\s]*)')
def cited_text(text, rfind=string.rfind, strip=string.strip):
"""Quote text for use in literal citations.
We prepend '>' to each line, splitting long lines (propagating
existing citation and leading whitespace) when necessary."""
got = []
for line in string.split(text, '\n'):
pref = '> '
if len(line) < 79:
got.append(pref + line)
continue
m = cite_prefixexp.match(line)
if m is None:
pref = '> %s'
else:
if m.group(1):
pref = pref + m.group(1)
line = line[m.end(1)+1:]
if m.end(1) > 60:
# Too deep quoting - collapse it:
pref = '> >> '
lencut = 0
pref = pref + '%s'
leading_space = m.group(2)
if leading_space:
pref = pref + leading_space
line = line[len(leading_space):]
lenpref = len(pref)
continuation_padding = ''
lastcurlen = 0
while 1:
curlen = len(line) + lenpref
if curlen < 79 or (lastcurlen and lastcurlen <= curlen):
# Small enough - we're done - or not shrinking - bail out
if line: got.append((pref % continuation_padding) + line)
break
else:
lastcurlen = curlen
splitpoint = max(rfind(line[:78-lenpref], ' '),
rfind(line[:78-lenpref], '\t'))
if not splitpoint or splitpoint == -1:
if strip(line):
got.append((pref % continuation_padding) +
line)
line = ''
else:
if strip(line[:splitpoint]):
got.append((pref % continuation_padding) +
line[:splitpoint])
line = line[splitpoint+1:]
if not continuation_padding:
# Continuation lines are indented more than intial - just
# enough to line up past, eg, simple bullets.
continuation_padding = ' '
return string.join(got, '\n')
def process_comment(comment, strip=string.strip):
"""Return formatted comment, escaping cited text."""
# Process the comment:
# - Strip leading whitespace,
# - indent every line so it's contained as part of the prefix
# definition list, and
# - cause all cited text to be preformatted.
inpre = incited = atcited = 0
presearch = preexp.search
presplit = preexp.split
unpresearch = unpreexp.search
unpresplit = unpreexp.split
citedsearch = citedexp.search
got = []
for i in string.split(string.strip(comment), '\n') + ['']:
atcited = citedsearch(i)
if not atcited:
if incited:
# Departing cited section.
incited = 0
if inpre:
# Close <pre> that we prepended.
got.append(' </pre>')
inpre = 0
# Check line for toggling of inpre.
# XXX We don't deal well with way imbalanced pres on a
# single line. Feh, we're working too hard, already.
if not inpre:
x = presplit(i)
if len(x) > 1 and not unprexpsearch(x[-1]):
# The line has a <pre> without subsequent </pre>
inpre = 1
else: # in <pre>
x = unpresplit(i)
if len(x) > 1 and not prexpsearch(x[-1]):
# The line has a </pre> without subsequent <pre>
inpre = 0
else:
# Quote the minimal set of chars, to reduce raw text
# ugliness. Do the '&' *before* any others that include '&'s!
if '&' in i and ';' in i: i = string.replace(i, '&', '&')
if '<' in i: i = string.replace(i, '<', '<')
if not incited:
incited = 1
if not inpre:
got.append(' <pre>')
inpre = 1
got.append(' ' + i)
return string.join(got, '\n')