aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xmanager.py4
-rw-r--r--migrations/versions/17dcb75f3fe_changed_the_type_of_the_column_last_.py19
-rw-r--r--migrations/versions/422da2d0234_adding_filters_field.py22
-rw-r--r--migrations/versions/48f561c0ce6_add_column_entry_id.py4
-rw-r--r--migrations/versions/cde34831ea_adding_feed_and_user_attributes_for_.py13
-rw-r--r--pyaggr3g470r/controllers/article.py47
-rw-r--r--pyaggr3g470r/lib/crawler.py4
-rw-r--r--pyaggr3g470r/models/feed.py1
-rw-r--r--pyaggr3g470r/static/js/articles.js50
-rw-r--r--pyaggr3g470r/static/js/feed.js22
-rw-r--r--pyaggr3g470r/templates/admin/user.html4
-rw-r--r--pyaggr3g470r/templates/article.html2
-rw-r--r--pyaggr3g470r/templates/edit_feed.html72
-rw-r--r--pyaggr3g470r/templates/feed.html2
-rw-r--r--pyaggr3g470r/templates/feeds.html2
-rw-r--r--pyaggr3g470r/templates/home.html207
-rw-r--r--pyaggr3g470r/templates/layout.html32
-rw-r--r--pyaggr3g470r/views/article.py1
-rw-r--r--pyaggr3g470r/views/feed.py117
-rw-r--r--pyaggr3g470r/views/views.py11
-rw-r--r--tests/controllers/article.py102
-rw-r--r--tests/controllers/feed.py10
22 files changed, 496 insertions, 252 deletions
diff --git a/manager.py b/manager.py
index 979f123e..008c7775 100755
--- a/manager.py
+++ b/manager.py
@@ -1,8 +1,6 @@
#! /usr/bin/env python
# -*- coding: utf-8 -*-
-import asyncio
-
from bootstrap import application, db, populate_g
from flask.ext.script import Manager
from flask.ext.migrate import Migrate, MigrateCommand
@@ -39,6 +37,8 @@ def fetch(user, password, limit=100, retreive_all=False):
@manager.command
def fetch_asyncio(user_id, feed_id):
"Crawl the feeds with asyncio."
+ import asyncio
+
with application.app_context():
populate_g()
from pyaggr3g470r.models import User
diff --git a/migrations/versions/17dcb75f3fe_changed_the_type_of_the_column_last_.py b/migrations/versions/17dcb75f3fe_changed_the_type_of_the_column_last_.py
index e7790b3f..035646c6 100644
--- a/migrations/versions/17dcb75f3fe_changed_the_type_of_the_column_last_.py
+++ b/migrations/versions/17dcb75f3fe_changed_the_type_of_the_column_last_.py
@@ -10,20 +10,25 @@ Create Date: 2015-03-10 14:20:53.676344
revision = '17dcb75f3fe'
down_revision = 'cde34831ea'
+from datetime import datetime
+import conf
from alembic import op
import sqlalchemy as sa
-from datetime import datetime
def upgrade():
unix_start = datetime(1970, 1, 1)
- op.drop_column('feed', 'last_modified')
- op.add_column('feed', sa.Column('last_modified', sa.String(),
- nullable=True, default=unix_start, server_default=str(unix_start)))
+ if 'sqlite' not in conf.SQLALCHEMY_DATABASE_URI:
+ op.drop_column('feed', 'last_modified')
+ op.add_column('feed', sa.Column('last_modified', sa.String(),
+ nullable=True, default=unix_start,
+ server_default=str(unix_start)))
def downgrade():
unix_start = datetime(1970, 1, 1)
- op.drop_column('feed', 'last_modified')
- op.add_column('feed', sa.Column('last_modified', sa.DateTime(),
- nullable=True, default=unix_start, server_default=unix_start))
+ if 'sqlite' not in conf.SQLALCHEMY_DATABASE_URI:
+ op.drop_column('feed', 'last_modified')
+ op.add_column('feed', sa.Column('last_modified', sa.DateTime(),
+ nullable=True, default=unix_start,
+ server_default=unix_start))
diff --git a/migrations/versions/422da2d0234_adding_filters_field.py b/migrations/versions/422da2d0234_adding_filters_field.py
new file mode 100644
index 00000000..bcbdf042
--- /dev/null
+++ b/migrations/versions/422da2d0234_adding_filters_field.py
@@ -0,0 +1,22 @@
+"""adding filters field
+
+Revision ID: 422da2d0234
+Revises: 17dcb75f3fe
+Create Date: 2015-05-18 23:03:15.809549
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = '422da2d0234'
+down_revision = '17dcb75f3fe'
+
+from alembic import op
+import sqlalchemy as sa
+
+
+def upgrade():
+ op.add_column('feed', sa.Column('filters', sa.PickleType(), nullable=True))
+
+
+def downgrade():
+ op.drop_column('feed', 'filters')
diff --git a/migrations/versions/48f561c0ce6_add_column_entry_id.py b/migrations/versions/48f561c0ce6_add_column_entry_id.py
index e5bc5735..f464849a 100644
--- a/migrations/versions/48f561c0ce6_add_column_entry_id.py
+++ b/migrations/versions/48f561c0ce6_add_column_entry_id.py
@@ -12,6 +12,7 @@ down_revision = None
branch_labels = None
depends_on = None
+import conf
from alembic import op
import sqlalchemy as sa
@@ -21,4 +22,5 @@ def upgrade():
def downgrade():
- op.drop_column('article', 'entry_id')
+ if 'sqlite' not in conf.SQLALCHEMY_DATABASE_URI:
+ op.drop_column('article', 'entry_id')
diff --git a/migrations/versions/cde34831ea_adding_feed_and_user_attributes_for_.py b/migrations/versions/cde34831ea_adding_feed_and_user_attributes_for_.py
index 03e96564..116fdaa1 100644
--- a/migrations/versions/cde34831ea_adding_feed_and_user_attributes_for_.py
+++ b/migrations/versions/cde34831ea_adding_feed_and_user_attributes_for_.py
@@ -34,10 +34,11 @@ def upgrade():
def downgrade():
# commands auto generated by Alembic - please adjust! ###
- op.drop_column('user', 'refresh_rate')
- op.drop_column('feed', 'last_modified')
- op.drop_column('feed', 'last_error')
- op.drop_column('feed', 'error_count')
- op.drop_column('feed', 'last_retrieved')
- op.drop_column('feed', 'etag')
+ if 'sqlite' not in conf.SQLALCHEMY_DATABASE_URI:
+ op.drop_column('user', 'refresh_rate')
+ op.drop_column('feed', 'last_modified')
+ op.drop_column('feed', 'last_error')
+ op.drop_column('feed', 'error_count')
+ op.drop_column('feed', 'last_retrieved')
+ op.drop_column('feed', 'etag')
# end Alembic commands ###
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">&times;</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">&times;</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())
diff --git a/tests/controllers/article.py b/tests/controllers/article.py
index 9a635fa7..5a54e0b7 100644
--- a/tests/controllers/article.py
+++ b/tests/controllers/article.py
@@ -1,15 +1,18 @@
from tests.base import BasePyaggTest
from pyaggr3g470r.controllers import ArticleController
+from pyaggr3g470r.controllers import FeedController
class ArticleControllerTest(BasePyaggTest):
_contr_cls = ArticleController
- def test_controller(self):
+ def test_article_rights(self):
article = ArticleController(2).read()[0].dump()
self.assertFalse(article['readed'])
article['readed'] = True # article get read when retreived through get
self._test_controller_rights(article, article['user_id'])
+
+ def test_article_challange_method(self):
self.assertEquals(0, len(list(ArticleController().challenge(
[{'id': art.id} for art in ArticleController(3).read()]))))
self.assertEquals(9, len(list(ArticleController(2).challenge(
@@ -17,7 +20,98 @@ class ArticleControllerTest(BasePyaggTest):
self.assertEquals(9, len(list(ArticleController(2).challenge(
[{'entry_id': art.id} for art in ArticleController(3).read()]
))))
- self.assertEquals({1: 2, 2: 3, 3: 3},
- ArticleController(2).get_unread())
+
+ def test_article_get_unread(self):
+ self.assertEquals({1: 3, 2: 3, 3: 3},
+ ArticleController(2).count_by_feed(readed=False))
self.assertEquals({4: 3, 5: 3, 6: 3},
- ArticleController(3).get_unread())
+ ArticleController(3).count_by_feed(readed=False))
+
+ def test_create_using_filters(self):
+ feed_ctr = FeedController(2)
+ feed1 = feed_ctr.read()[0].dump()
+ feed2 = feed_ctr.read()[1].dump()
+ feed3 = feed_ctr.read()[2].dump()
+ feed_ctr.update({'id': feed1['id']},
+ {'filters': [{"type": "simple match",
+ "pattern": "no see pattern",
+ "action on": "match",
+ "action": "mark as read"}]})
+ feed_ctr.update({'id': feed3['id']},
+ {'filters': [{"type": "regex",
+ "pattern": ".*(pattern1|pattern2).*",
+ "action on": "no match",
+ "action": "mark as favorite"},
+ {"type": "simple match",
+ "pattern": "no see pattern",
+ "action on": "match",
+ "action": "mark as read"}]})
+ art1 = ArticleController(2).create(
+ entry_id="thisisnotatest",
+ feed_id=feed1['id'],
+ title="garbage no see pattern garbage",
+ content="doesn't matter",
+ link="doesn't matter either")
+ art2 = ArticleController(2).create(
+ entry_id="thisisnotatesteither",
+ feed_id=feed1['id'],
+ title="garbage see pattern garbage",
+ content="doesn't matter2",
+ link="doesn't matter either2")
+
+ art3 = ArticleController(2).create(
+ entry_id="thisisnotatest",
+ user_id=2,
+ feed_id=feed2['id'],
+ title="garbage no see pattern garbage",
+ content="doesn't matter",
+ link="doesn't matter either")
+ art4 = ArticleController(2).create(
+ entry_id="thisisnotatesteither",
+ user_id=2,
+ feed_id=feed2['id'],
+ title="garbage see pattern garbage",
+ content="doesn't matter2",
+ link="doesn't matter either2")
+
+ art5 = ArticleController(2).create(
+ entry_id="thisisnotatest",
+ feed_id=feed3['id'],
+ title="garbage pattern1 garbage",
+ content="doesn't matter",
+ link="doesn't matter either")
+ art6 = ArticleController(2).create(
+ entry_id="thisisnotatesteither",
+ feed_id=feed3['id'],
+ title="garbage pattern2 garbage",
+ content="doesn't matter2",
+ link="doesn't matter either2")
+ art7 = ArticleController(2).create(
+ entry_id="thisisnotatesteither",
+ feed_id=feed3['id'],
+ title="garbage no see pattern3 garbage",
+ content="doesn't matter3",
+ link="doesn't matter either3")
+ art8 = ArticleController(2).create(
+ entry_id="thisisnotatesteither",
+ feed_id=feed3['id'],
+ title="garbage pattern4 garbage",
+ content="doesn't matter4",
+ link="doesn't matter either4")
+
+ self.assertTrue(art1.readed)
+ self.assertFalse(art1.like)
+ self.assertFalse(art2.readed)
+ self.assertFalse(art2.like)
+ self.assertFalse(art3.readed)
+ self.assertFalse(art3.like)
+ self.assertFalse(art4.readed)
+ self.assertFalse(art4.like)
+ self.assertFalse(art5.readed)
+ self.assertFalse(art5.like)
+ self.assertFalse(art6.readed)
+ self.assertFalse(art6.like)
+ self.assertTrue(art7.readed)
+ self.assertTrue(art7.like)
+ self.assertFalse(art8.readed)
+ self.assertTrue(art8.like)
diff --git a/tests/controllers/feed.py b/tests/controllers/feed.py
index 924b8ddd..863ac0bb 100644
--- a/tests/controllers/feed.py
+++ b/tests/controllers/feed.py
@@ -6,18 +6,24 @@ from pyaggr3g470r.controllers import ArticleController
class FeedControllerTest(BasePyaggTest):
_contr_cls = FeedController
- def test_controller(self):
+ def test_feed_rights(self):
feed = FeedController(2).read()[0].dump()
self.assertTrue(3,
ArticleController().read(feed_id=feed['id']).count())
self._test_controller_rights(feed, feed['user_id'])
# checking articles are deleted after the feed has been deleted
+
+ def test_feed_article_deletion(self):
+ feed_ctr = FeedController(2)
+ feed = feed_ctr.read()[0].dump()
+ feed_ctr.delete(feed['id'])
self.assertFalse(0,
ArticleController().read(feed_id=feed['id']).count())
+ def test_feed_list_fetchable(self):
self.assertEquals(3, len(FeedController(3).list_fetchable()))
self.assertEquals(0, len(FeedController(3).list_fetchable()))
- self.assertEquals(2, len(FeedController().list_fetchable()))
+ self.assertEquals(3, len(FeedController().list_fetchable()))
self.assertEquals(0, len(FeedController().list_fetchable()))
self.assertEquals(3,
len(FeedController(3).list_fetchable(refresh_rate=0)))
bgstack15