aboutsummaryrefslogtreecommitdiff
path: root/src/web/views
diff options
context:
space:
mode:
authorCédric Bonhomme <cedric@cedricbonhomme.org>2020-02-26 11:27:31 +0100
committerCédric Bonhomme <cedric@cedricbonhomme.org>2020-02-26 11:27:31 +0100
commit62b3afeeedfe054345f86093e2d243e956c1e3c9 (patch)
treebbd58f5c8c07f5d87b1c1cca73fa1d5af6178f48 /src/web/views
parentUpdated Python dependencies. (diff)
downloadnewspipe-62b3afeeedfe054345f86093e2d243e956c1e3c9.tar.gz
newspipe-62b3afeeedfe054345f86093e2d243e956c1e3c9.tar.bz2
newspipe-62b3afeeedfe054345f86093e2d243e956c1e3c9.zip
The project is now using Poetry.
Diffstat (limited to 'src/web/views')
-rw-r--r--src/web/views/__init__.py23
-rw-r--r--src/web/views/admin.py119
-rw-r--r--src/web/views/api/__init__.py0
-rw-r--r--src/web/views/api/v2/__init__.py3
-rw-r--r--src/web/views/api/v2/article.py53
-rw-r--r--src/web/views/api/v2/category.py27
-rw-r--r--src/web/views/api/v2/common.py222
-rw-r--r--src/web/views/api/v2/feed.py47
-rw-r--r--src/web/views/api/v3/__init__.py3
-rw-r--r--src/web/views/api/v3/article.py84
-rw-r--r--src/web/views/api/v3/common.py109
-rw-r--r--src/web/views/api/v3/feed.py58
-rw-r--r--src/web/views/article.py154
-rw-r--r--src/web/views/bookmark.py256
-rw-r--r--src/web/views/category.py86
-rw-r--r--src/web/views/common.py53
-rw-r--r--src/web/views/feed.py306
-rw-r--r--src/web/views/home.py172
-rw-r--r--src/web/views/icon.py15
-rw-r--r--src/web/views/session_mgmt.py113
-rw-r--r--src/web/views/user.py203
-rw-r--r--src/web/views/views.py95
22 files changed, 0 insertions, 2201 deletions
diff --git a/src/web/views/__init__.py b/src/web/views/__init__.py
deleted file mode 100644
index 41bb52f3..00000000
--- a/src/web/views/__init__.py
+++ /dev/null
@@ -1,23 +0,0 @@
-from web.views.api import v2, v3
-from web.views import views, home, session_mgmt
-from web.views.article import article_bp, articles_bp
-from web.views.feed import feed_bp, feeds_bp
-from web.views.category import category_bp, categories_bp
-from web.views.icon import icon_bp
-from web.views.admin import admin_bp
-from web.views.user import user_bp, users_bp
-from web.views.bookmark import bookmark_bp, bookmarks_bp
-
-__all__ = ['views', 'home', 'session_mgmt', 'v2', 'v3',
- 'article_bp', 'articles_bp', 'feed_bp', 'feeds_bp',
- 'category_bp', 'categories_bp', 'icon_bp',
- 'admin_bp', 'user_bp', 'users_bp', 'bookmark_bp', 'bookmarks_bp']
-
-import conf
-from flask import request
-from flask import g
-
-
-@g.babel.localeselector
-def get_locale():
- return request.accept_languages.best_match(conf.LANGUAGES.keys())
diff --git a/src/web/views/admin.py b/src/web/views/admin.py
deleted file mode 100644
index 73b2b668..00000000
--- a/src/web/views/admin.py
+++ /dev/null
@@ -1,119 +0,0 @@
-from datetime import datetime
-from flask import (Blueprint, render_template, redirect, flash, url_for)
-from flask_babel import gettext, format_timedelta
-from flask_login import login_required, current_user
-
-from lib.utils import redirect_url
-from web.views.common import admin_permission
-from web.controllers import UserController
-from web.forms import InformationMessageForm, UserForm
-
-admin_bp = Blueprint('admin', __name__, url_prefix='/admin')
-
-
-@admin_bp.route('/dashboard', methods=['GET', 'POST'])
-@login_required
-@admin_permission.require(http_exception=403)
-def dashboard():
- last_cons, now = {}, datetime.utcnow()
- users = list(UserController().read().order_by('id'))
- form = InformationMessageForm()
- for user in users:
- last_cons[user.id] = format_timedelta(now - user.last_seen)
- return render_template('admin/dashboard.html', now=datetime.utcnow(),
- last_cons=last_cons, users=users, current_user=current_user,
- form=form)
-
-
-@admin_bp.route('/user/create', methods=['GET'])
-@admin_bp.route('/user/edit/<int:user_id>', methods=['GET'])
-@login_required
-@admin_permission.require(http_exception=403)
-def user_form(user_id=None):
- if user_id is not None:
- user = UserController().get(id=user_id)
- form = UserForm(obj=user)
- message = gettext('Edit the user <i>%(nick)s</i>', nick=user.nickname)
- else:
- form = UserForm()
- message = gettext('Add a new user')
- return render_template('/admin/create_user.html',
- form=form, message=message)
-
-
-@admin_bp.route('/user/create', methods=['POST'])
-@admin_bp.route('/user/edit/<int:user_id>', methods=['POST'])
-@login_required
-@admin_permission.require(http_exception=403)
-def process_user_form(user_id=None):
- """
- Create or edit a user.
- """
- form = UserForm()
- user_contr = UserController()
-
- if not form.validate():
- return render_template('/admin/create_user.html', form=form,
- message=gettext('Some errors were found'))
-
- if user_id is not None:
- # Edit a user
- user_contr.update({'id': user_id},
- {'nickname': form.nickname.data,
- 'password': form.password.data,
- 'automatic_crawling': form.automatic_crawling.data})
- user = user_contr.get(id=user_id)
- flash(gettext('User %(nick)s successfully updated',
- nick=user.nickname), 'success')
- else:
- # Create a new user (by the admin)
- user = user_contr.create(nickname=form.nickname.data,
- password=form.password.data,
- automatic_crawling=form.automatic_crawling.data,
- is_admin=False,
- is_active=True)
- flash(gettext('User %(nick)s successfully created',
- nick=user.nickname), 'success')
- return redirect(url_for('admin.user_form', user_id=user.id))
-
-
-@admin_bp.route('/delete_user/<int:user_id>', methods=['GET'])
-@login_required
-@admin_permission.require(http_exception=403)
-def delete_user(user_id=None):
- """
- Delete a user (with all its data).
- """
- try:
- user = UserController().delete(user_id)
- flash(gettext('User %(nick)s successfully deleted',
- nick=user.nickname), 'success')
- except Exception as error:
- flash(
- gettext('An error occurred while trying to delete a user: %(error)s',
- error=error), 'danger')
- return redirect(url_for('admin.dashboard'))
-
-
-@admin_bp.route('/toggle_user/<int:user_id>', methods=['GET'])
-@login_required
-@admin_permission.require()
-def toggle_user(user_id=None):
- """
- Enable or disable the account of a user.
- """
- ucontr = UserController()
- user = ucontr.get(id=user_id)
- user_changed = ucontr.update({'id': user_id},
- {'is_active': not user.is_active})
-
- if not user_changed:
- flash(gettext('This user does not exist.'), 'danger')
- return redirect(url_for('admin.dashboard'))
-
- else:
- act_txt = 'activated' if user.is_active else 'desactivated'
- message = gettext('User %(nickname)s successfully %(is_active)s',
- nickname=user.nickname, is_active=act_txt)
- flash(message, 'success')
- return redirect(url_for('admin.dashboard'))
diff --git a/src/web/views/api/__init__.py b/src/web/views/api/__init__.py
deleted file mode 100644
index e69de29b..00000000
--- a/src/web/views/api/__init__.py
+++ /dev/null
diff --git a/src/web/views/api/v2/__init__.py b/src/web/views/api/v2/__init__.py
deleted file mode 100644
index 46760261..00000000
--- a/src/web/views/api/v2/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-from web.views.api.v2 import article, feed, category
-
-__all__ = ['article', 'feed', 'category']
diff --git a/src/web/views/api/v2/article.py b/src/web/views/api/v2/article.py
deleted file mode 100644
index 2be286c6..00000000
--- a/src/web/views/api/v2/article.py
+++ /dev/null
@@ -1,53 +0,0 @@
-from conf import API_ROOT
-import dateutil.parser
-from datetime import datetime
-from flask import current_app
-from flask_restful import Api
-
-from web.views.common import api_permission
-from web.controllers import ArticleController
-from web.views.api.v2.common import (PyAggAbstractResource,
- PyAggResourceNew, PyAggResourceExisting, PyAggResourceMulti)
-
-
-class ArticleNewAPI(PyAggResourceNew):
- controller_cls = ArticleController
-
-
-class ArticleAPI(PyAggResourceExisting):
- controller_cls = ArticleController
-
-
-class ArticlesAPI(PyAggResourceMulti):
- controller_cls = ArticleController
-
-
-class ArticlesChallenge(PyAggAbstractResource):
- controller_cls = ArticleController
- attrs = {'ids': {'type': list, 'default': []}}
-
- @api_permission.require(http_exception=403)
- def get(self):
- parsed_args = self.reqparse_args(right='read')
- # collecting all attrs for casting purpose
- attrs = self.controller_cls._get_attrs_desc('admin')
- for id_dict in parsed_args['ids']:
- keys_to_ignore = []
- for key in id_dict:
- if key not in attrs:
- keys_to_ignore.append(key)
- if issubclass(attrs[key]['type'], datetime):
- id_dict[key] = dateutil.parser.parse(id_dict[key])
- for key in keys_to_ignore:
- del id_dict[key]
-
- result = list(self.controller.challenge(parsed_args['ids']))
- return result or None, 200 if result else 204
-
-api = Api(current_app, prefix=API_ROOT)
-
-api.add_resource(ArticleNewAPI, '/article', endpoint='article_new.json')
-api.add_resource(ArticleAPI, '/article/<int:obj_id>', endpoint='article.json')
-api.add_resource(ArticlesAPI, '/articles', endpoint='articles.json')
-api.add_resource(ArticlesChallenge, '/articles/challenge',
- endpoint='articles_challenge.json')
diff --git a/src/web/views/api/v2/category.py b/src/web/views/api/v2/category.py
deleted file mode 100644
index 70fda1ea..00000000
--- a/src/web/views/api/v2/category.py
+++ /dev/null
@@ -1,27 +0,0 @@
-from conf import API_ROOT
-from flask import current_app
-from flask_restful import Api
-
-from web.controllers.category import CategoryController
-from web.views.api.v2.common import (PyAggResourceNew,
- PyAggResourceExisting,
- PyAggResourceMulti)
-
-
-class CategoryNewAPI(PyAggResourceNew):
- controller_cls = CategoryController
-
-
-class CategoryAPI(PyAggResourceExisting):
- controller_cls = CategoryController
-
-
-class CategoriesAPI(PyAggResourceMulti):
- controller_cls = CategoryController
-
-
-api = Api(current_app, prefix=API_ROOT)
-api.add_resource(CategoryNewAPI, '/category', endpoint='category_new.json')
-api.add_resource(CategoryAPI, '/category/<int:obj_id>',
- endpoint='category.json')
-api.add_resource(CategoriesAPI, '/categories', endpoint='categories.json')
diff --git a/src/web/views/api/v2/common.py b/src/web/views/api/v2/common.py
deleted file mode 100644
index 8a53d7e6..00000000
--- a/src/web/views/api/v2/common.py
+++ /dev/null
@@ -1,222 +0,0 @@
-"""For a given resources, classes in the module intend to create the following
-routes :
- GET resource/<id>
- -> to retrieve one
- POST resource
- -> to create one
- PUT resource/<id>
- -> to update one
- DELETE resource/<id>
- -> to delete one
-
- GET resources
- -> to retrieve several
- POST resources
- -> to create several
- PUT resources
- -> to update several
- DELETE resources
- -> to delete several
-"""
-import ast
-import logging
-from functools import wraps
-from werkzeug.exceptions import Unauthorized, BadRequest, Forbidden, NotFound
-from flask import request
-from flask_restful import Resource, reqparse
-from flask_login import current_user
-
-from web.views.common import admin_permission, api_permission, \
- login_user_bundle, jsonify
-from web.controllers import UserController
-
-logger = logging.getLogger(__name__)
-
-
-def authenticate(func):
- @wraps(func)
- def wrapper(*args, **kwargs):
- if request.authorization:
- ucontr = UserController()
- try:
- user = ucontr.get(nickname=request.authorization.username)
- except NotFound:
- raise Forbidden("Couldn't authenticate your user")
- if not ucontr.check_password(user, request.authorization.password):
- raise Forbidden("Couldn't authenticate your user")
- if not user.is_active:
- raise Forbidden("User is deactivated")
- login_user_bundle(user)
- if current_user.is_authenticated:
- return func(*args, **kwargs)
- raise Unauthorized()
- return wrapper
-
-
-class PyAggAbstractResource(Resource):
- method_decorators = [authenticate, jsonify]
- controller_cls = None
- attrs = None
-
- @property
- def controller(self):
- if admin_permission.can():
- return self.controller_cls()
- return self.controller_cls(current_user.id)
-
- def reqparse_args(self, right, req=None, strict=False, default=True,
- allow_empty=False):
- """
- strict: bool
- if True will throw 400 error if args are defined and not in request
- default: bool
- if True, won't return defaults
- args: dict
- the args to parse, if None, self.attrs will be used
- """
- try:
- if req:
- in_values = req.json
- else:
- in_values = request.args or request.json or {}
- if not in_values and allow_empty:
- return {}
- except BadRequest:
- if allow_empty:
- return {}
- raise
- parser = reqparse.RequestParser()
- if self.attrs is not None:
- attrs = self.attrs
- elif admin_permission.can():
- attrs = self.controller_cls._get_attrs_desc('admin')
- elif api_permission.can():
- attrs = self.controller_cls._get_attrs_desc('api', right)
- else:
- attrs = self.controller_cls._get_attrs_desc('base', right)
- assert attrs, "No defined attrs for %s" % self.__class__.__name__
-
- for attr_name, attr in attrs.items():
- if not default and attr_name not in in_values:
- continue
- else:
- parser.add_argument(attr_name, location='json',
- default=in_values[attr_name])
- return parser.parse_args(req=request.args, strict=strict)
-
-
-class PyAggResourceNew(PyAggAbstractResource):
-
- @api_permission.require(http_exception=403)
- def post(self):
- """Create a single new object"""
- return self.controller.create(**self.reqparse_args(right='write')), 201
-
-
-class PyAggResourceExisting(PyAggAbstractResource):
-
- def get(self, obj_id=None):
- """Retrieve a single object"""
- return self.controller.get(id=obj_id)
-
- def put(self, obj_id=None):
- """update an object, new attrs should be passed in the payload"""
- args = self.reqparse_args(right='write', default=False)
- if not args:
- raise BadRequest()
- return self.controller.update({'id': obj_id}, args), 200
-
- def delete(self, obj_id=None):
- """delete a object"""
- self.controller.delete(obj_id)
- return None, 204
-
-
-class PyAggResourceMulti(PyAggAbstractResource):
-
- def get(self):
- """retrieve several objects. filters can be set in the payload on the
- different fields of the object, and a limit can be set in there as well
- """
- args = {}
- try:
- limit = request.json.pop('limit', 10)
- order_by = request.json.pop('order_by', None)
- except Exception:
- args = self.reqparse_args(right='read', default=False)
- limit = request.args.get('limit', 10)
- order_by = request.args.get('order_by', None)
- query = self.controller.read(**args)
- if order_by:
- query = query.order_by(order_by)
- if limit:
- query = query.limit(limit)
- return [res for res in query]
-
- @api_permission.require(http_exception=403)
- def post(self):
- """creating several objects. payload should be:
- >>> payload
- [{attr1: val1, attr2: val2}, {attr1: val1, attr2: val2}]
- """
- status, fail_count, results = 200, 0, []
-
- class Proxy:
- pass
- for attrs in request.json:
- try:
- Proxy.json = attrs
- args = self.reqparse_args('write', req=Proxy, default=False)
- obj = self.controller.create(**args)
- results.append(obj)
- except Exception as error:
- fail_count += 1
- results.append(str(error))
- if fail_count == len(results): # all failed => 500
- status = 500
- elif fail_count: # some failed => 206
- status = 206
- return results, status
-
- def put(self):
- """updating several objects. payload should be:
- >>> payload
- [[obj_id1, {attr1: val1, attr2: val2}]
- [obj_id2, {attr1: val1, attr2: val2}]]
- """
- status, results = 200, []
-
- class Proxy:
- pass
- for obj_id, attrs in request.json:
- try:
- Proxy.json = attrs
- args = self.reqparse_args('write', req=Proxy, default=False)
- result = self.controller.update({'id': obj_id}, args)
- if result:
- results.append('ok')
- else:
- results.append('nok')
- except Exception as error:
- results.append(str(error))
- if results.count('ok') == 0: # all failed => 500
- status = 500
- elif results.count('ok') != len(results): # some failed => 206
- status = 206
- return results, status
-
- def delete(self):
- """will delete several objects,
- a list of their ids should be in the payload"""
- status, results = 204, []
- for obj_id in request.json:
- try:
- self.controller.delete(obj_id)
- results.append('ok')
- except Exception as error:
- status = 206
- results.append(error)
- # if no operation succeeded, it's not partial anymore, returning err 500
- if status == 206 and results.count('ok') == 0:
- status = 500
- return results, status
diff --git a/src/web/views/api/v2/feed.py b/src/web/views/api/v2/feed.py
deleted file mode 100644
index a0691277..00000000
--- a/src/web/views/api/v2/feed.py
+++ /dev/null
@@ -1,47 +0,0 @@
-from conf import API_ROOT
-from flask import current_app
-from flask_restful import Api
-
-from web.views.common import api_permission
-from web.controllers.feed import (FeedController,
- DEFAULT_MAX_ERROR,
- DEFAULT_LIMIT)
-
-from web.views.api.v2.common import PyAggAbstractResource, \
- PyAggResourceNew, \
- PyAggResourceExisting, \
- PyAggResourceMulti
-
-
-class FeedNewAPI(PyAggResourceNew):
- controller_cls = FeedController
-
-
-class FeedAPI(PyAggResourceExisting):
- controller_cls = FeedController
-
-
-class FeedsAPI(PyAggResourceMulti):
- controller_cls = FeedController
-
-
-class FetchableFeedAPI(PyAggAbstractResource):
- controller_cls = FeedController
- attrs = {'max_error': {'type': int, 'default': DEFAULT_MAX_ERROR},
- 'limit': {'type': int, 'default': DEFAULT_LIMIT}}
-
- @api_permission.require(http_exception=403)
- def get(self):
- args = self.reqparse_args(right='read', allow_empty=True)
- result = [feed for feed
- in self.controller.list_fetchable(**args)]
- return result or None, 200 if result else 204
-
-
-api = Api(current_app, prefix=API_ROOT)
-
-api.add_resource(FeedNewAPI, '/feed', endpoint='feed_new.json')
-api.add_resource(FeedAPI, '/feed/<int:obj_id>', endpoint='feed.json')
-api.add_resource(FeedsAPI, '/feeds', endpoint='feeds.json')
-api.add_resource(FetchableFeedAPI, '/feeds/fetchable',
- endpoint='fetchable_feed.json')
diff --git a/src/web/views/api/v3/__init__.py b/src/web/views/api/v3/__init__.py
deleted file mode 100644
index 76aa1f19..00000000
--- a/src/web/views/api/v3/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-from web.views.api.v3 import article
-
-__all__ = ['article']
diff --git a/src/web/views/api/v3/article.py b/src/web/views/api/v3/article.py
deleted file mode 100644
index 4cf35648..00000000
--- a/src/web/views/api/v3/article.py
+++ /dev/null
@@ -1,84 +0,0 @@
-#! /usr/bin/env python
-# -*- coding: utf-8 -*-
-
-# Newspipe - A Web based news aggregator.
-# Copyright (C) 2010-2018 Cédric Bonhomme - https://www.cedricbonhomme.org
-#
-# For more information : http://gitlab.com/newspipe/newspipe
-#
-# 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 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 General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>
-
-__author__ = "Cedric Bonhomme"
-__version__ = "$Revision: 0.1 $"
-__date__ = "$Date: 2016/04/29 $"
-__revision__ = "$Date: 2016/04/29 $"
-__copyright__ = "Copyright (c) Cedric Bonhomme"
-__license__ = "GPLv3"
-
-from flask_login import current_user
-from werkzeug.exceptions import NotFound
-from flask_restless import ProcessingException
-from web import models
-from bootstrap import application, manager
-from web.controllers import ArticleController, FeedController
-from web.views.api.v3.common import AbstractProcessor
-from web.views.api.v3.common import url_prefix, auth_func
-
-class ArticleProcessor(AbstractProcessor):
- """Concrete processors for the Article Web service.
- """
-
- def get_single_preprocessor(self, instance_id=None, **kw):
- try:
- article = ArticleController(current_user.id).get(id=instance_id)
- except NotFound:
- raise ProcessingException(description='No such article.', code=404)
- self.is_authorized(current_user, article)
-
- def post_preprocessor(self, data=None, **kw):
- data["user_id"] = current_user.id
-
- try:
- feed = FeedController(current_user.id).get(id=data["feed_id"])
- except NotFound:
- raise ProcessingException(description='No such feed.', code=404)
- self.is_authorized(current_user, feed)
-
- data["category_id"] = feed.category_id
-
- def delete_preprocessor(self, instance_id=None, **kw):
- try:
- article = ArticleController(current_user.id).get(id=instance_id)
- except NotFound:
- raise ProcessingException(description='No such article.', code=404)
- self.is_authorized(current_user, article)
-
-article_processor = ArticleProcessor()
-
-blueprint_article = manager.create_api_blueprint(models.Article,
- url_prefix=url_prefix,
- methods=['GET', 'POST', 'PUT', 'DELETE'],
- preprocessors=dict(GET_SINGLE=[auth_func,
- article_processor.get_single_preprocessor],
- GET_MANY=[auth_func,
- article_processor.get_many_preprocessor],
- POST=[auth_func,
- article_processor.post_preprocessor],
- PUT_SINGLE=[auth_func,
- article_processor.put_single_preprocessor],
- PUT_MANY=[auth_func,
- article_processor.put_many_preprocessor],
- DELETE=[auth_func,
- article_processor.delete_preprocessor]))
-application.register_blueprint(blueprint_article)
diff --git a/src/web/views/api/v3/common.py b/src/web/views/api/v3/common.py
deleted file mode 100644
index d5e94a3f..00000000
--- a/src/web/views/api/v3/common.py
+++ /dev/null
@@ -1,109 +0,0 @@
-#! /usr/bin/env python
-# -*- coding: utf-8 -*-
-
-# Newspipe - A Web based news aggregator.
-# Copyright (C) 2010-2018 Cédric Bonhomme - https://www.cedricbonhomme.org
-#
-# For more information : http://gitlab.com/newspipe/newspipe
-#
-# 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 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 General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>
-
-__author__ = "Cedric Bonhomme"
-__version__ = "$Revision: 0.1 $"
-__date__ = "$Date: 2016/04/29 $"
-__revision__ = "$Date: 2016/04/29 $"
-__copyright__ = "Copyright (c) Cedric Bonhomme"
-__license__ = "GPLv3"
-
-from flask import request
-from flask_login import current_user
-from flask_restless import ProcessingException
-from werkzeug.exceptions import NotFound
-from web.controllers import ArticleController, UserController
-from web.views.common import login_user_bundle
-
-url_prefix = '/api/v3'
-
-def auth_func(*args, **kw):
- if request.authorization:
- ucontr = UserController()
- try:
- user = ucontr.get(nickname=request.authorization.username)
- except NotFound:
- raise ProcessingException("Couldn't authenticate your user",
- code=401)
- if not ucontr.check_password(user, request.authorization.password):
- raise ProcessingException("Couldn't authenticate your user",
- code=401)
- if not user.is_active:
- raise ProcessingException("User is deactivated", code=401)
- login_user_bundle(user)
- if not current_user.is_authenticated:
- raise ProcessingException(description='Not authenticated!', code=401)
-
-class AbstractProcessor():
- """Abstract processors for the Web services.
- """
-
- def is_authorized(self, user, obj):
- if user.id != obj.user_id:
- raise ProcessingException(description='Not Authorized', code=401)
-
- def get_single_preprocessor(self, instance_id=None, **kw):
- # Check if the user is authorized to modify the specified
- # instance of the model.
- pass
-
- def get_many_preprocessor(self, search_params=None, **kw):
- """Accepts a single argument, `search_params`, which is a dictionary
- containing the search parameters for the request.
- """
- filt = dict(name="user_id",
- op="eq",
- val=current_user.id)
-
- # Check if there are any filters there already.
- if "filters" not in search_params:
- search_params["filters"] = []
-
- search_params["filters"].append(filt)
-
- def post_preprocessor(self, data=None, **kw):
- pass
-
- def put_single_preprocessor(instance_id=None, data=None, **kw):
- """Accepts two arguments, `instance_id`, the primary key of the
- instance of the model to patch, and `data`, the dictionary of fields
- to change on the instance.
- """
- pass
-
- def put_many_preprocessor(search_params=None, data=None, **kw):
- """Accepts two arguments: `search_params`, which is a dictionary
- containing the search parameters for the request, and `data`, which
- is a dictionary representing the fields to change on the matching
- instances and the values to which they will be set.
- """
- filt = dict(name="user_id",
- op="eq",
- val=current_user.id)
-
- # Check if there are any filters there already.
- if "filters" not in search_params:
- search_params["filters"] = []
-
- search_params["filters"].append(filt)
-
- def delete_preprocessor(self, instance_id=None, **kw):
- pass
diff --git a/src/web/views/api/v3/feed.py b/src/web/views/api/v3/feed.py
deleted file mode 100644
index 2cbbafd9..00000000
--- a/src/web/views/api/v3/feed.py
+++ /dev/null
@@ -1,58 +0,0 @@
-#! /usr/bin/env python
-# -*- coding: utf-8 -*-
-
-# Newspipe - A Web based news aggregator.
-# Copyright (C) 2010-2018 Cédric Bonhomme - https://www.cedricbonhomme.org
-#
-# For more information : http://gitlab.com/newspipe/newspipe
-#
-# 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 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 General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>
-
-__author__ = "Cedric Bonhomme"
-__version__ = "$Revision: 0.1 $"
-__date__ = "$Date: 2016/04/29 $"
-__revision__ = "$Date: 2016/04/29 $"
-__copyright__ = "Copyright (c) Cedric Bonhomme"
-__license__ = "GPLv3"
-
-from flask_login import current_user
-from web import models
-from bootstrap import application, manager
-from web.controllers import FeedController
-from web.views.api.v3.common import AbstractProcessor
-from web.views.api.v3.common import url_prefix, auth_func
-
-class FeedProcessor(AbstractProcessor):
- """Concrete processors for the Feed Web service.
- """
-
- def get_single_preprocessor(self, instance_id=None, **kw):
- # Check if the user is authorized to modify the specified
- # instance of the model.
- feed = FeedController(current_user.id).get(id=instance_id)
- self.is_authorized(current_user, feed)
-
-feed_processor = FeedProcessor()
-
-blueprint_feed = manager.create_api_blueprint(models.Feed,
- url_prefix=url_prefix,
- methods=['GET', 'POST', 'PUT', 'DELETE'],
- preprocessors=dict(GET_SINGLE=[auth_func,
- feed_processor.get_single_preprocessor],
- GET_MANY=[auth_func,
- feed_processor.get_many_preprocessor],
- PUT_SINGLE=[auth_func],
- POST=[auth_func],
- DELETE=[auth_func]))
-application.register_blueprint(blueprint_feed)
diff --git a/src/web/views/article.py b/src/web/views/article.py
deleted file mode 100644
index bf39795d..00000000
--- a/src/web/views/article.py
+++ /dev/null
@@ -1,154 +0,0 @@
-from datetime import datetime, timedelta
-from flask import (Blueprint, g, render_template, redirect,
- flash, url_for, make_response, request)
-
-from flask_babel import gettext
-from flask_login import login_required, current_user
-
-
-from bootstrap import db
-from lib.utils import clear_string, redirect_url
-from lib.data import export_json
-from web.controllers import (ArticleController, UserController,
- CategoryController)
-from web.lib.view_utils import etag_match
-
-articles_bp = Blueprint('articles', __name__, url_prefix='/articles')
-article_bp = Blueprint('article', __name__, url_prefix='/article')
-
-
-@article_bp.route('/redirect/<int:article_id>', methods=['GET'])
-@login_required
-def redirect_to_article(article_id):
- contr = ArticleController(current_user.id)
- article = contr.get(id=article_id)
- if not article.readed:
- contr.update({'id': article.id}, {'readed': True})
- return redirect(article.link)
-
-
-@article_bp.route('/<int:article_id>', methods=['GET'])
-@login_required
-@etag_match
-def article(article_id=None):
- """
- Presents an article.
- """
- article = ArticleController(current_user.id).get(id=article_id)
- return render_template('article.html',
- head_titles=[clear_string(article.title)],
- article=article)
-
-@article_bp.route('/public/<int:article_id>', methods=['GET'])
-@etag_match
-def article_pub(article_id=None):
- """
- Presents an article of a public feed if the profile of the owner is also
- public.
- """
- article = ArticleController().get(id=article_id)
- if article.source.private or not article.source.user.is_public_profile:
- return render_template('errors/404.html'), 404
- return render_template('article_pub.html',
- head_titles=[clear_string(article.title)],
- article=article)
-
-
-@article_bp.route('/like/<int:article_id>', methods=['GET'])
-@login_required
-def like(article_id=None):
- """
- Mark or unmark an article as favorites.
- """
- art_contr = ArticleController(current_user.id)
- article = art_contr.get(id=article_id)
- art_contr = art_contr.update({'id': article_id},
- {'like': not article.like})
- return redirect(redirect_url())
-
-
-@article_bp.route('/delete/<int:article_id>', methods=['GET'])
-@login_required
-def delete(article_id=None):
- """
- Delete an article from the database.
- """
- article = ArticleController(current_user.id).delete(article_id)
- flash(gettext('Article %(article_title)s deleted',
- article_title=article.title), 'success')
- return redirect(url_for('home'))
-
-
-@articles_bp.route('/history', methods=['GET'])
-@articles_bp.route('/history/<int:year>', methods=['GET'])
-@articles_bp.route('/history/<int:year>/<int:month>', methods=['GET'])
-@login_required
-def history(year=None, month=None):
- cntr, artcles = ArticleController(current_user.id).get_history(year, month)
- return render_template('history.html', articles_counter=cntr,
- articles=artcles, year=year, month=month)
-
-
-@article_bp.route('/mark_as/<string:new_value>', methods=['GET'])
-@article_bp.route('/mark_as/<string:new_value>/article/<int:article_id>',
- methods=['GET'])
-@login_required
-def mark_as(new_value='read', feed_id=None, article_id=None):
- """
- Mark all unreaded articles as read.
- """
- readed = new_value == 'read'
- art_contr = ArticleController(current_user.id)
- filters = {'readed': not readed}
- if feed_id is not None:
- filters['feed_id'] = feed_id
- message = 'Feed marked as %s.'
- elif article_id is not None:
- filters['id'] = article_id
- message = 'Article marked as %s.'
- else:
- message = 'All article marked as %s.'
- art_contr.update(filters, {"readed": readed})
- flash(gettext(message % new_value), 'info')
-
- if readed:
- return redirect(redirect_url())
- return redirect('home')
-
-
-@articles_bp.route('/expire_articles', methods=['GET'])
-@login_required
-def expire():
- """
- Delete articles older than the given number of weeks.
- """
- current_time = datetime.utcnow()
- weeks_ago = current_time - timedelta(int(request.args.get('weeks', 10)))
- art_contr = ArticleController(current_user.id)
-
- query = art_contr.read(__or__={'date__lt': weeks_ago,
- 'retrieved_date__lt': weeks_ago})
- count = query.count()
- query.delete()
- db.session.commit()
- flash(gettext('%(count)d articles deleted', count=count), 'info')
- return redirect(redirect_url())
-
-
-@articles_bp.route('/export', methods=['GET'])
-@login_required
-def export():
- """
- Export articles to JSON.
- """
- user = UserController(current_user.id).get(id=current_user.id)
- try:
- json_result = export_json(user)
- except Exception as e:
- flash(gettext("Error when exporting articles."), 'danger')
- return redirect(redirect_url())
- response = make_response(json_result)
- response.mimetype = 'application/json'
- response.headers["Content-Disposition"] \
- = 'attachment; filename=account.json'
- return response
diff --git a/src/web/views/bookmark.py b/src/web/views/bookmark.py
deleted file mode 100644
index 21d832d2..00000000
--- a/src/web/views/bookmark.py
+++ /dev/null
@@ -1,256 +0,0 @@
-#! /usr/bin/env python
-#-*- coding: utf-8 -*-
-
-# Newspipe - A Web based news aggregator.
-# Copyright (C) 2010-2017 Cédric Bonhomme - https://www.cedricbonhomme.org
-#
-# For more information : https://gitlab.com/newspipe/newspipe
-#
-# 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 <http://www.gnu.org/licenses/>.
-
-__author__ = "Cedric Bonhomme"
-__version__ = "$Revision: 0.1 $"
-__date__ = "$Date: 2017/05/24 $"
-__revision__ = "$Date: 2017/05/24 $"
-__copyright__ = "Copyright (c) Cedric Bonhomme"
-__license__ = "AGPLv3"
-
-import logging
-import datetime
-from werkzeug.exceptions import BadRequest
-
-from flask import Blueprint, render_template, flash, \
- redirect, request, url_for, make_response
-from flask_babel import gettext
-from flask_login import login_required, current_user
-from flask_paginate import Pagination, get_page_args
-from sqlalchemy import desc
-
-import conf
-from lib.utils import redirect_url
-from lib.data import import_pinboard_json, export_bookmarks
-from bootstrap import db
-from web.forms import BookmarkForm
-from web.controllers import BookmarkController, BookmarkTagController
-from web.models import BookmarkTag
-
-logger = logging.getLogger(__name__)
-bookmarks_bp = Blueprint('bookmarks', __name__, url_prefix='/bookmarks')
-bookmark_bp = Blueprint('bookmark', __name__, url_prefix='/bookmark')
-
-
-@bookmarks_bp.route('/', defaults={'per_page': '50'}, methods=['GET'])
-@bookmarks_bp.route('/<string:status>', defaults={'per_page': '50'},
- methods=['GET'])
-def list_(per_page, status='all'):
- "Lists the bookmarks."
- head_titles = [gettext("Bookmarks")]
- user_id = None
- filters = {}
- tag = request.args.get('tag', None)
- if tag:
- filters['tags_proxy__contains'] = tag
- query = request.args.get('query', None)
- if query:
- query_regex = '%' + query + '%'
- filters['__or__'] = {'title__ilike': query_regex,
- 'description__ilike': query_regex}
- if current_user.is_authenticated:
- # query for the bookmarks of the authenticated user
- user_id = current_user.id
- if status == 'public':
- # load public bookmarks only
- filters['shared'] = True
- elif status == 'private':
- # load private bookmarks only
- filters['shared'] = False
- else:
- # no filter: load shared and public bookmarks
- pass
- if status == 'unread':
- filters['to_read'] = True
- else:
- pass
- else:
- # query for the shared bookmarks (of all users)
- head_titles = [gettext("Recent bookmarks")]
- not_created_before = datetime.datetime.today() - \
- datetime.timedelta(days=900)
- filters['time__gt'] = not_created_before # only "recent" bookmarks
- filters['shared'] = True
-
- bookmarks = BookmarkController(user_id) \
- .read(**filters) \
- .order_by(desc('time'))
-
- #tag_contr = BookmarkTagController(user_id)
- #tag_contr.read().join(bookmarks).all()
-
- page, per_page, offset = get_page_args()
- pagination = Pagination(page=page, total=bookmarks.count(),
- css_framework='bootstrap3',
- search=False, record_name='bookmarks',
- per_page=per_page)
-
- return render_template('bookmarks.html',
- head_titles=head_titles,
- bookmarks=bookmarks.offset(offset).limit(per_page),
- pagination=pagination,
- tag=tag,
- query=query)
-
-
-@bookmark_bp.route('/create', methods=['GET'])
-@bookmark_bp.route('/edit/<int:bookmark_id>', methods=['GET'])
-@login_required
-def form(bookmark_id=None):
- "Form to create/edit bookmarks."
- action = gettext("Add a new bookmark")
- head_titles = [action]
- if bookmark_id is None:
- return render_template('edit_bookmark.html', action=action,
- head_titles=head_titles, form=BookmarkForm())
- bookmark = BookmarkController(current_user.id).get(id=bookmark_id)
- action = gettext('Edit bookmark')
- head_titles = [action]
- form = BookmarkForm(obj=bookmark)
- form.tags.data = ", ".join(bookmark.tags_proxy)
- return render_template('edit_bookmark.html', action=action,
- head_titles=head_titles, bookmark=bookmark,
- form=form)
-
-
-@bookmark_bp.route('/create', methods=['POST'])
-@bookmark_bp.route('/edit/<int:bookmark_id>', methods=['POST'])
-@login_required
-def process_form(bookmark_id=None):
- "Process the creation/edition of bookmarks."
- form = BookmarkForm()
- bookmark_contr = BookmarkController(current_user.id)
- tag_contr = BookmarkTagController(current_user.id)
-
- if not form.validate():
- return render_template('edit_bookmark.html', form=form)
-
- if form.title.data == '':
- title = form.href.data
- else:
- title = form.title.data
-
- bookmark_attr = {'href': form.href.data,
- 'description': form.description.data,
- 'title': title,
- 'shared': form.shared.data,
- 'to_read': form.to_read.data}
-
- if bookmark_id is not None:
- tags = []
- for tag in form.tags.data.split(','):
- new_tag = tag_contr.create(text=tag.strip(), user_id=current_user.id,
- bookmark_id=bookmark_id)
- tags.append(new_tag)
- bookmark_attr['tags'] = tags
- bookmark_contr.update({'id': bookmark_id}, bookmark_attr)
- flash(gettext('Bookmark successfully updated.'), 'success')
- return redirect(url_for('bookmark.form', bookmark_id=bookmark_id))
-
- # Create a new bookmark
- new_bookmark = bookmark_contr.create(**bookmark_attr)
- tags = []
- for tag in form.tags.data.split(','):
- new_tag = tag_contr.create(text=tag.strip(), user_id=current_user.id,
- bookmark_id=new_bookmark.id)
- tags.append(new_tag)
- bookmark_attr['tags'] = tags
- bookmark_contr.update({'id': new_bookmark.id}, bookmark_attr)
- flash(gettext('Bookmark successfully created.'), 'success')
- return redirect(url_for('bookmark.form', bookmark_id=new_bookmark.id))
-
-
-@bookmark_bp.route('/delete/<int:bookmark_id>', methods=['GET'])
-@login_required
-def delete(bookmark_id=None):
- "Delete a bookmark."
- bookmark = BookmarkController(current_user.id).delete(bookmark_id)
- flash(gettext("Bookmark %(bookmark_name)s successfully deleted.",
- bookmark_name=bookmark.title), 'success')
- return redirect(url_for('bookmarks.list_'))
-
-
-@bookmarks_bp.route('/delete', methods=['GET'])
-@login_required
-def delete_all():
- "Delete all bookmarks."
- bookmark = BookmarkController(current_user.id).read().delete()
- db.session.commit()
- flash(gettext("Bookmarks successfully deleted."), 'success')
- return redirect(redirect_url())
-
-
-@bookmark_bp.route('/bookmarklet', methods=['GET', 'POST'])
-@login_required
-def bookmarklet():
- bookmark_contr = BookmarkController(current_user.id)
- href = (request.args if request.method == 'GET' else request.form)\
- .get('href', None)
- if not href:
- flash(gettext("Couldn't add bookmark: url missing."), "error")
- raise BadRequest("url is missing")
- title = (request.args if request.method == 'GET' else request.form)\
- .get('title', None)
- if not title:
- title = href
-
- bookmark_exists = bookmark_contr.read(**{'href': href}).all()
- if bookmark_exists:
- flash(gettext("Couldn't add bookmark: bookmark already exists."),
- "warning")
- return redirect(url_for('bookmark.form',
- bookmark_id=bookmark_exists[0].id))
-
- bookmark_attr = {'href': href,
- 'description': '',
- 'title': title,
- 'shared': True,
- 'to_read': True}
-
- new_bookmark = bookmark_contr.create(**bookmark_attr)
- flash(gettext('Bookmark successfully created.'), 'success')
- return redirect(url_for('bookmark.form', bookmark_id=new_bookmark.id))
-
-
-@bookmark_bp.route('/import_pinboard', methods=['POST'])
-@login_required
-def import_pinboard():
- bookmarks = request.files.get('jsonfile', None)
- if bookmarks:
- try:
- nb_bookmarks = import_pinboard_json(current_user, bookmarks.read())
- flash(gettext("%(nb_bookmarks)s bookmarks successfully imported.",
- nb_bookmarks=nb_bookmarks), 'success')
- except Exception as e:
- flash(gettext('Error when importing bookmarks.'), 'error')
-
- return redirect(redirect_url())
-
-
-@bookmarks_bp.route('/export', methods=['GET'])
-@login_required
-def export():
- bookmarks = export_bookmarks(current_user)
- response = make_response(bookmarks)
- response.mimetype = 'application/json'
- response.headers["Content-Disposition"] \
- = 'attachment; filename=newspipe_bookmarks_export.json'
- return response
diff --git a/src/web/views/category.py b/src/web/views/category.py
deleted file mode 100644
index 138561dd..00000000
--- a/src/web/views/category.py
+++ /dev/null
@@ -1,86 +0,0 @@
-from flask import Blueprint, render_template, flash, redirect, url_for
-from flask_babel import gettext
-from flask_login import login_required, current_user
-
-from web.forms import CategoryForm
-from lib.utils import redirect_url
-from web.lib.view_utils import etag_match
-from web.controllers import ArticleController, FeedController, \
- CategoryController
-
-categories_bp = Blueprint('categories', __name__, url_prefix='/categories')
-category_bp = Blueprint('category', __name__, url_prefix='/category')
-
-
-@categories_bp.route('/', methods=['GET'])
-@login_required
-@etag_match
-def list_():
- "Lists the subscribed feeds in a table."
- art_contr = ArticleController(current_user.id)
- return render_template('categories.html',
- categories=list(CategoryController(current_user.id).read().order_by('name')),
- feeds_count=FeedController(current_user.id).count_by_category(),
- unread_article_count=art_contr.count_by_category(readed=False),
- article_count=art_contr.count_by_category())
-
-
-@category_bp.route('/create', methods=['GET'])
-@category_bp.route('/edit/<int:category_id>', methods=['GET'])
-@login_required
-@etag_match
-def form(category_id=None):
- action = gettext("Add a category")
- head_titles = [action]
- if category_id is None:
- return render_template('edit_category.html', action=action,
- head_titles=head_titles, form=CategoryForm())
- category = CategoryController(current_user.id).get(id=category_id)
- action = gettext('Edit category')
- head_titles = [action]
- if category.name:
- head_titles.append(category.name)
- return render_template('edit_category.html', action=action,
- head_titles=head_titles, category=category,
- form=CategoryForm(obj=category))
-
-
-@category_bp.route('/delete/<int:category_id>', methods=['GET'])
-@login_required
-def delete(category_id=None):
- category = CategoryController(current_user.id).delete(category_id)
- flash(gettext("Category %(category_name)s successfully deleted.",
- category_name=category.name), 'success')
- return redirect(redirect_url())
-
-
-@category_bp.route('/create', methods=['POST'])
-@category_bp.route('/edit/<int:category_id>', methods=['POST'])
-@login_required
-def process_form(category_id=None):
- form = CategoryForm()
- cat_contr = CategoryController(current_user.id)
-
- if not form.validate():
- return render_template('edit_category.html', form=form)
- existing_cats = list(cat_contr.read(name=form.name.data))
- if existing_cats and category_id is None:
- flash(gettext("Couldn't add category: already exists."), "warning")
- return redirect(url_for('category.form',
- category_id=existing_cats[0].id))
- # Edit an existing category
- category_attr = {'name': form.name.data}
-
- if category_id is not None:
- cat_contr.update({'id': category_id}, category_attr)
- flash(gettext('Category %(cat_name)r successfully updated.',
- cat_name=category_attr['name']), 'success')
- return redirect(url_for('category.form', category_id=category_id))
-
- # Create a new category
- new_category = cat_contr.create(**category_attr)
-
- flash(gettext('Category %(category_name)r successfully created.',
- category_name=new_category.name), 'success')
-
- return redirect(url_for('category.form', category_id=new_category.id))
diff --git a/src/web/views/common.py b/src/web/views/common.py
deleted file mode 100644
index e422fd57..00000000
--- a/src/web/views/common.py
+++ /dev/null
@@ -1,53 +0,0 @@
-import json
-from functools import wraps
-from datetime import datetime
-from flask import current_app, Response
-from flask_login import login_user
-from flask_principal import (Identity, Permission, RoleNeed,
- session_identity_loader, identity_changed)
-from web.controllers import UserController
-from lib.utils import default_handler
-
-admin_role = RoleNeed('admin')
-api_role = RoleNeed('api')
-
-admin_permission = Permission(admin_role)
-api_permission = Permission(api_role)
-
-
-def scoped_default_handler():
- if admin_permission.can():
- role = 'admin'
- elif api_permission.can():
- role = 'api'
- else:
- role = 'user'
-
- @wraps(default_handler)
- def wrapper(obj):
- return default_handler(obj, role=role)
- return wrapper
-
-
-def jsonify(func):
- """Will cast results of func as a result, and try to extract
- a status_code for the Response object"""
- @wraps(func)
- def wrapper(*args, **kwargs):
- status_code = 200
- result = func(*args, **kwargs)
- if isinstance(result, Response):
- return result
- elif isinstance(result, tuple):
- result, status_code = result
- return Response(json.dumps(result, default=scoped_default_handler()),
- mimetype='application/json', status=status_code)
- return wrapper
-
-
-def login_user_bundle(user):
- login_user(user)
- identity_changed.send(current_app, identity=Identity(user.id))
- session_identity_loader()
- UserController(user.id).update(
- {'id': user.id}, {'last_seen': datetime.utcnow()})
diff --git a/src/web/views/feed.py b/src/web/views/feed.py
deleted file mode 100644
index b98a005a..00000000
--- a/src/web/views/feed.py
+++ /dev/null
@@ -1,306 +0,0 @@
-import logging
-import requests.exceptions
-from datetime import datetime, timedelta
-from sqlalchemy import desc
-from werkzeug.exceptions import BadRequest
-
-from flask import Blueprint, render_template, flash, \
- redirect, request, url_for, make_response
-from flask_babel import gettext
-from flask_login import login_required, current_user
-from flask_paginate import Pagination, get_page_args
-
-import conf
-from lib import misc_utils, utils
-from lib.feed_utils import construct_feed_from
-from web.lib.view_utils import etag_match
-from web.forms import AddFeedForm
-from web.controllers import (UserController, CategoryController,
- FeedController, ArticleController)
-
-logger = logging.getLogger(__name__)
-feeds_bp = Blueprint('feeds', __name__, url_prefix='/feeds')
-feed_bp = Blueprint('feed', __name__, url_prefix='/feed')
-
-
-@feeds_bp.route('/', methods=['GET'])
-@login_required
-@etag_match
-def feeds():
- "Lists the subscribed feeds in a table."
- art_contr = ArticleController(current_user.id)
- return render_template('feeds.html',
- feeds=FeedController(current_user.id).read().order_by('title'),
- unread_article_count=art_contr.count_by_feed(readed=False),
- article_count=art_contr.count_by_feed())
-
-
-def feed_view(feed_id=None, user_id=None):
- feed = FeedController(user_id).get(id=feed_id)
- word_size = 6
- category = None
- if feed.category_id:
- category = CategoryController(user_id).get(id=feed.category_id)
- filters = {}
- filters['feed_id'] = feed_id
- articles = ArticleController(user_id).read_light(**filters)
-
- # Server-side pagination
- page, per_page, offset = get_page_args(per_page_parameter='per_page')
- pagination = Pagination(page=page, total=articles.count(),
- css_framework='bootstrap3',
- search=False, record_name='articles',
- per_page=per_page)
-
- today = datetime.now()
- try:
- last_article = articles[0].date
- first_article = articles[-1].date
- delta = last_article - first_article
- average = round(float(articles.count()) / abs(delta.days), 2)
- except Exception as e:
- last_article = datetime.fromtimestamp(0)
- first_article = datetime.fromtimestamp(0)
- delta = last_article - first_article
- average = 0
- elapsed = today - last_article
-
- return render_template('feed.html',
- head_titles=[utils.clear_string(feed.title)],
- feed=feed, category=category,
- articles=articles.offset(offset).limit(per_page),
- pagination=pagination,
- first_post_date=first_article,
- end_post_date=last_article,
- average=average, delta=delta, elapsed=elapsed)
-
-
-@feed_bp.route('/<int:feed_id>', methods=['GET'])
-@login_required
-@etag_match
-def feed(feed_id=None):
- "Presents detailed information about a feed."
- return feed_view(feed_id, current_user.id)
-
-
-@feed_bp.route('/public/<int:feed_id>', methods=['GET'])
-@etag_match
-def feed_pub(feed_id=None):
- """
- Presents details of a pubic feed if the profile of the owner is also
- public.
- """
- feed = FeedController(None).get(id=feed_id)
- if feed.private or not feed.user.is_public_profile:
- return render_template('errors/404.html'), 404
- return feed_view(feed_id, None)
-
-
-@feed_bp.route('/delete/<feed_id>', methods=['GET'])
-@login_required
-def delete(feed_id=None):
- feed_contr = FeedController(current_user.id)
- feed = feed_contr.get(id=feed_id)
- feed_contr.delete(feed_id)
- flash(gettext("Feed %(feed_title)s successfully deleted.",
- feed_title=feed.title), 'success')
- return redirect(url_for('home'))
-
-
-@feed_bp.route('/reset_errors/<int:feed_id>', methods=['GET', 'POST'])
-@login_required
-def reset_errors(feed_id):
- feed_contr = FeedController(current_user.id)
- feed = feed_contr.get(id=feed_id)
- feed_contr.update({'id': feed_id}, {'error_count': 0, 'last_error': ''})
- flash(gettext('Feed %(feed_title)r successfully updated.',
- feed_title=feed.title), 'success')
- return redirect(request.referrer or url_for('home'))
-
-
-@feed_bp.route('/bookmarklet', methods=['GET', 'POST'])
-@login_required
-def bookmarklet():
- feed_contr = FeedController(current_user.id)
- url = (request.args if request.method == 'GET' else request.form)\
- .get('url', None)
- if not url:
- flash(gettext("Couldn't add feed: url missing."), "error")
- raise BadRequest("url is missing")
-
- feed_exists = list(feed_contr.read(__or__={'link': url, 'site_link': url}))
- if feed_exists:
- flash(gettext("Couldn't add feed: feed already exists."),
- "warning")
- return redirect(url_for('feed.form', feed_id=feed_exists[0].id))
-
- try:
- feed = construct_feed_from(url)
- except requests.exceptions.ConnectionError:
- flash(gettext("Impossible to connect to the address: {}.".format(url)),
- "danger")
- return redirect(url_for('home'))
- except Exception:
- logger.exception('something bad happened when fetching %r', url)
- return redirect(url_for('home'))
- if not feed.get('link'):
- feed['enabled'] = False
- flash(gettext("Couldn't find a feed url, you'll need to find a Atom or"
- " RSS link manually and reactivate this feed"),
- 'warning')
- feed = feed_contr.create(**feed)
- flash(gettext('Feed was successfully created.'), 'success')
- if feed.enabled and conf.CRAWLING_METHOD == "default":
- misc_utils.fetch(current_user.id, feed.id)
- flash(gettext("Downloading articles for the new feed..."), 'info')
- return redirect(url_for('feed.form', feed_id=feed.id))
-
-
-@feed_bp.route('/update/<action>/<int:feed_id>', methods=['GET', 'POST'])
-@feeds_bp.route('/update/<action>', methods=['GET', 'POST'])
-@login_required
-def update(action, feed_id=None):
- readed = action == 'read'
- filters = {'readed__ne': readed}
-
- nb_days = request.args.get('nb_days', 0, type=int)
- if nb_days != 0:
- filters['date__lt'] = datetime.now() - timedelta(days=nb_days)
-
- if feed_id:
- filters['feed_id'] = feed_id
- ArticleController(current_user.id).update(filters, {'readed': readed})
- flash(gettext('Feed successfully updated.'), 'success')
- return redirect(request.referrer or url_for('home'))
-
-
-@feed_bp.route('/create', methods=['GET'])
-@feed_bp.route('/edit/<int:feed_id>', methods=['GET'])
-@login_required
-@etag_match
-def form(feed_id=None):
- action = gettext("Add a feed")
- categories = CategoryController(current_user.id).read()
- head_titles = [action]
- if feed_id is None:
- form = AddFeedForm()
- form.set_category_choices(categories)
- return render_template('edit_feed.html', action=action,
- head_titles=head_titles, form=form)
- feed = FeedController(current_user.id).get(id=feed_id)
- form = AddFeedForm(obj=feed)
- form.set_category_choices(categories)
- action = gettext('Edit feed')
- head_titles = [action]
- if feed.title:
- head_titles.append(feed.title)
- return render_template('edit_feed.html', action=action,
- head_titles=head_titles, categories=categories,
- form=form, feed=feed)
-
-
-@feed_bp.route('/create', methods=['POST'])
-@feed_bp.route('/edit/<int:feed_id>', methods=['POST'])
-@login_required
-def process_form(feed_id=None):
- form = AddFeedForm()
- feed_contr = FeedController(current_user.id)
- form.set_category_choices(CategoryController(current_user.id).read())
-
- if not form.validate():
- return render_template('edit_feed.html', form=form)
- existing_feeds = list(feed_contr.read(link=form.link.data))
- if existing_feeds and feed_id is None:
- flash(gettext("Couldn't add feed: feed already exists."), "warning")
- return redirect(url_for('feed.form', feed_id=existing_feeds[0].id))
- # Edit an existing feed
- feed_attr = {'title': form.title.data, 'enabled': form.enabled.data,
- 'link': form.link.data, 'site_link': form.site_link.data,
- 'filters': [], 'category_id': form.category_id.data,
- 'private': form.private.data}
- if not feed_attr['category_id'] or feed_attr['category_id'] == '0':
- del feed_attr['category_id']
-
- for filter_attr in ('type', 'pattern', 'action on', 'action'):
- for i, value in enumerate(
- request.form.getlist(filter_attr.replace(' ', '_'))):
- if i >= len(feed_attr['filters']):
- feed_attr['filters'].append({})
- feed_attr['filters'][i][filter_attr] = value
-
- if feed_id is not None:
- feed_contr.update({'id': feed_id}, feed_attr)
- flash(gettext('Feed %(feed_title)r successfully updated.',
- feed_title=feed_attr['title']), 'success')
- return redirect(url_for('feed.form', feed_id=feed_id))
-
- # Create a new feed
- new_feed = feed_contr.create(**feed_attr)
-
- flash(gettext('Feed %(feed_title)r successfully created.',
- feed_title=new_feed.title), 'success')
-
- if conf.CRAWLING_METHOD == "default":
- misc_utils.fetch(current_user.id, new_feed.id)
- flash(gettext("Downloading articles for the new feed..."), 'info')
-
- return redirect(url_for('feed.form', feed_id=new_feed.id))
-
-
-@feeds_bp.route('/inactives', methods=['GET'])
-@login_required
-def inactives():
- """
- List of inactive feeds.
- """
- nb_days = int(request.args.get('nb_days', 365))
- inactives = FeedController(current_user.id).get_inactives(nb_days)
- return render_template('inactives.html',
- inactives=inactives, nb_days=nb_days)
-
-
-@feed_bp.route('/duplicates/<int:feed_id>', methods=['GET'])
-@login_required
-def duplicates(feed_id):
- """
- Return duplicates article for a feed.
- """
- feed, duplicates = FeedController(current_user.id).get_duplicates(feed_id)
- if len(duplicates) == 0:
- flash(gettext('No duplicates in the feed "{}".').format(feed.title),
- 'info')
- return redirect(url_for('home'))
- return render_template('duplicates.html', duplicates=duplicates, feed=feed)
-
-
-@feeds_bp.route('/export', methods=['GET'])
-@login_required
-def export():
- """
- Export feeds to OPML.
- """
- include_disabled = request.args.get('includedisabled', '') == 'on'
- include_private = request.args.get('includeprivate', '') == 'on'
- include_exceeded_error_count = request.args. \
- get('includeexceedederrorcount', '') == 'on'
-
- filter = {}
- if not include_disabled:
- filter['enabled'] = True
- if not include_private:
- filter['private'] = False
- if not include_exceeded_error_count:
- filter['error_count__lt'] = conf.DEFAULT_MAX_ERROR
-
- user = UserController(current_user.id).get(id=current_user.id)
- feeds = FeedController(current_user.id).read(**filter)
- categories = {cat.id: cat.dump()
- for cat in CategoryController(user.id).read()}
-
- response = make_response(render_template('opml.xml',
- user=user, feeds=feeds,
- categories=categories,
- now=datetime.now()))
- response.headers['Content-Type'] = 'application/xml'
- response.headers['Content-Disposition'] = 'attachment; filename=feeds.opml'
- return response
diff --git a/src/web/views/home.py b/src/web/views/home.py
deleted file mode 100644
index 34ecb9fa..00000000
--- a/src/web/views/home.py
+++ /dev/null
@@ -1,172 +0,0 @@
-import pytz
-import logging
-from datetime import datetime
-
-from flask import current_app, render_template, \
- request, flash, url_for, redirect
-from flask_login import login_required, current_user
-from flask_babel import gettext, get_locale
-from babel.dates import format_datetime, format_timedelta
-
-import conf
-from lib.utils import redirect_url
-from lib import misc_utils
-from web.lib.view_utils import etag_match
-from web.views.common import jsonify
-
-from web.controllers import FeedController, \
- ArticleController, CategoryController
-
-localize = pytz.utc.localize
-logger = logging.getLogger(__name__)
-
-
-@current_app.route('/')
-@login_required
-@etag_match
-def home():
- return render_template('home.html', cdn=conf.CDN_ADDRESS)
-
-
-@current_app.route('/menu')
-@login_required
-@etag_match
-@jsonify
-def get_menu():
- now, locale = datetime.now(), get_locale()
- categories_order = [0]
- categories = {0: {'name': 'No category', 'id': 0}}
- for cat in CategoryController(current_user.id).read().order_by('name'):
- categories_order.append(cat.id)
- categories[cat.id] = cat
- unread = ArticleController(current_user.id).count_by_feed(readed=False)
- for cat_id in categories:
- categories[cat_id]['unread'] = 0
- categories[cat_id]['feeds'] = []
- feeds = {feed.id: feed for feed in FeedController(current_user.id).read()}
- for feed_id, feed in feeds.items():
- feed['created_rel'] = format_timedelta(feed.created_date - now,
- add_direction=True, locale=locale)
- feed['last_rel'] = format_timedelta(feed.last_retrieved - now,
- add_direction=True, locale=locale)
- feed['created_date'] = format_datetime(localize(feed.created_date),
- locale=locale)
- feed['last_retrieved'] = format_datetime(localize(feed.last_retrieved),
- locale=locale)
- feed['category_id'] = feed.category_id or 0
- feed['unread'] = unread.get(feed.id, 0)
- if not feed.filters:
- feed['filters'] = []
- if feed.icon_url:
- feed['icon_url'] = url_for('icon.icon', url=feed.icon_url)
- categories[feed['category_id']]['unread'] += feed['unread']
- categories[feed['category_id']]['feeds'].append(feed_id)
- return {'feeds': feeds, 'categories': categories,
- 'categories_order': categories_order,
- 'crawling_method': conf.CRAWLING_METHOD,
- 'max_error': conf.DEFAULT_MAX_ERROR,
- 'error_threshold': conf.ERROR_THRESHOLD,
- 'is_admin': current_user.is_admin,
- 'all_unread_count': sum(unread.values())}
-
-
-def _get_filters(in_dict):
- filters = {}
- query = in_dict.get('query')
- if query:
- search_title = in_dict.get('search_title') == 'true'
- search_content = in_dict.get('search_content') == 'true'
- if search_title:
- filters['title__ilike'] = "%%%s%%" % query
- if search_content:
- filters['content__ilike'] = "%%%s%%" % query
- if len(filters) == 0:
- filters['title__ilike'] = "%%%s%%" % query
- if len(filters) > 1:
- filters = {"__or__": filters}
- if in_dict.get('filter') == 'unread':
- filters['readed'] = False
- elif in_dict.get('filter') == 'liked':
- filters['like'] = True
- filter_type = in_dict.get('filter_type')
- if filter_type in {'feed_id', 'category_id'} and in_dict.get('filter_id'):
- filters[filter_type] = int(in_dict['filter_id']) or None
- return filters
-
-
-@jsonify
-def _articles_to_json(articles, fd_hash=None):
- now, locale = datetime.now(), get_locale()
- fd_hash = {feed.id: {'title': feed.title,
- 'icon_url': url_for('icon.icon', url=feed.icon_url)
- if feed.icon_url else None}
- for feed in FeedController(current_user.id).read()}
-
- return {'articles': [{'title': art.title, 'liked': art.like,
- 'read': art.readed, 'article_id': art.id, 'selected': False,
- 'feed_id': art.feed_id, 'category_id': art.category_id or 0,
- 'feed_title': fd_hash[art.feed_id]['title'] if fd_hash else None,
- 'icon_url': fd_hash[art.feed_id]['icon_url'] if fd_hash else None,
- 'date': format_datetime(localize(art.date), locale=locale),
- 'rel_date': format_timedelta(art.date - now,
- threshold=1.1, add_direction=True,
- locale=locale)}
- for art in articles.limit(1000)]}
-
-
-@current_app.route('/middle_panel')
-@login_required
-@etag_match
-def get_middle_panel():
- filters = _get_filters(request.args)
- art_contr = ArticleController(current_user.id)
- articles = art_contr.read_light(**filters)
- return _articles_to_json(articles)
-
-
-@current_app.route('/getart/<int:article_id>')
-@current_app.route('/getart/<int:article_id>/<parse>')
-@login_required
-@etag_match
-@jsonify
-def get_article(article_id, parse=False):
- locale = get_locale()
- contr = ArticleController(current_user.id)
- article = contr.get(id=article_id)
- if not article.readed:
- article['readed'] = True
- contr.update({'id': article_id}, {'readed': True})
- article['category_id'] = article.category_id or 0
- feed = FeedController(current_user.id).get(id=article.feed_id)
- article['icon_url'] = url_for('icon.icon', url=feed.icon_url) \
- if feed.icon_url else None
- article['date'] = format_datetime(localize(article.date), locale=locale)
- return article
-
-
-@current_app.route('/mark_all_as_read', methods=['PUT'])
-@login_required
-def mark_all_as_read():
- filters = _get_filters(request.json)
- acontr = ArticleController(current_user.id)
- processed_articles = _articles_to_json(acontr.read_light(**filters))
- acontr.update(filters, {'readed': True})
- return processed_articles
-
-
-@current_app.route('/fetch', methods=['GET'])
-@current_app.route('/fetch/<int:feed_id>', methods=['GET'])
-@login_required
-def fetch(feed_id=None):
- """
- Triggers the download of news.
- News are downloaded in a separated process.
- """
- if conf.CRAWLING_METHOD == "default" \
- and (not conf.ON_HEROKU or current_user.is_admin):
- misc_utils.fetch(current_user.id, feed_id)
- flash(gettext("Downloading articles..."), "info")
- else:
- flash(gettext("The manual retrieving of news is only available " +
- "for administrator, on the Heroku platform."), "info")
- return redirect(redirect_url())
diff --git a/src/web/views/icon.py b/src/web/views/icon.py
deleted file mode 100644
index 64e54cab..00000000
--- a/src/web/views/icon.py
+++ /dev/null
@@ -1,15 +0,0 @@
-import base64
-from flask import Blueprint, Response, request
-from web.controllers import IconController
-from web.lib.view_utils import etag_match
-
-icon_bp = Blueprint('icon', __name__, url_prefix='/icon')
-
-
-@icon_bp.route('/', methods=['GET'])
-@etag_match
-def icon():
- icon = IconController().get(url=request.args['url'])
- headers = {'Cache-Control': 'max-age=86400',
- 'Content-Type': icon.mimetype}
- return Response(base64.b64decode(icon.content), headers=headers)
diff --git a/src/web/views/session_mgmt.py b/src/web/views/session_mgmt.py
deleted file mode 100644
index 0db76115..00000000
--- a/src/web/views/session_mgmt.py
+++ /dev/null
@@ -1,113 +0,0 @@
-import json
-import logging
-
-from datetime import datetime
-from werkzeug.security import generate_password_hash
-from werkzeug.exceptions import NotFound
-from flask import (render_template, flash, session, request,
- url_for, redirect, current_app)
-from flask_babel import gettext, lazy_gettext
-from flask_login import LoginManager, logout_user, \
- login_required, current_user
-from flask_principal import (Principal, AnonymousIdentity, UserNeed,
- identity_changed, identity_loaded,
- session_identity_loader)
-
-import conf
-from web.views.common import admin_role, api_role, login_user_bundle
-from web.controllers import UserController
-from web.forms import SignupForm, SigninForm
-from notifications import notifications
-
-Principal(current_app)
-# Create a permission with a single Need, in this case a RoleNeed.
-
-login_manager = LoginManager()
-login_manager.init_app(current_app)
-login_manager.login_view = 'login'
-login_manager.login_message = lazy_gettext('Please log in to access this page.')
-login_manager.login_message_category = 'info'
-
-logger = logging.getLogger(__name__)
-
-
-@identity_loaded.connect_via(current_app._get_current_object())
-def on_identity_loaded(sender, identity):
- # Set the identity user object
- identity.user = current_user
-
- # Add the UserNeed to the identity
- if current_user.is_authenticated:
- identity.provides.add(UserNeed(current_user.id))
- if current_user.is_admin:
- identity.provides.add(admin_role)
- if current_user.is_api:
- identity.provides.add(api_role)
-
-
-@login_manager.user_loader
-def load_user(user_id):
- return UserController(user_id, ignore_context=True).get(
- id=user_id, is_active=True)
-
-@current_app.before_request
-def before_request():
- if current_user.is_authenticated:
- UserController(current_user.id).update(
- {'id': current_user.id}, {'last_seen': datetime.utcnow()})
-
-@current_app.route('/login', methods=['GET', 'POST'])
-def login():
- if current_user.is_authenticated:
- return redirect(url_for('home'))
- form = SigninForm()
- if form.validate_on_submit():
- login_user_bundle(form.user)
- return form.redirect('home')
- return render_template('login.html', form=form)
-
-
-@current_app.route('/logout')
-@login_required
-def logout():
- # Remove the user information from the session
- logout_user()
-
- # Remove session keys set by Flask-Principal
- for key in ('identity.name', 'identity.auth_type'):
- session.pop(key, None)
-
- # Tell Flask-Principal the user is anonymous
- identity_changed.send(current_app, identity=AnonymousIdentity())
- session_identity_loader()
-
- return redirect(url_for('login'))
-
-
-@current_app.route('/signup', methods=['GET', 'POST'])
-def signup():
- if not conf.SELF_REGISTRATION:
- flash(gettext('Self-registration is disabled.'), 'warning')
- return redirect(url_for('home'))
- if current_user.is_authenticated:
- return redirect(url_for('home'))
-
- form = SignupForm()
- if form.validate_on_submit():
- user = UserController().create(nickname=form.nickname.data,
- pwdhash=generate_password_hash(form.password.data))
-
- # Send the confirmation email
- try:
- notifications.new_account_notification(user, form.email.data)
- except Exception as error:
- flash(gettext('Problem while sending activation email: %(error)s',
- error=error), 'danger')
- return redirect(url_for('home'))
-
- flash(gettext('Your account has been created. '
- 'Check your mail to confirm it.'), 'success')
-
- return redirect(url_for('home'))
-
- return render_template('signup.html', form=form)
diff --git a/src/web/views/user.py b/src/web/views/user.py
deleted file mode 100644
index 24b73a60..00000000
--- a/src/web/views/user.py
+++ /dev/null
@@ -1,203 +0,0 @@
-import string
-import random
-from datetime import datetime, timedelta
-from flask import (Blueprint, g, render_template, redirect,
- flash, url_for, request)
-from flask_babel import gettext
-from flask_login import login_required, current_user
-from flask_paginate import Pagination, get_page_args
-
-import conf
-from notifications import notifications
-from lib import misc_utils
-from lib.data import import_opml, import_json
-from web.lib.user_utils import confirm_token
-from web.controllers import (UserController, FeedController, ArticleController,
- CategoryController, BookmarkController)
-
-from web.forms import ProfileForm
-
-users_bp = Blueprint('users', __name__, url_prefix='/users')
-user_bp = Blueprint('user', __name__, url_prefix='/user')
-
-
-@user_bp.route('/<string:nickname>', methods=['GET'])
-def profile_public(nickname=None):
- """
- Display the public profile of the user.
- """
- category_id = int(request.args.get('category_id', 0))
- user_contr = UserController()
- user = user_contr.get(nickname=nickname)
- if not user.is_public_profile:
- if current_user.is_authenticated and current_user.id == user.id:
- flash(gettext('You must set your profile to public.'), 'info')
- return redirect(url_for('user.profile'))
-
- filters = {}
- filters['private'] = False
- if category_id:
- filters['category_id'] = category_id
- feeds = FeedController(user.id).read(**filters)
-
- return render_template('profile_public.html', user=user, feeds=feeds,
- selected_category_id=category_id)
-
-
-@user_bp.route('/<string:nickname>/stream', defaults={'per_page': '25'}, methods=['GET'])
-def user_stream(per_page, nickname=None):
- """
- Display the stream of a user (list of articles of public feed).
- """
- user_contr = UserController()
- user = user_contr.get(nickname=nickname)
- if not user.is_public_profile:
- if current_user.is_authenticated and current_user.id == user.id:
- flash(gettext('You must set your profile to public.'), 'info')
- return redirect(url_for('user.profile'))
-
- category_id = int(request.args.get('category_id', 0))
- category = CategoryController().read(id=category_id).first()
-
- # Load the public feeds
- filters = {}
- filters['private'] = False
- if category_id:
- filters['category_id'] = category_id
- feeds = FeedController().read(**filters).all()
-
- # Re-initializes the filters to load the articles
- filters = {}
- filters['feed_id__in'] = [feed.id for feed in feeds]
- if category:
- filters['category_id'] = category_id
- articles = ArticleController(user.id).read_light(**filters)
-
- # Server-side pagination
- page, per_page, offset = get_page_args(per_page_parameter='per_page')
- pagination = Pagination(page=page, total=articles.count(),
- css_framework='bootstrap3',
- search=False, record_name='articles',
- per_page=per_page)
-
- return render_template('user_stream.html', user=user,
- articles=articles.offset(offset).limit(per_page),
- category=category,
- pagination=pagination)
-
-
-@user_bp.route('/management', methods=['GET', 'POST'])
-@login_required
-def management():
- """
- Display the management page.
- """
- if request.method == 'POST':
- if None != request.files.get('opmlfile', None):
- # Import an OPML file
- data = request.files.get('opmlfile', None)
- if not misc_utils.allowed_file(data.filename):
- flash(gettext('File not allowed.'), 'danger')
- else:
- try:
- nb = import_opml(current_user.nickname, data.read())
- if conf.CRAWLING_METHOD == "classic":
- misc_utils.fetch(current_user.id, None)
- flash(str(nb) + ' ' + gettext('feeds imported.'),
- "success")
- flash(gettext("Downloading articles..."), 'info')
- except:
- flash(gettext("Impossible to import the new feeds."),
- "danger")
- elif None != request.files.get('jsonfile', None):
- # Import an account
- data = request.files.get('jsonfile', None)
- if not misc_utils.allowed_file(data.filename):
- flash(gettext('File not allowed.'), 'danger')
- else:
- try:
- nb = import_json(current_user.nickname, data.read())
- flash(gettext('Account imported.'), "success")
- except:
- flash(gettext("Impossible to import the account."),
- "danger")
- else:
- flash(gettext('File not allowed.'), 'danger')
-
- nb_feeds = FeedController(current_user.id).read().count()
- art_contr = ArticleController(current_user.id)
- nb_articles = art_contr.read().count()
- nb_unread_articles = art_contr.read(readed=False).count()
- nb_categories = CategoryController(current_user.id).read().count()
- nb_bookmarks = BookmarkController(current_user.id).read().count()
- return render_template('management.html', user=current_user,
- nb_feeds=nb_feeds, nb_articles=nb_articles,
- nb_unread_articles=nb_unread_articles,
- nb_categories=nb_categories,
- nb_bookmarks=nb_bookmarks)
-
-
-@user_bp.route('/profile', methods=['GET', 'POST'])
-@login_required
-def profile():
- """
- Edit the profile of the currently logged user.
- """
- user_contr = UserController(current_user.id)
- user = user_contr.get(id=current_user.id)
- form = ProfileForm()
-
- if request.method == 'POST':
- if form.validate():
- try:
- user_contr.update({'id': current_user.id},
- {'nickname': form.nickname.data,
- 'password': form.password.data,
- 'automatic_crawling': form.automatic_crawling.data,
- 'is_public_profile': form.is_public_profile.data,
- 'bio': form.bio.data,
- 'webpage': form.webpage.data,
- 'twitter': form.twitter.data})
- except Exception as error:
- flash(gettext('Problem while updating your profile: '
- '%(error)s', error=error), 'danger')
- else:
- flash(gettext('User %(nick)s successfully updated',
- nick=user.nickname), 'success')
- return redirect(url_for('user.profile'))
- else:
- return render_template('profile.html', user=user, form=form)
-
- if request.method == 'GET':
- form = ProfileForm(obj=user)
- return render_template('profile.html', user=user, form=form)
-
-
-@user_bp.route('/delete_account', methods=['GET'])
-@login_required
-def delete_account():
- """
- Delete the account of the user (with all its data).
- """
- UserController(current_user.id).delete(current_user.id)
- flash(gettext('Your account has been deleted.'), 'success')
- return redirect(url_for('login'))
-
-
-@user_bp.route('/confirm_account/<string:token>', methods=['GET'])
-def confirm_account(token=None):
- """
- Confirm the account of a user.
- """
- user_contr = UserController()
- user, nickname = None, None
- if token != "":
- nickname = confirm_token(token)
- if nickname:
- user = user_contr.read(nickname=nickname).first()
- if user is not None:
- user_contr.update({'id': user.id}, {'is_active': True})
- flash(gettext('Your account has been confirmed.'), 'success')
- else:
- flash(gettext('Impossible to confirm this account.'), 'danger')
- return redirect(url_for('login'))
diff --git a/src/web/views/views.py b/src/web/views/views.py
deleted file mode 100644
index 57f790b1..00000000
--- a/src/web/views/views.py
+++ /dev/null
@@ -1,95 +0,0 @@
-import sys
-import logging
-import operator
-from datetime import datetime, timedelta
-from flask import (request, render_template, flash,
- url_for, redirect, current_app)
-from flask_babel import gettext
-from sqlalchemy import desc
-
-import conf
-from web import __version__
-from conf import API_ROOT, ADMIN_EMAIL
-from web.controllers import FeedController, UserController
-from web.lib.view_utils import etag_match
-
-logger = logging.getLogger(__name__)
-
-
-@current_app.errorhandler(401)
-def authentication_required(error):
- if API_ROOT in request.url:
- return error
- flash(gettext('Authentication required.'), 'info')
- return redirect(url_for('login'))
-
-
-@current_app.errorhandler(403)
-def authentication_failed(error):
- if API_ROOT in request.url:
- return error
- flash(gettext('Forbidden.'), 'danger')
- return redirect(url_for('login'))
-
-
-@current_app.errorhandler(404)
-def page_not_found(error):
- return render_template('errors/404.html'), 404
-
-
-@current_app.errorhandler(500)
-def internal_server_error(error):
- return render_template('errors/500.html'), 500
-
-
-@current_app.errorhandler(AssertionError)
-def handle_sqlalchemy_assertion_error(error):
- return error.args[0], 400
-
-
-@current_app.route('/popular', methods=['GET'])
-@etag_match
-def popular():
- """
- Return the most popular feeds for the last nb_days days.
- """
- # try to get the 'recent' popular websites, created after
- # 'not_created_before'
- # ie: not_added_before = date_last_added_feed - nb_days
- try:
- nb_days = int(request.args.get('nb_days', 365))
- except ValueError:
- nb_days = 10000
- last_added_feed = FeedController().read().\
- order_by(desc('created_date')).limit(1).all()
- if last_added_feed:
- date_last_added_feed = last_added_feed[0].created_date
- else:
- date_last_added_feed = datetime.now()
- not_added_before = date_last_added_feed - timedelta(days=nb_days)
-
- filters = {}
- filters['created_date__gt'] = not_added_before
- filters['private'] = False
- filters['error_count__lt'] = conf.DEFAULT_MAX_ERROR
- feeds = FeedController().count_by_link(**filters)
- sorted_feeds = sorted(list(feeds.items()), key=operator.itemgetter(1),
- reverse=True)
- return render_template('popular.html', popular=sorted_feeds)
-
-
-@current_app.route('/about', methods=['GET'])
-@etag_match
-def about():
- return render_template('about.html', contact=ADMIN_EMAIL)
-
-@current_app.route('/about/more', methods=['GET'])
-@etag_match
-def about_more():
- return render_template('about_more.html',
- newspipe_version=__version__.split()[1],
- on_heroku=[conf.ON_HEROKU and 'Yes' or 'No'][0],
- registration=[conf.SELF_REGISTRATION and 'Open' or 'Closed'][0],
- python_version="{}.{}.{}".format(*sys.version_info[:3]),
- nb_users=UserController().read().count())
-
bgstack15