Initial implementation of new Node system whereby Nodes are separate from the objects...
authorJoseph Spiros <joseph.spiros@ithinksw.com>
Fri, 25 Jun 2010 17:58:04 +0000 (13:58 -0400)
committerJoseph Spiros <joseph.spiros@ithinksw.com>
Fri, 25 Jun 2010 17:58:04 +0000 (13:58 -0400)
16 files changed:
admin.py [deleted file]
admin/__init__.py [new file with mode: 0644]
admin/base.py [new file with mode: 0644]
admin/collections.py [new file with mode: 0644]
admin/nodes.py [new file with mode: 0644]
admin/pages.py [new file with mode: 0644]
admin/widgets.py [new file with mode: 0644]
models/base.py
models/collections.py
models/nodes.py
models/pages.py
templatetags/collections.py
urls.py
utils.py
validators.py
views.py

diff --git a/admin.py b/admin.py
deleted file mode 100644 (file)
index 9e991d9..0000000
--- a/admin.py
+++ /dev/null
@@ -1,293 +0,0 @@
-from django.contrib import admin
-from django.contrib.contenttypes import generic
-from django.contrib.contenttypes.models import ContentType
-from django import forms
-from django.conf import settings
-from django.utils.translation import ugettext as _
-from django.utils.safestring import mark_safe
-from django.utils.html import escape
-from django.utils.text import truncate_words
-from philo.models import *
-from django.core.exceptions import ValidationError, ObjectDoesNotExist
-from validators import TreeParentValidator, TreePositionValidator
-
-
-COLLAPSE_CLASSES = ('collapse', 'collapse-closed', 'closed',)
-
-
-class AttributeInline(generic.GenericTabularInline):
-       ct_field = 'entity_content_type'
-       ct_fk_field = 'entity_object_id'
-       model = Attribute
-       extra = 1
-       template = 'admin/philo/edit_inline/tabular_collapse.html'
-       allow_add = True
-
-
-class RelationshipInline(generic.GenericTabularInline):
-       ct_field = 'entity_content_type'
-       ct_fk_field = 'entity_object_id'
-       model = Relationship
-       extra = 1
-       template = 'admin/philo/edit_inline/tabular_collapse.html'
-       allow_add = True
-
-
-class EntityAdmin(admin.ModelAdmin):
-       inlines = [AttributeInline, RelationshipInline]
-       save_on_top = True
-
-
-class CollectionMemberInline(admin.TabularInline):
-       fk_name = 'collection'
-       model = CollectionMember
-       extra = 1
-       classes = COLLAPSE_CLASSES
-       allow_add = True
-       fields = ('member_content_type', 'member_object_id', 'index',)
-
-
-class CollectionAdmin(admin.ModelAdmin):
-       inlines = [CollectionMemberInline]
-       list_display = ('name', 'description', 'get_count')
-
-
-class NodeAdmin(EntityAdmin):
-       pass
-
-
-class ModelLookupWidget(forms.TextInput):
-       # is_hidden = False
-       
-       def __init__(self, content_type, attrs=None):
-               self.content_type = content_type
-               super(ModelLookupWidget, self).__init__(attrs)
-       
-       def render(self, name, value, attrs=None):
-               related_url = '../../../%s/%s/' % (self.content_type.app_label, self.content_type.model)
-               if attrs is None:
-                       attrs = {}
-               if not attrs.has_key('class'):
-                       attrs['class'] = 'vForeignKeyRawIdAdminField'
-               output = super(ModelLookupWidget, self).render(name, value, attrs)
-               output += '<a href="%s" class="related-lookup" id="lookup_id_%s" onclick="return showRelatedObjectLookupPopup(this);">' % (related_url, name)
-               output += '<img src="%simg/admin/selector-search.gif" width="16" height="16" alt="%s" />' % (settings.ADMIN_MEDIA_PREFIX, _('Lookup'))
-               output += '</a>'
-               if value:
-                       value_class = self.content_type.model_class()
-                       try:
-                               value_object = value_class.objects.get(pk=value)
-                               output += '&nbsp;<strong>%s</strong>' % escape(truncate_words(value_object, 14))
-                       except value_class.DoesNotExist:
-                               pass
-               return mark_safe(output)
-
-
-class TreeForm(forms.ModelForm):
-       def __init__(self, *args, **kwargs):
-               super(TreeForm, self).__init__(*args, **kwargs)
-               instance = self.instance
-               instance_class = self.get_instance_class()
-               
-               if instance_class is not None:
-                       try:
-                               self.fields['parent'].queryset = instance_class.objects.exclude(id=instance.id)
-                       except ObjectDoesNotExist:
-                               pass
-                       
-               self.fields['parent'].validators = [TreeParentValidator(*self.get_validator_args())]
-       
-       def get_instance_class(self):
-               return self.instance.__class__
-               
-       def get_validator_args(self):
-               return [self.instance]
-       
-       def clean(self):
-               cleaned_data = self.cleaned_data
-               
-               try:
-                       parent = cleaned_data['parent']
-                       slug = cleaned_data['slug']
-                       obj_class = self.get_instance_class()
-                       tpv = TreePositionValidator(parent, slug, obj_class)
-                       tpv(self.instance)
-               except KeyError:
-                       pass
-               
-               return cleaned_data
-
-
-class NodeForm(TreeForm):
-       def get_instance_class(self):
-               return Node
-               
-       def get_validator_args(self):
-               return [self.instance, 'instance']
-
-
-class PageAdminForm(NodeForm):
-       class Meta:
-               model = Page
-
-
-class RedirectAdminForm(NodeForm):
-       class Meta:
-               model = Redirect
-
-
-class FileAdminForm(NodeForm):
-       class Meta:
-               model = File
-
-
-class RedirectAdmin(NodeAdmin):
-       fieldsets = (
-               (None, {
-                       'fields': ('slug', 'target', 'status_code')
-               }),
-               ('URL/Tree/Hierarchy', {
-                       'classes': COLLAPSE_CLASSES,
-                       'fields': ('parent',)
-               }),
-       )
-       list_display=('slug', 'target', 'path', 'status_code',)
-       list_filter=('status_code',)
-       form = RedirectAdminForm
-
-
-class FileAdmin(NodeAdmin):
-       prepopulated_fields = {'slug': ('file',)}
-       fieldsets = (
-               (None, {
-                       'fields': ('file', 'slug', 'mimetype')
-               }),
-               ('URL/Tree/Hierarchy', {
-                       'classes': COLLAPSE_CLASSES,
-                       'fields': ('parent',)
-               }),
-       )
-       form=FileAdminForm
-       list_display=('slug', 'mimetype', 'path', 'file',)
-
-
-class PageAdmin(NodeAdmin):
-       add_form_template = 'admin/philo/page/add_form.html'
-       prepopulated_fields = {'slug': ('title',)}
-       fieldsets = (
-               (None, {
-                       'fields': ('title', 'slug', 'template')
-               }),
-               ('URL/Tree/Hierarchy', {
-                       'classes': COLLAPSE_CLASSES,
-                       'fields': ('parent',)
-               }),
-       )
-       list_display = ('title', 'path', 'template')
-       list_filter = ('template',)
-       search_fields = ['title', 'slug', 'contentlets__content']
-       form = PageAdminForm
-       
-       def get_fieldsets(self, request, obj=None, **kwargs):
-               fieldsets = list(self.fieldsets)
-               if obj: # if no obj, creating a new page, thus no template set, thus no containers
-                       template = obj.template
-                       if template.documentation:
-                               fieldsets.append(('Template Documentation', {
-                                       'description': template.documentation
-                               }))
-                       contentlet_containers, contentreference_containers = template.containers
-                       for container_name in contentlet_containers:
-                               fieldsets.append((('Container: %s' % container_name), {
-                                       'fields': (('contentlet_container_content_%s' % container_name), ('contentlet_container_dynamic_%s' % container_name))
-                               }))
-                       for container_name, container_content_type in contentreference_containers:
-                               fieldsets.append((('Container: %s' % container_name), {
-                                       'fields': (('contentreference_container_%s' % container_name),)
-                               }))
-               return fieldsets
-       
-       def get_form(self, request, obj=None, **kwargs):
-               form = super(PageAdmin, self).get_form(request, obj, **kwargs)
-               if obj: # if no obj, creating a new page, thus no template set, thus no containers
-                       page = obj
-                       template = page.template
-                       contentlet_containers, contentreference_containers = template.containers
-                       for container_name in contentlet_containers:
-                               initial_content = None
-                               initial_dynamic = False
-                               try:
-                                       contentlet = page.contentlets.get(name__exact=container_name)
-                                       initial_content = contentlet.content
-                                       initial_dynamic = contentlet.dynamic
-                               except Contentlet.DoesNotExist:
-                                       pass
-                               form.base_fields[('contentlet_container_content_%s' % container_name)] = forms.CharField(label='Content', widget=forms.Textarea(), initial=initial_content, required=False)
-                               form.base_fields[('contentlet_container_dynamic_%s' % container_name)] = forms.BooleanField(label='Dynamic', help_text='Specify whether this content contains dynamic template code', initial=initial_dynamic, required=False)
-                       for container_name, container_content_type in contentreference_containers:
-                               initial_content = None
-                               try:
-                                       initial_content = page.contentreferences.get(name__exact=container_name, content_type=container_content_type).content.pk
-                               except (ContentReference.DoesNotExist, AttributeError):
-                                       pass
-                               form.base_fields[('contentreference_container_%s' % container_name)] = forms.ModelChoiceField(label='References', widget=ModelLookupWidget(container_content_type), initial=initial_content, required=False, queryset=container_content_type.model_class().objects.all())
-               return form
-       
-       def save_model(self, request, page, form, change):
-               page.save()
-               template = page.template
-               contentlet_containers, contentreference_containers = template.containers
-               for container_name in contentlet_containers:
-                       if (('contentlet_container_content_%s' % container_name) in form.cleaned_data) and (('contentlet_container_dynamic_%s' % container_name) in form.cleaned_data):
-                               content = form.cleaned_data[('contentlet_container_content_%s' % container_name)]
-                               dynamic = form.cleaned_data[('contentlet_container_dynamic_%s' % container_name)]
-                               contentlet, created = page.contentlets.get_or_create(name=container_name, defaults={'content': content, 'dynamic': dynamic})
-                               if not created:
-                                       contentlet.content = content
-                                       contentlet.dynamic = dynamic
-                                       contentlet.save()
-               for container_name, container_content_type in contentreference_containers:
-                       if ('contentreference_container_%s' % container_name) in form.cleaned_data:
-                               content = form.cleaned_data[('contentreference_container_%s' % container_name)]
-                               try:
-                                       contentreference = page.contentreferences.get(name=container_name)
-                               except ContentReference.DoesNotExist:
-                                       contentreference = ContentReference(name=container_name, page=page, content_type=container_content_type)
-                               
-                               if content == None:
-                                       contentreference.content_id = None
-                               else:
-                                       contentreference.content_id = content.id
-                               
-                               contentreference.save()
-
-
-class TemplateAdmin(admin.ModelAdmin):
-       prepopulated_fields = {'slug': ('name',)}
-       fieldsets = (
-               (None, {
-                       'fields': ('parent', 'name', 'slug')
-               }),
-               ('Documentation', {
-                       'classes': COLLAPSE_CLASSES,
-                       'fields': ('documentation',)
-               }),
-               (None, {
-                       'fields': ('code',)
-               }),
-               ('Advanced', {
-                       'classes': COLLAPSE_CLASSES,
-                       'fields': ('mimetype',)
-               }),
-       )
-       save_on_top = True
-       save_as = True
-       list_display = ('__unicode__', 'slug', 'get_path',)
-       form = TreeForm
-
-
-admin.site.register(Collection, CollectionAdmin)
-admin.site.register(Redirect, RedirectAdmin)
-admin.site.register(File, FileAdmin)
-admin.site.register(Page, PageAdmin)
-admin.site.register(Template, TemplateAdmin)
diff --git a/admin/__init__.py b/admin/__init__.py
new file mode 100644 (file)
index 0000000..9039448
--- /dev/null
@@ -0,0 +1,4 @@
+from philo.admin.base import *
+from philo.admin.collections import *
+from philo.admin.nodes import *
+from philo.admin.pages import *
\ No newline at end of file
diff --git a/admin/base.py b/admin/base.py
new file mode 100644 (file)
index 0000000..b6f47a0
--- /dev/null
@@ -0,0 +1,39 @@
+from django.contrib import admin
+#from django.contrib.contenttypes.models import ContentType
+from django.contrib.contenttypes import generic
+#from django import forms
+#from django.conf import settings
+#from django.utils.translation import ugettext as _
+#from django.utils.safestring import mark_safe
+#from django.utils.html import escape
+#from django.utils.text import truncate_words
+from philo.models import *
+#from philo.admin import widgets
+#from django.core.exceptions import ValidationError, ObjectDoesNotExist
+#from validators import TreeParentValidator, TreePositionValidator
+
+
+COLLAPSE_CLASSES = ('collapse', 'collapse-closed', 'closed',)
+
+
+class AttributeInline(generic.GenericTabularInline):
+       ct_field = 'entity_content_type'
+       ct_fk_field = 'entity_object_id'
+       model = Attribute
+       extra = 1
+       template = 'admin/philo/edit_inline/tabular_collapse.html'
+       allow_add = True
+
+
+class RelationshipInline(generic.GenericTabularInline):
+       ct_field = 'entity_content_type'
+       ct_fk_field = 'entity_object_id'
+       model = Relationship
+       extra = 1
+       template = 'admin/philo/edit_inline/tabular_collapse.html'
+       allow_add = True
+
+
+class EntityAdmin(admin.ModelAdmin):
+       inlines = [AttributeInline, RelationshipInline]
+       save_on_top = True
\ No newline at end of file
diff --git a/admin/collections.py b/admin/collections.py
new file mode 100644 (file)
index 0000000..dfc4826
--- /dev/null
@@ -0,0 +1,20 @@
+from django.contrib import admin
+from philo.admin.base import COLLAPSE_CLASSES
+from philo.models import CollectionMember, Collection
+
+
+class CollectionMemberInline(admin.TabularInline):
+       fk_name = 'collection'
+       model = CollectionMember
+       extra = 1
+       classes = COLLAPSE_CLASSES
+       allow_add = True
+       fields = ('member_content_type', 'member_object_id', 'index')
+
+
+class CollectionAdmin(admin.ModelAdmin):
+       inlines = [CollectionMemberInline]
+       list_display = ('name', 'description', 'get_count')
+
+
+admin.site.register(Collection, CollectionAdmin)
\ No newline at end of file
diff --git a/admin/nodes.py b/admin/nodes.py
new file mode 100644 (file)
index 0000000..093537e
--- /dev/null
@@ -0,0 +1,35 @@
+from django.contrib import admin
+from philo.admin.base import EntityAdmin
+from philo.models import Node, Redirect, File
+
+
+class NodeAdmin(EntityAdmin):
+       pass
+
+
+class ViewAdmin(EntityAdmin):
+       pass
+
+
+class RedirectAdmin(ViewAdmin):
+       fieldsets = (
+               (None, {
+                       'fields': ('target', 'status_code')
+               }),
+       )
+       list_display = ('target', 'status_code')
+       list_filter = ('status_code',)
+
+
+class FileAdmin(ViewAdmin):
+       fieldsets = (
+               (None, {
+                       'fields': ('file', 'mimetype')
+               }),
+       )
+       list_display = ('mimetype', 'file')
+
+
+admin.site.register(Node, NodeAdmin)
+admin.site.register(Redirect, RedirectAdmin)
+admin.site.register(File, FileAdmin)
\ No newline at end of file
diff --git a/admin/pages.py b/admin/pages.py
new file mode 100644 (file)
index 0000000..fc60ad1
--- /dev/null
@@ -0,0 +1,118 @@
+from django.contrib import admin
+from django import forms
+from philo.admin import widgets
+from philo.admin.base import COLLAPSE_CLASSES
+from philo.admin.nodes import ViewAdmin
+from philo.models.pages import Page, Template, Contentlet, ContentReference
+
+
+class PageAdmin(ViewAdmin):
+       add_form_template = 'admin/philo/page/add_form.html'
+       fieldsets = (
+               (None, {
+                       'fields': ('title', 'template')
+               }),
+       )
+       list_display = ('title', 'template')
+       list_filter = ('template',)
+       search_fields = ['title', 'contentlets__content']
+       
+       def get_fieldsets(self, request, obj=None, **kwargs):
+               fieldsets = list(self.fieldsets)
+               if obj: # if no obj, creating a new page, thus no template set, thus no containers
+                       template = obj.template
+                       if template.documentation:
+                               fieldsets.append(('Template Documentation', {
+                                       'description': template.documentation
+                               }))
+                       contentlet_containers, contentreference_containers = template.containers
+                       for container_name in contentlet_containers:
+                               fieldsets.append((('Container: %s' % container_name), {
+                                       'fields': (('contentlet_container_content_%s' % container_name), ('contentlet_container_dynamic_%s' % container_name))
+                               }))
+                       for container_name, container_content_type in contentreference_containers:
+                               fieldsets.append((('Container: %s' % container_name), {
+                                       'fields': (('contentreference_container_%s' % container_name),)
+                               }))
+               return fieldsets
+       
+       def get_form(self, request, obj=None, **kwargs):
+               form = super(PageAdmin, self).get_form(request, obj, **kwargs)
+               if obj: # if no obj, creating a new page, thus no template set, thus no containers
+                       page = obj
+                       template = page.template
+                       contentlet_containers, contentreference_containers = template.containers
+                       for container_name in contentlet_containers:
+                               initial_content = None
+                               initial_dynamic = False
+                               try:
+                                       contentlet = page.contentlets.get(name__exact=container_name)
+                                       initial_content = contentlet.content
+                                       initial_dynamic = contentlet.dynamic
+                               except Contentlet.DoesNotExist:
+                                       pass
+                               form.base_fields[('contentlet_container_content_%s' % container_name)] = forms.CharField(label='Content', widget=forms.Textarea(), initial=initial_content, required=False)
+                               form.base_fields[('contentlet_container_dynamic_%s' % container_name)] = forms.BooleanField(label='Dynamic', help_text='Specify whether this content contains dynamic template code', initial=initial_dynamic, required=False)
+                       for container_name, container_content_type in contentreference_containers:
+                               initial_content = None
+                               try:
+                                       initial_content = page.contentreferences.get(name__exact=container_name, content_type=container_content_type).content.pk
+                               except (ContentReference.DoesNotExist, AttributeError):
+                                       pass
+                               form.base_fields[('contentreference_container_%s' % container_name)] = forms.ModelChoiceField(label='References', widget=widgets.ModelLookupWidget(container_content_type), initial=initial_content, required=False, queryset=container_content_type.model_class().objects.all())
+               return form
+       
+       def save_model(self, request, page, form, change):
+               page.save()
+               template = page.template
+               contentlet_containers, contentreference_containers = template.containers
+               for container_name in contentlet_containers:
+                       if (('contentlet_container_content_%s' % container_name) in form.cleaned_data) and (('contentlet_container_dynamic_%s' % container_name) in form.cleaned_data):
+                               content = form.cleaned_data[('contentlet_container_content_%s' % container_name)]
+                               dynamic = form.cleaned_data[('contentlet_container_dynamic_%s' % container_name)]
+                               contentlet, created = page.contentlets.get_or_create(name=container_name, defaults={'content': content, 'dynamic': dynamic})
+                               if not created:
+                                       contentlet.content = content
+                                       contentlet.dynamic = dynamic
+                                       contentlet.save()
+               for container_name, container_content_type in contentreference_containers:
+                       if ('contentreference_container_%s' % container_name) in form.cleaned_data:
+                               content = form.cleaned_data[('contentreference_container_%s' % container_name)]
+                               try:
+                                       contentreference = page.contentreferences.get(name=container_name)
+                               except ContentReference.DoesNotExist:
+                                       contentreference = ContentReference(name=container_name, page=page, content_type=container_content_type)
+                               
+                               if content == None:
+                                       contentreference.content_id = None
+                               else:
+                                       contentreference.content_id = content.id
+                               
+                               contentreference.save()
+
+
+class TemplateAdmin(admin.ModelAdmin):
+       prepopulated_fields = {'slug': ('name',)}
+       fieldsets = (
+               (None, {
+                       'fields': ('parent', 'name', 'slug')
+               }),
+               ('Documentation', {
+                       'classes': COLLAPSE_CLASSES,
+                       'fields': ('documentation',)
+               }),
+               (None, {
+                       'fields': ('code',)
+               }),
+               ('Advanced', {
+                       'classes': COLLAPSE_CLASSES,
+                       'fields': ('mimetype',)
+               }),
+       )
+       save_on_top = True
+       save_as = True
+       list_display = ('__unicode__', 'slug', 'get_path',)
+
+
+admin.site.register(Page, PageAdmin)
+admin.site.register(Template, TemplateAdmin)
\ No newline at end of file
diff --git a/admin/widgets.py b/admin/widgets.py
new file mode 100644 (file)
index 0000000..a1f5bae
--- /dev/null
@@ -0,0 +1,29 @@
+from django import forms
+from django.conf import settings
+
+
+class ModelLookupWidget(forms.TextInput):
+       # is_hidden = False
+       
+       def __init__(self, content_type, attrs=None):
+               self.content_type = content_type
+               super(ModelLookupWidget, self).__init__(attrs)
+       
+       def render(self, name, value, attrs=None):
+               related_url = '../../../%s/%s/' % (self.content_type.app_label, self.content_type.model)
+               if attrs is None:
+                       attrs = {}
+               if not attrs.has_key('class'):
+                       attrs['class'] = 'vForeignKeyRawIdAdminField'
+               output = super(ModelLookupWidget, self).render(name, value, attrs)
+               output += '<a href="%s" class="related-lookup" id="lookup_id_%s" onclick="return showRelatedObjectLookupPopup(this);">' % (related_url, name)
+               output += '<img src="%simg/admin/selector-search.gif" width="16" height="16" alt="%s" />' % (settings.ADMIN_MEDIA_PREFIX, _('Lookup'))
+               output += '</a>'
+               if value:
+                       value_class = self.content_type.model_class()
+                       try:
+                               value_object = value_class.objects.get(pk=value)
+                               output += '&nbsp;<strong>%s</strong>' % escape(truncate_words(value_object, 14))
+                       except value_class.DoesNotExist:
+                               pass
+               return mark_safe(output)
\ No newline at end of file
index 5ca9d93..291fe70 100644 (file)
@@ -3,17 +3,10 @@ from django.contrib.contenttypes.models import ContentType
 from django.contrib.contenttypes import generic
 from django.utils import simplejson as json
 from django.core.exceptions import ObjectDoesNotExist
+from philo.utils import ContentTypeRegistryLimiter
 from UserDict import DictMixin
 
 
-def register_value_model(model):
-       pass
-
-
-def unregister_value_model(model):
-       pass
-
-
 class Attribute(models.Model):
        entity_content_type = models.ForeignKey(ContentType, verbose_name='Entity type')
        entity_object_id = models.PositiveIntegerField(verbose_name='Entity ID')
@@ -39,12 +32,24 @@ class Attribute(models.Model):
                app_label = 'philo'
 
 
+value_content_type_limiter = ContentTypeRegistryLimiter()
+
+
+def register_value_model(model):
+       value_content_type_limiter.register_class(model)
+
+
+def unregister_value_model(model):
+       value_content_type_limiter.unregister_class(model)
+
+
+
 class Relationship(models.Model):
        entity_content_type = models.ForeignKey(ContentType, related_name='relationship_entity_set', verbose_name='Entity type')
        entity_object_id = models.PositiveIntegerField(verbose_name='Entity ID')
        entity = generic.GenericForeignKey('entity_content_type', 'entity_object_id')
        key = models.CharField(max_length=255)
-       value_content_type = models.ForeignKey(ContentType, related_name='relationship_value_set', verbose_name='Value type')
+       value_content_type = models.ForeignKey(ContentType, related_name='relationship_value_set', limit_choices_to=value_content_type_limiter, verbose_name='Value type')
        value_object_id = models.PositiveIntegerField(verbose_name='Value ID')
        value = generic.GenericForeignKey('value_content_type', 'value_object_id')
        
@@ -144,6 +149,7 @@ class TreeModel(models.Model):
                return self.path
        
        class Meta:
+               unique_together = (('parent', 'slug'),)
                abstract = True
                app_label = 'philo'
 
@@ -161,47 +167,6 @@ class TreeEntity(TreeModel, Entity):
                        return QuerySetMapper(self.relationship_set, passthrough=self.parent.relationships)
                return super(TreeEntity, self).relationships
        
-       class Meta:
-               abstract = True
-               app_label = 'philo'
-
-
-class InheritableTreeEntity(TreeEntity):
-       instance_type = models.ForeignKey(ContentType, editable=False)
-       
-       def save(self, force_insert=False, force_update=False):
-               if not hasattr(self, 'instance_type_ptr'):
-                       self.instance_type = ContentType.objects.get_for_model(self.__class__)
-               super(InheritableTreeEntity, self).save(force_insert, force_update)
-       
-       @property
-       def instance(self):
-               try:
-                       return self.instance_type.get_object_for_this_type(id=self.id)
-               except:
-                       return None
-       
-       def get_path(self, pathsep='/', field='slug'):
-               path = getattr(self.instance, field, getattr(self.instance, 'slug', '?'))
-               parent = self.parent
-               while parent:
-                       path = getattr(parent.instance, field, getattr(parent.instance, 'slug', '?')) + pathsep + path
-                       parent = parent.parent
-               return path
-       path = property(get_path)
-       
-       @property
-       def attributes(self):
-               if self.parent:
-                       return QuerySetMapper(self.instance.attribute_set, passthrough=self.parent.instance.attributes)
-               return QuerySetMapper(self.instance.attribute_set)
-
-       @property
-       def relationships(self):
-               if self.parent:
-                       return QuerySetMapper(self.instance.relationship_set, passthrough=self.parent.instance.relationships)
-               return QuerySetMapper(self.instance.relationship_set)
-       
        class Meta:
                abstract = True
                app_label = 'philo'
\ No newline at end of file
index 61b2608..9a737eb 100644 (file)
@@ -1,7 +1,7 @@
 from django.db import models
 from django.contrib.contenttypes.models import ContentType
 from django.contrib.contenttypes import generic
-from philo.models.base import register_value_model
+from philo.models.base import value_content_type_limiter, register_value_model
 from philo.utils import fattr
 
 
@@ -31,7 +31,7 @@ class CollectionMember(models.Model):
        objects = CollectionMemberManager()
        collection = models.ForeignKey(Collection, related_name='members')
        index = models.PositiveIntegerField(verbose_name='Index', help_text='This will determine the ordering of the item within the collection. (Optional)', null=True, blank=True)
-       member_content_type = models.ForeignKey(ContentType, verbose_name='Member type')
+       member_content_type = models.ForeignKey(ContentType, limit_choices_to=value_content_type_limiter, verbose_name='Member type')
        member_object_id = models.PositiveIntegerField(verbose_name='Member ID')
        member = generic.GenericForeignKey('member_content_type', 'member_object_id')
        
index 78df3d6..bb1601e 100644 (file)
@@ -1,19 +1,30 @@
 from django.db import models
+from django.contrib.contenttypes.models import ContentType
+from django.contrib.contenttypes import generic
 from django.contrib.sites.models import Site
 from django.http import HttpResponse, HttpResponseServerError, HttpResponseRedirect
 from django.core.servers.basehttp import FileWrapper
-from philo.models.base import InheritableTreeEntity
+from philo.models.base import TreeEntity, Entity
+from philo.utils import ContentTypeSubclassLimiter
 from philo.validators import RedirectValidator
 
 
-class Node(InheritableTreeEntity):
-       accepts_subpath = False
+_view_content_type_limiter = ContentTypeSubclassLimiter(None)
+
+
+class Node(TreeEntity):
+       view_content_type = models.ForeignKey(ContentType, related_name='node_view_set', limit_choices_to=_view_content_type_limiter)
+       view_object_id = models.PositiveIntegerField()
+       view = generic.GenericForeignKey('view_content_type', 'view_object_id')
+       
+       @property
+       def accepts_subpath(self):
+               return self.view.accepts_subpath
        
        def render_to_response(self, request, path=None, subpath=None):
-               return HttpResponseServerError()
-               
+               return self.view.render_to_response(self, request, path, subpath)
+       
        class Meta:
-               unique_together = (('parent', 'slug'),)
                app_label = 'philo'
 
 
@@ -21,12 +32,28 @@ class Node(InheritableTreeEntity):
 models.ForeignKey(Node, related_name='sites', null=True, blank=True).contribute_to_class(Site, 'root_node')
 
 
-class MultiNode(Node):
+class View(Entity):
+       nodes = generic.GenericRelation(Node, content_type_field='view_content_type', object_id_field='view_object_id')
+       
+       accepts_subpath = False
+       
+       def render_to_response(self, node, request, path=None, subpath=None):
+               raise NotImplementedError('View subclasses must implement render_to_response.')
+       
+       class Meta:
+               abstract = True
+               app_label = 'philo'
+
+
+_view_content_type_limiter.cls = View
+
+
+class MultiView(View):
        accepts_subpath = True
        
        urlpatterns = []
        
-       def render_to_response(self, request, path=None, subpath=None):
+       def render_to_response(self, node, request, path=None, subpath=None):
                if not subpath:
                        subpath = ""
                subpath = "/" + subpath
@@ -39,15 +66,15 @@ class MultiNode(Node):
                app_label = 'philo'
 
 
-class Redirect(Node):
+class Redirect(View):
        STATUS_CODES = (
                (302, 'Temporary'),
                (301, 'Permanent'),
        )
-       target = models.CharField(max_length=200,validators=[RedirectValidator()])
+       target = models.CharField(max_length=200, validators=[RedirectValidator()])
        status_code = models.IntegerField(choices=STATUS_CODES, default=302, verbose_name='redirect type')
        
-       def render_to_response(self, request, path=None, subpath=None):
+       def render_to_response(self, node, request, path=None, subpath=None):
                response = HttpResponseRedirect(self.target)
                response.status_code = self.status_code
                return response
@@ -56,12 +83,13 @@ class Redirect(Node):
                app_label = 'philo'
 
 
-class File(Node):
+class File(View):
        """ For storing arbitrary files """
+       
        mimetype = models.CharField(max_length=255)
        file = models.FileField(upload_to='philo/files/%Y/%m/%d')
        
-       def render_to_response(self, request, path=None, subpath=None):
+       def render_to_response(self, node, request, path=None, subpath=None):
                wrapper = FileWrapper(self.file)
                response = HttpResponse(wrapper, content_type=self.mimetype)
                response['Content-Length'] = self.file.size
index 2ba56d0..b9e481b 100644 (file)
@@ -11,7 +11,7 @@ from django.template.loader import get_template
 from django.template.loader_tags import ExtendsNode, ConstantIncludeNode, IncludeNode
 from django.http import HttpResponse
 from philo.models.base import TreeModel, register_value_model
-from philo.models.nodes import Node
+from philo.models.nodes import View
 from philo.utils import fattr
 from philo.templatetags.containers import ContainerNode
 
@@ -90,18 +90,18 @@ class Template(TreeModel):
                app_label = 'philo'
 
 
-class Page(Node):
+class Page(View):
        """
        Represents a page - something which is rendered according to a template. The page will have a number of related Contentlets depending on the template selected - but these will appear only after the page has been saved with that template.
        """
        template = models.ForeignKey(Template, related_name='pages')
        title = models.CharField(max_length=255)
        
-       def render_to_response(self, request, path=None, subpath=None):
+       def render_to_response(self, node, request, path=None, subpath=None):
                return HttpResponse(self.template.django_template.render(RequestContext(request, {'page': self})), mimetype=self.template.mimetype)
        
        def __unicode__(self):
-               return self.get_path(u' › ', 'title')
+               return self.title
        
        class Meta:
                app_label = 'philo'
index ed8c54e..8b73293 100644 (file)
@@ -19,8 +19,8 @@ class MembersofNode(template.Node):
                except:
                        pass
                return settings.TEMPLATE_STRING_IF_INVALID
-       
-       
+
+
 def do_membersof(parser, token):
        """
        {% membersof <collection> with <model> as <var> %}
diff --git a/urls.py b/urls.py
index c4fcb5e..47be7da 100644 (file)
--- a/urls.py
+++ b/urls.py
@@ -1,4 +1,4 @@
-from django.conf.urls.defaults import url, include, patterns, handler404, handler500
+from django.conf.urls.defaults import patterns, url
 from philo.views import node_view
 
 
index e3d1124..5fc3274 100644 (file)
--- a/utils.py
+++ b/utils.py
@@ -1,3 +1,58 @@
+from django.db import models
+from django.contrib.contenttypes.models import ContentType
+
+
+class ContentTypeLimiter(object):
+       def q_object(self):
+               return models.Q(pk__in=[])
+       
+       def add_to_query(self, query, *args, **kwargs):
+               query.add_q(self.q_object(), *args, **kwargs)
+
+
+class ContentTypeRegistryLimiter(ContentTypeLimiter):
+       def __init__(self):
+               self.classes = []
+       
+       def register_class(self, cls):
+               self.classes.append(cls)
+       
+       def unregister_class(self, cls):
+               self.classes.remove(cls)
+       
+       def q_object(self):
+               contenttype_pks = []
+               for cls in self.classes:
+                       try:
+                               if issubclass(cls, models.Model):
+                                       if not cls._meta.abstract:
+                                               contenttype = ContentType.objects.get_for_model(cls)
+                                               contenttype_pks.append(contenttype.pk)
+                       except:
+                               pass
+               return models.Q(pk__in=contenttype_pks)
+
+
+class ContentTypeSubclassLimiter(ContentTypeLimiter):
+       def __init__(self, cls, inclusive=False):
+               self.cls = cls
+               self.inclusive = inclusive
+       
+       def q_object(self):
+               contenttype_pks = []
+               for subclass in self.cls.__subclasses__():
+                       try:
+                               if issubclass(subclass, models.Model):
+                                       if not subclass._meta.abstract:
+                                               if not self.inclusive and subclass is self.cls:
+                                                       continue
+                                               contenttype = ContentType.objects.get_for_model(subclass)
+                                               contenttype_pks.append(contenttype.pk)
+                       except:
+                               pass
+               return models.Q(pk__in=contenttype_pks)
+
+
 def fattr(*args, **kwargs):
        def wrapper(function):
                for key in kwargs:
index 0c6fa78..637dba8 100644 (file)
@@ -1,85 +1,8 @@
-from django.core.exceptions import ValidationError
 from django.utils.translation import ugettext_lazy as _
 from django.core.validators import RegexValidator
 import re
 
 
-class TreeParentValidator(object):
-       """
-       constructor takes instance and parent_attr, where instance is the model
-       being validated and parent_attr is where to look on that parent for the
-       comparison.
-       """
-       #message = _("A tree element can't be its own parent.")
-       code = 'invalid'
-       
-       def __init__(self, instance, parent_attr=None, message=None, code=None):
-               self.instance = instance
-               self.parent_attr = parent_attr
-               self.static_message = message
-               if code is not None:
-                       self.code = code
-       
-       def __call__(self, value):
-               """
-               Validates that the self.instance is not found in the parent tree of
-               the node given as value.
-               """
-               parent = value
-               
-               while parent:
-                       comparison=self.get_comparison(parent)
-                       if comparison == self.instance:
-                               # using (self.message, code=self.code) results in the admin interface
-                               # screwing with the error message and making it be 'Enter a valid value'
-                               raise ValidationError(self.message)
-                       parent=parent.parent
-       
-       def get_comparison(self, parent):
-               if self.parent_attr and hasattr(parent, self.parent_attr):
-                       return getattr(parent, self.parent_attr)
-               
-               return parent
-       
-       def get_message(self):
-               return self.static_message or _(u"A %s can't be its own parent." % self.instance.__class__.__name__)
-       message = property(get_message)
-
-
-class TreePositionValidator(object):
-       code = 'invalid'
-       
-       def __init__(self, parent, slug, obj_class, message=None, code=None):
-               self.parent = parent
-               self.slug = slug
-               self.obj_class = obj_class
-               self.static_message = message
-               
-               if code is not None:
-                       self.code = code
-       
-       def __call__(self, value):
-               """
-               Validates that there is no obj of obj_class with the same position
-               as the compared obj (value) but a different id.
-               """
-               if not isinstance(value, self.obj_class):
-                       raise ValidationError(_(u"The value must be an instance of %s." % self.obj_class.__name__))
-               
-               try:
-                       obj = self.obj_class.objects.get(slug=self.slug, parent=self.parent)
-                       
-                       if obj.id != value.id:
-                               raise ValidationError(self.message)
-               
-               except self.obj_class.DoesNotExist:
-                       pass
-       
-       def get_message(self):
-               return self.static_message or _(u"A %s with that path (parent and slug) already exists." % self.obj_class.__name__)
-       message = property(get_message)
-
-
 class RedirectValidator(RegexValidator):
        """Based loosely on the URLValidator, but no option to verify_exists"""
        regex = re.compile(
index f086bfd..911adfe 100644 (file)
--- a/views.py
+++ b/views.py
@@ -17,6 +17,6 @@ def node_view(request, path=None, **kwargs):
                raise Http404
        if not node:
                raise Http404
-       if subpath and not node.instance.accepts_subpath:
+       if subpath and not node.accepts_subpath:
                raise Http404
-       return node.instance.render_to_response(request, path=path, subpath=subpath)
+       return node.render_to_response(request, path=path, subpath=subpath)