From: Stephen Burrows Date: Tue, 21 Dec 2010 15:12:03 +0000 (-0500) Subject: Merge branch 'master' into navigation X-Git-Tag: philo-0.9~22^2~17^2~8 X-Git-Url: http://git.ithinksw.org/philo.git/commitdiff_plain/64e4cdefe89f642d349b5908a3bbaec76333e3b2?hp=ee56a2ca0d6c5d08ee42a9452de4353e8f2643ba Merge branch 'master' into navigation --- diff --git a/README b/README index e718287..90d12c0 100644 --- a/README +++ b/README @@ -3,7 +3,21 @@ Philo is a foundation for developing web content management systems. Prerequisites: * Python 2.5.4+ * Django 1.2+ + * django-mptt 0.4+ * (Optional) django-grappelli 2.0+ * (Optional) recaptcha-django r6 + * (Optional) south 0.7.2+ To contribute, please visit the project website . + +==== +Using philo +==== +After installing philo and mptt on your python path, make sure to complete the following steps: + +1. add 'philo.middleware.RequestNodeMiddleware' to settings.MIDDLEWARE_CLASSES. +2. add 'philo' and 'mptt' to settings.INSTALLED_APPS. +3. include 'philo.urls' somewhere in your urls.py file. +4. Optionally add a root node to your current Site. + +Philo should be ready to go! \ No newline at end of file diff --git a/contrib/penfield/models.py b/contrib/penfield/models.py index 8248340..d3eea16 100644 --- a/contrib/penfield/models.py +++ b/contrib/penfield/models.py @@ -37,6 +37,7 @@ class BlogEntry(Entity, Titled): class Meta: ordering = ['-date'] verbose_name_plural = "blog entries" + get_latest_by = "date" register_value_model(BlogEntry) @@ -104,7 +105,12 @@ class BlogView(MultiView, FeedMultiViewMixin): def urlpatterns(self): urlpatterns = patterns('', url(r'^', include(self.feed_patterns(self.get_all_entries, self.index_page, 'index'))), - url(r'^%s/(?P[-\w]+[-+/\w]*)/%s/' % (self.tag_permalink_base, self.feed_suffix), self.feed_view(self.get_entries_by_tag, 'entries_by_tag_feed'), name='entries_by_tag_feed'), + ) + if self.feeds_enabled: + urlpatterns += patterns('', + url(r'^%s/(?P[-\w]+[-+/\w]*)/%s/' % (self.tag_permalink_base, self.feed_suffix), self.feed_view(self.get_entries_by_tag, 'entries_by_tag_feed'), name='entries_by_tag_feed'), + ) + urlpatterns += patterns('', url(r'^%s/(?P[-\w]+[-+/\w]*)/' % self.tag_permalink_base, self.page_view(self.get_entries_by_tag, self.tag_page), name='entries_by_tag') ) if self.tag_archive_page: @@ -436,4 +442,4 @@ class NewsletterView(MultiView, FeedMultiViewMixin): 'title': title } defaults.update(kwargs or {}) - return super(NewsletterView, self).get_feed(feed_type, extra_context, defaults) \ No newline at end of file + return super(NewsletterView, self).get_feed(feed_type, extra_context, defaults) diff --git a/contrib/penfield/utils.py b/contrib/penfield/utils.py index 0386d38..43c7c91 100644 --- a/contrib/penfield/utils.py +++ b/contrib/penfield/utils.py @@ -87,11 +87,14 @@ class FeedMultiViewMixin(object): return self.atom_feed(**defaults) def feed_patterns(self, object_fetcher, page, base_name): - feed_name = '%s_feed' % base_name urlpatterns = patterns('', - url(r'^%s/$' % self.feed_suffix, self.feed_view(object_fetcher, feed_name), name=feed_name), url(r'^$', self.page_view(object_fetcher, page), name=base_name) ) + if self.feeds_enabled: + feed_name = '%s_feed' % base_name + urlpatterns = patterns('', + url(r'^%s/$' % self.feed_suffix, self.feed_view(object_fetcher, feed_name), name=feed_name), + ) + urlpatterns return urlpatterns def add_item(self, feed, obj, kwargs=None): diff --git a/contrib/waldo/forms.py b/contrib/waldo/forms.py index 18c22a4..615d302 100644 --- a/contrib/waldo/forms.py +++ b/contrib/waldo/forms.py @@ -56,4 +56,18 @@ class RegistrationForm(UserCreationForm): new_user = User.objects.create_user(username, email, password) new_user.is_active = False new_user.save() - return new_user \ No newline at end of file + return new_user + + +class UserAccountForm(forms.ModelForm): + first_name = User._meta.get_field('first_name').formfield(required=True) + last_name = User._meta.get_field('last_name').formfield(required=True) + email = User._meta.get_field('email').formfield(required=True, widget=EmailInput) + + def __init__(self, user, *args, **kwargs): + kwargs['instance'] = user + super(UserAccountForm, self).__init__(*args, **kwargs) + + class Meta: + model = User + fields = ('first_name', 'last_name', 'email') \ No newline at end of file diff --git a/contrib/waldo/models.py b/contrib/waldo/models.py index f1ee05a..e3dd079 100644 --- a/contrib/waldo/models.py +++ b/contrib/waldo/models.py @@ -7,7 +7,6 @@ from django.contrib.auth.models import User from django.contrib.auth.tokens import default_token_generator as password_token_generator from django.contrib.sites.models import Site from django.core.mail import EmailMultiAlternatives, send_mail -from django.core.urlresolvers import reverse from django.db import models from django.http import Http404, HttpResponseRedirect from django.shortcuts import render_to_response, get_object_or_404 @@ -17,7 +16,7 @@ from django.utils.translation import ugettext_lazy, ugettext as _ from django.views.decorators.cache import never_cache from django.views.decorators.csrf import csrf_protect from philo.models import MultiView, Page -from philo.contrib.waldo.forms import LOGIN_FORM_KEY, LoginForm, RegistrationForm +from philo.contrib.waldo.forms import LOGIN_FORM_KEY, LoginForm, RegistrationForm, UserAccountForm from philo.contrib.waldo.tokens import registration_token_generator, email_token_generator import urlparse @@ -25,13 +24,6 @@ import urlparse ERROR_MESSAGE = ugettext_lazy("Please enter a correct username and password. Note that both fields are case-sensitive.") -def get_field_data(obj, fields): - if fields == None: - fields = [field.name for field in obj._meta.fields if field.editable] - - return dict([(field.name, field.value_from_object(obj)) for field in obj._meta.fields if field.name in fields]) - - class LoginMultiView(MultiView): """ Handles login, registration, and forgotten passwords. In other words, this @@ -66,10 +58,19 @@ class LoginMultiView(MultiView): return urlpatterns - def get_context(self, extra_dict=None): - context = {} - context.update(extra_dict or {}) - return context + def make_confirmation_link(self, confirmation_view, token_generator, user, node, token_args=None, reverse_kwargs=None): + current_site = Site.objects.get_current() + token = token_generator.make_token(user, *(token_args or [])) + kwargs = { + 'uidb36': int_to_base36(user.id), + 'token': token + } + kwargs.update(reverse_kwargs or {}) + return 'http://%s%s' % (current_site.domain, self.reverse(confirmation_view, kwargs=kwargs, node=node)) + + def get_context(self): + """Hook for providing instance-specific context - such as the value of a Field - to all views.""" + return {} def display_login_page(self, request, message, extra_context=None): request.session.set_test_cookie() @@ -97,11 +98,12 @@ class LoginMultiView(MultiView): form = LoginForm(request.POST) else: form = LoginForm() - context = self.get_context({ + context = self.get_context() + context.update(extra_context or {}) + context.update({ 'message': message, 'form': form }) - context.update(extra_context or {}) return self.login_page.render_to_response(request, extra_context=context) def login(self, request, extra_context=None): @@ -111,7 +113,8 @@ class LoginMultiView(MultiView): if request.user.is_authenticated(): return HttpResponseRedirect(request.node.get_absolute_url()) - context = self.get_context(extra_context) + context = self.get_context() + context.update(extra_context or {}) from django.contrib.auth.models import User @@ -169,8 +172,7 @@ class LoginMultiView(MultiView): def login_required(self, view): def inner(request, *args, **kwargs): if not request.user.is_authenticated(): - login_url = reverse('login', urlconf=self).strip('/') - return HttpResponseRedirect('%s%s/' % (request.node.get_absolute_url(), login_url)) + return HttpResponseRedirect(self.reverse('login', node=request.node)) return view(request, *args, **kwargs) return inner @@ -195,10 +197,8 @@ class LoginMultiView(MultiView): if form.is_valid(): current_site = Site.objects.get_current() for user in form.users_cache: - token = token_generator.make_token(user) - link = 'http://%s/%s/%s/' % (current_site.domain, request.node.get_absolute_url().strip('/'), reverse('password_reset_confirm', urlconf=self, kwargs={'uidb36': int_to_base36(user.id), 'token': token}).strip('/')) context = { - 'link': link, + 'link': self.make_confirmation_link('password_reset_confirm', token_generator, user, request.node), 'username': user.username } self.send_confirmation_email('Confirm password reset for account at %s' % current_site.domain, user.email, self.password_reset_confirmation_email, context) @@ -207,8 +207,11 @@ class LoginMultiView(MultiView): else: form = PasswordResetForm() - context = self.get_context({'form': form}) + context = self.get_context() context.update(extra_context or {}) + context.update({ + 'form': form + }) return self.password_reset_page.render_to_response(request, extra_context=context) def password_reset_confirm(self, request, extra_context=None, uidb36=None, token=None, token_generator=password_token_generator): @@ -231,11 +234,15 @@ class LoginMultiView(MultiView): if form.is_valid(): form.save() messages.add_message(request, messages.SUCCESS, "Password reset successful.") - return HttpResponseRedirect('/%s/%s/' % (request.node.get_absolute_url().strip('/'), reverse('login', urlconf=self).strip('/'))) + return HttpResponseRedirect(self.reverse('login', node=request.node)) else: form = SetPasswordForm(user) - context = self.get_context({'form': form}) + context = self.get_context() + context.update(extra_context or {}) + context.update({ + 'form': form + }) return self.password_set_page.render_to_response(request, extra_context=context) raise Http404 @@ -250,8 +257,11 @@ class LoginMultiView(MultiView): else: form = PasswordChangeForm(request.user) - context = self.get_context({'form': form}) + context = self.get_context() context.update(extra_context or {}) + context.update({ + 'form': form + }) return self.password_change_page.render_to_response(request, extra_context=context) def register(self, request, extra_context=None, token_generator=registration_token_generator): @@ -262,20 +272,21 @@ class LoginMultiView(MultiView): form = RegistrationForm(request.POST) if form.is_valid(): user = form.save() - current_site = Site.objects.get_current() - token = token_generator.make_token(user) - link = 'http://%s/%s/%s/' % (current_site.domain, request.node.get_absolute_url().strip('/'), reverse('register_confirm', urlconf=self, kwargs={'uidb36': int_to_base36(user.id), 'token': token}).strip('/')) context = { - 'link': link + 'link': self.make_confirmation_link('register_confirm', token_generator, user, request.node) } + current_site = Site.objects.get_current() self.send_confirmation_email('Confirm account creation at %s' % current_site.name, user.email, self.register_confirmation_email, context) messages.add_message(request, messages.SUCCESS, 'An email has been sent to %s with details on activating your account.' % user.email, fail_silently=True) return HttpResponseRedirect(request.node.get_absolute_url()) else: form = RegistrationForm() - context = self.get_context({'form': form}) + context = self.get_context() context.update(extra_context or {}) + context.update({ + 'form': form + }) return self.register_page.render_to_response(request, extra_context=context) def register_confirm(self, request, extra_context=None, uidb36=None, token=None, token_generator=registration_token_generator): @@ -294,10 +305,11 @@ class LoginMultiView(MultiView): if token_generator.check_token(user, token): user.is_active = True true_password = user.password + temp_password = token_generator.make_token(user) try: - user.set_password('temp_password') + user.set_password(temp_password) user.save() - authenticated_user = authenticate(username=user.username, password='temp_password') + authenticated_user = authenticate(username=user.username, password=temp_password) login(request, authenticated_user) finally: # if anything goes wrong, ABSOLUTELY make sure that the true password is restored. @@ -316,16 +328,13 @@ class LoginMultiView(MultiView): class AccountMultiView(LoginMultiView): """ - Subclasses may define an account_profile model, fields from the User model - to include in the account, and fields from the account profile to use in - the account. + By default, the `account` consists of the first_name, last_name, and email fields + of the User model. Using a different account model is as simple as writing a form that + accepts a User instance as the first argument. """ manage_account_page = models.ForeignKey(Page, related_name='%(app_label)s_%(class)s_manage_account_related') email_change_confirmation_email = models.ForeignKey(Page, related_name='%(app_label)s_%(class)s_email_change_confirmation_email_related') - user_fields = ['first_name', 'last_name', 'email'] - required_user_fields = user_fields - account_profile = None - account_profile_fields = None + account_form = UserAccountForm @property def urlpatterns(self): @@ -336,71 +345,37 @@ class AccountMultiView(LoginMultiView): ) return urlpatterns - def get_account_forms(self): - user_form = forms.models.modelform_factory(User, fields=self.user_fields) - - if self.account_profile is None: - profile_form = None - else: - profile_form = forms.models.modelform_factory(self.account_profile, fields=self.account_profile_fields or [field.name for field in self.account_profile._meta.fields if field.editable and field.name != 'user']) - - for field_name, field in user_form.base_fields.items(): - if field_name in self.required_user_fields: - field.required = True - return user_form, profile_form - - def get_account_form_instances(self, user, data=None): - form_instances = [] - user_form, profile_form = self.get_account_forms() - if data is None: - form_instances.append(user_form(instance=user)) - if profile_form: - form_instances.append(profile_form(instance=self.account_profile._default_manager.get_or_create(user=user)[0])) - else: - form_instances.append(user_form(data, instance=user)) - if profile_form: - form_instances.append(profile_form(data, instance=self.account_profile._default_manager.get_or_create(user=user)[0])) - - return form_instances - def account_view(self, request, extra_context=None, token_generator=email_token_generator, *args, **kwargs): if request.method == 'POST': - form_instances = self.get_account_form_instances(request.user, request.POST) - current_email = request.user.email + form = self.account_form(request.user, request.POST, request.FILES) - for form in form_instances: - if not form.is_valid(): - break - else: - # When the user_form is validated, it changes the model instance, i.e. request.user, in place. - email = request.user.email - if current_email != email: - - request.user.email = current_email + if form.is_valid(): + if 'email' in form.changed_data: + # ModelForms modify their instances in-place during validation, + # so reset the instance's email to its previous value here, + # then remove the new value from cleaned_data. + request.user.email = form.initial['email'] - for form in form_instances: - form.cleaned_data.pop('email', None) + email = form.cleaned_data.pop('email') - current_site = Site.objects.get_current() - token = token_generator.make_token(request.user, email) - link = 'http://%s/%s/%s/' % (current_site.domain, request.node.get_absolute_url().strip('/'), reverse('email_change_confirm', urlconf=self, kwargs={'uidb36': int_to_base36(request.user.id), 'email': email.replace('@', '+'), 'token': token}).strip('/')) context = { - 'link': link + 'link': self.make_confirmation_link('email_change_confirm', token_generator, request.user, request.node, token_args=[email], reverse_kwargs={'email': email.replace('@', '+')}) } + current_site = Site.objects.get_current() self.send_confirmation_email('Confirm account email change at %s' % current_site.domain, email, self.email_change_confirmation_email, context) messages.add_message(request, messages.SUCCESS, "An email has be sent to %s to confirm the email change." % email) - - for form in form_instances: - form.save() + + form.save() messages.add_message(request, messages.SUCCESS, "Account information saved.", fail_silently=True) return HttpResponseRedirect('') else: - form_instances = self.get_account_form_instances(request.user) + form = self.account_form(request.user) - context = self.get_context({ - 'forms': form_instances - }) + context = self.get_context() context.update(extra_context or {}) + context.update({ + 'form': form + }) return self.manage_account_page.render_to_response(request, extra_context=context) def has_valid_account(self, user): @@ -430,7 +405,7 @@ class AccountMultiView(LoginMultiView): def post_register_confirm_redirect(self, request): messages.add_message(request, messages.INFO, 'Welcome! Please fill in some more information.', fail_silently=True) - return HttpResponseRedirect('/%s/%s/' % (request.node.get_absolute_url().strip('/'), reverse('account', urlconf=self).strip('/'))) + return HttpResponseRedirect(self.reverse('account', node=request.node)) def email_change_confirm(self, request, extra_context=None, uidb36=None, token=None, email=None, token_generator=email_token_generator): """ @@ -455,7 +430,7 @@ class AccountMultiView(LoginMultiView): user.email = email user.save() messages.add_message(request, messages.SUCCESS, 'Email changed successfully.') - return HttpResponseRedirect('/%s/%s/' % (request.node.get_absolute_url().strip('/'), reverse('account', urlconf=self).strip('/'))) + return HttpReponseRedirect(self.reverse('account', node=request.node)) raise Http404 diff --git a/migrations/0009_auto__add_field_node_lft__add_field_node_rght__add_field_node_tree_id_.py b/migrations/0009_auto__add_field_node_lft__add_field_node_rght__add_field_node_tree_id_.py index a6f58fd..d77affa 100644 --- a/migrations/0009_auto__add_field_node_lft__add_field_node_rght__add_field_node_tree_id_.py +++ b/migrations/0009_auto__add_field_node_lft__add_field_node_rght__add_field_node_tree_id_.py @@ -3,6 +3,8 @@ import datetime from south.db import db from south.v2 import SchemaMigration from django.db import models +from philo.models import Node, Template + class Migration(SchemaMigration): @@ -32,6 +34,10 @@ class Migration(SchemaMigration): # Adding field 'Template.level' db.add_column('philo_template', 'level', self.gf('django.db.models.fields.PositiveIntegerField')(default=1, db_index=True), keep_default=False) + # Rebuild trees! + Template._tree_manager.rebuild() + Node._tree_manager.rebuild() + def backwards(self, orm): diff --git a/models/base.py b/models/base.py index 202c2f3..c7b1c26 100644 --- a/models/base.py +++ b/models/base.py @@ -358,6 +358,8 @@ class TreeManager(models.Manager): if deepest_level == depth: # This should happen if nothing is found with any part of the given path. + if root is not None and deepest_found is None: + return root, build_path(segments) raise return find_obj(segments, depth, deepest_found) @@ -411,7 +413,7 @@ class TreeModel(MPTTModel): qs = self.get_ancestors() if root is not None: - qs = qs.filter(level__gt=root.level) + qs = qs.filter(**{'%s__gt' % self._mptt_meta.level_attr: root.get_level()}) return pathsep.join([getattr(parent, field, '?') for parent in list(qs) + [self]]) path = property(get_path) diff --git a/models/nodes.py b/models/nodes.py index 6035148..5676b58 100644 --- a/models/nodes.py +++ b/models/nodes.py @@ -219,6 +219,14 @@ class MultiView(View): kwargs['extra_context'] = extra_context return view(request, *args, **kwargs) + def reverse(self, view_name, args=None, kwargs=None, node=None): + """Shortcut method to handle the common pattern of getting the absolute url for a multiview's + subpaths.""" + subpath = reverse(view_name, urlconf=self, args=args or [], kwargs=kwargs or {}) + if node is not None: + return '/%s/%s/' % (node.get_absolute_url().strip('/'), subpath.strip('/')) + return subpath + class Meta: abstract = True diff --git a/templatetags/embed.py b/templatetags/embed.py index ef2eeb2..db5cea5 100644 --- a/templatetags/embed.py +++ b/templatetags/embed.py @@ -61,23 +61,28 @@ class EmbedContext(object): old_extends_node_init = ExtendsNode.__init__ -def get_embed_dict(nodelist): +def get_embed_dict(embed_list, context): embeds = {} - for n in nodelist.get_nodes_by_type(ConstantEmbedNode): - if n.content_type not in embeds: - embeds[n.content_type] = [n] + for e in embed_list: + ct = e.get_content_type(context) + if ct is None: + # Then the embed doesn't exist for this context. + continue + if ct not in embeds: + embeds[ct] = [e] else: - embeds[n.content_type].append(n) + embeds[ct].append(e) return embeds def extends_node_init(self, nodelist, *args, **kwargs): - self.embeds = get_embed_dict(nodelist) + self.embed_list = nodelist.get_nodes_by_type(ConstantEmbedNode) old_extends_node_init(self, nodelist, *args, **kwargs) def render_extends_node(self, context): compiled_parent = self.get_parent(context) + embeds = get_embed_dict(self.embed_list, context) if BLOCK_CONTEXT_KEY not in context.render_context: context.render_context[BLOCK_CONTEXT_KEY] = BlockContext() @@ -90,7 +95,7 @@ def render_extends_node(self, context): # Add the block nodes from this node to the block context # Do the equivalent for embed nodes block_context.add_blocks(self.blocks) - embed_context.add_embeds(self.embeds) + embed_context.add_embeds(embeds) # If this block's parent doesn't have an extends node it is the root, # and its block nodes also need to be added to the block context. @@ -100,10 +105,16 @@ def render_extends_node(self, context): if not isinstance(node, ExtendsNode): blocks = dict([(n.name, n) for n in compiled_parent.nodelist.get_nodes_by_type(BlockNode)]) block_context.add_blocks(blocks) - embeds = get_embed_dict(compiled_parent.nodelist) + embeds = get_embed_dict(compiled_parent.nodelist.get_nodes_by_type(ConstantEmbedNode), context) embed_context.add_embeds(embeds) break - + + # Explicitly render all direct embed children of this node. + if self.embed_list: + for node in self.nodelist: + if isinstance(node, ConstantEmbedNode): + node.render(context) + # Call Template._render explicitly so the parser context stays # the same. return compiled_parent._render(context) @@ -152,7 +163,7 @@ class ConstantEmbedNode(template.Node): try: return template.loader.get_template(template_name) except template.TemplateDoesNotExist: - if not hasattr(self, 'template_name') and settings.TEMPLATE_DEBUG: + if hasattr(self, 'template') and settings.TEMPLATE_DEBUG: # Then it's a constant node. raise return False @@ -199,16 +210,13 @@ class ConstantEmbedNode(template.Node): try: t = context.render_context[EMBED_CONTEXT_KEY].get_embed_template(self, context) except (KeyError, IndexError): - if settings.TEMPLATE_DEBUG: - raise + self.mark_rendered_for(context) return settings.TEMPLATE_STRING_IF_INVALID context.push() context['embedded'] = instance - kwargs = {} for k, v in self.kwargs.items(): - kwargs[k] = v.resolve(context) - context.update(kwargs) + context[k] = v.resolve(context) t_rendered = t.render(context) context.pop() self.mark_rendered_for(context) @@ -225,13 +233,11 @@ class EmbedNode(ConstantEmbedNode): self.object_pk = object_pk else: self.object_pk = None - self.instance = None if template_name is not None: self.template_name = template_name else: self.template_name = None - self.template = None def get_instance(self, context): if self.object_pk is None: @@ -256,7 +262,10 @@ class InstanceEmbedNode(EmbedNode): return self.instance.resolve(context) def get_content_type(self, context): - return ContentType.objects.get_for_model(self.get_instance(context)) + instance = self.get_instance(context) + if not instance: + return None + return ContentType.objects.get_for_model(instance) def get_embedded(self): diff --git a/templatetags/nodes.py b/templatetags/nodes.py index 744be4d..c2dcd9a 100644 --- a/templatetags/nodes.py +++ b/templatetags/nodes.py @@ -51,7 +51,9 @@ class NodeURLNode(template.Node): subpath = reverse(view_name, urlconf=node.view, args=args, kwargs=kwargs) except NoReverseMatch: if self.as_var is None: - raise + if settings.TEMPLATE_DEBUG: + raise + return settings.TEMPLATE_STRING_IF_INVALID else: if subpath[0] == '/': subpath = subpath[1:] @@ -68,7 +70,7 @@ class NodeURLNode(template.Node): @register.tag(name='node_url') def do_node_url(parser, token): """ - {% node_url [for ] [as ] [as ] %} {% node_url with [for ] [as ] %} {% node_url [ [ ...] ] [for ] [as ] %} {% node_url [= [= ...] ] [for ] [as ]%} diff --git a/tests.py b/tests.py index b0f40d5..96ac7b6 100644 --- a/tests.py +++ b/tests.py @@ -39,9 +39,10 @@ class TemplateTestCase(TestCase): expected_invalid_str = 'INVALID' failures = [] - + tests = template_tests.items() + tests.sort() # Run tests - for name, vals in template_tests.items(): + for name, vals in tests: xx, context, result = vals try: test_template = loader.get_template(name) @@ -96,6 +97,13 @@ class TemplateTestCase(TestCase): # Blocks and includes 'block-include01': ('{% extends "simple01" %}{% embed penfield.blog with "embed03" %}{% block one %}{% include "simple01" %}{% embed penfield.blog 1 %}{% endblock %}', {}, "%sSimple%sSimple%s is a lie!" % (blog.title, blog.title, blog.title)), 'block-include02': ('{% extends "simple01" %}{% block one %}{% include "simple04" %}{% embed penfield.blog with "embed03" %}{% include "simple04" %}{% embed penfield.blog 1 %}{% endblock %}', {}, "%sSimple%s%s is a lie!%s is a lie!" % (blog.title, blog.title, blog.title, blog.title)), + + # Tests for more complex situations... + 'complex01': ('{% block one %}{% endblock %}complex{% block two %}{% endblock %}', {}, 'complex'), + 'complex02': ('{% extends "complex01" %}', {}, 'complex'), + 'complex03': ('{% extends "complex02" %}{% embed penfield.blog with "embed01" %}', {}, 'complex'), + 'complex04': ('{% extends "complex03" %}{% block one %}{% embed penfield.blog 1 %}{% endblock %}', {}, '%scomplex' % blog.title), + 'complex05': ('{% extends "complex03" %}{% block one %}{% include "simple04" %}{% endblock %}', {}, '%scomplex' % blog.title), }