aboutsummaryrefslogtreecommitdiff
path: root/src/web/controllers
diff options
context:
space:
mode:
authorCédric Bonhomme <cedric@cedricbonhomme.org>2020-02-26 11:27:31 +0100
committerCédric Bonhomme <cedric@cedricbonhomme.org>2020-02-26 11:27:31 +0100
commit62b3afeeedfe054345f86093e2d243e956c1e3c9 (patch)
treebbd58f5c8c07f5d87b1c1cca73fa1d5af6178f48 /src/web/controllers
parentUpdated Python dependencies. (diff)
downloadnewspipe-62b3afeeedfe054345f86093e2d243e956c1e3c9.tar.gz
newspipe-62b3afeeedfe054345f86093e2d243e956c1e3c9.tar.bz2
newspipe-62b3afeeedfe054345f86093e2d243e956c1e3c9.zip
The project is now using Poetry.
Diffstat (limited to 'src/web/controllers')
-rw-r--r--src/web/controllers/__init__.py12
-rw-r--r--src/web/controllers/abstract.py161
-rw-r--r--src/web/controllers/article.py87
-rw-r--r--src/web/controllers/bookmark.py32
-rw-r--r--src/web/controllers/category.py12
-rw-r--r--src/web/controllers/feed.py98
-rw-r--r--src/web/controllers/icon.py23
-rw-r--r--src/web/controllers/tag.py22
-rw-r--r--src/web/controllers/user.py28
9 files changed, 0 insertions, 475 deletions
diff --git a/src/web/controllers/__init__.py b/src/web/controllers/__init__.py
deleted file mode 100644
index 5fbc2619..00000000
--- a/src/web/controllers/__init__.py
+++ /dev/null
@@ -1,12 +0,0 @@
-from .feed import FeedController
-from .category import CategoryController
-from .article import ArticleController
-from .user import UserController
-from .icon import IconController
-from .bookmark import BookmarkController
-from .tag import BookmarkTagController
-
-
-__all__ = ['FeedController', 'CategoryController', 'ArticleController',
- 'UserController', 'IconController', 'BookmarkController',
- 'BookmarkTagController']
diff --git a/src/web/controllers/abstract.py b/src/web/controllers/abstract.py
deleted file mode 100644
index 764ff305..00000000
--- a/src/web/controllers/abstract.py
+++ /dev/null
@@ -1,161 +0,0 @@
-import logging
-import dateutil.parser
-from bootstrap import db
-from datetime import datetime
-from collections import defaultdict
-from sqlalchemy import or_, func
-from werkzeug.exceptions import Forbidden, NotFound
-
-logger = logging.getLogger(__name__)
-
-
-class AbstractController:
- _db_cls = None # reference to the database class
- _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
- filter objects in database on their denormalized "user_id" field
- (or "id" field for users).
- Should no user_id be provided, the Controller won't apply any filter
- allowing for a kind of "super user" mode.
- """
- try:
- self.user_id = int(user_id)
- except TypeError:
- self.user_id = user_id
-
- def _to_filters(self, **filters):
- """
- Will translate filters to sqlalchemy filter.
- This method will also apply user_id restriction if available.
-
- each parameters of the function is treated as an equality unless the
- name of the parameter ends with either "__gt", "__lt", "__ge", "__le",
- "__ne", "__in" ir "__like".
- """
- db_filters = set()
- for key, value in filters.items():
- if key == '__or__':
- db_filters.add(or_(*self._to_filters(**value)))
- elif key.endswith('__gt'):
- db_filters.add(getattr(self._db_cls, key[:-4]) > value)
- elif key.endswith('__lt'):
- db_filters.add(getattr(self._db_cls, key[:-4]) < value)
- elif key.endswith('__ge'):
- db_filters.add(getattr(self._db_cls, key[:-4]) >= value)
- elif key.endswith('__le'):
- db_filters.add(getattr(self._db_cls, key[:-4]) <= value)
- elif key.endswith('__ne'):
- db_filters.add(getattr(self._db_cls, key[:-4]) != value)
- elif key.endswith('__in'):
- db_filters.add(getattr(self._db_cls, key[:-4]).in_(value))
- elif key.endswith('__contains'):
- db_filters.add(getattr(self._db_cls, key[:-10]).contains(value))
- elif key.endswith('__like'):
- db_filters.add(getattr(self._db_cls, key[:-6]).like(value))
- 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)
- return db_filters
-
- def _get(self, **filters):
- """ Will add the current user id if that one is not none (in which case
- the decision has been made in the code that the query shouldn't be user
- 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:
- filters[self._user_id_key] = self.user_id
- return self._db_cls.query.filter(*self._to_filters(**filters))
-
- def get(self, **filters):
- """Will return one single objects corresponding to filters"""
- 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)})
- if not obj:
- 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"
-
- obj = self._db_cls(**attrs)
- db.session.add(obj)
- db.session.flush()
- db.session.commit()
- return obj
-
- def read(self, **filters):
- return self._get(**filters)
-
- def update(self, filters, attrs, return_objs=False, commit=True):
- assert attrs, "attributes to update must not be empty"
- result = self._get(**filters).update(attrs, synchronize_session=False)
- if commit:
- db.session.flush()
- db.session.commit()
- if return_objs:
- return self._get(**filters)
- return result
-
- def delete(self, obj_id):
- obj = self.get(id=obj_id)
- db.session.delete(obj)
- try:
- db.session.commit()
- except Exception as e:
- db.session.rollback()
- return obj
-
- def _has_right_on(self, obj):
- # 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
-
- 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())
-
- @classmethod
- def _get_attrs_desc(cls, role, right=None):
- result = defaultdict(dict)
- 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))()
- 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
- except NotImplementedError:
- if db_col.default:
- 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)
- elif db_col.default:
- result[column]['default'] = db_col.default.arg
- return result
diff --git a/src/web/controllers/article.py b/src/web/controllers/article.py
deleted file mode 100644
index d7058229..00000000
--- a/src/web/controllers/article.py
+++ /dev/null
@@ -1,87 +0,0 @@
-import re
-import logging
-import sqlalchemy
-from sqlalchemy import func
-from collections import Counter
-
-from bootstrap import db
-from .abstract import AbstractController
-from lib.article_utils import process_filters
-from web.controllers import CategoryController, FeedController
-from web.models import Article
-
-logger = logging.getLogger(__name__)
-
-
-class ArticleController(AbstractController):
- _db_cls = Article
-
- def challenge(self, ids):
- """Will return each id that wasn't found in the database."""
- for id_ in ids:
- if self.read(**id_).first():
- continue
- yield id_
-
- def count_by_category(self, **filters):
- return self._count_by(Article.category_id, filters)
-
- def count_by_feed(self, **filters):
- 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())
-
- 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
-
- skipped, read, liked = process_filters(feed.filters, attrs)
- if skipped:
- return None
- article = super().create(**attrs)
- 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'])
- 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
- return super().update(filters, attrs)
-
- def get_history(self, year=None, month=None):
- """
- Sort articles by year and month.
- """
- articles_counter = Counter()
- articles = self.read()
- if year is not None:
- articles = articles.filter(
- sqlalchemy.extract('year', Article.date) == year)
- if month is not None:
- articles = articles.filter(
- sqlalchemy.extract('month', Article.date) == month)
- for article in articles.all():
- if year is not None:
- articles_counter[article.date.month] += 1
- else:
- articles_counter[article.date.year] += 1
- 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())
diff --git a/src/web/controllers/bookmark.py b/src/web/controllers/bookmark.py
deleted file mode 100644
index b5413243..00000000
--- a/src/web/controllers/bookmark.py
+++ /dev/null
@@ -1,32 +0,0 @@
-import logging
-import itertools
-from datetime import datetime, timedelta
-
-from bootstrap import db
-from web.models import Bookmark
-from .abstract import AbstractController
-from .tag import BookmarkTagController
-
-logger = logging.getLogger(__name__)
-
-
-class BookmarkController(AbstractController):
- _db_cls = Bookmark
-
- def count_by_href(self, **filters):
- return self._count_by(Bookmark.href, filters)
-
- def update(self, filters, attrs):
- BookmarkTagController(self.user_id) \
- .read(**{'bookmark_id': filters["id"]}) \
- .delete()
-
- 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']
- return super().update(filters, attrs)
diff --git a/src/web/controllers/category.py b/src/web/controllers/category.py
deleted file mode 100644
index fef5ca81..00000000
--- a/src/web/controllers/category.py
+++ /dev/null
@@ -1,12 +0,0 @@
-from .abstract import AbstractController
-from web.models import Category
-from .feed import FeedController
-
-
-class CategoryController(AbstractController):
- _db_cls = Category
-
- def delete(self, obj_id):
- FeedController(self.user_id).update({'category_id': obj_id},
- {'category_id': None})
- return super().delete(obj_id)
diff --git a/src/web/controllers/feed.py b/src/web/controllers/feed.py
deleted file mode 100644
index d75cd994..00000000
--- a/src/web/controllers/feed.py
+++ /dev/null
@@ -1,98 +0,0 @@
-import logging
-import itertools
-from datetime import datetime, timedelta
-
-import conf
-from .abstract import AbstractController
-from .icon import IconController
-from web.models import User, Feed
-from lib.utils import clear_string
-
-logger = logging.getLogger(__name__)
-DEFAULT_LIMIT = 5
-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_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})
- return feeds
-
- def get_duplicates(self, feed_id):
- """
- Compare a list of documents by pair.
- Pairs of duplicates are sorted by "retrieved date".
- """
- feed = self.get(id=feed_id)
- 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 pair[0].retrieved_date < pair[1].retrieved_date:
- duplicates.append((pair[0], pair[1]))
- else:
- duplicates.append((pair[1], pair[0]))
- return feed, duplicates
-
- def get_inactives(self, nb_days):
- today = datetime.now()
- inactives = []
- for feed in self.read():
- try:
- last_post = feed.articles[0].date
- except IndexError:
- continue
- except Exception as e:
- logger.exception(e)
- continue
- elapsed = today - last_post
- if elapsed > timedelta(days=nb_days):
- inactives.append((feed, elapsed))
- inactives.sort(key=lambda tup: tup[1], reverse=True)
- return inactives
-
- def count_by_category(self, **filters):
- return self._count_by(Feed.category_id, filters)
-
- def count_by_link(self, **filters):
- return self._count_by(Feed.link, filters)
-
- def _ensure_icon(self, attrs):
- 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']})
-
- def create(self, **attrs):
- self._ensure_icon(attrs)
- return super().create(**attrs)
-
- 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:
- art_contr = ArticleController(self.user_id)
- for feed in self.read(**filters):
- art_contr.update({'feed_id': feed.id},
- {'category_id': attrs['category_id']})
- return super().update(filters, attrs)
diff --git a/src/web/controllers/icon.py b/src/web/controllers/icon.py
deleted file mode 100644
index 07c4a4ef..00000000
--- a/src/web/controllers/icon.py
+++ /dev/null
@@ -1,23 +0,0 @@
-import base64
-import requests
-from web.models import Icon
-from .abstract import AbstractController
-
-
-class IconController(AbstractController):
- _db_cls = Icon
- _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')})
- return attrs
-
- def create(self, **attrs):
- return super().create(**self._build_from_url(attrs))
-
- def update(self, filters, attrs):
- return super().update(filters, self._build_from_url(attrs))
diff --git a/src/web/controllers/tag.py b/src/web/controllers/tag.py
deleted file mode 100644
index 35fd5613..00000000
--- a/src/web/controllers/tag.py
+++ /dev/null
@@ -1,22 +0,0 @@
-import logging
-import itertools
-from datetime import datetime, timedelta
-
-from bootstrap import db
-from .abstract import AbstractController
-from web.models.tag import BookmarkTag
-
-logger = logging.getLogger(__name__)
-
-
-class BookmarkTagController(AbstractController):
- _db_cls = BookmarkTag
-
- def count_by_href(self, **filters):
- return self._count_by(BookmarkTag.text, filters)
-
- def create(self, **attrs):
- return super().create(**attrs)
-
- def update(self, filters, attrs):
- return super().update(filters, attrs)
diff --git a/src/web/controllers/user.py b/src/web/controllers/user.py
deleted file mode 100644
index 6ab04d44..00000000
--- a/src/web/controllers/user.py
+++ /dev/null
@@ -1,28 +0,0 @@
-import logging
-from werkzeug.security import generate_password_hash, check_password_hash
-from .abstract import AbstractController
-from web.models import User
-
-logger = logging.getLogger(__name__)
-
-
-class UserController(AbstractController):
- _db_cls = User
- _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']
-
- def check_password(self, user, password):
- return check_password_hash(user.pwdhash, password)
-
- def create(self, **attrs):
- self._handle_password(attrs)
- return super().create(**attrs)
-
- def update(self, filters, attrs):
- self._handle_password(attrs)
- return super().update(filters, attrs)
bgstack15