Added NodeOverrideInlineFormSet to clean up admin editing of node overrides by only...
authorStephen Burrows <stephen.r.burrows@gmail.com>
Mon, 29 Nov 2010 15:59:21 +0000 (10:59 -0500)
committerStephen Burrows <stephen.r.burrows@gmail.com>
Mon, 29 Nov 2010 18:32:43 +0000 (13:32 -0500)
admin/nodes.py
forms.py
migrations/0010_auto__add_nodenavigationoverride.py
migrations/0011_auto__chg_field_nodenavigationoverride_parent.py [deleted file]
models/nodes.py
templatetags/nodes.py

index 16c2aaf..0fac7ad 100644 (file)
@@ -1,12 +1,13 @@
 from django.contrib import admin
 from philo.admin.base import EntityAdmin, TreeEntityAdmin, COLLAPSE_CLASSES
 from philo.models import Node, Redirect, File, NodeNavigationOverride
-from philo.forms import NodeWithOverrideForm
+from philo.forms import NodeWithOverrideForm, NodeOverrideInlineFormSet
 
 
 class ChildNavigationOverrideInline(admin.StackedInline):
        fk_name = 'parent'
        model = NodeNavigationOverride
+       formset = NodeOverrideInlineFormSet
        sortable_field_name = 'order'
        verbose_name = 'child'
        verbose_name_plural = 'children'
index 192d02b..b404f0d 100644 (file)
--- a/forms.py
+++ b/forms.py
@@ -378,4 +378,16 @@ class NodeWithOverrideForm(forms.ModelForm):
                return obj
        
        class Meta:
-               model = Node
\ No newline at end of file
+               model = Node
+
+
+class NodeOverrideInlineFormSet(BaseInlineFormSet):
+       def __init__(self, data=None, files=None, instance=None, save_as_new=False, prefix=None, queryset=None):
+               if queryset is None:
+                       queryset = self.model._default_manager
+               queryset = queryset.filter(parent=instance, child__parent=instance)
+               super(NodeOverrideInlineFormSet, self).__init__(data, files, instance, save_as_new, prefix, queryset)
+       
+       def add_fields(self, form, index):
+               super(NodeOverrideInlineFormSet, self).add_fields(form, index)
+               form.fields['child'].queryset = self.instance.children.all()
\ No newline at end of file
index d4473b4..e36b98c 100644 (file)
@@ -11,7 +11,7 @@ class Migration(SchemaMigration):
         # Adding model 'NodeNavigationOverride'
         db.create_table('philo_nodenavigationoverride', (
             ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
-            ('parent', self.gf('django.db.models.fields.related.ForeignKey')(related_name='child_navigation_overrides', to=orm['philo.Node'])),
+            ('parent', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, null=True, related_name='child_navigation_overrides', to=orm['philo.Node'])),
             ('child', self.gf('django.db.models.fields.related.ForeignKey')(related_name='navigation_overrides', to=orm['philo.Node'])),
             ('title', self.gf('django.db.models.fields.CharField')(max_length=100, blank=True)),
             ('url', self.gf('django.db.models.fields.CharField')(max_length=200, blank=True)),
diff --git a/migrations/0011_auto__chg_field_nodenavigationoverride_parent.py b/migrations/0011_auto__chg_field_nodenavigationoverride_parent.py
deleted file mode 100644 (file)
index b667d4b..0000000
+++ /dev/null
@@ -1,147 +0,0 @@
-# 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):
-        
-        # Changing field 'NodeNavigationOverride.parent'
-        db.alter_column('philo_nodenavigationoverride', 'parent_id', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, null=True, to=orm['philo.Node']))
-
-
-    def backwards(self, orm):
-        
-        # Changing field 'NodeNavigationOverride.parent'
-        db.alter_column('philo_nodenavigationoverride', 'parent_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['philo.Node']))
-
-
-    models = {
-        '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'})
-        },
-        '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', [], {}),
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'key': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
-            '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', [], {'null': 'True', 'blank': 'True'})
-        },
-        'philo.collection': {
-            'Meta': {'object_name': 'Collection'},
-            'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'})
-        },
-        'philo.collectionmember': {
-            'Meta': {'object_name': 'CollectionMember'},
-            'collection': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'members'", 'to': "orm['philo.Collection']"}),
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'index': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
-            'member_content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
-            'member_object_id': ('django.db.models.fields.PositiveIntegerField', [], {})
-        },
-        'philo.contentlet': {
-            'Meta': {'object_name': 'Contentlet'},
-            'content': ('philo.models.fields.TemplateField', [], {}),
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
-            'page': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'contentlets'", 'to': "orm['philo.Page']"})
-        },
-        'philo.contentreference': {
-            'Meta': {'object_name': 'ContentReference'},
-            'content_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
-            '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': '255'}),
-            'page': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'contentreferences'", 'to': "orm['philo.Page']"})
-        },
-        'philo.file': {
-            'Meta': {'object_name': 'File'},
-            'file': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}),
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'mimetype': ('django.db.models.fields.CharField', [], {'max_length': '255'})
-        },
-        'philo.foreignkeyvalue': {
-            'Meta': {'object_name': 'ForeignKeyValue'},
-            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']", 'null': 'True', 'blank': 'True'}),
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'object_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'})
-        },
-        'philo.jsonvalue': {
-            'Meta': {'object_name': 'JSONValue'},
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'value': ('philo.models.fields.JSONField', [], {})
-        },
-        'philo.manytomanyvalue': {
-            'Meta': {'object_name': 'ManyToManyValue'},
-            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']", 'null': 'True', 'blank': 'True'}),
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'values': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['philo.ForeignKeyValue']", '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.nodenavigationoverride': {
-            'Meta': {'unique_together': "(('parent', 'child'),)", 'object_name': 'NodeNavigationOverride'},
-            'child': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'navigation_overrides'", 'to': "orm['philo.Node']"}),
-            'child_navigation': ('philo.models.fields.JSONField', [], {'default': 'None'}),
-            'hide': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'order': ('django.db.models.fields.PositiveSmallIntegerField', [], {'null': 'True', 'blank': 'True'}),
-            'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'child_navigation_overrides'", 'null': 'True', 'to': "orm['philo.Node']"}),
-            'title': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
-            'url': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'})
-        },
-        '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.redirect': {
-            'Meta': {'object_name': 'Redirect'},
-            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
-            'status_code': ('django.db.models.fields.IntegerField', [], {'default': '302'}),
-            'target': ('django.db.models.fields.CharField', [], {'max_length': '200'})
-        },
-        '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 = ['philo']
index 58a5b99..6035148 100644 (file)
@@ -59,7 +59,6 @@ class Node(TreeEntity):
                        node_overrides = dict([(override.child.pk, override) for override in NodeNavigationOverride.objects.filter(parent=parent, child__in=nodes).select_related('child')])
                        
                        navigation_list = []
-                       
                        for node in nodes:
                                node._override = node_overrides.get(node.pk, None)
                                
@@ -82,7 +81,11 @@ class Node(TreeEntity):
                        
                        return navigation_list
                
-               return get_nav(self.parent, tree)
+               navigation = get_nav(self.parent, tree)
+               root = navigation[0]
+               navigation = [root] + root['children']
+               del(root['children'])
+               return navigation
        
        def save(self):
                super(Node, self).save()
@@ -106,18 +109,26 @@ class NodeNavigationOverride(Entity):
        child_navigation = JSONField()
        hide = models.BooleanField()
        
-       def get_navigation(self, node, depth, current_depth):
-               if self.child_navigation:
-                       depth = current_depth
-               default = node.view.get_navigation(depth, current_depth)
+       def get_navigation(self, node, max_depth):
+               default = node.view.get_navigation(node, max_depth)
                if self.url:
                        default['url'] = self.url
                if self.title:
                        default['title'] = self.title
                if self.order:
                        default['order'] = self.order
-               if isinstance(self.child_navigation, list):
+               if isinstance(self.child_navigation, list) and node.get_level() < max_depth:
+                       child_navigation = self.child_navigation[:]
+                       
+                       for child in child_navigation:
+                               child['url'] = default['url'] + child['url']
+                       
                        if 'children' in default:
+                               overridden = set([child['url'] for child in default['children']]) & set([child['url'] for child in self.child_navigation])
+                               if overridden:
+                                       for child in default[:]:
+                                               if child['url'] in overridden:
+                                                       default.remove(child)
                                default['children'] += self.child_navigation
                        else:
                                default['children'] = self.child_navigation
index 338ac2d..744be4d 100644 (file)
@@ -112,4 +112,84 @@ def do_node_url(parser, token):
                                args.append(parser.compile_filter(value))
                return NodeURLNode(view_name=view_name, args=args, kwargs=kwargs, node=node, as_var=as_var)
        
-       return NodeURLNode(node=node, as_var=as_var)
\ No newline at end of file
+       return NodeURLNode(node=node, as_var=as_var)
+
+
+class NavigationNode(template.Node):
+       def __init__(self, node=None, as_var=None):
+               self.as_var = as_var
+               self.node = node
+       
+       def render(self, context):
+               if 'request' not in context:
+                       return settings.TEMPLATE_STRING_IF_INVALID
+               
+               if self.node:
+                       node = self.node.resolve(context)
+               else:
+                       node = context.get('node', None)
+               
+               if not node:
+                       return settings.TEMPLATE_STRING_IF_INVALID
+               
+               try:
+                       nav_root = node.attributes['navigation_root']
+               except KeyError:
+                       if settings.TEMPLATE_DEBUG:
+                               raise
+                       return settings.TEMPLATE_STRING_IF_INVALID
+               
+               # Should I get its override and check for a max depth override there?
+               navigation = nav_root.get_navigation()
+               
+               if self.as_var:
+                       context[self.as_var] = navigation
+                       return ''
+               
+               return self.compile(navigation, context['request'].path, nav_root.get_absolute_url(), nav_root.get_level(), nav_root.get_level() + 3)
+       
+       def compile(self, navigation, active_path, root_url, current_depth, max_depth):
+               compiled = ""
+               for item in navigation:
+                       if item['url'] in active_path and (item['url'] != root_url or root_url == active_path):
+                               compiled += "<li class='active'>"
+                       else:
+                               compiled += "<li>"
+                       
+                       if item['url']:
+                               compiled += "<a href='%s'>" % item['url']
+                       
+                       compiled += item['title']
+                       
+                       if item['url']:
+                               compiled += "</a>"
+                       
+                       if 'children' in item and current_depth < max_depth:
+                               compiled += "<ul>%s</ul>" % self.compile(item['children'], active_path, root_url, current_depth + 1, max_depth)
+                       
+                       compiled += "</li>"
+               return compiled
+
+
+@register.tag(name='navigation')
+def do_navigation(parser, token):
+       """
+       {% navigation [for <node>] [as <var>] %}
+       """
+       bits = token.split_contents()
+       tag = bits[0]
+       bits = bits[1:]
+       node = None
+       as_var = None
+       
+       if len(bits) >= 2 and bits[-2] == 'as':
+               as_var = bits[-1]
+               bits = bits[:-2]
+       
+       if len(bits) >= 2 and bits[-2] == 'for':
+               node = parser.compile_filter(bits[-1])
+               bits = bits[-2]
+       
+       if bits:
+               raise template.TemplateSyntaxError('`%s` template tag expects the syntax {%% %s [for <node>] [as <var>] %}' % (tag, tag))
+       return NavigationNode(node, as_var)