From 462f6d3b21558ed0a283c24e0e0332eac6ccbbb3 Mon Sep 17 00:00:00 2001 From: François Schmidts Date: Fri, 11 Sep 2015 18:28:12 +0200 Subject: base modification in model for category support --- src/web/controllers/article.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'src/web/controllers') diff --git a/src/web/controllers/article.py b/src/web/controllers/article.py index 8b6926b7..a8788f46 100644 --- a/src/web/controllers/article.py +++ b/src/web/controllers/article.py @@ -34,8 +34,7 @@ class ArticleController(AbstractController): .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)) + return dict(db.session.query(Article.user_id, func.count(Article.id)) .filter(*self._to_filters(**filters)) .group_by(Article.user_id).all()) -- cgit From 1095a049a63e4286da620b914c23411ea7a02e64 Mon Sep 17 00:00:00 2001 From: François Schmidts Date: Sat, 10 Oct 2015 12:59:32 +0200 Subject: base category creation/edition --- src/web/controllers/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'src/web/controllers') diff --git a/src/web/controllers/__init__.py b/src/web/controllers/__init__.py index ad77fa1d..a1b89ea8 100644 --- a/src/web/controllers/__init__.py +++ b/src/web/controllers/__init__.py @@ -1,8 +1,9 @@ from .feed import FeedController +from .category import CategoryController from .article import ArticleController from .user import UserController from .icon import IconController -__all__ = ['FeedController', 'ArticleController', 'UserController', - 'IconController'] +__all__ = ['FeedController', 'CategoryController', 'ArticleController', + 'UserController', 'IconController'] -- cgit From f81d231465e7a3d7b4f434f266a5a733fa45ec9b Mon Sep 17 00:00:00 2001 From: François Schmidts Date: Sun, 11 Oct 2015 01:41:20 +0200 Subject: assigning categories to feeds and articles --- src/web/controllers/article.py | 2 +- src/web/controllers/feed.py | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) (limited to 'src/web/controllers') diff --git a/src/web/controllers/article.py b/src/web/controllers/article.py index a8788f46..3d8d5c01 100644 --- a/src/web/controllers/article.py +++ b/src/web/controllers/article.py @@ -45,7 +45,7 @@ class ArticleController(AbstractController): 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 + attrs['user_id'], attrs['category_id'] = feed.user_id, feed.category_id # handling feed's filters for filter_ in feed.filters or []: diff --git a/src/web/controllers/feed.py b/src/web/controllers/feed.py index 15be8663..3b05b294 100644 --- a/src/web/controllers/feed.py +++ b/src/web/controllers/feed.py @@ -66,5 +66,12 @@ class FeedController(AbstractController): return super().create(**attrs) def update(self, filters, attrs): + from .article import ArticleController self._ensure_icon(attrs) - return super().update(filters, attrs) + result = super().update(filters, attrs) + if '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': feed.category_id}) + return result -- cgit From 5b7db9398abaacea241d9fcce7885457c562d7fa Mon Sep 17 00:00:00 2001 From: François Schmidts Date: Sun, 11 Oct 2015 12:18:07 +0200 Subject: a bit of cleaning, putting code where it belongs --- src/web/controllers/article.py | 21 +++++++++++++++++++++ src/web/controllers/feed.py | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) (limited to 'src/web/controllers') diff --git a/src/web/controllers/article.py b/src/web/controllers/article.py index 3d8d5c01..72288a09 100644 --- a/src/web/controllers/article.py +++ b/src/web/controllers/article.py @@ -1,6 +1,8 @@ import re import logging +import sqlalchemy from sqlalchemy import func +from collections import Counter from bootstrap import db from .abstract import AbstractController @@ -70,3 +72,22 @@ class ArticleController(AbstractController): attrs['link']) return super().create(**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 diff --git a/src/web/controllers/feed.py b/src/web/controllers/feed.py index 3b05b294..b76c4e42 100644 --- a/src/web/controllers/feed.py +++ b/src/web/controllers/feed.py @@ -20,12 +20,14 @@ # along with this program. If not, see . import logging +import itertools from datetime import datetime, timedelta import conf from .abstract import AbstractController from .icon import IconController from web.models import Feed +from web.lib.utils import clear_string logger = logging.getLogger(__name__) DEFAULT_LIMIT = 5 @@ -54,6 +56,37 @@ class FeedController(AbstractController): {'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, 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 + 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 _ensure_icon(self, attrs): if not attrs.get('icon_url'): return -- cgit From 5f66e6465d3822b150898de2a7fb8df39ed7fdc6 Mon Sep 17 00:00:00 2001 From: François Schmidts Date: Sun, 11 Oct 2015 23:34:33 +0200 Subject: removing misplaced stuffs from views, more controllers use --- src/web/controllers/abstract.py | 2 +- src/web/controllers/user.py | 25 +++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) (limited to 'src/web/controllers') diff --git a/src/web/controllers/abstract.py b/src/web/controllers/abstract.py index f33d241e..99d92ff3 100644 --- a/src/web/controllers/abstract.py +++ b/src/web/controllers/abstract.py @@ -84,7 +84,7 @@ class AbstractController(object): 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, \ + or self.user_id is 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: diff --git a/src/web/controllers/user.py b/src/web/controllers/user.py index 3f96b185..d8bf1fa1 100644 --- a/src/web/controllers/user.py +++ b/src/web/controllers/user.py @@ -1,3 +1,6 @@ +import random +import hashlib +from werkzeug import generate_password_hash from .abstract import AbstractController from web.models import User @@ -5,3 +8,25 @@ from web.models import User class UserController(AbstractController): _db_cls = User _user_id_key = 'id' + + def unset_activation_key(self, obj_id): + self.update({'id': obj_id}, {'activation_key': ""}) + + def set_activation_key(self, obj_id): + key = str(random.getrandbits(256)).encode("utf-8") + key = hashlib.sha512(key).hexdigest()[:86] + self.update({'id': obj_id}, {'activation_key': key}) + + 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 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) -- cgit From 7cbbcb59f4c434fbd7e74e85c90e98fadd189b65 Mon Sep 17 00:00:00 2001 From: François Schmidts Date: Mon, 12 Oct 2015 17:48:41 +0200 Subject: adding, improving UI to manage categories --- src/web/controllers/abstract.py | 13 ++++++++++--- src/web/controllers/article.py | 9 ++++----- src/web/controllers/feed.py | 8 +++++--- 3 files changed, 19 insertions(+), 11 deletions(-) (limited to 'src/web/controllers') diff --git a/src/web/controllers/abstract.py b/src/web/controllers/abstract.py index 99d92ff3..828e6a29 100644 --- a/src/web/controllers/abstract.py +++ b/src/web/controllers/abstract.py @@ -1,7 +1,7 @@ import logging from flask import g from bootstrap import db -from sqlalchemy import or_ +from sqlalchemy import or_, func from werkzeug.exceptions import Forbidden, NotFound logger = logging.getLogger(__name__) @@ -83,12 +83,12 @@ class AbstractController(object): return obj def create(self, **attrs): + 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" - 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() @@ -114,3 +114,10 @@ class AbstractController(object): 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()) diff --git a/src/web/controllers/article.py b/src/web/controllers/article.py index 72288a09..50e6757f 100644 --- a/src/web/controllers/article.py +++ b/src/web/controllers/article.py @@ -28,12 +28,11 @@ class ArticleController(AbstractController): continue yield id_ + def count_by_category(self, **filters): + return self._count_by(Article.category_id, filters) + 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()) + 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)) diff --git a/src/web/controllers/feed.py b/src/web/controllers/feed.py index b76c4e42..31a1ec41 100644 --- a/src/web/controllers/feed.py +++ b/src/web/controllers/feed.py @@ -87,6 +87,9 @@ class FeedController(AbstractController): 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 _ensure_icon(self, attrs): if not attrs.get('icon_url'): return @@ -101,10 +104,9 @@ class FeedController(AbstractController): def update(self, filters, attrs): from .article import ArticleController self._ensure_icon(attrs) - result = super().update(filters, attrs) if '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': feed.category_id}) - return result + {'category_id': attrs['category_id']}) + return super().update(filters, attrs) -- cgit From 2c0e17cb977a1e8782799b337df8b1583d019906 Mon Sep 17 00:00:00 2001 From: François Schmidts Date: Mon, 12 Oct 2015 22:36:01 +0200 Subject: bootstraping react --- src/web/controllers/category.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 src/web/controllers/category.py (limited to 'src/web/controllers') diff --git a/src/web/controllers/category.py b/src/web/controllers/category.py new file mode 100644 index 00000000..fef5ca81 --- /dev/null +++ b/src/web/controllers/category.py @@ -0,0 +1,12 @@ +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) -- cgit From 4c5415754593986d1540820d13dfa34a34ffeed6 Mon Sep 17 00:00:00 2001 From: François Schmidts Date: Fri, 29 Jan 2016 20:28:10 +0100 Subject: impact on menus when loading article --- src/web/controllers/article.py | 6 ------ 1 file changed, 6 deletions(-) (limited to 'src/web/controllers') diff --git a/src/web/controllers/article.py b/src/web/controllers/article.py index 50e6757f..bc9ef36e 100644 --- a/src/web/controllers/article.py +++ b/src/web/controllers/article.py @@ -15,12 +15,6 @@ 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: -- cgit