aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--pyaggr3g470r/controllers/category.py6
-rw-r--r--pyaggr3g470r/templates/categories.html36
-rw-r--r--pyaggr3g470r/views/category.py36
-rw-r--r--src/web/controllers/abstract.py13
-rw-r--r--src/web/controllers/article.py9
-rw-r--r--src/web/controllers/feed.py8
-rw-r--r--src/web/forms.py7
-rw-r--r--src/web/templates/feed_list.html2
-rw-r--r--src/web/templates/feeds.html2
-rw-r--r--src/web/views/feed.py1
10 files changed, 99 insertions, 21 deletions
diff --git a/pyaggr3g470r/controllers/category.py b/pyaggr3g470r/controllers/category.py
index b6fc591c..a0f746e9 100644
--- a/pyaggr3g470r/controllers/category.py
+++ b/pyaggr3g470r/controllers/category.py
@@ -1,6 +1,12 @@
from .abstract import AbstractController
from pyaggr3g470r.models import Category
+from .feed import FeedController
class CategoryController(AbstractController):
_db_cls = Category
+
+ def delete(self, obj_id):
+ FeedController(self.user_id).update({'category_id': obj_id},
+ {'category_id': None})
+ return super().delete(obj_id)
diff --git a/pyaggr3g470r/templates/categories.html b/pyaggr3g470r/templates/categories.html
new file mode 100644
index 00000000..a61cc4b2
--- /dev/null
+++ b/pyaggr3g470r/templates/categories.html
@@ -0,0 +1,36 @@
+{% extends "layout.html" %}
+{% block content %}
+<div class="container">
+ <h1>{{ _("You have %(categories)d categories &middot; Add a %(start_link)scategory%(end_link)s", categories=categories|count, start_link=("<a href='%s'>" % url_for("category.form"))|safe, end_link="</a>"|safe) }}</h1>
+ {% if categories|count == 0 %}
+ <h1>{{_("No category")}}</h1>
+ {% else %}
+ <div class="table-responsive">
+ <table class="table table-striped">
+ <thead>
+ <tr>
+ <th>#</th>
+ <th>{{ _('Name') }}</th>
+ <th>{{ _('Feeds') }}</th>
+ <th>{{ _('Articles') }}</th>
+ <th>{{ _('Actions') }}</th>
+ </tr>
+ </thead>
+ <tbody>
+ {% for category in categories|sort(attribute="name") %}
+ <tr>
+ <td>{{ loop.index }}</td>
+ <td>{{ category.name }}</td>
+ <td>{{ feeds_count.get(category.id, 0) }}</td>
+ <td>( {{ unread_article_count.get(category.id, 0) }} ) {{ article_count.get(category.id, 0) }}</td>
+ <td>
+ <a href="{{ url_for("category.form", category_id=category.id) }}"><i class="glyphicon glyphicon-edit" title='{{ _("Edit this category") }}'></i></a>
+ <a href="{{ url_for("category.delete", category_id=category.id) }}"><i class="glyphicon glyphicon-remove" title='{{ _("Delete this category") }}' onclick="return confirm('{{ _('You are going to delete this category.') }}');"></i></a>
+ </td>
+ </tr>
+ {% endfor %}
+ </tbody>
+ </table>
+ </div>
+ {% endif %}
+{% endblock %}
diff --git a/pyaggr3g470r/views/category.py b/pyaggr3g470r/views/category.py
index c5defb7f..027a800f 100644
--- a/pyaggr3g470r/views/category.py
+++ b/pyaggr3g470r/views/category.py
@@ -2,14 +2,29 @@ from flask import g, Blueprint, render_template, flash, redirect, url_for
from flask.ext.babel import gettext
from flask.ext.login import login_required
-from pyaggr3g470r.forms import AddCategoryForm
+from pyaggr3g470r.forms import CategoryForm
+from pyaggr3g470r.lib.utils import redirect_url
from pyaggr3g470r.lib.view_utils import etag_match
-from pyaggr3g470r.controllers.category import CategoryController
+from pyaggr3g470r.controllers \
+ import ArticleController, FeedController, CategoryController
categories_bp = Blueprint('categories', __name__, url_prefix='/categories')
category_bp = Blueprint('category', __name__, url_prefix='/category')
+@categories_bp.route('/', methods=['GET'])
+@login_required
+@etag_match
+def list_():
+ "Lists the subscribed feeds in a table."
+ art_contr = ArticleController(g.user.id)
+ return render_template('categories.html',
+ categories=list(CategoryController(g.user.id).read()),
+ feeds_count=FeedController(g.user.id).count_by_category(),
+ unread_article_count=art_contr.count_by_category(readed=False),
+ article_count=art_contr.count_by_category())
+
+
@category_bp.route('/create', methods=['GET'])
@category_bp.route('/edit/<int:category_id>', methods=['GET'])
@login_required
@@ -19,7 +34,7 @@ def form(category_id=None):
head_titles = [action]
if category_id is None:
return render_template('edit_category.html', action=action,
- head_titles=head_titles, form=AddCategoryForm())
+ head_titles=head_titles, form=CategoryForm())
category = CategoryController(g.user.id).get(id=category_id)
action = gettext('Edit category')
head_titles = [action]
@@ -27,14 +42,23 @@ def form(category_id=None):
head_titles.append(category.name)
return render_template('edit_category.html', action=action,
head_titles=head_titles, category=category,
- form=AddCategoryForm(obj=category))
+ form=CategoryForm(obj=category))
+
+
+@category_bp.route('/delete/<int:category_id>', methods=['GET'])
+@login_required
+def delete(category_id=None):
+ category = CategoryController(g.user.id).delete(category_id)
+ flash(gettext("Category %(category_name)s successfully deleted.",
+ category_name=category.name), 'success')
+ return redirect(redirect_url())
@category_bp.route('/create', methods=['POST'])
-@category_bp.route('/edit/<int:cat_id>', methods=['POST'])
+@category_bp.route('/edit/<int:category_id>', methods=['POST'])
@login_required
def process_form(category_id=None):
- form = AddCategoryForm()
+ form = CategoryForm()
cat_contr = CategoryController(g.user.id)
if not form.validate():
diff --git a/src/web/controllers/abstract.py b/src/web/controllers/abstract.py
index 99d92ff3..828e6a29 100644
--- a/src/web/controllers/abstract.py
+++ b/src/web/controllers/abstract.py
@@ -1,7 +1,7 @@
import logging
from flask import g
from bootstrap import db
-from sqlalchemy import or_
+from sqlalchemy import or_, func
from werkzeug.exceptions import Forbidden, NotFound
logger = logging.getLogger(__name__)
@@ -83,12 +83,12 @@ class AbstractController(object):
return obj
def create(self, **attrs):
+ if self._user_id_key is not None and self._user_id_key not in attrs:
+ attrs[self._user_id_key] = self.user_id
assert self._user_id_key is None or self._user_id_key in attrs \
or self.user_id is None, \
"You must provide user_id one way or another"
- if self._user_id_key is not None and self._user_id_key not in attrs:
- attrs[self._user_id_key] = self.user_id
obj = self._db_cls(**attrs)
db.session.add(obj)
db.session.commit()
@@ -114,3 +114,10 @@ class AbstractController(object):
return True
return self.user_id is None \
or getattr(obj, self._user_id_key, None) == self.user_id
+
+ def _count_by(self, elem_to_group_by, filters):
+ if self.user_id:
+ filters['user_id'] = self.user_id
+ return dict(db.session.query(elem_to_group_by, func.count('id'))
+ .filter(*self._to_filters(**filters))
+ .group_by(elem_to_group_by).all())
diff --git a/src/web/controllers/article.py b/src/web/controllers/article.py
index 72288a09..50e6757f 100644
--- a/src/web/controllers/article.py
+++ b/src/web/controllers/article.py
@@ -28,12 +28,11 @@ class ArticleController(AbstractController):
continue
yield id_
+ def count_by_category(self, **filters):
+ return self._count_by(Article.category_id, filters)
+
def count_by_feed(self, **filters):
- if self.user_id:
- filters['user_id'] = self.user_id
- return dict(db.session.query(Article.feed_id, func.count(Article.id))
- .filter(*self._to_filters(**filters))
- .group_by(Article.feed_id).all())
+ return self._count_by(Article.feed_id, filters)
def count_by_user_id(self, **filters):
return dict(db.session.query(Article.user_id, func.count(Article.id))
diff --git a/src/web/controllers/feed.py b/src/web/controllers/feed.py
index b76c4e42..31a1ec41 100644
--- a/src/web/controllers/feed.py
+++ b/src/web/controllers/feed.py
@@ -87,6 +87,9 @@ class FeedController(AbstractController):
inactives.sort(key=lambda tup: tup[1], reverse=True)
return inactives
+ def count_by_category(self, **filters):
+ return self._count_by(Feed.category_id, filters)
+
def _ensure_icon(self, attrs):
if not attrs.get('icon_url'):
return
@@ -101,10 +104,9 @@ class FeedController(AbstractController):
def update(self, filters, attrs):
from .article import ArticleController
self._ensure_icon(attrs)
- result = super().update(filters, attrs)
if 'category_id' in attrs:
art_contr = ArticleController(self.user_id)
for feed in self.read(**filters):
art_contr.update({'feed_id': feed.id},
- {'category_id': feed.category_id})
- return result
+ {'category_id': attrs['category_id']})
+ return super().update(filters, attrs)
diff --git a/src/web/forms.py b/src/web/forms.py
index f57b31d0..9dbf1b5f 100644
--- a/src/web/forms.py
+++ b/src/web/forms.py
@@ -38,6 +38,7 @@ from flask_wtf import RecaptchaField
from web import utils
from web.models import User
+
class SignupForm(Form):
"""
Sign up form (registration to jarr).
@@ -63,6 +64,7 @@ class SignupForm(Form):
validated = False
return validated
+
class RedirectForm(Form):
"""
Secure back redirects with WTForms.
@@ -80,6 +82,7 @@ class RedirectForm(Form):
target = utils.get_redirect_target()
return redirect(target or url_for(endpoint, **values))
+
class SigninForm(RedirectForm):
"""
Sign in form (connection to jarr).
@@ -179,9 +182,9 @@ class AddFeedForm(Form):
for cat in categories]
-class AddCategoryForm(Form):
+class CategoryForm(Form):
name = TextField(lazy_gettext("Name"))
- submit = SubmitField(lazy_gettext("Sign up"))
+ submit = SubmitField(lazy_gettext("Submit"))
class InformationMessageForm(Form):
diff --git a/src/web/templates/feed_list.html b/src/web/templates/feed_list.html
index 27815250..51561dee 100644
--- a/src/web/templates/feed_list.html
+++ b/src/web/templates/feed_list.html
@@ -1,4 +1,4 @@
-{% if feeds.all()| count == 0 %}
+{% if feeds| count == 0 %}
<h1>{{_("No feed")}}</h1>
{% else %}
<div class="table-responsive">
diff --git a/src/web/templates/feeds.html b/src/web/templates/feeds.html
index 9ba16359..074957f7 100644
--- a/src/web/templates/feeds.html
+++ b/src/web/templates/feeds.html
@@ -1,7 +1,7 @@
{% extends "layout.html" %}
{% block content %}
<div class="container">
- <h1>{{ _('You are subscribed to') }} {{ feeds.count() }} {{ _('feeds') }} &middot; {{ _('Add a') }} <a href="{{ url_for("feed.form") }}">{{ _('feed') }}</a></h1>
+ <h1>{{ _('You are subscribed to') }} {{ feeds|count }} {{ _('feeds') }} &middot; {{ _('Add a') }} <a href="{{ url_for("feed.form") }}">{{ _('feed') }}</a></h1>
{% include "feed_list.html" %}
</div><!-- /.container -->
{% endblock %}
diff --git a/src/web/views/feed.py b/src/web/views/feed.py
index 6569d1b7..e1c2fb55 100644
--- a/src/web/views/feed.py
+++ b/src/web/views/feed.py
@@ -14,6 +14,7 @@ from flask.ext.login import login_required
import conf
from web import utils
+from web.lib.utils import redirect_url
from web.lib.view_utils import etag_match
from web.lib.feed_utils import construct_feed_from
from web.forms import AddFeedForm
bgstack15