From ca2138fef978345b9076a2f855ed434a0bac65e1 Mon Sep 17 00:00:00 2001 From: Stephen Burrows Date: Tue, 16 Nov 2010 11:39:05 -0500 Subject: [PATCH] Minor improvements to navigationoverrides, including admin support and working navigation construction. --- admin/nodes.py | 27 ++- forms.py | 45 ++++- .../0010_auto__add_nodenavigationoverride.py | 157 ++++++++++++++++++ ...chg_field_nodenavigationoverride_parent.py | 147 ++++++++++++++++ models/nodes.py | 89 ++++++---- models/pages.py | 5 +- 6 files changed, 429 insertions(+), 41 deletions(-) create mode 100644 migrations/0010_auto__add_nodenavigationoverride.py create mode 100644 migrations/0011_auto__chg_field_nodenavigationoverride_parent.py diff --git a/admin/nodes.py b/admin/nodes.py index 45a3172..16c2aaf 100644 --- a/admin/nodes.py +++ b/admin/nodes.py @@ -1,10 +1,31 @@ from django.contrib import admin -from philo.admin.base import EntityAdmin, TreeEntityAdmin -from philo.models import Node, Redirect, File +from philo.admin.base import EntityAdmin, TreeEntityAdmin, COLLAPSE_CLASSES +from philo.models import Node, Redirect, File, NodeNavigationOverride +from philo.forms import NodeWithOverrideForm + + +class ChildNavigationOverrideInline(admin.StackedInline): + fk_name = 'parent' + model = NodeNavigationOverride + sortable_field_name = 'order' + verbose_name = 'child' + verbose_name_plural = 'children' + extra = 0 + max_num = 0 class NodeAdmin(TreeEntityAdmin): - pass + form = NodeWithOverrideForm + fieldsets = ( + (None, { + 'fields': ('parent', 'slug', 'view_content_type', 'view_object_id'), + }), + ('Navigation Overrides', { + 'fields': ('title', 'url', 'child_navigation'), + 'classes': COLLAPSE_CLASSES + }) + ) + inlines = [ChildNavigationOverrideInline] + TreeEntityAdmin.inlines class ViewAdmin(EntityAdmin): diff --git a/forms.py b/forms.py index a1785fb..192d02b 100644 --- a/forms.py +++ b/forms.py @@ -9,7 +9,7 @@ from django.forms.formsets import TOTAL_FORM_COUNT from django.template import loader, loader_tags, TemplateDoesNotExist, Context, Template as DjangoTemplate from django.utils.datastructures import SortedDict from philo.admin.widgets import ModelLookupWidget -from philo.models import Entity, Template, Contentlet, ContentReference, Attribute +from philo.models import Entity, Template, Contentlet, ContentReference, Attribute, Node, NodeNavigationOverride from philo.utils import fattr @@ -337,4 +337,45 @@ class ContentReferenceInlineFormSet(ContainerInlineFormSet): name, content_type = self.extra_containers[i - self.initial_form_count() - 1] kwargs['instance'] = self.model(name=name, content_type=content_type) - return super(ContentReferenceInlineFormSet, self)._construct_form(i, **kwargs) \ No newline at end of file + return super(ContentReferenceInlineFormSet, self)._construct_form(i, **kwargs) + + +class NodeWithOverrideForm(forms.ModelForm): + title = NodeNavigationOverride._meta.get_field('title').formfield() + url = NodeNavigationOverride._meta.get_field('url').formfield() + child_navigation = NodeNavigationOverride._meta.get_field('child_navigation').formfield(required=False) + + def __init__(self, *args, **kwargs): + super(NodeWithOverrideForm, self).__init__(*args, **kwargs) + if self.instance.pk: + self._override = override = self.get_override(self.instance) + self.initial.update({ + 'title': override.title, + 'url': override.url, + 'child_navigation': override.child_navigation_json + }) + + def get_override(self, instance): + try: + return NodeNavigationOverride.objects.get(parent=self.instance.parent, child=self.instance) + except NodeNavigationOverride.DoesNotExist: + override = NodeNavigationOverride(parent=self.instance.parent, child=self.instance) + override.child_navigation = None + return override + + def save(self, commit=True): + obj = super(NodeWithOverrideForm, self).save(commit) + cleaned_data = self.cleaned_data + override = self.get_override(obj) + + # Override information should only be set if there was no previous override or if the + # information was just manually set - i.e. was not equal to the data on the cached override. + if not override.pk or cleaned_data['title'] != self._override.title or cleaned_data['url'] != self._override.url or cleaned_data['child_navigation'] != self._override.child_navigation: + override.title = self.cleaned_data['title'] + override.url = self.cleaned_data['url'] + override.child_navigation = self.cleaned_data['child_navigation'] + override.save() + return obj + + class Meta: + model = Node \ No newline at end of file diff --git a/migrations/0010_auto__add_nodenavigationoverride.py b/migrations/0010_auto__add_nodenavigationoverride.py new file mode 100644 index 0000000..d4473b4 --- /dev/null +++ b/migrations/0010_auto__add_nodenavigationoverride.py @@ -0,0 +1,157 @@ +# 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 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'])), + ('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)), + ('order', self.gf('django.db.models.fields.PositiveSmallIntegerField')(null=True, blank=True)), + ('child_navigation', self.gf('philo.models.fields.JSONField')(default=None)), + ('hide', self.gf('django.db.models.fields.BooleanField')(default=False, blank=True)), + )) + db.send_create_signal('philo', ['NodeNavigationOverride']) + + + def backwards(self, orm): + + # Deleting model 'NodeNavigationOverride' + db.delete_table('philo_nodenavigationoverride') + + + 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', [], {'related_name': "'child_navigation_overrides'", '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/migrations/0011_auto__chg_field_nodenavigationoverride_parent.py b/migrations/0011_auto__chg_field_nodenavigationoverride_parent.py new file mode 100644 index 0000000..b667d4b --- /dev/null +++ b/migrations/0011_auto__chg_field_nodenavigationoverride_parent.py @@ -0,0 +1,147 @@ +# 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 90184ab..58a5b99 100644 --- a/models/nodes.py +++ b/models/nodes.py @@ -15,6 +15,7 @@ from philo.utils import ContentTypeSubclassLimiter from philo.validators import RedirectValidator from philo.exceptions import ViewCanNotProvideSubpath, ViewDoesNotProvideSubpaths, AncestorDoesNotExist from philo.signals import view_about_to_render, view_finished_rendering +from mptt.templatetags.mptt_tags import cache_tree_children _view_content_type_limiter = ContentTypeSubclassLimiter(None) @@ -50,38 +51,38 @@ class Node(TreeEntity): except AncestorDoesNotExist, ViewDoesNotExist: return None - def get_navigation(self, depth=DEFAULT_NAVIGATION_DEPTH, current_depth=0, found_node_pks=None): - navigation = self.view.get_navigation(self, depth, current_depth) + def get_navigation(self, depth=DEFAULT_NAVIGATION_DEPTH): + max_depth = depth + self.get_level() + tree = cache_tree_children(self.get_descendants(include_self=True).filter(level__lte=max_depth)) - if depth == current_depth: - return navigation - import pdb - pdb.set_trace() - found_node_pks = found_node_pks or [self.pk] - ordered_child_pks = NodeNavigationOverride.objects.filter(parent=self, child__parent=self).values_list('child__pk', flat=True) - - children = self.children.exclude(pk__in=found_node_pks) - ordered_children = children.filter(pk__in=ordered_child_pks) - unordered_children = children.exclude(pk__in=ordered_child_pks) - - children = list(ordered_children) + list(unordered_children) - - if children: - child_navigation = [] - for child in children: - found_node_pks.append(child.pk) - try: - child_navigation.append(child.get_navigation(depth, current_depth + 1, found_node_pks)) - except NotImplementedError: - pass + def get_nav(parent, nodes): + node_overrides = dict([(override.child.pk, override) for override in NodeNavigationOverride.objects.filter(parent=parent, child__in=nodes).select_related('child')]) - if child_navigation: - if 'children' in navigation: - navigation['children'] += child_navigation + navigation_list = [] + + for node in nodes: + node._override = node_overrides.get(node.pk, None) + + if node._override: + if node._override.hide: + continue + navigation = node._override.get_navigation(node, max_depth) else: - navigation['children'] = child_navigation + navigation = node.view.get_navigation(node, max_depth) + + if not node.is_leaf_node() and node.get_level() < max_depth: + children = navigation.get('children', []) + children += get_nav(node, node.get_children()) + navigation['children'] = children + + if 'children' in navigation: + navigation['children'].sort(cmp=lambda x,y: cmp(x['order'], y['order'])) + + navigation_list.append(navigation) + + return navigation_list - return navigation + return get_nav(self.parent, tree) def save(self): super(Node, self).save() @@ -96,29 +97,36 @@ models.ForeignKey(Node, related_name='sites', null=True, blank=True).contribute_ class NodeNavigationOverride(Entity): - parent = models.ForeignKey(Node, related_name="navigation_override_child_set") - child = models.OneToOneField(Node, related_name="navigation_override") - url = models.CharField(max_length=200, validators=[RedirectValidator()], blank=True) + parent = models.ForeignKey(Node, related_name="child_navigation_overrides", blank=True, null=True) + child = models.ForeignKey(Node, related_name="navigation_overrides") + title = models.CharField(max_length=100, blank=True) + url = models.CharField(max_length=200, validators=[RedirectValidator()], blank=True) order = models.PositiveSmallIntegerField(blank=True, null=True) child_navigation = JSONField() + hide = models.BooleanField() def get_navigation(self, node, depth, current_depth): if self.child_navigation: depth = current_depth - default = node.get_navigation(depth, current_depth) + default = node.view.get_navigation(depth, current_depth) if self.url: default['url'] = self.url if self.title: default['title'] = self.title - if self.child_navigation: + if self.order: + default['order'] = self.order + if isinstance(self.child_navigation, list): if 'children' in default: default['children'] += self.child_navigation else: default['children'] = self.child_navigation + return default class Meta: ordering = ['order'] + unique_together = ('parent', 'child',) + app_label = 'philo' class View(Entity): @@ -156,7 +164,20 @@ class View(Entity): def actually_render_to_response(self, request, extra_context=None): raise NotImplementedError('View subclasses must implement render_to_response.') - def get_navigation(self, node, depth, current_depth): + def get_navigation(self, node, max_depth): + """ + Subclasses should implement get_navigation to support auto-generated navigation. + max_depth is the deepest `level` that should be generated; node is the node that + is asking for the navigation. This method should return a dictionary of the form: + { + 'url': url, + 'title': title, + 'order': order, # None for no ordering. + 'children': [ # Optional + + ] + } + """ raise NotImplementedError('View subclasses must implement get_navigation.') class Meta: diff --git a/models/pages.py b/models/pages.py index 3669338..6f5bc9a 100644 --- a/models/pages.py +++ b/models/pages.py @@ -102,10 +102,11 @@ class Page(View): if errors: raise ValidationError(errors) - def get_navigation(self, node, depth, current_depth): + def get_navigation(self, node, max_depth): return { 'url': node.get_absolute_url(), - 'title': self.title + 'title': self.title, + 'order': None, } class Meta: -- 2.20.1