mirror of
https://github.com/spiral-project/ihatemoney.git
synced 2025-04-29 09:52:36 +02:00

By formatting datetime on the server, we get nice localized datetime strings that are adapted to the currently-selected language. Example: - English: "Apr 26, 2020, 3:58:54 PM" - French: "26 avr. 2020 à 15:58:54" - German: "26.04.2020, 15:58:54" - Spanish: "26 abr. 2020 15:58:54" - Indonesian: "26 Apr 2020 15.58.54" - Chinese: "2020年4月26日 下午3:58:54" However, there is a downside: time is not adapted to the user timezone. The solution is to define a timezone on the server: we use the server OS timezone by default, and it can be customized through the BABEL_DEFAULT_TIMEZONE setting. It's still not ideal, because it assumes that all users are in the same timezone (the one configured on the server).
139 lines
5.7 KiB
Python
139 lines
5.7 KiB
Python
from flask_babel import gettext as _
|
|
from sqlalchemy_continuum import Operation, parent_class
|
|
|
|
from ihatemoney.models import BillVersion, Person, PersonVersion, ProjectVersion
|
|
|
|
|
|
def get_history_queries(project):
|
|
"""Generate queries for each type of version object for a given project."""
|
|
person_changes = PersonVersion.query.filter_by(project_id=project.id)
|
|
|
|
project_changes = ProjectVersion.query.filter_by(id=project.id)
|
|
|
|
bill_changes = (
|
|
BillVersion.query.with_entities(BillVersion.id.label("bill_version_id"))
|
|
.join(Person, BillVersion.payer_id == Person.id)
|
|
.filter(Person.project_id == project.id)
|
|
)
|
|
sub_query = bill_changes.subquery()
|
|
bill_changes = BillVersion.query.filter(BillVersion.id.in_(sub_query))
|
|
|
|
return person_changes, project_changes, bill_changes
|
|
|
|
|
|
def history_sort_key(history_item_dict):
|
|
"""
|
|
Return the key necessary to sort history entries. First order sort is time
|
|
of modification, but for simultaneous modifications we make the re-name
|
|
modification occur last so that the simultaneous entries make sense using
|
|
the old name.
|
|
"""
|
|
second_order = 0
|
|
if "prop_changed" in history_item_dict:
|
|
changed_property = history_item_dict["prop_changed"]
|
|
if changed_property == "name" or changed_property == "what":
|
|
second_order = 1
|
|
|
|
return history_item_dict["time"], second_order
|
|
|
|
|
|
def describe_version(version_obj):
|
|
"""Use the base model str() function to describe a version object"""
|
|
return parent_class(type(version_obj)).__str__(version_obj)
|
|
|
|
|
|
def describe_owers_change(version, human_readable_names):
|
|
"""Compute the set difference to get added/removed owers lists."""
|
|
before_owers = {version.id: version for version in version.previous.owers}
|
|
after_owers = {version.id: version for version in version.owers}
|
|
|
|
added_ids = set(after_owers).difference(set(before_owers))
|
|
removed_ids = set(before_owers).difference(set(after_owers))
|
|
|
|
if not human_readable_names:
|
|
return added_ids, removed_ids
|
|
|
|
added = [describe_version(after_owers[ower_id]) for ower_id in added_ids]
|
|
removed = [describe_version(before_owers[ower_id]) for ower_id in removed_ids]
|
|
|
|
return added, removed
|
|
|
|
|
|
def get_history(project, human_readable_names=True):
|
|
"""
|
|
Fetch history for all models associated with a given project.
|
|
:param human_readable_names Whether to replace id numbers with readable names
|
|
:return A sorted list of dicts with history information
|
|
"""
|
|
person_query, project_query, bill_query = get_history_queries(project)
|
|
history = []
|
|
for version_list in [person_query.all(), project_query.all(), bill_query.all()]:
|
|
for version in version_list:
|
|
object_type = {
|
|
"Person": _("Participant"),
|
|
"Bill": _("Bill"),
|
|
"Project": _("Project"),
|
|
}[parent_class(type(version)).__name__]
|
|
|
|
# Use the old name if applicable
|
|
if version.previous:
|
|
object_str = describe_version(version.previous)
|
|
else:
|
|
object_str = describe_version(version)
|
|
|
|
common_properties = {
|
|
"time": version.transaction.issued_at,
|
|
"operation_type": version.operation_type,
|
|
"object_type": object_type,
|
|
"object_desc": object_str,
|
|
"ip": version.transaction.remote_addr,
|
|
}
|
|
|
|
if version.operation_type == Operation.UPDATE:
|
|
# Only iterate the changeset if the previous version
|
|
# Was logged
|
|
if version.previous:
|
|
changeset = version.changeset
|
|
if isinstance(version, BillVersion):
|
|
if version.owers != version.previous.owers:
|
|
added, removed = describe_owers_change(
|
|
version, human_readable_names
|
|
)
|
|
|
|
if added:
|
|
changeset["owers_added"] = (None, added)
|
|
if removed:
|
|
changeset["owers_removed"] = (None, removed)
|
|
|
|
# Remove converted_amount if amount changed in the same way.
|
|
if (
|
|
"amount" in changeset
|
|
and "converted_amount" in changeset
|
|
and changeset["amount"] == changeset["converted_amount"]
|
|
):
|
|
del changeset["converted_amount"]
|
|
|
|
for (prop, (val_before, val_after)) in changeset.items():
|
|
if human_readable_names:
|
|
if prop == "payer_id":
|
|
prop = "payer"
|
|
if val_after is not None:
|
|
val_after = describe_version(version.payer)
|
|
if version.previous and val_before is not None:
|
|
val_before = describe_version(
|
|
version.previous.payer
|
|
)
|
|
else:
|
|
val_after = None
|
|
|
|
next_event = common_properties.copy()
|
|
next_event["prop_changed"] = prop
|
|
next_event["val_before"] = val_before
|
|
next_event["val_after"] = val_after
|
|
history.append(next_event)
|
|
else:
|
|
history.append(common_properties)
|
|
else:
|
|
history.append(common_properties)
|
|
|
|
return sorted(history, key=history_sort_key, reverse=True)
|