diff --git a/migrations/versions/e33f225323f3_.py b/migrations/versions/e33f225323f3_.py new file mode 100644 index 0000000..3f00ee5 --- /dev/null +++ b/migrations/versions/e33f225323f3_.py @@ -0,0 +1,106 @@ +"""empty message + +Revision ID: e33f225323f3 +Revises: 7b105c6e08bf +Create Date: 2020-12-30 17:59:47.917389 + +""" +from alembic import op +import sqlalchemy as sa +import sqlalchemy_utils +from project import dbtypes + + +# revision identifiers, used by Alembic. +revision = "e33f225323f3" +down_revision = "7b105c6e08bf" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column("adminunit", sa.Column("updated_at", sa.DateTime(), nullable=True)) + op.add_column("adminunit", sa.Column("updated_by_id", sa.Integer(), nullable=True)) + op.create_foreign_key(None, "adminunit", "user", ["updated_by_id"], ["id"]) + op.add_column("event", sa.Column("updated_at", sa.DateTime(), nullable=True)) + op.add_column("event", sa.Column("updated_by_id", sa.Integer(), nullable=True)) + op.create_foreign_key(None, "event", "user", ["updated_by_id"], ["id"]) + op.add_column( + "eventorganizer", sa.Column("updated_at", sa.DateTime(), nullable=True) + ) + op.add_column( + "eventorganizer", sa.Column("updated_by_id", sa.Integer(), nullable=True) + ) + op.create_foreign_key(None, "eventorganizer", "user", ["updated_by_id"], ["id"]) + op.add_column("eventplace", sa.Column("updated_at", sa.DateTime(), nullable=True)) + op.add_column("eventplace", sa.Column("updated_by_id", sa.Integer(), nullable=True)) + op.create_foreign_key(None, "eventplace", "user", ["updated_by_id"], ["id"]) + op.add_column( + "eventreference", sa.Column("updated_at", sa.DateTime(), nullable=True) + ) + op.add_column( + "eventreference", sa.Column("updated_by_id", sa.Integer(), nullable=True) + ) + op.create_foreign_key(None, "eventreference", "user", ["updated_by_id"], ["id"]) + op.add_column( + "eventreferencerequest", sa.Column("updated_at", sa.DateTime(), nullable=True) + ) + op.add_column( + "eventreferencerequest", sa.Column("updated_by_id", sa.Integer(), nullable=True) + ) + op.create_foreign_key( + None, "eventreferencerequest", "user", ["updated_by_id"], ["id"] + ) + op.add_column( + "eventsuggestion", sa.Column("updated_at", sa.DateTime(), nullable=True) + ) + op.add_column( + "eventsuggestion", sa.Column("updated_by_id", sa.Integer(), nullable=True) + ) + op.create_foreign_key(None, "eventsuggestion", "user", ["updated_by_id"], ["id"]) + op.add_column("image", sa.Column("updated_at", sa.DateTime(), nullable=True)) + op.add_column("image", sa.Column("updated_by_id", sa.Integer(), nullable=True)) + op.create_foreign_key(None, "image", "user", ["updated_by_id"], ["id"]) + op.add_column("location", sa.Column("updated_at", sa.DateTime(), nullable=True)) + op.add_column("location", sa.Column("updated_by_id", sa.Integer(), nullable=True)) + op.create_foreign_key(None, "location", "user", ["updated_by_id"], ["id"]) + op.add_column("settings", sa.Column("updated_at", sa.DateTime(), nullable=True)) + op.add_column("settings", sa.Column("updated_by_id", sa.Integer(), nullable=True)) + op.create_foreign_key(None, "settings", "user", ["updated_by_id"], ["id"]) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_constraint(None, "settings", type_="foreignkey") + op.drop_column("settings", "updated_by_id") + op.drop_column("settings", "updated_at") + op.drop_constraint(None, "location", type_="foreignkey") + op.drop_column("location", "updated_by_id") + op.drop_column("location", "updated_at") + op.drop_constraint(None, "image", type_="foreignkey") + op.drop_column("image", "updated_by_id") + op.drop_column("image", "updated_at") + op.drop_constraint(None, "eventsuggestion", type_="foreignkey") + op.drop_column("eventsuggestion", "updated_by_id") + op.drop_column("eventsuggestion", "updated_at") + op.drop_constraint(None, "eventreferencerequest", type_="foreignkey") + op.drop_column("eventreferencerequest", "updated_by_id") + op.drop_column("eventreferencerequest", "updated_at") + op.drop_constraint(None, "eventreference", type_="foreignkey") + op.drop_column("eventreference", "updated_by_id") + op.drop_column("eventreference", "updated_at") + op.drop_constraint(None, "eventplace", type_="foreignkey") + op.drop_column("eventplace", "updated_by_id") + op.drop_column("eventplace", "updated_at") + op.drop_constraint(None, "eventorganizer", type_="foreignkey") + op.drop_column("eventorganizer", "updated_by_id") + op.drop_column("eventorganizer", "updated_at") + op.drop_constraint(None, "event", type_="foreignkey") + op.drop_column("event", "updated_by_id") + op.drop_column("event", "updated_at") + op.drop_constraint(None, "adminunit", type_="foreignkey") + op.drop_column("adminunit", "updated_by_id") + op.drop_column("adminunit", "updated_at") + # ### end Alembic commands ### diff --git a/project/models.py b/project/models.py index 9e8674f..1e2907c 100644 --- a/project/models.py +++ b/project/models.py @@ -17,7 +17,7 @@ from sqlalchemy import ( Numeric, ) from sqlalchemy_utils import ColorType -from flask_security import UserMixin, RoleMixin +from flask_security import UserMixin, RoleMixin, current_user from flask_dance.consumer.storage.sqla import OAuthConsumerMixin from enum import IntEnum import datetime @@ -28,16 +28,49 @@ from sqlalchemy import and_ # Base +def _current_user_id_or_none(): + if current_user.is_authenticated: + return current_user.id + + return None + + class TrackableMixin(object): created_at = Column(DateTime, default=datetime.datetime.utcnow) + updated_at = Column( + DateTime, default=datetime.datetime.utcnow, onupdate=datetime.datetime.utcnow + ) @declared_attr def created_by_id(cls): - return Column("created_by_id", ForeignKey("user.id")) + return Column( + "created_by_id", ForeignKey("user.id"), default=_current_user_id_or_none + ) @declared_attr def created_by(cls): - return relationship("User") + return relationship( + "User", + primaryjoin="User.id == %s.created_by_id" % cls.__name__, + remote_side="User.id", + ) + + @declared_attr + def updated_by_id(cls): + return Column( + "updated_by_id", + ForeignKey("user.id"), + default=_current_user_id_or_none, + onupdate=_current_user_id_or_none, + ) + + @declared_attr + def updated_by(cls): + return relationship( + "User", + primaryjoin="User.id == %s.updated_by_id" % cls.__name__, + remote_side="User.id", + ) # Global diff --git a/project/templates/_macros.html b/project/templates/_macros.html index 4dfe7f7..b734d61 100644 --- a/project/templates/_macros.html +++ b/project/templates/_macros.html @@ -309,6 +309,29 @@ {{ render_enum_prop(reference_request.rejection_reason, 'fa-search-minus', 'Rejection reason') }} {% endmacro %} +{% macro render_audit(tracking_mixing, show_user=False) %} + {% set created_at = tracking_mixing.created_at | datetimeformat('short') %} + {% set updated_at = tracking_mixing.updated_at | datetimeformat('short') %} + + {% if show_user %} + {{ _('Created at %(created_at)s by %(created_by)s.', created_at=created_at, created_by=tracking_mixing.created_by.email) }} + {% else %} + {{ _('Created at %(created_at)s.', created_at=created_at) }} + {% endif %} + + {% if created_at != updated_at %} + {% if show_user %} + {{ _('Last updated at %(updated_at)s by %(updated_by)s.', updated_at=updated_at, updated_by=tracking_mixing.updated_by.email) }} + {% else %} + {{ _('Last updated at %(updated_at)s.', updated_at=updated_at) }} + {% endif %} + {% endif %} +{% endmacro %} + +{% macro render_audit_container(tracking_mixing, show_user=False) %} +
{{ render_audit(tracking_mixing, show_user) }}
+{% endmacro %} + {% macro render_event_props(event, start, end, dates = None, show_rating = False, show_admin_unit = True) %}
diff --git a/project/templates/event/read.html b/project/templates/event/read.html index 5ae32f2..ef43265 100644 --- a/project/templates/event/read.html +++ b/project/templates/event/read.html @@ -1,5 +1,5 @@ {% extends "layout.html" %} -{% from "_macros.html" import render_event_menu, render_event_props, render_image_with_link, render_place, render_link_prop %} +{% from "_macros.html" import render_audit_container, render_event_menu, render_event_props, render_image_with_link, render_place, render_link_prop %} {% block title %} {{ event.name }} {% endblock %} @@ -9,7 +9,7 @@
- {{ render_event_props(event, event.start, event.end, dates, user_can_verify_event) }} + {{ render_event_props(event, event.start, event.end, dates, user_rights['can_verify_event']) }} {% if dates|length > 0 %}
@@ -24,6 +24,7 @@
{% endif %} + {{ render_audit_container(event, user_rights['can_verify_event']) }}
{% endblock %} \ No newline at end of file