--- /dev/null
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+
+ # Adding field 'NewsletterView.feed_length'
+ db.add_column('penfield_newsletterview', 'feed_length', self.gf('django.db.models.fields.PositiveIntegerField')(default=15, null=True, blank=True), keep_default=False)
+
+ # Adding field 'BlogView.feed_length'
+ db.add_column('penfield_blogview', 'feed_length', self.gf('django.db.models.fields.PositiveIntegerField')(default=15, null=True, blank=True), keep_default=False)
+
+
+ def backwards(self, orm):
+
+ # Deleting field 'NewsletterView.feed_length'
+ db.delete_column('penfield_newsletterview', 'feed_length')
+
+ # Deleting field 'BlogView.feed_length'
+ db.delete_column('penfield_blogview', 'feed_length')
+
+
+ models = {
+ 'auth.group': {
+ 'Meta': {'object_name': 'Group'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+ 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+ },
+ 'auth.permission': {
+ 'Meta': {'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
+ 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+ },
+ 'auth.user': {
+ 'Meta': {'object_name': 'User'},
+ 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+ 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),
+ 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+ 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+ 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+ },
+ 'contenttypes.contenttype': {
+ 'Meta': {'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+ },
+ 'oberlin.person': {
+ 'Meta': {'object_name': 'Person'},
+ 'bio': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '70', 'blank': 'True'}),
+ 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True', 'null': 'True', 'blank': 'True'})
+ },
+ 'penfield.blog': {
+ 'Meta': {'object_name': 'Blog'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '255', 'db_index': 'True'}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+ },
+ 'penfield.blogentry': {
+ 'Meta': {'object_name': 'BlogEntry'},
+ 'author': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'blogentries'", 'to': "orm['oberlin.Person']"}),
+ 'blog': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'entries'", 'null': 'True', 'to': "orm['penfield.Blog']"}),
+ 'content': ('django.db.models.fields.TextField', [], {}),
+ 'date': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
+ 'excerpt': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '255', 'db_index': 'True'}),
+ 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'blogentries'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['philo.Tag']"}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+ },
+ 'penfield.blogview': {
+ 'Meta': {'object_name': 'BlogView'},
+ 'blog': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'blogviews'", 'to': "orm['penfield.Blog']"}),
+ 'entries_per_page': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+ 'entry_archive_page': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'blog_entry_archive_related'", 'null': 'True', 'to': "orm['philo.Page']"}),
+ 'entry_page': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'blog_entry_related'", 'to': "orm['philo.Page']"}),
+ 'entry_permalink_base': ('django.db.models.fields.CharField', [], {'default': "'entries'", 'max_length': '255'}),
+ 'entry_permalink_style': ('django.db.models.fields.CharField', [], {'max_length': '1'}),
+ 'feed_length': ('django.db.models.fields.PositiveIntegerField', [], {'default': '15', 'null': 'True', 'blank': 'True'}),
+ 'feed_suffix': ('django.db.models.fields.CharField', [], {'default': "'feed'", 'max_length': '255'}),
+ 'feed_type': ('django.db.models.fields.CharField', [], {'default': "'application/atom+xml; charset=utf8'", 'max_length': '50'}),
+ 'feeds_enabled': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'index_page': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'blog_index_related'", 'to': "orm['philo.Page']"}),
+ 'item_description_template': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'penfield_blogview_description_related'", 'null': 'True', 'to': "orm['philo.Template']"}),
+ 'item_title_template': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'penfield_blogview_title_related'", 'null': 'True', 'to': "orm['philo.Template']"}),
+ 'tag_archive_page': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'blog_tag_archive_related'", 'null': 'True', 'to': "orm['philo.Page']"}),
+ 'tag_page': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'blog_tag_related'", 'to': "orm['philo.Page']"}),
+ 'tag_permalink_base': ('django.db.models.fields.CharField', [], {'default': "'tags'", 'max_length': '255'})
+ },
+ 'penfield.newsletter': {
+ 'Meta': {'object_name': 'Newsletter'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '255', 'db_index': 'True'}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+ },
+ 'penfield.newsletterarticle': {
+ 'Meta': {'unique_together': "(('newsletter', 'slug'),)", 'object_name': 'NewsletterArticle'},
+ 'authors': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'newsletterarticles'", 'symmetrical': 'False', 'to': "orm['oberlin.Person']"}),
+ 'date': ('django.db.models.fields.DateTimeField', [], {'default': 'None'}),
+ 'full_text': ('philo.models.fields.TemplateField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'lede': ('philo.models.fields.TemplateField', [], {'null': 'True', 'blank': 'True'}),
+ 'newsletter': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'articles'", 'to': "orm['penfield.Newsletter']"}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '255', 'db_index': 'True'}),
+ 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'newsletterarticles'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['philo.Tag']"}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+ },
+ 'penfield.newsletterissue': {
+ 'Meta': {'unique_together': "(('newsletter', 'numbering'),)", 'object_name': 'NewsletterIssue'},
+ 'articles': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'issues'", 'symmetrical': 'False', 'to': "orm['penfield.NewsletterArticle']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'newsletter': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'issues'", 'to': "orm['penfield.Newsletter']"}),
+ 'numbering': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '255', 'db_index': 'True'}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+ },
+ 'penfield.newsletterview': {
+ 'Meta': {'object_name': 'NewsletterView'},
+ 'article_archive_page': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'newsletter_article_archive_related'", 'null': 'True', 'to': "orm['philo.Page']"}),
+ 'article_page': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'newsletter_article_related'", 'to': "orm['philo.Page']"}),
+ 'article_permalink_base': ('django.db.models.fields.CharField', [], {'default': "'articles'", 'max_length': '255'}),
+ 'article_permalink_style': ('django.db.models.fields.CharField', [], {'max_length': '1'}),
+ 'feed_length': ('django.db.models.fields.PositiveIntegerField', [], {'default': '15', 'null': 'True', 'blank': 'True'}),
+ 'feed_suffix': ('django.db.models.fields.CharField', [], {'default': "'feed'", 'max_length': '255'}),
+ 'feed_type': ('django.db.models.fields.CharField', [], {'default': "'application/atom+xml; charset=utf8'", 'max_length': '50'}),
+ 'feeds_enabled': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'index_page': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'newsletter_index_related'", 'to': "orm['philo.Page']"}),
+ 'issue_archive_page': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'newsletter_issue_archive_related'", 'null': 'True', 'to': "orm['philo.Page']"}),
+ 'issue_page': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'newsletter_issue_related'", 'to': "orm['philo.Page']"}),
+ 'issue_permalink_base': ('django.db.models.fields.CharField', [], {'default': "'issues'", 'max_length': '255'}),
+ 'item_description_template': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'penfield_newsletterview_description_related'", 'null': 'True', 'to': "orm['philo.Template']"}),
+ 'item_title_template': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'penfield_newsletterview_title_related'", 'null': 'True', 'to': "orm['philo.Template']"}),
+ 'newsletter': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'newsletterviews'", 'to': "orm['penfield.Newsletter']"})
+ },
+ 'philo.attribute': {
+ 'Meta': {'unique_together': "(('key', 'entity_content_type', 'entity_object_id'), ('value_content_type', 'value_object_id'))", 'object_name': 'Attribute'},
+ 'entity_content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'attribute_entity_set'", 'to': "orm['contenttypes.ContentType']"}),
+ 'entity_object_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
+ 'value_content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'attribute_value_set'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
+ 'value_object_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'})
+ },
+ 'philo.node': {
+ 'Meta': {'object_name': 'Node'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'level': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+ 'lft': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+ 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['philo.Node']"}),
+ 'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '255', 'db_index': 'True'}),
+ 'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+ 'view_content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'node_view_set'", 'to': "orm['contenttypes.ContentType']"}),
+ 'view_object_id': ('django.db.models.fields.PositiveIntegerField', [], {})
+ },
+ 'philo.page': {
+ 'Meta': {'object_name': 'Page'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'template': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'pages'", 'to': "orm['philo.Template']"}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+ },
+ 'philo.tag': {
+ 'Meta': {'object_name': 'Tag'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '255', 'db_index': 'True'})
+ },
+ 'philo.template': {
+ 'Meta': {'object_name': 'Template'},
+ 'code': ('philo.models.fields.TemplateField', [], {}),
+ 'documentation': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'level': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+ 'lft': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+ 'mimetype': ('django.db.models.fields.CharField', [], {'default': "'text/html'", 'max_length': '255'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['philo.Template']"}),
+ 'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '255', 'db_index': 'True'}),
+ 'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'})
+ }
+ }
+
+ complete_apps = ['penfield']
from django.utils.safestring import mark_safe
from philo.contrib.shipherd.models import Navigation
from philo.models import Node
-from mptt.templatetags.mptt_tags import RecurseTreeNode, cache_tree_children
+from django.utils.safestring import mark_safe
from django.utils.translation import ugettext as _
register = template.Library()
-class RecurseNavigationNode(RecurseTreeNode):
- def __init__(self, template_nodes, instance_var, key):
+class LazyNavigationRecurser(object):
+ def __init__(self, template_nodes, items, context, request):
self.template_nodes = template_nodes
- self.instance_var = instance_var
- self.key = key
+ self.items = items
+ self.context = context
+ self.request = request
- def _render_node(self, context, item, request):
- bits = []
+ def __call__(self):
+ items = self.items
+ context = self.context
+ request = self.request
+
+ if not items:
+ return ''
+
+ if 'navloop' in context:
+ parentloop = context['navloop']
+ else:
+ parentloop = {}
context.push()
- for child in item.get_children():
- context['item'] = child
- bits.append(self._render_node(context, child, request))
- context['item'] = item
- context['children'] = mark_safe(u''.join(bits))
- context['active'] = item.is_active(request)
- context['active_descendants'] = item.has_active_descendants(request)
- rendered = self.template_nodes.render(context)
+
+ depth = items[0].get_level()
+ len_items = len(items)
+
+ loop_dict = context['navloop'] = {
+ 'parentloop': parentloop,
+ 'depth': depth + 1,
+ 'depth0': depth
+ }
+
+ bits = []
+
+ for i, item in enumerate(items):
+ # First set context variables.
+ loop_dict['counter0'] = i
+ loop_dict['counter'] = i + 1
+ loop_dict['revcounter'] = len_items - i
+ loop_dict['revcounter0'] = len_items - i - 1
+ loop_dict['first'] = (i == 0)
+ loop_dict['last'] = (i == len_items - 1)
+
+ # Set on loop_dict and context for backwards-compatibility.
+ # Eventually only allow access through the loop_dict.
+ loop_dict['active'] = context['active'] = item.is_active(request)
+ loop_dict['active_descendants'] = context['active_descendants'] = item.has_active_descendants(request)
+
+ # Set these directly in the context for easy access.
+ context['item'] = item
+ context['children'] = self.__class__(self.template_nodes, item.get_children(), context, request)
+
+ # Then render the nodelist bit by bit.
+ for node in self.template_nodes:
+ bits.append(node.render(context))
context.pop()
- return rendered
+ return mark_safe(''.join(bits))
+
+
+class RecurseNavigationNode(template.Node):
+ def __init__(self, template_nodes, instance_var, key):
+ self.template_nodes = template_nodes
+ self.instance_var = instance_var
+ self.key = key
def render(self, context):
try:
instance = self.instance_var.resolve(context)
try:
- navigation = instance.navigation[self.key]
+ items = instance.navigation[self.key]
except:
return settings.TEMPLATE_STRING_IF_INVALID
- bits = [self._render_node(context, item, request) for item in navigation]
- return ''.join(bits)
+ return LazyNavigationRecurser(self.template_nodes, items, context, request)()
@register.tag
def recursenavigation(parser, token):
"""
- Based on django-mptt's recursetree templatetag. In addition to {{ item }} and {{ children }},
- sets {{ active }} and {{ active_descendants }} in the context.
+ The recursenavigation templatetag takes two arguments:
+ - the node for which the navigation should be found
+ - the navigation's key.
- Note that the tag takes one variable, which is a Node instance.
+ It will then recursively loop over each item in the navigation and render the template
+ chunk within the block. recursenavigation sets the following variables in the context:
- Usage:
+ ============================== ================================================
+ Variable Description
+ ============================== ================================================
+ ``navloop.depth`` The current depth of the loop (1 is the top level)
+ ``navloop.depth0`` The current depth of the loop (0 is the top level)
+ ``navloop.counter`` The current iteration of the current level(1-indexed)
+ ``navloop.counter0`` The current iteration of the current level(0-indexed)
+ ``navloop.first`` True if this is the first time through the current level
+ ``navloop.last`` True if this is the last time through the current level
+ ``navloop.parentloop`` This is the loop one level "above" the current one
+ ============================== ================================================
+ ``item`` The current item in the loop (a NavigationItem instance)
+ ``children`` If accessed, performs the next level of recursion.
+ ``navloop.active`` True if the item is active for this request
+ ``navloop.active_descendants`` True if the item has active descendants for this request
+ ============================== ================================================
+
+ Example:
<ul>
{% recursenavigation node main %}
- <li{% if active %} class='active'{% endif %}>
- {{ navigation.text }}
- {% if navigation.get_children %}
+ <li{% if navloop.active %} class='active'{% endif %}>
+ {{ navloop.item.text }}
+ {% if item.get_children %}
<ul>
{{ children }}
</ul>
instance_var = parser.compile_filter(bits[1])
key = bits[2]
- template_nodes = parser.parse(('endrecursenavigation',))
- parser.delete_first_token()
+ template_nodes = parser.parse(('recurse', 'endrecursenavigation',))
+
+ token = parser.next_token()
+ if token.contents == 'recurse':
+ template_nodes.append(RecurseNavigationMarker())
+ template_nodes.extend(parser.parse(('endrecursenavigation')))
+ parser.delete_first_token()
return RecurseNavigationNode(template_nodes, instance_var, key)