util:http: special handling for HTTP_USER_ERROR in get_post_url().
[be.git] / libbe / comment.py
index ee2857bd036e9f65be96605da81589f3178aeb16..a669e4e5012e180f229ca324426dbee53d64994b 100644 (file)
@@ -1,23 +1,25 @@
-# Copyright (C) 2008-2010 Gianluca Montecchi <gian@grys.it>
+# Copyright (C) 2008-2012 Chris Ball <cjb@laptop.org>
+#                         Gianluca Montecchi <gian@grys.it>
+#                         Niall Douglas (s_sourceforge@nedprod.com) <spam@spamtrap.com>
 #                         Thomas Habets <thomas@habets.pp.se>
-#                         W. Trevor King <wking@drexel.edu>
+#                         W. Trevor King <wking@tremily.us>
 #
 # This file is part of Bugs Everywhere.
 #
-# Bugs Everywhere is free software; you can redistribute it and/or modify it
-# under the terms of the GNU General Public License as published by the
-# Free Software Foundation, either version 2 of the License, or (at your
-# option) any later version.
+# Bugs Everywhere is free software: you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 2 of the License, or (at your option) any
+# later version.
 #
 # Bugs Everywhere is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# General Public License for more details.
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+# more details.
 #
-# You should have received a copy of the GNU General Public License
-# along with Bugs Everywhere.  If not, see <http://www.gnu.org/licenses/>.
+# You should have received a copy of the GNU General Public License along with
+# Bugs Everywhere.  If not, see <http://www.gnu.org/licenses/>.
 
-"""Define the :class:`Comment` class for representing bug comments.
+"""Define :py:class:`Comment` for representing bug comments.
 """
 
 import base64
@@ -53,13 +55,6 @@ if libbe.TESTING == True:
     import doctest
 
 
-class InvalidShortname(KeyError):
-    def __init__(self, shortname, shortnames):
-        msg = "Invalid shortname %s\n%s" % (shortname, shortnames)
-        KeyError.__init__(self, msg)
-        self.shortname = shortname
-        self.shortnames = shortnames
-
 class MissingReference(ValueError):
     def __init__(self, comment):
         msg = "Missing reference to %s" % (comment.in_reply_to)
@@ -67,11 +62,6 @@ class MissingReference(ValueError):
         self.reference = comment.in_reply_to
         self.comment = comment
 
-class DiskAccessRequired (Exception):
-    def __init__(self, goal):
-        msg = "Cannot %s without accessing the disk" % goal
-        Exception.__init__(self, msg)
-
 INVALID_UUID = "!!~~\n INVALID-UUID \n~~!!"
 
 def load_comments(bug, load_full=False):
@@ -97,11 +87,13 @@ def load_comments(bug, load_full=False):
 
 def save_comments(bug):
     for comment in bug.comment_root.traverse():
+        comment.bug = bug
+        comment.storage = bug.storage
         comment.save()
 
 
 class Comment (Tree, settings_object.SavedSettingsObject):
-    """Comments are a notes that attach to :class:`~libbe.bug.Bug`\s in
+    """Comments are a notes that attach to :py:class:`~libbe.bug.Bug`\s in
     threaded trees.  In mailing-list terms, a comment is analogous to
     a single part of an email.
 
@@ -167,7 +159,8 @@ class Comment (Tree, settings_object.SavedSettingsObject):
         assert self.uuid != INVALID_UUID, self
         if self.content_type.startswith('text/') \
                 and self.bug != None and self.bug.bugdir != None:
-            new = libbe.util.id.short_to_long_text([self.bug.bugdir], new)
+            new = libbe.util.id.short_to_long_text(
+                {self.bug.bugdir.uuid: self.bug.bugdir}, new)
         if (self.storage != None and self.storage.writeable == True) \
                 or force==True:
             assert new != None, "Can't save empty comment"
@@ -206,8 +199,8 @@ class Comment (Tree, settings_object.SavedSettingsObject):
         if ``from_storage==False`` (the default).  When
         ``from_storage==True``, they are loaded from the bug database.
         ``content_type`` decides if the body should be run through
-        :func:`util.id.short_to_long_text` before saving.  See
-        :meth:`_set_comment_body` for details.
+        :py:func:`util.id.short_to_long_text` before saving.  See
+        :py:meth:`_set_comment_body` for details.
 
         ``in_reply_to`` should be the uuid string of the parent comment.
         """
@@ -340,7 +333,7 @@ class Comment (Tree, settings_object.SavedSettingsObject):
         sep = '\n' + istring
         return istring + sep.join(lines).rstrip('\n')
 
-    def from_xml(self, xml_string, verbose=True):
+    def from_xml(self, xml_string, preserve_uuids=False, verbose=True):
         u"""
         Note: If alt-id is not given, translates any <uuid> fields to
         <alt-id> fields.
@@ -360,6 +353,10 @@ class Comment (Tree, settings_object.SavedSettingsObject):
         >>> commB.alt_id = None
         >>> commB.xml() == xml
         True
+        >>> commC = Comment()
+        >>> commC.from_xml(xml, preserve_uuids=True)
+        >>> commC.uuid == commA.uuid
+        True
         """
         if type(xml_string) == types.UnicodeType:
             xml_string = xml_string.strip().encode('unicode_escape')
@@ -384,8 +381,10 @@ class Comment (Tree, settings_object.SavedSettingsObject):
                     text = settings_object.EMPTY
                 else:
                     text = xml.sax.saxutils.unescape(child.text)
-                    text = text.decode('unicode_escape').strip()
-                if child.tag == 'uuid':
+                    if not isinstance(text, unicode):
+                        text = text.decode('unicode_escape')
+                    text = text.strip()
+                if child.tag == 'uuid' and not preserve_uuids:
                     uuid = text
                     continue # don't set the comment's uuid tag.
                 elif child.tag == 'body':
@@ -463,16 +462,17 @@ class Comment (Tree, settings_object.SavedSettingsObject):
           <extra-string>TAG: very helpful</extra-string>
         </comment>
         """
-        for attr in other.explicit_attrs:
-            old = getattr(self, attr)
-            new = getattr(other, attr)
-            if old != new:
-                if accept_changes == True:
-                    setattr(self, attr, new)
-                elif change_exception == True:
-                    raise ValueError, \
-                        'Merge would change %s "%s"->"%s" for comment %s' \
-                        % (attr, old, new, self.uuid)
+        if hasattr(other, 'explicit_attrs'):
+            for attr in other.explicit_attrs:
+                old = getattr(self, attr)
+                new = getattr(other, attr)
+                if old != new:
+                    if accept_changes:
+                        setattr(self, attr, new)
+                    elif change_exception:
+                        raise ValueError(
+                            ('Merge would change {} "{}"->"{}" for comment {}'
+                             ).format(attr, old, new, self.uuid))
         if self.alt_id == self.uuid:
             self.alt_id = None
         for estr in other.extra_strings:
@@ -508,7 +508,8 @@ class Comment (Tree, settings_object.SavedSettingsObject):
         if self.content_type.startswith("text/"):
             body = (self.body or "")
             if self.bug != None and self.bug.bugdir != None:
-                body = libbe.util.id.long_to_short_text([self.bug.bugdir], body)
+                body = libbe.util.id.long_to_short_text(
+                    {self.bug.bugdir.uuid: self.bug.bugdir}, body)
             lines.extend(body.splitlines())
         else:
             lines.append("Content type %s not printable.  Try XML output instead" % self.content_type)
@@ -607,8 +608,8 @@ class Comment (Tree, settings_object.SavedSettingsObject):
         if self.uuid == INVALID_UUID:
             return
         if settings_mapfile == None:
-            settings_mapfile = \
-                self.storage.get(self.id.storage("values"), default="\n")
+            settings_mapfile = self.storage.get(
+                self.id.storage('values'), '{}\n')
         try:
             settings = mapfile.parse(settings_mapfile)
         except mapfile.InvalidMapfileContents, e: