1. abernier
  2. yab

Source

yab / views.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
#       This program is free software; you can redistribute it and/or modify
#       it under the terms of the GNU General Public License as published by
#       the Free Software Foundation; either version 2 of the License, or
#       (at your option) any later version.
#       
#       This program is distributed in the hope that it will be useful,
#       but WITHOUT ANY WARRANTY; without even the implied warranty of
#       MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#       GNU General Public License for more details.
#       
#       You should have received a copy of the GNU General Public License
#       along with this program; if not, write to the Free Software
#       Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
#       MA 02110-1301, USA.

from collections import defaultdict
from datetime import date, datetime, timedelta
import logging, os

from google.appengine.api import mail
from google.appengine.api import memcache
from google.appengine.api import users
from google.appengine.ext import webapp
from google.appengine.ext.webapp import template

from google.appengine.ext.db import GqlQuery

import config
import models
from lib import pager
from lib import sanitizer

def get_tag_list():
    ''' Slightly modified from Joey Bratton's example.
    
        Returns a sorted list of tags and their respective counts. 
        Possible performance issues here; hence the caching.
        Cache is updated whenever a post is created or edited.
    '''
    tag_list = memcache.get('tag_list')
    if tag_list:
        return tag_list
 
    tag_list = defaultdict(int)

    query = models.BlogPost.all()
    
    for item in query:
        for tag in item.tags:
            tag_list[tag] += 1
 
    sorted_tag_list = []
    for tag in sorted(tag_list.iterkeys()):
        sorted_tag_list.append({'tag': tag,
                                'count': tag_list[tag],
                                'url': '/tag/%s' % (tag),
                                })
 
    memcache.set('tag_list', sorted_tag_list)
    return sorted_tag_list


class BaseHandler(webapp.RequestHandler):
    def __init__(self):
        self.page_size = config.SETTINGS['page_size']
    
    def template_path(self, filename):
        '''Returns full path for template from path relative to here.'''
        return os.path.join(os.path.dirname(__file__), 'templates', filename)

    def render(self, template_filename, template_args):
        ''' Renders template, sends to client.
        
            Arguments:
            template_filename: template path (relative to this file)
            template_args: argument dict for the template
        '''
        user = users.get_current_user()
        if user:
            greeting = ("<a href=\"%s\">logout</a>" %
                        users.create_logout_url("/"))
        else:
            greeting = ("<a href=\"%s\">login</a>" %
                        users.create_login_url("/"))
        
        tag_list = get_tag_list()
        
        # odds and ends we'll need in many areas of the site
        template_args['current_uri'] = self.request.uri
        template_args['tag_list'] = tag_list
        template_args['user'] = user
        template_args['is_admin'] = users.is_current_user_admin()
        template_args['greeting'] = greeting
        
        if 'blogroll' in config.NAVIGATION:
            template_args['blogroll'] = config.NAVIGATION['blogroll']
        if 'links' in config.NAVIGATION:
            template_args['navigation_links'] = config.NAVIGATION['links']
        template_args['settings'] = config.SETTINGS
        
        self.response.out.write(
            template.render(self.template_path(template_filename), 
                template_args))

    def render_paginated(self, query, template_filename, template_args):
        ''' Rodrigo Moraes' paginated query.
        
            PagerQuery wraps db.Query / has same methods (w00t!).
            
            Example:
            query = pager.PagerQuery(models.BlogPost)
            query.filter('created >', 'bar')
            query.filter('baz =', 'ding') \
            query.order('created')
        
            Arguments:
            query: a GQL query, from which results are pulled
            template_filename: template path (relative to this file)
            template_args: argument dict for the template
        '''
        bookmark = self.request.get('bookmark')

        prev, results, next = query.fetch(self.page_size, bookmark)
        
        template_args['prev_page'] = prev
        template_args['results'] = results
        template_args['next_page'] = next
        
        # passing some additional keyword args
        self.render(self.template_path(template_filename), template_args)


class IndexHandler(BaseHandler):
    ''' Handler for listing blog posts.
        
        Supports returning all blog posts (page_size == 0),  
        or a page_size number at a time.
    '''
    def get(self):
        if self.page_size == 0: # list all blog posts without pagination
            blog_posts = models.BlogPost.all().order('-created')
            
            self.render('index.html', 
                {   'blog_posts': blog_posts, 
                    'title': 'Home',
                })
        else: # if non-zero page_size, use pagination
            bookmark = self.request.get('bookmark')

            query = pager.PagerQuery(models.BlogPost)
            query.order('-created')

            self.render_paginated(query, 'index.html', {'title': 'Home',})


class PostHandler(BaseHandler):
    ''' Handler for viewing blog posts. '''
    def get(self, id, blog_uri):
        ''' Displays a single blog post.

        Note that in this and later handlers, the args come
        from capturing groups in the WSGIApplication specification.
        See the webapp framework docs for more info.

        Args:
          blog_uri: the blog's uri.
        '''
        # this step-by-step method of building a query looks a bit ugly, 
        # but it's how GQL works:
        # http://code.google.com/appengine/docs/python/datastore/queriesandindexes.html
        #
        # prepare the query using either instance methods (done here), 
        # or by parametrized GQL query string.
        # and note that the query isn't actually run until 
        # either `get()` or `fetch()` is called
        
        blog_post = models.BlogPost.get_by_id(int(id))
    
        # workaround to sort comments by date entered (ascending)
        query1 = models.BlogComment.all()
        query1.filter('blog_post = ', blog_post.key())
        query1.order('date')
        blog_comments = query1.fetch(100) # arbitrary max
        
        max_comment_date = (blog_post.created + 
            timedelta(days=config.SETTINGS['comment_open_days']))
        if datetime.now() < max_comment_date:
            comments_open = True
        else:
            comments_open = False
        
        if blog_post:
            self.render('view.html', {
                'blog_post': blog_post,
                'comments': blog_comments, #blog_post.blogcomment_set
                'comments_open': comments_open
            })

    def post(self, id, blog_uri):
        ''' Processes BlogComment creation request. 
            Takes key of current blog_post from hidden field.
            Using Bill Katz's HTML sanitization method.
        '''
        comment_content = self.request.get('comment_content')
        comment_content = sanitizer.sanitize_html(comment_content, 
            allow_attributes=['href', 'src'], blacklist_tags=['img'])
        
        blog_key = self.request.get('blog_key') # get current post
        blog_post = models.BlogPost.get(blog_key)

        max_comment_date = (blog_post.created + 
            timedelta(days=config.SETTINGS['comment_open_days']))
        
        # only process comment if within window
        if datetime.now() < max_comment_date:
            blog_comment = models.BlogComment(author=self.request.get('author'))
            blog_comment.blog_post = blog_post
            blog_comment.content = comment_content
            
            blog_comment.htmlify()
            blog_comment.put()

            admin_name = config.SETTINGS['admin_name']
            admin_email = config.SETTINGS['admin_email']
                
            recipient = "%s <%s>" % (admin_name, admin_email,)
            body = ('''Comment from '%s' re '%s':\n    %s''' 
                    % ( blog_comment.author, 
                        blog_post.title, 
                        blog_comment.content,))
            mail.send_mail(sender=admin_email,
                            to=recipient,
                            subject="Comment on post '%s'." % blog_post.title,
                            body=body)
            # after comment submission 
            # redirect to comment section 
            # of commented-on post (least-astonishment)
            self.redirect('/post/%d/blog_uri#comment' % blog_post.key().id())
        else:
            # perhaps something more interesting here
            self.redirect('/')

class TagHandler(BaseHandler):
    ''' Handler for listing blog posts with a given tag. 
        Lists all blog posts with a given tag using pagination.
    '''
    def get(self, tag):
        query = pager.PagerQuery(models.BlogPost)
        query.filter('tags =', tag)
        query.order('-created')
        
        self.render_paginated(query, 'index.html', 
            {   'tag': tag,
                'title': 'Posts tagged "%s"' % tag,
            })