Switch to side-by-side view

--- a/Allura/allura/model/artifact.py
+++ b/Allura/allura/model/artifact.py
@@ -44,20 +44,20 @@
 
 log = logging.getLogger(__name__)
 
+
 class Artifact(MappedClass):
     """
-    The base class for anything you want to keep track of.
-
-    It will automatically be added to solr (see index() method).  It also
-    gains a discussion thread and can have files attached to it.
-
-    :var tool_version: default's to the app's version
+    Base class for anything you want to keep track of.
+
+    - Automatically indexed into Solr (see index() method)
+    - Has a discussion thread that can have files attached to it
+
+    :var mod_date: last-modified :class:`datetime`
+    :var tool_version: defaults to the parent Application's version
     :var acl: dict of permission name => [roles]
     :var labels: list of plain old strings
-    :var references: list of outgoing references to other tickets
-    :var backreferences: dict of incoming references to this artifact, mapped by solr id
+
     """
-
     class __mongometa__:
         session = artifact_orm_session
         name='artifact'
@@ -90,6 +90,10 @@
     deleted=FieldProperty(bool, if_missing=False)
 
     def __json__(self):
+        """Return a JSON-encodable :class:`dict` representation of this
+        Artifact.
+
+        """
         return dict(
             _id=str(self._id),
             mod_date=self.mod_date,
@@ -100,8 +104,14 @@
         )
 
     def parent_security_context(self):
-        '''ACL processing should continue at the  AppConfig object. This lets
-        AppConfigs provide a 'default' ACL for all artifacts in the tool.'''
+        """Return the :class:`allura.model.project.AppConfig` instance for
+        this Artifact.
+
+        ACL processing for this Artifact continues at the AppConfig object.
+        This lets AppConfigs provide a 'default' ACL for all artifacts in the
+        tool.
+
+        """
         return self.app_config
 
     @classmethod
@@ -110,6 +120,11 @@
 
     @classmethod
     def translate_query(cls, q, fields):
+        """Return a translated Solr query (``q``), where generic field
+        identifiers are replaced by the 'strongly typed' versions defined in
+        ``fields``.
+
+        """
         for f in fields:
             if '_' in f:
                 base, typ = f.rsplit('_', 1)
@@ -118,18 +133,34 @@
 
     @LazyProperty
     def ref(self):
+        """Return :class:`allura.model.index.ArtifactReference` for this
+        Artifact.
+
+        """
         return ArtifactReference.from_artifact(self)
 
     @LazyProperty
     def refs(self):
+        """Artifacts referenced by this one.
+
+        :return: list of :class:`allura.model.index.ArtifactReference`
+        """
         return self.ref.references
 
     @LazyProperty
     def backrefs(self):
+        """Artifacts that reference this one.
+
+        :return: list of :attr:`allura.model.index.ArtifactReference._id`'s
+
+        """
         q = ArtifactReference.query.find(dict(references=self.index_id()))
         return [ aref._id for aref in q ]
 
     def related_artifacts(self):
+        """Return all Artifacts that are related to this one.
+
+        """
         related_artifacts = []
         for ref_id in self.refs+self.backrefs:
             ref = ArtifactReference.query.get(_id=ref_id)
@@ -140,6 +171,8 @@
             # don't link to artifacts in deleted tools
             if hasattr(artifact, 'app_config') and artifact.app_config is None:
                 continue
+            # TODO: This should be refactored. We shouldn't be checking
+            # artifact type strings in platform code.
             if artifact.type_s == 'Commit' and not artifact.repo:
                 ac = AppConfig.query.get(
                         _id=ref.artifact_reference['app_config_id'])
@@ -151,6 +184,14 @@
         return related_artifacts
 
     def subscribe(self, user=None, topic=None, type='direct', n=1, unit='day'):
+        """Subscribe ``user`` to the :class:`allura.model.notification.Mailbox`
+        for this Artifact.
+
+        :param user: :class:`allura.model.auth.User`
+
+        If ``user`` is None, ``c.user`` will be subscribed.
+
+        """
         from allura.model import Mailbox
         if user is None: user = c.user
         Mailbox.subscribe(
@@ -161,6 +202,14 @@
             type=type, n=n, unit=unit)
 
     def unsubscribe(self, user=None):
+        """Unsubscribe ``user`` from the
+        :class:`allura.model.notification.Mailbox` for this Artifact.
+
+        :param user: :class:`allura.model.auth.User`
+
+        If ``user`` is None, ``c.user`` will be unsubscribed.
+
+        """
         from allura.model import Mailbox
         if user is None: user = c.user
         Mailbox.unsubscribe(
@@ -170,19 +219,27 @@
             artifact_index_id=self.index_id())
 
     def primary(self):
-        '''If an artifact is a "secondary" artifact (discussion of a ticket, for
+        """If an artifact is a "secondary" artifact (discussion of a ticket, for
         instance), return the artifact that is the "primary".
-        '''
+
+        """
         return self
 
     @classmethod
     def artifacts_labeled_with(cls, label, app_config):
-        """Return all artifacts of type `cls` that have the label `label` and
-        are in the tool denoted by `app_config`.
+        """Return all artifacts of type ``cls`` that have the label ``label`` and
+        are in the tool denoted by ``app_config``.
+
+        :param label: str
+        :param app_config: :class:`allura.model.project.AppConfig` instance
+
         """
         return cls.query.find({'labels':label, 'app_config_id': app_config._id})
 
     def email_link(self, subject='artifact'):
+        """Return a 'mailto' URL for this Artifact, with optional subject.
+
+        """
         if subject:
             return 'mailto:%s?subject=[%s:%s:%s] Re: %s' % (
                 self.email_address,
@@ -195,14 +252,26 @@
 
     @property
     def project(self):
+        """Return the :class:`allura.model.project.Project` instance to which
+        this Artifact belongs.
+
+        """
         return self.app_config.project
 
     @property
     def project_id(self):
+        """Return the ``_id`` of the :class:`allura.model.project.Project`
+        instance to which this Artifact belongs.
+
+        """
         return self.app_config.project_id
 
     @LazyProperty
     def app(self):
+        """Return the :class:`allura.model.app.Application` instance to which
+        this Artifact belongs.
+
+        """
         if not self.app_config:
             return None
         if getattr(c, 'app', None) and c.app.config._id == self.app_config._id:
@@ -211,9 +280,11 @@
             return self.app_config.load()(self.project, self.app_config)
 
     def index_id(self):
-        '''Globally unique artifact identifier.  Used for
-        SOLR ID, shortlinks, and maybe elsewhere
-        '''
+        """Return a globally unique artifact identifier.
+
+        Used for SOLR ID, shortlinks, and possibly elsewhere.
+
+        """
         id = '%s.%s#%s' % (
             self.__class__.__module__,
             self.__class__.__name__,
@@ -221,17 +292,25 @@
         return id.replace('.', '/')
 
     def index(self):
-        """
+        """Return a :class:`dict` representation of this Artifact suitable for
+        search indexing.
+
         Subclasses should override this, providing a dictionary of solr_field => value.
-        These fields & values will be stored by solr.  Subclasses should call the
+        These fields & values will be stored by Solr.  Subclasses should call the
         super() index() and then extend it with more fields.
 
-        The _s and _t suffixes, for example, follow solr dynamic field naming
-        pattern.
         You probably want to override at least title and text to have
         meaningful search results and email senders.
-        """
-
+
+        You can take advantage of Solr's dynamic field typing by adding a type
+        suffix to your field names, e.g.:
+
+            _s (string) (not analyzed)
+            _t (text) (analyzed)
+            _b (bool)
+            _i (int)
+
+        """
         project = self.project
         return dict(
             id=self.index_id(),
@@ -250,32 +329,39 @@
             deleted_b=self.deleted)
 
     def url(self):
-        """
-        Subclasses should implement this, providing the URL to the artifact
+        """Return the URL for this Artifact.
+
+        Subclasses must implement this.
+
         """
         raise NotImplementedError, 'url' # pragma no cover
 
     def shorthand_id(self):
-        '''How to refer to this artifact within the app instance context.
+        """How to refer to this artifact within the app instance context.
 
         For a wiki page, it might be the title.  For a ticket, it might be the
         ticket number.  For a discussion, it might be the message ID.  Generally
         this should have a strong correlation to the URL.
-        '''
+
+        """
         return str(self._id) # pragma no cover
 
     def link_text(self):
-        '''The link text that will be used when a shortlink to this artifact
+        """Return the link text to use when a shortlink to this artifact
         is expanded into an <a></a> tag.
 
-        By default this method returns shorthand_id(). Subclasses should
+        By default this method returns :meth:`shorthand_id`. Subclasses should
         override this method to provide more descriptive link text.
-        '''
+
+        """
         return self.shorthand_id()
 
     def get_discussion_thread(self, data=None):
-        '''Return the discussion thread for this artifact (possibly made more
-        specific by the message_data)'''
+        """Return the discussion thread and parent_id for this artifact.
+
+        :return: (:class:`allura.model.discuss.Thread`, parent_thread_id (int))
+
+        """
         from .discuss import Thread
         t = Thread.query.get(ref_id=self.index_id())
         if t is None:
@@ -293,17 +379,32 @@
 
     @LazyProperty
     def discussion_thread(self):
+        """Return the :class:`discussion thread <allura.model.discuss.Thread>`
+        for this Artifact.
+
+        """
         return self.get_discussion_thread()[0]
 
     def attach(self, filename, fp, **kw):
+        """Attach a file to this Artifact.
+
+        :param filename: file name
+        :param fp: a file-like object (implements ``read()``)
+        :param \*\*kw: passed through to Attachment class constructor
+
+        """
         att = self.attachment_class().save_attachment(
             filename=filename,
             fp=fp, artifact_id=self._id, **kw)
         return att
 
     def delete(self):
+        """Delete this Artifact.
+
+        """
         ArtifactReference.query.remove(dict(_id=self.index_id()))
         super(Artifact, self).delete()
+
 
 class Snapshot(Artifact):
     """A snapshot of an :class:`Artifact <allura.model.artifact.Artifact>`, used in :class:`VersionedArtifact <allura.model.artifact.VersionedArtifact>`"""