aboutsummaryrefslogtreecommitdiff
path: root/ihatemoney/versioning.py
diff options
context:
space:
mode:
authorAndrew Dickinson <Andrew-Dickinson@users.noreply.github.com>2020-04-20 09:30:27 -0400
committerGitHub <noreply@github.com>2020-04-20 15:30:27 +0200
commit026a0722357d74b143ed2d974ad2d871a56041b3 (patch)
tree2f23323f01e5ec1dec07ef1032acc407cba38879 /ihatemoney/versioning.py
parent91ef80ebb712b06b6c48336beeb7f60219b0f062 (diff)
downloadihatemoney-mirror-026a0722357d74b143ed2d974ad2d871a56041b3.zip
ihatemoney-mirror-026a0722357d74b143ed2d974ad2d871a56041b3.tar.gz
ihatemoney-mirror-026a0722357d74b143ed2d974ad2d871a56041b3.tar.bz2
Add Project History Page (#553)
Co-Authored-By: Glandos <bugs-github@antipoul.fr> All project activity can be tracked, using SQLAlchemy-continuum. IP addresses can optionally be recorded.
Diffstat (limited to 'ihatemoney/versioning.py')
-rw-r--r--ihatemoney/versioning.py94
1 files changed, 94 insertions, 0 deletions
diff --git a/ihatemoney/versioning.py b/ihatemoney/versioning.py
new file mode 100644
index 0000000..50ad6ec
--- /dev/null
+++ b/ihatemoney/versioning.py
@@ -0,0 +1,94 @@
+from flask import g
+from sqlalchemy.orm.attributes import get_history
+from sqlalchemy_continuum import VersioningManager
+from sqlalchemy_continuum.plugins.flask import fetch_remote_addr
+
+from ihatemoney.utils import FormEnum
+
+
+class LoggingMode(FormEnum):
+ """Represents a project's history preferences."""
+
+ DISABLED = 0
+ ENABLED = 1
+ RECORD_IP = 2
+
+ @classmethod
+ def default(cls):
+ return cls.ENABLED
+
+
+class ConditionalVersioningManager(VersioningManager):
+ """Conditionally enable version tracking based on the given predicate."""
+
+ def __init__(self, tracking_predicate, *args, **kwargs):
+ """Create version entry iff tracking_predicate() returns True."""
+ super().__init__(*args, **kwargs)
+ self.tracking_predicate = tracking_predicate
+
+ def before_flush(self, session, flush_context, instances):
+ if self.tracking_predicate():
+ return super().before_flush(session, flush_context, instances)
+ else:
+ # At least one call to unit_of_work() needs to be made against the
+ # session object to prevent a KeyError later. This doesn't create
+ # a version or transaction entry
+ self.unit_of_work(session)
+
+ def after_flush(self, session, flush_context):
+ if self.tracking_predicate():
+ return super().after_flush(session, flush_context)
+ else:
+ # At least one call to unit_of_work() needs to be made against the
+ # session object to prevent a KeyError later. This doesn't create
+ # a version or transaction entry
+ self.unit_of_work(session)
+
+
+def version_privacy_predicate():
+ """Evaluate if the project of the current session has enabled logging."""
+ logging_enabled = False
+ try:
+ if g.project.logging_preference != LoggingMode.DISABLED:
+ logging_enabled = True
+
+ # If logging WAS enabled prior to this transaction,
+ # we log this one last transaction
+ old_logging_mode = get_history(g.project, "logging_preference")[2]
+ if old_logging_mode and old_logging_mode[0] != LoggingMode.DISABLED:
+ logging_enabled = True
+ except AttributeError:
+ # g.project doesn't exist, it's being created or this action is outside
+ # the scope of a project. Use the default logging mode to decide
+ if LoggingMode.default() != LoggingMode.DISABLED:
+ logging_enabled = True
+ return logging_enabled
+
+
+def get_ip_if_allowed():
+ """
+ Get the remote address (IP address) of the current Flask context, if the
+ project's privacy settings allow it. Behind the scenes, this calls back to
+ the FlaskPlugin from SQLAlchemy-Continuum in order to maintain forward
+ compatibility
+ """
+ ip_logging_allowed = False
+ try:
+ if g.project.logging_preference == LoggingMode.RECORD_IP:
+ ip_logging_allowed = True
+
+ # If ip recording WAS enabled prior to this transaction,
+ # we record the IP for this one last transaction
+ old_logging_mode = get_history(g.project, "logging_preference")[2]
+ if old_logging_mode and old_logging_mode[0] == LoggingMode.RECORD_IP:
+ ip_logging_allowed = True
+ except AttributeError:
+ # g.project doesn't exist, it's being created or this action is outside
+ # the scope of a project. Use the default logging mode to decide
+ if LoggingMode.default() == LoggingMode.RECORD_IP:
+ ip_logging_allowed = True
+
+ if ip_logging_allowed:
+ return fetch_remote_addr()
+ else:
+ return None