diff options
Diffstat (limited to 'pyaggr3g470r')
-rw-r--r-- | pyaggr3g470r/controllers/article.py | 47 | ||||
-rw-r--r-- | pyaggr3g470r/lib/crawler.py | 4 | ||||
-rw-r--r-- | pyaggr3g470r/models/feed.py | 1 | ||||
-rw-r--r-- | pyaggr3g470r/static/js/articles.js | 50 | ||||
-rw-r--r-- | pyaggr3g470r/static/js/feed.js | 22 | ||||
-rw-r--r-- | pyaggr3g470r/templates/admin/user.html | 4 | ||||
-rw-r--r-- | pyaggr3g470r/templates/article.html | 2 | ||||
-rw-r--r-- | pyaggr3g470r/templates/edit_feed.html | 72 | ||||
-rw-r--r-- | pyaggr3g470r/templates/feed.html | 2 | ||||
-rw-r--r-- | pyaggr3g470r/templates/feeds.html | 2 | ||||
-rw-r--r-- | pyaggr3g470r/templates/home.html | 207 | ||||
-rw-r--r-- | pyaggr3g470r/templates/layout.html | 32 | ||||
-rw-r--r-- | pyaggr3g470r/views/article.py | 1 | ||||
-rw-r--r-- | pyaggr3g470r/views/feed.py | 117 | ||||
-rw-r--r-- | pyaggr3g470r/views/views.py | 11 |
15 files changed, 344 insertions, 230 deletions
diff --git a/pyaggr3g470r/controllers/article.py b/pyaggr3g470r/controllers/article.py index d22911bd..70b9d2dd 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 @@ -21,8 +26,42 @@ 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 + 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/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', '') 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="") 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 = []; diff --git a/pyaggr3g470r/static/js/feed.js b/pyaggr3g470r/static/js/feed.js new file mode 100644 index 00000000..b1e3110e --- /dev/null +++ b/pyaggr3g470r/static/js/feed.js @@ -0,0 +1,22 @@ +$('.container').on('click', '#add-feed-filter-row', function() { + $('#filters-container').append( + '<div class="col-sm-9">' + + ' <input value="-" type="button" class="del-feed-filter-row" />' + + ' <select name="type">' + + ' <option value="simple match" selected>simple match</option>' + + ' <option value="regex">regex</option>' + + ' </select/>' + + ' <input type="text" size="50%" name="pattern" />' + + ' <select name="action_on">' + + ' <option value="match" selected>match</option>' + + ' <option value="no match">no match</option>' + + ' </select/>' + + ' <select name="action">' + + ' <option value="mark as read" selected>mark as read</option>' + + ' <option value="mark as favorite">mark as favorite</option>' + + ' </select/>' + + '</div>'); +}); +$('.container').on('click', '.del-feed-filter-row', function() { + $(this).parent().remove(); +}); 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 @@ <th>{{ _('Name') }}</th> <th>{{ _('Feed link') }}</th> <th>{{ _('Site link') }}</th> - <th>{{ _('Number of articles') }}</th> + <th>{{ _('(unread) articles') }}</th> <th>{{ _('Actions') }}</th> </tr> </thead> @@ -37,7 +37,7 @@ <td><a href="/feed/{{ feed.id }}">{{ feed.title }}</a></td> <td>{{ feed.link }}</td> <td>{{ feed.site_link }}</td> - <td>{{ feed.articles.all()|count }}</td> + <td>( {{ unread_article_count.get(feed.id, 0) }} ) {{ article_count.get(feed.id, 0) }}</td> <td> <a href="{{ url_for("feed.feed", feed_id=feed.id) }}"><i class="glyphicon glyphicon-th-list" title="{{ _('Feed') }}"></i></a> <a href="{{ url_for("feed.form", feed_id=feed.id) }}"><i class="glyphicon glyphicon-edit" title="{{ _('Edit this feed') }}"></i></a> diff --git a/pyaggr3g470r/templates/article.html b/pyaggr3g470r/templates/article.html index 92014599..97fb3fbf 100644 --- a/pyaggr3g470r/templates/article.html +++ b/pyaggr3g470r/templates/article.html @@ -16,7 +16,7 @@ {% endif %} {% if article.readed %} <a href="#"><i class="glyphicon glyphicon-unchecked readed" title="{{ _('Mark this article as unread') }}"></i></a> - {% elseĀ %} + {% else %} <a href="#"><i class="glyphicon glyphicon-check readed" title="{{ _('Mark this article as read') }}"></i></a> {% endif %} <h6>{{ article.date | datetime }}</h6> diff --git a/pyaggr3g470r/templates/edit_feed.html b/pyaggr3g470r/templates/edit_feed.html index be63aa53..22aab58b 100644 --- a/pyaggr3g470r/templates/edit_feed.html +++ b/pyaggr3g470r/templates/edit_feed.html @@ -2,23 +2,69 @@ {% block content %} <div class="container"> <div class="well"> - <h1>{{ action }}</h1> - <form action="" method="post" name="save"> + <h3>{{ action }}</h3> + <form action="" method="post" name="save" class="form-horizontal"> {{ form.hidden_tag() }} + <div class="form-group"> + <label for="{{ form.link.id }}" class="col-sm-3 control-label">{{ form.link.label }}</label> + <div class="col-sm-9"> + {{ form.link(size="100%") }} + </div> + {% for error in form.link.errors %} <span style="color: red;">{{ error }}<br /></span>{% endfor %} + </div> - {{ form.link.label }} - {{ form.link(class_="form-control") }} {% for error in form.link.errors %} <span style="color: red;">{{ error }}<br /></span>{% endfor %} + <div class="form-group"> + <label for="{{ form.title.id }}" class="col-sm-3 control-label">{{ form.title.label }}</label> + <div class="col-sm-9"> + {{ form.title(size="100%", placeholder=_('Optional')) }} + </div> + {% for error in form.title.errors %} <span style="color: red;">{{ error }}<br /></span>{% endfor %} + </div> - {{ form.title.label }} - {{ form.title(class_="form-control", placeholder=_('Optional')) }} {% for error in form.title.errors %} <span style="color: red;">{{ error }}<br /></span>{% endfor %} + <div class="form-group"> + <label for="{{ form.site_link.id }}" class="col-sm-3 control-label">{{ form.site_link.label }}</label> + <div class="col-sm-9"> + {{ form.site_link(size="100%", placeholder=_('Optional')) }} + </div> + {% for error in form.site_link.errors %} <span style="color: red;">{{ error }}<br /></span>{% endfor %} + </div> - {{ form.site_link.label }} - {{ form.site_link(class_="form-control", placeholder=_('Optional')) }} {% for error in form.site_link.errors %} <span style="color: red;">{{ error }}<br /></span>{% endfor %} - - {{ form.enabled.label }} - {{ form.enabled(class_="checkbox") }} - <br /> - {{ form.submit(class_="btn btn-default") }} + <div class="form-group"> + <label for="{{ form.enabled.id }}" class="col-sm-3 control-label">{{ form.enabled.label }}</label> + <div class="col-sm-9"> + <div class="checkbox"> + {{ form.enabled(style="margin-left: 0px;") }} + </div> + </div> + </div> + <div class="form-group" id="filters-container"> + <label class="col-sm-3 control-label">{{ _("Filters") }} <input value="+" type="button" id="add-feed-filter-row" /></label> + {% if feed %} + {% for filter_ in feed.filters or [] %} + <div class="col-sm-9"> + <input value="-" type="button" class="del-feed-filter-row" /> + <select name="type"> + <option value="simple match" {% if filter_.get("type") == "simple match" %}selected{% endif %}>{{ _("simple match") }}</option> + <option value="regex" {% if filter_.get("type") == "regex" %}selected{% endif %}>{{ _("regex") }}</option> + </select/> + <input type="text" value="{{ filter_.get("pattern") }}" size="50%" name="pattern" /> + <select name="action_on"> + <option value="match" {% if filter_.get("action on") == "match" %}selected{% endif %}>{{ _("match") }}</option> + <option value="no match" {% if filter_.get("action on") == "no match" %}selected{% endif %}>{{ _("no match") }}</option> + </select/> + <select name="action"> + <option value="mark as read" {% if filter_.get("action") == "mark as read" %}selected{% endif %}>{{ _("mark as read") }}</option> + <option value="mark as favorite" {% if filter_.get("action") == "mark as favorite" %}selected{% endif %}>{{ _("mark as favorite") }}</option> + </select/> + </div> + {% endfor %} + {% endif %} + </div> + <div class="form-group"> + <div class="col-sm-offset-3 col-sm-9"> + {{ form.submit(class_="btn btn-default") }} + </div> + </div> </form> </div> </div><!-- /.container --> diff --git a/pyaggr3g470r/templates/feed.html b/pyaggr3g470r/templates/feed.html index 09a064e5..cce74b19 100644 --- a/pyaggr3g470r/templates/feed.html +++ b/pyaggr3g470r/templates/feed.html @@ -21,7 +21,7 @@ {{ _("Last download:") }} {{ feed.last_retrieved | datetime }}<br /> {% endif %} - {% if feed.error_count > 2 %} + {% if feed.error_count > conf.DEFAULT_MAX_ERROR %} <b>{{ _("That feed has encountered too much consecutive errors and won't be retrieved anymore.") }}</b><br /> {{ _("You can click <a href='%(reset_error_url)s'>here</a> to reset the error count and reactivate the feed.", reset_error_url=url_for("feed.reset_errors", feed_id=feed.id)) }} {% elif feed.error_count > 0 %} diff --git a/pyaggr3g470r/templates/feeds.html b/pyaggr3g470r/templates/feeds.html index f0e674c9..789decf5 100644 --- a/pyaggr3g470r/templates/feeds.html +++ b/pyaggr3g470r/templates/feeds.html @@ -30,7 +30,7 @@ </td> <td><a href="{{ url_for("feed.feed", feed_id=feed.id) }}" {% if feed.description %}title="{{ feed.description }}"{% endif %}>{{ feed.title }}</a></td> <td><a href="{{ feed.site_link }}">{{ feed.site_link }}</a></td> - <td>{{ feed.articles.count() }}</td> + <td>( {{ unread_article_count.get(feed.id, 0) }} ) {{ article_count.get(feed.id, 0) }}</td> <td> <a href="{{ url_for("home", feed_id=feed.id, filter_="all") }}"><i class="glyphicon glyphicon-th-list" title="{{ _('Articles') }}"></i></a> <a href="{{ url_for("feed.form", feed_id=feed.id) }}"><i class="glyphicon glyphicon-edit" title="{{ _('Edit this feed') }}"></i></a> 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 @@ <h1><a href="{{ url_for("feed.form") }}">{{ _('Add some') }}</a>, {{ _('or') }} <a href="/management">{{ _('upload an OPML file.') }}</a></h1> </div> {% else %} - <div id="affix-nav" class="col-md-3 sidebar hidden-xs hidden-sm"> - <ul class="nav sidenav navbar-collapse pre-scrollable" data-offset-top="0" data-offset-bottom="0" style="min-height: 650px;"> - <li><a href="{{ gen_url(feed_id=0) }}"> - {% if not feed_id %}<b>{% endif %} - {{ _('All feeds') }} <span id="total-unread" class="badge pull-right">{{ articles.__len__() }}</span> - {% if not feed_id %}</b>{% endif %} - </a></li> - {% for fid, nbunread in unread|dictsort(by='value')|reverse %} - <li class="feed-menu"><a href="{{ gen_url(feed_id=fid) }}"> - {% if feed_id == fid %}<b>{% endif %} +<div class="container-fluid"> + <div class="row row-offcanvas row-offcanvas-left"> + <div class="col-md-2 sidebar sidebar-offcanvas pre-scrollable affix hidden-sm hidden-xs" id="sidebar" role="navigation" data-spy="affix" style="max-height: 100%;"> + <ul class="nav nav-sidebar" data-offset-top="0" data-offset-bottom="0"> + <li><a href="{{ gen_url(feed_id=0) }}"> + {% if not feed_id %}<b>{% endif %} + {{ _('All feeds') }} <span id="total-unread" class="badge pull-right">{{ articles.__len__() }}</span> + {% if not feed_id %}</b>{% endif %} + </a></li> + {% for fid, nbunread in unread|dictsort(by='value')|reverse %} + <li class="feed-menu"><a href="{{ gen_url(feed_id=fid) }}"> + {% if feed_id == fid %}<b>{% endif %} + {% if in_error.get(fid, 0) > 0 %} + <span style="background-color: {{ "red" if in_error[fid] > conf.DEFAULT_MAX_ERROR -1 else "orange" }} ;" class="badge pull-right" title="Some errors occured while trying to retrieve that feed.">{{ in_error[fid] }} {{ _("error") }}{% if in_error[fid] > 1 %}s{% endif %}</span> + {% endif %} + <span id="unread-{{ fid }}" class="badge pull-right">{{ nbunread }}</span> + {{ feeds[fid]|safe }} + {% if feed_id == fid %}</b>{% endif %} + </a></li> + <li class="feed-commands"><span> + <a href="/feed/{{ fid }}"><i class="glyphicon glyphicon-info-sign" title="{{ _('Details') }}"></i></a> + <a href="{{ url_for("feed.form", feed_id=fid) }}"><i class="glyphicon glyphicon-edit" title="{{ _('Edit this feed') }}"></i></a> + <a href="{{ url_for("feed.delete", feed_id=fid) }}"><i class="glyphicon glyphicon-remove" title="{{ _('Delete this feed') }}" onclick="return confirm('{{ _('You are going to delete this feed.') }}');"></i></a> + <a href="{{ url_for("feed.update", feed_id=fid, action="read") }}"><i class="glyphicon glyphicon-check" title="{{ _('Mark this feed as read') }}"></i></a> + <a href="{{ url_for("feed.update", feed_id=fid, action="unread") }}"><i class="glyphicon glyphicon-unchecked" title="{{ _('Mark this feed as unread') }}"></i></a> + </span></li> + {% endfor %} + {% for fid, ftitle in feeds|dictsort(case_sensitive=False, by='value') if not fid in unread %} + <li class="feed-menu"><a href="{{ gen_url(feed_id=fid) }}"> {% if in_error.get(fid, 0) > 0 %} - <span style="background-color: {{ "red" if in_error[fid] > conf.DEFAULT_MAX_ERROR -1 else "orange" }} ;" class="badge pull-right" title="Some errors occured while trying to retrieve that feed.">{{ in_error[fid] }} {{ _("error") }}{% if in_error[fid] > 1 %}s{% endif %}</span> + <span style="background-color: {{ "red" if in_error[fid] > conf.DEFAULT_MAX_ERROR - 1 else "orange" }} ;" class="badge pull-right" title="Some errors occured while trying to retrieve that feed.">{{ in_error[fid] }} {{ _("error") }}{% if in_error[fid] > 1 %}s{% endif %}</span> {% endif %} - <span id="unread-{{ fid }}" class="badge pull-right">{{ nbunread }}</span> - {{ feeds[fid]|safe }} - {% if feed_id == fid %}</b>{% endif %} - </a></li> - <li class="feed-commands"><span> - <a href="/feed/{{ fid }}"><i class="glyphicon glyphicon-info-sign" title="{{ _('Details') }}"></i></a> - <a href="{{ url_for("feed.form", feed_id=fid) }}"><i class="glyphicon glyphicon-edit" title="{{ _('Edit this feed') }}"></i></a> - <a href="{{ url_for("feed.delete", feed_id=fid) }}"><i class="glyphicon glyphicon-remove" title="{{ _('Delete this feed') }}" onclick="return confirm('{{ _('You are going to delete this feed.') }}');"></i></a> - <a href="{{ url_for("feed.update", feed_id=fid, action="read") }}"><i class="glyphicon glyphicon-check" title="{{ _('Mark this feed as read') }}"></i></a> - <a href="{{ url_for("feed.update", feed_id=fid, action="unread") }}"><i class="glyphicon glyphicon-unchecked" title="{{ _('Mark this feed as unread') }}"></i></a> - </span></li> - {% endfor %} - {% for fid, ftitle in feeds|dictsort(case_sensitive=False, by='value') if not fid in unread %} - <li class="feed-menu"><a href="{{ gen_url(feed_id=fid) }}"> - {% if in_error.get(fid, 0) > 0 %} - <span style="background-color: {{ "red" if in_error[fid] > conf.DEFAULT_MAX_ERROR - 1 else "orange" }} ;" class="badge pull-right" title="Some errors occured while trying to retrieve that feed.">{{ in_error[fid] }} {{ _("error") }}{% if in_error[fid] > 1 %}s{% endif %}</span> - {% endif %} - {% if feed_id == fid %}<b>{% endif %} - {{ ftitle|safe }} - {% if feed_id == fid %}</b>{% endif %} - </a></li> - <li class="feed-commands"><span> - <a href="{{ url_for("feed.feed", feed_id=fid) }}"><i class="glyphicon glyphicon-info-sign" title="{{ _('Details') }}"></i></a> - <a href="{{ url_for("feed.form", feed_id=fid) }}"><i class="glyphicon glyphicon-edit" title="{{ _('Edit this feed') }}"></i></a> - <a href="{{ url_for("feed.delete", feed_id=fid) }}"><i class="glyphicon glyphicon-remove" title="{{ _('Delete this feed') }}" onclick="return confirm('{{ _('You are going to delete this feed.') }}');"></i></a> - <a href="{{ url_for("feed.update", feed_id=fid, action="read") }}"><i class="glyphicon glyphicon-check" title="{{ _('Mark this feed as read') }}"></i></a> - <a href="{{ url_for("feed.update", feed_id=fid, action="unread") }}"><i class="glyphicon glyphicon-unchecked" title="{{ _('Mark this feed as unread') }}"></i></a> - </span></li> - {% endfor %} - </ul> - </div> - <div class="container col-md-9"> - <div id="filters" data-filter="{{ filter_ }}"> - <ul id="myTab" class="nav nav-tabs" role="tablist"> - <li id="tab-all"><a href="{{ gen_url(filter_='all') }}">{{ _('All') }}</a></li> - <li id="tab-read"><a href="{{ gen_url(filter_='read') }}">{{ _('Read') }}</a></li> - <li id="tab-unread"><a href="{{ gen_url(filter_='unread') }}">{{ _('Unread') }}</a></li> - <li id="tab-nbdisplay" class="pull-right"> - <div id="nbdisplay"> - <a href="{{ gen_url(limit=10) }}" class="label {% if limit == 10 %}label-primary{% else %}label-info{% endif %}">{{ _(10) }}</a> - <a href="{{ gen_url(limit=100) }}" class="label {% if limit == 100 %}label-primary{% else %}label-info{% endif %}">{{ _(100) }}</a> - <a href="{{ gen_url(limit=1000) }}" class="label {% if limit == 1000 %}label-primary{% else %}label-info{% endif %}">{{ _(1000) }}</a> - <a href="{{ gen_url(limit='all') }}" class="label {% if limit == 'all' %}label-primary{% else %}label-info{% endif %}">{{ _('All') }}</a> - </div> - </li> - </div> - {% if articles | count != 0%} - <div class="table-responsive"> - <table class="table table-striped strict-table"> - <thead> - <tr> - <th></th> - <th><a href="{{ gen_url(sort_='-feed' if sort_ == 'feed' else 'feed') }}">{{ _('Feed') }}</a></th> - <th><a href="{{ gen_url(sort_='-article' if sort_ == 'article' else 'article') }}">{{ _('Article') }}</a></th> - <th><a href="{{ gen_url(sort_='-date' if sort_ == 'date' else 'date') }}">{{ _('Date') }}</a></th> - </tr> - </thead> - <tbody> - {% for article in articles %} - <tr data-article="{{ article.id }}" data-feed="{{ article.feed_id }}"> - <td> - <a><i class="glyphicon glyphicon-remove delete" title="{{ _('Delete this article') }}"></i></a> - {% if article.like %} - <a><i class="glyphicon glyphicon-star like" title="{{ _('One of your favorites') }}"></i></a> - {% else %} - <a><i class="glyphicon glyphicon-star-empty like" title="{{ _('Click if you like this article') }}"></i></a> - {% endif %} - {% if article.readed %} - <a><i class="glyphicon glyphicon-unchecked readed" title="{{ _('Mark this article as unread') }}"></i></a> - {% else %} - <a><i class="glyphicon glyphicon-check readed" title="{{ _('Mark this article as read') }}"></i></a> - {% if filter_ == 'all' %}</b>{% endif %} - {% endif %} - </td> - <td><a class="open-article" href="/article/redirect/{{ article.id}}" target="_blank">{{ article.source.title|safe }}</a></td> - <td {%if filter_ == 'all' and article.readed == False %}style='font-weight:bold'{% endif %}> - <a href="/article/{{ article.id }}">{{ article.title|safe }}</a> - </td> - <td class="date">{{ article.date|datetime }}</a></td> - </tr> - {% endfor %} - </tbody> - </table> + {% if feed_id == fid %}<b>{% endif %} + {{ ftitle|safe }} + {% if feed_id == fid %}</b>{% endif %} + </a></li> + <li class="feed-commands"><span> + <a href="{{ url_for("feed.feed", feed_id=fid) }}"><i class="glyphicon glyphicon-info-sign" title="{{ _('Details') }}"></i></a> + <a href="{{ url_for("feed.form", feed_id=fid) }}"><i class="glyphicon glyphicon-edit" title="{{ _('Edit this feed') }}"></i></a> + <a href="{{ url_for("feed.delete", feed_id=fid) }}"><i class="glyphicon glyphicon-remove" title="{{ _('Delete this feed') }}" onclick="return confirm('{{ _('You are going to delete this feed.') }}');"></i></a> + <a href="{{ url_for("feed.update", feed_id=fid, action="read") }}"><i class="glyphicon glyphicon-check" title="{{ _('Mark this feed as read') }}"></i></a> + <a href="{{ url_for("feed.update", feed_id=fid, action="unread") }}"><i class="glyphicon glyphicon-unchecked" title="{{ _('Mark this feed as unread') }}"></i></a> + </span></li> + {% endfor %} + </ul> + </div><!-- row --> + + {% with messages = get_flashed_messages(with_categories=true) %} + {% if messages %} + <div class="col-md-offset-2 col-md-10 main"> + {% block messages %} + {{ super() }} + {% endblock %} + </div> + {% endif %} + {% endwith %} + <div class="col-md-offset-2 col-md-10 main"> + <div id="filters" data-filter="{{ filter_ }}"> + <ul id="myTab" class="nav nav-tabs" role="tablist"> + <li id="tab-all"><a href="{{ gen_url(filter_='all') }}">{{ _('All') }}</a></li> + <li id="tab-read"><a href="{{ gen_url(filter_='read') }}">{{ _('Read') }}</a></li> + <li id="tab-unread"><a href="{{ gen_url(filter_='unread') }}">{{ _('Unread') }}</a></li> + <li id="tab-nbdisplay" class="pull-right"> + <div id="nbdisplay"> + <a href="{{ gen_url(limit=10) }}" class="label {% if limit == 10 %}label-primary{% else %}label-info{% endif %}">{{ _(10) }}</a> + <a href="{{ gen_url(limit=100) }}" class="label {% if limit == 100 %}label-primary{% else %}label-info{% endif %}">{{ _(100) }}</a> + <a href="{{ gen_url(limit=1000) }}" class="label {% if limit == 1000 %}label-primary{% else %}label-info{% endif %}">{{ _(1000) }}</a> + <a href="{{ gen_url(limit='all') }}" class="label {% if limit == 'all' %}label-primary{% else %}label-info{% endif %}">{{ _('All') }}</a> + </div> + </li> </div> - {% endif %} - </div><!-- /.container --> + {% if articles | count != 0%} + <div class="table-responsive"> + <table class="table table-striped strict-table"> + <thead> + <tr> + <th></th> + <th><a href="{{ gen_url(sort_='-feed' if sort_ == 'feed' else 'feed') }}">{{ _('Feed') }}</a></th> + <th><a href="{{ gen_url(sort_='-article' if sort_ == 'article' else 'article') }}">{{ _('Article') }}</a></th> + <th><a href="{{ gen_url(sort_='-date' if sort_ == 'date' else 'date') }}">{{ _('Date') }}</a></th> + </tr> + </thead> + <tbody> + {% for article in articles %} + <tr data-article="{{ article.id }}" data-feed="{{ article.feed_id }}"> + <td> + <a><i class="glyphicon glyphicon-remove delete" title="{{ _('Delete this article') }}"></i></a> + {% if article.like %} + <a><i class="glyphicon glyphicon-star like" title="{{ _('One of your favorites') }}"></i></a> + {% else %} + <a><i class="glyphicon glyphicon-star-empty like" title="{{ _('Click if you like this article') }}"></i></a> + {% endif %} + {% if article.readed %} + <a><i class="glyphicon glyphicon-unchecked readed" title="{{ _('Mark this article as unread') }}"></i></a> + {% else %} + <a><i class="glyphicon glyphicon-check readed" title="{{ _('Mark this article as read') }}"></i></a> + {% if filter_ == 'all' %}</b>{% endif %} + {% endif %} + </td> + <td><a class="open-article" href="/article/redirect/{{ article.id}}" target="_blank">{{ article.source.title|safe }}</a></td> + <td {%if filter_ == 'all' and article.readed == False %}style='font-weight:bold'{% endif %}> + <a href="/article/{{ article.id }}">{{ article.title|safe }}</a> + </td> + <td class="date">{{ article.date|datetime }}</a></td> + </tr> + {% endfor %} + </tbody> + </table> + </div> + {% endif %} + </div><!-- row --> + </div><!-- main --> +</div><!-- container-fluid --> +<style>.not-at-home {display: none};</style> {% endif %} {% endblock %} diff --git a/pyaggr3g470r/templates/layout.html b/pyaggr3g470r/templates/layout.html index 80c74703..fcdbee32 100644 --- a/pyaggr3g470r/templates/layout.html +++ b/pyaggr3g470r/templates/layout.html @@ -129,29 +129,31 @@ </div><!-- /.navbar-collapse --> </div><!-- /.container --> </nav> - <br /> - <div class="container"> - {% with messages = get_flashed_messages(with_categories=true) %} - {% if messages %} - {% for category, message in messages %} - <div class="alert alert-{{category}}"> - <button type="button" class="close" data-dismiss="alert">×</button> - {{ message }} - </div> - {% endfor %} - {% endif %} - {% endwith %} + <div class="container-fluid not-at-home"> + {% block messages %} + {% with messages = get_flashed_messages(with_categories=true) %} + {% if messages %} + {% for category, message in messages %} + <div class="alert alert-{{category}}"> + <button type="button" class="close" data-dismiss="alert">×</button> + {{ message }} + </div> + {% endfor %} + {% endif %} + {% endwith %} + {% endblock %} </div> {% block content %}{% endblock %} <!-- Bootstrap core JavaScript --> <!-- Placed at the end of the document so the pages load faster --> - <script src="{{ url_for('static', filename = 'js/jquery.js') }}"></script> - <script src="{{ url_for('static', filename = 'js/bootstrap.js') }}"></script> - <script src="{{ url_for('static', filename = 'js/articles.js') }}"></script> + <script type="text/javascript" src="{{ url_for('static', filename = 'js/jquery.js') }}"></script> + <script type="text/javascript" src="{{ url_for('static', filename = 'js/bootstrap.js') }}"></script> + <script type="text/javascript" src="{{ url_for('static', filename = 'js/articles.js') }}"></script> + <script type="text/javascript" src="{{ url_for('static', filename = 'js/feed.js') }}"></script> <script type="text/javascript" class="source"> var filter_ = {% if filter_ %}"{{ filter_ }}"{% else %}undefined{% endif %}; if (filter_ == undefined) { 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 51832ea5..8bd2f8e9 100644 --- a/pyaggr3g470r/views/feed.py +++ b/pyaggr3g470r/views/feed.py @@ -23,8 +23,11 @@ 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()) + feeds=FeedController(g.user.id).read(), + unread_article_count=art_contr.count_by_feed(readed=False), + article_count=art_contr.count_by_feed()) @feed_bp.route('/<int:feed_id>', methods=['GET']) @@ -106,15 +109,6 @@ def bookmarklet(): return redirect(url_for('feed.form', feed_id=feed.id)) -@feed_bp.route('/read/<int:feed_id>', 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/<action>/<int:feed_id>', methods=['GET', 'POST']) @feeds_bp.route('/update/<action>', methods=['GET', 'POST']) @login_required @@ -128,59 +122,64 @@ def update(action, feed_id=None): return redirect(request.referrer or url_for('home')) -@feed_bp.route('/create', methods=['GET', 'POST', 'PUT']) -@feed_bp.route('/edit/<int:feed_id>', methods=['GET', 'POST']) +@feed_bp.route('/create', methods=['GET']) +@feed_bp.route('/edit/<int:feed_id>', methods=['GET']) @login_required def form(feed_id=None): + action = gettext("Add a feed") + head_titles = [action] + if feed_id is None: + return render_template('edit_feed.html', action=action, + head_titles=head_titles, form=AddFeedForm()) + feed = FeedController(g.user.id).get(id=feed_id) + 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, + form=AddFeedForm(obj=feed), 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() feed_contr = FeedController(g.user.id) - if request.method == 'POST': - if not form.validate(): - 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)) - # Edit an existing feed - if feed_id is not None: - feed_contr.update({'id': feed_id}, - {'title': form.title.data, - 'link': form.link.data, - 'enabled': form.enabled.data, - 'site_link': form.site_link.data}) - flash(gettext('Feed %(feed_title)r successfully updated.', - feed_title=form.title.data), 'success') - return redirect(url_for('feed.form', feed_id=feed_id)) - - # Create a new feed - new_feed = FeedController(g.user.id).create( - title=form.title.data, - description="", - link=form.link.data, - site_link=form.site_link.data, - enabled=form.enabled.data) - - flash(gettext('Feed %(feed_title)r successfully created.', - feed_title=new_feed.title), 'success') - - if conf.CRAWLING_METHOD == "classic": - utils.fetch(g.user.id, new_feed.id) - flash(gettext("Downloading articles for the new feed..."), 'info') + if not form.validate(): + 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)) + # 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': []} + + 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 - return redirect(url_for('feed.form', - feed_id=new_feed.id)) - - # Getting the form for an existing feed if feed_id is not None: - feed = FeedController(g.user.id).get(id=feed_id) - form = AddFeedForm(obj=feed) - return render_template('edit_feed.html', - action=gettext("Edit the feed"), - form=form, feed=feed) - - # Return an empty form in order to create a new feed - return render_template('edit_feed.html', action=gettext("Add a feed"), - form=form) + 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 = FeedController(g.user.id).create(**feed_attr) + + flash(gettext('Feed %(feed_title)r successfully created.', + feed_title=new_feed.title), 'success') + + if conf.CRAWLING_METHOD == "classic": + utils.fetch(g.user.id, new_feed.id) + flash(gettext("Downloading articles for the new feed..."), 'info') + + return redirect(url_for('feed.form', feed_id=new_feed.id)) diff --git a/pyaggr3g470r/views/views.py b/pyaggr3g470r/views/views.py index 77f3b147..a4e799cc 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)} @@ -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()) |