diff options
-rw-r--r-- | pyaggr3g470r/templates/admin/create_user.html | 29 | ||||
-rw-r--r-- | pyaggr3g470r/templates/admin/dashboard.html | 44 | ||||
-rw-r--r-- | pyaggr3g470r/templates/admin/user.html | 49 | ||||
-rw-r--r-- | pyaggr3g470r/templates/home.html | 2 | ||||
-rw-r--r-- | pyaggr3g470r/templates/layout.html | 3 | ||||
-rw-r--r-- | pyaggr3g470r/views.py | 120 |
6 files changed, 245 insertions, 2 deletions
diff --git a/pyaggr3g470r/templates/admin/create_user.html b/pyaggr3g470r/templates/admin/create_user.html new file mode 100644 index 00000000..5a6838bd --- /dev/null +++ b/pyaggr3g470r/templates/admin/create_user.html @@ -0,0 +1,29 @@ +{% extends "layout.html" %} +{% block head%} +{{super()}} +{% endblock %} +{% block content %} +<div class="container"> + <div class="jumbotron"> + <h2>{{ message|safe }}</h2> + <form action="" method="post" name="saveprofileform" id="profileform"> + {{ form.hidden_tag() }} + + {{ form.firstname.label }} + {{ form.firstname(class_="form-control") }} {% for error in form.firstname.errors %} <span style="color: red;">{{ error }}<br /></span>{% endfor %} + + {{ form.lastname.label }} + {{ form.lastname(class_="form-control") }} {% for error in form.lastname.errors %} <span style="color: red;">{{ error }}<br /></span>{% endfor %} + + {{ form.email.label }} + {{ form.email(class_="form-control") }} {% for error in form.email.errors %} <span style="color: red;">{{ error }}<br /></span>{% endfor %} + + {{ form.password.label }} + {{ form.password(class_="form-control") }} {% for error in form.password.errors %} <span style="color: red;">{{ error }}<br /></span>{% endfor %} + + <br /> + {{ form.submit(class_="btn") }} + </form> + </div> +</div> +{% endblock %}
\ No newline at end of file diff --git a/pyaggr3g470r/templates/admin/dashboard.html b/pyaggr3g470r/templates/admin/dashboard.html new file mode 100644 index 00000000..2913d603 --- /dev/null +++ b/pyaggr3g470r/templates/admin/dashboard.html @@ -0,0 +1,44 @@ +{% extends "layout.html" %} +{% block head%} +{{super()}} +{% endblock %} +{% block content %} +<div class="container"> +<h1>Registered users</h1> +<table class="table table-striped"> + <thead> + <tr> + <th>#</th> + <th>Firstname</th> + <th>Lastname</th> + <th>Email</th> + <th>Actions</th> + </tr> + </thead> + <tbody> + {% for user in users|sort(attribute="firstname") %} + <tr> + <td>{{ loop.index }}</td> + <td><a href="/admin/user/{{ user.id }}/">{{ user.firstname }}</a></td> + <td>{{ user.lastname }}</td> + <td>{{ user.email }}</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> + {% if user.apikey == "" %} + <a href="/admin/enable_user/{{ user.id }}/"><i class="glyphicon glyphicon-ok-circle" title="Enable API key of this user"></i></a> + {% else %} + <a href="/admin/disable_user/{{ user.id }}/"><i class="glyphicon glyphicon-ban-circle" title="Disable API key of this user"></i></a> + {% endif %} + <a href="/admin/delete_user/{{ user.id }}/"><i class="glyphicon glyphicon-remove" title="Delete this user"></i></a> + </td> + </tr> + {% endfor %} + </tbody> +</table> +<br /> +<a href="/admin/create_user/" class="btn btn-default">Add a new user</a> +<br /><br /> +<p class="text-info">As an administrator you are not listed in this table.</p> +</div> +{% endblock %}
\ No newline at end of file diff --git a/pyaggr3g470r/templates/admin/user.html b/pyaggr3g470r/templates/admin/user.html new file mode 100644 index 00000000..767ff460 --- /dev/null +++ b/pyaggr3g470r/templates/admin/user.html @@ -0,0 +1,49 @@ +{% extends "layout.html" %} +{% block head%} +{{super()}} +{% endblock %} +{% block content %} +<div class="container"> + <div class="jumbotron"> + <a href="/admin/edit_user/{{ user.id }}/" class="btn btn-default">Edit this user</a> + <h2>Membership</h2> + <p>Contributor since {{ user.date_created.strftime('%A, %d %B %Y') }}.</p> + <p>Last seen: {{ user.last_seen.strftime('%A, %d %B %Y at %H:%M:%S') }}.</p> + </div> + <div class="jumbotron"> + {% if user.feeds.all()|count == 0 %} + <h1>This user is not subscribed to any feed.</h1> + {% else %} + <h1>Feeds</h1> + <table class="table table-striped"> + <thead> + <tr> + <th>#</th> + <th>Name</th> + <th>Feed link</th> + <th>Site link</th> + <th>Number of articles</th> + <th>Actions</th> + </tr> + </thead> + <tbody> + {% for feed in user.feeds|sort(attribute="name") %} + <tr> + <td>{{ loop.index }}</td> + <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> + <a href="/feed/{{ feed.id }}"><i class="glyphicon glyphicon-th-list" title="Measures"></i></a> + <a href="/edit_feed/{{ feed.id }}"><i class="glyphicon glyphicon-edit" title="Edit this feed"></i></a> + <a href="/delete_feed/{{ feed.id }}"><i class="glyphicon glyphicon-remove" title="Delete this feed"></i></a> + </td> + {% endfor %} + </tbody> + </table> + {% endif %} + <a href="/create_feed/" class="btn btn-default">Add a new feed</a> + </div> +</div> +{% endblock %}
\ No newline at end of file diff --git a/pyaggr3g470r/templates/home.html b/pyaggr3g470r/templates/home.html index 223653c1..6363ee06 100644 --- a/pyaggr3g470r/templates/home.html +++ b/pyaggr3g470r/templates/home.html @@ -2,7 +2,7 @@ {% block content %} <div class="container"> {% if user.feeds.all()|count == 0 %} - <h1>You are not subscribed to any feed. <a href="/edit_feed/">Fix this</a>.</h1> + <h1>You are not subscribed to any feed. <a href="/create_feed/">Fix this</a>.</h1> {% else %} {% for feed in user.feeds|sort(attribute="title") %} <div class="row"> diff --git a/pyaggr3g470r/templates/layout.html b/pyaggr3g470r/templates/layout.html index e89706e9..71572181 100644 --- a/pyaggr3g470r/templates/layout.html +++ b/pyaggr3g470r/templates/layout.html @@ -56,6 +56,9 @@ </ul> </li> <li><a accesskey="m" href="/management/">Management</a></li> + {% if g.user.is_admin() %} + <li><a href="{{ url_for('dashboard') }}"><span class="glyphicon glyphicon-dashboard"></span> Dashboard</a></li> + {% endif %} <li><a href="/about/">About</a></li> <li><a href="{{ url_for('logout') }}">Logout</a></li> <li class="dropdown"> diff --git a/pyaggr3g470r/views.py b/pyaggr3g470r/views.py index 1a5d45a9..7ca3ac02 100644 --- a/pyaggr3g470r/views.py +++ b/pyaggr3g470r/views.py @@ -31,6 +31,7 @@ import datetime from flask import render_template, jsonify, request, flash, session, url_for, redirect, g, current_app, make_response from flask.ext.login import LoginManager, login_user, logout_user, login_required, current_user, AnonymousUserMixin from flask.ext.principal import Principal, Identity, AnonymousIdentity, identity_changed, identity_loaded, Permission, RoleNeed, UserNeed +from werkzeug import generate_password_hash import utils import export @@ -41,6 +42,10 @@ from forms import SigninForm, AddFeedForm, ProfileForm from pyaggr3g470r import app, db from pyaggr3g470r.models import User, Feed, Article, Role +Principal(app) +# Create a permission with a single Need, in this case a RoleNeed. +admin_permission = Permission(RoleNeed('admin')) + login_manager = LoginManager() login_manager.init_app(app) @@ -156,7 +161,7 @@ def logout(): identity_changed.send(current_app._get_current_object(), identity=AnonymousIdentity()) flash("Logged out successfully.", 'success') - return redirect(url_for('map_view')) + return redirect(url_for('login')) @app.route('/') @login_required @@ -556,3 +561,116 @@ def profile(): if request.method == 'GET': form = ProfileForm(obj=user) return render_template('profile.html', user=user, form=form) + + + +# +# Views dedicated to administration tasks. +# +@app.route('/admin/dashboard/', methods=['GET', 'POST']) +@login_required +@admin_permission.require() +def dashboard(): + """ + Adminstrator's dashboard. + """ + users = User.query.all() + users.remove(g.user) + return render_template('admin/dashboard.html', users=users) + +@app.route('/admin/create_user/', methods=['GET', 'POST']) +@app.route('/admin/edit_user/<int:user_id>/', methods=['GET', 'POST']) +@login_required +@admin_permission.require() +def create_user(user_id=None): + """ + Create or edit a user. + """ + form = ProfileForm() + + if request.method == 'POST': + if form.validate(): + if user_id != 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('User "' + user.firstname + '" successfully updated.', 'success') + else: + # Create a new user + role_user = Role.query.filter(Role.name == "user").first() + user = User(firstname=form.firstname.data, + lastname=form.lastname.data, + email=form.email.data, + pwdhash=generate_password_hash(form.password.data)) + user.roles.extend([role_user]) + db.session.add(user) + db.session.commit() + flash('User "' + user.firstname + '" successfully created.', 'success') + return redirect("/admin/edit_user/"+str(user.id)+"/") + else: + return render_template('profile.html', form=form) + + if request.method == 'GET': + if user_id != None: + user = User.query.filter(User.id == user_id).first() + form = ProfileForm(obj=user) + message = "Edit the user <i>" + user.firstname + "</i>" + else: + form = ProfileForm() + message="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() +def user(user_id=None): + """ + See information about a user (stations, etc.). + """ + user = User.query.filter(User.id == user_id).first() + if user is not None: + return render_template('/admin/user.html', user=user) + else: + flash('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() +def delete_user(user_id=None): + """ + Delete a user (with its stations and measures). + """ + user = User.query.filter(User.id == user_id).first() + if user is not None: + db.session.delete(user) + db.session.commit() + flash('User "' + user.firstname + '" successfully deleted.', 'success') + else: + flash('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 API key of a user. + """ + user = User.query.filter(User.id == user_id).first() + if user is not None: + if user.apikey != "": + user.pwdhash = "" + flash(user.firstname + '" successfully disabled.', 'success') + else: + #import random, base64, hashlib + user.pwdhash = "newpass" + flash(user.firstname + '" successfully enabled.', 'success') + db.session.commit() + else: + flash('This user does not exist.', 'danger') + return redirect(redirect_url())
\ No newline at end of file |