Commits

James Bennett committed eeac914

Initial Mercurial import

Comments (0)

Files changed (20)

+Thanks for downloading template_utils.
+
+To install it, run the following command inside this directory::
+
+    python setup.py install
+
+Note that this application requires Python 2.3 or later, and a recent
+Subversion checkout of Django; you can obtain Python from
+http://www.python.org/ and Django from http://www.djangoproject.com/.
+Copyright (c) 2007, James Bennett
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+    * Neither the name of the author nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+include INSTALL.txt
+include LICENSE.txt
+include MANIFEST.in
+include README.txt
+recursive-include docs *
+=========================
+Django template utilities
+=========================
+
+This is a small library of template tags and other template-related
+utilities for use with Django_; while Django does a great job in
+general of helping developers avoid repetitive code, there are still a
+few things which tend to be useful or even needed in many different
+types of projects, so this application aims to genericize and bundle
+many of them into one reusable package.
+
+.. _Django: http://www.djangoproject.com/
+
+
+Downloading and installing
+==========================
+
+The easiest download method is a Subversion_ checkout; all of the code
+is maintained in a Subversion repository, and checking the code out
+from the repository makes it easy to handle updates. To download and
+install, simply execute this command from a directory that's on your
+Python path::
+
+    svn co http://django-template-utils.googlecode.com/svn/trunk/template_utils/
+
+This will create a directory called ``template_utils``, and download
+the current code into it. From there, you should be able to add
+``template_utils`` to the ``INSTALLED_APPS`` setting of any Django
+project and have it work. This application provides no models, so you
+don't need to run ``manage.py syncdb`` before using it.
+
+.. _Subversion: http://subversion.tigris.org/
+
+
+Using ``distutils``
+-------------------
+
+Alternatively, you can download a packaged version of the entire
+application and use Python's ``distutils`` to install it::
+
+    wget http://django-template-utils.googlecode.com/files/template_utils-0.4.tar.gz
+    tar zxvf template_utils-0.4.tar.gz
+    cd template_utils-0.4
+    python setup.py install
+
+
+Feature overview
+================
+
+Currently, five main components are bundled into ``template_utils``:
+
+* Template tags for `generic content retrieval`_.
+
+* Template tags for `robust comparison operations`_.
+
+* Template tags for `retrieving public comments`_ (for when a
+  comment-moderation system is in use).
+
+* Template tags for `retrieving and parsing RSS and Atom feeds`_
+  and displaying the results in template.
+
+* A `generic text-to-HTML conversion system`_ with template filter
+  support.
+
+* A system for generating `template context processors`_ which can
+  add arbitrary settings to template contexts.
+  
+* `Node classes`_ for simplifying some common types of custom
+  template tags.
+    
+
+.. _generic content retrieval: docs/generic_content.html
+.. _robust comparison operations: docs/comparison.html
+.. _retrieving public comments: docs/public_comments.html
+.. _retrieving and parsing RSS and Atom feeds: docs/feeds.html
+.. _generic text-to-HTML conversion system: docs/markup.html
+.. _template context processors: docs/context_processors.html
+.. _Node classes: docs/nodes.html

docs/comparison.txt

+===============
+Comparison tags
+===============
+
+
+It's usually a bad idea to have a lot of logic in your templates, but
+occasionally there's a true presentational need for certain
+operations. Django provides an ``ifequal`` tag which tests equality of
+two values, and this tag library supplements it by providing a set of
+tags which can handle other types of comparisons.
+
+To use these tags, you'll need to have ``template_utils`` in your
+``INSTALLED_APPS`` list, and you'll need to have ``{% load comparison
+%}`` in your template.
+
+
+``if_greater``
+==============
+
+Tests whether one value is greater than another.
+
+Syntax::
+
+    {% if_greater [var1] [var2] %}
+    ...do something...
+    {% else %}
+    ...do something else...
+    {% endif_greater %}
+
+The ``else`` clause is optional, and ``var1`` and ``var2`` can be
+template variables or literal values.
+
+Example::
+
+    {% if_greater forloop.counter 1 %}
+    <p>We've been through the loop at least twice.</p>
+    {% else %}
+    <p>This is one of the first two times through the loop.</p>
+    {% endif_greater %}
+
+
+``if_greater_or_equal``
+=======================
+
+Similar to the ``if_greater`` tag, but applies a "greater than or
+equal to" comparison.
+
+Syntax::
+
+    {% if_greater_or_equal [var1] [var2] %}
+    ...do something...
+    {% else %}
+    ...do something else...
+    {% endif_greater_or_equal %}
+
+The ``else`` clause is optional, and ``var1`` and ``var2`` can be
+template variables or literal values.
+
+
+``if_less``
+===========
+
+Similar to the ``if_greater`` tag, but tests whether one value is less
+than another.
+
+Syntax::
+
+    {% if_less [var1] [var2] %}
+    ...do something...
+    {% else %}
+    ...do something else...
+    {% endif_less %}
+
+The ``else`` clause is optional, and ``var1`` and ``var2`` can be
+template variables or literal values.
+
+
+``if_less_or_equal``
+====================
+
+Similar to the ``if_less`` tag, but applies a "less than or equal to"
+comparison.
+
+Syntax::
+
+    {% if_less_or_equal [var1] [var2] %}
+    ...do something...
+    {% else %}
+    ...do something else...
+    {% endif_less_or_equal %}
+
+The ``else`` clause is optional, and ``var1`` and ``var2`` can be
+template variables or literal values.

docs/context_processors.txt

+===============================
+Context processors for settings
+===============================
+
+
+It's often useful for an application to expose the values of certain
+settings -- especially custom settings -- for use in
+templates. Django's ``RequestContext`` and context processors provide
+an easy way to automatically add certain variables to every template
+context, but (for good reasons) Django does not provide a context
+processor which can expose the values of settings.
+
+This module provides a simple function which can generate context
+processors for settings, and a pre-generated context processor for the
+most common use case: media settings.
+
+
+``template_utils.context_processors.settings_processor``
+========================================================
+
+This function takes a list of setting names, and returns a new
+function: a context processor which will add the values of those
+settings to any ``RequestContext`` in which it is used.
+
+So, for example, you might define a file ``context_processors.py`` in
+your application, and place the following in it::
+
+    from template_utils.context_processors import settings_processor
+    my_settings_processor = settings_processor('INTERNAL_IPS', 'SITE_ID')
+
+The function ``my_settings_processor`` would be a context processor
+which added the values of the settings ``INTERNAL_IPS`` and
+``SITE_ID`` to each ``RequestContext`` in which it was used, and then
+you would just need to add
+``myproject.myapp.context_processors.my_settings_processor`` (changing
+the name to suit the actual names of your project and application, of
+course) to your `` TEMPLATE_CONTEXT_PROCESSORS`` setting.
+
+
+``template_utils.context_processors.media``
+===========================================
+
+This is a pre-generated context processor which adds the values of the
+settings ``MEDIA_URL`` and ``ADMIN_MEDIA_PREFIX`` to each
+``RequestContext`` in which it is used.
+
+To enable it, add ``template_utils.context_processors.media`` to your
+``TEMPLATE_CONTEXT_PROCESSORS`` setting.
+============================
+Parsing and displaying feeds
+============================
+
+
+Retrieving content from an RSS or Atom feed and displaying it in a
+page is a fairly common need; many sites, for example, syndicate
+content from affiliates or partners, and simply parse a feed to obtain
+data which is then displayed in a sidebar as "latest headlines" or
+similar.
+
+To facilitate this, ``template_utils`` includes a template tag library
+for parsing and displaying RSS and Atom feeds. To use these tags,
+you'll need to have ``template_utils`` in your ``INSTALLED_APPS``
+list, and you'll need to have ``{% load feeds %}`` in your template.
+
+
+``include_feed``
+================
+
+Parse an RSS or Atom feed and render a given number of its items
+into HTML.
+
+It is **highly** recommended that you use `Django's template
+fragment caching`_ to cache the output of this tag for a
+reasonable amount of time (e.g., one hour); polling a feed too
+often is impolite, wastes bandwidth and may lead to the feed
+provider banning your IP address.
+
+.. _Django's template fragment caching: http://www.djangoproject.com/documentation/cache/#template-fragment-caching
+
+Arguments should be:
+
+1. The URL of the feed to parse.
+
+2. The number of items to render (if not supplied, renders all
+   items in the feed).
+   
+3. The name of a template to use for rendering the results into HTML.
+
+The template used to render the results will receive two variables:
+
+``items``
+    A list of dictionaries representing feed items, each with 'title',
+    'summary', 'link' and 'date' members.
+
+``feed``
+    The feed itself, for pulling out arbitrary attributes.
+
+Requires the Universal Feed Parser, which can be obtained at
+http://feedparser.org/. See `its documentation`_ for details of the
+parsed feed object.
+
+.. _its documentation: http://feedparser.org/docs/
+
+Syntax::
+
+    {% include_feed [feed_url] [num_items] [template_name] %}
+
+Example::
+
+    {% include_feed "http://www2.ljworld.com/rss/headlines/" 10 feed_includes/ljworld_headlines.html %}
+
+
+``parse_feed``
+==============
+
+Parses a given feed and returns the result in a given context
+variable.
+
+It is **highly** recommended that you use `Django's template
+fragment caching`_ to cache the output of this tag for a
+reasonable amount of time (e.g., one hour); polling a feed too
+often is impolite, wastes bandwidth and may lead to the feed
+provider banning your IP address.
+
+.. _Django's template fragment caching: http://www.djangoproject.com/documentation/cache/#template-fragment-caching
+
+Arguments should be:
+
+1. The URL of the feed to parse.
+
+2. The name of a context variable in which to return the result.
+
+Requires the Universal Feed Parser, which can be obtained at
+http://feedparser.org/. See `its documentation`_ for details of the
+parsed feed object.
+
+.. _its documentation: http://feedparser.org/docs/
+
+Syntax::
+
+    {% parse_feed [feed_url] as [varname] %}
+
+Example::
+
+    {% parse_feed "http://www2.ljworld.com/rss/headlines/" as ljworld_feed %}
+
+

docs/generic_content.txt

+=========================
+Generic content retrieval
+=========================
+
+A common task in Django applications is writing template tags which
+retrieve particular pieces of content; for example, a weblog might
+need a "get latest entries" tag, while a news site would want "get
+latest stories", and so on. Writing these tags to work only with
+specific models results in duplication of code across applications, so
+``template_utils`` provides a set of template tags which can retrieve
+content from any installed model, according to certain criteria.
+
+To use these tags, you'll need to have ``template_utils`` in your
+``INSTALLED_APPS`` list, and you'll need to have ``{% load
+generic_content %}`` in your template.
+
+
+``get_latest_object``
+=====================
+
+This tag retrieves the "latest" object from a model, according to the
+model's default ordering.
+
+Syntax::
+
+    {% get_latest_object [app_name].[model_name] as [varname] %}
+
+So, for example, to retrieve the latest ``FreeComment`` posted::
+
+    {% get_latest_object comments.freecomment as latest_comment %}
+
+
+``get_latest_objects``
+======================
+
+Similar to ``get_latest_object``, but takes an additional argument --
+a number -- and returns a list of that many objects.
+
+Syntax::
+
+    {% get_latest_objects [app_name].[model_name] [num] as [varname] %}
+
+Continuing with comments as an example::
+
+    {% get_latest_objects comments.freecomment 5 as latest_comments %}
+
+
+``get_random_object``
+=====================
+
+Retrieves a single object, randomly selected, from a model.
+
+Syntax::
+
+    {% get_random_object [app_name].[model_name] as [varname] %}
+
+So to retrieve a random comment::
+
+    {% get_random_object comments.freecomment as random_comment %}
+
+
+``get_random_objects``
+======================
+
+Much like ``get_latest_objects``, this tag returns a given number of
+objects (randomly selected).
+
+Syntax::
+
+    {% get_random_object [app_name].[model_name] [num] as [varname] %}
+
+
+``retrieve_object``
+===================
+
+Retrieves a single specific object by primary-key lookup.
+
+Syntax::
+
+    {% retrieve_object [app_name].[model_name] [primary_key] as [varname] %}
+
+For example, if you wanted to retrieve a flatpage with ``id`` 12::
+
+    {% retrieve_object flatpages.flatpage 12 as my_flatpage %}
+
+
+Performing additional filtering
+===============================
+
+The tags described above all use the default manager of the model
+specified in the tag call, and by default do not perform any filtering
+on the default ``QuerySet`` obtained from that manager. However, as a
+convenience for situations where it is desirable or necessary to
+filter the objects (e.g., to retrieve the latest public comments, or
+latest objects with a particular status), two mechanisms are supplied:
+one broad and one fine-grained.
+
+The broader method involves adding an extra setting to your Django
+settings file: ``GENERIC_CONTENT_LOOKUP_KWARGS``. The value of this
+setting should be a dictionary with keys corresponding to names of
+models (e.g., "comments.freecomment", "auth.user", etc.) and values
+containing dictionaries of valid database lookup arguments for those
+models. When this setting exists and contains an entry for the model
+whose objects are being queried, ``get_latest_object``,
+``get_latest_objects``, ``get_random_object`` and
+``get_random_objects`` will all read the lookup arguments and apply
+them to the model's default ``QuerySet``.
+
+So, for example, if you wanted these tags to only retrieve comments
+whose ``is_public`` field is set to ``True`` (useful if you're doing
+comment moderation), you'd add this to your settings file:
+
+    GENERIC_CONTENT_LOOKUP_KWARGS = {
+        'comments.freecomment': { 'is_public__exact': True }
+    }
+
+Because it does a primary-key lookup (and thus it's safe to assume you
+know precisely which object you want), ``retrieve_object`` does not
+filter in any situation.
+
+The finer-grained method involves subclassing the ``Node`` class
+common to most of the tags listed above; they're instances of
+``template_utils.templatetags.generic_content.GenericContentNode``,
+which is documented in the file ``nodes.txt`` in this directory.
+===============================
+Generic text-to-HTML conversion
+===============================
+
+
+It's extremely common to use some form of text-to-HTML conversion
+utility, such as Markdown or Textile, to allow plain text entered by
+staff members or users to be transformed into HTML for output, and not
+terribly uncommon to even store that generated HTML in the database,
+by having it converted when a model instance is saved. And, in the
+bundled ``django.contrib.markup``, Django provides template filters
+which can be used for Markdown, Textile and reStructuredText, making
+this even easier to do.
+
+The downside to all of this is that your code becomes coupled to the
+particular system you're using: if you use Markdown, for example, you
+end up applying the ``markdown`` filter in all of your templates, or
+hard-coding a call to ``markdown.markdown`` in a model's ``save``
+method. And although most of the popular text-to-HTML converters
+support lots of useful options, Django's template filters don't really
+offer a way to take advantage of them.
+
+This module provides a generic ``MarkupFormatter`` class which aims to
+solve these problems; it is designed to allow the use of any
+text-to-HTML converter with minimal coupling of your code.
+
+
+Basic usage
+===========
+
+The ``MarkupFormatter`` class handles text-to-HTML conversion by using
+"filter functions"; by default, three filters are available:
+``markdown``, ``textile`` and ``restructuredtext``, which apply those
+systems. You can, however, enable the use of any system you like by
+writing a new filter function and registering it with an instance of
+``MarkupFormatter``.
+
+In the simplest case, converting text to HTML works by creating an
+instance of ``MarkupFormatter``, then calling it, passing a string and
+the name of a filter to use::
+
+    from template_utils.markup import MarkupFormatter
+    formatter = MarkupFormatter()
+    my_string = """Lorem ipsum dolor sit amet.
+    
+    Consectetuer adipiscing elit."""
+    my_html = formatter(my_string, filter_name='markdown')
+
+You can also pass arbitrary keyword arguments, and they will be handed
+off to the filter function; for example, to use Markdown's "safe mode"
+(which strips raw HTML before processing), you could call it like
+this::
+
+    my_html = formatter(my_string, filter_name='markdown', safe_mode=True)
+
+The ``safe_mode=True`` argument will be handed directly to the
+``markdown`` filter.
+
+To add a new filter, simply define it as a function; for example, to
+achieve the same effect as Django's built-in ``escape`` and
+``linebreaks`` template filters, you could define this function::
+
+    def escape_linebreaks(text, **kwargs):
+        from django.utils.html import escape, linebreaks
+        return linebreaks(escape(text))
+
+Filter functions must accept the string of text to convert as their
+first postional argument, and should accept ``**kwargs`` even if they
+don't do anything with it.
+
+To register the filter, simply call the ``register`` method of an
+instance of ``MarkupFormatter``::
+
+    formatter.register('escape_linebreaks', escape_linebreaks)
+
+The arguments to ``register`` are:
+
+1. A string to use as the name of the filter.
+
+2. The filter function.
+
+Once the filter is registered, it can be used the same as the built-in
+filters::
+
+    my_html = formatter(my_string, filter_name='escape_linebreaks')
+
+**Note:** ``template_utils.markup`` creates an instance of
+``MarkupFormatter``, called ``formatter``; unless you have a need for
+multiple instances of ``MarkupFormatter``, it's probably best to
+import and use that instance (by doing ``from template_utils.markup
+import formatter``). This allows the single instance to be used in
+multiple places, and avoids the need to register any custom filter
+functions with multiple instances.
+
+
+Specifying default behavior in a Django setting
+===============================================
+
+
+To cut down on repetitive typing, ``MarkupFormatter`` can read a
+default filter name and keyword arguments from the Django setting
+``MARKUP_FILTER``; if you specify this setting, it should be a tuple
+with two elements:
+
+1. The name of a filter to use.
+
+2. A dictionary of keyword arguments to pass to it.
+
+So to have Markdown with "safe mode" be the default behavior, you
+would add the following to your settings file::
+
+    MARKUP_FILTER = ('markdown', { 'safe_mode': True })
+
+When you've specified ``MARKUP_FILTER`` in this way, you can perform
+text-to-html conversion just by passing in the string to convert, with
+no other arguments::
+
+    my_html = formatter(my_string)
+
+The filter function specified in ``MARKUP_FILTER`` does not have to be
+in the default filter set; so long as you register a filter of the
+correct name before trying to do any text conversion, it will work.
+
+To have the default behavior apply no conversion at all, specify
+`None` as the filter name and an empty dictionary of keyword
+arguments::
+
+    MARKUP_FILTER = (None, {})
+
+**Note:** if you pass the ``filter_name`` keyword argument when
+performing text-to-HTML conversion, ``MarkupFormatter`` will
+completely ignore the ``MARKUP_FILTER`` setting (it won't even try to
+import the setting), so be sure to explicitly pass any keyword
+arguments you want to use. ``MarkupFormatter`` behaves this way so
+that it can be used stand-alone, without the need to configure or even
+install Django.
+
+
+Applying text-to-HTML conversion in templates
+=============================================
+
+A filter library called ``generic_markup`` is also included here, and
+it contains the filter ``apply_markup``, which imports
+``template_utils.markup.formatter`` and applies it to the text the
+filter is attached to::
+
+    {% load generic_markup %}
+    {{ some_text|apply_markup %}
+
+By itself, ``apply_markup`` will use the default behavior specified in
+``MARKUP_FILTER``, but it does accept a single argument which allows
+you to override the filter name::
+
+    {{ some_text|apply_markup:"textile" }}
+
+This would have the same effect as the following Python code::
+
+    from template_utils.markup import formatter
+    print formatter(some_text, filter_name='textile')
+
+But remember that this will override *all* use of ``MARKUP_FILTER``,
+so any custom keyword arguments you specified there will not be used
+when you pass an argument to ``apply_markup``.
+
+Also, note that on Django trunk templates automatically escape
+variable output by default; the ``apply_markup`` filter will mark its
+output as "safe" in order to avoid escaping of the generated HTML.
+============================
+Custom template node classes
+============================
+
+
+The core of Django's template system is the class
+``django.template.Node``; a Django template is, ultimately, a list of
+``Node`` instances, and it is the output of each ``Node``'s
+``render()`` method which becomes the final template output.
+
+As such, most custom template tags involve subclasses of ``Node``, and
+many classes of custom tags can be greatly simplified by providing an
+intermediate ``Node`` class which implements a generic form of the
+desired behavior. Included in ``template_utils.nodes`` are two
+``Node`` subclasses which follow that pattern.
+
+
+``template_utils.nodes.ContextUpdatingNode``
+============================================
+
+This is a ``Node`` subclass which simplifies the common case of
+writing a custom tag to add some values to the current template
+context.
+
+To use, import and subclass it, and -- rather than defining
+``render()`` as with a standard ``Node`` subclass -- define a method
+named ``get_content()``. This method should accept a ``Context``
+instance as its first positional argument, and should return a
+dictionary; the keys and values in that dictionary will be added to
+the context as new variables and values.
+
+
+``template_utils.nodes.GenericContentNode``
+===========================================
+
+Base Node class for retrieving objects from any model.
+
+By itself, this class will retrieve a number of objects from a
+particular model (specified by an "app_name.model_name" string)
+and store them in a specified context variable (these are the
+``num``, ``model`` and ``varname`` arguments to the constructor,
+respectively), but is also intended to be subclassed for
+customization.
+
+There are two ways to add extra bits to the eventual database
+lookup:
+
+1. Add the setting ``GENERIC_CONTENT_LOOKUP_KWARGS`` to your
+   settings file; this should be a dictionary whose keys are
+   "app_name.model_name" strings corresponding to models, and whose
+   values are dictionaries of keyword arguments which will be
+   passed to ``filter()``.
+
+2. Subclass and override ``_get_query_set``; all that's expected
+   is that it will return a ``QuerySet`` which will be used to
+   retrieve the object(s). The default ``QuerySet`` for the
+   specified model (filtered as described above) will be available
+   as ``self.query_set`` if you want to work with it.
+
+For finer-grained flexibility, override ``__init__()`` to control the
+manner in which lookup arguments are determined.
+from distutils.core import setup
+
+setup(name='template_utils',
+      version='0.4p2',
+      description='Template-related utilities for Django applications',
+      author='James Bennett',
+      author_email='james@b-list.org',
+      url='http://code.google.com/p/django-template-utils/',
+      packages=['template_utils', 'template_utils.templatetags'],
+      classifiers=['Development Status :: 4 - Beta',
+                   'Environment :: Web Environment',
+                   'Intended Audience :: Developers',
+                   'License :: OSI Approved :: BSD License',
+                   'Operating System :: OS Independent',
+                   'Programming Language :: Python',
+                   'Topic :: Utilities'],
+      )

template_utils/__init__.py

Empty file added.

template_utils/context_processors.py

+"""
+A generic function for generating context processors, and a processor
+which adds media-specific settings to each ``RequestContext``.
+
+"""
+
+def settings_processor(*settings_list):
+    """
+    Generates and returns a context processor function which will read
+    the values of all the settings passed in and return them in each
+    ``RequestContext`` in which it is applied.
+    
+    For example::
+    
+        my_settings_processor = settings_processor('INTERNAL_IPS', 'SITE_ID')
+    
+    ``my_settings_processor`` would then be a valid context processor
+    which would return the values of the settings ``INTERNAL_IPS`` and
+    ``SITE_ID`` in each ``RequestContext`` in which it was applied.
+    
+    """
+    def _processor(request):
+        from django.conf import settings
+        settings_dict = {}
+        for setting_name in settings_list:
+            settings_dict[setting_name] = getattr(settings, setting_name)
+        return settings_dict
+    return _processor
+
+media = settings_processor('ADMIN_MEDIA_PREFIX', 'MEDIA_URL')
+media.__doc__ = """A context processor which adds the values of the settings
+``ADMIN_MEDIA_PREFIX`` and ``MEDIA_URL`` to a ``RequestContext``."""

template_utils/markup.py

+"""
+Utilities for text-to-HTML conversion.
+
+"""
+
+
+def textile(text, **kwargs):
+    """
+    Applies Textile conversion to a string, and returns the HTML.
+    
+    This is simply a pass-through to the ``textile`` template filter
+    included in ``django.contrib.markup``, which works around issues
+    PyTextile has with Unicode strings. If you're not using Django but
+    want to use Textile with ``MarkupFormatter``, you'll need to
+    supply your own Textile filter.
+    
+    """
+    from django.contrib.markup.templatetags.markup import textile
+    return textile(text)
+
+def markdown(text, **kwargs):
+    """
+    Applies Markdown conversion to a string, and returns the HTML.
+    
+    """
+    import markdown
+    return markdown.markdown(text, **kwargs)
+
+def restructuredtext(text, **kwargs):
+    """
+    Applies reStructuredText conversion to a string, and returns the
+    HTML.
+    
+    """
+    from docutils import core
+    parts = core.publish_parts(source=text,
+                               writer_name='html4css1',
+                               **kwargs)
+    return parts['fragment']
+
+DEFAULT_MARKUP_FILTERS = {
+    'textile': textile,
+    'markdown': markdown,
+    'restructuredtext': restructuredtext
+    }
+
+
+class MarkupFormatter(object):
+    """
+    Generic markup formatter which can handle multiple text-to-HTML
+    conversion systems.
+
+    
+    Overview
+    ========
+    
+    Conversion is handled by filter functions registered with an
+    instance; a set of default filters is provided which cover
+    Markdown, reStructuredText and Textile (though using one of these
+    requires the appropriate module to be available on your system --
+    e.g., using the reST filter requires you to have ``docutils``
+    installed).
+    
+    New filters can be added by registering them with an instance;
+    simply define a function which performs the conversion you want,
+    and use the ``register`` method to add it; ``register`` expects
+    two arguments:
+    
+    1. The name to associate with the filter.
+    
+    2. The actual filter function.
+    
+    So, for example, you might define a new filter function called
+    ``my_filter``, and register it like so::
+    
+        formatter = MarkupFormatter()
+        formatter.register('my_filter', my_filter)
+    
+    Instances are callable, so applying the conversion to a string is
+    simple::
+    
+        my_html = formatter(my_string, filter_name='my_filter')
+    
+    The filter to use for conversion is determined in either of two
+    ways:
+    
+    1. If the keyword argument ``filter_name`` is supplied, it will be
+       used as the filter name.
+    
+    2. Absent an explicit argument, the filter name will be taken from
+       the ``MARKUP_FILTER`` setting in your Django settings file (see
+       below).
+    
+    Additionally, arbitrary keyword arguments can be supplied, and
+    they will be passed on to the filter function.
+    
+    
+    Reading default bahavior from a Django setting
+    ==============================================
+    
+    The Django setting ``MARKUP_FILTER`` can be used to specify
+    default behavior; if used, its value should be a 2-tuple:
+    
+    * The first element should be the name of a filter.
+    
+    * The second element should be a dictionary to use as keyword
+      arguments for that filter.
+    
+    So, for example, to have the default behavior apply Markdown with
+    safe mode enabled, you would add this to your Django settings
+    file::
+    
+        MARKUP_FILTER = ('markdown', { 'safe_mode': True })
+    
+    The filter named in this setting does not have to be from the
+    default set; as long as you register a filter of that name before
+    trying to use the formatter, it will work.
+    
+    To have the default behavior apply no conversion whatsoever, set
+    ``MARKUP_FILTER`` like so::
+    
+        MARKUP_FILTER = (None, {})
+    
+    When the ``filter_name`` keyword argument is supplied, the
+    ``MARKUP_FILTER`` setting is ignored entirely -- neither a filter
+    name nor any keyword arguments will be read from it. This means
+    that, by always supplying ``filter_name`` explicitly, it is
+    possible to use this formatter without configuring or even
+    installing Django.
+
+
+    Django and template autoescaping
+    ================================
+
+    Django's template system defaults to escaping the output of
+    template variables, which can interfere with functions intended to
+    return HTML. ``MarkupFormatter`` does not in any way tamper with
+    Django's autoescaping, so pasing the results of formatting
+    directly to a Django template will result in that text being
+    escaped.
+
+    If you need to use ``MarkupFormatter`` for items which will be
+    passed to a Django template as variables, use the function
+    ``django.utils.safestring.mark_safe`` to tell Django's template
+    system not to escape that text.
+    
+    For convenience, a Django template filter is included (in
+    ``templatetags/generic_markup.py``) which applies
+    ``MarkupFormatter`` to a string and marks the result as not
+    requiring autoescaping.
+    
+    
+    Examples
+    ========
+    
+    Using the default behavior, with the filter name and arguments
+    taken from the ``MARKUP_FILTER`` setting::
+    
+        formatter = MarkupFormatter()
+        my_string = 'Lorem ipsum dolor sit amet.\n\nConsectetuer adipiscing elit.'
+        my_html = formatter(my_string)
+    
+    Explicitly naming the filter to use::
+    
+        my_html = formatter(my_string, filter_name='markdown')
+    
+    Passing keyword arguments::
+    
+        my_html = formatter(my_string, filter_name='markdown', safe_mode=True)
+    
+    Perform no conversion (return the text as-is)::
+    
+        my_html = formatter(my_string, filter_name=None)
+    
+    """
+    def __init__(self):
+        self._filters = {}
+        for filter_name, filter_func in DEFAULT_MARKUP_FILTERS.items():
+            self.register(filter_name, filter_func)
+    
+    def register(self, filter_name, filter_func):
+        """
+        Registers a new filter for use.
+        
+        """
+        self._filters[filter_name] = filter_func
+    
+    def __call__(self, text, **kwargs):
+        """
+        Applies text-to-HTML conversion to a string, and returns the
+        HTML.
+        
+        """
+        if 'filter_name' in kwargs:
+            filter_name = kwargs['filter_name']
+            del kwargs['filter_name']
+            filter_kwargs = {}
+        else:
+            from django.conf import settings
+            filter_name, filter_kwargs = settings.MARKUP_FILTER
+        if filter_name is None:
+            return text
+        if filter_name not in self._filters:
+            raise ValueError("'%s' is not a registered markup filter. Registered filters are: %s." % (filter_name,
+                                                                                                       ', '.join(self._filters.iterkeys())))
+        filter_func = self._filters[filter_name]
+        filter_kwargs.update(**kwargs)
+        return filter_func(text, **filter_kwargs)
+
+
+# Unless you need to have multiple instances of MarkupFormatter lying
+# around, or want to subclass it, the easiest way to use it is to
+# import this instance.
+
+formatter = MarkupFormatter()

template_utils/nodes.py

+"""
+Subclass of ``template.Node`` for easy context updating.
+
+"""
+
+from django.db.models import get_model
+from django.conf import settings
+from django import template
+
+
+class ContextUpdatingNode(template.Node):
+    """
+    Node that updates the context with certain values.
+    
+    Subclasses should define ``get_content()``, which should return a
+    dictionary to be added to the context.
+    
+    """
+    def render(self, context):
+        context.update(self.get_content(context))
+        return ''
+
+    def get_content(self, context):
+        raise NotImplementedError
+
+
+class GenericContentNode(ContextUpdatingNode):
+    """
+    Base Node class for retrieving objects from any model.
+
+    By itself, this class will retrieve a number of objects from a
+    particular model (specified by an "app_name.model_name" string)
+    and store them in a specified context variable (these are the
+    ``num``, ``model`` and ``varname`` arguments to the constructor,
+    respectively), but is also intended to be subclassed for
+    customization.
+
+    There are two ways to add extra bits to the eventual database
+    lookup:
+
+    1. Add the setting ``GENERIC_CONTENT_LOOKUP_KWARGS`` to your
+       settings file; this should be a dictionary whose keys are
+       "app_name.model_name" strings corresponding to models, and whose
+       values are dictionaries of keyword arguments which will be
+       passed to ``filter()``.
+
+    2. Subclass and override ``_get_query_set``; all that's expected
+       is that it will return a ``QuerySet`` which will be used to
+       retrieve the object(s). The default ``QuerySet`` for the
+       specified model (filtered as described above) will be available
+       as ``self.query_set`` if you want to work with it.
+    
+    """
+    def __init__(self, model, num, varname):
+        self.num = num
+        self.varname = varname
+        lookup_dict = getattr(settings, 'GENERIC_CONTENT_LOOKUP_KWARGS', {})
+        self.model = get_model(*model.split('.'))
+        if self.model is None:
+            raise template.TemplateSyntaxError("Generic content tag got invalid model: %s" % model)
+        self.query_set = self.model._default_manager.filter(**lookup_dict.get(model, {}))
+        
+    def _get_query_set(self):
+        return self.query_set
+    
+    def get_content(self, context):
+        query_set = self._get_query_set()
+        if self.num == 1:
+            result = query_set[0]
+        else:
+            result = list(query_set[:self.num])
+        return { self.varname: result }

template_utils/templatetags/__init__.py

Empty file added.

template_utils/templatetags/comparison.py

+"""
+Tags for performing basic value comparisons in templates.
+
+"""
+
+
+from django import template
+
+
+COMPARISON_DICT = {
+    'less': lambda x: x < 0,
+    'less_or_equal': lambda x: x <= 0,
+    'greater_or_equal': lambda x: x >= 0,
+    'greater': lambda x: x > 0,
+    }
+
+
+class ComparisonNode(template.Node):
+    def __init__(self, var1, var2, comparison, nodelist_true, nodelist_false):
+        self.var1 = template.Variable(var1)
+        self.var2 = template.Variable(var2)
+        self.comparison = comparison
+        self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
+    
+    def render(self, context):
+        try:
+            result = cmp(self.var1.resolve(context),
+                         self.var2.resolve(context))
+            if COMPARISON_DICT[self.comparison](result):
+                return self.nodelist_true.render(context)
+        # If either variable fails to resolve, return nothing.
+        except template.VariableDoesNotExist:
+            return ''
+        # If the types don't permit comparison, return nothing.
+        except TypeError:
+            return ''
+        return self.nodelist_false.render(context)
+
+
+def do_comparison(parser, token):
+    """
+    Compares two values.
+    
+    Syntax::
+    
+        {% if_[comparison] [var1] [var2] %}
+        ...
+        {% else %}
+        ...
+        {% endif_[comparison] %}
+
+    The {% else %} block is optional, and ``var1`` and ``var2`` may be
+    variables or literal values.
+    
+    Supported comparisons are ``less``, ``less_or_equal``, ``greater``
+    and ``greater_or_equal``.
+    
+    Examples::
+    
+        {% if_less some_object.id 3 %}
+        <p>{{ some_object }} has an id less than 3.</p>
+        {% endif_less %}
+    
+        {% if_greater_or_equal forloop.counter 4 %}
+        <p>This is at least the fifth time through the loop.</p>
+        {% else %}
+        <p>This is one of the first four trips through the loop.</p>
+        {% endif_greater_or_equal %}
+    
+    """
+    bits = token.contents.split()
+    if len(bits) != 3:
+        raise template.TemplateSyntaxError("'%s' tag takes two arguments" % bits[0])
+    end_tag = 'end' + bits[0]
+    nodelist_true = parser.parse(('else', end_tag))
+    token = parser.next_token()
+    if token.contents == 'else':
+        nodelist_false = parser.parse((end_tag,))
+        parser.delete_first_token()
+    else:
+        nodelist_false = template.NodeList()
+    comparison = bits[0].split('if_')[1]
+    return ComparisonNode(bits[1], bits[2], comparison, nodelist_true, nodelist_false)
+
+register = template.Library()
+for tag_name in ('if_less', 'if_less_or_equal', 'if_greater_or_equal', 'if_greater'):
+    register.tag(tag_name, do_comparison)

template_utils/templatetags/feeds.py

+"""
+Tags which can retrieve and parse RSS and Atom feeds, and return the
+results for use in templates.
+
+Based, in part, on the original idea by user baumer1122 and posted to
+djangosnippets at http://www.djangosnippets.org/snippets/311/
+
+"""
+
+import datetime
+import feedparser
+from django import template
+from django.template.loader import render_to_string
+
+from template_utils.nodes import ContextUpdatingNode
+
+
+class FeedIncludeNode(template.Node):
+    def __init__(self, feed_url, template_name, num_items=None):
+        self.feed_url = template.Variable(feed_url)
+        self.num_items = num_items
+        self.template_name = template_name
+
+    def render(self, context):
+        feed_url = self.feed_url.resolve(context)
+        feed = feedparser.parse(feed_url)
+        items = []
+        num_items = int(self.num_items) or len(feed['entries'])
+        for i in range(num_items):
+            pub_date = feed['entries'][i].updated_parsed
+            published = datetime.date(pub_date[0], pub_date[1], pub_date[2])
+            items.append({ 'title': feed['entries'][i].title,
+                           'summary': feed['entries'][i].summary,
+                           'link': feed['entries'][i].link,
+                           'date': published })
+        return render_to_string(self.template_name, { 'items': items,
+                                                      'feed': feed })
+
+
+class FeedParserNode(ContextUpdatingNode):
+    def __init__(self, feed_url, varname):
+        self.feed_url = template.Variable(feed_url)
+        self.varname = varname
+    
+    def get_content(self, context):
+        feed_url = self.feed_url.resolve(context)
+        return { self.varname: feedparser.parse(feed_url) }
+
+
+def do_include_feed(parser, token):
+    """
+    Parse an RSS or Atom feed and render a given number of its items
+    into HTML.
+    
+    It is **highly** recommended that you use `Django's template
+    fragment caching`_ to cache the output of this tag for a
+    reasonable amount of time (e.g., one hour); polling a feed too
+    often is impolite, wastes bandwidth and may lead to the feed
+    provider banning your IP address.
+    
+    .. _Django's template fragment caching: http://www.djangoproject.com/documentation/cache/#template-fragment-caching
+    
+    Arguments should be:
+    
+    1. The URL of the feed to parse.
+    
+    2. The number of items to render (if not supplied, renders all
+       items in the feed).
+       
+    3. The name of a template to use for rendering the results into HTML.
+    
+    The template used to render the results will receive two variables:
+    
+    ``items``
+        A list of dictionaries representing feed items, each with
+        'title', 'summary', 'link' and 'date' members.
+    
+    ``feed``
+        The feed itself, for pulling out arbitrary attributes.
+    
+    Requires the Universal Feed Parser, which can be obtained at
+    http://feedparser.org/. See `its documentation`_ for details of the
+    parsed feed object.
+    
+    .. _its documentation: http://feedparser.org/docs/
+    
+    Syntax::
+    
+        {% include_feed [feed_url] [num_items] [template_name] %}
+    
+    Example::
+    
+        {% include_feed "http://www2.ljworld.com/rss/headlines/" 10 feed_includes/ljworld_headlines.html %}
+    
+    """
+    bits = token.contents.split()
+    if len(bits) == 3:
+        return FeedIncludeNode(feed_url=bits[1], template_name=bits[2])
+    elif len(bits) == 4:
+        return FeedIncludeNode(feed_url=bits[1], num_items=bits[2], template_name=bits[3])
+    else:
+        raise template.TemplateSyntaxError("'%s' tag takes either two or three arguments" % bits[0])
+
+def do_parse_feed(parser, token):
+    """
+    Parses a given feed and returns the result in a given context
+    variable.
+    
+    It is **highly** recommended that you use `Django's template
+    fragment caching`_ to cache the output of this tag for a
+    reasonable amount of time (e.g., one hour); polling a feed too
+    often is impolite, wastes bandwidth and may lead to the feed
+    provider banning your IP address.
+    
+    .. _Django's template fragment caching: http://www.djangoproject.com/documentation/cache/#template-fragment-caching
+    
+    Arguments should be:
+    
+    1. The URL of the feed to parse.
+    
+    2. The name of a context variable in which to return the result.
+    
+    Requires the Universal Feed Parser, which can be obtained at
+    http://feedparser.org/. See `its documentation`_ for details of the
+    parsed feed object.
+    
+    .. _its documentation: http://feedparser.org/docs/
+    
+    Syntax::
+    
+        {% parse_feed [feed_url] as [varname] %}
+    
+    Example::
+    
+        {% parse_feed "http://www2.ljworld.com/rss/headlines/" as ljworld_feed %}
+    
+    """
+    bits = token.contents.split()
+    if len(bits) != 4:
+        raise template.TemplateSyntaxError(u"'%s' tag takes three arguments" % bits[0])
+    return FeedParserNode(bits[1], bits[3])
+
+register = template.Library()
+register.tag('include_feed', do_include_feed)
+register.tag('parse_feed', do_parse_feed)

template_utils/templatetags/generic_content.py

+"""
+Template tags which can do retrieval of content from any model.
+
+"""
+
+
+from django import template
+from django.db.models import get_model
+
+from template_utils.nodes import ContextUpdatingNode, GenericContentNode
+
+
+class RandomObjectsNode(GenericContentNode):
+    """
+    A subclass of ``GenericContentNode`` which overrides
+    ``_get_query_set`` to apply random ordering.
+    
+    """
+    def _get_query_set(self):
+        return self.query_set.order_by('?')
+
+
+class RetrieveObjectNode(ContextUpdatingNode):
+    """
+    ``Node`` subclass which retrieves a single object -- by
+    primary-key lookup -- from a given model.
+
+    Because this is a primary-key lookup, it is assumed that no other
+    filtering is needed; hence, the settings-based filtering performed
+    by ``GenericContentNode`` is not used here.
+    
+    """
+    def __init__(self, model, pk, varname):
+        self.pk = template.Variable(pk)
+        self.varname = varname
+        self.model = get_model(*model.split('.'))
+        if self.model is None:
+            raise template.TemplateSyntaxError("Generic content tag got invalid model: %s" % model)
+    
+    def get_content(self, context):
+        return { self.varname: self.model._default_manager.get(pk=self.pk.resolve(context))}
+
+
+def do_latest_object(parser, token):
+    """
+    Retrieves the latest object from a given model, in that model's
+    default ordering, and stores it in a context variable.
+    
+    Syntax::
+    
+        {% get_latest_object [app_name].[model_name] as [varname] %}
+    
+    Example::
+    
+        {% get_latest_object comments.freecomment as latest_comment %}
+    
+    """
+    bits = token.contents.split()
+    if len(bits) != 4:
+        raise template.TemplateSyntaxError("'%s' tag takes three arguments" % bits[0])
+    if bits [2] != 'as':
+        raise template.TemplateSyntaxError("second argument to '%s' tag must be 'as'" % bits[0])
+    return GenericContentNode(bits[1], 1, bits[3])
+
+
+def do_latest_objects(parser, token):
+    """
+    Retrieves the latest ``num`` objects from a given model, in that
+    model's default ordering, and stores them in a context variable.
+    
+    Syntax::
+    
+        {% get_latest_objects [app_name].[model_name] [num] as [varname] %}
+    
+    Example::
+    
+        {% get_latest_objects comments.freecomment 5 as latest_comments %}
+    
+    """
+    bits = token.contents.split()
+    if len(bits) != 5:
+        raise template.TemplateSyntaxError("'%s' tag takes four arguments" % bits[0])
+    if bits [3] != 'as':
+        raise template.TemplateSyntaxError("third argument to '%s' tag must be 'as'" % bits[0])
+    return GenericContentNode(bits[1], bits[2], bits[4])
+
+def do_random_object(parser, token):
+    """
+    Retrieves a random object from a given model, and stores it in a
+    context variable.
+    
+    Syntax::
+    
+        {% get_random_object [app_name].[model_name] as [varname] %}
+    
+    Example::
+    
+        {% get_random_object comments.freecomment as random_comment %}
+    
+    """
+    bits = token.contents.split()
+    if len(bits) != 4:
+        raise template.TemplateSyntaxError("'%s' tag takes three arguments" % bits[0])
+    if bits [2] != 'as':
+        raise template.TemplateSyntaxError("second argument to '%s' tag must be 'as'" % bits[0])
+    return RandomObjectsNode(bits[1], 1, bits[3])
+
+
+def do_random_objects(parser, token):
+    """
+    Retrieves ``num`` random objects from a given model, and stores
+    them in a context variable.
+    
+    Syntax::
+    
+        {% get_random_objects [app_name].[model_name] [num] as [varname] %}
+    
+    Example::
+    
+        {% get_random_objects comments.freecomment 5 as random_comments %}
+    
+    """
+    bits = token.contents.split()
+    if len(bits) != 5:
+        raise template.TemplateSyntaxError("'%s' tag takes four arguments" % bits[0])
+    if bits [3] != 'as':
+        raise template.TemplateSyntaxError("third argument to '%s' tag must be 'as'" % bits[0])
+    return RandomObjectsNode(bits[1], bits[2], bits[4])
+
+
+def do_retrieve_object(parser, token):
+    """
+    Retrieves a specific object from a given model by primary-key
+    lookup, and stores it in a context variable.
+    
+    Syntax::
+    
+        {% retrieve_object [app_name].[model_name] [pk] as [varname] %}
+    
+    Example::
+    
+        {% retrieve_object flatpages.flatpage 12 as my_flat_page %}
+    
+    """
+    bits = token.contents.split()
+    if len(bits) != 5:
+        raise template.TemplateSyntaxError("'%s' tag takes four arguments" % bits[0])
+    if bits[3] != 'as':
+        raise template.TemplateSyntaxError("third argument to '%s' tag must be 'as'" % bits[0])
+    return RetrieveObjectNode(bits[1], bits[2], bits[4])
+
+register = template.Library()
+register.tag('get_latest_object', do_latest_object)
+register.tag('get_latest_objects', do_latest_objects)
+register.tag('get_random_object', do_random_object)
+register.tag('get_random_objects', do_random_objects)
+register.tag('retrieve_object', do_retrieve_object)

template_utils/templatetags/generic_markup.py

+"""
+Filters for converting plain text to HTML and enhancing the
+typographic appeal of text on the Web.
+
+"""
+
+
+from django.conf import settings
+from django.template import Library
+from django.utils.safestring import mark_safe
+
+from template_utils.markup import formatter
+
+
+def apply_markup(value, arg=None):
+    """
+    Applies text-to-HTML conversion.
+    
+    Takes an optional argument to specify the name of a filter to use.
+    
+    """
+    if arg is not None:
+        return mark_safe(formatter(value, filter_name=arg))
+    return mark_safe(formatter(value))
+apply_markup.is_safe = True
+
+def smartypants(value):
+    """
+    Applies SmartyPants to a piece of text, applying typographic
+    niceties.
+    
+    Requires the Python SmartyPants library to be installed; see
+    http://web.chad.org/projects/smartypants.py/
+    
+    """
+    try:
+        from smartypants import smartyPants
+    except ImportError:
+        if settings.DEBUG:
+            raise template.TemplateSyntaxError("Error in smartypants filter: the Python smartypants module is not installed or could not be imported")
+        return value
+    else:
+        return mark_safe(smartyPants(value))
+
+register = Library()
+register.filter(apply_markup)
+register.filter(smartypants)