mirror of
https://github.com/jazzband/django-auditlog.git
synced 2026-03-16 22:20:26 +00:00
Merge branch 'master' into bugfix/changes-display-dict-errors
This commit is contained in:
commit
f6ade2078b
10 changed files with 81 additions and 8 deletions
4
MANIFEST
4
MANIFEST
|
|
@ -3,6 +3,7 @@ setup.py
|
|||
src/auditlog/__init__.py
|
||||
src/auditlog/admin.py
|
||||
src/auditlog/apps.py
|
||||
src/auditlog/compat.py
|
||||
src/auditlog/diff.py
|
||||
src/auditlog/filters.py
|
||||
src/auditlog/middleware.py
|
||||
|
|
@ -10,6 +11,9 @@ src/auditlog/mixins.py
|
|||
src/auditlog/models.py
|
||||
src/auditlog/receivers.py
|
||||
src/auditlog/registry.py
|
||||
src/auditlog/management/__init__.py
|
||||
src/auditlog/management/commands/__init__.py
|
||||
src/auditlog/management/commands/auditlogflush.py
|
||||
src/auditlog/migrations/0001_initial.py
|
||||
src/auditlog/migrations/0002_auto_support_long_primary_keys.py
|
||||
src/auditlog/migrations/0003_logentry_remote_addr.py
|
||||
|
|
|
|||
20
src/auditlog/compat.py
Normal file
20
src/auditlog/compat.py
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
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
|
||||
|
|
@ -8,6 +8,7 @@ from django.db.models.signals import pre_save
|
|||
from django.utils.functional import curry
|
||||
from django.apps import apps
|
||||
from auditlog.models import LogEntry
|
||||
from auditlog.compat import is_authenticated
|
||||
|
||||
# Use MiddlewareMixin when present (Django >= 1.10)
|
||||
try:
|
||||
|
|
@ -41,7 +42,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 hasattr(request.user, 'is_authenticated') and request.user.is_authenticated():
|
||||
if hasattr(request, 'user') and is_authenticated(request.user):
|
||||
set_actor = curry(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)
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ class Migration(migrations.Migration):
|
|||
('changes', models.TextField(verbose_name='change message', blank=True)),
|
||||
('timestamp', models.DateTimeField(auto_now_add=True, verbose_name='timestamp')),
|
||||
('actor', models.ForeignKey(related_name='+', on_delete=django.db.models.deletion.SET_NULL, verbose_name='actor', blank=True, to=settings.AUTH_USER_MODEL, null=True)),
|
||||
('content_type', models.ForeignKey(related_name='+', verbose_name='content type', to='contenttypes.ContentType')),
|
||||
('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='+', verbose_name='content type', to='contenttypes.ContentType')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['-timestamp'],
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
import json
|
||||
|
||||
from django.conf import settings
|
||||
from django.core import urlresolvers
|
||||
try:
|
||||
from django.core import urlresolvers
|
||||
except ImportError:
|
||||
from django import urls as urlresolvers
|
||||
try:
|
||||
from django.urls.exceptions import NoReverseMatch
|
||||
except ImportError:
|
||||
|
|
|
|||
|
|
@ -171,13 +171,13 @@ class LogEntry(models.Model):
|
|||
(DELETE, _("delete")),
|
||||
)
|
||||
|
||||
content_type = models.ForeignKey('contenttypes.ContentType', on_delete=models.CASCADE, related_name='+', verbose_name=_("content type"))
|
||||
content_type = models.ForeignKey(to='contenttypes.ContentType', on_delete=models.CASCADE, related_name='+', verbose_name=_("content type"))
|
||||
object_pk = models.CharField(db_index=True, max_length=255, verbose_name=_("object pk"))
|
||||
object_id = models.BigIntegerField(blank=True, db_index=True, null=True, verbose_name=_("object id"))
|
||||
object_repr = models.TextField(verbose_name=_("object representation"))
|
||||
action = models.PositiveSmallIntegerField(choices=Action.choices, verbose_name=_("action"))
|
||||
changes = models.TextField(blank=True, verbose_name=_("change message"))
|
||||
actor = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True, on_delete=models.SET_NULL, related_name='+', verbose_name=_("actor"))
|
||||
actor = models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, blank=True, null=True, related_name='+', verbose_name=_("actor"))
|
||||
remote_addr = models.GenericIPAddressField(blank=True, null=True, verbose_name=_("remote address"))
|
||||
timestamp = models.DateTimeField(auto_now_add=True, verbose_name=_("timestamp"))
|
||||
additional_data = JSONField(blank=True, null=True, verbose_name=_("additional data"))
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ class RelatedModel(models.Model):
|
|||
A model with a foreign key.
|
||||
"""
|
||||
|
||||
related = models.ForeignKey('self')
|
||||
related = models.ForeignKey(to='self', on_delete=models.CASCADE)
|
||||
|
||||
history = AuditlogHistoryField()
|
||||
|
||||
|
|
@ -124,7 +124,7 @@ class AdditionalDataIncludedModel(models.Model):
|
|||
|
||||
label = models.CharField(max_length=100)
|
||||
text = models.TextField(blank=True)
|
||||
related = models.ForeignKey(SimpleModel)
|
||||
related = models.ForeignKey(to=SimpleModel, on_delete=models.CASCADE)
|
||||
|
||||
history = AuditlogHistoryField()
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ SECRET_KEY = 'test'
|
|||
INSTALLED_APPS = [
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'auditlog',
|
||||
'auditlog_tests',
|
||||
'multiselectfield',
|
||||
|
|
@ -15,6 +16,7 @@ INSTALLED_APPS = [
|
|||
|
||||
MIDDLEWARE_CLASSES = (
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware'
|
||||
'auditlog.middleware.AuditlogMiddleware',
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import datetime
|
||||
import django
|
||||
from django.conf import settings
|
||||
from django.contrib import auth
|
||||
from django.contrib.auth.models import User, AnonymousUser
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db.models.signals import pre_save
|
||||
|
|
@ -15,6 +17,7 @@ from auditlog_tests.models import SimpleModel, AltPrimaryKeyModel, UUIDPrimaryKe
|
|||
ProxyModel, SimpleIncludeModel, SimpleExcludeModel, SimpleMappingModel, RelatedModel, \
|
||||
ManyRelatedModel, AdditionalDataIncludedModel, DateTimeFieldModel, ChoicesFieldModel, \
|
||||
CharfieldTextfieldModel, PostgresArrayFieldModel
|
||||
from auditlog import compat
|
||||
|
||||
|
||||
class SimpleModelTest(TestCase):
|
||||
|
|
@ -581,3 +584,41 @@ class PostgresArrayFieldModelTest(TestCase):
|
|||
self.obj.save()
|
||||
self.assertTrue(self.obj.history.latest().changes_display_dict["arrayfield"][1] == "Green",
|
||||
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)
|
||||
|
|
|
|||
4
tox.ini
4
tox.ini
|
|
@ -3,6 +3,7 @@ envlist =
|
|||
{py27,py34,py35,py36}-django-18
|
||||
{py27,py34,py35,py36}-django-110
|
||||
{py27,py34,py35,py36}-django-111
|
||||
{py34,py35,py36}-django-20
|
||||
|
||||
[testenv]
|
||||
setenv =
|
||||
|
|
@ -11,7 +12,8 @@ commands = coverage run --source src/auditlog src/runtests.py
|
|||
deps =
|
||||
django-18: Django>=1.8,<1.9
|
||||
django-110: Django>=1.10,<1.11
|
||||
django-111: Django>=1.11
|
||||
django-111: Django>=1.11,<2.0
|
||||
django-20: Django>=2.0
|
||||
-r{toxinidir}/requirements-test.txt
|
||||
basepython =
|
||||
py36: python3.6
|
||||
|
|
|
|||
Loading…
Reference in a new issue