From 75aacd279e6988cc8bc14cab7a2d643d40d1445c Mon Sep 17 00:00:00 2001 From: kaiyou <pierre@jaury.eu> Date: Sat, 14 Sep 2019 14:26:51 +0200 Subject: [PATCH] Add an event history and display it on the home page --- .../{3fe66efb6088_.py => b2fe21a1da94_.py} | 40 +++++++++++----- trurt/account/profiles.py | 11 +++-- trurt/account/settings.py | 3 +- trurt/account/templates/account_home.html | 28 +++++++++++ trurt/account/templates/account_pick.html | 14 ++++++ trurt/configuration.py | 3 +- trurt/manage.py | 13 +++++ trurt/models.py | 47 +++++++++++++++++-- trurt/templates/base.html | 1 + trurt/templates/macros.html | 36 ++++++++++++++ 10 files changed, 174 insertions(+), 22 deletions(-) rename migrations/versions/{3fe66efb6088_.py => b2fe21a1da94_.py} (59%) create mode 100644 trurt/account/templates/account_pick.html create mode 100644 trurt/templates/macros.html diff --git a/migrations/versions/3fe66efb6088_.py b/migrations/versions/b2fe21a1da94_.py similarity index 59% rename from migrations/versions/3fe66efb6088_.py rename to migrations/versions/b2fe21a1da94_.py index 0a2fc98..91a95e5 100644 --- a/migrations/versions/3fe66efb6088_.py +++ b/migrations/versions/b2fe21a1da94_.py @@ -1,14 +1,14 @@ """ empty message -Revision ID: 3fe66efb6088 +Revision ID: b2fe21a1da94 Revises: -Create Date: 2019-09-14 12:13:37.847931 +Create Date: 2019-09-14 13:27:34.971323 """ from alembic import op import sqlalchemy as sa -revision = '3fe66efb6088' +revision = 'b2fe21a1da94' down_revision = None branch_labels = None depends_on = None @@ -23,16 +23,16 @@ def upgrade(): sa.Column('max_profiles', sa.Integer(), nullable=True), sa.Column('config', sa.String(), nullable=True), sa.Column('uuid', sa.String(length=36), nullable=False), - sa.Column('created_at', sa.Date(), nullable=False), - sa.Column('updated_at', sa.Date(), nullable=True), + sa.Column('created_at', sa.DateTime(), nullable=False), + sa.Column('updated_at', sa.DateTime(), nullable=True), sa.Column('comment', sa.String(length=255), nullable=True), sa.PrimaryKeyConstraint('uuid') ) op.create_table('user', sa.Column('username', sa.String(length=255), nullable=False), sa.Column('uuid', sa.String(length=36), nullable=False), - sa.Column('created_at', sa.Date(), nullable=False), - sa.Column('updated_at', sa.Date(), nullable=True), + sa.Column('created_at', sa.DateTime(), nullable=False), + sa.Column('updated_at', sa.DateTime(), nullable=True), sa.Column('comment', sa.String(length=255), nullable=True), sa.PrimaryKeyConstraint('uuid'), sa.UniqueConstraint('username') @@ -42,8 +42,8 @@ def upgrade(): sa.Column('value', sa.String(), nullable=True), sa.Column('extra', sa.String(), nullable=True), sa.Column('uuid', sa.String(length=36), nullable=False), - sa.Column('created_at', sa.Date(), nullable=False), - sa.Column('updated_at', sa.Date(), nullable=True), + sa.Column('created_at', sa.DateTime(), nullable=False), + sa.Column('updated_at', sa.DateTime(), nullable=True), sa.Column('comment', sa.String(length=255), nullable=True), sa.ForeignKeyConstraint(['user_uuid'], ['user.uuid'], ), sa.PrimaryKeyConstraint('uuid') @@ -54,18 +54,36 @@ def upgrade(): sa.Column('username', sa.String(length=255), nullable=False), sa.Column('status', sa.String(length=25), nullable=False), sa.Column('uuid', sa.String(length=36), nullable=False), - sa.Column('created_at', sa.Date(), nullable=False), - sa.Column('updated_at', sa.Date(), nullable=True), + sa.Column('created_at', sa.DateTime(), nullable=False), + sa.Column('updated_at', sa.DateTime(), nullable=True), sa.Column('comment', sa.String(length=255), nullable=True), sa.ForeignKeyConstraint(['service_uuid'], ['service.uuid'], ), sa.ForeignKeyConstraint(['user_uuid'], ['user.uuid'], ), sa.PrimaryKeyConstraint('uuid') ) + op.create_table('history', + sa.Column('user_uuid', sa.String(length=36), nullable=True), + sa.Column('profile_uuid', sa.String(length=36), nullable=True), + sa.Column('service_uuid', sa.String(length=36), nullable=True), + sa.Column('actor_uuid', sa.String(length=36), nullable=True), + sa.Column('category', sa.String(length=25), nullable=True), + sa.Column('value', sa.String(), nullable=True), + sa.Column('uuid', sa.String(length=36), nullable=False), + sa.Column('created_at', sa.DateTime(), nullable=False), + sa.Column('updated_at', sa.DateTime(), nullable=True), + sa.Column('comment', sa.String(length=255), nullable=True), + sa.ForeignKeyConstraint(['actor_uuid'], ['user.uuid'], ), + sa.ForeignKeyConstraint(['profile_uuid'], ['profile.uuid'], ), + sa.ForeignKeyConstraint(['service_uuid'], ['service.uuid'], ), + sa.ForeignKeyConstraint(['user_uuid'], ['user.uuid'], ), + sa.PrimaryKeyConstraint('uuid') + ) # ### end Alembic commands ### def downgrade(): # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('history') op.drop_table('profile') op.drop_table('auth') op.drop_table('user') diff --git a/trurt/account/profiles.py b/trurt/account/profiles.py index 6eb8f0a..2bfbbe3 100644 --- a/trurt/account/profiles.py +++ b/trurt/account/profiles.py @@ -1,5 +1,6 @@ from trurt.account import blueprint from trurt.sso import forms as sso_forms +from trurt import models, utils import flask_login import flask @@ -13,13 +14,13 @@ def profiles(): @blueprint.route("/pick") @flask_login.login_required def pick(): - service_spn = flask.request.args.get("service_spn") or flask.abort(404) - service = models.Service.query.filter_by(spn=service_spn).first_or_404() + service_uuid = flask.request.args.get("service_uuid") or flask.abort(404) + service = models.Service.query.get(service_uuid) or flask.arort(404) profiles = models.Profile.query.filter_by( - service_id=service.id, - user_id=flask_login.current_user.id + service_uuid=service.uuid, + user_uuid=flask_login.current_user.uuid ) form = sso_forms.SSOValidateForm() - return flask.render_template("sso_pick.html", + return flask.render_template("account_pick.html", service=service, profiles=profiles, form=form, action=utils.url_or_intent("account.status")) diff --git a/trurt/account/settings.py b/trurt/account/settings.py index a16c1e8..7beae73 100644 --- a/trurt/account/settings.py +++ b/trurt/account/settings.py @@ -7,7 +7,8 @@ import flask @blueprint.route("/home") @flask_login.login_required def home(): - return flask.render_template("account_home.html") + history = flask_login.current_user.history + return flask.render_template("account_home.html", history=history) @blueprint.route("/password") diff --git a/trurt/account/templates/account_home.html b/trurt/account/templates/account_home.html index 70c9b96..e1a375d 100644 --- a/trurt/account/templates/account_home.html +++ b/trurt/account/templates/account_home.html @@ -2,3 +2,31 @@ {% block title %}My account{% endblock %} {% block subtitle %}overview of {{ current_user.username }}{% endblock %} + +{% block content %} +<div class="row"> + <div class="col-md-6 col-s-12"> + <section class="content"> + <div class="row"> + <div class="col-md-6 col-xs-12"> + {{ macros.infobox("Account age", "{} days".format((current_user.created_at.today() - current_user.created_at).total_seconds() // 86400), "aqua", "calendar") }} + </div> + <div class="col-md-6 col-xs-12"> + {{ macros.infobox("Profile count", current_user.profiles.__len__(), "red", "users") }} + </div> + </div> + <div class="row"> + <div class="col-md-6 col-xs-12"> + {{ macros.infobox("Pending requests", "0", "green", "hourglass") }} + </div> + <div class="col-md-6 col-xs-12"> + {{ macros.infobox("Role", "registered user", "yellow", "lock") }} + </div> + </div> + </section> + </div> + <div class="col-md-6 col-s-12"> + {{ macros.timeline(history) }} + </div> +</div> +{% endblock %} diff --git a/trurt/account/templates/account_pick.html b/trurt/account/templates/account_pick.html new file mode 100644 index 0000000..4449f54 --- /dev/null +++ b/trurt/account/templates/account_pick.html @@ -0,0 +1,14 @@ +{% extends "base.html" %} + +{% block title %}Pick a profile{% endblock %} +{% block subtitle %}for the service {{ service.name }}{% endblock %} + +{% block content %} +{% for profile in profiles %} +<form method="POST" action="{{ action }}"> + {{ form.hidden_tag() }} + <input type="hidden" name="profile_uuid" value="{{ profile.uuid }}"> + <input type="submit" value="{{ profile.username }}"> +</form> +{% endfor %} +{% endblock %} diff --git a/trurt/configuration.py b/trurt/configuration.py index d2a1688..6a3e8b1 100644 --- a/trurt/configuration.py +++ b/trurt/configuration.py @@ -9,7 +9,8 @@ DEFAULT_CONFIG = { 'SQLALCHEMY_DATABASE_URI': 'sqlite:////tmp/trurt.db', 'SQLALCHEMY_TRACK_MODIFICATIONS': False, 'SECRET_KEY': 'changeMe', - 'TEMPLATES_AUTO_RELOAD': False + 'TEMPLATES_AUTO_RELOAD': False, + 'MAIL_DOMAIN': 'tedomum.net' } class ConfigManager(dict): diff --git a/trurt/manage.py b/trurt/manage.py index 9c550eb..f88e06c 100644 --- a/trurt/manage.py +++ b/trurt/manage.py @@ -23,6 +23,11 @@ def create_user(username, password): auth = models.Auth() auth.set_password(password) user.auths.append(auth) + event = models.History() + event.user = user + event.category = models.History.REGISTER + event.comment = "Created from the command line" + models.db.session.add(event) models.db.session.add(user) models.db.session.add(auth) models.db.session.commit() @@ -40,5 +45,13 @@ def create_profile(username, service_uuid, profile_username): profile.user = user profile.service = service profile.username = profile_username + event = models.History() + event.user = user + event.service = service + event.profile = profile + event.category = models.History.CREATE + event.value = profile.username + event.comment = "Created from the command line" + models.db.session.add(event) models.db.session.add(profile) models.db.session.commit() diff --git a/trurt/models.py b/trurt/models.py index 31b6868..4dc8553 100644 --- a/trurt/models.py +++ b/trurt/models.py @@ -1,7 +1,7 @@ from passlib import context, hash from flask import current_app as app from sqlalchemy.ext import declarative -from datetime import date +from datetime import datetime import flask_sqlalchemy import sqlalchemy @@ -26,11 +26,11 @@ class Base(flask_sqlalchemy.Model): @declarative.declared_attr def created_at(cls): - return sqlalchemy.Column(sqlalchemy.Date, nullable=False, default=date.today) + return sqlalchemy.Column(sqlalchemy.DateTime, nullable=False, default=datetime.now) @declarative.declared_attr def updated_at(cls): - return sqlalchemy.Column(sqlalchemy.Date, nullable=True, onupdate=date.today) + return sqlalchemy.Column(sqlalchemy.DateTime, nullable=True, onupdate=datetime.today) @declarative.declared_attr def comment(cls): @@ -132,6 +132,7 @@ class Profile(db.Model): __tablename__ = "profile" ACTIVE = "active" + DELETED = "deleted" user_uuid = db.Column(db.String(36), db.ForeignKey(User.uuid)) service_uuid = db.Column(db.String(36), db.ForeignKey(Service.uuid)) @@ -145,4 +146,42 @@ class Profile(db.Model): @property def email(self): - return self.uuid + "@kaiyou.fr" + return "{}@{}".format(self.uuid, app.config.get("MAIL_DOMAIN")) + + +class History(db.Model): + """ Records an even in an account's or profile's lifetime. + """ + __tablename__ = "history" + + REGISTER = "register" + CREATE = "create" + DELETE = "delete" + NOTE = "note" + STATUS = "status" + + DESCRIPTION = { + REGISTER: "registered this account", + CREATE: "created the profile {this.profile.username} on {this.service.name}", + } + + user_uuid = db.Column(db.String(36), db.ForeignKey(User.uuid)) + profile_uuid = db.Column(db.String(36), db.ForeignKey(Profile.uuid)) + service_uuid = db.Column(db.String(36), db.ForeignKey(Service.uuid)) + actor_uuid = db.Column(db.String(36), db.ForeignKey(User.uuid)) + user = db.relationship(User, foreign_keys=[user_uuid], + backref=db.backref('history', cascade='all, delete-orphan')) + profile = db.relationship(Profile, + backref=db.backref('history', cascade='all, delete-orphan')) + service = db.relationship(Service, + backref=db.backref('history', cascade='all, delete-orphan')) + actor = db.relationship(User, foreign_keys=[actor_uuid], + backref=db.backref('actions', cascade='all, delete-orphan')) + + category = db.Column(db.String(25)) + value = db.Column(db.String()) + + @property + def description(self): + print(History.DESCRIPTION.get(self.category, "").format(this=self)) + return History.DESCRIPTION.get(self.category, "").format(this=self) diff --git a/trurt/templates/base.html b/trurt/templates/base.html index 68eb448..3b7b8f3 100644 --- a/trurt/templates/base.html +++ b/trurt/templates/base.html @@ -1,3 +1,4 @@ +{% import "macros.html" as macros %} <!doctype html> <html> <head> diff --git a/trurt/templates/macros.html b/trurt/templates/macros.html new file mode 100644 index 0000000..60b77a1 --- /dev/null +++ b/trurt/templates/macros.html @@ -0,0 +1,36 @@ +{% macro timeline(events) %} +<ul class="timeline"> + {% set dates = [] %} + {% for event in events | reverse %} + {% if not event.created_at.date() == dates[-1] %} + {% set _ = dates.append(event.created_at.date()) %} + <li class="time-label"> + <span class="bg-red">{{ event.created_at.date() }}</span> + </li> + {% endif %} + <li> + <i class"fa fa-enveloppe bg-blue"></i> + <div class="timeline-item"> + <span class="time"><i class="fa fa-clock-o"></i> {{ event.created_at.time().strftime("%H:%m") }}</span> + <h3 class="timeline-header"> + <strong>{{ event.actor.username or "You" }}</strong> + {{ event.description }} + </h3> + <div class="timeline-body"> + {{ event.comment }} + </div> + </div> + </li> + {% endfor %} +</ul> +{% endmacro %} + +{% macro infobox(title, text, color, icon) %} +<div class="info-box"> + <span class="info-box-icon bg-{{ color }}"><i class="fa fa-{{ icon }}"></i></span> + <div class="info-box-content"> + <span class="info-box-text">{{ title }}</span> + <span class="info-box-number">{{ text }}</span> + </div> +</div> +{% endmacro %} -- GitLab