Source

Mango / views.py

Full commit
#!/usr/bin/python
# -*- coding: utf-8 -*-

import datetime
import hashlib
import os
import re
import urllib

import disqus

from django.core.mail import EmailMultiAlternatives, send_mail
from django.core.urlresolvers import reverse
from django.http import Http404, HttpResponse, HttpResponseRedirect
from django.shortcuts import render_to_response
from django.template import Context, loader
from django.template.loader import render_to_string

from mango import settings, utils
from mango.forms import CommentForm, ContactForm
from mango.settings import *
from mango.templatetags.mango_extras import slugify
from mango.utils import convert_html_chars, RE

MESSAGES = {
    'approve': {'do': 'Approve comment', 'done': 'Comment approved.'},
    'delete': {'do': 'Delete comment', 'done': 'Comment deleted.'},
    'spam': {'do': 'Mark comment as spam (and delete it)', 'done': 'Comment marked as spam and deleted.'},
}

def redirect(request, path):
    return HttpResponseRedirect(reverse('mango.views.post', args=[path]))

def context_defaults(request):
    path_to_css, css = settings.__dict__.get('CSS', (None, []))
    path_to_js, js = settings.__dict__.get('JS', (None, []))
    return {
        'archives': utils.archives(),
        'posts': utils.posts(),
        'settings': settings,
        'stylesheets': [dict(media=media, href=path_to_css.lstrip('.')+filename) for media, stylesheets in css for filename in stylesheets],
        'scripts': [dict(src=path_to_js.lstrip('.')+filename) for filename in js],
        'now': datetime.datetime.now(),
    }

def index(request):
    return render_to_response('index.dhtml', context_defaults(request))

def archives(request):
    return render_to_response('archives.dhtml', context_defaults(request))

def tags(request):
    tags = {}
    for post in utils.posts():
        for tag in post['meta'].get('tags', []):
            if tag in tags:
                tags[tag] += 1
            else:
                tags[tag] = 1
    tags = [(key, value) for key, value in sorted(tags.items(), key=lambda pair: pair[0].lower())]
    return render_to_response('tags.dhtml', dict({'tags': tags}, **context_defaults(request)))

def tagged_as(request, tag):
    tag = slugify(tag)
    return render_to_response('tag.dhtml', dict({
                'tag': tag,
                'tagged': [p for p in utils.posts() if tag in [slugify(t) for t in p['meta'].get('tags', [])]],
            }, **context_defaults(request)))

#TODO - this name should be changed as it now is also an index page, should this be combined with the index post?
def post(request, path, view_source=False):
    def render_post(filepath):
        context = utils.parse_file(filepath)
        form = CommentForm()
        thread = None
        comment = request.session.get('comment')
        if comment:
            del request.session['comment']
            t = loader.get_template('comment.dhtml')
            comment = t.render(Context({'comment': comment}))

        def get_forum(forums):
            shortname = DISQUS['shortname']
            for forum in forums:
                if forum.shortname == shortname:
                    return forum
            raise disqus.APIError

        comments = []
        #TODO - This will need to be updated, I don't currently have a DISQUS account so can't test, but I've changed the identifier
        if settings.__dict__.get('DISQUS'):
            try:
                dsq = disqus.DisqusService(DISQUS_API_VERSION)
                dsq.login(DISQUS.get('api_key'))
                forum = get_forum(dsq.get_forum_list())
                #This call is only here for legacy and migration support, it will never return a value on Mango made threads
                thread = dsq.get_thread_by_url(forum, canon_urls['abs'])
                if not thread:
                    thread = dsq.thread_by_identifier(forum, context['meta']['title'], canon_urls['rel'])['thread']

                if request.method == 'POST':
                    form = CommentForm(request.POST)
                    if form.is_valid():
                        author_name = form.cleaned_data['author_name']
                        author_email = form.cleaned_data['author_email']
                        author_url = form.cleaned_data['author_url']
                        message = form.cleaned_data['message']
                        subscribe = form.cleaned_data['subscribe']

                        try:
                            ip_address = urllib.urlopen('http://www.whatismyip.com/automation/n09230945.asp').readlines()[0]
                        except:
                            ip_address = None

                        # send request to Disqus
                        comment = dsq.create_post(forum, thread, message=message, ip_address=ip_address,
                                author_name=author_name, author_email=author_email, author_url=author_url)

                        # store comment so that it can be displayed to the author even if withheld for moderation
                        request.session['comment'] = comment

                        # send e-mail notification
                        author = u'%s <%s>' % (author_name, author_email)
                        subject = u'[%s] Comment: "%s"' % (SITE_TITLE.decode('utf-8'), context['meta']['title'])

                        query_string = u'api_key=%s&post_id=%s' % (
                                hashlib.sha1(DISQUS['api_key']).hexdigest(), comment.id)

                        text_content = u'%s wrote:\n\n%s' % (author, message)
                        html_content = u'<p>%s wrote:</p>' % convert_html_chars(author)
                        html_content += u'<blockquote>%s</blockquote><ul>' % re.sub(r'([ \t]*\n){2,}', '</p><p>',
                                convert_html_chars(message))
                        for action, messages in sorted(MESSAGES.items()):
                            text_content += u'\n\n* %s: %s/moderate/%s?%s' % (
                                    messages['do'], BASE_URL, action, query_string)
                            html_content += u'<li><a href="%s/moderate/%s?%s">%s</a></li>' % (
                                    BASE_URL, action, convert_html_chars(query_string), messages['do'])
                        text_content += u'\n\n* Respond: %s/%s/#respond' % (BASE_URL, path)
                        html_content += u'<li><a href="%s/%s/#respond">Respond</a></li></ul>' % (BASE_URL, path)

                        msg = EmailMultiAlternatives(subject, text_content, author, [utils.primary_author_email()])
                        msg.attach_alternative(html_content, 'text/html')
                        msg.send(fail_silently=False)

                        return HttpResponseRedirect('redirect/')

                for c in dsq.get_thread_posts(forum, thread, limit=9999, exclude='killed'):
                    if c.has_been_moderated:
                        comments.append(c)
                if DISQUS.get('sort') == 'oldest_first':
                    comments.sort(key=lambda comment: comment.created_at)
            except disqus.APIError:
                pass

        template = context['meta'].get('type', 'post' if 'datetime' in context['meta'] else 'page')
        return render_to_string('%s.dhtml' % template, dict(context, **dict({
                    'comments': comments,
                    'form': form,
                    'new_comment': comment,
                    'short_urls': short_urls,
                    'thread': thread,
                }, **context_defaults(request))))

    filepath = os.path.join(os.sep, *utils.absolute_path_to_posts().split('/'))
    is_short = False
    fragments = path.split('/')
    last_index = len(fragments) - 1

    def match_fragment(dirnames=[], filenames=[]):
        for name in dirnames or filenames:
            match = re.match(RE['alias=>canon'], name if dirnames else os.path.splitext(name)[0])
            if fragment in match.groupdict().values():
                return (True, os.path.join(filepath, name), is_short or fragment == match.group('alias'))
        return (False, filepath, is_short)

    for index, fragment in enumerate(fragments):
        found = False
        for dirpath, dirnames, filenames in os.walk(filepath):
            if index == last_index:
                found, filepath, is_short = match_fragment(filenames=filenames) # files take precedence
                if found:
                    break
            found, filepath, is_short = match_fragment(dirnames=dirnames)
            if found:
                break

    if not found:
        raise Http404

    short_urls, canon_urls = utils.post_urls(filepath)

    if is_short:
        if view_source:
            canon_urls['abs'] += 'm/'
        return HttpResponseRedirect(canon_urls['abs'])

    if os.path.isdir(filepath):
        #TODO - if view_source is true what do we do here?
        #TODO - caching
        match = re.match(RE['alias=>canon'], os.path.split(filepath)[1])
        html = render_to_response('category.dhtml', dict({
            'name': match.group('canon'),
            'category': utils.posts(filepath, include_pages=True),
        }, **context_defaults(request)))
        return HttpResponse(html)

    if os.path.islink(filepath): # symbolic link - must appear before view source
        short_urls, canon_urls = utils.post_urls(os.path.realpath(filepath))
        return HttpResponseRedirect(canon_urls['rel'])

    if view_source: # Return the plain text of the Markdown page.
        text = utils.parse_file(filepath, plaintext=True)
        return HttpResponse(text, content_type='text/plain; charset=utf-8')

    # Parse the Markdown and return it through the Django template.
    value = render_post(filepath)
    if isinstance(value, HttpResponseRedirect):
        return value

    return HttpResponse(value)

def contact(request, message_sent=False):
    if message_sent:
        return render_to_response('contact.dhtml', context_defaults(request))

    if request.method == 'POST':
        form = ContactForm(request.POST)
        if form.is_valid():
            subject = form.cleaned_data['subject']
            message = form.cleaned_data['message']
            sender = u'%s <%s>' % (form.cleaned_data['sender_name'], form.cleaned_data['sender_email'])

            recipients = [utils.primary_author_email()]
            if form.cleaned_data['cc_sender']:
                recipients.append(sender)

            send_mail(subject, message, sender, recipients, fail_silently=False)
            return HttpResponseRedirect('thanks/')
    else:
        form = ContactForm()

    return render_to_response('contact.dhtml', dict({'form': form}, **context_defaults(request)))

def moderate(request, action):
    if request.GET.get('api_key') != hashlib.sha1(DISQUS['api_key']).hexdigest():
        return HttpResponseBadRequest('Invalid API key.')

    post_id = request.GET.get('post_id')
    dsq = disqus.DisqusService(DISQUS_API_VERSION)
    dsq.login(DISQUS['api_key'])

    if action == 'approve':
        dsq.moderate_post(post_id, 'approve')
    else:
        dsq.moderate_post(post_id, 'kill')
        if action == 'spam':
            dsq.moderate_post(post_id, 'spam')

    return HttpResponse(MESSAGES[action]['done'], content_type='text/plain; charset=utf-8')