Merge branch 'master' of git://github.com/melinath/philo
authorJoseph Spiros <joseph.spiros@ithinksw.com>
Wed, 29 Sep 2010 18:39:17 +0000 (14:39 -0400)
committerJoseph Spiros <joseph.spiros@ithinksw.com>
Wed, 29 Sep 2010 18:39:17 +0000 (14:39 -0400)
* 'master' of git://github.com/melinath/philo:
  Implemented better container filtering for page container inlines.
  Modified FeedMultiViewMixin to allow greater customization of feed creation, especially with regards to feed titles. Tweaked BlogView and NewsletterView to be in line with the changes.
  Adjusted feedmultiviewmixin to allow more customization of objects being added to feeds and to link to complete urls instead of absolute in-site urls.
  Corrected blogview tag feed urls.
  Added filter_horizontal on blogentry tags. Made minor corrections to BlogView's get_entries_by_tag method.
  Added waldo filtering to send multipart or plaintext messages based on the template mimetype. Tweaked Template model to actually default to text/html instead of just saying it.
  Fixed login redirects
  Added recaptcha support to waldo registration form.
  Switched newsletterissue numbering to a more generic field. Added newsletterview unicode. Minor change to container templatetags to ensure that the container_content variable is always set.
  Added 500 handling to nodeview. Made nodeview vary on Accept header to allow fancy json/html switchouts - possibly also other fanciness.
  Added philo 404 handling via a relationship named Http404 on nodes. Uses the passthrough feature of relationships to let 404 views inherit.
  Added tags to newsletter articles
  Tweaked AccountMultiView to account for emails containing '+' in the local address.
  Upped philo.tag name/slug lengths to 255.
  Added slug to TagAdmin list_display. Corrected FeedMultiViewMixin to also paginate the root page.
  Added helper function to ease response 'returns' for signal listeners
  Switched over to render_to_response and actually_render_to_response, since render_to_response is the open api used by say multiviews and should always be called when a page is to render. Calling a hidden method would not be good. Also added render_to_string listeners for pages.
  Added view_about_to_render and view_finished_rendering signals. Added hidden _render_to_response method to ensure they're called on all view subclasses. Also some minor streamlining on Newsletters.
  Shifted definition of the context list_var (entries vs. articles) to a class attribute. Since account_view can be put in place of any arbitrary view, let it accept args and kwargs to handle anything unexpected.

13 files changed:
admin/base.py
contrib/penfield/admin.py
contrib/penfield/models.py
contrib/penfield/utils.py
contrib/waldo/forms.py
contrib/waldo/models.py
forms.py
models/base.py
models/nodes.py
models/pages.py
signals.py
templatetags/containers.py
views.py

index f8485d2..46c7c22 100644 (file)
@@ -32,6 +32,7 @@ class EntityAdmin(admin.ModelAdmin):
 
 
 class TagAdmin(admin.ModelAdmin):
+       list_display = ('name', 'slug')
        prepopulated_fields = {"slug": ("name",)}
 
 admin.site.register(Tag, TagAdmin)
\ No newline at end of file
index 56edd45..85888aa 100644 (file)
@@ -13,7 +13,7 @@ class BlogAdmin(TitledAdmin):
 
 
 class BlogEntryAdmin(TitledAdmin):
-       pass
+       filter_horizontal = ['tags']
 
 
 class BlogViewAdmin(EntityAdmin):
index e433b84..f927a58 100644 (file)
@@ -65,7 +65,8 @@ class BlogView(MultiView, FeedMultiViewMixin):
        entry_permalink_base = models.CharField(max_length=255, blank=False, default='entries')
        tag_permalink_base = models.CharField(max_length=255, blank=False, default='tags')
        feed_suffix = models.CharField(max_length=255, blank=False, default=FeedMultiViewMixin.feed_suffix)
-       feeds_enabled = models.BooleanField() 
+       feeds_enabled = models.BooleanField()
+       list_var = 'entries'
        
        def __unicode__(self):
                return u'BlogView for %s' % self.blog.title
@@ -74,10 +75,6 @@ class BlogView(MultiView, FeedMultiViewMixin):
        def per_page(self):
                return self.entries_per_page
        
-       @property
-       def feed_title(self):
-               return self.blog.title
-       
        def get_subpath(self, obj):
                if isinstance(obj, BlogEntry):
                        if obj.blog == self.blog:
@@ -116,7 +113,8 @@ 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]*)/' % self.tag_permalink_base), include(self.feed_patterns(self.get_entries_by_tag, self.tag_page, 'entries_by_tag')))
+                       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'),
+                       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:
                        urlpatterns += patterns('',
@@ -177,7 +175,7 @@ class BlogView(MultiView, FeedMultiViewMixin):
                context.update({'year': year, 'month': month, 'day': day})
                return entries, context
        
-       def get_entries_by_tag(self, request, node=None, extra_context=None):
+       def get_entries_by_tag(self, request, tag_slugs, node=None, extra_context=None):
                tags = []
                for tag_slug in tag_slugs.replace('+', '/').split('/'):
                        if tag_slug: # ignore blank slugs, handles for multiple consecutive separators (+ or /)
@@ -192,13 +190,35 @@ class BlogView(MultiView, FeedMultiViewMixin):
                entries = self.blog.entries.all()
                for tag in tags:
                        entries = entries.filter(tags=tag)
-               if entries.count() <= 0:
-                       raise Http404
                
-               return entries, extra_context
+               context = self.get_context()
+               context.update(extra_context or {})
+               context.update({'tags': tags})
+               
+               return entries, context
        
-       def get_obj_description(self, obj):
-               return obj.excerpt
+       def add_item(self, feed, obj, kwargs=None):
+               defaults = {
+                       'title': obj.title,
+                       'description': obj.excerpt or obj.content,
+                       'author_name': obj.author.get_full_name(),
+                       'pubdate': obj.date
+               }
+               defaults.update(kwargs or {})
+               super(BlogView, self).add_item(feed, obj, defaults)
+       
+       def get_feed(self, feed_type, extra_context, kwargs=None):
+               tags = (extra_context or {}).get('tags', None)
+               title = self.blog.title
+               
+               if tags is not None:
+                       title += " - %s" % ', '.join([tag.name for tag in tags])
+               
+               defaults = {
+                       'title': title
+               }
+               defaults.update(kwargs or {})
+               return super(BlogView, self).get_feed(feed_type, extra_context, defaults)
        
        def entry_view(self, request, slug, year=None, month=None, day=None, node=None, extra_context=None):
                entries = self.blog.entries.all()
@@ -239,6 +259,7 @@ class NewsletterArticle(Entity, Titled):
        date = models.DateTimeField(default=datetime.now)
        lede = models.TextField(null=True, blank=True)
        full_text = models.TextField()
+       tags = models.ManyToManyField(Tag, related_name='newsletterarticles', blank=True, null=True)
        
        class Meta:
                get_latest_by = 'date'
@@ -251,12 +272,12 @@ register_value_model(NewsletterArticle)
 
 class NewsletterIssue(Entity, Titled):
        newsletter = models.ForeignKey(Newsletter, related_name='issues')
-       number = models.PositiveIntegerField()
+       numbering = models.CharField(max_length=50, help_text='For example, 04.02 for volume 4, issue 2.')
        articles = models.ManyToManyField(NewsletterArticle, related_name='issues')
        
        class Meta:
-               ordering = ['-number']
-               unique_together = (('newsletter', 'number'),)
+               ordering = ['-numbering']
+               unique_together = (('newsletter', 'numbering'),)
 
 
 register_value_model(NewsletterIssue)
@@ -284,10 +305,10 @@ class NewsletterView(MultiView, FeedMultiViewMixin):
        
        feed_suffix = models.CharField(max_length=255, blank=False, default=FeedMultiViewMixin.feed_suffix)
        feeds_enabled = models.BooleanField()
+       list_var = 'articles'
        
-       @property
-       def feed_title(self):
-               return self.newsletter.title
+       def __unicode__(self):
+               return self.newsletter.__unicode__()
        
        def get_subpath(self, obj):
                if isinstance(obj, NewsletterArticle):
@@ -302,14 +323,14 @@ class NewsletterView(MultiView, FeedMultiViewMixin):
                                return reverse(self.article_view, urlconf=self, kwargs=kwargs)
                elif isinstance(obj, NewsletterIssue):
                        if obj.newsletter == self.newsletter:
-                               return reverse('issue', urlconf=self, kwargs={'number': str(obj.number)})
+                               return reverse('issue', urlconf=self, kwargs={'numbering': obj.numbering})
                raise ViewCanNotProvideSubpath
        
        @property
        def urlpatterns(self):
                urlpatterns = patterns('',
                        url(r'^', include(self.feed_patterns(self.get_all_articles, self.index_page, 'index'))),
-                       url(r'^(?:%s)/(?P<number>\d+)/' % self.issue_permalink_base, include(self.feed_patterns(self.get_articles_by_issue, self.issue_page, 'issue')))
+                       url(r'^(?:%s)/(?P<numbering>.+)/' % self.issue_permalink_base, include(self.feed_patterns(self.get_articles_by_issue, self.issue_page, 'issue')))
                )
                if self.issue_archive_page:
                        urlpatterns += patterns('',
@@ -365,9 +386,9 @@ class NewsletterView(MultiView, FeedMultiViewMixin):
                        articles = articles.filter(date__day=day)
                return articles
        
-       def get_articles_by_issue(self, request, number, node=None, extra_context=None):
+       def get_articles_by_issue(self, request, numbering, node=None, extra_context=None):
                try:
-                       issue = self.newsletter.issues.get(number=number)
+                       issue = self.newsletter.issues.get(numbering=numbering)
                except:
                        raise Http404
                context = extra_context or {}
@@ -386,9 +407,9 @@ class NewsletterView(MultiView, FeedMultiViewMixin):
                        article = articles.get(slug=slug)
                except:
                        raise Http404
-               context = {}
+               context = self.get_context()
                context.update(extra_context or {})
-               context.update({'newsletter': self.newsletter, 'article': article})
+               context.update({'article': article})
                return self.article_page.render_to_response(node, request, extra_context=context)
        
        def issue_archive_view(self, request, node=None, extra_context=None):
@@ -399,5 +420,22 @@ class NewsletterView(MultiView, FeedMultiViewMixin):
                context.update({'newsletter': self.newsletter})
                return self.issue_archive_page.render_to_response(node, request, extra_context=context)
        
-       def get_obj_description(self, obj):
-               return obj.lede or obj.full_text
\ No newline at end of file
+       def add_item(self, feed, obj, kwargs=None):
+               defaults = {
+                       'title': obj.title,
+                       'author_name': ', '.join(obj.authors),
+                       'pubdate': obj.date,
+                       'description': obj.lede or obj.full_text,
+                       'categories': [tag.name for tag in obj.tags.all()]
+               }
+               defaults.update(kwargs or {})
+               super(NewsletterView, self).add_item(feed, obj, defaults)
+       
+       def get_feed(self, feed_type, extra_context, kwargs=None):
+               title = self.newsletter.title
+               
+               defaults = {
+                       'title': title
+               }
+               defaults.update(kwargs or {})
+               return super(NewsletterView, self).get_feed(feed_type, extra_context, defaults)
\ No newline at end of file
index 7b5e946..8103d9d 100644 (file)
@@ -1,7 +1,9 @@
 from django.utils.feedgenerator import Atom1Feed, Rss201rev2Feed
 from django.conf.urls.defaults import url, patterns
+from django.contrib.sites.models import Site
 from django.core.urlresolvers import reverse
 from django.http import HttpResponse
+from philo.utils import paginate
 
 
 class FeedMultiViewMixin(object):
@@ -14,9 +16,11 @@ class FeedMultiViewMixin(object):
        feeds_enabled = True
        atom_feed = Atom1Feed
        rss_feed = Rss201rev2Feed
-       feed_description = ''
+       feed_title = None
+       feed_description = None
+       list_var = 'objects'
        
-       def page_view(self, func, page, list_var='entries'):
+       def page_view(self, func, page):
                """
                Wraps an object-fetching function and renders the results as a page.
                """
@@ -26,12 +30,12 @@ class FeedMultiViewMixin(object):
                        context = self.get_context()
                        context.update(extra_context or {})
 
-                       if 'page' in kwargs or 'page' in request.GET:
+                       if 'page' in kwargs or 'page' in request.GET or (hasattr(self, 'per_page') and self.per_page):
                                page_num = kwargs.get('page', request.GET.get('page', 1))
                                paginator, paginated_page, objects = paginate(objects, self.per_page, page_num)
-                               context.update({'paginator': paginator, 'paginated_page': paginated_page, list_var: objects})
+                               context.update({'paginator': paginator, 'paginated_page': paginated_page, self.list_var: objects})
                        else:
-                               context.update({list_var: objects})
+                               context.update({self.list_var: objects})
 
                        return page.render_to_response(node, request, extra_context=context)
 
@@ -49,10 +53,18 @@ class FeedMultiViewMixin(object):
                        else:
                                feed_type = 'atom'
                        
-                       feed = self.get_feed(feed_type, request, node, kwargs, reverse_name)
+                       current_site = Site.objects.get_current()
+                       
+                       feed_kwargs = {
+                               'link': 'http://%s/%s/%s/' % (current_site.domain, node.get_absolute_url().strip('/'), reverse(reverse_name, urlconf=self, kwargs=kwargs).strip('/'))
+                       }
+                       feed = self.get_feed(feed_type, extra_context, feed_kwargs)
                        
                        for obj in objects:
-                               feed.add_item(obj.title, '/%s/%s/' % (node.get_absolute_url().strip('/'), self.get_subpath(obj).strip('/')), description=self.get_obj_description(obj))
+                               kwargs = {
+                                       'link': 'http://%s/%s/%s/' % (current_site.domain, node.get_absolute_url().strip('/'), self.get_subpath(obj).strip('/'))
+                               }
+                               self.add_item(feed, obj, kwargs=kwargs)
        
                        response = HttpResponse(mimetype=feed.mime_type)
                        feed.write(response, 'utf-8')
@@ -60,14 +72,19 @@ class FeedMultiViewMixin(object):
 
                return inner
        
-       def get_feed(self, feed_type, request, node, kwargs, reverse_name):
-               title = self.feed_title
-               link = '/%s/%s/' % (node.get_absolute_url().strip('/'), reverse(reverse_name, urlconf=self, kwargs=kwargs).strip('/'))
-               description = self.feed_description
+       def get_feed(self, feed_type, extra_context, kwargs=None):
+               defaults = {
+                       'description': ''
+               }
+               defaults.update(kwargs or {})
+               
                if feed_type == 'rss':
-                       return self.rss_feed(title, link, description)
+                       return self.rss_feed(**defaults)
+               
+               if 'description' in defaults and defaults['description'] and 'subtitle' not in defaults:
+                       defaults['subtitle'] = defaults['description']
                
-               return self.atom_feed(title, link, description, subtitle=description)
+               return self.atom_feed(**defaults)
        
        def feed_patterns(self, object_fetcher, page, base_name):
                feed_name = '%s_feed' % base_name
@@ -77,5 +94,6 @@ class FeedMultiViewMixin(object):
                )
                return urlpatterns
        
-       def get_obj_description(self, obj):
-               raise NotImplementedError
\ No newline at end of file
+       def add_item(self, feed, obj, kwargs=None):
+               defaults = kwargs or {}
+               feed.add_item(**defaults)
\ No newline at end of file
index 4465a99..18c22a4 100644 (file)
@@ -1,5 +1,6 @@
 from datetime import date
 from django import forms
+from django.conf import settings
 from django.contrib.auth.forms import AuthenticationForm, UserCreationForm
 from django.contrib.auth.models import User
 from django.core.exceptions import ValidationError
@@ -19,6 +20,13 @@ class EmailInput(forms.TextInput):
 
 class RegistrationForm(UserCreationForm):
        email = forms.EmailField(widget=EmailInput)
+       try:
+               from recaptcha_django import ReCaptchaField
+       except ImportError:
+               pass
+       else:
+               if 'recaptcha_django.middleware.ReCaptchaMiddleware' in settings.MIDDLEWARE_CLASSES:
+                       recaptcha = ReCaptchaField()
        
        def clean_username(self):
                username = self.cleaned_data['username']
index 93edab1..efb6504 100644 (file)
@@ -6,11 +6,12 @@ from django.contrib.auth.forms import PasswordResetForm, SetPasswordForm, Passwo
 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 send_mail
+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
+from django.template.defaultfilters import striptags
 from django.utils.http import int_to_base36, base36_to_int
 from django.utils.translation import ugettext_lazy, ugettext as _
 from django.views.decorators.cache import never_cache
@@ -81,7 +82,7 @@ class LoginMultiView(MultiView):
                        if host != request.get_host():
                                referrer = None
                        else:
-                               redirect = ''.join(referrer[2:])
+                               redirect = '%s?%s' % (referrer[2], referrer[4])
                
                if referrer is None:
                        redirect = node.get_absolute_url()
@@ -107,6 +108,9 @@ class LoginMultiView(MultiView):
                """
                Displays the login form for the given HttpRequest.
                """
+               if request.user.is_authenticated():
+                       return HttpResponseRedirect(node.get_absolute_url())
+               
                context = self.get_context(extra_context)
                
                from django.contrib.auth.models import User
@@ -172,11 +176,20 @@ class LoginMultiView(MultiView):
                return inner
        
        def send_confirmation_email(self, subject, email, page, extra_context):
-               message = page.render_to_string(extra_context=extra_context)
+               text_content = page.render_to_string(extra_context=extra_context)
                from_email = 'noreply@%s' % Site.objects.get_current().domain
-               send_mail(subject, message, from_email, [email])
+               
+               if page.template.mimetype == 'text/html':
+                       msg = EmailMultiAlternatives(subject, striptags(text_content), from_email, [email])
+                       msg.attach_alternative(text_content, 'text/html')
+                       msg.send()
+               else:
+                       send_mail(subject, text_content, from_email, [email])
        
        def password_reset(self, request, node=None, extra_context=None, token_generator=password_token_generator):
+               if request.user.is_authenticated():
+                       return HttpResponseRedirect(node.get_absolute_url())
+               
                if request.method == 'POST':
                        form = PasswordResetForm(request.POST)
                        if form.is_valid():
@@ -257,7 +270,7 @@ class LoginMultiView(MultiView):
                                }
                                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('')
+                               return HttpResponseRedirect(node.get_absolute_url())
                else:
                        form = RegistrationForm()
                
@@ -350,7 +363,7 @@ class AccountMultiView(LoginMultiView):
                
                return form_instances
        
-       def account_view(self, request, node=None, extra_context=None, token_generator=email_token_generator):
+       def account_view(self, request, node=None, 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
@@ -407,7 +420,8 @@ class AccountMultiView(LoginMultiView):
        def account_required(self, view):
                def inner(request, *args, **kwargs):
                        if not self.has_valid_account(request.user):
-                               messages.add_message(request, messages.ERROR, "You need to add some account information before you can post listings.", fail_silently=True)
+                               if not request.method == "POST":
+                                       messages.add_message(request, messages.ERROR, "You need to add some account information before you can access this page.", fail_silently=True)
                                return self.account_view(request, *args, **kwargs)
                        return view(request, *args, **kwargs)
                
@@ -431,7 +445,7 @@ class AccountMultiView(LoginMultiView):
                
                user = get_object_or_404(User, id=uid_int)
                
-               email = email.replace('+', '@')
+               email = '@'.join(email.rsplit('+', 1))
                
                if email == user.email:
                        # Then short-circuit.
index 57e772d..bf498fa 100644 (file)
--- a/forms.py
+++ b/forms.py
@@ -1,6 +1,7 @@
 from django import forms
 from django.contrib.admin.widgets import AdminTextareaWidget
 from django.core.exceptions import ValidationError, ObjectDoesNotExist
+from django.db.models import Q
 from django.forms.models import model_to_dict, fields_for_model, ModelFormMetaclass, ModelForm, BaseInlineFormSet
 from django.forms.formsets import TOTAL_FORM_COUNT
 from django.template import loader, loader_tags, TemplateDoesNotExist, Context, Template as DjangoTemplate
@@ -280,7 +281,12 @@ class ContentReferenceInlineFormSet(ContainerInlineFormSet):
                super(ContentReferenceInlineFormSet, self).__init__(containers, data, files, instance, save_as_new, prefix, queryset)
        
        def get_container_instances(self, containers, qs):
-               qs = qs.filter(name__in=[c[0] for c in containers])
+               filter = Q()
+               
+               for name, ct in containers:
+                       filter |= Q(name=name, content_type=ct)
+               
+               qs = qs.filter(filter)
                container_instances = []
                for container in qs:
                        container_instances.append(container)
index 33c994c..ceeb81e 100644 (file)
@@ -11,8 +11,8 @@ from UserDict import DictMixin
 
 
 class Tag(models.Model):
-       name = models.CharField(max_length=250)
-       slug = models.SlugField(unique=True)
+       name = models.CharField(max_length=255)
+       slug = models.SlugField(max_length=255, unique=True)
        
        def __unicode__(self):
                return self.name
index 23be42b..14f5063 100644 (file)
@@ -11,6 +11,7 @@ from philo.models.base import TreeEntity, Entity, QuerySetMapper, register_value
 from philo.utils import ContentTypeSubclassLimiter
 from philo.validators import RedirectValidator
 from philo.exceptions import ViewDoesNotProvideSubpaths, AncestorDoesNotExist
+from philo.signals import view_about_to_render, view_finished_rendering
 
 
 _view_content_type_limiter = ContentTypeSubclassLimiter(None)
@@ -60,6 +61,13 @@ class View(Entity):
                return QuerySetMapper(self.relationship_set, passthrough=node.relationships)
        
        def render_to_response(self, node, request, path=None, subpath=None, extra_context=None):
+               extra_context = extra_context or {}
+               view_about_to_render.send(sender=self, node=node, request=request, path=path, subpath=subpath, extra_context=extra_context)
+               response = self.actually_render_to_response(node, request, path, subpath, extra_context)
+               view_finished_rendering.send(sender=self, response=response)
+               return response
+       
+       def actually_render_to_response(self, node, request, path=None, subpath=None, extra_context=None):
                raise NotImplementedError('View subclasses must implement render_to_response.')
        
        class Meta:
@@ -74,7 +82,7 @@ class MultiView(View):
        
        urlpatterns = []
        
-       def render_to_response(self, node, request, path=None, subpath=None, extra_context=None):
+       def actually_render_to_response(self, node, request, path=None, subpath=None, extra_context=None):
                clear_url_caches()
                if not subpath:
                        subpath = ""
@@ -101,7 +109,7 @@ class Redirect(View):
        target = models.CharField(max_length=200, validators=[RedirectValidator()])
        status_code = models.IntegerField(choices=STATUS_CODES, default=302, verbose_name='redirect type')
        
-       def render_to_response(self, node, request, path=None, subpath=None, extra_context=None):
+       def actually_render_to_response(self, node, request, path=None, subpath=None, extra_context=None):
                response = HttpResponseRedirect(self.target)
                response.status_code = self.status_code
                return response
@@ -116,7 +124,7 @@ class File(View):
        mimetype = models.CharField(max_length=255)
        file = models.FileField(upload_to='philo/files/%Y/%m/%d')
        
-       def render_to_response(self, node, request, path=None, subpath=None, extra_context=None):
+       def actually_render_to_response(self, node, request, path=None, subpath=None, extra_context=None):
                wrapper = FileWrapper(self.file)
                response = HttpResponse(wrapper, content_type=self.mimetype)
                response['Content-Length'] = self.file.size
index 4260761..e000b1b 100644 (file)
@@ -14,12 +14,13 @@ from philo.models.base import TreeModel, register_value_model
 from philo.models.nodes import View
 from philo.utils import fattr
 from philo.templatetags.containers import ContainerNode
+from philo.signals import page_about_to_render_to_string, page_finished_rendering_to_string
 
 
 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, help_text='Default: %s' % settings.DEFAULT_CONTENT_TYPE)
+       mimetype = models.CharField(max_length=255, default=getattr(settings, 'DEFAULT_CONTENT_TYPE', 'text/html'))
        code = models.TextField(verbose_name='django template code')
        
        @property
@@ -110,10 +111,15 @@ class Page(View):
                context.update({'page': self, 'attributes': self.attributes, 'relationships': self.relationships})
                if node and request:
                        context.update({'node': node, 'attributes': self.attributes_with_node(node), 'relationships': self.relationships_with_node(node)})
-                       return self.template.django_template.render(RequestContext(request, context))
-               return self.template.django_template.render(Context(context))
+                       page_about_to_render_to_string.send(sender=self, node=node, request=request, extra_context=context)
+                       string = self.template.django_template.render(RequestContext(request, context))
+               else:
+                       page_about_to_render_to_string.send(sender=self, node=node, request=request, extra_context=context)
+                       string = self.template.django_template.render(Context(context))
+               page_finished_rendering_to_string.send(sender=self, string=string)
+               return string
        
-       def render_to_response(self, node, request, path=None, subpath=None, extra_context=None):
+       def actually_render_to_response(self, node, request, path=None, subpath=None, extra_context=None):
                return HttpResponse(self.render_to_string(node, request, path, subpath, extra_context), mimetype=self.template.mimetype)
        
        def __unicode__(self):
index e76f319..37ea3b1 100644 (file)
@@ -1,3 +1,15 @@
 from django.dispatch import Signal
 
-entity_class_prepared = Signal(providing_args=['class'])
\ No newline at end of file
+
+entity_class_prepared = Signal(providing_args=['class'])
+view_about_to_render = Signal(providing_args=['node', 'request', 'path', 'subpath', 'extra_context'])
+view_finished_rendering = Signal(providing_args=['response'])
+page_about_to_render_to_string = Signal(providing_args=['node', 'request', 'path', 'subpath', 'extra_context'])
+page_finished_rendering_to_string = Signal(providing_args=['string'])
+
+
+def replace_sender_response(sender, response):
+       """Helper function to swap in a new response."""
+       def render_to_response(self, *args, **kwargs):
+               return response
+       sender.actually_render_to_response = render_to_response
\ No newline at end of file
index 92e126e..90af297 100644 (file)
@@ -18,6 +18,8 @@ class ContainerNode(template.Node):
                content = settings.TEMPLATE_STRING_IF_INVALID
                if 'page' in context:
                        container_content = self.get_container_content(context)
+               else:
+                       container_content = None
                
                if self.as_var:
                        context[self.as_var] = container_content
index 911adfe..ab4c216 100644 (file)
--- a/views.py
+++ b/views.py
@@ -1,22 +1,57 @@
+from django.contrib.sites.models import Site
+from django.conf import settings
 from django.http import Http404, HttpResponse
 from django.template import RequestContext
-from django.contrib.sites.models import Site
+from django.views.decorators.vary import vary_on_headers
 from philo.models import Node
 
 
+@vary_on_headers('Accept')
 def node_view(request, path=None, **kwargs):
        node = None
        subpath = None
        if path is None:
                path = '/'
+       current_site = Site.objects.get_current()
        try:
-               current_site = Site.objects.get_current()
-               if current_site:
-                       node, subpath = Node.objects.get_with_path(path, root=current_site.root_node, absolute_result=False)
+               node, subpath = Node.objects.get_with_path(path, root=current_site.root_node, absolute_result=False)
        except Node.DoesNotExist:
                raise Http404
+       
        if not node:
                raise Http404
-       if subpath and not node.accepts_subpath:
-               raise Http404
-       return node.render_to_response(request, path=path, subpath=subpath)
+       
+       try:
+               if subpath and not node.accepts_subpath:
+                       raise Http404
+               return node.render_to_response(request, path=path, subpath=subpath)
+       except Http404, e:
+               if settings.DEBUG:
+                       raise e
+               
+               try:
+                       Http404View = node.relationships['Http404']
+               except KeyError:
+                       Http404View = None
+               
+               if not Http404View:
+                       raise e
+               
+               extra_context = {'exception': e}
+               
+               return Http404View.render_to_response(node, request, path, subpath, extra_context)
+       except Exception, e:
+               if settings.DEBUG:
+                       raise e
+               
+               try:
+                       Http500View = node.relationships['Http500']
+                       
+                       if not Http500View:
+                               raise e
+                       
+                       extra_context = {'exception': e}
+                       
+                       return Http500View.render_to_response(node, request, path, subpath, extra_context)
+               except:
+                       raise e
\ No newline at end of file