diff options
Diffstat (limited to 'src/web')
-rw-r--r-- | src/web/controllers/abstract.py | 2 | ||||
-rw-r--r-- | src/web/controllers/user.py | 25 | ||||
-rw-r--r-- | src/web/models/user.py | 10 | ||||
-rw-r--r-- | src/web/templates/admin/dashboard.html | 18 | ||||
-rw-r--r-- | src/web/templates/home.html | 2 | ||||
-rw-r--r-- | src/web/templates/layout.html | 6 | ||||
-rwxr-xr-x | src/web/utils.py | 2 | ||||
-rw-r--r-- | src/web/views/__init__.py | 2 | ||||
-rw-r--r-- | src/web/views/views.py | 274 |
9 files changed, 47 insertions, 294 deletions
diff --git a/src/web/controllers/abstract.py b/src/web/controllers/abstract.py index f33d241e..99d92ff3 100644 --- a/src/web/controllers/abstract.py +++ b/src/web/controllers/abstract.py @@ -84,7 +84,7 @@ class AbstractController(object): def create(self, **attrs): assert self._user_id_key is None or self._user_id_key in attrs \ - or self.user_id is not None, \ + 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: diff --git a/src/web/controllers/user.py b/src/web/controllers/user.py index 3f96b185..d8bf1fa1 100644 --- a/src/web/controllers/user.py +++ b/src/web/controllers/user.py @@ -1,3 +1,6 @@ +import random +import hashlib +from werkzeug import generate_password_hash from .abstract import AbstractController from web.models import User @@ -5,3 +8,25 @@ from web.models import User class UserController(AbstractController): _db_cls = User _user_id_key = 'id' + + def unset_activation_key(self, obj_id): + self.update({'id': obj_id}, {'activation_key': ""}) + + def set_activation_key(self, obj_id): + key = str(random.getrandbits(256)).encode("utf-8") + key = hashlib.sha512(key).hexdigest()[:86] + self.update({'id': obj_id}, {'activation_key': key}) + + def _handle_password(self, attrs): + if attrs.get('password'): + attrs['pwdhash'] = generate_password_hash(attrs.pop('password')) + elif 'password' in attrs: + del attrs['password'] + + def create(self, **attrs): + self._handle_password(attrs) + return super().create(**attrs) + + def update(self, filters, attrs): + self._handle_password(attrs) + return super().update(filters, attrs) diff --git a/src/web/models/user.py b/src/web/models/user.py index c5e70036..d1b9c568 100644 --- a/src/web/models/user.py +++ b/src/web/models/user.py @@ -30,7 +30,7 @@ import re import random import hashlib from datetime import datetime -from werkzeug import generate_password_hash, check_password_hash +from werkzeug import check_password_hash from flask.ext.login import UserMixin from bootstrap import db @@ -63,12 +63,6 @@ class User(db.Model, UserMixin): """ return self.id - def set_password(self, password): - """ - Hash the password of the user. - """ - self.pwdhash = generate_password_hash(password) - def check_password(self, password): """ Check the password of the user. @@ -79,7 +73,7 @@ class User(db.Model, UserMixin): """ Return True if the user has administrator rights. """ - return len([role for role in self.roles if role.name == "admin"]) != 0 + return "admin" in [role.name for role in self.roles] def __eq__(self, other): return self.id == other.id diff --git a/src/web/templates/admin/dashboard.html b/src/web/templates/admin/dashboard.html index 25bd3883..2436c955 100644 --- a/src/web/templates/admin/dashboard.html +++ b/src/web/templates/admin/dashboard.html @@ -20,29 +20,31 @@ <tr {% if user.activation_key != "" %}class="warning"{% endif %}> <td>{{ loop.index }}</td> {% if user.id == current_user.id %} - <td><a href="/management">{{ user.nickname }}</a> (It's you!)</td> + <td><a href="{{ url_for("user.management") }}">{{ user.nickname }}</a> (It's you!)</td> {% else %} - <td><a href="/admin/user/{{ user.id }}">{{ user.nickname }}</a></td> + <td><a href="{{ url_for("admin.user", user_id=user.id) }}">{{ user.nickname }}</a></td> {% endif %} <td><a href="mailto:{{ user.email }}">{{ user.email }}</a></td> <td class="date">{{ user.last_seen }}</td> <td> - <a href="/admin/user/{{ user.id }}"><i class="glyphicon glyphicon-user" title="{{ _('View this user') }}"></i></a> - <a href="/admin/edit_user/{{ user.id }}"><i class="glyphicon glyphicon-edit" title="{{ _('Edit this user') }}"></i></a> + <a href="{{ url_for("admin.user", user_id=user.id) }}"><i class="glyphicon glyphicon-user" title="{{ _('View this user') }}"></i></a> + <a href="{{ url_for("admin.user_form", user_id=user.id) }}"><i class="glyphicon glyphicon-edit" title="{{ _('Edit this user') }}"></i></a> {% if user.id != current_user.id %} + <a href="{{ url_for("admin.toggle_user", user_id=user.id) }}"> {% if user.activation_key == "" %} - <a href="/admin/disable_user/{{ user.id }}"><i class="glyphicon glyphicon-ban-circle" title="Disable this account"></i></a> + <i class="glyphicon glyphicon-ban-circle" title="{{ _("Disable this account") }}"></i> {% else %} - <a href="/admin/enable_user/{{ user.id }}"><i class="glyphicon glyphicon-ok-circle" title="Enable this account"></i></a> + <i class="glyphicon glyphicon-ok-circle" title="{{ _("Enable this account") }}"></i> {% endif %} - <a href="/admin/delete_user/{{ user.id }}"><i class="glyphicon glyphicon-remove" title="{{ _('Delete this user') }}" onclick="return confirm('{{ _('You are going to delete this account.') }}');"></i></a> + </a> + <a href="{{ url_for("admin.delete_user", user_id=user.id) }}"><i class="glyphicon glyphicon-remove" title="{{ _('Delete this user') }}" onclick="return confirm('{{ _('You are going to delete this account.') }}');"></i></a> {% endif %} </td> </tr> {% endfor %} </tbody> </table> -<a href="/admin/create_user" class="btn btn-default">{{ _('Add a new user') }}</a> +<a href="{{ url_for("admin.user_form") }}" class="btn btn-default">{{ _('Add a new user') }}</a> <h1>{{ _('Send notification messages') }}</h1> <form action="" method="post"> {{ form.hidden_tag() }} diff --git a/src/web/templates/home.html b/src/web/templates/home.html index 86d96e94..6b136870 100644 --- a/src/web/templates/home.html +++ b/src/web/templates/home.html @@ -3,7 +3,7 @@ {% if feeds|count == 0 %} <div class="col-md-4 col-md-offset-4"> <h1>{{ _("You don't have any feeds.") }}</h1> - <h1><a href="{{ url_for("feed.form") }}">{{ _('Add some') }}</a>, {{ _('or') }} <a href="/management">{{ _('upload an OPML file.') }}</a></h1> + <h1><a href="{{ url_for("feed.form") }}">{{ _('Add some') }}</a>, {{ _('or') }} <a href="{{ url_for("user.management") }}">{{ _('upload an OPML file.') }}</a></h1> </div> {% else %} <div class="container-fluid"> diff --git a/src/web/templates/layout.html b/src/web/templates/layout.html index cf2498e2..eb213ca5 100644 --- a/src/web/templates/layout.html +++ b/src/web/templates/layout.html @@ -81,12 +81,12 @@ <div><span class="glyphicon glyphicon-user"></span> <b class="caret"></b></div> </a> <ul class="dropdown-menu"> - <li><a href="{{ url_for("profile") }}"><span class="glyphicon glyphicon-user"></span> {{ _('Profile') }}</a></li> - <li><a href="{{ url_for("management") }}"><span class="glyphicon glyphicon-cog"></span> {{ _('Your data') }}</a></li> + <li><a href="{{ url_for("user.profile") }}"><span class="glyphicon glyphicon-user"></span> {{ _('Profile') }}</a></li> + <li><a href="{{ url_for("user.management") }}"><span class="glyphicon glyphicon-cog"></span> {{ _('Your data') }}</a></li> <li><a href="{{ url_for("about") }}"><span class="glyphicon glyphicon-question-sign"></span> {{ _('About') }}</a></li> {% if g.user.is_admin() %} <li role="presentation" class="divider"></li> - <li><a href="{{ url_for("dashboard") }}"><span class="glyphicon glyphicon-dashboard"></span> {{ _('Dashboard') }}</a></li> + <li><a href="{{ url_for("admin.dashboard") }}"><span class="glyphicon glyphicon-dashboard"></span> {{ _('Dashboard') }}</a></li> <li role="presentation" class="divider"></li> {% endif %} <li><a href="{{ url_for("logout") }}"><span class="glyphicon glyphicon-log-out"></span> {{ _('Logout') }}</a></li> diff --git a/src/web/utils.py b/src/web/utils.py index 38d15f25..6182eb0e 100755 --- a/src/web/utils.py +++ b/src/web/utils.py @@ -112,7 +112,7 @@ def fetch(id, feed_id=None): """ cmd = [sys.executable, conf.BASE_DIR+'/manager.py', 'fetch_asyncio', str(id), str(feed_id)] - p = subprocess.Popen(cmd, stdout=subprocess.PIPE) + return subprocess.Popen(cmd, stdout=subprocess.PIPE) def history(user_id, year=None, month=None): """ diff --git a/src/web/views/__init__.py b/src/web/views/__init__.py index 1da9205b..f02e86e6 100644 --- a/src/web/views/__init__.py +++ b/src/web/views/__init__.py @@ -5,3 +5,5 @@ from .article import article_bp, articles_bp from .feed import feed_bp, feeds_bp from .category import category_bp, categories_bp from .icon import icon_bp +from .admin import admin_bp +from .user import user_bp, users_bp diff --git a/src/web/views/views.py b/src/web/views/views.py index 1e7b7539..f543c6fa 100644 --- a/src/web/views/views.py +++ b/src/web/views/views.py @@ -53,8 +53,7 @@ from web import utils, notifications, export from web.lib.view_utils import etag_match from web.models import User, Feed, Article, Role from web.decorators import feed_access_required -from web.forms import SignupForm, SigninForm, InformationMessageForm,\ - ProfileForm, UserForm, RecoverPasswordForm \ +from web.forms import SignupForm, SigninForm from web.controllers import UserController, FeedController, \ ArticleController @@ -130,11 +129,6 @@ def internal_server_error(e): return render_template('errors/500.html'), 500 -def redirect_url(default='home'): - return request.args.get('next') or \ - request.referrer or \ - url_for(default) - @g.babel.localeselector def get_locale(): """ @@ -196,6 +190,7 @@ def logout(): flash(gettext("Logged out successfully."), 'success') return redirect(url_for('login')) + @app.route('/signup', methods=['GET', 'POST']) def signup(): """ @@ -423,94 +418,6 @@ def export_opml(): return response -@app.route('/management', methods=['GET', 'POST']) -@login_required -def management(): - """ - Display the management page. - """ - if request.method == 'POST': - if None != request.files.get('opmlfile', None): - # Import an OPML file - data = request.files.get('opmlfile', None) - if not utils.allowed_file(data.filename): - flash(gettext('File not allowed.'), 'danger') - else: - try: - nb = utils.import_opml(g.user.email, data.read()) - if conf.CRAWLING_METHOD == "classic": - utils.fetch(g.user.email, None) - flash(str(nb) + ' ' + gettext('feeds imported.'), - "success") - flash(gettext("Downloading articles..."), 'info') - except: - flash(gettext("Impossible to import the new feeds."), - "danger") - elif None != request.files.get('jsonfile', None): - # Import an account - data = request.files.get('jsonfile', None) - if not utils.allowed_file(data.filename): - flash(gettext('File not allowed.'), 'danger') - else: - try: - nb = utils.import_json(g.user.email, data.read()) - flash(gettext('Account imported.'), "success") - except: - flash(gettext("Impossible to import the account."), - "danger") - else: - flash(gettext('File not allowed.'), 'danger') - - nb_feeds = len(g.user.feeds.all()) - articles = Article.query.filter(Article.user_id == g.user.id) - nb_articles = articles.count() - nb_unread_articles = articles.filter(Article.readed == False).count() - return render_template('management.html', user=g.user, - nb_feeds=nb_feeds, nb_articles=nb_articles, - nb_unread_articles=nb_unread_articles) - - -@app.route('/profile', methods=['GET', 'POST']) -@login_required -def profile(): - """ - Edit the profile of the currently logged user. - """ - user = UserController(g.user.id).get(id=g.user.id) - form = ProfileForm() - - if request.method == 'POST': - if form.validate(): - form.populate_obj(user) - if form.password.data != "": - user.set_password(form.password.data) - db.session.commit() - flash("%s %s %s" % (gettext('User'), user.nickname, - gettext('successfully updated.')), - 'success') - return redirect(url_for('profile')) - else: - return render_template('profile.html', user=user, form=form) - - if request.method == 'GET': - form = ProfileForm(obj=user) - return render_template('profile.html', user=user, form=form) - -@app.route('/delete_account', methods=['GET']) -@login_required -def delete_account(): - """ - Delete the account of the user (with all its data). - """ - user = UserController(g.user.id).get(id=g.user.id) - if user is not None: - db.session.delete(user) - db.session.commit() - flash(gettext('Your account has been deleted.'), 'success') - else: - flash(gettext('This user does not exist.'), 'danger') - return redirect(url_for('login')) - @app.route('/expire_articles', methods=['GET']) @login_required def expire_articles(): @@ -526,180 +433,3 @@ def expire_articles(): flash(gettext('Articles deleted.'), 'info') db.session.commit() return redirect(redirect_url()) - -@app.route('/confirm_account/<string:activation_key>', methods=['GET']) -def confirm_account(activation_key=None): - """ - Confirm the account of a user. - """ - if activation_key != "": - user = User.query.filter(User.activation_key == activation_key).first() - if user is not None: - user.activation_key = "" - db.session.commit() - flash(gettext('Your account has been confirmed.'), 'success') - else: - flash(gettext('Impossible to confirm this account.'), 'danger') - return redirect(url_for('login')) - -@app.route('/recover', methods=['GET', 'POST']) -def recover(): - """ - Enables the user to recover its account when he has forgotten - its password. - """ - form = RecoverPasswordForm() - - if request.method == 'POST': - if form.validate(): - user = User.query.filter(User.email == form.email.data).first() - characters = string.ascii_letters + string.digits - password = "".join(random.choice(characters) for x in range(random.randint(8, 16))) - user.set_password(password) - db.session.commit() - - # Send the confirmation email - try: - notifications.new_password_notification(user, password) - flash(gettext('New password sent to your address.'), 'success') - except Exception as e: - flash(gettext('Problem while sending your new password.') + ': ' + str(e), 'danger') - - return redirect(url_for('login')) - return render_template('recover.html', form=form) - - if request.method == 'GET': - return render_template('recover.html', form=form) - -# -# Views dedicated to administration tasks. -# -@app.route('/admin/dashboard', methods=['GET', 'POST']) -@login_required -@admin_permission.require(http_exception=403) -def dashboard(): - """ - Adminstrator's dashboard. - """ - form = InformationMessageForm() - - if request.method == 'POST': - if form.validate(): - try: - notifications.information_message(form.subject.data, form.message.data) - except Exception as e: - flash(gettext('Problem while sending email') + ': ' + str(e), 'danger') - - users = User.query.all() - return render_template('admin/dashboard.html', users=users, current_user=g.user, form=form) - -@app.route('/admin/create_user', methods=['GET', 'POST']) -@app.route('/admin/edit_user/<int:user_id>', methods=['GET', 'POST']) -@login_required -@admin_permission.require(http_exception=403) -def create_user(user_id=None): - """ - Create or edit a user. - """ - form = UserForm() - - if request.method == 'POST': - if form.validate(): - role_user = Role.query.filter(Role.name == "user").first() - if user_id is not None: - # Edit a user - user = User.query.filter(User.id == user_id).first() - form.populate_obj(user) - if form.password.data != "": - user.set_password(form.password.data) - db.session.commit() - flash(gettext('User') + ' ' + user.nickname + ' ' + gettext('successfully updated.'), 'success') - else: - # Create a new user - user = User(nickname=form.nickname.data, - email=form.email.data, - pwdhash=generate_password_hash(form.password.data)) - user.roles.extend([role_user]) - user.activation_key = "" - db.session.add(user) - db.session.commit() - flash("%s %s %s" % (gettext('User'), user.nickname, - gettext('successfully created.')), - 'success') - return redirect(url_for('create_user', user_id=user.id)) - else: - return redirect(url_for('create_user')) - - if request.method == 'GET': - if user_id is not None: - user = User.query.filter(User.id == user_id).first() - form = UserForm(obj=user) - message = "%s <i>%s</i>" % (gettext('Edit the user'), - user.nickname) - else: - form = UserForm() - message = gettext('Add a new user') - return render_template('/admin/create_user.html', - form=form, message=message) - -@app.route('/admin/user/<int:user_id>', methods=['GET']) -@login_required -@admin_permission.require(http_exception=403) -def user(user_id=None): - """ - See information about a user (stations, etc.). - """ - user = UserController().get(id=user_id) - if user is not None: - article_contr = ArticleController(user_id) - return render_template('/admin/user.html', user=user, feeds=user.feeds, - 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()) - -@app.route('/admin/delete_user/<int:user_id>', methods=['GET']) -@login_required -@admin_permission.require(http_exception=403) -def delete_user(user_id=None): - """ - Delete a user (with all its data). - """ - user = User.query.filter(User.id == user_id).first() - if user is not None: - db.session.delete(user) - db.session.commit() - flash(gettext('User') + ' ' + user.nickname + ' ' + gettext('successfully deleted.'), 'success') - else: - flash(gettext('This user does not exist.'), 'danger') - return redirect(redirect_url()) - -@app.route('/admin/enable_user/<int:user_id>', methods=['GET']) -@app.route('/admin/disable_user/<int:user_id>', methods=['GET']) -@login_required -@admin_permission.require() -def disable_user(user_id=None): - """ - Enable or disable the account of a user. - """ - user = User.query.filter(User.id == user_id).first() - if user is not None: - if user.activation_key != "": - - # Send the confirmation email - try: - notifications.new_account_activation(user) - user.activation_key = "" - flash(gettext('Account of the user') + ' ' + user.nickname + ' ' + gettext('successfully activated.'), 'success') - except Exception as e: - flash(gettext('Problem while sending activation email') + ': ' + str(e), 'danger') - - else: - user.activation_key = hashlib.sha512(str(random.getrandbits(256)).encode("utf-8")).hexdigest()[:86] - flash(gettext('Account of the user') + ' ' + user.nickname + ' ' + gettext('successfully disabled.'), 'success') - db.session.commit() - else: - flash(gettext('This user does not exist.'), 'danger') - return redirect(redirect_url()) |