Updated entity/attribute docs - moved documentation into class/method/etc docstrings...
authorStephen Burrows <stephen.r.burrows@gmail.com>
Thu, 28 Apr 2011 19:23:04 +0000 (15:23 -0400)
committerStephen Burrows <stephen.r.burrows@gmail.com>
Thu, 28 Apr 2011 19:34:53 +0000 (15:34 -0400)
docs/conf.py
docs/dummy-settings.py [new file with mode: 0644]
docs/models/entities.rst
philo/models/base.py

index a879f03..ece87fc 100644 (file)
@@ -17,15 +17,18 @@ import sys, os
 # add these directories to sys.path here. If the directory is relative to the
 # documentation root, use os.path.abspath to make it absolute, like shown here.
 sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "_ext")))
+sys.path.append(os.path.abspath(os.path.dirname(os.path.dirname(__file__))))
+
+os.environ['DJANGO_SETTINGS_MODULE'] = 'dummy-settings'
 
 # -- General configuration -----------------------------------------------------
 
 # If your documentation needs a minimal Sphinx version, state it here.
-#needs_sphinx = '1.0'
+needs_sphinx = '1.0'
 
 # Add any Sphinx extension module names here, as strings. They can be extensions
 # coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
-extensions = ['djangodocs']
+extensions = ['djangodocs', 'sphinx.ext.autodoc']
 
 # Add any paths that contain templates here, relative to this directory.
 templates_path = ['_templates']
diff --git a/docs/dummy-settings.py b/docs/dummy-settings.py
new file mode 100644 (file)
index 0000000..e69de29
index fee27c1..a834b13 100644 (file)
@@ -1,72 +1,54 @@
 Entities and Attributes
 =======================
 
-One of the core concepts in philo is the relationship between the :class:`Entity` and :class:`Attribute` classes. :class:`Attribute`\ s represent an arbitrary key/value pair by having one :class:`GenericForeignKey` to an :class:`Entity` and another to an :class:`AttributeValue`.
+.. module:: philo.models.base
 
-Functions
----------
+One of the core concepts in Philo is the relationship between the :class:`Entity` and :class:`Attribute` classes. :class:`Attribute`\ s represent an arbitrary key/value pair by having one :class:`GenericForeignKey` to an :class:`Entity` and another to an :class:`AttributeValue`.
 
-.. function:: register_value_model(model)
-.. function:: unregister_value_model(model)
 
-   Helper functions to register/unregister a model as a valid content type for a :class:`ForeignKeyValue` or :class:`ManyToManyValue`.
+Attributes
+----------
 
+.. autoclass:: Attribute
+   :members:
 
-Classes
--------
+.. autoclass:: AttributeValue
+   :members:
 
-.. class:: AttributeValue
+.. automodule:: philo.models.base
+   :members: attribute_value_limiter
 
-   This is an abstract base class for models that can be used as values for :class:`Attribute`\ s. See the code for examples of implementations.
+.. autoclass:: JSONValue
+   :show-inheritance:
 
-   .. attribute:: attribute_set
+.. autoclass:: ForeignKeyValue
+   :show-inheritance:
 
-      :class:`GenericRelation` back to :class:`Attribute`
+.. autoclass:: ManyToManyValue
+   :show-inheritance:
 
-   .. method:: set_value(value)
+.. automodule:: philo.models.base
+   :members: value_content_type_limiter
 
-      Interpret ``value`` and set the appropriate fields so that the value displayed to the world is equivalent to ``value``.
+.. autofunction:: register_value_model(model)
+.. autofunction:: unregister_value_model(model)
 
-   .. method:: value_formfields(**kwargs)
+Entities
+--------
 
-      Define any formfields that would be used to construct an instance of this value.
+.. autoclass:: Entity
+   :members:
+   :exclude-members: attribute_set
 
-   .. method:: construct_instance(**kwargs)
+.. autoclass:: TreeManager
+   :members:
 
-      Apply cleaned data from the formfields generated by valid_formfields to oneself.
+.. autoclass:: TreeEntity
+   :members:
+   :exclude-members: attribute_set
 
-.. class:: Attribute
+   .. attribute:: objects
 
-   Represents an arbitrary key/value pair attached to a model.
-
-   .. attribute:: entity
-
-      :class:`GenericForeignKey` to anything.
-
-   .. attribute:: value
-
-      :class:`GenericForeignKey` to a subclass of :class:`AttributeValue`.
-
-   .. attribute:: key
-
-      :class:`CharField` containing a key (up to 255 characters) consisting of alphanumeric characters and underscores.
-
-.. class:: Entity
-
-   A class that simplifies access to related attributes.
-
-   .. attribute:: attribute_set
-
-      :class:`GenericRelation` back to :class:`Attribute`.
-
-   .. attribute:: attributes
-
-      Property that returns a dictionary-like object which can be used to retrieve :class:`Attribute`\ s values directly.
-
-      Example::
-
-         >>> attr = entity.attribute_set.get(key='spam')
-         >>> attr.value.value
-         u'eggs'
-         >>> entity.attributes['spam']
-         u'eggs'
\ No newline at end of file
+      An instance of :class:`TreeManager`.
+   
+   .. automethod:: get_path
\ No newline at end of file
index af1e880..01fdd64 100644 (file)
@@ -39,9 +39,17 @@ class Titled(models.Model):
 
 
 value_content_type_limiter = ContentTypeRegistryLimiter()
+"""
+An instance of :class:`ContentTypeRegistryLimiter` which is used to track the content types which can be related to by ForeignKeyValues and ManyToManyValues.
+
+"""
 
 
 def register_value_model(model):
+       """
+       Registers a model as a valid content type for a :class:`ForeignKeyValue` or :class:`ManyToManyValue` through the :data:`value_content_type_limiter`.
+       
+       """
        value_content_type_limiter.register_class(model)
 
 
@@ -49,21 +57,40 @@ register_value_model(Tag)
 
 
 def unregister_value_model(model):
+       """
+       Registers a model as a valid content type for a :class:`ForeignKeyValue` or :class:`ManyToManyValue` through the :data:`value_content_type_limiter`.
+       
+       """
        value_content_type_limiter.unregister_class(model)
 
 
 class AttributeValue(models.Model):
+       """
+       This is an abstract base class for models that can be used as values for :class:`Attribute`\ s.
+       
+       AttributeValue subclasses are expected to supply access to a clean version of their value through an attribute called "value".
+       
+       """
        attribute_set = generic.GenericRelation('Attribute', content_type_field='value_content_type', object_id_field='value_object_id')
+       """
+       :class:`GenericRelation` to :class:`Attribute`
        
+       """
        def set_value(self, value):
+               """Given a ``value``, sets the appropriate fields so that it can be correctly stored in the database."""
                raise NotImplementedError
        
        def value_formfields(self, **kwargs):
-               """Define any formfields that would be used to construct an instance of this value."""
+               """
+               Returns any formfields that would be used to construct an instance of this value.
+               
+               :returns: A dictionary mapping field names to formfields.
+               
+               """
                raise NotImplementedError
        
        def construct_instance(self, **kwargs):
-               """Apply cleaned data from the formfields generated by valid_formfields to oneself."""
+               """Applies cleaned data from the formfields generated by valid_formfields to oneself."""
                raise NotImplementedError
        
        def __unicode__(self):
@@ -74,9 +101,14 @@ class AttributeValue(models.Model):
 
 
 attribute_value_limiter = ContentTypeSubclassLimiter(AttributeValue)
+"""
+An instance of :class:`ContentTypeSubclassLimiter` which is used to track the content types which are considered valid value models for an :class:`Attribute`.
+
+"""
 
 
 class JSONValue(AttributeValue):
+       """Stores a python object as a json string."""
        value = JSONField(verbose_name='Value (JSON)', help_text='This value must be valid JSON.', default='null', db_index=True)
        
        def __unicode__(self):
@@ -99,6 +131,7 @@ class JSONValue(AttributeValue):
 
 
 class ForeignKeyValue(AttributeValue):
+       """Stores a generic relationship to an instance of any value content type (as defined by the :data:`value_content_type_limiter`)."""
        content_type = models.ForeignKey(ContentType, limit_choices_to=value_content_type_limiter, verbose_name='Value type', null=True, blank=True)
        object_id = models.PositiveIntegerField(verbose_name='Value ID', null=True, blank=True, db_index=True)
        value = generic.GenericForeignKey()
@@ -136,6 +169,7 @@ class ForeignKeyValue(AttributeValue):
 
 
 class ManyToManyValue(AttributeValue):
+       """Stores a generic relationship to many instances of any value content type (as defined by the :data:`value_content_type_limiter`)."""
        content_type = models.ForeignKey(ContentType, limit_choices_to=value_content_type_limiter, verbose_name='Value type', null=True, blank=True)
        values = models.ManyToManyField(ForeignKeyValue, blank=True, null=True)
        
@@ -215,15 +249,31 @@ class ManyToManyValue(AttributeValue):
 
 
 class Attribute(models.Model):
+       """
+       Represents an arbitrary key/value pair on an arbitrary :class:`Model` where the key consists of word characters and the value is a subclass of :class:`AttributeValue`.
+       
+       """
        entity_content_type = models.ForeignKey(ContentType, related_name='attribute_entity_set', verbose_name='Entity type')
        entity_object_id = models.PositiveIntegerField(verbose_name='Entity ID', db_index=True)
        entity = generic.GenericForeignKey('entity_content_type', 'entity_object_id')
+       """
+       :class:`GenericForeignKey` to anything (generally an instance of an Entity subclass).
+       
+       """
        
        value_content_type = models.ForeignKey(ContentType, related_name='attribute_value_set', limit_choices_to=attribute_value_limiter, verbose_name='Value type', null=True, blank=True)
        value_object_id = models.PositiveIntegerField(verbose_name='Value ID', null=True, blank=True, db_index=True)
        value = generic.GenericForeignKey('value_content_type', 'value_object_id')
+       """
+       :class:`GenericForeignKey` to an instance of a subclass of :class:`AttributeValue` as determined by the :data:`attribute_value_limiter`.
+       
+       """
        
        key = models.CharField(max_length=255, validators=[RegexValidator("\w+")], help_text="Must contain one or more alphanumeric characters or underscores.", db_index=True)
+       """
+       :class:`CharField` containing a key (up to 255 characters) consisting of alphanumeric characters and underscores.
+       
+       """
        
        def __unicode__(self):
                return u'"%s": %s' % (self.key, self.value)
@@ -279,12 +329,24 @@ class EntityBase(models.base.ModelBase):
 
 
 class Entity(models.Model):
+       """An abstract class that simplifies access to related attributes. Most models provided by Philo subclass Entity."""
        __metaclass__ = EntityBase
        
        attribute_set = generic.GenericRelation(Attribute, content_type_field='entity_content_type', object_id_field='entity_object_id')
        
        @property
        def attributes(self):
+               """
+               Property that returns a dictionary-like object which can be used to retrieve related :class:`Attribute`\ s' values directly.
+
+               Example::
+
+                       >>> attr = entity.attribute_set.get(key='spam')
+                       >>> attr.value.value
+                       u'eggs'
+                       >>> entity.attributes['spam']
+                       u'eggs'
+               """
                return QuerySetMapper(self.attribute_set.all())
        
        class Meta:
@@ -296,19 +358,20 @@ class TreeManager(models.Manager):
        
        def get_with_path(self, path, root=None, absolute_result=True, pathsep='/', field='slug'):
                """
-               Returns the object with the path, unless absolute_result is set to False, in which
-               case it returns a tuple containing the deepest object found along the path, and the
-               remainder of the path after that object as a string (or None if there is no remaining
-               path). Raises a DoesNotExist exception if no object is found with the given path.
+               If ``absolute_result`` is ``True``, returns the object at ``path`` (starting at ``root``) or raises a :exception:`DoesNotExist` exception. Otherwise, returns a tuple containing the deepest object found along ``path`` (or ``root`` if no deeper object is found) and the remainder of the path after that object as a string (or None if there is no remaining path).
+               
+               .. note:: If you are looking for something with an exact path, it is faster to use absolute_result=True, unless the path depth is over ~40, in which case the high cost of the absolute query may make a binary search (i.e. non-absolute) faster.
+               
+               .. note:: SQLite allows max of 64 tables in one join. That means the binary search will only work on paths with a max depth of 127 and the absolute fetch will only work to a max depth of (surprise!) 63. Larger depths could be handled, but since the common use case will not have a tree structure that deep, they are not.
+               
+               :param path: The path of the object
+               :param root: The object which will be considered the root of the search
+               :param absolute_result: Whether to return an absolute result or do a binary search
+               :param pathsep: The path separator used in ``path``
+               :param field: The field on the model which should be queried for ``path`` segment matching.
+               :returns: An instance if absolute_result is True or (instance, remaining_path) otherwise.
                
-               If the path you're searching for is known to exist, it is always faster to use
-               absolute_result=True - unless the path depth is over ~40, in which case the high cost
-               of the absolute query makes a binary search (i.e. non-absolute) faster.
                """
-               # Note: SQLite allows max of 64 tables in one join. That means the binary search will
-               # only work on paths with a max depth of 127 and the absolute fetch will only work
-               # to a max depth of (surprise!) 63. Although this could be handled, chances are your
-               # tree structure won't be that deep.
                segments = path.split(pathsep)
                
                # Clean out blank segments. Handles multiple consecutive pathseps.
@@ -407,6 +470,13 @@ class TreeModel(MPTTModel):
        slug = models.SlugField(max_length=255)
        
        def get_path(self, root=None, pathsep='/', field='slug'):
+               """
+               :param root: Only return the path since this object.
+               :param pathsep: The path separator to use when constructing an instance's path
+               :param field: The field to pull path information from for each ancestor.
+               :returns: A string representation of an object's path.
+               
+               """
                if root == self:
                        return ''
                
@@ -438,10 +508,28 @@ class TreeEntityBase(MPTTModelBase, EntityBase):
 
 
 class TreeEntity(Entity, TreeModel):
+       """
+       An abstract subclass of Entity which represents a tree relationship.
+       
+       """
        __metaclass__ = TreeEntityBase
        
        @property
        def attributes(self):
+               """
+               Property that returns a dictionary-like object which can be used to retrieve related :class:`Attribute`\ s' values directly. If an attribute with a given key is not related to the :class:`Entity`, then the object will check the parent's attributes.
+
+               Example::
+
+                       >>> attr = entity.attribute_set.get(key='spam')
+                       DoesNotExist: Attribute matching query does not exist.
+                       >>> attr = entity.parent.attribute_set.get(key='spam')
+                       >>> attr.value.value
+                       u'eggs'
+                       >>> entity.attributes['spam']
+                       u'eggs'
+               
+               """
                if self.parent:
                        return QuerySetMapper(self.attribute_set.all(), passthrough=self.parent.attributes)
                return super(TreeEntity, self).attributes