mirror of
https://github.com/jazzband/django-auditlog.git
synced 2026-03-16 22:20:26 +00:00
147 lines
4.8 KiB
Python
147 lines
4.8 KiB
Python
from django.conf import settings
|
|
from django.core.exceptions import ObjectDoesNotExist
|
|
from django.db.models import NOT_PROVIDED, DateTimeField, Model
|
|
from django.utils import timezone
|
|
from django.utils.encoding import smart_str
|
|
|
|
|
|
def track_field(field):
|
|
"""
|
|
Returns whether the given field should be tracked by Auditlog.
|
|
|
|
Untracked fields are many-to-many relations and relations to the Auditlog LogEntry model.
|
|
|
|
:param field: The field to check.
|
|
:type field: Field
|
|
:return: Whether the given field should be tracked.
|
|
:rtype: bool
|
|
"""
|
|
from auditlog.models import LogEntry
|
|
|
|
# Do not track many to many relations
|
|
if field.many_to_many:
|
|
return False
|
|
|
|
# Do not track relations to LogEntry
|
|
if (
|
|
getattr(field, "remote_field", None) is not None
|
|
and field.remote_field.model == LogEntry
|
|
):
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
def get_fields_in_model(instance):
|
|
"""
|
|
Returns the list of fields in the given model instance. Checks whether to use the official _meta API or use the raw
|
|
data. This method excludes many to many fields.
|
|
|
|
:param instance: The model instance to get the fields for
|
|
:type instance: Model
|
|
:return: The list of fields for the given model (instance)
|
|
:rtype: list
|
|
"""
|
|
assert isinstance(instance, Model)
|
|
|
|
return [f for f in instance._meta.get_fields() if track_field(f)]
|
|
|
|
|
|
def get_field_value(obj, field):
|
|
"""
|
|
Gets the value of a given model instance field.
|
|
|
|
:param obj: The model instance.
|
|
:type obj: Model
|
|
:param field: The field you want to find the value of.
|
|
:type field: Any
|
|
:return: The value of the field as a string.
|
|
:rtype: str
|
|
"""
|
|
if isinstance(field, DateTimeField):
|
|
# DateTimeFields are timezone-aware, so we need to convert the field
|
|
# to its naive form before we can accurately compare them for changes.
|
|
try:
|
|
value = field.to_python(getattr(obj, field.name, None))
|
|
if value is not None and settings.USE_TZ and not timezone.is_naive(value):
|
|
value = timezone.make_naive(value, timezone=timezone.utc)
|
|
except ObjectDoesNotExist:
|
|
value = field.default if field.default is not NOT_PROVIDED else None
|
|
else:
|
|
try:
|
|
value = smart_str(getattr(obj, field.name, None))
|
|
except ObjectDoesNotExist:
|
|
value = field.default if field.default is not NOT_PROVIDED else None
|
|
|
|
return value
|
|
|
|
|
|
def model_instance_diff(old, new):
|
|
"""
|
|
Calculates the differences between two model instances. One of the instances may be ``None`` (i.e., a newly
|
|
created model or deleted model). This will cause all fields with a value to have changed (from ``None``).
|
|
|
|
:param old: The old state of the model instance.
|
|
:type old: Model
|
|
:param new: The new state of the model instance.
|
|
:type new: Model
|
|
:return: A dictionary with the names of the changed fields as keys and a two tuple of the old and new field values
|
|
as value.
|
|
:rtype: dict
|
|
"""
|
|
from auditlog.registry import auditlog
|
|
|
|
if not (old is None or isinstance(old, Model)):
|
|
raise TypeError("The supplied old instance is not a valid model instance.")
|
|
if not (new is None or isinstance(new, Model)):
|
|
raise TypeError("The supplied new instance is not a valid model instance.")
|
|
|
|
diff = {}
|
|
|
|
if old is not None and new is not None:
|
|
fields = set(old._meta.fields + new._meta.fields)
|
|
model_fields = auditlog.get_model_fields(new._meta.model)
|
|
elif old is not None:
|
|
fields = set(get_fields_in_model(old))
|
|
model_fields = auditlog.get_model_fields(old._meta.model)
|
|
elif new is not None:
|
|
fields = set(get_fields_in_model(new))
|
|
model_fields = auditlog.get_model_fields(new._meta.model)
|
|
else:
|
|
fields = set()
|
|
model_fields = None
|
|
|
|
# Check if fields must be filtered
|
|
if (
|
|
model_fields
|
|
and (model_fields["include_fields"] or model_fields["exclude_fields"])
|
|
and fields
|
|
):
|
|
filtered_fields = []
|
|
if model_fields["include_fields"]:
|
|
filtered_fields = [
|
|
field
|
|
for field in fields
|
|
if field.name in model_fields["include_fields"]
|
|
]
|
|
else:
|
|
filtered_fields = fields
|
|
if model_fields["exclude_fields"]:
|
|
filtered_fields = [
|
|
field
|
|
for field in filtered_fields
|
|
if field.name not in model_fields["exclude_fields"]
|
|
]
|
|
fields = filtered_fields
|
|
|
|
for field in fields:
|
|
old_value = get_field_value(old, field)
|
|
new_value = get_field_value(new, field)
|
|
|
|
if old_value != new_value:
|
|
diff[field.name] = (smart_str(old_value), smart_str(new_value))
|
|
|
|
if len(diff) == 0:
|
|
diff = None
|
|
|
|
return diff
|