Example #1
0
File: blog.py Project: mloar/bloog
def process_article_edit(handler, permalink):
    # For http PUT, the parameters are passed in URIencoded string in body
    body = handler.request.body
    params = cgi.parse_qs(body)
    for key,value in params.iteritems():
        params[key] = value[0]
    property_hash = restful.get_sent_properties(params.get,
        ['title',
         ('body', get_sanitizer_func(handler, trusted_source=True)),
         ('format', get_format),
         ('updated', get_datetime),
         ('tags', get_tags),
         ('html', get_html, 'body', 'format')])

    if property_hash:
        if 'tags' in property_hash:
            property_hash['tag_keys'] = [get_tag_key(name) 
                                         for name in property_hash['tags']]
        article = db.Query(models.blog.Article).filter('permalink =', permalink).get()
        before_tags = set(article.tag_keys)
        for key,value in property_hash.iteritems():
            setattr(article, key, value)
        after_tags = set(article.tag_keys)
        for removed_tag in before_tags - after_tags:
            db.get(removed_tag).counter.decrement()
        for added_tag in after_tags - before_tags:
            db.get(added_tag).counter.increment()
        process_embedded_code(article)
        article.put()
        restful.send_successful_response(handler, '/' + article.permalink)
        view.invalidate_cache()
    else:
        handler.error(400)
Example #2
0
File: blog.py Project: mloar/bloog
def process_article_submission(handler, article_type):
    property_hash = restful.get_sent_properties(handler.request.get, 
        ['title',
         ('body', get_sanitizer_func(handler, trusted_source=True)),
         'legacy_id',
         ('format', get_format),
         ('published', get_datetime),
         ('updated', get_datetime),
         ('tags', get_tags),
         ('html', get_html, 'body', 'format'),
         ('permalink', permalink_funcs[article_type], 'title', 'published')])

    if property_hash:
        if 'tags' in property_hash:
            property_hash['tag_keys'] = [get_tag_key(name) 
                                         for name in property_hash['tags']]
        property_hash['format'] = 'html'   # For now, convert all to HTML
        property_hash['article_type'] = article_type
        article = models.blog.Article(**property_hash)
        article.set_associated_data(
            {'relevant_links': handler.request.get('relevant_links'),
             'amazon_items': handler.request.get('amazon_items')})
        process_embedded_code(article)
        article.put()
        for key in article.tag_keys:
            db.get(key).counter.increment()
        do_sitemap_ping()
        restful.send_successful_response(handler, '/' + article.permalink)
        view.invalidate_cache()
    else:
        handler.error(400)
Example #3
0
def process_article_submission(handler, article_type):
    property_hash = restful.get_sent_properties(handler.request.get, [
        'title', ('body', get_sanitizer_func(handler, trusted_source=True)),
        'legacy_id', ('format', get_format), ('published', get_datetime),
        ('updated', get_datetime), ('tags', get_tags),
        ('html', get_html, 'body', 'format'),
        ('permalink', permalink_funcs[article_type], 'title', 'published')
    ])

    if property_hash:
        if 'tags' in property_hash:
            property_hash['tag_keys'] = [
                get_tag_key(name) for name in property_hash['tags']
            ]
        property_hash['format'] = 'html'  # For now, convert all to HTML
        property_hash['article_type'] = article_type
        article = models.blog.Article(**property_hash)
        article.set_associated_data({
            'relevant_links':
            handler.request.get('relevant_links'),
            'amazon_items':
            handler.request.get('amazon_items')
        })
        process_embedded_code(article)
        article.put()
        # Ensure there is a year entity for this entry's year
        models.blog.Year.get_or_insert('Y%d' % (article.published.year, ))
        # Update tags
        for key in article.tag_keys:
            db.get(key).counter.increment()
        do_sitemap_ping()
        restful.send_successful_response(handler, '/' + article.permalink)
        view.invalidate_cache()
    else:
        handler.error(400)
Example #4
0
def process_article_edit(handler, permalink):
    # For http PUT, the parameters are passed in URIencoded string in body
    body = handler.request.body
    params = cgi.parse_qs(body)
    for key, value in params.iteritems():
        params[key] = value[0]
    property_hash = restful.get_sent_properties(params.get, [
        'title', ('body', get_sanitizer_func(handler, trusted_source=True)),
        ('format', get_format), ('updated', get_datetime), ('tags', get_tags),
        ('html', get_html, 'body', 'format')
    ])

    if property_hash:
        if 'tags' in property_hash:
            property_hash['tag_keys'] = [
                get_tag_key(name) for name in property_hash['tags']
            ]
        article = db.Query(models.blog.Article).filter('permalink =',
                                                       permalink).get()
        before_tags = set(article.tag_keys)
        for key, value in property_hash.iteritems():
            setattr(article, key, value)
        after_tags = set(article.tag_keys)
        for removed_tag in before_tags - after_tags:
            db.get(removed_tag).counter.decrement()
        for added_tag in after_tags - before_tags:
            db.get(added_tag).counter.increment()
        process_embedded_code(article)
        article.put()
        restful.send_successful_response(handler, '/' + article.permalink)
        view.invalidate_cache()
    else:
        handler.error(400)
Example #5
0
  def put(self, comment_id):
    logging.debug("CommentHandler#put for comment %s", comment_id)
    # For HTTP PUT, the parameters are passed in URIencoded string in body
    sanitize_comment = get_sanitizer_func(self,
      allow_attributes = ['href', 'src'],
      blacklist_tags = ['img', 'script'])
    body = self.request.body
    params = cgi.parse_qs(body)
    for key, value in params.iteritems():
      params[key] = value[0]
      if not isinstance(params[key], unicode):
        params[key] = params[key].decode(config.APP['charset'])

    tmp_hash = restful.get_sent_properties(self.request.get,
      [('commentEmail', cgi.escape),
      ('commentHomepage', cgi.escape),
      ('commentTitle', cgi.escape),
      ('commentName', cgi.escape),
      ('commentBody', sanitize_comment)])
    property_hash = {}
    props = (("commentEmail", "email"), ("commentHomepage", "homepage"),
        ("commentTitle", "title"), ("commentBody", "body"),
        ('commentName', 'name'))
    for pair in props:
      if pair[0] in tmp_hash:
        logging.debug("Copying '%s' from received properties to '%s' in property hash (value: %s)", pair[0], pair[1], str(tmp_hash[pair[0]]))
        property_hash[pair[1]] = tmp_hash[pair[0]]
    if property_hash:
      comment = models.blog.Comment.get(db.Key(comment_id))
      for key, value in property_hash.iteritems():
        setattr(comment, key, value)
      comment.put()

      # Render just this comment and send it to client
      view_path = view.find_file(view.templates, "gablog/blog/comment.html")
      allow_comments = comment.article.allow_comments
      if allow_comments is None:
        age = (datetime.datetime.now() - comment.article.published).days
        allow_comments = (age <= config.BLOG['comment_window'])

      # response = template.render(os.path.join(config.APP['template_dir'], view_path),
      response = render_to_string(os.path.join(config.APP['template_dir'], view_path),
        { 'comment': comment, "use_gravatars": config.BLOG["use_gravatars"],
          "allow_comments": allow_comments, "user_is_admin":
          users.is_current_user_admin()},
        debug = config.DEBUG)
      self.response.out.write(response)
      view.invalidate_cache(comment.article.permalink)
    else:
      self.error(400)
Example #6
0
File: blog.py Project: mloar/bloog
def process_comment_submission(handler, article):
    sanitize_comment = get_sanitizer_func(handler,
                                          allow_attributes=['href', 'src'],
                                          blacklist_tags=['img'])
    property_hash = restful.get_sent_properties(handler.request.get, 
        ['name',
         'email',
         'homepage',
         'title',
         ('body', sanitize_comment),
         'key',
         'thread',    # If it's given, use it.  Else generate it.
         'captcha',
         ('published', get_datetime)])

    # If we aren't administrator, abort if bad captcha
    if not users.is_current_user_admin():
        if property_hash.get('captcha', None) != get_captcha(article.key()):
            logging.info("Received captcha (%s) != %s", 
                          property_hash.get('captcha', None),
                          get_captcha(article.key()))
            handler.error(401)      # Unauthorized
            return
    if 'key' not in property_hash and 'thread' not in property_hash:
        handler.error(401)
        return

    # Generate a thread string.
    if 'thread' not in property_hash:
        matchobj = re.match(r'[^#]+#comment-(?P<key>\w+)', 
                            property_hash['key'])
        if matchobj:
            logging.debug("Comment has parent: %s", matchobj.group('key'))
            comment_key = matchobj.group('key')
            # TODO -- Think about GQL injection security issue since 
            # it can be submitted by public
            parent = models.blog.Comment.get(db.Key(comment_key))
            thread_string = parent.next_child_thread_string()
        else:
            logging.debug("Comment is off main article")
            comment_key = None
            thread_string = article.next_comment_thread_string()
        if not thread_string:
            handler.error(400)
            return
        property_hash['thread'] = thread_string
        del property_hash['key']

    # Get and store some pieces of information from parent article.
    # TODO: See if this overhead can be avoided
    if not article.num_comments:
        article.num_comments = 1
    else:
        article.num_comments += 1
    property_hash['article'] = article.put()

    try:
        comment = models.blog.Comment(**property_hash)
        comment.put()
    except:
        logging.debug("Bad comment: %s", property_hash)
        handler.error(400)
        return
        
    # Notify the author of a new comment (from matteocrippa.it)
    if config.BLOG['send_comment_notification']:
        recipient = "%s <%s>" % (config.BLOG['author'], config.BLOG['email'],)
        body = ("A new comment has just been posted on %s/%s by %s."
                % (config.BLOG['root_url'], article.permalink, comment.name))
        mail.send_mail(sender=config.BLOG['email'],
                       to=recipient,
                       subject="New comment by %s" % (comment.name,),
                       body=body)

    # Render just this comment and send it to client
    view_path = view.find_file(view.templates, "bloog/blog/comment.html")
    response = template.render(
        os.path.join("views", view_path),
        { 'comment': comment, "use_gravatars": config.BLOG["use_gravatars"] },
        debug=config.DEBUG)
    handler.response.out.write(response)
    view.invalidate_cache()
Example #7
0
File: blog.py Project: JamieS/bloog
def process_comment_submission(handler, parent=None):
    sanitize_comment = get_sanitizer_func(handler,
                                          allow_attributes=['href', 'src'],
                                          blacklist_tags=['img', 'script'])
    property_hash = restful.get_sent_properties(handler.request.get, 
        [('name', cgi.escape),
         ('email', cgi.escape),
         ('homepage', cgi.escape),
         ('title', cgi.escape),
         ('body', sanitize_comment),
         ('article_id', cgi.escape),
         'recaptcha_challenge_field',
         'recaptcha_response_field',
         ('published', get_datetime)])

    # If we aren't administrator, abort if bad captcha
    if not users.is_current_user_admin():
        cap_challenge = property_hash.get('recaptcha_challenge_field')
        cap_response = property_hash.get('recaptcha_response_field')
        
        cap_validation = captcha.RecaptchaResponse(False)
        if cap_challenge and cap_response:
          cap_validation = captcha.submit( cap_challenge, cap_response, 
            config.BLOG['recap_private_key'], handler.request.remote_addr )
        
        if not cap_validation.is_valid: 
          logging.info( "Invalid captcha: %s", cap_validation.error_code )
          handler.response.set_status(401, 'Invalid Captcha') # Unauthorized
          return
          
    if 'article_id' not in property_hash:
        return handler.error(400)
        
    article = db.Query(models.blog.Article).filter(
        'permalink =', property_hash['article_id'] ).get()

    # Generate a thread string.
    if parent:
        logging.debug("Comment has parent: %s", parent.key())
        thread_string = parent.next_child_thread_string()
    else:
        logging.debug("Comment is off main article")
        thread_string = article.next_comment_thread_string()
        
    property_hash['thread'] = thread_string

    # Get and store some pieces of information from parent article.
    # TODO: See if this overhead can be avoided
    if not article.num_comments: article.num_comments = 1
    else: article.num_comments += 1
    property_hash['article'] = article.put()

    try:
        comment = models.blog.Comment(**property_hash)
        comment.put()
    except:
        logging.debug("Bad comment: %s", property_hash)
        return handler.error(400)
        
    # Notify the author of a new comment (from matteocrippa.it)
    if config.BLOG['send_comment_notification'] and not users.is_current_user_admin():
        recipient = "%s <%s>" % (config.BLOG['author'], config.BLOG['email'])
        article_link = config.BLOG['root_url'] + "/" + article.permalink
        comment_link = '%s#comment-%s' % (article_link, comment.key())
        body = ('''A new comment has just been posted on %s by %s:\n\n"%s"
                \n\nReply to the comment here: %s'''
                % (article_link, comment.name, comment.body, comment_link))
        mail.send_mail(sender=config.BLOG['email'],
                       to=recipient,
                       subject="New comment by %s" % (comment.name),
                       body=body)

    # Render just this comment and send it to client
    view_path = view.find_file(view.templates, "bloog/blog/comment.html")
    response = template.render(
        os.path.join("views", view_path),
        { 'comment': comment, "use_gravatars": config.BLOG["use_gravatars"] },
        debug=config.DEBUG)
    handler.response.out.write(response)
    view.invalidate_cache(comment.article.permalink)
Example #8
0
  def post(self, comment_id):

    sanitize_comment = get_sanitizer_func(self,
      allow_attributes = ['href'],
      blacklist_tags = ['img', 'script', 'iframe']
    )

    tmp_hash = restful.get_sent_properties(self.request.get,
      [('key', cgi.escape),
      ('commentEmail', cgi.escape),
      ('commentHomepage', cgi.escape),
      ('commentTitle', cgi.escape),
      ('commentName', cgi.escape),
      ('commentBody', sanitize_comment),
      ('article_id', cgi.escape),
      'captcha',
      ('published', get_datetime)])
    property_hash = {}
    props = (("key", "key"), ("commentEmail", "email"),
    ("commentHomepage", "homepage"), ("commentTitle", "title"),
    ("commentBody", "body"), ("published", "published"), ("article_id",
      "article_id"), ("captcha", "captcha"), ('commentName', 'name')
    )

    for pair in props:
      if pair[0] in tmp_hash:
        logging.debug("Copying '%s' from received properties to '%s' in property hash (value: %s)", pair[0], pair[1], str(tmp_hash[pair[0]]))
        property_hash[pair[1]] = tmp_hash[pair[0]]

    if "article_id" not in property_hash:
      logging.error("No article_id found: %s", str(property_hash))
      self.error(400)
      return

    article = db.Query(models.blog.Article).filter('permalink =', property_hash["article_id"]).get()

    # If we aren't administrator, abort if bad captcha
    if not users.is_current_user_admin():
      if property_hash.get('captcha', None) != get_captcha(article.key()):
        logging.info("Received captcha (%s) != %s", property_hash.get('captcha', None), get_captcha(article.key()))
        self.error(403)
        self.response.out.write("Invalid CAPTCHA");
        return

    # Generate a thread string.
    if article:
      thread_string = article.next_comment_thread_string()
    else:
      logging.error("No article with ID %s found!", property_hash["article_id"])
      self.error(400)
      return

    logging.debug("New thread string: %s", thread_string)
    property_hash['thread'] = thread_string

    # Get and store some pieces of information from parent article.
    # TODO: See if this overhead can be avoided
    if not article.num_comments:
      article.num_comments = 1
    else:
      article.num_comments += 1
    property_hash['article'] = article.put()

    try:
      comment = models.blog.Comment(**property_hash)
      comment.put()
    except Exception, e:
      logging.error(e)
      logging.error("Bad comment: %s", property_hash)
      handler.error(400)
      return
Example #9
0
def process_article_submission(handler, article_type):
  """
  takes care of permalink
  """

  aio.debug("blog.process_article_submission article_type: %s", article_type)

  tmp_hash = restful.get_sent_properties(handler.request.get, [
    ('postTitle',     get_sanitizer_func(handler, trusted_source = True)),
    ('postBody',      get_sanitizer_func(handler, trusted_source = True)),
    ('postFormat',    get_format),
    ('postType',      get_type),
    ('postUpdated',   get_datetime),
    ('postTags',      get_tags),
    ('html',          get_html, 'postBody', 'postFormat'),
    ('permalink',     permalink_funcs[article_type], 'postTitle', 'postPublished'),
    'postExcerpt',
    'postThumb'
  ])

  property_hash = {}
  props = (
    ("key",           "key"), 
    ("postTitle",     "title"), 
    ("postBody",      "body"),
    ("postExcerpt",   "excerpt"),
    ("postThumb",     "thumb"),
    ("postFormat",    "format"),
    ("postType",      "article_type"), 
    ("postPublished", "published"), 
    ("postUpdated",   "updated"), 
    ("article_id",    "article_id"),
    ("permalink",     "permalink"), 
    ("html",          "html"), 
    ("postTags",      "tags"),
  )

  for pair in props:
    if pair[0] in tmp_hash:
      property_hash[pair[1]] = tmp_hash[pair[0]]

  if property_hash:

    if 'tags' in property_hash:
      property_hash['tag_keys'] = [get_tag_key(name) for name in property_hash['tags']]

    # property_hash['format']       = 'html'   # For now, convert all to HTML
    # property_hash['article_type'] = article_type

    article = models.blog.Article(**property_hash)
    article.set_associated_data({'relevant_links': handler.request.get('relevant_links'), })
    process_embedded_code(article)

    article.put()
    time.sleep(1)

    # Ensure there is a year entity for this entry's year
    if article.published :
      models.blog.Year.get_or_insert('Y%d' % (article.published.year, ))

    # Update tags
    for key in article.tag_keys:
      db.get(key).counter.increment()

    aio.debug("blog.process_article_submission perm: %s, key: %s", article.permalink, article.key())

    # restful.send_successful_response(handler, '/' + article.permalink)

    restful.send_successful_response(handler, '/post/edit/' + str(article.key()))

    view.invalidate_cache(article.permalink)

  else:
    aio.debug("blog.process_article_submission no property_hash")
    handler.error(400)
Example #10
0
def process_article_edit(handler, postlink):
  # For HTTP PUT, the parameters are passed in URIencoded string in body

  body = handler.request.body
  params = cgi.parse_qs(body)

  for key, value in params.iteritems():
    params[key] = value[0]
    if not isinstance(params[key], unicode):
      params[key] = params[key].decode(config.APP['charset'])


  aio.debug("blog.process_article_edit: params.keys: %s", params.keys())

  # article_type = restful.get_sent_properties(params.get, [('postType', get_type)])['postType']
  article_type = params['postType']

  aio.debug("blog.process_article_edit article with postlink: %s, type: %s/%s", postlink, article_type, params['postFormat'])

  tmp_hash = restful.get_sent_properties(params.get, [
    ('postTitle',     cgi.escape),
    ('postBody',      get_sanitizer_func(handler, trusted_source = True)),
    ('postFormat',    get_format),     ## markdown, html, text
    ('postType',      get_type),       ## draft, post, article
    ('postTags',      get_tags),
    ('html',          get_html, 'postBody', 'postFormat'),

    ('postPublished',   get_datetime, 'postPublished'),

    ('permalink',     permalink_funcs[article_type], 'postTitle', 'postPublished'),
    'postThumb',
    # 'postPublished',
  ])

  # aio.debug("blog.process_article_edit tmp_hash: %s", tmp_hash)

  property_hash = {}
  props = (
    ("postTitle",     "title"), 
    ("postBody",      "body"),
    ("postFormat",    "format"),
    ("postPublished", "published"), 
    ("postThumb",     "thumb"),
    ("legacy_id",     "legacy_id"), 
    ("article_id",    "article_id"), 
    ("postType",      "article_type"), 
    ("postTags",      "tags"),
    ("html",          "html"), 
    ("postPerma",     "permalink")
  )
  for pair in props :
    # aio.debug("PAIR: %s", pair)
    if pair is not None:
      if pair[0] in tmp_hash :
        # logging.info("COPY  PAIR %s", pair)
        property_hash[pair[1]] = tmp_hash[pair[0]]
      else : 
        pass
        # aio.debug("IGNORE %s from sent properties to %s in property hash", pair[0], pair[1])
    else :
      aio.debug("ERROR '%s' with pair", pair)


  if property_hash:
    if 'tags' in property_hash:
      property_hash['tag_keys'] = [get_tag_key(name) for name in property_hash['tags'] if name != ""]

    aio.debug("blog.process_article_edit search article by postlink: %s", postlink)


    ## adjust dates
    property_hash['updated'] = datetime.datetime.utcnow()

    if property_hash['article_type'] == 'draft' : 
      property_hash['published'] = None
    else :
      # property_hash['published'] = get_datetime(property_hash['published'])
      property_hash['published'] = property_hash['published']

    aio.debug("blog.process_article_edit pub: %s: upd: %s", property_hash['published'], property_hash['updated'])


    article = db.Query(models.blog.Article).filter('permalink =', postlink).get()
    before_tags = set(article.tag_keys)

    for key, value in property_hash.iteritems():
      # aio.debug("blog.process_article_edit: SAVE: " + key + " > " + aio.asciify(value)[:10]) ## utf errors
      setattr(article, key, value)

    after_tags = set(article.tag_keys)

    for removed_tag in before_tags - after_tags:
      tag = db.get(removed_tag)
      logging.debug("Decrementing tag '%s' with initial value %d", tag.name, tag.counter.count)
      tag.counter.decrement()
      if tag.counter.count == 0:
        logging.debug("Tag %s has count 0, removing tag", tag.name)
        tag.delete_counter()
        tag.delete()

    for added_tag in after_tags - before_tags:
      db.get(added_tag).counter.increment()

    process_embedded_code(article)
    article.put()

    # restful.send_successful_response(handler, '/' + article.permalink)
    restful.send_successful_response(handler, '/post/edit/' + str(article.key()))
    view.invalidate_cache(article.permalink)

  else:
    handler.error(400)
Example #11
0
def process_comment_submission(handler, article):
    sanitize_comment = get_sanitizer_func(handler,
                                          allow_attributes=['href', 'src'],
                                          blacklist_tags=['img', 'script'])
    property_hash = restful.get_sent_properties(
        handler.request.get,
        [
            ('name', cgi.escape),
            ('email', cgi.escape),
            ('homepage', cgi.escape),
            ('title', cgi.escape),
            ('body', sanitize_comment),
            ('key', cgi.escape),
            'thread',  # If it's given, use it.  Else generate it.
            'captcha',
            ('published', get_datetime)
        ])

    # If we aren't administrator, abort if bad captcha
    if not users.is_current_user_admin():
        if property_hash.get('captcha', None) != get_captcha(article.key()):
            logging.info("Received captcha (%s) != %s",
                         property_hash.get('captcha', None),
                         get_captcha(article.key()))
            handler.error(401)  # Unauthorized
            return
    if 'key' not in property_hash and 'thread' not in property_hash:
        handler.error(401)
        return

    # Generate a thread string.
    if 'thread' not in property_hash:
        matchobj = re.match(r'[^#]+#comment-(?P<key>\w+)',
                            property_hash['key'])
        if matchobj:
            logging.debug("Comment has parent: %s", matchobj.group('key'))
            comment_key = matchobj.group('key')
            # TODO -- Think about GQL injection security issue since
            # it can be submitted by public
            parent = models.blog.Comment.get(db.Key(comment_key))
            thread_string = parent.next_child_thread_string()
        else:
            logging.debug("Comment is off main article")
            comment_key = None
            thread_string = article.next_comment_thread_string()
        if not thread_string:
            handler.error(400)
            return
        property_hash['thread'] = thread_string
        del property_hash['key']

    # Get and store some pieces of information from parent article.
    # TODO: See if this overhead can be avoided
    if not article.num_comments:
        article.num_comments = 1
    else:
        article.num_comments += 1
    property_hash['article'] = article.put()

    try:
        comment = models.blog.Comment(**property_hash)
        comment.put()
    except:
        logging.debug("Bad comment: %s", property_hash)
        handler.error(400)
        return

    # Notify the author of a new comment (from matteocrippa.it)
    if config.BLOG['send_comment_notification']:
        recipient = "%s <%s>" % (
            config.BLOG['author'],
            config.BLOG['email'],
        )
        body = ("A new comment has just been posted on %s/%s by %s." %
                (config.BLOG['root_url'], article.permalink, comment.name))
        mail.send_mail(sender=config.BLOG['email'],
                       to=recipient,
                       subject="New comment by %s" % (comment.name, ),
                       body=body)

    # Render just this comment and send it to client
    view_path = view.find_file(view.templates, "bloog/blog/comment.html")
    response = template.render(os.path.join("views", view_path), {
        'comment': comment,
        "use_gravatars": config.BLOG["use_gravatars"]
    },
                               debug=config.DEBUG)
    handler.response.out.write(response)
    view.invalidate_cache()
Example #12
0
def process_comment_submission(handler, parent=None):
    sanitize_comment = get_sanitizer_func(handler,
                                          allow_attributes=['href', 'src'],
                                          blacklist_tags=['img', 'script'])
    property_hash = restful.get_sent_properties(
        handler.request.get,
        [('name', cgi.escape), ('email', cgi.escape), ('homepage', cgi.escape),
         ('title', cgi.escape), ('body', sanitize_comment),
         ('article_id', cgi.escape), 'recaptcha_challenge_field',
         'recaptcha_response_field', ('published', get_datetime)])

    # If we aren't administrator, abort if bad captcha
    if not users.is_current_user_admin():
        cap_challenge = property_hash.get('recaptcha_challenge_field')
        cap_response = property_hash.get('recaptcha_response_field')

        cap_validation = captcha.RecaptchaResponse(False)
        if cap_challenge and cap_response:
            cap_validation = captcha.submit(cap_challenge, cap_response,
                                            config.BLOG['recap_private_key'],
                                            handler.request.remote_addr)

        if not cap_validation.is_valid:
            logging.info("Invalid captcha: %s", cap_validation.error_code)
            handler.response.set_status(401, 'Invalid Captcha')  # Unauthorized
            return

    if 'article_id' not in property_hash:
        return handler.error(400)

    article = db.Query(models.blog.Article).filter(
        'permalink =', property_hash['article_id']).get()

    # Generate a thread string.
    if parent:
        logging.debug("Comment has parent: %s", parent.key())
        thread_string = parent.next_child_thread_string()
    else:
        logging.debug("Comment is off main article")
        thread_string = article.next_comment_thread_string()

    property_hash['thread'] = thread_string

    # Get and store some pieces of information from parent article.
    # TODO: See if this overhead can be avoided
    if not article.num_comments: article.num_comments = 1
    else: article.num_comments += 1
    property_hash['article'] = article.put()

    try:
        comment = models.blog.Comment(**property_hash)
        comment.put()
    except:
        logging.debug("Bad comment: %s", property_hash)
        return handler.error(400)

    # Notify the author of a new comment (from matteocrippa.it)
    if config.BLOG[
            'send_comment_notification'] and not users.is_current_user_admin():
        recipient = "%s <%s>" % (config.BLOG['author'], config.BLOG['email'])
        article_link = config.BLOG['root_url'] + "/" + article.permalink
        comment_link = '%s#comment-%s' % (article_link, comment.key())
        body = ('''A new comment has just been posted on %s by %s:\n\n"%s"
                \n\nReply to the comment here: %s''' %
                (article_link, comment.name, comment.body, comment_link))
        mail.send_mail(sender=config.BLOG['email'],
                       to=recipient,
                       subject="New comment by %s" % (comment.name),
                       body=body)

    # Render just this comment and send it to client
    view_path = view.find_file(view.templates, "bloog/blog/comment.html")
    response = template.render(os.path.join("views", view_path), {
        'comment': comment,
        "use_gravatars": config.BLOG["use_gravatars"]
    },
                               debug=config.DEBUG)
    handler.response.out.write(response)
    view.invalidate_cache(comment.article.permalink)