From 83081ad7e488c44757e43ff40e83458a2e1451ed Mon Sep 17 00:00:00 2001 From: Cédric Bonhomme Date: Tue, 1 Mar 2016 22:47:53 +0100 Subject: begin integration of the new architecture --- src/web/controllers/abstract.py | 46 +++++++++++++++++++++++++++++++++-------- src/web/controllers/article.py | 18 +++++++++++++--- src/web/controllers/feed.py | 28 ++++--------------------- src/web/controllers/user.py | 5 ++++- 4 files changed, 60 insertions(+), 37 deletions(-) (limited to 'src/web/controllers') diff --git a/src/web/controllers/abstract.py b/src/web/controllers/abstract.py index 828e6a29..2a2e6f9f 100644 --- a/src/web/controllers/abstract.py +++ b/src/web/controllers/abstract.py @@ -1,30 +1,29 @@ import logging -from flask import g +import dateutil.parser from bootstrap import db +from datetime import datetime +from collections import defaultdict from sqlalchemy import or_, func from werkzeug.exceptions import Forbidden, NotFound logger = logging.getLogger(__name__) -class AbstractController(object): +class AbstractController: _db_cls = None # reference to the database class _user_id_key = 'user_id' - def __init__(self, user_id=None): + def __init__(self, user_id=None, ignore_context=False): """User id is a right management mechanism that should be used to filter objects in database on their denormalized "user_id" field (or "id" field for users). Should no user_id be provided, the Controller won't apply any filter allowing for a kind of "super user" mode. """ - self.user_id = user_id try: - if self.user_id is not None \ - and self.user_id != g.user.id and not g.user.is_admin(): - self.user_id = g.user.id - except RuntimeError: # passing on out of context errors - pass + self.user_id = int(user_id) + except TypeError: + self.user_id = user_id def _to_filters(self, **filters): """ @@ -83,6 +82,7 @@ class AbstractController(object): return obj def create(self, **attrs): + assert attrs, "attributes to update must not be empty" 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 \ @@ -98,6 +98,7 @@ class AbstractController(object): return self._get(**filters) def update(self, filters, attrs): + assert attrs, "attributes to update must not be empty" result = self._get(**filters).update(attrs, synchronize_session=False) db.session.commit() return result @@ -121,3 +122,30 @@ class AbstractController(object): return dict(db.session.query(elem_to_group_by, func.count('id')) .filter(*self._to_filters(**filters)) .group_by(elem_to_group_by).all()) + + @classmethod + def _get_attrs_desc(cls, role, right=None): + result = defaultdict(dict) + if role == 'admin': + columns = cls._db_cls.__table__.columns.keys() + else: + assert role in {'base', 'api'}, 'unknown role %r' % role + assert right in {'read', 'write'}, \ + "right must be 'read' or 'write' with role %r" % role + columns = getattr(cls._db_cls, 'fields_%s_%s' % (role, right))() + for column in columns: + result[column] = {} + db_col = getattr(cls._db_cls, column).property.columns[0] + try: + result[column]['type'] = db_col.type.python_type + except NotImplementedError: + if db_col.default: + result[column]['type'] = db_col.default.arg.__class__ + if column not in result: + continue + if issubclass(result[column]['type'], datetime): + result[column]['default'] = datetime.utcnow() + result[column]['type'] = lambda x: dateutil.parser.parse(x) + elif db_col.default: + result[column]['default'] = db_col.default.arg + return result diff --git a/src/web/controllers/article.py b/src/web/controllers/article.py index bc9ef36e..8c6952cb 100644 --- a/src/web/controllers/article.py +++ b/src/web/controllers/article.py @@ -6,7 +6,7 @@ from collections import Counter from bootstrap import db from .abstract import AbstractController -from web.controllers import FeedController +from web.controllers import CategoryController, FeedController from web.models import Article logger = logging.getLogger(__name__) @@ -35,11 +35,12 @@ class ArticleController(AbstractController): def create(self, **attrs): # handling special denorm for article rights - assert 'feed_id' in attrs + assert 'feed_id' in attrs, "must provide feed_id when creating article" 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 + assert feed.user_id == attrs['user_id'] or self.user_id is None, \ + "no right on feed %r" % feed.id attrs['user_id'], attrs['category_id'] = feed.user_id, feed.category_id # handling feed's filters @@ -66,6 +67,17 @@ class ArticleController(AbstractController): return super().create(**attrs) + def update(self, filters, attrs): + user_id = attrs.get('user_id', self.user_id) + if 'feed_id' in attrs: + feed = FeedController().get(id=attrs['feed_id']) + assert feed.user_id == user_id, "no right on feed %r" % feed.id + attrs['category_id'] = feed.category_id + if attrs.get('category_id'): + cat = CategoryController().get(id=attrs['category_id']) + assert cat.user_id == user_id, "no right on cat %r" % cat.id + return super().update(filters, attrs) + def get_history(self, year=None, month=None): """ Sort articles by year and month. diff --git a/src/web/controllers/feed.py b/src/web/controllers/feed.py index 78caf2e1..95b1eceb 100644 --- a/src/web/controllers/feed.py +++ b/src/web/controllers/feed.py @@ -1,24 +1,3 @@ -#! /usr/bin/env python -# -*- coding: utf-8 -*- - -# jarr - A Web based news aggregator. -# Copyright (C) 2010-2016 Cédric Bonhomme - https://www.cedricbonhomme.org -# -# For more information : https://github.com/JARR-aggregator/JARR/ -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . - import logging import itertools from datetime import datetime, timedelta @@ -26,7 +5,7 @@ from datetime import datetime, timedelta import conf from .abstract import AbstractController from .icon import IconController -from web.models import Feed +from web.models import User, Feed from web.lib.utils import clear_string logger = logging.getLogger(__name__) @@ -43,11 +22,12 @@ class FeedController(AbstractController): return [feed for feed in self.read( error_count__lt=max_error, enabled=True, last_retrieved__lt=max_last) + .join(User).filter(User.is_active == True) .order_by('last_retrieved') .limit(limit)] - def list_fetchable(self, max_error=DEFAULT_MAX_ERROR, limit=DEFAULT_LIMIT, - refresh_rate=DEFAULT_REFRESH_RATE): + def list_fetchable(self, max_error=DEFAULT_MAX_ERROR, + limit=DEFAULT_LIMIT, refresh_rate=DEFAULT_REFRESH_RATE): now = datetime.now() max_last = now - timedelta(minutes=refresh_rate) feeds = self.list_late(max_last, max_error, limit) diff --git a/src/web/controllers/user.py b/src/web/controllers/user.py index ae169b05..ee2eb4c2 100644 --- a/src/web/controllers/user.py +++ b/src/web/controllers/user.py @@ -1,6 +1,6 @@ import random import hashlib -from werkzeug import generate_password_hash +from werkzeug import generate_password_hash, check_password_hash from .abstract import AbstractController from web.models import User @@ -15,6 +15,9 @@ class UserController(AbstractController): elif 'password' in attrs: del attrs['password'] + def check_password(self, user, password): + return check_password_hash(user.pwdhash, password) + def create(self, **attrs): self._handle_password(attrs) return super().create(**attrs) -- cgit