# IssueTrackerProduct
#
# Peter Bengtsson <mail@peterbe.com>
# License: ZPL
#
# Zope
from OFS.SimpleItem import SimpleItem
from OFS.PropertyManager import PropertyManager
from Products.ZCatalog.CatalogAwareness import CatalogAware
from AccessControl import ClassSecurityInfo
from DateTime import DateTime
from Acquisition import aq_inner,aq_parent
try:
# >= Zope 2.12
from App.special_dtml import DTMLFile
from App.class_init import InitializeClass
except ImportError:
# < Zope 2.12
from Globals import InitializeClass,DTMLFile
# Is CMF installed?
try:
from Products.CMFCore.utils import getToolByName
except ImportError:
CMF_getToolByName = None
# Product
from IssueTracker import IssueTrackerFolderBase
from TemplateAdder import addTemplates2Class
from Permissions import VMS
from I18N import _
from Constants import *
from Utils import unicodify,asciify
#----------------------------------------------------------------------------
manage_addIssueNoteForm = DTMLFile('dtml/NotImplemented', globals())
def manage_addIssueNote(*args, **kw):
""" This is not supported """
raise NotImplementedError("This method should not be used")
#----------------------------------------------------------------------------
class IssueNote(SimpleItem, PropertyManager, CatalogAware,
IssueTrackerFolderBase,# needed?
):
"""
A note is put on an issue or a followup inside an issue.
"""
meta_type = ISSUENOTE_METATYPE
icon = '%s/issuenote.png' % ICON_LOCATION
_properties=({'id':'title', 'type': 'ustring', 'mode':'w'},
{'id':'comment', 'type': 'utext', 'mode':'w'},
{'id':'notedate', 'type': 'date', 'mode':'w'},
{'id':'fromname', 'type': 'ustring', 'mode':'w'},
{'id':'email', 'type': 'string', 'mode':'w'},
{'id':'acl_adder', 'type': 'string', 'mode':'w'},
{'id':'threadID', 'type': 'string', 'mode':'w'},
{'id':'display_format','type': 'string', 'mode':'w'},
)
security=ClassSecurityInfo()
manage_options = (
{'label':'Properties', 'action':'manage_propertiesForm'},
)
acl_adder = '' # backward compatability
def __init__(self, id, title, comment, fromname, email,
notedate=None, display_format=None, acl_adder='',
threadID=''):
""" create thread """
self.id = str(id)
self.title = unicodify(title)
self.comment = unicodify(comment)
if isinstance(notedate, basestring):
notedate = DateTime(notedate)
elif not notedate:
notedate = DateTime()
self.notedate = notedate
self.fromname = unicodify(fromname)
if isinstance(email, basestring):
email = asciify(email, 'ignore')
self.email = email
self.display_format = display_format
if acl_adder is None:
acl_adder = ''
self.acl_adder = acl_adder
self.threadID = threadID
def _getIssue(self):
"""a note always belongs to an issue"""
return aq_parent(aq_inner(self))
def getModifyDate(self):
return self.bobobase_modification_time()
def getFromname(self, issueusercheck=True):
""" return fromname """
acl_adder = self.getACLAdder()
if issueusercheck and acl_adder:
ufpath, name = acl_adder.split(',')
try:
uf = self.unrestrictedTraverse(ufpath)
except KeyError:
try:
uf = self.unrestrictedTraverse(ufpath.split('/')[-1])
except KeyError:
# the userfolder (as it was saved) no longer exists
return self.fromname
if uf.meta_type == ISSUEUSERFOLDER_METATYPE:
if uf.data.has_key(name):
issueuserobj = uf.data[name]
return issueuserobj.getFullname() or self.fromname
elif CMF_getToolByName and hasattr(uf, 'portal_membership'):
mtool = CMF_getToolByName(self, 'portal_membership')
member = mtool.getMemberById(name)
if member and member.getProperty('fullname'):
return member.getProperty('fullname')
return self.fromname
def getEmail(self, issueusercheck=True):
""" return email """
acl_adder = self.getACLAdder()
if issueusercheck and acl_adder:
ufpath, name = acl_adder.split(',')
try:
uf = self.unrestrictedTraverse(ufpath)
except KeyError:
try:
uf = self.unrestrictedTraverse(ufpath.split('/')[-1])
except KeyError:
# the userfolder (as it was saved) no longer exists
return self.email
if uf.meta_type == ISSUEUSERFOLDER_METATYPE:
if uf.data.has_key(name):
issueuserobj = uf.data[name]
return issueuserobj.getEmail() or self.email
elif CMF_getToolByName and hasattr(uf, 'portal_membership'):
mtool = CMF_getToolByName(self, 'portal_membership')
member = mtool.getMemberById(name)
if member and member.getProperty('email'):
return member.getProperty('email')
return self.email
def getACLAdder(self):
""" return acl_adder """
return self.acl_adder
def _setACLAdder(self, acl_adder):
""" set acl_adder """
self.acl_adder = acl_adder
def getThreadID(self):
return self.threadID
def getThread(self):
return getattr(self, self.getThreadID(), None)
def getComment(self):
""" return comment """
return self.comment
def getCommentPure(self):
""" return comment purified.
If the comment contains HTML for example, remove it."""
comment = self.getComment()
if self.getDisplayFormat() =='html':
# textify() coverts "<tag>Something</tag>" to "Something". Simple.
comment = Utils.textify(comment)
# a very common thing is that the description contains
# these faux double linebreaks and when you run textify()
# on '<p> </p>' the result is ' '. Too many of
# those result in ' ' which
# isn't pure and purifying is what this method aims to do
comment = comment.replace('<p> </p>','')
return comment
def _unicode_comment(self):
""" make the comment of this thread a unicode string """
self.comment = unicodify(self.comment)
self._prerendered_comment = unicodify(self._prerendered_comment)
def _prerender_comment(self):
""" Run the methods that pre-renders the comment of the issue. """
comment = self.getComment()
display_format = self.display_format
formatted = self.ShowDescription(comment+' ', display_format)
formatted = self._getIssue()._findIssueLinks(formatted)
self._prerendered_comment = formatted
def _getFormattedComment(self):
""" return the comment formatted """
if getattr(self, '_prerendered_comment', None):
formatted = self._prerendered_comment
else:
comment = self.getComment()
display_format = self.display_format
formatted = self.ShowDescription(comment+' ', display_format)
return formatted
def showComment(self):
""" combine ShowDescription (which is generic) with this
threads display format."""
formatted = self._getFormattedComment()
return self.HighlightQ(formatted)
def index_object(self, idxs=None):
"""A common method to allow Findables to index themselves."""
path = '/'.join(self.getPhysicalPath())
catalog = self.getCatalog()
if idxs is None:
# because I don't want to put mutable defaults in
# the keyword arguments
idxs = ['comment', 'meta_type', 'fromname', 'email',
'path', 'modifydate']
else:
# No matter what, when indexing you must always include 'path'
# otherwise you might update indexes without putting the object
# brain in the catalog. If that happens the object won't be
# findable in the searchResults(path='/some/path') even if it's
# findable on other indexes such as comment.
if 'path' not in idxs:
idxs.append('path')
catalog.catalog_object(self, path, idxs=idxs)
def getFromname_idx(self):
return self.getFromname()
def getComment_idx(self):
return self.getComment()
def unindex_object(self):
"""A common method to allow Findables to unindex themselves."""
self.getCatalog().uncatalog_object('/'.join(self.getPhysicalPath()))
def manage_afterAdd(self, REQUEST, RESPONSE):
""" intercept so that we prerender always """
try:
self._prerender_comment()
except:
if DEBUG:
raise
else:
try:
err_log = self.error_log
err_log.raising(sys.exc_info())
except:
pass
logging.error("Unable to _prerender_comment() after add",
exc_info=True)
security.declareProtected(VMS, 'assertAllProperties')
def assertAllProperties(self):
""" make sure it has all properties """
return 0
InitializeClass(IssueNote)
|