From fac4fc5a33359a69506ac3ef1bff415554f35e21 Mon Sep 17 00:00:00 2001 From: François Schmidts Date: Mon, 18 May 2015 22:44:22 +0200 Subject: some factorising --- pyaggr3g470r/static/js/articles.js | 50 +++++++++++++------------------------- 1 file changed, 17 insertions(+), 33 deletions(-) (limited to 'pyaggr3g470r') diff --git a/pyaggr3g470r/static/js/articles.js b/pyaggr3g470r/static/js/articles.js index bb31f6d8..8bceb1b0 100644 --- a/pyaggr3g470r/static/js/articles.js +++ b/pyaggr3g470r/static/js/articles.js @@ -22,6 +22,16 @@ API_ROOT = '/api/v2.0/' if (typeof jQuery === 'undefined') { throw new Error('Requires jQuery') } +function change_unread_counter(feed_id, increment) { + var new_value = parseInt($("#unread-"+feed_id).text()) + increment; + $("#unread-"+feed_id).text(new_value); + if (new_value == 0) { + $("#unread-"+feed_id).hide(); + } else { + $("#unread-"+feed_id).show(); + } +} + +function ($) { // Mark an article as read when it is opened in a new table @@ -30,17 +40,10 @@ if (typeof jQuery === 'undefined') { throw new Error('Requires jQuery') } var filter = $('#filters').attr("data-filter"); if (filter == "unread") { $(this).parent().parent().remove(); - $("#total-unread").text(parseInt($("#total-unread").text()) - 1); - if (parseInt($("#unread-"+feed_id).text()) == 1) { - $("#unread-"+feed_id).remove(); - } else { - $("#unread-"+feed_id).text(parseInt($("#unread-"+feed_id).text()) - 1); - } + change_unread_counter(feed_id, -1); } }); - - // Mark an article as read or unread. $('.readed').on('click', function() { var article_id = $(this).parent().parent().parent().attr("data-article"); @@ -55,38 +58,25 @@ if (typeof jQuery === 'undefined') { throw new Error('Requires jQuery') } if (filter == "read") { $(this).parent().parent().parent().remove(); $("#total-unread").text(parseInt($("#total-unread").text()) - 1); - $("#unread-"+feed_id).text(parseInt($("#unread-"+feed_id).text()) + 1); } else { // here, filter == "all" $(this).parent().parent().parent().children("td:nth-child(2)").css( "font-weight", "bold" ); $(this).removeClass('glyphicon-unchecked').addClass('glyphicon-check'); - $("#unread-"+feed_id).text(parseInt($("#unread-"+feed_id).text()) + 1); } + change_unread_counter(feed_id, 1); } else { - data = JSON.stringify({ - readed: true - }) + data = JSON.stringify({readed: true}) if (filter == "unread") { $(this).parent().parent().parent().remove(); - $("#total-unread").text(parseInt($("#total-unread").text()) - 1); - if (parseInt($("#unread-"+feed_id).text()) == 1) { - $("#unread-"+feed_id).remove(); - } else { - $("#unread-"+feed_id).text(parseInt($("#unread-"+feed_id).text()) - 1); - } } else { // here, filter == "all" $(this).parent().parent().parent().children("td:nth-child(2)").css( "font-weight", "normal" ); $(this).removeClass('glyphicon-check').addClass('glyphicon-unchecked'); - if (parseInt($("#unread-"+feed_id).text()) == 1) { - $("#unread-"+feed_id).remove(); - } else { - $("#unread-"+feed_id).text(parseInt($("#unread-"+feed_id).text()) - 1); - } } + change_unread_counter(feed_id, -1); } // sends the updates to the server @@ -115,15 +105,11 @@ if (typeof jQuery === 'undefined') { throw new Error('Requires jQuery') } var data; if ($(this).hasClass("glyphicon-star")) { - data = JSON.stringify({ - like: false - }) - $(this).removeClass('glyphicon-star').addClass('glyphicon-star-empty'); + data = JSON.stringify({like: false}) + $(this).removeClass('glyphicon-star').addClass('glyphicon-star-empty'); } else { - data = JSON.stringify({ - like: true - }) + data = JSON.stringify({like: true}) $(this).removeClass('glyphicon-star-empty').addClass('glyphicon-star'); } @@ -163,8 +149,6 @@ if (typeof jQuery === 'undefined') { throw new Error('Requires jQuery') } }); }); - - // Delete all duplicate articles (used in the page /duplicates) $('.delete-all').click(function(){ var data = []; -- cgit From 84e335ebe6ffdd0d543e2a52060b417ba89ff378 Mon Sep 17 00:00:00 2001 From: François Schmidts Date: Mon, 18 May 2015 23:05:14 +0200 Subject: adding filters field --- pyaggr3g470r/models/feed.py | 1 + 1 file changed, 1 insertion(+) (limited to 'pyaggr3g470r') diff --git a/pyaggr3g470r/models/feed.py b/pyaggr3g470r/models/feed.py index e43045f1..793642fb 100644 --- a/pyaggr3g470r/models/feed.py +++ b/pyaggr3g470r/models/feed.py @@ -42,6 +42,7 @@ class Feed(db.Model): site_link = db.Column(db.String(), default="") enabled = db.Column(db.Boolean(), default=True) created_date = db.Column(db.DateTime(), default=datetime.now) + filters = db.Column(db.PickleType, default=[]) # cache handling etag = db.Column(db.String(), default="") -- cgit From 6b4d80849649fb68c7c3283dccd84f9a2db02acb Mon Sep 17 00:00:00 2001 From: François Schmidts Date: Tue, 19 May 2015 00:12:00 +0200 Subject: adding filters mechanism + splitting tests + adding tests for filters --- pyaggr3g470r/controllers/article.py | 39 +++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) (limited to 'pyaggr3g470r') diff --git a/pyaggr3g470r/controllers/article.py b/pyaggr3g470r/controllers/article.py index d22911bd..9051a910 100644 --- a/pyaggr3g470r/controllers/article.py +++ b/pyaggr3g470r/controllers/article.py @@ -1,9 +1,14 @@ +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 @@ -26,3 +31,37 @@ class ArticleController(AbstractController): .filter(*self._to_filters(readed=False, user_id=self.user_id)) .group_by(Article.feed_id).all()) + + def create(self, **attrs): + 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 + if not feed.filters: + return super().create(**attrs) + for filter_ in feed.filters: + 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) + + -- cgit From 5fba105078286cebb299eaa59eec4f801b5bc84e Mon Sep 17 00:00:00 2001 From: François Schmidts Date: Tue, 19 May 2015 10:21:07 +0200 Subject: adding comments and tests --- pyaggr3g470r/controllers/article.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) (limited to 'pyaggr3g470r') diff --git a/pyaggr3g470r/controllers/article.py b/pyaggr3g470r/controllers/article.py index 9051a910..b3a79838 100644 --- a/pyaggr3g470r/controllers/article.py +++ b/pyaggr3g470r/controllers/article.py @@ -33,15 +33,16 @@ class ArticleController(AbstractController): .group_by(Article.feed_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 - if not feed.filters: - return super().create(**attrs) - for filter_ in feed.filters: + + # 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', '')) @@ -63,5 +64,3 @@ class ArticleController(AbstractController): attrs['link']) return super().create(**attrs) - - -- cgit From 4f03d7b324360f718780fcbdfc359f60896fead4 Mon Sep 17 00:00:00 2001 From: François Schmidts Date: Sun, 24 May 2015 19:09:35 +0200 Subject: accelerating the feeds page --- pyaggr3g470r/controllers/article.py | 9 +++++---- pyaggr3g470r/templates/feeds.html | 2 +- pyaggr3g470r/views/feed.py | 3 ++- pyaggr3g470r/views/views.py | 2 +- 4 files changed, 9 insertions(+), 7 deletions(-) (limited to 'pyaggr3g470r') diff --git a/pyaggr3g470r/controllers/article.py b/pyaggr3g470r/controllers/article.py index b3a79838..70b9d2dd 100644 --- a/pyaggr3g470r/controllers/article.py +++ b/pyaggr3g470r/controllers/article.py @@ -26,11 +26,12 @@ class ArticleController(AbstractController): continue yield id_ - def get_unread(self): + 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(readed=False, - user_id=self.user_id)) - .group_by(Article.feed_id).all()) + .filter(*self._to_filters(**filters)) + .group_by(Article.feed_id).all()) def create(self, **attrs): # handling special denorm for article rights diff --git a/pyaggr3g470r/templates/feeds.html b/pyaggr3g470r/templates/feeds.html index f0e674c9..4d4581d4 100644 --- a/pyaggr3g470r/templates/feeds.html +++ b/pyaggr3g470r/templates/feeds.html @@ -30,7 +30,7 @@ {{ feed.title }} {{ feed.site_link }} - {{ feed.articles.count() }} + {{ article_count[feed.id] }} diff --git a/pyaggr3g470r/views/feed.py b/pyaggr3g470r/views/feed.py index 51832ea5..f940e22d 100644 --- a/pyaggr3g470r/views/feed.py +++ b/pyaggr3g470r/views/feed.py @@ -24,7 +24,8 @@ feed_bp = Blueprint('feed', __name__, url_prefix='/feed') def feeds(): "Lists the subscribed feeds in a table." return render_template('feeds.html', - feeds=FeedController(g.user.id).read()) + feeds=FeedController(g.user.id).read(), + article_count=ArticleController(g.user.id).count_by_feed()) @feed_bp.route('/', methods=['GET']) diff --git a/pyaggr3g470r/views/views.py b/pyaggr3g470r/views/views.py index 77f3b147..e0bd1dcb 100644 --- a/pyaggr3g470r/views/views.py +++ b/pyaggr3g470r/views/views.py @@ -246,7 +246,7 @@ def render_home(filters=None, head_titles=None, arti_contr = ArticleController(g.user.id) feeds = {feed.id: feed.title for feed in feed_contr.read()} - unread = arti_contr.get_unread() + unread = arti_contr.count_by_feed(readed=False) in_error = {feed.id: feed.error_count for feed in feed_contr.read(error_count__gt=2)} -- cgit From be33517445c787be0da5577da048d4b2d91452d4 Mon Sep 17 00:00:00 2001 From: François Schmidts Date: Thu, 28 May 2015 15:27:21 +0200 Subject: adding unread count when listing feeds, using count_by_feed for user management --- pyaggr3g470r/templates/admin/user.html | 4 ++-- pyaggr3g470r/templates/feeds.html | 2 +- pyaggr3g470r/views/feed.py | 5 ++++- pyaggr3g470r/views/views.py | 9 +++++++-- 4 files changed, 14 insertions(+), 6 deletions(-) (limited to 'pyaggr3g470r') diff --git a/pyaggr3g470r/templates/admin/user.html b/pyaggr3g470r/templates/admin/user.html index 317fef49..e50741ee 100644 --- a/pyaggr3g470r/templates/admin/user.html +++ b/pyaggr3g470r/templates/admin/user.html @@ -26,7 +26,7 @@ {{ _('Name') }} {{ _('Feed link') }} {{ _('Site link') }} - {{ _('Number of articles') }} + {{ _('(unread) articles') }} {{ _('Actions') }} @@ -37,7 +37,7 @@ {{ feed.title }} {{ feed.link }} {{ feed.site_link }} - {{ feed.articles.all()|count }} + ( {{ unread_article_count.get(feed.id, 0) }} ) {{ article_count.get(feed.id, 0) }} diff --git a/pyaggr3g470r/templates/feeds.html b/pyaggr3g470r/templates/feeds.html index 4d4581d4..789decf5 100644 --- a/pyaggr3g470r/templates/feeds.html +++ b/pyaggr3g470r/templates/feeds.html @@ -30,7 +30,7 @@ {{ feed.title }} {{ feed.site_link }} - {{ article_count[feed.id] }} + ( {{ unread_article_count.get(feed.id, 0) }} ) {{ article_count.get(feed.id, 0) }} diff --git a/pyaggr3g470r/views/feed.py b/pyaggr3g470r/views/feed.py index f940e22d..d50d0883 100644 --- a/pyaggr3g470r/views/feed.py +++ b/pyaggr3g470r/views/feed.py @@ -23,9 +23,12 @@ feed_bp = Blueprint('feed', __name__, url_prefix='/feed') @login_required def feeds(): "Lists the subscribed feeds in a table." + art_contr = ArticleController(g.user.id) return render_template('feeds.html', feeds=FeedController(g.user.id).read(), - article_count=ArticleController(g.user.id).count_by_feed()) + unread_article_count=art_contr.count_by_feed(readed=False), + article_count=art_contr.count_by_feed(), + ) @feed_bp.route('/', methods=['GET']) diff --git a/pyaggr3g470r/views/views.py b/pyaggr3g470r/views/views.py index e0bd1dcb..a4e799cc 100644 --- a/pyaggr3g470r/views/views.py +++ b/pyaggr3g470r/views/views.py @@ -736,9 +736,14 @@ def user(user_id=None): """ See information about a user (stations, etc.). """ - user = User.query.filter(User.id == user_id).first() + user = UserController().get(id=user_id) if user is not None: - return render_template('/admin/user.html', user=user) + article_contr = ArticleController(user_id) + return render_template('/admin/user.html', user=user, + article_count=article_contr.count_by_feed(), + unread_article_count=article_contr.count_by_feed(readed=False), + ) + else: flash(gettext('This user does not exist.'), 'danger') return redirect(redirect_url()) -- cgit From b682feb33d2830f2095eeb88123e05960f64ad1a Mon Sep 17 00:00:00 2001 From: François Schmidts Date: Sat, 6 Jun 2015 15:43:04 +0200 Subject: registering an strftime even if headers don't provide one --- pyaggr3g470r/lib/crawler.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'pyaggr3g470r') diff --git a/pyaggr3g470r/lib/crawler.py b/pyaggr3g470r/lib/crawler.py index c4c80ad4..324f0d8e 100644 --- a/pyaggr3g470r/lib/crawler.py +++ b/pyaggr3g470r/lib/crawler.py @@ -21,6 +21,7 @@ import dateutil.parser from hashlib import md5 from functools import wraps from datetime import datetime +from time import strftime, gmtime from concurrent.futures import ThreadPoolExecutor from requests_futures.sessions import FuturesSession from pyaggr3g470r.lib.utils import default_handler @@ -189,7 +190,8 @@ class PyAggUpdater(AbstractCrawler): dico = {'error_count': 0, 'last_error': None, 'etag': self.headers.get('etag', ''), - 'last_modified': self.headers.get('last-modified', ''), + 'last_modified': self.headers.get('last-modified', + strftime('%a, %d %b %Y %X %Z', gmtime())), 'site_link': self.parsed_feed.get('link')} if not self.feed.get('title'): dico['title'] = self.parsed_feed.get('title', '') -- cgit From c9b8d7ce0eba10fa3e0f0f0b87ad9f9479589062 Mon Sep 17 00:00:00 2001 From: François Schmidts Date: Tue, 9 Jun 2015 12:06:53 +0200 Subject: a more bootstrap edit feed page & removing unused code --- pyaggr3g470r/templates/edit_feed.html | 49 +++++++++++++++++++++++++---------- pyaggr3g470r/views/article.py | 1 - pyaggr3g470r/views/feed.py | 12 +-------- 3 files changed, 37 insertions(+), 25 deletions(-) (limited to 'pyaggr3g470r') diff --git a/pyaggr3g470r/templates/edit_feed.html b/pyaggr3g470r/templates/edit_feed.html index be63aa53..a56238d5 100644 --- a/pyaggr3g470r/templates/edit_feed.html +++ b/pyaggr3g470r/templates/edit_feed.html @@ -2,23 +2,46 @@ {% block content %}
-

{{ action }}

-
+

{{ action }}

+ {{ form.hidden_tag() }} +
+ +
+ {{ form.link(size="100%") }} +
+ {% for error in form.link.errors %} {{ error }}
{% endfor %} +
- {{ form.link.label }} - {{ form.link(class_="form-control") }} {% for error in form.link.errors %} {{ error }}
{% endfor %} +
+ +
+ {{ form.title(size="100%", placeholder=_('Optional')) }} +
+ {% for error in form.title.errors %} {{ error }}
{% endfor %} +
- {{ form.title.label }} - {{ form.title(class_="form-control", placeholder=_('Optional')) }} {% for error in form.title.errors %} {{ error }}
{% endfor %} +
+ +
+ {{ form.site_link(size="100%", placeholder=_('Optional')) }} +
+ {% for error in form.site_link.errors %} {{ error }}
{% endfor %} +
- {{ form.site_link.label }} - {{ form.site_link(class_="form-control", placeholder=_('Optional')) }} {% for error in form.site_link.errors %} {{ error }}
{% endfor %} - - {{ form.enabled.label }} - {{ form.enabled(class_="checkbox") }} -
- {{ form.submit(class_="btn btn-default") }} +
+ +
+
+ {{ form.enabled(style="margin-left: 0px;") }} +
+
+
+
+
+ {{ form.submit(class_="btn btn-default") }} +
+
diff --git a/pyaggr3g470r/views/article.py b/pyaggr3g470r/views/article.py index 5843551e..6de07ad3 100644 --- a/pyaggr3g470r/views/article.py +++ b/pyaggr3g470r/views/article.py @@ -2,7 +2,6 @@ # -*- coding: utf-8 - from flask import Blueprint, g, render_template, redirect -from sqlalchemy import desc from pyaggr3g470r import controllers, utils from pyaggr3g470r.decorators import pyagg_default_decorator diff --git a/pyaggr3g470r/views/feed.py b/pyaggr3g470r/views/feed.py index d50d0883..022980f4 100644 --- a/pyaggr3g470r/views/feed.py +++ b/pyaggr3g470r/views/feed.py @@ -27,8 +27,7 @@ def feeds(): return render_template('feeds.html', feeds=FeedController(g.user.id).read(), unread_article_count=art_contr.count_by_feed(readed=False), - article_count=art_contr.count_by_feed(), - ) + article_count=art_contr.count_by_feed()) @feed_bp.route('/', methods=['GET']) @@ -110,15 +109,6 @@ def bookmarklet(): return redirect(url_for('feed.form', feed_id=feed.id)) -@feed_bp.route('/read/', methods=['GET', 'POST']) -@login_required -def read(feed_id): - FeedController(g.user.id).update(readed=True) - flash(gettext('Feed successfully updated.', - feed_title=feed.title), 'success') - return redirect(request.referrer or url_for('home')) - - @feed_bp.route('/update//', methods=['GET', 'POST']) @feeds_bp.route('/update/', methods=['GET', 'POST']) @login_required -- cgit From bd0bdc2d1b16d1c5f05e670e6b36c31a5d2ae5d5 Mon Sep 17 00:00:00 2001 From: François Schmidts Date: Wed, 10 Jun 2015 17:51:34 +0200 Subject: redoing home page in a more 'bootstrapy' way --- pyaggr3g470r/templates/home.html | 207 ++++++++++++++++++++----------------- pyaggr3g470r/templates/layout.html | 31 +++--- 2 files changed, 127 insertions(+), 111 deletions(-) (limited to 'pyaggr3g470r') diff --git a/pyaggr3g470r/templates/home.html b/pyaggr3g470r/templates/home.html index dbb95451..6d1ca85e 100644 --- a/pyaggr3g470r/templates/home.html +++ b/pyaggr3g470r/templates/home.html @@ -6,104 +6,119 @@

{{ _('Add some') }}, {{ _('or') }} {{ _('upload an OPML file.') }}

{% else %} - + + {% with messages = get_flashed_messages(with_categories=true) %} + {% if messages %} +
+ {% block messages %} + {{ super() }} + {% endblock %} +
+ {% endif %} + {% endwith %} + + {% if articles | count != 0%} +
+ + + + + + + + + + + {% for article in articles %} + + + + + + + {% endfor %} + +
{{ _('Feed') }}{{ _('Article') }}{{ _('Date') }}
+ + {% if article.like %} + + {% else %} + + {% endif %} + {% if article.readed %} + + {% else %} + + {% if filter_ == 'all' %}{% endif %} + {% endif %} + {{ article.source.title|safe }} + {{ article.title|safe }} + {{ article.date|datetime }}
+
+ {% endif %} + + + + {% endif %} {% endblock %} diff --git a/pyaggr3g470r/templates/layout.html b/pyaggr3g470r/templates/layout.html index 80c74703..c66cce3e 100644 --- a/pyaggr3g470r/templates/layout.html +++ b/pyaggr3g470r/templates/layout.html @@ -129,29 +129,30 @@ -
-
- {% with messages = get_flashed_messages(with_categories=true) %} - {% if messages %} - {% for category, message in messages %} -
- - {{ message }} -
- {% endfor %} - {% endif %} - {% endwith %} +
+ {% block messages %} + {% with messages = get_flashed_messages(with_categories=true) %} + {% if messages %} + {% for category, message in messages %} +
+ + {{ message }} +
+ {% endfor %} + {% endif %} + {% endwith %} + {% endblock %}
{% block content %}{% endblock %} - - - + + + +