From b2618e9404b84cc62d4becb02436233a0d53b375 Mon Sep 17 00:00:00 2001 From: Cédric Bonhomme Date: Wed, 25 Nov 2015 22:45:43 +0100 Subject: Rfactorization. Just a start... --- pyaggr3g470r/controllers/__init__.py | 8 --- pyaggr3g470r/controllers/abstract.py | 116 ----------------------------------- pyaggr3g470r/controllers/article.py | 73 ---------------------- pyaggr3g470r/controllers/feed.py | 70 --------------------- pyaggr3g470r/controllers/icon.py | 23 ------- pyaggr3g470r/controllers/user.py | 7 --- 6 files changed, 297 deletions(-) delete mode 100644 pyaggr3g470r/controllers/__init__.py delete mode 100644 pyaggr3g470r/controllers/abstract.py delete mode 100644 pyaggr3g470r/controllers/article.py delete mode 100644 pyaggr3g470r/controllers/feed.py delete mode 100644 pyaggr3g470r/controllers/icon.py delete mode 100644 pyaggr3g470r/controllers/user.py (limited to 'pyaggr3g470r/controllers') diff --git a/pyaggr3g470r/controllers/__init__.py b/pyaggr3g470r/controllers/__init__.py deleted file mode 100644 index ad77fa1d..00000000 --- a/pyaggr3g470r/controllers/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -from .feed import FeedController -from .article import ArticleController -from .user import UserController -from .icon import IconController - - -__all__ = ['FeedController', 'ArticleController', 'UserController', - 'IconController'] diff --git a/pyaggr3g470r/controllers/abstract.py b/pyaggr3g470r/controllers/abstract.py deleted file mode 100644 index f33d241e..00000000 --- a/pyaggr3g470r/controllers/abstract.py +++ /dev/null @@ -1,116 +0,0 @@ -import logging -from flask import g -from bootstrap import db -from sqlalchemy import or_ -from werkzeug.exceptions import Forbidden, NotFound - -logger = logging.getLogger(__name__) - - -class AbstractController(object): - _db_cls = None # reference to the database class - _user_id_key = 'user_id' - - def __init__(self, user_id=None): - """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. - """ - self.user_id = user_id - try: - if self.user_id is not None \ - and self.user_id != g.user.id and not g.user.is_admin(): - self.user_id = g.user.id - except RuntimeError: # passing on out of context errors - pass - - 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('__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 - dependant) 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 self._user_id_key is None or self._user_id_key in attrs \ - or self.user_id is not None, \ - "You must provide user_id one way or another" - - if self._user_id_key is not None and self._user_id_key not in attrs: - attrs[self._user_id_key] = self.user_id - obj = self._db_cls(**attrs) - db.session.add(obj) - db.session.commit() - return obj - - def read(self, **filters): - return self._get(**filters) - - def update(self, filters, attrs): - result = self._get(**filters).update(attrs, synchronize_session=False) - db.session.commit() - return result - - def delete(self, obj_id): - obj = self.get(id=obj_id) - db.session.delete(obj) - db.session.commit() - 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 diff --git a/pyaggr3g470r/controllers/article.py b/pyaggr3g470r/controllers/article.py deleted file mode 100644 index 21b4b5e7..00000000 --- a/pyaggr3g470r/controllers/article.py +++ /dev/null @@ -1,73 +0,0 @@ -import re -import logging -from sqlalchemy import func - -from bootstrap import db -from .abstract import AbstractController -from pyaggr3g470r.controllers import FeedController -from pyaggr3g470r.models import Article - -logger = logging.getLogger(__name__) - - -class ArticleController(AbstractController): - _db_cls = Article - - def get(self, **filters): - article = super(ArticleController, self).get(**filters) - if not article.readed: - self.update({'id': article.id}, {'readed': True}) - return 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_feed(self, **filters): - if self.user_id: - filters['user_id'] = self.user_id - return dict(db.session.query(Article.feed_id, func.count(Article.id)) - .filter(*self._to_filters(**filters)) - .group_by(Article.feed_id).all()) - - 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 - 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 - attrs['user_id'] = feed.user_id - - # handling feed's filters - for filter_ in feed.filters or []: - match = False - if filter_.get('type') == 'regex': - match = re.match(filter_['pattern'], attrs.get('title', '')) - elif filter_.get('type') == 'simple match': - match = filter_['pattern'] in attrs.get('title', '') - take_action = match and filter_.get('action on') == 'match' \ - or not match and filter_.get('action on') == 'no match' - - if not take_action: - continue - - if filter_.get('action') == 'mark as read': - attrs['readed'] = True - logger.warn("article %s will be created as read", - attrs['link']) - elif filter_.get('action') == 'mark as favorite': - attrs['like'] = True - logger.warn("article %s will be created as liked", - attrs['link']) - - return super().create(**attrs) diff --git a/pyaggr3g470r/controllers/feed.py b/pyaggr3g470r/controllers/feed.py deleted file mode 100644 index 90ee6ea5..00000000 --- a/pyaggr3g470r/controllers/feed.py +++ /dev/null @@ -1,70 +0,0 @@ -#! /usr/bin/env python -# -*- coding: utf-8 -*- - -# pyAggr3g470r - A Web based news aggregator. -# Copyright (C) 2010-2015 Cédric Bonhomme - https://www.cedricbonhomme.org -# -# For more information : https://bitbucket.org/cedricbonhomme/pyaggr3g470r/ -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . - -import logging -from datetime import datetime, timedelta - -import conf -from .abstract import AbstractController -from .icon import IconController -from pyaggr3g470r.models import Feed - -logger = logging.getLogger(__name__) -DEFAULT_LIMIT = 5 -DEFAULT_REFRESH_RATE = 60 -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) - .order_by('last_retrieved') - .limit(limit)] - - def list_fetchable(self, max_error=DEFAULT_MAX_ERROR, limit=DEFAULT_LIMIT, - refresh_rate=DEFAULT_REFRESH_RATE): - now = datetime.now() - max_last = now - timedelta(minutes=refresh_rate) - 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 _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): - self._ensure_icon(attrs) - return super().update(filters, attrs) diff --git a/pyaggr3g470r/controllers/icon.py b/pyaggr3g470r/controllers/icon.py deleted file mode 100644 index 194c601c..00000000 --- a/pyaggr3g470r/controllers/icon.py +++ /dev/null @@ -1,23 +0,0 @@ -import base64 -import requests -from pyaggr3g470r.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/pyaggr3g470r/controllers/user.py b/pyaggr3g470r/controllers/user.py deleted file mode 100644 index c6c1d545..00000000 --- a/pyaggr3g470r/controllers/user.py +++ /dev/null @@ -1,7 +0,0 @@ -from .abstract import AbstractController -from pyaggr3g470r.models import User - - -class UserController(AbstractController): - _db_cls = User - _user_id_key = 'id' -- cgit