#! /usr/bin/env python
# -*- coding: utf-8 -*-
# pyAggr3g470r - A Web based news aggregator.
# Copyright (C) 2010-2014 Cédric Bonhomme - http://cedricbonhomme.org/
#
# For more information : https://bitbucket.org/cedricbonhomme/pyaggr3g470r/
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
__author__ = "Cedric Bonhomme"
__version__ = "$Revision: 0.1 $"
__date__ = "$Date: 2014/06/18 $"
__revision__ = "$Date: 2014/06/18 $"
__copyright__ = "Copyright (c) Cedric Bonhomme"
__license__ = "AGPLv3"
import dateutil
from functools import wraps
from flask import g, Response, request, session, jsonify
from flask.ext.restful import Resource, reqparse
from pyaggr3g470r import api, db
from pyaggr3g470r.models import User, Article, Feed
def authenticate(func):
"""
Decorator for the authentication to the web services.
"""
@wraps(func)
def wrapper(*args, **kwargs):
if not getattr(func, 'authenticated', True):
return func(*args, **kwargs)
# authentication based on the session (already logged on the site)
if 'email' in session or g.user.is_authenticated():
return func(*args, **kwargs)
# authentication via HTTP only
auth = request.authorization
try:
email = auth.username
user = User.query.filter(User.email == email).first()
if user and user.check_password(auth.password) and user.activation_key == "":
g.user = user
return func(*args, **kwargs)
except AttributeError:
pass
return Response('', 401,
{'WWWAuthenticate':'Basic realm="Login Required"'})
return wrapper
class ArticleListAPI(Resource):
"""
Defines a RESTful API for Article elements.
"""
method_decorators = [authenticate]
def __init__(self):
self.reqparse = reqparse.RequestParser()
self.reqparse.add_argument('title', type = unicode, location = 'json')
self.reqparse.add_argument('content', type = unicode, location = 'json')
self.reqparse.add_argument('link', type = unicode, location = 'json')
self.reqparse.add_argument('date', type = str, location = 'json')
self.reqparse.add_argument('feed_id', type = int, location = 'json')
super(ArticleListAPI, self).__init__()
def get(self):
"""
Returns a list of articles.
"""
feeds = {feed.id: feed.title for feed in g.user.feeds if feed.enabled}
articles = Article.query.filter(Article.feed_id.in_(feeds.keys()),
Article.user_id == g.user.id)
filter_ = request.args.get('filter_', 'unread')
feed_id = int(request.args.get('feed', 0))
limit = request.args.get('limit', 1000)
if filter_ != 'all':
articles = articles.filter(Article.readed == (filter_ == 'read'))
if feed_id:
articles = articles.filter(Article.feed_id == feed_id)
articles = articles.order_by(Article.date.desc())
if limit != 'all':
limit = int(limit)
articles = articles.limit(limit)
return jsonify(result= [{
"id": article.id,
"title": article.title,
"link": article.link,
"content": article.content,
"readed": article.readed,
"like": article.like,
"date": article.date,
"retrieved_date": article.retrieved_date,
"feed_id": article.source.id,
"feed_name": article.source.title
}
for article in articles]
)
def post(self):
"""
POST method - Create a new article.
"""
args = self.reqparse.parse_args()
article_dict = {}
for k, v in args.iteritems():
if v != None:
article_dict[k] = v
else:
return {"message":"missing argument: %s" % (k,)}
article_date = None
try:
article_date = dateutil.parser.parse(article_dict["date"], dayfirst=True)
return {"message":"bad format for the date"}
except:
try: # trying to clean date field from letters
article_date = dateutil.parser.parse(re.sub('[A-z]', '', article_dict["date"], dayfirst=True))
return {"message":"bad format for the date"}
except:
pass
article = Article(link=article_dict["link"], title=article_dict["title"],
content=article_dict["content"], readed=False, like=False,
date=article_date, user_id=g.user.id,
feed_id=article_dict["feed_id"])
feed = Feed.query.filter(Feed.id == article_dict["feed_id"], Feed.user_id == g.user.id).first()
feed.articles.append(article)
db.session.commit()
return {"message":"ok"}
def put(self):
"""
PUT method - no sense here.
"""
response = jsonify({'code': 501, 'message': 'PUT method not implemented for Article objects.'})
response.status_code = 501
return response
def delete(self):
"""
DELETE method - no sense here.
"""
response = jsonify({'code': 501, 'message': 'DELETE method not implemented for Article objects.'})
response.status_code = 501
return response
class ArticleAPI(Resource):
"""
Defines a RESTful API for Article elements.
"""
method_decorators = [authenticate]
def __init__(self):
super(ArticleAPI, self).__init__()
def get(self, id=None):
"""
Returns an article.
"""
result = []
if id is not None:
article = Article.query.filter(Article.user_id == g.user.id, Article.id == id).first()
if article is not None:
if not article.readed:
article.readed = True
db.session.commit()
result.append(article)
return jsonify(result= [{
"id": article.id,
"title": article.title,
"link": article.link,
"content": article.content,
"readed": article.readed,
"like": article.like,
"date": article.date,
"retrieved_date": article.retrieved_date,
"feed_id": article.source.id,
"feed_name": article.source.title
}
for article in result]
)
def put(self, id):
"""
PUT method - no sense here.
"""
response = jsonify({'code': 501, 'message': 'PUT method not implemented for Article objects.'})
response.status_code = 501
return response
def delete(self, id):
"""
DELETE method - no sense here.
"""
response = jsonify({'code': 501, 'message': 'DELETE method not implemented for Article objects.'})
response.status_code = 501
return response
api.add_resource(ArticleListAPI, '/api/v1.0/articles', endpoint = 'articles.json')
api.add_resource(ArticleAPI, '/api/v1.0/articles/', endpoint = 'article.json')