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 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
 
 
 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')
 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'
 
 
                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)
 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')
        
        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:
                return self.path
        
        class Meta:
+               unique_together = (('parent', 'slug'),)
                abstract = True
                app_label = 'philo'
 
                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
        
                        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
        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 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
 
 
 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)
        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')
        
        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.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 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
 
 
 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):
        
        def render_to_response(self, request, path=None, subpath=None):
-               return HttpResponseServerError()
-               
+               return self.view.render_to_response(self, request, path, subpath)
+       
        class Meta:
        class Meta:
-               unique_together = (('parent', 'slug'),)
                app_label = 'philo'
 
 
                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')
 
 
 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 = []
        
        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
                if not subpath:
                        subpath = ""
                subpath = "/" + subpath
@@ -39,15 +66,15 @@ class MultiNode(Node):
                app_label = 'philo'
 
 
                app_label = 'philo'
 
 
-class Redirect(Node):
+class Redirect(View):
        STATUS_CODES = (
                (302, 'Temporary'),
                (301, 'Permanent'),
        )
        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')
        
        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
                response = HttpResponseRedirect(self.target)
                response.status_code = self.status_code
                return response
@@ -56,12 +83,13 @@ class Redirect(Node):
                app_label = 'philo'
 
 
                app_label = 'philo'
 
 
-class File(Node):
+class File(View):
        """ For storing arbitrary files """
        """ For storing arbitrary files """
+       
        mimetype = models.CharField(max_length=255)
        file = models.FileField(upload_to='philo/files/%Y/%m/%d')
        
        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
                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 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
 
 from philo.utils import fattr
 from philo.templatetags.containers import ContainerNode
 
@@ -90,18 +90,18 @@ class Template(TreeModel):
                app_label = 'philo'
 
 
                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)
        
        """
        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 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'
        
        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
                except:
                        pass
                return settings.TEMPLATE_STRING_IF_INVALID
-       
-       
+
+
 def do_membersof(parser, token):
        """
        {% membersof <collection> with <model> as <var> %}
 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
 
 
 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:
 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
 
 
 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(
 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
                raise Http404
        if not node:
                raise Http404
-       if subpath and not node.instance.accepts_subpath:
+       if subpath and not node.accepts_subpath:
                raise Http404
                raise Http404
-       return node.instance.render_to_response(request, path=path, subpath=subpath)
+       return node.render_to_response(request, path=path, subpath=subpath)