From ee56a2ca0d6c5d08ee42a9452de4353e8f2643ba Mon Sep 17 00:00:00 2001 From: Stephen Burrows Date: Mon, 29 Nov 2010 10:59:21 -0500 Subject: [PATCH] Added NodeOverrideInlineFormSet to clean up admin editing of node overrides by only displaying overrides where both the override's parent and the override's child__parent are the current instance. Cleaned up migrations by merging the change of NodeNavigationOverride.parent to allow null into the original migration that created NodeNavigationOverrides. Made navigation include the root node in the top-level navigation... not sure this is the correct way to handle things. Added navigation-generating template tag. --- admin/nodes.py | 3 +- forms.py | 14 +- .../0010_auto__add_nodenavigationoverride.py | 2 +- ...chg_field_nodenavigationoverride_parent.py | 147 ------------------ models/nodes.py | 25 ++- templatetags/nodes.py | 82 +++++++++- 6 files changed, 115 insertions(+), 158 deletions(-) delete mode 100644 migrations/0011_auto__chg_field_nodenavigationoverride_parent.py diff --git a/admin/nodes.py b/admin/nodes.py index 16c2aaf..0fac7ad 100644 --- a/admin/nodes.py +++ b/admin/nodes.py @@ -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' diff --git a/forms.py b/forms.py index 192d02b..b404f0d 100644 --- 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 diff --git a/migrations/0010_auto__add_nodenavigationoverride.py b/migrations/0010_auto__add_nodenavigationoverride.py index d4473b4..e36b98c 100644 --- a/migrations/0010_auto__add_nodenavigationoverride.py +++ b/migrations/0010_auto__add_nodenavigationoverride.py @@ -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 index b667d4b..0000000 --- a/migrations/0011_auto__chg_field_nodenavigationoverride_parent.py +++ /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'] diff --git a/models/nodes.py b/models/nodes.py index 58a5b99..6035148 100644 --- a/models/nodes.py +++ b/models/nodes.py @@ -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 diff --git a/templatetags/nodes.py b/templatetags/nodes.py index 338ac2d..744be4d 100644 --- a/templatetags/nodes.py +++ b/templatetags/nodes.py @@ -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 += "
  • " + else: + compiled += "
  • " + + if item['url']: + compiled += "" % item['url'] + + compiled += item['title'] + + if item['url']: + compiled += "" + + if 'children' in item and current_depth < max_depth: + compiled += "" % self.compile(item['children'], active_path, root_url, current_depth + 1, max_depth) + + compiled += "
  • " + return compiled + + +@register.tag(name='navigation') +def do_navigation(parser, token): + """ + {% navigation [for ] [as ] %} + """ + 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 ] [as ] %}' % (tag, tag)) + return NavigationNode(node, as_var) -- 2.20.1