本文将教大家如何基于 Django 内置的日志记录更新将详细的操作记录到数据库。
内置功能介绍
大家在使用 Django 的 Admin 页面时会发现有个最近动作(Recent actions) 的列表,这就是 Django 自带的日志记录功能。
这个功能记录到对象的操作记录,比如创建、更改、删除等操作,但是没有记录详细的字段操作记录,比如某个字段从什么值改成什么值。
内置模块介绍
django 内置的日志记录核心模块是 django.contrib.admin.models.LogEntry,以下是这个 model 的详情。
class LogEntry(models.Model):
action_time = models.DateTimeField(
_('action time'),
default=timezone.now,
editable=False,
)
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
models.CASCADE,
verbose_name=_('user'),
)
content_type = models.ForeignKey(
ContentType,
models.SET_NULL,
verbose_name=_('content type'),
blank=True, null=True,
)
object_id = models.TextField(_('object id'), blank=True, null=True)
# Translators: 'repr' means representation (https://docs.python.org/library/functions.html#repr)
object_repr = models.CharField(_('object repr'), max_length=200)
action_flag = models.PositiveSmallIntegerField(_('action flag'), choices=ACTION_FLAG_CHOICES)
# change_message is either a string or a JSON structure
change_message = models.TextField(_('change message'), blank=True)
objects = LogEntryManager()
class Meta:
verbose_name = _('log entry')
verbose_name_plural = _('log entries')
db_table = 'django_admin_log'
ordering = ('-action_time',)
原始的调用方法如下:
def log_addition(self, request, object, message):
"""
Log that an object has been successfully added.
The default implementation creates an admin LogEntry object.
"""
from django.contrib.admin.models import LogEntry, ADDITION
return LogEntry.objects.log_action(
user_id=request.user.pk,
content_type_id=get_content_type_for_model(object).pk,
object_id=object.pk,
object_repr=str(object),
action_flag=ADDITION,
change_message=message,
)
def log_change(self, request, object, message):
"""
Log that an object has been successfully changed.
The default implementation creates an admin LogEntry object.
"""
from django.contrib.admin.models import LogEntry, CHANGE
return LogEntry.objects.log_action(
user_id=request.user.pk,
content_type_id=get_content_type_for_model(object).pk,
object_id=object.pk,
object_repr=str(object),
action_flag=CHANGE,
change_message=message,
)
def log_deletion(self, request, object, object_repr):
"""
Log that an object will be deleted. Note that this method must be
called before the deletion.
The default implementation creates an admin LogEntry object.
"""
from django.contrib.admin.models import LogEntry, DELETION
return LogEntry.objects.log_action(
user_id=request.user.pk,
content_type_id=get_content_type_for_model(object).pk,
object_id=object.pk,
object_repr=object_repr,
action_flag=DELETION,
)
重构方法
我们重新构建3个方法:
import json
from django.contrib.admin.models import LogEntry, ADDITION, CHANGE, DELETION
from django.contrib.contenttypes.models import ContentType
def get_content_type_for_model(obj):
return ContentType.objects.get_for_model(obj, for_concrete_model=False)
def field_value_to_json(f_value):
if str(type(f_value)) in [
"<class 'django.db.models.fields.files.FieldFile'>",
"<class 'django.db.models.fields.files.ImageFile'>",
"<class 'django.db.models.fields.files.ImageFieldFile'>",
"<class 'django.core.files.uploadedfile.InMemoryUploadedFile'>"
]:
value = f_value.name
elif str(type(f_value)) in [
"<class 'uuid.UUID'>",
"<class 'datetime.datetime'>",
"<class 'datetime.date'>",
"<class 'bool'>"
]:
value = str(f_value)
elif 'models' in str(type(f_value)):
value = f_value.pk
elif str(type(f_value)) in [
"<class 'decimal.Decimal'>"
]:
value = str(float(f_value))
else:
value = str(f_value)
return value
def create_addition_log(user_id, oj):
lg = LogEntry.objects.log_action(
user_id=user_id,
content_type_id=get_content_type_for_model(oj).pk,
object_id=oj.pk,
object_repr=str(oj),
action_flag=ADDITION,
)
# 因为 LogEntry 不会保存具体数据,需要另外将操作涉及的数据存入该条记录中
addition_message_dict = {
'addition': {
'fields': list(),
'values': list()
}
}
for i in oj._meta.fields:
fields = i.attname
value = getattr(oj, fields)
addition_message_dict['addition']['fields'].append(fields)
addition_message_dict['addition']['values'].append(field_value_to_json(value))
addition_message = json.dumps(addition_message_dict, ensure_ascii=False)
lg.change_message = addition_message
lg.save()
return True
def create_change_log(user_id, oj, origin_data, new_data):
lg = LogEntry.objects.log_action(
user_id=user_id,
content_type_id=get_content_type_for_model(oj).pk,
object_id=oj.pk,
object_repr=str(oj),
action_flag=CHANGE,
)
change_message_dict = {
'changed': {
'fields': list(),
'origin_values': list(),
'new_values': list(),
}
}
for fields, value in new_data.items():
origin_value = origin_data.get(fields)
if value != origin_value and fields in origin_data:
change_message_dict['changed']['fields'].append(fields)
change_message_dict['changed']['origin_values'].append(field_value_to_json(origin_value))
change_message_dict['changed']['new_values'].append(field_value_to_json(value))
change_message = json.dumps(change_message_dict, ensure_ascii=False)
lg.change_message = change_message
lg.save()
return True
def create_delete_log(user_id, oj):
lg = LogEntry.objects.log_action(
user_id=user_id,
content_type_id=get_content_type_for_model(oj).pk,
object_id=oj.pk,
object_repr=str(oj),
action_flag=DELETION,
)
# 因为 LogEntry 不会保存具体数据,需要另外将操作涉及的数据存入该条记录中
delete_message_dict = {
'delete': {
'fields': list(),
'values': list()
}
}
for i in oj._meta.fields:
fields = i.attname
value = getattr(oj, fields)
delete_message_dict['delete']['fields'].append(fields)
delete_message_dict['delete']['values'].append(field_value_to_json(value))
delete_message = json.dumps(delete_message_dict, ensure_ascii=False)
lg.change_message = delete_message
lg.save()
return True
方法重构好了以后,就可以在相应的位置进行调用。