Merge branch 'master' into navigation
authorStephen Burrows <stephen.r.burrows@gmail.com>
Tue, 21 Dec 2010 15:12:03 +0000 (10:12 -0500)
committerStephen Burrows <stephen.r.burrows@gmail.com>
Tue, 21 Dec 2010 15:12:03 +0000 (10:12 -0500)
README
contrib/penfield/models.py
contrib/penfield/utils.py
contrib/waldo/forms.py
contrib/waldo/models.py
migrations/0009_auto__add_field_node_lft__add_field_node_rght__add_field_node_tree_id_.py
models/base.py
models/nodes.py
templatetags/embed.py
templatetags/nodes.py
tests.py

diff --git a/README b/README
index e718287..90d12c0 100644 (file)
--- a/README
+++ b/README
@@ -3,7 +3,21 @@ Philo is a foundation for developing web content management systems.
 Prerequisites:
        * Python 2.5.4+ <http://www.python.org/>
        * Django 1.2+ <http://www.djangoproject.com/>
+       * django-mptt 0.4+ <https://github.com/django-mptt/django-mptt/> 
        * (Optional) django-grappelli 2.0+ <http://code.google.com/p/django-grappelli/>
        * (Optional) recaptcha-django r6 <http://code.google.com/p/recaptcha-django/>
+       * (Optional) south 0.7.2+ <http://south.aeracode.org/>
 
 To contribute, please visit the project website <http://philo.ithinksw.org/>.
+
+====
+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
index 8248340..d3eea16 100644 (file)
@@ -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<tag_slugs>[-\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<tag_slugs>[-\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<tag_slugs>[-\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)
index 0386d38..43c7c91 100644 (file)
@@ -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):
index 18c22a4..615d302 100644 (file)
@@ -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
index f1ee05a..e3dd079 100644 (file)
@@ -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
        
index a6f58fd..d77affa 100644 (file)
@@ -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):
         
index 202c2f3..c7b1c26 100644 (file)
@@ -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)
index 6035148..5676b58 100644 (file)
@@ -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
 
index ef2eeb2..db5cea5 100644 (file)
@@ -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):
index 744be4d..c2dcd9a 100644 (file)
@@ -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 <node>] [as <var] %}
+       {% node_url [for <node>] [as <var>] %}
        {% node_url with <obj> [for <node>] [as <var>] %}
        {% node_url <view_name> [<arg1> [<arg2> ...] ] [for <node>] [as <var>] %}
        {% node_url <view_name> [<key1>=<value1> [<key2>=<value2> ...] ] [for <node>] [as <var>]%}
index b0f40d5..96ac7b6 100644 (file)
--- 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),
                }