Merge branch 'attribute_access' into release
authorStephen Burrows <stephen.r.burrows@gmail.com>
Wed, 11 May 2011 21:23:41 +0000 (17:23 -0400)
committerStephen Burrows <stephen.r.burrows@gmail.com>
Wed, 11 May 2011 21:23:41 +0000 (17:23 -0400)
Conflicts:
philo/models/nodes.py

17 files changed:
docs/dummy-settings.py
docs/exceptions.rst [new file with mode: 0644]
docs/index.rst
docs/middleware.rst [new file with mode: 0644]
docs/models/fields.rst
docs/signals.rst [new file with mode: 0644]
docs/validators.rst [new file with mode: 0644]
philo/contrib/shipherd/models.py
philo/contrib/sobol/models.py
philo/contrib/sobol/templates/admin/sobol/search/grappelli_results.html
philo/contrib/sobol/templates/admin/sobol/search/results.html
philo/exceptions.py
philo/middleware.py
philo/models/base.py
philo/models/nodes.py
philo/signals.py
philo/validators.py

index e69de29..7e424ab 100644 (file)
@@ -0,0 +1,6 @@
+DATABASES = {
+    'default': {
+        'ENGINE': 'django.db.backends.sqlite3',
+        'NAME': 'db.sl3'
+    }
+}
\ No newline at end of file
diff --git a/docs/exceptions.rst b/docs/exceptions.rst
new file mode 100644 (file)
index 0000000..679ac77
--- /dev/null
@@ -0,0 +1,5 @@
+Exceptions
+==========
+
+.. automodule:: philo.exceptions
+       :members: MIDDLEWARE_NOT_CONFIGURED, AncestorDoesNotExist, ViewCanNotProvideSubpath, ViewDoesNotProvideSubpaths
\ No newline at end of file
index cfc7136..36470fb 100644 (file)
@@ -13,6 +13,10 @@ Contents:
    
    intro
    models/intro
+   exceptions
+   middleware
+   signals
+   validators
 
 Indices and tables
 ==================
diff --git a/docs/middleware.rst b/docs/middleware.rst
new file mode 100644 (file)
index 0000000..4a5c05f
--- /dev/null
@@ -0,0 +1,5 @@
+Middleware
+==========
+
+.. automodule:: philo.middleware
+       :members:
index 91164a3..0b3d0f9 100644 (file)
@@ -5,7 +5,7 @@ Custom Fields
        :members:
 
 EntityProxyFields
-=================
+-----------------
 
 .. automodule:: philo.models.fields.entities
        :members:
\ No newline at end of file
diff --git a/docs/signals.rst b/docs/signals.rst
new file mode 100644 (file)
index 0000000..8b3da3c
--- /dev/null
@@ -0,0 +1,5 @@
+Signals
+=======
+
+.. automodule:: philo.signals
+       :members:
diff --git a/docs/validators.rst b/docs/validators.rst
new file mode 100644 (file)
index 0000000..f91818b
--- /dev/null
@@ -0,0 +1,5 @@
+Validators
+==========
+
+.. automodule:: philo.validators
+       :members:
index 3ff5c44..a09f385 100644 (file)
@@ -8,7 +8,6 @@ from django.db import models
 from django.forms.models import model_to_dict
 
 from philo.models import TreeEntity, Node, TreeManager, Entity, TargetURLModel
-from philo.validators import RedirectValidator
 
 
 DEFAULT_NAVIGATION_DEPTH = 3
index 2cb1651..dbf37ef 100644 (file)
@@ -4,6 +4,7 @@ from django.conf import settings
 from django.conf.urls.defaults import patterns, url
 from django.contrib import messages
 from django.core.exceptions import ValidationError
+from django.core.validators import URLValidator
 from django.db import models
 from django.http import HttpResponseRedirect, Http404, HttpResponse
 from django.utils import simplejson as json
@@ -15,7 +16,6 @@ from philo.contrib.sobol.utils import HASH_REDIRECT_GET_KEY, URL_REDIRECT_GET_KE
 from philo.exceptions import ViewCanNotProvideSubpath
 from philo.models import MultiView, Page
 from philo.models.fields import SlugMultipleChoiceField
-from philo.validators import RedirectValidator
 
 eventlet = None
 if getattr(settings, 'SOBOL_USE_EVENTLET', False):
@@ -83,7 +83,7 @@ class Search(models.Model):
 
 class ResultURL(models.Model):
        search = models.ForeignKey(Search, related_name='result_urls')
-       url = models.TextField(validators=[RedirectValidator()])
+       url = models.TextField(validators=[URLValidator()])
        
        def __unicode__(self):
                return self.url
index 45135ff..f01eb88 100644 (file)
@@ -28,9 +28,8 @@
 {% block content %}
        <div class="container-grid delete-confirmation">
                {% for search in queryset %}
-               {% if not forloop.first and not forloop.last %}<h1>{{ search.string }}</h1>{% endif %}
                <div class="group tabular">
-                       <h2>{% blocktrans %}Results{% endblocktrans %}</h2>{% comment %}For the favored results, add a class?{% endcomment %}
+                       <h2>{{ search_string }}</h2>
                        <div class="module table">
                                <div class="module thead">
                                        <div class="tr">
index 44d4e7c..24442c7 100644 (file)
@@ -23,9 +23,8 @@
 
 {% block content %}
                {% for search in queryset %}
-               {% if not forloop.first and not forloop.last %}<h1>{{ search.string }}</h1>{% endif %}
                        <fieldset class="module">
-                               <h2>{% blocktrans %}Results{% endblocktrans %}</h2>{% comment %}For the favored results, add a class?{% endcomment %}
+                               <h2>{{ search.string }}</h2>
                                <table>
                                        <thead>
                                                <tr>
index f53083d..9a8908e 100644 (file)
@@ -1,19 +1,20 @@
 from django.core.exceptions import ImproperlyConfigured
 
 
+#: Raised if ``request.node`` is required but not present. For example, this can be raised by :func:`philo.views.node_view`. :data:`MIDDLEWARE_NOT_CONFIGURED` is an instance of :exc:`django.core.exceptions.ImproperlyConfigured`.
 MIDDLEWARE_NOT_CONFIGURED = ImproperlyConfigured("""Philo requires the RequestNode middleware to be installed. Edit your MIDDLEWARE_CLASSES setting to insert 'philo.middleware.RequestNodeMiddleware'.""")
 
 
 class ViewDoesNotProvideSubpaths(Exception):
-       """ Raised by View.reverse when the View does not provide subpaths (the default). """
+       """Raised by :meth:`View.reverse` when the View does not provide subpaths (the default)."""
        silent_variable_failure = True
 
 
 class ViewCanNotProvideSubpath(Exception):
-       """ Raised by View.reverse when the View can not provide a subpath for the supplied arguments. """
+       """Raised by :meth:`View.reverse` when the :class:`View` can not provide a subpath for the supplied arguments."""
        silent_variable_failure = True
 
 
 class AncestorDoesNotExist(Exception):
-       """ Raised by get_path if the root model is not an ancestor of the current model """
+       """Raised by :meth:`TreeModel.get_path` if the root instance is not an ancestor of the current instance."""
        pass
\ No newline at end of file
index 4082d4b..b90067a 100644 (file)
@@ -44,7 +44,7 @@ class LazyNode(object):
 
 
 class RequestNodeMiddleware(object):
-       """Middleware to process the request's path and attach the closest ancestor node."""
+       """Adds a ``node`` attribute, representing the currently-viewed node, to every incoming :class:`HttpRequest` object. This is required by :func:`philo.views.node_view`."""
        def process_request(self, request):
                request.__class__.node = LazyNode()
        
index 0ab9b70..7d78383 100644 (file)
@@ -332,7 +332,7 @@ class TreeManager(models.Manager):
        
        def get_with_path(self, path, root=None, absolute_result=True, pathsep='/', field='slug'):
                """
-               If ``absolute_result`` is ``True``, returns the object at ``path`` (starting at ``root``) or raises a :class:`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).
+               If ``absolute_result`` is ``True``, returns the object at ``path`` (starting at ``root``) or raises an :class:`~django.core.exceptions.ObjectDoesNotExist` 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.
                
@@ -343,7 +343,8 @@ class TreeManager(models.Manager):
                :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.
+               :returns: An instance if ``absolute_result`` is ``True`` or an (instance, remaining_path) tuple otherwise.
+               :raises django.core.exceptions.ObjectDoesNotExist: if no object can be found matching the input parameters.
                
                """
                
index 44a4d31..a225416 100644 (file)
@@ -15,7 +15,6 @@ from philo.exceptions import MIDDLEWARE_NOT_CONFIGURED, ViewCanNotProvideSubpath
 from philo.models.base import TreeEntity, Entity, register_value_model
 from philo.models.fields import JSONField
 from philo.utils import ContentTypeSubclassLimiter, LazyPassthroughAttributeMapper
-from philo.validators import RedirectValidator
 from philo.signals import view_about_to_render, view_finished_rendering
 
 
@@ -133,17 +132,14 @@ class View(Entity):
                
                If ``obj`` is provided, :meth:`get_reverse_params` will be called and the results will be combined with any ``view_name``, ``args``, and ``kwargs`` that may have been passed in.
                
-               This method will raise the following exceptions:
-               
-               - :class:`~philo.exceptions.ViewDoesNotProvideSubpaths` if :attr:`accepts_subpath` is False.
-               - :class:`~philo.exceptions.ViewCanNotProvideSubpath` if a reversal is not possible.
-               
                :param view_name: The name of the view to be reversed.
                :param args: Extra args for reversing the view.
                :param kwargs: A dictionary of arguments for reversing the view.
                :param node: The node whose subpath this is.
                :param obj: An object to be passed to :meth:`get_reverse_params` to generate a view_name, args, and kwargs for reversal.
                :returns: A subpath beyond the node that reverses the view, or an absolute url that reverses the view if a node was passed in.
+               :except philo.exceptions.ViewDoesNotProvideSubpaths: if :attr:`accepts_subpath` is False
+               :except philo.exceptions.ViewCanNotProvideSubpath: if a reversal is not possible.
                
                """
                if not self.accepts_subpath:
@@ -294,8 +290,8 @@ class TargetURLModel(models.Model):
        """An abstract parent class for models which deal in targeting a url."""
        #: An optional :class:`ForeignKey` to a :class:`Node`. If provided, that node will be used as the basis for the redirect.
        target_node = models.ForeignKey(Node, blank=True, null=True, related_name="%(app_label)s_%(class)s_related")
-       #: A :class:`CharField` which may contain an absolute or relative URL. This will be validated with :class:`philo.validators.RedirectValidator`.
-       url_or_subpath = models.CharField(max_length=200, validators=[RedirectValidator()], blank=True, help_text="Point to this url or, if a node is defined and accepts subpaths, this subpath of the node.")
+       #: A :class:`CharField` which may contain an absolute or relative URL, or the name of a node's subpath.
+       url_or_subpath = models.CharField(max_length=200, blank=True, help_text="Point to this url or, if a node is defined and accepts subpaths, this subpath of the node.")
        #: A :class:`~philo.models.fields.JSONField` instance. If the value of :attr:`reversing_parameters` is not None, the :attr:`url_or_subpath` will be treated as the name of a view to be reversed. The value of :attr:`reversing_parameters` will be passed into the reversal as args if it is a list or as kwargs if it is a dictionary. Otherwise it will be ignored.
        reversing_parameters = JSONField(blank=True, help_text="If reversing parameters are defined, url_or_subpath will instead be interpreted as the view name to be reversed.")
        
index 3653c54..558c6fe 100644 (file)
@@ -1,8 +1,60 @@
 from django.dispatch import Signal
 
 
+#: Sent whenever an Entity subclass has been "prepared" -- that is, after the processing necessary to make :mod:`EntityProxyFields <philo.models.fields.entities>` work has been completed. This will fire after :obj:`django.db.models.signals.class_prepared`.
+#:
+#: Arguments that are sent with this signal:
+#: 
+#: ``sender``
+#:     The model class.
 entity_class_prepared = Signal(providing_args=['class'])
+
+#: Sent when a :class:`~philo.models.nodes.View` instance is about to render. This allows you, for example, to modify the ``extra_context`` dictionary used in rendering.
+#:
+#: Arguments that are sent with this signal:
+#:
+#: ``sender``
+#:     The :class:`~philo.models.nodes.View` instance
+#:
+#: ``request``
+#:     The :class:`HttpRequest` instance which the :class:`~philo.models.nodes.View` is rendering in response to.
+#:
+#: ``extra_context``
+#:     A dictionary which will be passed into :meth:`~philo.models.nodes.View.actually_render_to_response`.
 view_about_to_render = Signal(providing_args=['request', 'extra_context'])
+
+#: Sent when a view instance has finished rendering.
+#:
+#: Arguments that are sent with this signal:
+#:
+#: ``sender``
+#:     The :class:`~philo.models.nodes.View` instance
+#:
+#: ``response``
+#:     The :class:`HttpResponse` instance which :class:`~philo.models.nodes.View` view has rendered to.
 view_finished_rendering = Signal(providing_args=['response'])
+
+#: Sent when a :class:`~philo.models.pages.Page` instance is about to render as a string. If the :class:`~philo.models.pages.Page` is rendering as a response, this signal is sent after :obj:`view_about_to_render` and serves a similar function. However, there are situations where a :class:`~philo.models.pages.Page` may be rendered as a string without being rendered as a response afterwards.
+#:
+#: Arguments that are sent with this signal:
+#:
+#: ``sender``
+#:     The :class:`~philo.models.pages.Page` instance
+#:
+#: ``request``
+#:     The :class:`HttpRequest` instance which the :class:`~philo.models.pages.Page` is rendering in response to (if any).
+#:
+#: ``extra_context``
+#:     A dictionary which will be passed into the :class:`Template` context.
 page_about_to_render_to_string = Signal(providing_args=['request', 'extra_context'])
+
+#: Sent when a :class:`~philo.models.pages.Page` instance has just finished rendering as a string. If the :class:`~philo.models.pages.Page` is rendering as a response, this signal is sent before :obj:`view_finished_rendering` and serves a similar function. However, there are situations where a :class:`~philo.models.pages.Page` may be rendered as a string without being rendered as a response afterwards.
+#:
+#: Arguments that are sent with this signal:
+#:
+#: ``sender``
+#:     The :class:`~philo.models.pages.Page` instance
+#:
+#: ``string``
+#:     The string which the :class:`~philo.models.pages.Page` has rendered to.
 page_finished_rendering_to_string = Signal(providing_args=['string'])
\ No newline at end of file
index 144699e..349dd56 100644 (file)
@@ -1,7 +1,6 @@
 import re
 
 from django.core.exceptions import ValidationError
-from django.core.validators import RegexValidator
 from django.template import Template, Parser, Lexer, TOKEN_BLOCK, TOKEN_VAR, TemplateSyntaxError
 from django.utils import simplejson as json
 from django.utils.html import escape, mark_safe
@@ -10,6 +9,7 @@ from django.utils.translation import ugettext_lazy as _
 from philo.utils import LOADED_TEMPLATE_ATTR
 
 
+#: Tags which are considered insecure and are therefore always disallowed by secure :class:`TemplateValidator` instances.
 INSECURE_TAGS = (
        'load',
        'extends',
@@ -18,34 +18,8 @@ INSECURE_TAGS = (
 )
 
 
-class RedirectValidator(RegexValidator):
-       """Based loosely on the URLValidator, but no option to verify_exists"""
-       regex = re.compile(
-               r'^(?:https?://' # http:// or https://
-               r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?|' #domain...
-               r'localhost|' #localhost...
-               r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' # ...or ip
-               r'(?::\d+)?' # optional port
-               r'(?:/?|[/?#]?\S+)|'
-               r'[^?#\s]\S*)$',
-               re.IGNORECASE)
-       message = _(u'Enter a valid absolute or relative redirect target')
-
-
-class URLLinkValidator(RegexValidator):
-       """Based loosely on the URLValidator, but no option to verify_exists"""
-       regex = re.compile(
-               r'^(?:https?://' # http:// or https://
-               r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?|' #domain...
-               r'localhost|' #localhost...
-               r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' # ...or ip
-               r'(?::\d+)?' # optional port
-               r'|)' # also allow internal links
-               r'(?:/?|[/?#]?\S+)$', re.IGNORECASE)
-       message = _(u'Enter a valid absolute or relative redirect target')
-
-
 def json_validator(value):
+       """Validates whether ``value`` is a valid json string."""
        try:
                json.loads(value)
        except Exception, e:
@@ -130,6 +104,14 @@ def linebreak_iter(template_source):
 
 
 class TemplateValidator(object): 
+       """
+       Validates whether a string represents valid Django template code.
+       
+       :param allow: ``None`` or an iterable of tag names which are explicitly allowed. If provided, tags whose names are not in the iterable will cause a ValidationError to be raised if they are used in the template code.
+       :param disallow: ``None`` or an iterable of tag names which are explicitly allowed. If provided, tags whose names are in the iterable will cause a ValidationError to be raised if they are used in the template code. If a tag's name is in ``allow`` and ``disallow``, it will be disallowed.
+       :param secure: If the validator is set to secure, it will automatically disallow the tag names listed in :const:`INSECURE_TAGS`. Defaults to ``True``.
+       
+       """
        def __init__(self, allow=None, disallow=None, secure=True):
                self.allow = allow
                self.disallow = disallow