mirror of
https://github.com/jazzband/django-auditlog.git
synced 2026-03-16 22:20:26 +00:00
Code improvements
This commit is contained in:
parent
228c5949fb
commit
469fe362de
6 changed files with 51 additions and 99 deletions
|
|
@ -1,20 +0,0 @@
|
|||
import django
|
||||
|
||||
def is_authenticated(user):
|
||||
"""Return whether or not a User is authenticated.
|
||||
|
||||
Function provides compatibility following deprecation of method call to
|
||||
`is_authenticated()` in Django 2.0.
|
||||
|
||||
This is *only* required to support Django < v1.10 (i.e. v1.9 and earlier),
|
||||
as `is_authenticated` was introduced as a property in v1.10.s
|
||||
"""
|
||||
if not hasattr(user, 'is_authenticated'):
|
||||
return False
|
||||
if callable(user.is_authenticated):
|
||||
# Will be callable if django.version < 2.0, but is only necessary in
|
||||
# v1.9 and earlier due to change introduced in v1.10 making
|
||||
# `is_authenticated` a property instead of a callable.
|
||||
return user.is_authenticated()
|
||||
else:
|
||||
return user.is_authenticated
|
||||
|
|
@ -55,6 +55,7 @@ def get_fields_in_model(instance):
|
|||
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.
|
||||
|
|
@ -64,7 +65,7 @@ def get_field_value(obj, field):
|
|||
"""
|
||||
if isinstance(field, DateTimeField):
|
||||
# DateTimeFields are timezone-aware, so we need to convert the field
|
||||
# to its naive form before we can accuratly compare them for changes.
|
||||
# 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):
|
||||
|
|
@ -95,9 +96,9 @@ def model_instance_diff(old, new):
|
|||
"""
|
||||
from auditlog.registry import auditlog
|
||||
|
||||
if not(old is None or isinstance(old, Model)):
|
||||
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)):
|
||||
if not (new is None or isinstance(new, Model)):
|
||||
raise TypeError("The supplied new instance is not a valid model instance.")
|
||||
|
||||
diff = {}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ from django.conf import settings
|
|||
from django.db.models.signals import pre_save
|
||||
from django.utils.deprecation import MiddlewareMixin
|
||||
|
||||
from auditlog.compat import is_authenticated
|
||||
from auditlog.models import LogEntry
|
||||
|
||||
threadlocal = threading.local()
|
||||
|
|
@ -35,7 +34,7 @@ class AuditlogMiddleware(MiddlewareMixin):
|
|||
threadlocal.auditlog['remote_addr'] = request.META.get('HTTP_X_FORWARDED_FOR').split(',')[0]
|
||||
|
||||
# Connect signal for automatic logging
|
||||
if hasattr(request, 'user') and is_authenticated(request.user):
|
||||
if hasattr(request, 'user') and getattr(request.user, 'is_authenticated', False):
|
||||
set_actor = partial(self.set_actor, user=request.user, signal_duid=threadlocal.auditlog['signal_duid'])
|
||||
pre_save.connect(set_actor, sender=LogEntry, dispatch_uid=threadlocal.auditlog['signal_duid'], weak=False)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,17 +1,13 @@
|
|||
import json
|
||||
|
||||
from django import urls as urlresolvers
|
||||
from django.conf import settings
|
||||
try:
|
||||
from django.core import urlresolvers
|
||||
except ImportError:
|
||||
from django import urls as urlresolvers
|
||||
try:
|
||||
from django.urls.exceptions import NoReverseMatch
|
||||
except ImportError:
|
||||
from django.core.urlresolvers import NoReverseMatch
|
||||
from django.urls.exceptions import NoReverseMatch
|
||||
from django.utils.html import format_html
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
from auditlog.models import LogEntry
|
||||
|
||||
MAX = 75
|
||||
|
||||
|
||||
|
|
@ -19,6 +15,7 @@ class LogEntryAdminMixin(object):
|
|||
|
||||
def created(self, obj):
|
||||
return obj.timestamp.strftime('%Y-%m-%d %H:%M:%S')
|
||||
|
||||
created.short_description = 'Created'
|
||||
|
||||
def user_url(self, obj):
|
||||
|
|
@ -32,6 +29,7 @@ class LogEntryAdminMixin(object):
|
|||
return format_html(u'<a href="{}">{}</a>', link, obj.actor)
|
||||
|
||||
return 'system'
|
||||
|
||||
user_url.short_description = 'User'
|
||||
|
||||
def resource_url(self, obj):
|
||||
|
|
@ -44,10 +42,11 @@ class LogEntryAdminMixin(object):
|
|||
return obj.object_repr
|
||||
else:
|
||||
return format_html(u'<a href="{}">{}</a>', link, obj.object_repr)
|
||||
|
||||
resource_url.short_description = 'Resource'
|
||||
|
||||
def msg_short(self, obj):
|
||||
if obj.action == 2:
|
||||
if obj.action == LogEntry.Action.DELETE:
|
||||
return '' # delete
|
||||
changes = json.loads(obj.changes)
|
||||
s = '' if len(changes) == 1 else 's'
|
||||
|
|
@ -56,10 +55,11 @@ class LogEntryAdminMixin(object):
|
|||
i = fields.rfind(' ', 0, MAX)
|
||||
fields = fields[:i] + ' ..'
|
||||
return '%d change%s: %s' % (len(changes), s, fields)
|
||||
|
||||
msg_short.short_description = 'Changes'
|
||||
|
||||
def msg(self, obj):
|
||||
if obj.action == 2:
|
||||
if obj.action == LogEntry.Action.DELETE:
|
||||
return '' # delete
|
||||
changes = json.loads(obj.changes)
|
||||
msg = '<table><tr><th>#</th><th>Field</th><th>From</th><th>To</th></tr>'
|
||||
|
|
@ -69,4 +69,5 @@ class LogEntryAdminMixin(object):
|
|||
|
||||
msg += '</table>'
|
||||
return mark_safe(msg)
|
||||
|
||||
msg.short_description = 'Changes'
|
||||
|
|
|
|||
|
|
@ -1,12 +1,19 @@
|
|||
from django.db.models.signals import pre_save, post_save, post_delete
|
||||
from typing import Dict, Callable, Optional, List, Tuple
|
||||
|
||||
from django.db.models import Model
|
||||
from django.db.models.base import ModelBase
|
||||
from django.db.models.signals import pre_save, post_save, post_delete, ModelSignal
|
||||
|
||||
DispatchUID = Tuple[int, str, int]
|
||||
|
||||
|
||||
class AuditlogModelRegistry(object):
|
||||
"""
|
||||
A registry that keeps track of the models that use Auditlog to track changes.
|
||||
"""
|
||||
def __init__(self, create=True, update=True, delete=True, custom=None):
|
||||
|
||||
def __init__(self, create: bool = True, update: bool = True, delete: bool = True,
|
||||
custom: Optional[Dict[ModelSignal, Callable]] = None):
|
||||
from auditlog.receivers import log_create, log_update, log_delete
|
||||
|
||||
self._registry = {}
|
||||
|
|
@ -22,17 +29,25 @@ class AuditlogModelRegistry(object):
|
|||
if custom is not None:
|
||||
self._signals.update(custom)
|
||||
|
||||
def register(self, model=None, include_fields=[], exclude_fields=[], mapping_fields={}):
|
||||
def register(self, model: ModelBase = None, include_fields: Optional[List[str]] = None,
|
||||
exclude_fields: Optional[List[str]] = None, mapping_fields: Optional[Dict[str, str]] = None):
|
||||
"""
|
||||
Register a model with auditlog. Auditlog will then track mutations on this model's instances.
|
||||
|
||||
:param model: The model to register.
|
||||
:type model: Model
|
||||
:param include_fields: The fields to include. Implicitly excludes all other fields.
|
||||
:type include_fields: list
|
||||
:param exclude_fields: The fields to exclude. Overrides the fields to include.
|
||||
:type exclude_fields: list
|
||||
:param mapping_fields: Mapping from field names to strings in diff.
|
||||
|
||||
"""
|
||||
|
||||
if include_fields is None:
|
||||
include_fields = []
|
||||
if exclude_fields is None:
|
||||
exclude_fields = []
|
||||
if mapping_fields is None:
|
||||
mapping_fields = {}
|
||||
|
||||
def registrar(cls):
|
||||
"""Register models for a given class."""
|
||||
if not issubclass(cls, Model):
|
||||
|
|
@ -58,23 +73,21 @@ class AuditlogModelRegistry(object):
|
|||
# Otherwise, just register the model.
|
||||
registrar(model)
|
||||
|
||||
def contains(self, model):
|
||||
def contains(self, model: ModelBase) -> bool:
|
||||
"""
|
||||
Check if a model is registered with auditlog.
|
||||
|
||||
:param model: The model to check.
|
||||
:type model: Model
|
||||
:return: Whether the model has been registered.
|
||||
:rtype: bool
|
||||
"""
|
||||
return model in self._registry
|
||||
|
||||
def unregister(self, model):
|
||||
def unregister(self, model: ModelBase) -> None:
|
||||
"""
|
||||
Unregister a model with auditlog. This will not affect the database.
|
||||
|
||||
:param model: The model to unregister.
|
||||
:type model: Model
|
||||
"""
|
||||
try:
|
||||
del self._registry[model]
|
||||
|
|
@ -83,6 +96,16 @@ class AuditlogModelRegistry(object):
|
|||
else:
|
||||
self._disconnect_signals(model)
|
||||
|
||||
def get_models(self) -> List[ModelBase]:
|
||||
return list(self._registry.keys())
|
||||
|
||||
def get_model_fields(self, model: ModelBase):
|
||||
return {
|
||||
'include_fields': list(self._registry[model]['include_fields']),
|
||||
'exclude_fields': list(self._registry[model]['exclude_fields']),
|
||||
'mapping_fields': dict(self._registry[model]['mapping_fields']),
|
||||
}
|
||||
|
||||
def _connect_signals(self, model):
|
||||
"""
|
||||
Connect signals for the model.
|
||||
|
|
@ -98,24 +121,11 @@ class AuditlogModelRegistry(object):
|
|||
for signal, receiver in self._signals.items():
|
||||
signal.disconnect(sender=model, dispatch_uid=self._dispatch_uid(signal, model))
|
||||
|
||||
def _dispatch_uid(self, signal, model):
|
||||
def _dispatch_uid(self, signal, model) -> DispatchUID:
|
||||
"""
|
||||
Generate a dispatch_uid.
|
||||
"""
|
||||
return (self.__class__, model, signal)
|
||||
|
||||
def get_model_fields(self, model):
|
||||
return {
|
||||
'include_fields': self._registry[model]['include_fields'],
|
||||
'exclude_fields': self._registry[model]['exclude_fields'],
|
||||
'mapping_fields': self._registry[model]['mapping_fields'],
|
||||
}
|
||||
|
||||
|
||||
class AuditLogModelRegistry(AuditlogModelRegistry):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(AuditLogModelRegistry, self).__init__(*args, **kwargs)
|
||||
raise DeprecationWarning("Use AuditlogModelRegistry instead of AuditLogModelRegistry, AuditLogModelRegistry will be removed in django-auditlog 0.4.0 or later.")
|
||||
return self.__hash__(), model.__qualname__, signal.__hash__()
|
||||
|
||||
|
||||
auditlog = AuditlogModelRegistry()
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ from auditlog_tests.models import SimpleModel, AltPrimaryKeyModel, UUIDPrimaryKe
|
|||
ProxyModel, SimpleIncludeModel, SimpleExcludeModel, SimpleMappingModel, RelatedModel, \
|
||||
ManyRelatedModel, AdditionalDataIncludedModel, DateTimeFieldModel, ChoicesFieldModel, \
|
||||
CharfieldTextfieldModel, PostgresArrayFieldModel, NoDeleteHistoryModel
|
||||
from auditlog import compat
|
||||
|
||||
|
||||
class SimpleModelTest(TestCase):
|
||||
|
|
@ -586,44 +585,6 @@ class PostgresArrayFieldModelTest(TestCase):
|
|||
msg="The human readable text 'Green' is displayed.")
|
||||
|
||||
|
||||
class CompatibilityTest(TestCase):
|
||||
"""Test case for compatibility functions."""
|
||||
|
||||
def test_is_authenticated(self):
|
||||
"""Test that the 'is_authenticated' compatibility function is working.
|
||||
|
||||
Bit of explanation: the `is_authenticated` property on request.user is
|
||||
*always* set to 'False' for AnonymousUser, and it is *always* set to
|
||||
'True' for *any* other (i.e. identified/authenticated) user.
|
||||
|
||||
So, the logic of this test is to ensure that compat.is_authenticated()
|
||||
returns the correct value based on whether or not the User is an
|
||||
anonymous user (simulating what goes on in the real request.user).
|
||||
|
||||
"""
|
||||
|
||||
# Test compat.is_authenticated for anonymous users
|
||||
self.user = auth.get_user(self.client)
|
||||
if django.VERSION < (1, 10):
|
||||
assert self.user.is_anonymous()
|
||||
else:
|
||||
assert self.user.is_anonymous
|
||||
assert not compat.is_authenticated(self.user)
|
||||
|
||||
# Setup some other user, which is *not* anonymous, and check
|
||||
# compat.is_authenticated
|
||||
self.user = User.objects.create(
|
||||
username="test.user",
|
||||
email="test.user@mail.com",
|
||||
password="auditlog"
|
||||
)
|
||||
if django.VERSION < (1, 10):
|
||||
assert not self.user.is_anonymous()
|
||||
else:
|
||||
assert not self.user.is_anonymous
|
||||
assert compat.is_authenticated(self.user)
|
||||
|
||||
|
||||
class AdminPanelTest(TestCase):
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
|
|
|
|||
Loading…
Reference in a new issue