Added support for Node subclasses whose instances accept subpaths, and created a...
[philo.git] / models.py
index cb8ed07..6691aca 100644 (file)
--- a/models.py
+++ b/models.py
@@ -5,12 +5,11 @@ from django.contrib.contenttypes import generic
 from django.contrib.contenttypes.models import ContentType
 from django.db import models
 from django.contrib.sites.models import Site
-import mptt
 from utils import fattr
 from django.template import add_to_builtins as register_templatetags
 from django.template import Template as DjangoTemplate
 from django.template import TemplateDoesNotExist
-from django.template import Context
+from django.template import Context, RequestContext
 from django.core.exceptions import ObjectDoesNotExist
 try:
        import json
@@ -18,8 +17,10 @@ except ImportError:
        import simplejson as json
 from UserDict import DictMixin
 from templatetags.containers import ContainerNode
-from django.template.loader_tags import ExtendsNode, ConstantIncludeNode, IncludeNode, BlockNode
+from django.template.loader_tags import ExtendsNode, ConstantIncludeNode, IncludeNode
 from django.template.loader import get_template
+from django.http import Http404, HttpResponse, HttpResponseServerError, HttpResponseRedirect
+from django.core.servers.basehttp import FileWrapper
 
 
 def _ct_model_name(model):
@@ -84,7 +85,7 @@ class QuerySetMapper(object, DictMixin):
                self.passthrough = passthrough
        def __getitem__(self, key):
                try:
-                       return queryset.get(key__exact=key)
+                       return self.queryset.get(key__exact=key).value
                except ObjectDoesNotExist:
                        if self.passthrough:
                                return self.passthrough.__getitem__(key)
@@ -161,18 +162,33 @@ class TreeManager(models.Manager):
        def roots(self):
                return self.filter(parent__isnull=True)
        
-       def get_with_path(self, path, root=None, pathsep='/'):
+       def get_with_path(self, path, root=None, absolute_result=True, pathsep='/'):
+               """
+               Returns the object with the path, or None if there is no object with that path,
+               unless absolute_result is set to False, in which case it returns a tuple containing
+               the deepest object found along the path, and the remainder of the path after that
+               object as a string (or None in the case that there is no remaining path).
+               """
                slugs = path.split(pathsep)
                obj = root
+               remaining_slugs = list(slugs)
+               remainder = None
                for slug in slugs:
+                       remaining_slugs.remove(slug)
                        if slug: # ignore blank slugs, handles for multiple consecutive pathseps
                                try:
                                        obj = self.get(slug__exact=slug, parent__exact=obj)
                                except self.model.DoesNotExist:
-                                       obj = None
+                                       if absolute_result:
+                                               obj = None
+                                       remaining_slugs.insert(0, slug)
+                                       remainder = pathsep.join(remaining_slugs)
                                        break
                if obj:
-                       return obj
+                       if absolute_result:
+                               return obj
+                       else:
+                               return (obj, remainder)
                raise self.model.DoesNotExist('%s matching query does not exist.' % self.model._meta.object_name)
 
 
@@ -202,18 +218,81 @@ class TreeEntity(TreeModel, Entity):
        def attributes(self):
                if self.parent:
                        return QuerySetMapper(self.attribute_set, passthrough=self.parent.attributes)
-               return super(Entity, self).attributes()
+               return super(TreeEntity, self).attributes
        
        @property
        def relationships(self):
                if self.parent:
                        return QuerySetMapper(self.relationship_set, passthrough=self.parent.relationships)
-               return super(Entity, self).relationships()
+               return super(TreeEntity, self).relationships
+       
+       class Meta:
+               abstract = True
+
+
+class Node(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(Node, self).save(force_insert, force_update)
+       
+       @property
+       def instance(self):
+               return self.instance_type.get_object_for_this_type(id=self.id)
+       
+       accepts_subpath = False
+       
+       def render_to_response(self, request, path=None, subpath=None):
+               return HttpResponseServerError()
+
+
+class MultiNode(Node):
+       accepts_subpath = True
+       
+       @property
+       def urlpatterns(self):
+               return []
+       
+       def render_to_response(self, request, path=None, subpath=None):
+               if not subpath:
+                       subpath = ""
+               subpath = "/" + subpath
+               from django.core.urlresolvers import resolve
+               view, args, kwargs = resolve(subpath, urlconf=self)
+               return view(request, *args, **kwargs)
        
        class Meta:
                abstract = True
 
 
+class Redirect(Node):
+       STATUS_CODES = (
+               (302, 'Temporary'),
+               (301, 'Permanent'),
+       )
+       target = models.URLField()
+       status_code = models.IntegerField(choices=STATUS_CODES, default=302, verbose_name='redirect type')
+       
+       def render_to_response(self, request, path=None, subpath=None):
+               response = HttpResponseRedirect(self.target)
+               response.status_code = self.status_code
+               return response
+
+
+class File(Node):
+       """ 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):
+               wrapper = FileWrapper(self.file)
+               response = HttpResponse(wrapper, content_type=self.mimetype)
+               response['Content-Length'] = self.file.size
+               return response
+
+
 class Template(TreeModel):
        name = models.CharField(max_length=255)
        documentation = models.TextField(null=True, blank=True)
@@ -240,10 +319,12 @@ class Template(TreeModel):
                                names = []
                                for node in nodelist:
                                        try:
+                                               for nodelist_name in ('nodelist', 'nodelist_loop', 'nodelist_empty', 'nodelist_true', 'nodelist_false'):
+                                                       if hasattr(node, nodelist_name):
+                                                               names.extend(nodelist_container_node_names(getattr(node, nodelist_name)))
                                                if isinstance(node, ContainerNode):
                                                        names.append(node.name)
                                                elif isinstance(node, ExtendsNode):
-                                                       names.extend(nodelist_container_node_names(node.nodelist))
                                                        extended_template = node.get_parent(Context())
                                                        if extended_template:
                                                                names.extend(container_node_names(extended_template))
@@ -255,8 +336,6 @@ class Template(TreeModel):
                                                        included_template = get_template(node.template_name.resolve(Context()))
                                                        if included_template:
                                                                names.extend(container_node_names(included_template))
-                                               elif isinstance(node, BlockNode):
-                                                       names.extend(nodelist_container_node_names(node.nodelist))
                                        except:
                                                pass # fail for this node
                                return names
@@ -274,20 +353,21 @@ class Template(TreeModel):
                except Template.DoesNotExist:
                        raise TemplateDoesNotExist(template_name)
                return (template.code, template.origin)
-mptt.register(Template)
 
 
-class Page(TreeEntity):
+class Page(Node):
        template = models.ForeignKey(Template, related_name='pages')
        title = models.CharField(max_length=255)
        
+       def render_to_response(self, 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')
-mptt.register(Page)
 
 
-# the following line enables the selection of a page as the root for a given django.contrib.sites Site object
-models.ForeignKey(Page, related_name='sites', null=True, blank=True).contribute_to_class(Site, 'root_page')
+# the following line enables the selection of a node as the root for a given django.contrib.sites Site object
+models.ForeignKey(Node, related_name='sites', null=True, blank=True).contribute_to_class(Site, 'root_node')
 
 
 class Contentlet(models.Model):