Source

digrsser / app / digrsser / views.py

Full commit
# -*- coding: utf-8 -*-
"""
The views building logic are implemented in this file.
The routes leading to the views are defined in urls.py
"""
import logging

from google.appengine.api import users

import flask
from flask import abort
from flask import render_template
from flask import redirect
from flask import request
from flask import flash
from flask import url_for
from flask import make_response
import jinja2

from digrsser.application import app

from digrsser.decorators import login_required, admin_required, do_logout
from digrsser.secret_keys import SESSION_KEY
from digrsser import forms
from digrsser.models import Digrsser, Subscription, FeedEntry, FeedGroupingRule, Subscriber, Key


def main():
  # Check if installed and no grouping roles exists
  if not FeedGroupingRule.all().count():
    return redirect(url_for('install'))

  # Show the startup/subscription page based on user
  if users.get_current_user():
    return redirect(url_for('list_subs'))
  else:
    return render_template('welcome.html', name='private')


def cache(engine='gae', param=None):
  """
  Only for testing purposes
  """
  cache = None

  logging.info('Using %s engine' % engine)

  if engine == 'gae':
    from werkzeug.contrib.cache import GAEMemcachedCache as Cache
    cache = Cache(default_timeout=60)
  if engine == 'ext':
    from flaskext.cache import Cache
    cache = Cache(app)
  if engine == 'sdk':
    from google.appengine.api import memcache as cache

  if cache:
    key = '%skey' % engine
    data = cache.get(key)
    if not data:
      data = '%svalue' % engine
      logging.warning('setting data to %s: %s' % (engine, data))
      cache.set(key, data)
    else:
      logging.info('using cached value: %s' % data)

    return make_response('cache test completed: %s:%s' % (engine, param))

  return abort(404)


@admin_required
def install():
  """
  Runs the installation steps
  for new app. Requires admin privileges.
  """
  # Do the installation
  if request.method == 'POST':
    # Generate default grouping rules
    rules = [
      {'name': '1 hour', 'interval':1, 'count':''},
      {'name': '6 hours', 'interval':6, 'count':''},
      {'name': '12 hours', 'interval':12, 'count':''},
      {'name': '1 day', 'interval':24, 'count':''},
      {'name': '2 days', 'interval':48, 'count':''},
      {'name': '5 days', 'interval':120, 'count':''},
      {'name': '7 hours', 'interval':168, 'count':''},
      {'name': '12 hours or 5 entries', 'interval':168, 'count':5},
      {'name': '2 entries', 'interval':'', 'count':2},
    ]
    counter = 0
    for rule in rules:
      name = rule['name']
      count = int(rule['count']) if rule['count'] else None
      interval = int(rule['interval']) if rule['interval'] else None

      # Check if the rule already exists with the name: if not, create it
      grule = FeedGroupingRule.all().filter('title =', name)
      forms.refresh_forms()
      #print grule.count()

      if not grule.count():
        logging.debug('no rule')
        fgr = FeedGroupingRule(title=name, interval=interval, count=count)
        fgr.put()
        counter += 1
    
    reload(forms)

    flash('Installation completed (%d rules created)' % counter)
    return redirect(url_for('main'))

  # Show the installation confirmation
  setup_form = forms.SetupForm(request.form)

  return render_template('install.html', form=setup_form)

@admin_required
def uninstall():
  """
  Delete all the content

  .. NOTE:: Testing purposes only
  """
  FeedGroupingRule.delete_all()
  Subscriber.delete_all()

  flash('Uninstallation completed')

  return redirect(url_for('main'))

@admin_required
def admin():
  """
  Redirects to appengine admin page
  """
  if app.config['DEBUG_MODE']:
    return redirect('/_ah/admin')
  return redirect('https://appengine.google.com/dashboard?app_id=digrsser')


@do_logout
def logout():
  return redirect('/')


@login_required
def show_feed():
  baker = Digrsser()
  feeds = baker.subscribe('http://www.osnews.com/files/recent.xml')
  return str(feeds)


@login_required
def list_subs():
  """
  List the subscriptions
  """
  # If the request path is /sub, the redirect to /sub/list
  if request.path.split('/')[-1] == 'sub':
    return redirect(url_for('list_subs'))
  return render_template('list_subs.html')


@login_required
def edit_sub(id):
  """
  Show details of the selected subscription
  and allows to modify it
  """
  usb = Subscriber.get_current()
  sub = Subscription.get_by_userid(id)

  if not sub:
    flash('Subscription %d cannot be found ' % id)
    return redirect(url_for('subscriptions'))

  form = forms.SubscriptionForm(request.form)

  # Handle form submit
  if request.method == 'POST':

    # Cancel the action
    if form.cancel.data:
      flash('Cancelled by the user')
      return redirect(url_for('list_subs'))

    sub.title = form.title.data
    sub.urls = [form.url.data]
    sub.grouping_rule = FeedGroupingRule.get_by_id(long(form.grouping_rule.data))
    sub.put()

    flash('Updated subscription')
    return redirect(url_for('edit_sub', id=id))

  # Fill form values
  form.id.data = sub.key().id()
  form.title.data = sub.title
  form.url.data = sub.urls[0]
  form.grouping_rule.data = sub.grouping_rule.key().id()

  return render_template('edit_sub.html', form=form, sub=sub)


@login_required
def add_sub():
  """
  Creates new subscription with following POST

  Method:
    GET, POST
  Params:

    url:
      URL to fetch
    gid:
      Grouping rule id
  """
  form = forms.SubscriptionForm(request.form)

  # If the view was requested, show the page with form
  # otherwise handle the post
  if request.method == 'GET':
    return make_response(render_template('add_sub.html', form=form))

  # Cancel the action
  if form.cancel.data:
    flash('Cancelled by the user')
    return redirect(url_for('list_subs'))

  # Run validation does not pass, return back (form contains the errors)
  if not form.validate():
    return make_response(render_template('add_sub.html', form=form))

  subscriber = Subscriber.get_current()
  try:
    urls = [form.url.data]
    rule = FeedGroupingRule.get_by_id(long(form.grouping_rule.data))

    logging.info('Rule: %s Urls: %s' % (rule, urls))

    sub = Subscription(subscriber=subscriber, urls=urls, grouping_rule=rule)
    sub.put()
    flash('URL subscribed')

    # Try retrieving the data right away
    dig = Digrsser(sub)
    count = len(dig.fetch(10))

    # Try to get the name for the subscription
    if not sub.title:
      sub.title = dig.get_title() or sub.urls[0]
      sub.put()

    if count:
      flash('Retrieved %d entries' % count)
    else:
      flash('No entries found - please check the URL', 'warning')

  except Exception, ex:
    logging.error('Failed to add URL: %s' % ex)
    flash('Failed to add URL', 'error')

  return redirect(url_for('list_subs'))



@login_required
def show_sub_feed(id):
  """Documentation"""
  sub = Subscription.get_by_userid(id)
  entries = FeedEntry.all().filter('subscription = ', sub)

  return render_template('feed.html', entries=entries)


def show_sub_rss(key):
  """
  Show the feed contents of subscription (based on given Subscription key)
  in XML (RSS) format

  .. NOTE::

     This URL does not require any authentication (easier for the RSS readers)
     and therefore the path is constructed using Subscription key - making it
     somewhat harder to guess

  """
  # Load subscription directly using the given key
  sub = Subscription.get(key)

  if not sub:
    return flask.abort(404)

  groups = sub.get_closed_groups()

  # Provide the escape function to view
  escape = jinja2.escape

  response = make_response(render_template('base.xml', subscription=sub, groups=groups, escape=escape))
  response.headers['Content-Type'] = 'application/rss+xml'

  return response


@login_required
def update_sub(id):
  """
  Fetches the possible feed updates for the subscription and updates
  the entries to database
  """
  sub = Subscription.get_by_userid(id)
  sgt = Digrsser(sub)
  new_entries = sgt.fetch()

  if not new_entries:
    flash('No updates')
  else:
    flash('Updated %d entries' % len(new_entries))

  return redirect(url_for('list_subs'))


@login_required
def del_sub(id):
  """
  Remove the subscription pointed by id
  """
  sub = Subscription.get_by_userid(id)
  if sub:
    urls = sub.urls
    sub.delete()
    flash('Removed %s' % ', '.join(urls))
  else:
    flash('Failed to remove %s' % id, 'warning')

  return redirect(url_for('list_subs'))


@login_required
def show_messages():
  """
  Makes an ajax-compatible responses
  from requested paths
  """
  msghtml = render_template('message.html')
  return msghtml


@login_required
def ajax_sub_entries(id):
  """
  Returns HTML list of feed entries
  from the selected subscription
  """
  sub = Subscription.get_by_userid(id)

  #entries = FeedEntry.all().filter('subscription = ', sub)
  groups = sub.get_groups()
  logging.info(groups.count())

  return render_template('entries.html', groups=groups)


@app.context_processor
def inject_usersubs():
  msgs = flask.get_flashed_messages()
  user = users.get_current_user()

  # Form for subscribing
  form = forms.SubscriptionForm(request.form)

  logging.info('form: %s' % form)

  # Get list of subscriptions
  subscriber = Subscriber.get_current()
  subs = list(Subscription.all().filter('subscriber = ', subscriber))
  rules = FeedGroupingRule.all()

  is_admin = users.is_current_user_admin()

  return dict(
    form=form,
    user=user,
    subscriber=subscriber,
    admin=is_admin,
    messages=msgs,
    subs=subs,
    sub=None,
    rules=rules
  )


def get_hashkey(key):
    """
    Returns the salted key, with length of 40 characters
    """
    import base64
    import hashlib
    
    return base64.b64encode(hashlib.sha1.new(SESSION_KEY + key))