Fixed a bug where the ContainerNode had a nodelist_empty=None, whereas nodelist_empty...
[philo.git] / models.py
index bacaefd..dcb5d74 100644 (file)
--- a/models.py
+++ b/models.py
@@ -21,28 +21,15 @@ from django.template.loader_tags import ExtendsNode, ConstantIncludeNode, Includ
 from django.template.loader import get_template
 from django.http import Http404, HttpResponse, HttpResponseServerError, HttpResponseRedirect
 from django.core.servers.basehttp import FileWrapper
-
-
-_value_models = {}
-_value_models_ct_pks = []
+from django.conf import settings
 
 
 def register_value_model(model):
-       if issubclass(model, models.Model):
-               if model not in _value_models:
-                       _value_models[model] = ContentType.objects.get_for_model(model)
-                       _value_models_ct_pks.append(_value_models[model].pk)
-       else:
-               raise TypeError('philo.models.register_value_model only accepts subclasses of django.db.models.Model')
+       pass
 
 
 def unregister_value_model(model):
-       if issubclass(model, models.Model):
-               if model in _value_models:
-                       _value_models_ct_pks.remove(_value_models[model].pk)
-                       del _value_models[model]
-       else:
-               raise TypeError('philo.models.unregister_value_model only accepts subclasses of django.db.models.Model')
+       pass
 
 
 class Attribute(models.Model):
@@ -72,7 +59,7 @@ class Relationship(models.Model):
        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', limit_choices_to={'pk__in': _value_models_ct_pks}, verbose_name='Value type')
+       value_content_type = models.ForeignKey(ContentType, related_name='relationship_value_set', verbose_name='Value type')
        value_object_id = models.PositiveIntegerField(verbose_name='Value ID')
        value = generic.GenericForeignKey('value_content_type', 'value_object_id')
        
@@ -123,17 +110,14 @@ class CollectionMemberManager(models.Manager):
        use_for_related_fields = True
 
        def with_model(self, model):
-               if model in _value_models:
-                       return model._default_manager.filter(pk__in=self.filter(member_content_type=_value_models[model]).values_list('member_object_id', flat=True))
-               else:
-                       raise TypeError('CollectionMemberManager.with_model only accepts models previously registered with philo.models.register_value_model')
+               return model._default_manager.filter(pk__in=self.filter(member_content_type=ContentType.objects.get_for_model(model)).values_list('member_object_id', flat=True))
 
 
 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, limit_choices_to={'pk__in': _value_models_ct_pks}, verbose_name='Member type')
+       member_content_type = models.ForeignKey(ContentType, verbose_name='Member type')
        member_object_id = models.PositiveIntegerField(verbose_name='Member ID')
        member = generic.GenericForeignKey('member_content_type', 'member_object_id')
 
@@ -180,10 +164,10 @@ class TreeModel(models.Model):
        slug = models.SlugField()
        
        def get_path(self, pathsep='/', field='slug'):
-               path = getattr(self, field)
+               path = getattr(self, field, '?')
                parent = self.parent
                while parent:
-                       path = getattr(parent, field) + pathsep + path
+                       path = getattr(parent, field, '?') + pathsep + path
                        parent = parent.parent
                return path
        path = property(get_path)
@@ -212,18 +196,44 @@ class TreeEntity(TreeModel, Entity):
                abstract = True
 
 
-class Node(TreeEntity):
+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(Node, self).save(force_insert, force_update)
+               super(InheritableTreeEntity, self).save(force_insert, force_update)
        
        @property
        def instance(self):
                return self.instance_type.get_object_for_this_type(id=self.id)
        
+       def get_path(self, pathsep='/', field='slug'):
+               path = getattr(self.instance, field, '?')
+               parent = self.parent
+               while parent:
+                       path = getattr(parent.instance, field, '?') + 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
+
+
+class Node(InheritableTreeEntity):
        accepts_subpath = False
        
        def render_to_response(self, request, path=None, subpath=None):
@@ -252,7 +262,7 @@ class Redirect(Node):
                (302, 'Temporary'),
                (301, 'Permanent'),
        )
-       target = models.URLField()
+       target = models.URLField(help_text='Must be a valid, absolute URL (i.e. http://)')
        status_code = models.IntegerField(choices=STATUS_CODES, default=302, verbose_name='redirect type')
        
        def render_to_response(self, request, path=None, subpath=None):
@@ -276,8 +286,9 @@ class File(Node):
 class Template(TreeModel):
        name = models.CharField(max_length=255)
        documentation = models.TextField(null=True, blank=True)
-       mimetype = models.CharField(max_length=255, null=True, blank=True)
-       code = models.TextField()
+       mimetype = models.CharField(max_length=255, null=True, blank=True,
+           help_text='Default: %s' % settings.DEFAULT_CONTENT_TYPE)
+       code = models.TextField(verbose_name='django template code')
        
        @property
        def origin(self):
@@ -290,37 +301,46 @@ class Template(TreeModel):
        @property
        def containers(self):
                """
-               Returns a list of names of contentlets referenced by containers. 
+               Returns a tuple where the first item is a list of names of contentlets referenced by containers,
+               and the second item is a list of tuples of names and contenttypes of contentreferences referenced by containers.
                This will break if there is a recursive extends or includes in the template code.
                Due to the use of an empty Context, any extends or include tags with dynamic arguments probably won't work.
                """
-               def container_node_names(template):
-                       def nodelist_container_node_names(nodelist):
-                               names = []
+               def container_nodes(template):
+                       def nodelist_container_nodes(nodelist):
+                               nodes = []
                                for node in nodelist:
                                        try:
-                                               for nodelist_name in ('nodelist', 'nodelist_loop', 'nodelist_empty', 'nodelist_true', 'nodelist_false'):
+                                               for nodelist_name in ('nodelist', 'nodelist_loop', 'nodelist_empty', 'nodelist_true', 'nodelist_false', 'nodelist_main'):
                                                        if hasattr(node, nodelist_name):
-                                                               names.extend(nodelist_container_node_names(getattr(node, nodelist_name)))
+                                                               nodes.extend(nodelist_container_nodes(getattr(node, nodelist_name)))
                                                if isinstance(node, ContainerNode):
-                                                       names.append(node.name)
+                                                       nodes.append(node)
                                                elif isinstance(node, ExtendsNode):
                                                        extended_template = node.get_parent(Context())
                                                        if extended_template:
-                                                               names.extend(container_node_names(extended_template))
+                                                               nodes.extend(container_nodes(extended_template))
                                                elif isinstance(node, ConstantIncludeNode):
                                                        included_template = node.template
                                                        if included_template:
-                                                               names.extend(container_node_names(included_template))
+                                                               nodes.extend(container_nodes(included_template))
                                                elif isinstance(node, IncludeNode):
                                                        included_template = get_template(node.template_name.resolve(Context()))
                                                        if included_template:
-                                                               names.extend(container_node_names(included_template))
+                                                               nodes.extend(container_nodes(included_template))
                                        except:
                                                pass # fail for this node
-                               return names
-                       return nodelist_container_node_names(template.nodelist)
-               return set(container_node_names(self.django_template))
+                               return nodes
+                       return nodelist_container_nodes(template.nodelist)
+               all_nodes = container_nodes(self.django_template)
+               contentlet_node_names = set([node.name for node in all_nodes if not node.references])
+               contentreference_node_names = []
+               contentreference_node_specs = []
+               for node in all_nodes:
+                       if node.references and node.name not in contentreference_node_names:
+                               contentreference_node_specs.append((node.name, node.references))
+                               contentreference_node_names.append(node.name)
+               return contentlet_node_names, contentreference_node_specs
        
        def __unicode__(self):
                return self.get_path(u' › ', 'name')
@@ -336,6 +356,11 @@ class Template(TreeModel):
 
 
 class Page(Node):
+       """
+       Represents an HTML page. 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)
        
@@ -357,6 +382,14 @@ class Contentlet(models.Model):
        dynamic = models.BooleanField(default=False)
 
 
+class ContentReference(models.Model):
+       page = models.ForeignKey(Page, related_name='contentreferences')
+       name = models.CharField(max_length=255)
+       content_type = models.ForeignKey(ContentType, verbose_name='Content type')
+       content_id = models.PositiveIntegerField(verbose_name='Content ID')
+       content = generic.GenericForeignKey('content_type', 'content_id')
+
+
 register_templatetags('philo.templatetags.containers')