Prerequisites:
* Python 2.5.4+ <http://www.python.org/>
- * simplejson <http://code.google.com/p/simplejson/> (Not required with Python 2.6+)
* Django 1.1.1+ <http://www.djangoproject.com/>
* (Optional) django-grappelli 2.0+ <http://code.google.com/p/django-grappelli/>
-from models import Template
+from philo.models import Template
load_template_source = Template.loader
from django.utils.safestring import mark_safe
from django.utils.html import escape
from django.utils.text import truncate_words
-from models import *
+from philo.models import *
class AttributeInline(generic.GenericTabularInline):
ct_fk_field = 'entity_object_id'
model = Attribute
extra = 1
- classes = ('collapse-closed',)
+ template = 'admin/philo/edit_inline/tabular_collapse.html'
allow_add = True
ct_fk_field = 'entity_object_id'
model = Relationship
extra = 1
- classes = ('collapse-closed',)
+ template = 'admin/philo/edit_inline/tabular_collapse.html'
allow_add = True
inlines = [CollectionMemberInline]
-class TemplateAdmin(admin.ModelAdmin):
- prepopulated_fields = {'slug': ('name',)}
- fieldsets = (
- (None, {
- 'fields': ('parent', 'name', 'slug')
- }),
- ('Documentation', {
- 'classes': ('collapse', 'collapse-closed'),
- 'fields': ('documentation',)
- }),
- (None, {
- 'fields': ('code',)
- }),
- ('Advanced', {
- 'classes': ('collapse','collapse-closed'),
- 'fields': ('mimetype',)
- }),
- )
- save_on_top = True
- save_as = True
+class NodeAdmin(EntityAdmin):
+ pass
class ModelLookupWidget(forms.TextInput):
return mark_safe(output)
-class PageAdmin(EntityAdmin):
+class RedirectAdmin(NodeAdmin):
+ fieldsets = (
+ (None, {
+ 'fields': ('slug', 'target', 'status_code')
+ }),
+ ('URL/Tree/Hierarchy', {
+ 'classes': ('collapse', 'collapse-closed'),
+ 'fields': ('parent',)
+ }),
+ )
+
+
+class FileAdmin(NodeAdmin):
+ prepopulated_fields = {'slug': ('file',)}
+ fieldsets = (
+ (None, {
+ 'fields': ('file', 'slug', 'mimetype')
+ }),
+ ('URL/Tree/Hierarchy', {
+ 'classes': ('collapse', 'collapse-closed'),
+ 'fields': ('parent',)
+ }),
+ )
+
+
+class PageAdmin(NodeAdmin):
+ add_form_template = 'admin/philo/page/add_form.html'
prepopulated_fields = {'slug': ('title',)}
fieldsets = (
(None, {
- 'fields': ('title', 'template')
+ 'fields': ('title', 'slug', 'template')
}),
('URL/Tree/Hierarchy', {
'classes': ('collapse', 'collapse-closed'),
- 'fields': ('parent', 'slug')
+ 'fields': ('parent',)
}),
)
list_display = ('title', 'path', 'template')
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
- page = obj
- template = page.template
+ 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), {
contentreference.save()
+class TemplateAdmin(admin.ModelAdmin):
+ prepopulated_fields = {'slug': ('name',)}
+ fieldsets = (
+ (None, {
+ 'fields': ('parent', 'name', 'slug')
+ }),
+ ('Documentation', {
+ 'classes': ('collapse', 'collapse-closed'),
+ 'fields': ('documentation',)
+ }),
+ (None, {
+ 'fields': ('code',)
+ }),
+ ('Advanced', {
+ 'classes': ('collapse','collapse-closed'),
+ 'fields': ('mimetype',)
+ }),
+ )
+ save_on_top = True
+ save_as = True
+
+
admin.site.register(Collection, CollectionAdmin)
-admin.site.register(Redirect)
-admin.site.register(File)
+admin.site.register(Redirect, RedirectAdmin)
+admin.site.register(File, FileAdmin)
admin.site.register(Page, PageAdmin)
admin.site.register(Template, TemplateAdmin)
from django.contrib.contenttypes.models import ContentType
from django.db import models
from django.contrib.sites.models import Site
-from utils import fattr
+from philo.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, RequestContext
from django.core.exceptions import ObjectDoesNotExist
-try:
- import json
-except ImportError:
- import simplejson as json
+from django.utils import simplejson as json
from UserDict import DictMixin
-from templatetags.containers import ContainerNode
+from philo.templatetags.containers import ContainerNode
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
+from django.conf import settings
def register_value_model(model):
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(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
return path
path = property(get_path)
- 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)
+ 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):
(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):
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):
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):
nodes.extend(nodelist_container_nodes(getattr(node, nodelist_name)))
if isinstance(node, ContainerNode):
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)
--- /dev/null
+{% load i18n adminmedia %}
+<div class="inline-group" id="{{ inline_admin_formset.formset.prefix }}-group">
+ <div class="tabular inline-related {% if forloop.last %}last-related{% endif %}">
+{{ inline_admin_formset.formset.management_form }}
+<fieldset class="module collapse collapse-closed">
+ <h2>{{ inline_admin_formset.opts.verbose_name_plural|capfirst }}</h2>
+ {{ inline_admin_formset.formset.non_form_errors }}
+ <table>
+ <thead><tr>
+ {% for field in inline_admin_formset.fields %}
+ {% if not field.widget.is_hidden %}
+ <th{% if forloop.first %} colspan="2"{% endif %}{% if field.required %} class="required"{% endif %}>{{ field.label|capfirst }}</th>
+ {% endif %}
+ {% endfor %}
+ {% if inline_admin_formset.formset.can_delete %}<th>{% trans "Delete?" %}</th>{% endif %}
+ </tr></thead>
+
+ <tbody>
+ {% for inline_admin_form in inline_admin_formset %}
+ {% if inline_admin_form.form.non_field_errors %}
+ <tr><td colspan="{{ inline_admin_form.field_count }}">{{ inline_admin_form.form.non_field_errors }}</td></tr>
+ {% endif %}
+ <tr class="{% cycle "row1" "row2" %} {% if inline_admin_form.original or inline_admin_form.show_url %}has_original{% endif %}{% if forloop.last %} empty-form{% endif %}"
+ id="{{ inline_admin_formset.formset.prefix }}-{% if not forloop.last %}{{ forloop.counter0 }}{% else %}empty{% endif %}">
+ <td class="original">
+ {% if inline_admin_form.original or inline_admin_form.show_url %}<p>
+ {% if inline_admin_form.original %} {{ inline_admin_form.original }}{% endif %}
+ {% if inline_admin_form.show_url %}<a href="../../../r/{{ inline_admin_form.original_content_type_id }}/{{ inline_admin_form.original.id }}/">{% trans "View on site" %}</a>{% endif %}
+ </p>{% endif %}
+ {% if inline_admin_form.has_auto_field %}{{ inline_admin_form.pk_field.field }}{% endif %}
+ {{ inline_admin_form.fk_field.field }}
+ {% spaceless %}
+ {% for fieldset in inline_admin_form %}
+ {% for line in fieldset %}
+ {% for field in line %}
+ {% if field.is_hidden %} {{ field.field }} {% endif %}
+ {% endfor %}
+ {% endfor %}
+ {% endfor %}
+ {% endspaceless %}
+ </td>
+ {% for fieldset in inline_admin_form %}
+ {% for line in fieldset %}
+ {% for field in line %}
+ <td class="{{ field.field.name }}">
+ {% if field.is_readonly %}
+ <p>{{ field.contents }}</p>
+ {% else %}
+ {{ field.field.errors.as_ul }}
+ {{ field.field }}
+ {% endif %}
+ </td>
+ {% endfor %}
+ {% endfor %}
+ {% endfor %}
+ {% if inline_admin_formset.formset.can_delete %}
+ <td class="delete">{% if inline_admin_form.original %}{{ inline_admin_form.deletion_field.field }}{% endif %}</td>
+ {% endif %}
+ </tr>
+ {% endfor %}
+ </tbody>
+ </table>
+</fieldset>
+ </div>
+</div>
+
+<script type="text/javascript">
+(function($) {
+ $(document).ready(function($) {
+ var rows = "#{{ inline_admin_formset.formset.prefix }}-group .tabular.inline-related tbody tr";
+ var alternatingRows = function(row) {
+ $(rows).not(".add-row").removeClass("row1 row2")
+ .filter(":even").addClass("row1").end()
+ .filter(rows + ":odd").addClass("row2");
+ }
+ var reinitDateTimeShortCuts = function() {
+ // Reinitialize the calendar and clock widgets by force
+ if (typeof DateTimeShortcuts != "undefined") {
+ $(".datetimeshortcuts").remove();
+ DateTimeShortcuts.init();
+ }
+ }
+ var updateSelectFilter = function() {
+ // If any SelectFilter widgets are a part of the new form,
+ // instantiate a new SelectFilter instance for it.
+ if (typeof SelectFilter != "undefined"){
+ $(".selectfilter").each(function(index, value){
+ var namearr = value.name.split('-');
+ SelectFilter.init(value.id, namearr[namearr.length-1], false, "{% admin_media_prefix %}");
+ })
+ $(".selectfilterstacked").each(function(index, value){
+ var namearr = value.name.split('-');
+ SelectFilter.init(value.id, namearr[namearr.length-1], true, "{% admin_media_prefix %}");
+ })
+ }
+ }
+ var initPrepopulatedFields = function(row) {
+ row.find('.prepopulated_field').each(function() {
+ var field = $(this);
+ var input = field.find('input, select, textarea');
+ var dependency_list = input.data('dependency_list') || [];
+ var dependencies = row.find(dependency_list.join(',')).find('input, select, textarea');
+ if (dependencies.length) {
+ input.prepopulate(dependencies, input.attr('maxlength'));
+ }
+ });
+ }
+ $(rows).formset({
+ prefix: "{{ inline_admin_formset.formset.prefix }}",
+ addText: "{% blocktrans with inline_admin_formset.opts.verbose_name|title as verbose_name %}Add another {{ verbose_name }}{% endblocktrans %}",
+ formCssClass: "dynamic-{{ inline_admin_formset.formset.prefix }}",
+ deleteCssClass: "inline-deletelink",
+ deleteText: "{% trans "Remove" %}",
+ emptyCssClass: "empty-form",
+ removed: alternatingRows,
+ added: (function(row) {
+ initPrepopulatedFields(row);
+ reinitDateTimeShortCuts();
+ updateSelectFilter();
+ alternatingRows(row);
+ })
+ });
+ });
+})(django.jQuery);
+</script>
--- /dev/null
+{% extends "admin/change_form.html" %}
+{% load i18n %}
+
+{% block form_top %}
+ <p>{% trans "First, choose a template. After saving, you'll be able to provide additional content for containers." %}</p>
+ <input type="hidden" name="_continue" value="1" />
+{% endblock %}
+
+{% block content %}
+{% with 0 as save_on_top %}
+{{ block.super }}
+{% endwith %}
+{% endblock %}
\ No newline at end of file
class ContainerNode(template.Node):
- def __init__(self, name, references=None, as_var=None):
+ child_nodelists = ('nodelist_main', 'nodelist_empty',)
+
+ def __init__(self, name, references=None, as_var=None, nodelist_main=None, nodelist_empty=None):
self.name = name
self.as_var = as_var
self.references = references
+
+ if nodelist_main is None:
+ self.nodelist_main = template.NodeList()
+ else:
+ self.nodelist_main = nodelist_main
+
+ if nodelist_empty is None:
+ self.nodelist_empty = template.NodeList()
+ else:
+ self.nodelist_empty = nodelist_empty
+
def render(self, context):
content = settings.TEMPLATE_STRING_IF_INVALID
if 'page' in context:
- page = context['page']
- if self.references:
- try:
- contentreference = page.contentreferences.get(name__exact=self.name, content_type=self.references)
- content = contentreference.content
- except ObjectDoesNotExist:
- pass
- else:
- try:
- contentlet = page.contentlets.get(name__exact=self.name)
- if contentlet.dynamic:
- try:
- content = mark_safe(template.Template(contentlet.content, name=contentlet.name).render(context))
- except template.TemplateSyntaxError, error:
- if settings.DEBUG:
- content = ('[Error parsing contentlet \'%s\': %s]' % self.name, error)
- else:
- content = contentlet.content
- except ObjectDoesNotExist:
- pass
- if content and self.as_var:
- context[self.as_var] = content
- return ''
+ container_content = self.get_container_content(context['page'])
+
+ if self.nodelist_main is None:
+ self.nodelist_main
+ if container_content and self.as_var:
+ context[self.as_var] = container_content
+ return ''
+ return container_content
+
+ if container_content:
+ if self.as_var is None:
+ self.as_var = self.name
+
+ #make a new context
+ context.push()
+ context[self.as_var] = container_content
+ nodelist = template.NodeList()
+ for node in self.nodelist_main:
+ nodelist.append(node.render(context))
+ context.pop()
+ return nodelist.render(context)
+
+ if self.nodelist_empty is not None:
+ return self.nodelist_empty.render(context)
+
+ return ''
+
+ def get_container_content(self, page):
+ if self.references:
+ try:
+ contentreference = page.contentreferences.get(name__exact=self.name, content_type=self.references)
+ content = contentreference.content
+ except ObjectDoesNotExist:
+ content = ''
+ else:
+ try:
+ contentlet = page.contentlets.get(name__exact=self.name)
+ if contentlet.dynamic:
+ try:
+ content = mark_safe(template.Template(contentlet.content, name=contentlet.name).render(context))
+ except template.TemplateSyntaxError, error:
+ if settings.DEBUG:
+ content = ('[Error parsing contentlet \'%s\': %s]' % self.name, error)
+ else:
+ content = contentlet.content
+ except ObjectDoesNotExist:
+ content = ''
return content
def do_container(parser, token):
"""
- {% container <name> [[references <type>] as <variable>] %}
+ {% container <name> [[references <type>] as <variable>] %}
+ {% blockcontainer <name> [[references <type>] as <variable>] %} [ {% empty %} ] {% endblockcontainer %}
"""
params = token.split_contents()
if len(params) >= 2:
+ tag = params[0]
name = params[1].strip('"')
references = None
as_var = None
app_label, model = remaining_tokens.pop(0).strip('"').split('.')
references = ContentType.objects.get(app_label=app_label, model=model)
except IndexError:
- raise template.TemplateSyntaxError('"container" template tag option "references" requires an argument specifying a content type')
+ raise template.TemplateSyntaxError('"%s" template tag option "references" requires an argument specifying a content type' % tag)
except ValueError:
- raise template.TemplateSyntaxError('"container" template tag option "references" requires an argument of the form app_label.model (see django.contrib.contenttypes)')
+ raise template.TemplateSyntaxError('"%s" template tag option "references" requires an argument of the form app_label.model (see django.contrib.contenttypes)' % tag)
except ObjectDoesNotExist:
- raise template.TemplateSyntaxError('"container" template tag option "references" requires an argument of the form app_label.model which refers to an installed content type (see django.contrib.contenttypes)')
+ raise template.TemplateSyntaxError('"%s" template tag option "references" requires an argument of the form app_label.model which refers to an installed content type (see django.contrib.contenttypes)' % tag)
elif option_token == 'as':
try:
as_var = remaining_tokens.pop(0)
except IndexError:
- raise template.TemplateSyntaxError('"container" template tag option "as" requires an argument specifying a variable name')
+ raise template.TemplateSyntaxError('"%s" template tag option "as" requires an argument specifying a variable name' % tag)
if references and not as_var:
- raise template.TemplateSyntaxError('"container" template tags using "references" option require additional use of the "as" option specifying a variable name')
- return ContainerNode(name, references, as_var)
+ raise template.TemplateSyntaxError('"%s" template tags using "references" option require additional use of the "as" option specifying a variable name' % tag)
+ if tag == 'container':
+ return ContainerNode(name, references, as_var)
+
+ nodelist_main = parser.parse(('empty','endblockcontainer',))
+ token = parser.next_token()
+
+ if token.contents == 'empty':
+ nodelist_empty = parser.parse(('endblockcontainer',))
+ parser.delete_first_token()
+ else:
+ nodelist_empty = None
+ return ContainerNode(name, references, as_var, nodelist_main, nodelist_empty)
+
else: # error
- raise template.TemplateSyntaxError('"container" template tag provided without arguments (at least one required)')
-register.tag('container', do_container)
\ No newline at end of file
+ raise template.TemplateSyntaxError('"%s" template tag provided without arguments (at least one required)' % tag)
+
+
+register.tag('container', do_container)
+register.tag('blockcontainer', do_container)
from django.http import Http404, HttpResponse
from django.template import RequestContext
from django.contrib.sites.models import Site
-from models import Node
+from philo.models import Node
def node_view(request, path=None, **kwargs):