aboutsummaryrefslogtreecommitdiff
path: root/newspipe/web
diff options
context:
space:
mode:
Diffstat (limited to 'newspipe/web')
-rw-r--r--newspipe/web/controllers/__init__.py12
-rw-r--r--newspipe/web/controllers/abstract.py90
-rw-r--r--newspipe/web/controllers/article.py65
-rw-r--r--newspipe/web/controllers/bookmark.py23
-rw-r--r--newspipe/web/controllers/category.py5
-rw-r--r--newspipe/web/controllers/feed.py47
-rw-r--r--newspipe/web/controllers/icon.py14
-rw-r--r--newspipe/web/controllers/user.py10
-rw-r--r--newspipe/web/decorators.py3
-rw-r--r--newspipe/web/forms.py174
-rw-r--r--newspipe/web/lib/user_utils.py12
-rw-r--r--newspipe/web/lib/view_utils.py10
-rw-r--r--newspipe/web/models/__init__.py29
-rw-r--r--newspipe/web/models/article.py48
-rw-r--r--newspipe/web/models/bookmark.py22
-rw-r--r--newspipe/web/models/category.py13
-rw-r--r--newspipe/web/models/feed.py44
-rw-r--r--newspipe/web/models/right_mixin.py27
-rw-r--r--newspipe/web/models/role.py3
-rw-r--r--newspipe/web/models/tag.py24
-rw-r--r--newspipe/web/models/user.py32
-rw-r--r--newspipe/web/views/__init__.py23
-rw-r--r--newspipe/web/views/admin.py120
-rw-r--r--newspipe/web/views/api/v2/__init__.py2
-rw-r--r--newspipe/web/views/api/v2/article.py32
-rw-r--r--newspipe/web/views/api/v2/category.py15
-rw-r--r--newspipe/web/views/api/v2/common.py64
-rw-r--r--newspipe/web/views/api/v2/feed.py34
-rw-r--r--newspipe/web/views/api/v3/__init__.py2
-rw-r--r--newspipe/web/views/api/v3/article.py36
-rw-r--r--newspipe/web/views/api/v3/common.py28
-rw-r--r--newspipe/web/views/api/v3/feed.py24
-rw-r--r--newspipe/web/views/article.py115
-rw-r--r--newspipe/web/views/bookmark.py246
-rw-r--r--newspipe/web/views/category.py91
-rw-r--r--newspipe/web/views/common.py32
-rw-r--r--newspipe/web/views/feed.py309
-rw-r--r--newspipe/web/views/home.py180
-rw-r--r--newspipe/web/views/icon.py9
-rw-r--r--newspipe/web/views/session_mgmt.py93
-rw-r--r--newspipe/web/views/user.py193
-rw-r--r--newspipe/web/views/views.py54
42 files changed, 1412 insertions, 997 deletions
diff --git a/newspipe/web/controllers/__init__.py b/newspipe/web/controllers/__init__.py
index 5fbc2619..9b193cc5 100644
--- a/newspipe/web/controllers/__init__.py
+++ b/newspipe/web/controllers/__init__.py
@@ -7,6 +7,12 @@ from .bookmark import BookmarkController
from .tag import BookmarkTagController
-__all__ = ['FeedController', 'CategoryController', 'ArticleController',
- 'UserController', 'IconController', 'BookmarkController',
- 'BookmarkTagController']
+__all__ = [
+ "FeedController",
+ "CategoryController",
+ "ArticleController",
+ "UserController",
+ "IconController",
+ "BookmarkController",
+ "BookmarkTagController",
+]
diff --git a/newspipe/web/controllers/abstract.py b/newspipe/web/controllers/abstract.py
index 764ff305..9d9e84f2 100644
--- a/newspipe/web/controllers/abstract.py
+++ b/newspipe/web/controllers/abstract.py
@@ -11,7 +11,7 @@ logger = logging.getLogger(__name__)
class AbstractController:
_db_cls = None # reference to the database class
- _user_id_key = 'user_id'
+ _user_id_key = "user_id"
def __init__(self, user_id=None, ignore_context=False):
"""User id is a right management mechanism that should be used to
@@ -36,25 +36,25 @@ class AbstractController:
"""
db_filters = set()
for key, value in filters.items():
- if key == '__or__':
+ if key == "__or__":
db_filters.add(or_(*self._to_filters(**value)))
- elif key.endswith('__gt'):
+ elif key.endswith("__gt"):
db_filters.add(getattr(self._db_cls, key[:-4]) > value)
- elif key.endswith('__lt'):
+ elif key.endswith("__lt"):
db_filters.add(getattr(self._db_cls, key[:-4]) < value)
- elif key.endswith('__ge'):
+ elif key.endswith("__ge"):
db_filters.add(getattr(self._db_cls, key[:-4]) >= value)
- elif key.endswith('__le'):
+ elif key.endswith("__le"):
db_filters.add(getattr(self._db_cls, key[:-4]) <= value)
- elif key.endswith('__ne'):
+ elif key.endswith("__ne"):
db_filters.add(getattr(self._db_cls, key[:-4]) != value)
- elif key.endswith('__in'):
+ elif key.endswith("__in"):
db_filters.add(getattr(self._db_cls, key[:-4]).in_(value))
- elif key.endswith('__contains'):
+ elif key.endswith("__contains"):
db_filters.add(getattr(self._db_cls, key[:-10]).contains(value))
- elif key.endswith('__like'):
+ elif key.endswith("__like"):
db_filters.add(getattr(self._db_cls, key[:-6]).like(value))
- elif key.endswith('__ilike'):
+ elif key.endswith("__ilike"):
db_filters.add(getattr(self._db_cls, key[:-7]).ilike(value))
else:
db_filters.add(getattr(self._db_cls, key) == value)
@@ -66,8 +66,11 @@ class AbstractController:
dependent) and the user is not an admin and the filters doesn't already
contains a filter for that user.
"""
- if self._user_id_key is not None and self.user_id \
- and filters.get(self._user_id_key) != self.user_id:
+ if (
+ self._user_id_key is not None
+ and self.user_id
+ and filters.get(self._user_id_key) != self.user_id
+ ):
filters[self._user_id_key] = self.user_id
return self._db_cls.query.filter(*self._to_filters(**filters))
@@ -76,20 +79,27 @@ class AbstractController:
obj = self._get(**filters).first()
if obj and not self._has_right_on(obj):
- raise Forbidden({'message': 'No authorized to access %r (%r)'
- % (self._db_cls.__class__.__name__, filters)})
+ raise Forbidden(
+ {
+ "message": "No authorized to access %r (%r)"
+ % (self._db_cls.__class__.__name__, filters)
+ }
+ )
if not obj:
- raise NotFound({'message': 'No %r (%r)'
- % (self._db_cls.__class__.__name__, filters)})
+ raise NotFound(
+ {"message": "No %r (%r)" % (self._db_cls.__class__.__name__, filters)}
+ )
return obj
def create(self, **attrs):
assert attrs, "attributes to update must not be empty"
if self._user_id_key is not None and self._user_id_key not in attrs:
attrs[self._user_id_key] = self.user_id
- assert self._user_id_key is None or self._user_id_key in attrs \
- or self.user_id is None, \
- "You must provide user_id one way or another"
+ assert (
+ self._user_id_key is None
+ or self._user_id_key in attrs
+ or self.user_id is None
+ ), "You must provide user_id one way or another"
obj = self._db_cls(**attrs)
db.session.add(obj)
@@ -123,39 +133,45 @@ class AbstractController:
# user_id == None is like being admin
if self._user_id_key is None:
return True
- return self.user_id is None \
- or getattr(obj, self._user_id_key, None) == self.user_id
+ return (
+ self.user_id is None
+ or getattr(obj, self._user_id_key, None) == self.user_id
+ )
def _count_by(self, elem_to_group_by, filters):
if self.user_id:
- filters['user_id'] = self.user_id
- return dict(db.session.query(elem_to_group_by, func.count('id'))
- .filter(*self._to_filters(**filters))
- .group_by(elem_to_group_by).all())
+ filters["user_id"] = self.user_id
+ return dict(
+ db.session.query(elem_to_group_by, func.count("id"))
+ .filter(*self._to_filters(**filters))
+ .group_by(elem_to_group_by)
+ .all()
+ )
@classmethod
def _get_attrs_desc(cls, role, right=None):
result = defaultdict(dict)
- if role == 'admin':
+ if role == "admin":
columns = cls._db_cls.__table__.columns.keys()
else:
- assert role in {'base', 'api'}, 'unknown role %r' % role
- assert right in {'read', 'write'}, \
- "right must be 'read' or 'write' with role %r" % role
- columns = getattr(cls._db_cls, 'fields_%s_%s' % (role, right))()
+ assert role in {"base", "api"}, "unknown role %r" % role
+ assert right in {"read", "write"}, (
+ "right must be 'read' or 'write' with role %r" % role
+ )
+ columns = getattr(cls._db_cls, "fields_%s_%s" % (role, right))()
for column in columns:
result[column] = {}
db_col = getattr(cls._db_cls, column).property.columns[0]
try:
- result[column]['type'] = db_col.type.python_type
+ result[column]["type"] = db_col.type.python_type
except NotImplementedError:
if db_col.default:
- result[column]['type'] = db_col.default.arg.__class__
+ result[column]["type"] = db_col.default.arg.__class__
if column not in result:
continue
- if issubclass(result[column]['type'], datetime):
- result[column]['default'] = datetime.utcnow()
- result[column]['type'] = lambda x: dateutil.parser.parse(x)
+ if issubclass(result[column]["type"], datetime):
+ result[column]["default"] = datetime.utcnow()
+ result[column]["type"] = lambda x: dateutil.parser.parse(x)
elif db_col.default:
- result[column]['default'] = db_col.default.arg
+ result[column]["default"] = db_col.default.arg
return result
diff --git a/newspipe/web/controllers/article.py b/newspipe/web/controllers/article.py
index d7058229..03342a1f 100644
--- a/newspipe/web/controllers/article.py
+++ b/newspipe/web/controllers/article.py
@@ -30,19 +30,24 @@ class ArticleController(AbstractController):
return self._count_by(Article.feed_id, filters)
def count_by_user_id(self, **filters):
- return dict(db.session.query(Article.user_id, func.count(Article.id))
- .filter(*self._to_filters(**filters))
- .group_by(Article.user_id).all())
+ return dict(
+ db.session.query(Article.user_id, func.count(Article.id))
+ .filter(*self._to_filters(**filters))
+ .group_by(Article.user_id)
+ .all()
+ )
def create(self, **attrs):
# handling special denorm for article rights
- assert 'feed_id' in attrs, "must provide feed_id when creating article"
- feed = FeedController(
- attrs.get('user_id', self.user_id)).get(id=attrs['feed_id'])
- if 'user_id' in attrs:
- assert feed.user_id == attrs['user_id'] or self.user_id is None, \
- "no right on feed %r" % feed.id
- attrs['user_id'], attrs['category_id'] = feed.user_id, feed.category_id
+ assert "feed_id" in attrs, "must provide feed_id when creating article"
+ feed = FeedController(attrs.get("user_id", self.user_id)).get(
+ id=attrs["feed_id"]
+ )
+ if "user_id" in attrs:
+ assert feed.user_id == attrs["user_id"] or self.user_id is None, (
+ "no right on feed %r" % feed.id
+ )
+ attrs["user_id"], attrs["category_id"] = feed.user_id, feed.category_id
skipped, read, liked = process_filters(feed.filters, attrs)
if skipped:
@@ -51,15 +56,16 @@ class ArticleController(AbstractController):
return article
def update(self, filters, attrs):
- user_id = attrs.get('user_id', self.user_id)
- if 'feed_id' in attrs:
- feed = FeedController().get(id=attrs['feed_id'])
+ user_id = attrs.get("user_id", self.user_id)
+ if "feed_id" in attrs:
+ feed = FeedController().get(id=attrs["feed_id"])
assert feed.user_id == user_id, "no right on feed %r" % feed.id
- attrs['category_id'] = feed.category_id
- if attrs.get('category_id'):
- cat = CategoryController().get(id=attrs['category_id'])
- assert self.user_id is None or cat.user_id == user_id, \
- "no right on cat %r" % cat.id
+ attrs["category_id"] = feed.category_id
+ if attrs.get("category_id"):
+ cat = CategoryController().get(id=attrs["category_id"])
+ assert self.user_id is None or cat.user_id == user_id, (
+ "no right on cat %r" % cat.id
+ )
return super().update(filters, attrs)
def get_history(self, year=None, month=None):
@@ -69,11 +75,11 @@ class ArticleController(AbstractController):
articles_counter = Counter()
articles = self.read()
if year is not None:
- articles = articles.filter(
- sqlalchemy.extract('year', Article.date) == year)
+ articles = articles.filter(sqlalchemy.extract("year", Article.date) == year)
if month is not None:
articles = articles.filter(
- sqlalchemy.extract('month', Article.date) == month)
+ sqlalchemy.extract("month", Article.date) == month
+ )
for article in articles.all():
if year is not None:
articles_counter[article.date.month] += 1
@@ -82,6 +88,17 @@ class ArticleController(AbstractController):
return articles_counter, articles
def read_light(self, **filters):
- return super().read(**filters).with_entities(Article.id, Article.title,
- Article.readed, Article.like, Article.feed_id, Article.date,
- Article.category_id).order_by(Article.date.desc())
+ return (
+ super()
+ .read(**filters)
+ .with_entities(
+ Article.id,
+ Article.title,
+ Article.readed,
+ Article.like,
+ Article.feed_id,
+ Article.date,
+ Article.category_id,
+ )
+ .order_by(Article.date.desc())
+ )
diff --git a/newspipe/web/controllers/bookmark.py b/newspipe/web/controllers/bookmark.py
index b5413243..d1c1260c 100644
--- a/newspipe/web/controllers/bookmark.py
+++ b/newspipe/web/controllers/bookmark.py
@@ -17,16 +17,19 @@ class BookmarkController(AbstractController):
return self._count_by(Bookmark.href, filters)
def update(self, filters, attrs):
- BookmarkTagController(self.user_id) \
- .read(**{'bookmark_id': filters["id"]}) \
- .delete()
+ BookmarkTagController(self.user_id).read(
+ **{"bookmark_id": filters["id"]}
+ ).delete()
- for tag in attrs['tags']:
+ for tag in attrs["tags"]:
BookmarkTagController(self.user_id).create(
- **{'text': tag.text,
- 'id': tag.id,
- 'bookmark_id': tag.bookmark_id,
- 'user_id': tag.user_id})
-
- del attrs['tags']
+ **{
+ "text": tag.text,
+ "id": tag.id,
+ "bookmark_id": tag.bookmark_id,
+ "user_id": tag.user_id,
+ }
+ )
+
+ del attrs["tags"]
return super().update(filters, attrs)
diff --git a/newspipe/web/controllers/category.py b/newspipe/web/controllers/category.py
index fef5ca81..ec54f5a3 100644
--- a/newspipe/web/controllers/category.py
+++ b/newspipe/web/controllers/category.py
@@ -7,6 +7,7 @@ class CategoryController(AbstractController):
_db_cls = Category
def delete(self, obj_id):
- FeedController(self.user_id).update({'category_id': obj_id},
- {'category_id': None})
+ FeedController(self.user_id).update(
+ {"category_id": obj_id}, {"category_id": None}
+ )
return super().delete(obj_id)
diff --git a/newspipe/web/controllers/feed.py b/newspipe/web/controllers/feed.py
index d75cd994..19ba463f 100644
--- a/newspipe/web/controllers/feed.py
+++ b/newspipe/web/controllers/feed.py
@@ -16,22 +16,26 @@ DEFAULT_MAX_ERROR = conf.DEFAULT_MAX_ERROR
class FeedController(AbstractController):
_db_cls = Feed
- def list_late(self, max_last, max_error=DEFAULT_MAX_ERROR,
- limit=DEFAULT_LIMIT):
- return [feed for feed in self.read(
- error_count__lt=max_error, enabled=True,
- last_retrieved__lt=max_last)
- .join(User).filter(User.is_active == True)
- .order_by('last_retrieved')
- .limit(limit)]
+ def list_late(self, max_last, max_error=DEFAULT_MAX_ERROR, limit=DEFAULT_LIMIT):
+ return [
+ feed
+ for feed in self.read(
+ error_count__lt=max_error, enabled=True, last_retrieved__lt=max_last
+ )
+ .join(User)
+ .filter(User.is_active == True)
+ .order_by("last_retrieved")
+ .limit(limit)
+ ]
def list_fetchable(self, max_error=DEFAULT_MAX_ERROR, limit=DEFAULT_LIMIT):
now = datetime.now()
max_last = now - timedelta(minutes=60)
feeds = self.list_late(max_last, max_error, limit)
if feeds:
- self.update({'id__in': [feed.id for feed in feeds]},
- {'last_retrieved': now})
+ self.update(
+ {"id__in": [feed.id for feed in feeds]}, {"last_retrieved": now}
+ )
return feeds
def get_duplicates(self, feed_id):
@@ -43,8 +47,9 @@ class FeedController(AbstractController):
duplicates = []
for pair in itertools.combinations(feed.articles[:1000], 2):
date1, date2 = pair[0].date, pair[1].date
- if clear_string(pair[0].title) == clear_string(pair[1].title) \
- and (date1 - date2) < timedelta(days=1):
+ if clear_string(pair[0].title) == clear_string(pair[1].title) and (
+ date1 - date2
+ ) < timedelta(days=1):
if pair[0].retrieved_date < pair[1].retrieved_date:
duplicates.append((pair[0], pair[1]))
else:
@@ -75,11 +80,11 @@ class FeedController(AbstractController):
return self._count_by(Feed.link, filters)
def _ensure_icon(self, attrs):
- if not attrs.get('icon_url'):
+ if not attrs.get("icon_url"):
return
icon_contr = IconController()
- if not icon_contr.read(url=attrs['icon_url']).count():
- icon_contr.create(**{'url': attrs['icon_url']})
+ if not icon_contr.read(url=attrs["icon_url"]).count():
+ icon_contr.create(**{"url": attrs["icon_url"]})
def create(self, **attrs):
self._ensure_icon(attrs)
@@ -87,12 +92,14 @@ class FeedController(AbstractController):
def update(self, filters, attrs):
from .article import ArticleController
+
self._ensure_icon(attrs)
- if 'category_id' in attrs and attrs['category_id'] == 0:
- del attrs['category_id']
- elif 'category_id' in attrs:
+ if "category_id" in attrs and attrs["category_id"] == 0:
+ del attrs["category_id"]
+ elif "category_id" in attrs:
art_contr = ArticleController(self.user_id)
for feed in self.read(**filters):
- art_contr.update({'feed_id': feed.id},
- {'category_id': attrs['category_id']})
+ art_contr.update(
+ {"feed_id": feed.id}, {"category_id": attrs["category_id"]}
+ )
return super().update(filters, attrs)
diff --git a/newspipe/web/controllers/icon.py b/newspipe/web/controllers/icon.py
index 07c4a4ef..de86b52f 100644
--- a/newspipe/web/controllers/icon.py
+++ b/newspipe/web/controllers/icon.py
@@ -9,11 +9,15 @@ class IconController(AbstractController):
_user_id_key = None
def _build_from_url(self, attrs):
- if 'url' in attrs and 'content' not in attrs:
- resp = requests.get(attrs['url'], verify=False)
- attrs.update({'url': resp.url,
- 'mimetype': resp.headers.get('content-type', None),
- 'content': base64.b64encode(resp.content).decode('utf8')})
+ if "url" in attrs and "content" not in attrs:
+ resp = requests.get(attrs["url"], verify=False)
+ attrs.update(
+ {
+ "url": resp.url,
+ "mimetype": resp.headers.get("content-type", None),
+ "content": base64.b64encode(resp.content).decode("utf8"),
+ }
+ )
return attrs
def create(self, **attrs):
diff --git a/newspipe/web/controllers/user.py b/newspipe/web/controllers/user.py
index 6ab04d44..71eb7d08 100644
--- a/newspipe/web/controllers/user.py
+++ b/newspipe/web/controllers/user.py
@@ -8,13 +8,13 @@ logger = logging.getLogger(__name__)
class UserController(AbstractController):
_db_cls = User
- _user_id_key = 'id'
+ _user_id_key = "id"
def _handle_password(self, attrs):
- if attrs.get('password'):
- attrs['pwdhash'] = generate_password_hash(attrs.pop('password'))
- elif 'password' in attrs:
- del attrs['password']
+ if attrs.get("password"):
+ attrs["pwdhash"] = generate_password_hash(attrs.pop("password"))
+ elif "password" in attrs:
+ del attrs["password"]
def check_password(self, user, password):
return check_password_hash(user.pwdhash, password)
diff --git a/newspipe/web/decorators.py b/newspipe/web/decorators.py
index 3835f646..8569a024 100644
--- a/newspipe/web/decorators.py
+++ b/newspipe/web/decorators.py
@@ -13,9 +13,11 @@ def async_maker(f):
indexing the database) in background.
This prevent the server to freeze.
"""
+
def wrapper(*args, **kwargs):
thr = Thread(target=f, args=args, kwargs=kwargs)
thr.start()
+
return wrapper
@@ -24,4 +26,5 @@ def pyagg_default_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
+
return wrapper
diff --git a/newspipe/web/forms.py b/newspipe/web/forms.py
index 7b1893e2..046ce1ec 100644
--- a/newspipe/web/forms.py
+++ b/newspipe/web/forms.py
@@ -30,8 +30,17 @@ from flask import flash, url_for, redirect
from flask_wtf import FlaskForm
from flask_babel import lazy_gettext
from werkzeug.exceptions import NotFound
-from wtforms import TextField, TextAreaField, PasswordField, BooleanField, \
- SubmitField, IntegerField, SelectField, validators, HiddenField
+from wtforms import (
+ TextField,
+ TextAreaField,
+ PasswordField,
+ BooleanField,
+ SubmitField,
+ IntegerField,
+ SelectField,
+ validators,
+ HiddenField,
+)
from wtforms.fields.html5 import EmailField, URLField
from lib import misc_utils
@@ -43,27 +52,44 @@ class SignupForm(FlaskForm):
"""
Sign up form (registration to newspipe).
"""
- nickname = TextField(lazy_gettext("Nickname"),
- [validators.Required(lazy_gettext("Please enter your nickname."))])
- email = EmailField(lazy_gettext("Email"),
- [validators.Length(min=6, max=35),
- validators.Required(
- lazy_gettext("Please enter your email address (only for account activation, won't be stored)."))])
- password = PasswordField(lazy_gettext("Password"),
- [validators.Required(lazy_gettext("Please enter a password.")),
- validators.Length(min=6, max=100)])
+
+ nickname = TextField(
+ lazy_gettext("Nickname"),
+ [validators.Required(lazy_gettext("Please enter your nickname."))],
+ )
+ email = EmailField(
+ lazy_gettext("Email"),
+ [
+ validators.Length(min=6, max=35),
+ validators.Required(
+ lazy_gettext(
+ "Please enter your email address (only for account activation, won't be stored)."
+ )
+ ),
+ ],
+ )
+ password = PasswordField(
+ lazy_gettext("Password"),
+ [
+ validators.Required(lazy_gettext("Please enter a password.")),
+ validators.Length(min=6, max=100),
+ ],
+ )
submit = SubmitField(lazy_gettext("Sign up"))
def validate(self):
ucontr = UserController()
validated = super().validate()
if ucontr.read(nickname=self.nickname.data).count():
- self.nickname.errors.append('Nickname already taken')
+ self.nickname.errors.append("Nickname already taken")
validated = False
if self.nickname.data != User.make_valid_nickname(self.nickname.data):
- self.nickname.errors.append(lazy_gettext(
- 'This nickname has invalid characters. '
- 'Please use letters, numbers, dots and underscores only.'))
+ self.nickname.errors.append(
+ lazy_gettext(
+ "This nickname has invalid characters. "
+ "Please use letters, numbers, dots and underscores only."
+ )
+ )
validated = False
return validated
@@ -72,14 +98,15 @@ class RedirectForm(FlaskForm):
"""
Secure back redirects with WTForms.
"""
+
next = HiddenField()
def __init__(self, *args, **kwargs):
FlaskForm.__init__(self, *args, **kwargs)
if not self.next.data:
- self.next.data = misc_utils.get_redirect_target() or ''
+ self.next.data = misc_utils.get_redirect_target() or ""
- def redirect(self, endpoint='home', **values):
+ def redirect(self, endpoint="home", **values):
if misc_utils.is_safe_url(self.next.data):
return redirect(self.next.data)
target = misc_utils.get_redirect_target()
@@ -90,13 +117,21 @@ class SigninForm(RedirectForm):
"""
Sign in form (connection to newspipe).
"""
- nickmane = TextField("Nickname",
- [validators.Length(min=3, max=35),
- validators.Required(
- lazy_gettext("Please enter your nickname."))])
- password = PasswordField(lazy_gettext('Password'),
- [validators.Required(lazy_gettext("Please enter a password.")),
- validators.Length(min=6, max=100)])
+
+ nickmane = TextField(
+ "Nickname",
+ [
+ validators.Length(min=3, max=35),
+ validators.Required(lazy_gettext("Please enter your nickname.")),
+ ],
+ )
+ password = PasswordField(
+ lazy_gettext("Password"),
+ [
+ validators.Required(lazy_gettext("Please enter a password.")),
+ validators.Length(min=6, max=100),
+ ],
+ )
submit = SubmitField(lazy_gettext("Log In"))
def __init__(self, *args, **kwargs):
@@ -109,15 +144,14 @@ class SigninForm(RedirectForm):
try:
user = ucontr.get(nickname=self.nickmane.data)
except NotFound:
- self.nickmane.errors.append(
- 'Wrong nickname')
+ self.nickmane.errors.append("Wrong nickname")
validated = False
else:
if not user.is_active:
- self.nickmane.errors.append('Account not active')
+ self.nickmane.errors.append("Account not active")
validated = False
if not ucontr.check_password(user, self.password.data):
- self.password.errors.append('Wrong password')
+ self.password.errors.append("Wrong password")
validated = False
self.user = user
return validated
@@ -127,19 +161,24 @@ class UserForm(FlaskForm):
"""
Create or edit a user (for the administrator).
"""
- nickname = TextField(lazy_gettext("Nickname"),
- [validators.Required(lazy_gettext("Please enter your nickname."))])
+
+ nickname = TextField(
+ lazy_gettext("Nickname"),
+ [validators.Required(lazy_gettext("Please enter your nickname."))],
+ )
password = PasswordField(lazy_gettext("Password"))
- automatic_crawling = BooleanField(lazy_gettext("Automatic crawling"),
- default=True)
+ automatic_crawling = BooleanField(lazy_gettext("Automatic crawling"), default=True)
submit = SubmitField(lazy_gettext("Save"))
def validate(self):
validated = super(UserForm, self).validate()
if self.nickname.data != User.make_valid_nickname(self.nickname.data):
- self.nickname.errors.append(lazy_gettext(
- 'This nickname has invalid characters. '
- 'Please use letters, numbers, dots and underscores only.'))
+ self.nickname.errors.append(
+ lazy_gettext(
+ "This nickname has invalid characters. "
+ "Please use letters, numbers, dots and underscores only."
+ )
+ )
validated = False
return validated
@@ -148,17 +187,18 @@ class ProfileForm(FlaskForm):
"""
Edit user information.
"""
- nickname = TextField(lazy_gettext("Nickname"),
- [validators.Required(lazy_gettext("Please enter your nickname."))])
+
+ nickname = TextField(
+ lazy_gettext("Nickname"),
+ [validators.Required(lazy_gettext("Please enter your nickname."))],
+ )
password = PasswordField(lazy_gettext("Password"))
password_conf = PasswordField(lazy_gettext("Password Confirmation"))
- automatic_crawling = BooleanField(lazy_gettext("Automatic crawling"),
- default=True)
+ automatic_crawling = BooleanField(lazy_gettext("Automatic crawling"), default=True)
bio = TextAreaField(lazy_gettext("Bio"))
webpage = URLField(lazy_gettext("Webpage"))
twitter = URLField(lazy_gettext("Twitter"))
- is_public_profile = BooleanField(lazy_gettext("Public profile"),
- default=True)
+ is_public_profile = BooleanField(lazy_gettext("Public profile"), default=True)
submit = SubmitField(lazy_gettext("Save"))
def validate(self):
@@ -169,28 +209,34 @@ class ProfileForm(FlaskForm):
self.password_conf.errors.append(message)
validated = False
if self.nickname.data != User.make_valid_nickname(self.nickname.data):
- self.nickname.errors.append(lazy_gettext('This nickname has '
- 'invalid characters. Please use letters, numbers, dots and'
- ' underscores only.'))
+ self.nickname.errors.append(
+ lazy_gettext(
+ "This nickname has "
+ "invalid characters. Please use letters, numbers, dots and"
+ " underscores only."
+ )
+ )
validated = False
return validated
class AddFeedForm(FlaskForm):
title = TextField(lazy_gettext("Title"), [validators.Optional()])
- link = TextField(lazy_gettext("Feed link"),
- [validators.Required(lazy_gettext("Please enter the URL."))])
+ link = TextField(
+ lazy_gettext("Feed link"),
+ [validators.Required(lazy_gettext("Please enter the URL."))],
+ )
site_link = TextField(lazy_gettext("Site link"), [validators.Optional()])
enabled = BooleanField(lazy_gettext("Check for updates"), default=True)
submit = SubmitField(lazy_gettext("Save"))
- category_id = SelectField(lazy_gettext("Category of the feed"),
- [validators.Optional()])
+ category_id = SelectField(
+ lazy_gettext("Category of the feed"), [validators.Optional()]
+ )
private = BooleanField(lazy_gettext("Private"), default=False)
def set_category_choices(self, categories):
- self.category_id.choices = [('0', 'No Category')]
- self.category_id.choices += [(str(cat.id), cat.name)
- for cat in categories]
+ self.category_id.choices = [("0", "No Category")]
+ self.category_id.choices += [(str(cat.id), cat.name) for cat in categories]
class CategoryForm(FlaskForm):
@@ -199,13 +245,13 @@ class CategoryForm(FlaskForm):
class BookmarkForm(FlaskForm):
- href = TextField(lazy_gettext("URL"),
- [validators.Required(
- lazy_gettext("Please enter an URL."))])
- title = TextField(lazy_gettext("Title"),
- [validators.Length(min=0, max=100)])
- description = TextField(lazy_gettext("Description"),
- [validators.Length(min=0, max=500)])
+ href = TextField(
+ lazy_gettext("URL"), [validators.Required(lazy_gettext("Please enter an URL."))]
+ )
+ title = TextField(lazy_gettext("Title"), [validators.Length(min=0, max=100)])
+ description = TextField(
+ lazy_gettext("Description"), [validators.Length(min=0, max=500)]
+ )
tags = TextField(lazy_gettext("Tags"))
to_read = BooleanField(lazy_gettext("To read"), default=False)
shared = BooleanField(lazy_gettext("Shared"), default=False)
@@ -213,8 +259,12 @@ class BookmarkForm(FlaskForm):
class InformationMessageForm(FlaskForm):
- subject = TextField(lazy_gettext("Subject"),
- [validators.Required(lazy_gettext("Please enter a subject."))])
- message = TextAreaField(lazy_gettext("Message"),
- [validators.Required(lazy_gettext("Please enter a content."))])
+ subject = TextField(
+ lazy_gettext("Subject"),
+ [validators.Required(lazy_gettext("Please enter a subject."))],
+ )
+ message = TextAreaField(
+ lazy_gettext("Message"),
+ [validators.Required(lazy_gettext("Please enter a content."))],
+ )
submit = SubmitField(lazy_gettext("Send"))
diff --git a/newspipe/web/lib/user_utils.py b/newspipe/web/lib/user_utils.py
index f78a6ed6..84b1c75c 100644
--- a/newspipe/web/lib/user_utils.py
+++ b/newspipe/web/lib/user_utils.py
@@ -1,22 +1,20 @@
-
-
from itsdangerous import URLSafeTimedSerializer
import conf
from bootstrap import application
def generate_confirmation_token(nickname):
- serializer = URLSafeTimedSerializer(application.config['SECRET_KEY'])
- return serializer.dumps(nickname, salt=application.config['SECURITY_PASSWORD_SALT'])
+ serializer = URLSafeTimedSerializer(application.config["SECRET_KEY"])
+ return serializer.dumps(nickname, salt=application.config["SECURITY_PASSWORD_SALT"])
def confirm_token(token):
- serializer = URLSafeTimedSerializer(application.config['SECRET_KEY'])
+ serializer = URLSafeTimedSerializer(application.config["SECRET_KEY"])
try:
nickname = serializer.loads(
token,
- salt=application.config['SECURITY_PASSWORD_SALT'],
- max_age=conf.TOKEN_VALIDITY_PERIOD
+ salt=application.config["SECURITY_PASSWORD_SALT"],
+ max_age=conf.TOKEN_VALIDITY_PERIOD,
)
except:
return False
diff --git a/newspipe/web/lib/view_utils.py b/newspipe/web/lib/view_utils.py
index 1d8c6aed..218ebb4c 100644
--- a/newspipe/web/lib/view_utils.py
+++ b/newspipe/web/lib/view_utils.py
@@ -15,12 +15,14 @@ def etag_match(func):
headers = {}
else:
return response
- if request.headers.get('if-none-match') == etag:
+ if request.headers.get("if-none-match") == etag:
response = Response(status=304)
- response.headers['Cache-Control'] \
- = headers.get('Cache-Control', 'pragma: no-cache')
+ response.headers["Cache-Control"] = headers.get(
+ "Cache-Control", "pragma: no-cache"
+ )
elif not isinstance(response, Response):
response = make_response(response)
- response.headers['etag'] = etag
+ response.headers["etag"] = etag
return response
+
return wrapper
diff --git a/newspipe/web/models/__init__.py b/newspipe/web/models/__init__.py
index bfb1368c..09249368 100644
--- a/newspipe/web/models/__init__.py
+++ b/newspipe/web/models/__init__.py
@@ -36,18 +36,29 @@ from .tag import BookmarkTag
from .tag import ArticleTag
from .bookmark import Bookmark
-__all__ = ['Feed', 'Role', 'User', 'Article', 'Icon', 'Category',
- 'Bookmark', 'ArticleTag', 'BookmarkTag']
+__all__ = [
+ "Feed",
+ "Role",
+ "User",
+ "Article",
+ "Icon",
+ "Category",
+ "Bookmark",
+ "ArticleTag",
+ "BookmarkTag",
+]
import os
from sqlalchemy.engine import reflection
from sqlalchemy.schema import (
- MetaData,
- Table,
- DropTable,
- ForeignKeyConstraint,
- DropConstraint)
+ MetaData,
+ Table,
+ DropTable,
+ ForeignKeyConstraint,
+ DropConstraint,
+)
+
def db_empty(db):
"Will drop every datas stocked in db."
@@ -71,9 +82,9 @@ def db_empty(db):
for table_name in inspector.get_table_names():
fks = []
for fk in inspector.get_foreign_keys(table_name):
- if not fk['name']:
+ if not fk["name"]:
continue
- fks.append(ForeignKeyConstraint((), (), name=fk['name']))
+ fks.append(ForeignKeyConstraint((), (), name=fk["name"]))
t = Table(table_name, metadata, *fks)
tbs.append(t)
all_fks.extend(fks)
diff --git a/newspipe/web/models/article.py b/newspipe/web/models/article.py
index d55e59c1..c826a3c9 100644
--- a/newspipe/web/models/article.py
+++ b/newspipe/web/models/article.py
@@ -48,40 +48,54 @@ class Article(db.Model, RightMixin):
retrieved_date = db.Column(db.DateTime(), default=datetime.utcnow)
# foreign keys
- user_id = db.Column(db.Integer(), db.ForeignKey('user.id'))
- feed_id = db.Column(db.Integer(), db.ForeignKey('feed.id'))
- category_id = db.Column(db.Integer(), db.ForeignKey('category.id'))
+ user_id = db.Column(db.Integer(), db.ForeignKey("user.id"))
+ feed_id = db.Column(db.Integer(), db.ForeignKey("feed.id"))
+ category_id = db.Column(db.Integer(), db.ForeignKey("category.id"))
# relationships
- tag_objs = db.relationship('ArticleTag', back_populates='article',
- cascade='all,delete-orphan',
- lazy=False,
- foreign_keys='[ArticleTag.article_id]')
- tags = association_proxy('tag_objs', 'text')
+ tag_objs = db.relationship(
+ "ArticleTag",
+ back_populates="article",
+ cascade="all,delete-orphan",
+ lazy=False,
+ foreign_keys="[ArticleTag.article_id]",
+ )
+ tags = association_proxy("tag_objs", "text")
# indexes
- #__table_args__ = (
+ # __table_args__ = (
# Index('user_id'),
# Index('user_id', 'category_id'),
# Index('user_id', 'feed_id'),
# Index('ix_article_uid_fid_eid', user_id, feed_id, entry_id)
- #)
+ # )
# api whitelists
@staticmethod
def _fields_base_write():
- return {'readed', 'like', 'feed_id', 'category_id'}
+ return {"readed", "like", "feed_id", "category_id"}
@staticmethod
def _fields_base_read():
- return {'id', 'entry_id', 'link', 'title', 'content', 'date',
- 'retrieved_date', 'user_id', 'tags'}
+ return {
+ "id",
+ "entry_id",
+ "link",
+ "title",
+ "content",
+ "date",
+ "retrieved_date",
+ "user_id",
+ "tags",
+ }
@staticmethod
def _fields_api_write():
- return {'tags'}
+ return {"tags"}
def __repr__(self):
- return "<Article(id=%d, entry_id=%s, title=%r, " \
- "date=%r, retrieved_date=%r)>" % (self.id, self.entry_id,
- self.title, self.date, self.retrieved_date)
+ return (
+ "<Article(id=%d, entry_id=%s, title=%r, "
+ "date=%r, retrieved_date=%r)>"
+ % (self.id, self.entry_id, self.title, self.date, self.retrieved_date)
+ )
diff --git a/newspipe/web/models/bookmark.py b/newspipe/web/models/bookmark.py
index eb6b73e3..c557225e 100644
--- a/newspipe/web/models/bookmark.py
+++ b/newspipe/web/models/bookmark.py
@@ -40,6 +40,7 @@ class Bookmark(db.Model, RightMixin):
"""
Represent a bookmark.
"""
+
id = db.Column(db.Integer(), primary_key=True)
href = db.Column(db.String(), default="")
title = db.Column(db.String(), default="")
@@ -47,22 +48,25 @@ class Bookmark(db.Model, RightMixin):
shared = db.Column(db.Boolean(), default=False)
to_read = db.Column(db.Boolean(), default=False)
time = db.Column(db.DateTime(), default=datetime.utcnow)
- user_id = db.Column(db.Integer(), db.ForeignKey('user.id'))
+ user_id = db.Column(db.Integer(), db.ForeignKey("user.id"))
# relationships
- tags = db.relationship(BookmarkTag, backref='of_bookmark', lazy='dynamic',
- cascade='all,delete-orphan',
- order_by=desc(BookmarkTag.text))
- tags_proxy = association_proxy('tags', 'text')
-
+ tags = db.relationship(
+ BookmarkTag,
+ backref="of_bookmark",
+ lazy="dynamic",
+ cascade="all,delete-orphan",
+ order_by=desc(BookmarkTag.text),
+ )
+ tags_proxy = association_proxy("tags", "text")
- @validates('description')
+ @validates("description")
def validates_title(self, key, value):
return str(value).strip()
- @validates('extended')
+ @validates("extended")
def validates_description(self, key, value):
return str(value).strip()
def __repr__(self):
- return '<Bookmark %r>' % (self.href)
+ return "<Bookmark %r>" % (self.href)
diff --git a/newspipe/web/models/category.py b/newspipe/web/models/category.py
index 2da7809a..bb47ce45 100644
--- a/newspipe/web/models/category.py
+++ b/newspipe/web/models/category.py
@@ -11,19 +11,18 @@ class Category(db.Model, RightMixin):
name = db.Column(db.String())
# relationships
- user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
- feeds = db.relationship('Feed', cascade='all,delete-orphan')
- articles = db.relationship('Article',
- cascade='all,delete-orphan')
+ user_id = db.Column(db.Integer, db.ForeignKey("user.id"))
+ feeds = db.relationship("Feed", cascade="all,delete-orphan")
+ articles = db.relationship("Article", cascade="all,delete-orphan")
# index
- idx_category_uid = Index('user_id')
+ idx_category_uid = Index("user_id")
# api whitelists
@staticmethod
def _fields_base_read():
- return {'id', 'user_id'}
+ return {"id", "user_id"}
@staticmethod
def _fields_base_write():
- return {'name'}
+ return {"name"}
diff --git a/newspipe/web/models/feed.py b/newspipe/web/models/feed.py
index fc0b64cb..f440a39c 100644
--- a/newspipe/web/models/feed.py
+++ b/newspipe/web/models/feed.py
@@ -38,6 +38,7 @@ class Feed(db.Model, RightMixin):
"""
Represent a feed.
"""
+
id = db.Column(db.Integer(), primary_key=True)
title = db.Column(db.String(), default="")
description = db.Column(db.String(), default="FR")
@@ -58,34 +59,47 @@ class Feed(db.Model, RightMixin):
error_count = db.Column(db.Integer(), default=0)
# relationship
- icon_url = db.Column(db.String(), db.ForeignKey('icon.url'), default=None)
- user_id = db.Column(db.Integer(), db.ForeignKey('user.id'))
- category_id = db.Column(db.Integer(), db.ForeignKey('category.id'))
- articles = db.relationship(Article, backref='source', lazy='dynamic',
- cascade='all,delete-orphan',
- order_by=desc(Article.date))
+ icon_url = db.Column(db.String(), db.ForeignKey("icon.url"), default=None)
+ user_id = db.Column(db.Integer(), db.ForeignKey("user.id"))
+ category_id = db.Column(db.Integer(), db.ForeignKey("category.id"))
+ articles = db.relationship(
+ Article,
+ backref="source",
+ lazy="dynamic",
+ cascade="all,delete-orphan",
+ order_by=desc(Article.date),
+ )
# index
- idx_feed_uid_cid = Index('user_id', 'category_id')
- idx_feed_uid = Index('user_id')
+ idx_feed_uid_cid = Index("user_id", "category_id")
+ idx_feed_uid = Index("user_id")
- # api whitelists
+ # api whitelists
@staticmethod
def _fields_base_write():
- return {'title', 'description', 'link', 'site_link', 'enabled',
- 'filters', 'last_error', 'error_count', 'category_id'}
+ return {
+ "title",
+ "description",
+ "link",
+ "site_link",
+ "enabled",
+ "filters",
+ "last_error",
+ "error_count",
+ "category_id",
+ }
@staticmethod
def _fields_base_read():
- return {'id', 'user_id', 'icon_url', 'last_retrieved'}
+ return {"id", "user_id", "icon_url", "last_retrieved"}
- @validates('title')
+ @validates("title")
def validates_title(self, key, value):
return str(value).strip()
- @validates('description')
+ @validates("description")
def validates_description(self, key, value):
return str(value).strip()
def __repr__(self):
- return '<Feed %r>' % (self.title)
+ return "<Feed %r>" % (self.title)
diff --git a/newspipe/web/models/right_mixin.py b/newspipe/web/models/right_mixin.py
index 1c316f95..670beafa 100644
--- a/newspipe/web/models/right_mixin.py
+++ b/newspipe/web/models/right_mixin.py
@@ -2,14 +2,13 @@ from sqlalchemy.ext.associationproxy import _AssociationList
class RightMixin:
-
@staticmethod
def _fields_base_write():
return set()
@staticmethod
def _fields_base_read():
- return set(['id'])
+ return set(["id"])
@staticmethod
def _fields_api_write():
@@ -17,7 +16,7 @@ class RightMixin:
@staticmethod
def _fields_api_read():
- return set(['id'])
+ return set(["id"])
@classmethod
def fields_base_write(cls):
@@ -36,26 +35,28 @@ class RightMixin:
return cls.fields_base_read().union(cls._fields_api_read())
def __getitem__(self, key):
- if not hasattr(self, '__dump__'):
+ if not hasattr(self, "__dump__"):
self.__dump__ = {}
return self.__dump__.get(key)
def __setitem__(self, key, value):
- if not hasattr(self, '__dump__'):
+ if not hasattr(self, "__dump__"):
self.__dump__ = {}
self.__dump__[key] = value
- def dump(self, role='admin'):
- if role == 'admin':
- dico = {k: getattr(self, k)
- for k in set(self.__table__.columns.keys())
- .union(self.fields_api_read())
- .union(self.fields_base_read())}
- elif role == 'api':
+ def dump(self, role="admin"):
+ if role == "admin":
+ dico = {
+ k: getattr(self, k)
+ for k in set(self.__table__.columns.keys())
+ .union(self.fields_api_read())
+ .union(self.fields_base_read())
+ }
+ elif role == "api":
dico = {k: getattr(self, k) for k in self.fields_api_read()}
else:
dico = {k: getattr(self, k) for k in self.fields_base_read()}
- if hasattr(self, '__dump__'):
+ if hasattr(self, "__dump__"):
dico.update(self.__dump__)
for key, value in dico.items(): # preventing association proxy to die
if isinstance(value, _AssociationList):
diff --git a/newspipe/web/models/role.py b/newspipe/web/models/role.py
index 0a2ecd4a..15a08b73 100644
--- a/newspipe/web/models/role.py
+++ b/newspipe/web/models/role.py
@@ -33,7 +33,8 @@ class Role(db.Model):
"""
Represent a role.
"""
+
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(), unique=True)
- user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
+ user_id = db.Column(db.Integer, db.ForeignKey("user.id"))
diff --git a/newspipe/web/models/tag.py b/newspipe/web/models/tag.py
index 76467c0b..9bd8afc5 100644
--- a/newspipe/web/models/tag.py
+++ b/newspipe/web/models/tag.py
@@ -8,12 +8,14 @@ class ArticleTag(db.Model):
text = db.Column(db.String, primary_key=True, unique=False)
# foreign keys
- article_id = db.Column(db.Integer, db.ForeignKey('article.id', ondelete='CASCADE'),
- primary_key=True)
+ article_id = db.Column(
+ db.Integer, db.ForeignKey("article.id", ondelete="CASCADE"), primary_key=True
+ )
# relationships
- article = db.relationship('Article', back_populates='tag_objs',
- foreign_keys=[article_id])
+ article = db.relationship(
+ "Article", back_populates="tag_objs", foreign_keys=[article_id]
+ )
def __init__(self, text):
self.text = text
@@ -24,12 +26,18 @@ class BookmarkTag(db.Model):
text = db.Column(db.String, unique=False)
# foreign keys
- user_id = db.Column(db.Integer, db.ForeignKey('user.id', ondelete='CASCADE'))
- bookmark_id = db.Column(db.Integer, db.ForeignKey('bookmark.id', ondelete='CASCADE'))
+ user_id = db.Column(db.Integer, db.ForeignKey("user.id", ondelete="CASCADE"))
+ bookmark_id = db.Column(
+ db.Integer, db.ForeignKey("bookmark.id", ondelete="CASCADE")
+ )
# relationships
- bookmark = db.relationship('Bookmark', back_populates='tags',
- cascade="all,delete", foreign_keys=[bookmark_id])
+ bookmark = db.relationship(
+ "Bookmark",
+ back_populates="tags",
+ cascade="all,delete",
+ foreign_keys=[bookmark_id],
+ )
# def __init__(self, text, user_id):
# self.text = text
diff --git a/newspipe/web/models/user.py b/newspipe/web/models/user.py
index 4d65c3c5..a088c74e 100644
--- a/newspipe/web/models/user.py
+++ b/newspipe/web/models/user.py
@@ -44,6 +44,7 @@ class User(db.Model, UserMixin, RightMixin):
"""
Represent a user.
"""
+
id = db.Column(db.Integer, primary_key=True)
nickname = db.Column(db.String(), unique=True)
pwdhash = db.Column(db.String())
@@ -64,29 +65,34 @@ class User(db.Model, UserMixin, RightMixin):
is_api = db.Column(db.Boolean(), default=False)
# relationships
- categories = db.relationship('Category', backref='user',
- cascade='all, delete-orphan',
- foreign_keys=[Category.user_id])
- feeds = db.relationship('Feed', backref='user',
- cascade='all, delete-orphan',
- foreign_keys=[Feed.user_id])
+ categories = db.relationship(
+ "Category",
+ backref="user",
+ cascade="all, delete-orphan",
+ foreign_keys=[Category.user_id],
+ )
+ feeds = db.relationship(
+ "Feed",
+ backref="user",
+ cascade="all, delete-orphan",
+ foreign_keys=[Feed.user_id],
+ )
@staticmethod
def _fields_base_write():
- return {'login', 'password'}
+ return {"login", "password"}
@staticmethod
def _fields_base_read():
- return {'date_created', 'last_connection'}
+ return {"date_created", "last_connection"}
@staticmethod
def make_valid_nickname(nickname):
- return re.sub('[^a-zA-Z0-9_\.]', '', nickname)
+ return re.sub("[^a-zA-Z0-9_\.]", "", nickname)
- @validates('bio')
+ @validates("bio")
def validates_bio(self, key, value):
- assert len(value) <= 5000, \
- AssertionError("maximum length for bio: 5000")
+ assert len(value) <= 5000, AssertionError("maximum length for bio: 5000")
return value.strip()
def get_id(self):
@@ -105,4 +111,4 @@ class User(db.Model, UserMixin, RightMixin):
return self.id == other.id
def __repr__(self):
- return '<User %r>' % (self.nickname)
+ return "<User %r>" % (self.nickname)
diff --git a/newspipe/web/views/__init__.py b/newspipe/web/views/__init__.py
index 41bb52f3..b8b415f9 100644
--- a/newspipe/web/views/__init__.py
+++ b/newspipe/web/views/__init__.py
@@ -8,10 +8,25 @@ 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']
+__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
diff --git a/newspipe/web/views/admin.py b/newspipe/web/views/admin.py
index 73b2b668..fe1b389b 100644
--- a/newspipe/web/views/admin.py
+++ b/newspipe/web/views/admin.py
@@ -1,5 +1,5 @@
from datetime import datetime
-from flask import (Blueprint, render_template, redirect, flash, url_for)
+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
@@ -8,41 +8,45 @@ 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 = Blueprint("admin", __name__, url_prefix="/admin")
-@admin_bp.route('/dashboard', methods=['GET', 'POST'])
+@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'))
+ 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'])
+ 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)
+ 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)
+ 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'])
+@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):
@@ -53,31 +57,42 @@ def process_user_form(user_id=None):
user_contr = UserController()
if not form.validate():
- return render_template('/admin/create_user.html', form=form,
- message=gettext('Some errors were found'))
+ 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_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')
+ 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))
+ 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'])
+@admin_bp.route("/delete_user/<int:user_id>", methods=["GET"])
@login_required
@admin_permission.require(http_exception=403)
def delete_user(user_id=None):
@@ -86,16 +101,21 @@ def delete_user(user_id=None):
"""
try:
user = UserController().delete(user_id)
- flash(gettext('User %(nick)s successfully deleted',
- nick=user.nickname), 'success')
+ 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'))
+ 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'])
+@admin_bp.route("/toggle_user/<int:user_id>", methods=["GET"])
@login_required
@admin_permission.require()
def toggle_user(user_id=None):
@@ -104,16 +124,18 @@ def toggle_user(user_id=None):
"""
ucontr = UserController()
user = ucontr.get(id=user_id)
- user_changed = ucontr.update({'id': user_id},
- {'is_active': not user.is_active})
+ 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'))
+ 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'))
+ 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/newspipe/web/views/api/v2/__init__.py b/newspipe/web/views/api/v2/__init__.py
index 46760261..ef587e72 100644
--- a/newspipe/web/views/api/v2/__init__.py
+++ b/newspipe/web/views/api/v2/__init__.py
@@ -1,3 +1,3 @@
from web.views.api.v2 import article, feed, category
-__all__ = ['article', 'feed', 'category']
+__all__ = ["article", "feed", "category"]
diff --git a/newspipe/web/views/api/v2/article.py b/newspipe/web/views/api/v2/article.py
index 2be286c6..8da6c6dd 100644
--- a/newspipe/web/views/api/v2/article.py
+++ b/newspipe/web/views/api/v2/article.py
@@ -6,8 +6,12 @@ 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)
+from web.views.api.v2.common import (
+ PyAggAbstractResource,
+ PyAggResourceNew,
+ PyAggResourceExisting,
+ PyAggResourceMulti,
+)
class ArticleNewAPI(PyAggResourceNew):
@@ -24,30 +28,32 @@ class ArticlesAPI(PyAggResourceMulti):
class ArticlesChallenge(PyAggAbstractResource):
controller_cls = ArticleController
- attrs = {'ids': {'type': list, 'default': []}}
+ attrs = {"ids": {"type": list, "default": []}}
@api_permission.require(http_exception=403)
def get(self):
- parsed_args = self.reqparse_args(right='read')
+ 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']:
+ 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):
+ 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']))
+ 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')
+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/newspipe/web/views/api/v2/category.py b/newspipe/web/views/api/v2/category.py
index 70fda1ea..a830624d 100644
--- a/newspipe/web/views/api/v2/category.py
+++ b/newspipe/web/views/api/v2/category.py
@@ -3,9 +3,11 @@ 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)
+from web.views.api.v2.common import (
+ PyAggResourceNew,
+ PyAggResourceExisting,
+ PyAggResourceMulti,
+)
class CategoryNewAPI(PyAggResourceNew):
@@ -21,7 +23,6 @@ class CategoriesAPI(PyAggResourceMulti):
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')
+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/newspipe/web/views/api/v2/common.py b/newspipe/web/views/api/v2/common.py
index 8a53d7e6..81248422 100644
--- a/newspipe/web/views/api/v2/common.py
+++ b/newspipe/web/views/api/v2/common.py
@@ -26,8 +26,12 @@ 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.views.common import (
+ admin_permission,
+ api_permission,
+ login_user_bundle,
+ jsonify,
+)
from web.controllers import UserController
logger = logging.getLogger(__name__)
@@ -50,6 +54,7 @@ def authenticate(func):
if current_user.is_authenticated:
return func(*args, **kwargs)
raise Unauthorized()
+
return wrapper
@@ -64,8 +69,9 @@ class PyAggAbstractResource(Resource):
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):
+ 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
@@ -89,42 +95,41 @@ class PyAggAbstractResource(Resource):
if self.attrs is not None:
attrs = self.attrs
elif admin_permission.can():
- attrs = self.controller_cls._get_attrs_desc('admin')
+ attrs = self.controller_cls._get_attrs_desc("admin")
elif api_permission.can():
- attrs = self.controller_cls._get_attrs_desc('api', right)
+ attrs = self.controller_cls._get_attrs_desc("api", right)
else:
- attrs = self.controller_cls._get_attrs_desc('base', right)
+ 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])
+ 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
+ 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)
+ args = self.reqparse_args(right="write", default=False)
if not args:
raise BadRequest()
- return self.controller.update({'id': obj_id}, args), 200
+ return self.controller.update({"id": obj_id}, args), 200
def delete(self, obj_id=None):
"""delete a object"""
@@ -133,19 +138,18 @@ class PyAggResourceExisting(PyAggAbstractResource):
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)
+ 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)
+ 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)
@@ -163,10 +167,11 @@ class PyAggResourceMulti(PyAggAbstractResource):
class Proxy:
pass
+
for attrs in request.json:
try:
Proxy.json = attrs
- args = self.reqparse_args('write', req=Proxy, default=False)
+ args = self.reqparse_args("write", req=Proxy, default=False)
obj = self.controller.create(**args)
results.append(obj)
except Exception as error:
@@ -188,20 +193,21 @@ class PyAggResourceMulti(PyAggAbstractResource):
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)
+ args = self.reqparse_args("write", req=Proxy, default=False)
+ result = self.controller.update({"id": obj_id}, args)
if result:
- results.append('ok')
+ results.append("ok")
else:
- results.append('nok')
+ results.append("nok")
except Exception as error:
results.append(str(error))
- if results.count('ok') == 0: # all failed => 500
+ if results.count("ok") == 0: # all failed => 500
status = 500
- elif results.count('ok') != len(results): # some failed => 206
+ elif results.count("ok") != len(results): # some failed => 206
status = 206
return results, status
@@ -212,11 +218,11 @@ class PyAggResourceMulti(PyAggAbstractResource):
for obj_id in request.json:
try:
self.controller.delete(obj_id)
- results.append('ok')
+ 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:
+ if status == 206 and results.count("ok") == 0:
status = 500
return results, status
diff --git a/newspipe/web/views/api/v2/feed.py b/newspipe/web/views/api/v2/feed.py
index a0691277..1e4fabf2 100644
--- a/newspipe/web/views/api/v2/feed.py
+++ b/newspipe/web/views/api/v2/feed.py
@@ -3,14 +3,14 @@ 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.controllers.feed import FeedController, DEFAULT_MAX_ERROR, DEFAULT_LIMIT
-from web.views.api.v2.common import PyAggAbstractResource, \
- PyAggResourceNew, \
- PyAggResourceExisting, \
- PyAggResourceMulti
+from web.views.api.v2.common import (
+ PyAggAbstractResource,
+ PyAggResourceNew,
+ PyAggResourceExisting,
+ PyAggResourceMulti,
+)
class FeedNewAPI(PyAggResourceNew):
@@ -27,21 +27,21 @@ class FeedsAPI(PyAggResourceMulti):
class FetchableFeedAPI(PyAggAbstractResource):
controller_cls = FeedController
- attrs = {'max_error': {'type': int, 'default': DEFAULT_MAX_ERROR},
- 'limit': {'type': int, 'default': DEFAULT_LIMIT}}
+ 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)]
+ 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')
+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/newspipe/web/views/api/v3/__init__.py b/newspipe/web/views/api/v3/__init__.py
index 76aa1f19..5066b0b6 100644
--- a/newspipe/web/views/api/v3/__init__.py
+++ b/newspipe/web/views/api/v3/__init__.py
@@ -1,3 +1,3 @@
from web.views.api.v3 import article
-__all__ = ['article']
+__all__ = ["article"]
diff --git a/newspipe/web/views/api/v3/article.py b/newspipe/web/views/api/v3/article.py
index 4cf35648..04d19552 100644
--- a/newspipe/web/views/api/v3/article.py
+++ b/newspipe/web/views/api/v3/article.py
@@ -35,6 +35,7 @@ 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.
"""
@@ -43,7 +44,7 @@ class ArticleProcessor(AbstractProcessor):
try:
article = ArticleController(current_user.id).get(id=instance_id)
except NotFound:
- raise ProcessingException(description='No such article.', code=404)
+ raise ProcessingException(description="No such article.", code=404)
self.is_authorized(current_user, article)
def post_preprocessor(self, data=None, **kw):
@@ -52,7 +53,7 @@ class ArticleProcessor(AbstractProcessor):
try:
feed = FeedController(current_user.id).get(id=data["feed_id"])
except NotFound:
- raise ProcessingException(description='No such feed.', code=404)
+ raise ProcessingException(description="No such feed.", code=404)
self.is_authorized(current_user, feed)
data["category_id"] = feed.category_id
@@ -61,24 +62,23 @@ class ArticleProcessor(AbstractProcessor):
try:
article = ArticleController(current_user.id).get(id=instance_id)
except NotFound:
- raise ProcessingException(description='No such article.', code=404)
+ 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]))
+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/newspipe/web/views/api/v3/common.py b/newspipe/web/views/api/v3/common.py
index d5e94a3f..5467ee30 100644
--- a/newspipe/web/views/api/v3/common.py
+++ b/newspipe/web/views/api/v3/common.py
@@ -33,7 +33,8 @@ from werkzeug.exceptions import NotFound
from web.controllers import ArticleController, UserController
from web.views.common import login_user_bundle
-url_prefix = '/api/v3'
+url_prefix = "/api/v3"
+
def auth_func(*args, **kw):
if request.authorization:
@@ -41,24 +42,23 @@ def auth_func(*args, **kw):
try:
user = ucontr.get(nickname=request.authorization.username)
except NotFound:
- raise ProcessingException("Couldn't authenticate your user",
- code=401)
+ 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)
+ 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)
+ raise ProcessingException(description="Not authenticated!", code=401)
+
-class AbstractProcessor():
+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)
+ 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
@@ -69,13 +69,11 @@ class AbstractProcessor():
"""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)
+ 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"] = []
search_params["filters"].append(filt)
@@ -95,13 +93,11 @@ class AbstractProcessor():
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)
+ 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"] = []
search_params["filters"].append(filt)
diff --git a/newspipe/web/views/api/v3/feed.py b/newspipe/web/views/api/v3/feed.py
index 2cbbafd9..9d2c9180 100644
--- a/newspipe/web/views/api/v3/feed.py
+++ b/newspipe/web/views/api/v3/feed.py
@@ -33,6 +33,7 @@ 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.
"""
@@ -43,16 +44,19 @@ class FeedProcessor(AbstractProcessor):
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]))
+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/newspipe/web/views/article.py b/newspipe/web/views/article.py
index bf39795d..ba66726e 100644
--- a/newspipe/web/views/article.py
+++ b/newspipe/web/views/article.py
@@ -1,6 +1,14 @@
from datetime import datetime, timedelta
-from flask import (Blueprint, g, render_template, redirect,
- flash, url_for, make_response, request)
+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
@@ -9,25 +17,24 @@ 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.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')
+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'])
+@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})
+ contr.update({"id": article.id}, {"readed": True})
return redirect(article.link)
-@article_bp.route('/<int:article_id>', methods=['GET'])
+@article_bp.route("/<int:article_id>", methods=["GET"])
@login_required
@etag_match
def article(article_id=None):
@@ -35,11 +42,12 @@ 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)
+ return render_template(
+ "article.html", head_titles=[clear_string(article.title)], article=article
+ )
-@article_bp.route('/public/<int:article_id>', methods=['GET'])
+
+@article_bp.route("/public/<int:article_id>", methods=["GET"])
@etag_match
def article_pub(article_id=None):
"""
@@ -48,13 +56,13 @@ def article_pub(article_id=None):
"""
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)
+ 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'])
+@article_bp.route("/like/<int:article_id>", methods=["GET"])
@login_required
def like(article_id=None):
"""
@@ -62,80 +70,84 @@ def like(article_id=None):
"""
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})
+ 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'])
+@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'))
+ 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'])
+@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)
+ 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'])
+@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):
+def mark_as(new_value="read", feed_id=None, article_id=None):
"""
Mark all unreaded articles as read.
"""
- readed = new_value == 'read'
+ readed = new_value == "read"
art_contr = ArticleController(current_user.id)
- filters = {'readed': not readed}
+ filters = {"readed": not readed}
if feed_id is not None:
- filters['feed_id'] = feed_id
- message = 'Feed marked as %s.'
+ 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.'
+ filters["id"] = article_id
+ message = "Article marked as %s."
else:
- message = 'All article marked as %s.'
+ message = "All article marked as %s."
art_contr.update(filters, {"readed": readed})
- flash(gettext(message % new_value), 'info')
+ flash(gettext(message % new_value), "info")
if readed:
return redirect(redirect_url())
- return redirect('home')
+ return redirect("home")
-@articles_bp.route('/expire_articles', methods=['GET'])
+@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)))
+ 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})
+ 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')
+ flash(gettext("%(count)d articles deleted", count=count), "info")
return redirect(redirect_url())
-@articles_bp.route('/export', methods=['GET'])
+@articles_bp.route("/export", methods=["GET"])
@login_required
def export():
"""
@@ -145,10 +157,9 @@ def export():
try:
json_result = export_json(user)
except Exception as e:
- flash(gettext("Error when exporting articles."), 'danger')
+ 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'
+ response.mimetype = "application/json"
+ response.headers["Content-Disposition"] = "attachment; filename=account.json"
return response
diff --git a/newspipe/web/views/bookmark.py b/newspipe/web/views/bookmark.py
index 21d832d2..bceda631 100644
--- a/newspipe/web/views/bookmark.py
+++ b/newspipe/web/views/bookmark.py
@@ -1,5 +1,5 @@
#! /usr/bin/env python
-#-*- coding: utf-8 -*-
+# -*- coding: utf-8 -*-
# Newspipe - A Web based news aggregator.
# Copyright (C) 2010-2017 Cédric Bonhomme - https://www.cedricbonhomme.org
@@ -30,8 +30,15 @@ import logging
import datetime
from werkzeug.exceptions import BadRequest
-from flask import Blueprint, render_template, flash, \
- redirect, request, url_for, make_response
+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
@@ -46,93 +53,105 @@ 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 = 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'):
+@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)
+ tag = request.args.get("tag", None)
if tag:
- filters['tags_proxy__contains'] = tag
- query = request.args.get('query', None)
+ 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}
+ 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':
+ if status == "public":
# load public bookmarks only
- filters['shared'] = True
- elif status == 'private':
+ filters["shared"] = True
+ elif status == "private":
# load private bookmarks only
- filters['shared'] = False
+ filters["shared"] = False
else:
# no filter: load shared and public bookmarks
pass
- if status == 'unread':
- filters['to_read'] = True
+ 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
+ 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'))
+ bookmarks = BookmarkController(user_id).read(**filters).order_by(desc("time"))
- #tag_contr = BookmarkTagController(user_id)
- #tag_contr.read().join(bookmarks).all()
+ # 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'])
+ 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())
+ 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')
+ 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)
+ 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'])
+@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."
@@ -141,116 +160,131 @@ def process_form(bookmark_id=None):
tag_contr = BookmarkTagController(current_user.id)
if not form.validate():
- return render_template('edit_bookmark.html', form=form)
+ return render_template("edit_bookmark.html", form=form)
- if form.title.data == '':
+ 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}
+ 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)
+ 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))
+ 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)
+ 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_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'])
+@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_'))
+ 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'])
+@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')
+ flash(gettext("Bookmarks successfully deleted."), "success")
return redirect(redirect_url())
-@bookmark_bp.route('/bookmarklet', methods=['GET', 'POST'])
+@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)
+ 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)
+ 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()
+ 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))
+ 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}
+ 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))
+ flash(gettext("Bookmark successfully created."), "success")
+ return redirect(url_for("bookmark.form", bookmark_id=new_bookmark.id))
-@bookmark_bp.route('/import_pinboard', methods=['POST'])
+@bookmark_bp.route("/import_pinboard", methods=["POST"])
@login_required
def import_pinboard():
- bookmarks = request.files.get('jsonfile', None)
+ 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')
+ flash(
+ gettext(
+ "%(nb_bookmarks)s bookmarks successfully imported.",
+ nb_bookmarks=nb_bookmarks,
+ ),
+ "success",
+ )
except Exception as e:
- flash(gettext('Error when importing bookmarks.'), 'error')
+ flash(gettext("Error when importing bookmarks."), "error")
return redirect(redirect_url())
-@bookmarks_bp.route('/export', methods=['GET'])
+@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'
+ response.mimetype = "application/json"
+ response.headers[
+ "Content-Disposition"
+ ] = "attachment; filename=newspipe_bookmarks_export.json"
return response
diff --git a/newspipe/web/views/category.py b/newspipe/web/views/category.py
index 138561dd..1c897058 100644
--- a/newspipe/web/views/category.py
+++ b/newspipe/web/views/category.py
@@ -5,82 +5,105 @@ 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
+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 = Blueprint("categories", __name__, url_prefix="/categories")
+category_bp = Blueprint("category", __name__, url_prefix="/category")
-@categories_bp.route('/', methods=['GET'])
+@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())
+ 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'])
+@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())
+ 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')
+ 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))
+ 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'])
+@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')
+ 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'])
+@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)
+ 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))
+ return redirect(url_for("category.form", category_id=existing_cats[0].id))
# Edit an existing category
- category_attr = {'name': form.name.data}
+ 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))
+ 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')
+ 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))
+ return redirect(url_for("category.form", category_id=new_category.id))
diff --git a/newspipe/web/views/common.py b/newspipe/web/views/common.py
index e422fd57..c2d8e2df 100644
--- a/newspipe/web/views/common.py
+++ b/newspipe/web/views/common.py
@@ -3,13 +3,18 @@ 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 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_role = RoleNeed("admin")
+api_role = RoleNeed("api")
admin_permission = Permission(admin_role)
api_permission = Permission(api_role)
@@ -17,21 +22,23 @@ api_permission = Permission(api_role)
def scoped_default_handler():
if admin_permission.can():
- role = 'admin'
+ role = "admin"
elif api_permission.can():
- role = 'api'
+ role = "api"
else:
- role = 'user'
+ 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
@@ -40,8 +47,12 @@ def jsonify(func):
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 Response(
+ json.dumps(result, default=scoped_default_handler()),
+ mimetype="application/json",
+ status=status_code,
+ )
+
return wrapper
@@ -49,5 +60,4 @@ 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()})
+ UserController(user.id).update({"id": user.id}, {"last_seen": datetime.utcnow()})
diff --git a/newspipe/web/views/feed.py b/newspipe/web/views/feed.py
index b98a005a..592e3cbf 100644
--- a/newspipe/web/views/feed.py
+++ b/newspipe/web/views/feed.py
@@ -4,8 +4,15 @@ 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 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
@@ -15,24 +22,30 @@ 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)
+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 = Blueprint("feeds", __name__, url_prefix="/feeds")
+feed_bp = Blueprint("feed", __name__, url_prefix="/feed")
-@feeds_bp.route('/', methods=['GET'])
+@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())
+ 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):
@@ -42,15 +55,19 @@ def feed_view(feed_id=None, user_id=None):
if feed.category_id:
category = CategoryController(user_id).get(id=feed.category_id)
filters = {}
- filters['feed_id'] = feed_id
+ 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)
+ 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:
@@ -65,17 +82,22 @@ def feed_view(feed_id=None, user_id=None):
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'])
+ 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):
@@ -83,7 +105,7 @@ def feed(feed_id=None):
return feed_view(feed_id, current_user.id)
-@feed_bp.route('/public/<int:feed_id>', methods=['GET'])
+@feed_bp.route("/public/<int:feed_id>", methods=["GET"])
@etag_match
def feed_pub(feed_id=None):
"""
@@ -92,90 +114,97 @@ def feed_pub(feed_id=None):
"""
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 render_template("errors/404.html"), 404
return feed_view(feed_id, None)
-@feed_bp.route('/delete/<feed_id>', methods=['GET'])
+@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'))
+ 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'])
+@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_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'])
+@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)
+ 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}))
+ 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))
+ 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'))
+ 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')
+ 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')
+ 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))
+ 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'])
+@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}
+ readed = action == "read"
+ filters = {"readed__ne": readed}
- nb_days = request.args.get('nb_days', 0, type=int)
+ nb_days = request.args.get("nb_days", 0, type=int)
if nb_days != 0:
- filters['date__lt'] = datetime.now() - timedelta(days=nb_days)
+ 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'))
+ 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'])
+@feed_bp.route("/create", methods=["GET"])
+@feed_bp.route("/edit/<int:feed_id>", methods=["GET"])
@login_required
@etag_match
def form(feed_id=None):
@@ -185,22 +214,28 @@ def form(feed_id=None):
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)
+ 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')
+ 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'])
+ 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()
@@ -208,58 +243,68 @@ def process_form(feed_id=None):
form.set_category_choices(CategoryController(current_user.id).read())
if not form.validate():
- return render_template('edit_feed.html', form=form)
+ 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))
+ 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
+ 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))
+ 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')
+ 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')
+ flash(gettext("Downloading articles for the new feed..."), "info")
- return redirect(url_for('feed.form', feed_id=new_feed.id))
+ return redirect(url_for("feed.form", feed_id=new_feed.id))
-@feeds_bp.route('/inactives', methods=['GET'])
+@feeds_bp.route("/inactives", methods=["GET"])
@login_required
def inactives():
"""
List of inactive feeds.
"""
- nb_days = int(request.args.get('nb_days', 365))
+ 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)
+ return render_template("inactives.html", inactives=inactives, nb_days=nb_days)
-@feed_bp.route('/duplicates/<int:feed_id>', methods=['GET'])
+@feed_bp.route("/duplicates/<int:feed_id>", methods=["GET"])
@login_required
def duplicates(feed_id):
"""
@@ -267,40 +312,44 @@ def duplicates(feed_id):
"""
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)
+ 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'])
+@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'
+ 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
+ filter["enabled"] = True
if not include_private:
- filter['private'] = False
+ filter["private"] = False
if not include_exceeded_error_count:
- filter['error_count__lt'] = conf.DEFAULT_MAX_ERROR
+ 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'
+ 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/newspipe/web/views/home.py b/newspipe/web/views/home.py
index dc7a361a..1f51c55c 100644
--- a/newspipe/web/views/home.py
+++ b/newspipe/web/views/home.py
@@ -2,8 +2,7 @@ import pytz
import logging
from datetime import datetime
-from flask import current_app, render_template, \
- request, flash, url_for, redirect
+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
@@ -14,107 +13,128 @@ 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
+from web.controllers import FeedController, ArticleController, CategoryController
localize = pytz.utc.localize
logger = logging.getLogger(__name__)
-@current_app.route('/')
+@current_app.route("/")
@login_required
@etag_match
def home():
- return render_template('home.html', cdn=conf.CDN_ADDRESS)
+ return render_template("home.html", cdn=conf.CDN_ADDRESS)
-@current_app.route('/menu')
+@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 = {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'] = []
+ 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)
+ 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'] = []
+ 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())}
+ 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')
+ query = in_dict.get("query")
if query:
- search_title = in_dict.get('search_title') == 'true'
- search_content = in_dict.get('search_content') == 'true'
+ search_title = in_dict.get("search_title") == "true"
+ search_content = in_dict.get("search_content") == "true"
if search_title:
- filters['title__ilike'] = "%%%s%%" % query
+ filters["title__ilike"] = "%%%s%%" % query
if search_content:
- filters['content__ilike'] = "%%%s%%" % query
+ filters["content__ilike"] = "%%%s%%" % query
if len(filters) == 0:
- filters['title__ilike'] = "%%%s%%" % query
+ 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
+ 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')
+ 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():
@@ -124,8 +144,8 @@ def get_middle_panel():
return _articles_to_json(articles)
-@current_app.route('/getart/<int:article_id>')
-@current_app.route('/getart/<int:article_id>/<parse>')
+@current_app.route("/getart/<int:article_id>")
+@current_app.route("/getart/<int:article_id>/<parse>")
@login_required
@etag_match
@jsonify
@@ -134,28 +154,29 @@ def get_article(article_id, parse=False):
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
+ 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)
+ 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'])
+@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})
+ acontr.update(filters, {"readed": True})
return processed_articles
-@current_app.route('/fetch', methods=['GET'])
-@current_app.route('/fetch/<int:feed_id>', methods=['GET'])
+@current_app.route("/fetch", methods=["GET"])
+@current_app.route("/fetch/<int:feed_id>", methods=["GET"])
@login_required
def fetch(feed_id=None):
"""
@@ -166,6 +187,11 @@ def fetch(feed_id=None):
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")
+ flash(
+ gettext(
+ "The manual retrieving of news is only available "
+ + "for administrator, on the Heroku platform."
+ ),
+ "info",
+ )
return redirect(redirect_url())
diff --git a/newspipe/web/views/icon.py b/newspipe/web/views/icon.py
index 64e54cab..e1de6402 100644
--- a/newspipe/web/views/icon.py
+++ b/newspipe/web/views/icon.py
@@ -3,13 +3,12 @@ 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 = Blueprint("icon", __name__, url_prefix="/icon")
-@icon_bp.route('/', methods=['GET'])
+@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}
+ 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/newspipe/web/views/session_mgmt.py b/newspipe/web/views/session_mgmt.py
index 0db76115..809825d3 100644
--- a/newspipe/web/views/session_mgmt.py
+++ b/newspipe/web/views/session_mgmt.py
@@ -4,14 +4,25 @@ 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 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)
+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
@@ -24,9 +35,9 @@ Principal(current_app)
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'
+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__)
@@ -47,67 +58,77 @@ def on_identity_loaded(sender, identity):
@login_manager.user_loader
def load_user(user_id):
- return UserController(user_id, ignore_context=True).get(
- id=user_id, is_active=True)
+ 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()})
+ {"id": current_user.id}, {"last_seen": datetime.utcnow()}
+ )
+
-@current_app.route('/login', methods=['GET', 'POST'])
+@current_app.route("/login", methods=["GET", "POST"])
def login():
if current_user.is_authenticated:
- return redirect(url_for('home'))
+ 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)
+ return form.redirect("home")
+ return render_template("login.html", form=form)
-@current_app.route('/logout')
+@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'):
+ 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'))
+ return redirect(url_for("login"))
-@current_app.route('/signup', methods=['GET', 'POST'])
+@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'))
+ flash(gettext("Self-registration is disabled."), "warning")
+ return redirect(url_for("home"))
if current_user.is_authenticated:
- return redirect(url_for('home'))
+ 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))
+ 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)
+ 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/newspipe/web/views/user.py b/newspipe/web/views/user.py
index 24b73a60..10974947 100644
--- a/newspipe/web/views/user.py
+++ b/newspipe/web/views/user.py
@@ -1,8 +1,7 @@
import string
import random
from datetime import datetime, timedelta
-from flask import (Blueprint, g, render_template, redirect,
- flash, url_for, request)
+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
@@ -12,39 +11,47 @@ 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.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')
+users_bp = Blueprint("users", __name__, url_prefix="/users")
+user_bp = Blueprint("user", __name__, url_prefix="/user")
-@user_bp.route('/<string:nickname>', methods=['GET'])
+@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))
+ 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'))
+ flash(gettext("You must set your profile to public."), "info")
+ return redirect(url_for("user.profile"))
filters = {}
- filters['private'] = False
+ filters["private"] = False
if category_id:
- filters['category_id'] = 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)
+ 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'])
+@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).
@@ -53,76 +60,80 @@ def user_stream(per_page, nickname=None):
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'))
+ 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_id = int(request.args.get("category_id", 0))
category = CategoryController().read(id=category_id).first()
# Load the public feeds
filters = {}
- filters['private'] = False
+ filters["private"] = False
if category_id:
- filters['category_id'] = 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]
+ filters["feed_id__in"] = [feed.id for feed in feeds]
if category:
- filters['category_id'] = category_id
+ 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'])
+ 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):
+ if request.method == "POST":
+ if None != request.files.get("opmlfile", None):
# Import an OPML file
- data = request.files.get('opmlfile', None)
+ data = request.files.get("opmlfile", None)
if not misc_utils.allowed_file(data.filename):
- flash(gettext('File not allowed.'), 'danger')
+ 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')
+ 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):
+ 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)
+ data = request.files.get("jsonfile", None)
if not misc_utils.allowed_file(data.filename):
- flash(gettext('File not allowed.'), 'danger')
+ flash(gettext("File not allowed."), "danger")
else:
try:
nb = import_json(current_user.nickname, data.read())
- flash(gettext('Account imported.'), "success")
+ flash(gettext("Account imported."), "success")
except:
- flash(gettext("Impossible to import the account."),
- "danger")
+ flash(gettext("Impossible to import the account."), "danger")
else:
- flash(gettext('File not allowed.'), 'danger')
+ flash(gettext("File not allowed."), "danger")
nb_feeds = FeedController(current_user.id).read().count()
art_contr = ArticleController(current_user.id)
@@ -130,14 +141,18 @@ def management():
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'])
+ 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():
"""
@@ -147,44 +162,54 @@ def profile():
user = user_contr.get(id=current_user.id)
form = ProfileForm()
- if request.method == 'POST':
+ 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})
+ 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')
+ 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'))
+ 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)
+ return render_template("profile.html", user=user, form=form)
- if request.method == 'GET':
+ if request.method == "GET":
form = ProfileForm(obj=user)
- return render_template('profile.html', user=user, form=form)
+ return render_template("profile.html", user=user, form=form)
-@user_bp.route('/delete_account', methods=['GET'])
+@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'))
+ flash(gettext("Your account has been deleted."), "success")
+ return redirect(url_for("login"))
-@user_bp.route('/confirm_account/<string:token>', methods=['GET'])
+@user_bp.route("/confirm_account/<string:token>", methods=["GET"])
def confirm_account(token=None):
"""
Confirm the account of a user.
@@ -196,8 +221,8 @@ def confirm_account(token=None):
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')
+ 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'))
+ flash(gettext("Impossible to confirm this account."), "danger")
+ return redirect(url_for("login"))
diff --git a/newspipe/web/views/views.py b/newspipe/web/views/views.py
index d587bd09..1fde12c7 100644
--- a/newspipe/web/views/views.py
+++ b/newspipe/web/views/views.py
@@ -2,8 +2,7 @@ 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 import request, render_template, flash, url_for, redirect, current_app
from flask_babel import gettext
from sqlalchemy import desc
@@ -20,26 +19,26 @@ logger = logging.getLogger(__name__)
def authentication_required(error):
if API_ROOT in request.url:
return error
- flash(gettext('Authentication required.'), 'info')
- return redirect(url_for('login'))
+ 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'))
+ 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
+ return render_template("errors/404.html"), 404
@current_app.errorhandler(500)
def internal_server_error(error):
- return render_template('errors/500.html'), 500
+ return render_template("errors/500.html"), 500
@current_app.errorhandler(AssertionError)
@@ -47,7 +46,7 @@ def handle_sqlalchemy_assertion_error(error):
return error.args[0], 400
-@current_app.route('/popular', methods=['GET'])
+@current_app.route("/popular", methods=["GET"])
@etag_match
def popular():
"""
@@ -57,11 +56,12 @@ def popular():
# 'not_created_before'
# ie: not_added_before = date_last_added_feed - nb_days
try:
- nb_days = int(request.args.get('nb_days', 365))
+ 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()
+ 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:
@@ -69,25 +69,27 @@ def popular():
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
+ 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)
+ 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'])
+@current_app.route("/about", methods=["GET"])
@etag_match
def about():
- return render_template('about.html', contact=ADMIN_EMAIL)
+ return render_template("about.html", contact=ADMIN_EMAIL)
-@current_app.route('/about/more', methods=['GET'])
+
+@current_app.route("/about/more", methods=["GET"])
@etag_match
def about_more():
- return render_template('about_more.html',
- newspipe_version=__version__.split()[1],
- registration=[conf.SELF_REGISTRATION and 'Open' or 'Closed'][0],
- python_version="{}.{}.{}".format(*sys.version_info[:3]),
- nb_users=UserController().read().count())
+ return render_template(
+ "about_more.html",
+ newspipe_version=__version__.split()[1],
+ registration=[conf.SELF_REGISTRATION and "Open" or "Closed"][0],
+ python_version="{}.{}.{}".format(*sys.version_info[:3]),
+ nb_users=UserController().read().count(),
+ )
bgstack15