mirror of
https://github.com/jazzband/django-auditlog.git
synced 2026-03-16 22:20:26 +00:00
Drop Python 2, support Django 3.0, update dependencies
This commit is contained in:
parent
4e7c640ba0
commit
3acab4322b
22 changed files with 53 additions and 148 deletions
37
.travis.yml
37
.travis.yml
|
|
@ -11,40 +11,23 @@ addons:
|
|||
|
||||
matrix:
|
||||
include:
|
||||
- python: 2.7
|
||||
env: TOXENV=py27-django-111
|
||||
|
||||
- python: 3.4
|
||||
env: TOXENV=py34-django-111
|
||||
- python: 3.4
|
||||
env: TOXENV=py34-django-20
|
||||
|
||||
- python: 3.5
|
||||
env: TOXENV=py35-django-111
|
||||
- python: 3.5
|
||||
env: TOXENV=py35-django-20
|
||||
- python: 3.5
|
||||
env: TOXENV=py35-django-21
|
||||
- python: 3.5
|
||||
env: TOXENV=py35-django-22
|
||||
|
||||
- python: 3.6
|
||||
env: TOXENV=py36-django-111
|
||||
- python: 3.6
|
||||
env: TOXENV=py36-django-20
|
||||
- python: 3.6
|
||||
env: TOXENV=py36-django-21
|
||||
- python: 3.6
|
||||
env: TOXENV=py36-django-22
|
||||
- python: 3.6
|
||||
env: TOXENV=py36-django-30
|
||||
|
||||
- python: 3.7
|
||||
env: TOXENV=py37-django-111
|
||||
- python: 3.7
|
||||
env: TOXENV=py37-django-20
|
||||
- python: 3.7
|
||||
env: TOXENV=py37-django-21
|
||||
- python: 3.7
|
||||
env: TOXENV=py37-django-22
|
||||
- python: 3.7
|
||||
env: TOXENV=py37-django-30
|
||||
|
||||
- python: 3.8
|
||||
env: TOXENV=py38-django-22
|
||||
- python: 3.8
|
||||
env: TOXENV=py38-django-30
|
||||
|
||||
fast_finish: true
|
||||
|
||||
|
|
@ -61,5 +44,5 @@ deploy:
|
|||
on:
|
||||
repo: jjkester/django-auditlog
|
||||
branch: stable
|
||||
condition: $TOXENV = py36-django-20
|
||||
condition: $TOXENV = py38-django-30
|
||||
edge: true
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
__version__ = '1.0a1'
|
||||
|
||||
default_app_config = 'auditlog.apps.AuditlogConfig'
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.db.models import Model, NOT_PROVIDED, DateTimeField
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
from django.core.management.base import BaseCommand
|
||||
from six import moves
|
||||
|
||||
from auditlog.models import LogEntry
|
||||
|
||||
|
|
@ -11,7 +10,7 @@ class Command(BaseCommand):
|
|||
answer = None
|
||||
|
||||
while answer not in ['', 'y', 'n']:
|
||||
answer = moves.input("Are you sure? [y/N]: ").lower().strip()
|
||||
answer = input("Are you sure? [y/N]: ").lower().strip()
|
||||
|
||||
if answer == 'y':
|
||||
count = LogEntry.objects.all().count()
|
||||
|
|
|
|||
|
|
@ -1,21 +1,14 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
import threading
|
||||
import time
|
||||
from functools import partial
|
||||
|
||||
from django.apps import apps
|
||||
from django.conf import settings
|
||||
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 django.utils.deprecation import MiddlewareMixin
|
||||
|
||||
from auditlog.compat import is_authenticated
|
||||
|
||||
# Use MiddlewareMixin when present (Django >= 1.10)
|
||||
try:
|
||||
from django.utils.deprecation import MiddlewareMixin
|
||||
except ImportError:
|
||||
MiddlewareMixin = object
|
||||
|
||||
from auditlog.models import LogEntry
|
||||
|
||||
threadlocal = threading.local()
|
||||
|
||||
|
|
@ -43,7 +36,7 @@ class AuditlogMiddleware(MiddlewareMixin):
|
|||
|
||||
# Connect signal for automatic logging
|
||||
if hasattr(request, 'user') and is_authenticated(request.user):
|
||||
set_actor = curry(self.set_actor, user=request.user, signal_duid=threadlocal.auditlog['signal_duid'])
|
||||
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)
|
||||
|
||||
def process_response(self, request, response):
|
||||
|
|
|
|||
|
|
@ -1,6 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
|
|
|
|||
|
|
@ -1,6 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
import jsonfield.fields
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import jsonfield.fields
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
import json
|
||||
import ast
|
||||
import json
|
||||
|
||||
from dateutil import parser
|
||||
from dateutil.tz import gettz
|
||||
from django.conf import settings
|
||||
from django.contrib.contenttypes.fields import GenericRelation
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
|
@ -10,13 +10,9 @@ from django.core.exceptions import FieldDoesNotExist
|
|||
from django.db import models, DEFAULT_DB_ALIAS
|
||||
from django.db.models import QuerySet, Q
|
||||
from django.utils import formats, timezone
|
||||
from django.utils.encoding import python_2_unicode_compatible, smart_text
|
||||
from django.utils.six import iteritems, integer_types
|
||||
from django.utils.encoding import smart_str
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from jsonfield.fields import JSONField
|
||||
from dateutil import parser
|
||||
from dateutil.tz import gettz
|
||||
|
||||
|
||||
class LogEntryManager(models.Manager):
|
||||
|
|
@ -41,9 +37,9 @@ class LogEntryManager(models.Manager):
|
|||
if changes is not None:
|
||||
kwargs.setdefault('content_type', ContentType.objects.get_for_model(instance))
|
||||
kwargs.setdefault('object_pk', pk)
|
||||
kwargs.setdefault('object_repr', smart_text(instance))
|
||||
kwargs.setdefault('object_repr', smart_str(instance))
|
||||
|
||||
if isinstance(pk, integer_types):
|
||||
if isinstance(pk, int):
|
||||
kwargs.setdefault('object_id', pk)
|
||||
|
||||
get_additional_data = getattr(instance, 'get_additional_data', None)
|
||||
|
|
@ -53,7 +49,9 @@ class LogEntryManager(models.Manager):
|
|||
# Delete log entries with the same pk as a newly created model. This should only be necessary when an pk is
|
||||
# used twice.
|
||||
if kwargs.get('action', None) is LogEntry.Action.CREATE:
|
||||
if kwargs.get('object_id', None) is not None and self.filter(content_type=kwargs.get('content_type'), object_id=kwargs.get('object_id')).exists():
|
||||
if kwargs.get('object_id', None) is not None and self.filter(content_type=kwargs.get('content_type'),
|
||||
object_id=kwargs.get(
|
||||
'object_id')).exists():
|
||||
self.filter(content_type=kwargs.get('content_type'), object_id=kwargs.get('object_id')).delete()
|
||||
else:
|
||||
self.filter(content_type=kwargs.get('content_type'), object_pk=kwargs.get('object_pk', '')).delete()
|
||||
|
|
@ -78,10 +76,10 @@ class LogEntryManager(models.Manager):
|
|||
content_type = ContentType.objects.get_for_model(instance.__class__)
|
||||
pk = self._get_pk_value(instance)
|
||||
|
||||
if isinstance(pk, integer_types):
|
||||
if isinstance(pk, int):
|
||||
return self.filter(content_type=content_type, object_id=pk)
|
||||
else:
|
||||
return self.filter(content_type=content_type, object_pk=smart_text(pk))
|
||||
return self.filter(content_type=content_type, object_pk=smart_str(pk))
|
||||
|
||||
def get_for_objects(self, queryset):
|
||||
"""
|
||||
|
|
@ -98,10 +96,10 @@ class LogEntryManager(models.Manager):
|
|||
content_type = ContentType.objects.get_for_model(queryset.model)
|
||||
primary_keys = list(queryset.values_list(queryset.model._meta.pk.name, flat=True))
|
||||
|
||||
if isinstance(primary_keys[0], integer_types):
|
||||
if isinstance(primary_keys[0], int):
|
||||
return self.filter(content_type=content_type).filter(Q(object_id__in=primary_keys)).distinct()
|
||||
elif isinstance(queryset.model._meta.pk, models.UUIDField):
|
||||
primary_keys = [smart_text(pk) for pk in primary_keys]
|
||||
primary_keys = [smart_str(pk) for pk in primary_keys]
|
||||
return self.filter(content_type=content_type).filter(Q(object_pk__in=primary_keys)).distinct()
|
||||
else:
|
||||
return self.filter(content_type=content_type).filter(Q(object_pk__in=primary_keys)).distinct()
|
||||
|
|
@ -140,7 +138,6 @@ class LogEntryManager(models.Manager):
|
|||
return pk
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class LogEntry(models.Model):
|
||||
"""
|
||||
Represents an entry in the audit log. The content type is saved along with the textual and numeric (if available)
|
||||
|
|
@ -171,13 +168,15 @@ class LogEntry(models.Model):
|
|||
(DELETE, _("delete")),
|
||||
)
|
||||
|
||||
content_type = models.ForeignKey(to='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(to=settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, blank=True, null=True, 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"))
|
||||
|
|
@ -213,7 +212,7 @@ class LogEntry(models.Model):
|
|||
return {}
|
||||
|
||||
@property
|
||||
def changes_str(self, colon=': ', arrow=smart_text(' \u2192 '), separator='; '):
|
||||
def changes_str(self, colon=': ', arrow=' \u2192 ', separator='; '):
|
||||
"""
|
||||
Return the changes recorded in this log entry as a string. The formatting of the string can be customized by
|
||||
setting alternate values for colon, arrow and separator. If the formatting is still not satisfying, please use
|
||||
|
|
@ -226,8 +225,8 @@ class LogEntry(models.Model):
|
|||
"""
|
||||
substrings = []
|
||||
|
||||
for field, values in iteritems(self.changes_dict):
|
||||
substring = smart_text('{field_name:s}{colon:s}{old:s}{arrow:s}{new:s}').format(
|
||||
for field, values in self.changes_dict.items():
|
||||
substring = '{field_name:s}{colon:s}{old:s}{arrow:s}{new:s}'.format(
|
||||
field_name=field,
|
||||
colon=colon,
|
||||
old=values[0],
|
||||
|
|
@ -249,7 +248,7 @@ class LogEntry(models.Model):
|
|||
model_fields = auditlog.get_model_fields(model._meta.model)
|
||||
changes_display_dict = {}
|
||||
# grab the changes_dict and iterate through
|
||||
for field_name, values in iteritems(self.changes_dict):
|
||||
for field_name, values in self.changes_dict.items():
|
||||
# try to get the field attribute on the model
|
||||
try:
|
||||
field = model._meta.get_field(field_name)
|
||||
|
|
@ -355,6 +354,7 @@ class AuditlogHistoryField(GenericRelation):
|
|||
# South compatibility for AuditlogHistoryField
|
||||
try:
|
||||
from south.modelsinspector import add_introspection_rules
|
||||
|
||||
add_introspection_rules([], ["^auditlog\.models\.AuditlogHistoryField"])
|
||||
raise DeprecationWarning("South support will be dropped in django-auditlog 0.4.0 or later.")
|
||||
except ImportError:
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
import json
|
||||
|
||||
from auditlog.diff import model_instance_diff
|
||||
|
|
|
|||
|
|
@ -1,8 +1,5 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
from django.db.models.signals import pre_save, post_save, post_delete
|
||||
from django.db.models import Model
|
||||
from django.utils.six import iteritems
|
||||
|
||||
|
||||
class AuditlogModelRegistry(object):
|
||||
|
|
|
|||
|
|
@ -5,8 +5,6 @@ from django.db import models
|
|||
from auditlog.models import AuditlogHistoryField
|
||||
from auditlog.registry import auditlog
|
||||
|
||||
from multiselectfield import MultiSelectField
|
||||
|
||||
|
||||
@auditlog.register()
|
||||
class SimpleModel(models.Model):
|
||||
|
|
@ -171,7 +169,6 @@ class ChoicesFieldModel(models.Model):
|
|||
)
|
||||
|
||||
status = models.CharField(max_length=1, choices=STATUS_CHOICES)
|
||||
multiselect = MultiSelectField(max_length=3, choices=STATUS_CHOICES, max_choices=3)
|
||||
multiplechoice = models.CharField(max_length=255, choices=STATUS_CHOICES)
|
||||
|
||||
history = AuditlogHistoryField()
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
"""
|
||||
Settings file for the Auditlog test suite.
|
||||
"""
|
||||
import django
|
||||
|
||||
SECRET_KEY = 'test'
|
||||
|
||||
|
|
@ -13,10 +12,9 @@ INSTALLED_APPS = [
|
|||
'django.contrib.admin',
|
||||
'auditlog',
|
||||
'auditlog_tests',
|
||||
'multiselectfield',
|
||||
]
|
||||
|
||||
middlewares = (
|
||||
MIDDLEWARE = (
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
|
|
@ -24,19 +22,9 @@ middlewares = (
|
|||
'auditlog.middleware.AuditlogMiddleware',
|
||||
)
|
||||
|
||||
if django.VERSION < (1, 10):
|
||||
MIDDLEWARE_CLASSES = middlewares
|
||||
else:
|
||||
MIDDLEWARE = middlewares
|
||||
|
||||
if django.VERSION <= (1, 9):
|
||||
POSTGRES_DRIVER = 'django.db.backends.postgresql_psycopg2'
|
||||
else:
|
||||
POSTGRES_DRIVER = 'django.db.backends.postgresql'
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': POSTGRES_DRIVER,
|
||||
'ENGINE': 'django.db.backends.postgresql',
|
||||
'NAME': 'auditlog_tests_db',
|
||||
'USER': 'postgres',
|
||||
'PASSWORD': '',
|
||||
|
|
|
|||
|
|
@ -506,7 +506,6 @@ class ChoicesFieldModelTest(TestCase):
|
|||
def setUp(self):
|
||||
self.obj = ChoicesFieldModel.objects.create(
|
||||
status=ChoicesFieldModel.RED,
|
||||
multiselect=[ChoicesFieldModel.RED, ChoicesFieldModel.GREEN],
|
||||
multiplechoice=[ChoicesFieldModel.RED, ChoicesFieldModel.YELLOW, ChoicesFieldModel.GREEN],
|
||||
)
|
||||
|
||||
|
|
@ -518,22 +517,6 @@ class ChoicesFieldModelTest(TestCase):
|
|||
self.obj.save()
|
||||
self.assertTrue(self.obj.history.latest().changes_display_dict["status"][1] == "Green", msg="The human readable text 'Green' is displayed.")
|
||||
|
||||
def test_changes_display_dict_multiselect(self):
|
||||
self.assertTrue(self.obj.history.latest().changes_display_dict["multiselect"][1] == "Red, Green",
|
||||
msg="The human readable text for the two choices, 'Red, Green' is displayed.")
|
||||
self.obj.multiselect = ChoicesFieldModel.GREEN
|
||||
self.obj.save()
|
||||
self.assertTrue(self.obj.history.latest().changes_display_dict["multiselect"][1] == "Green",
|
||||
msg="The human readable text 'Green' is displayed.")
|
||||
self.obj.multiselect = None
|
||||
self.obj.save()
|
||||
self.assertTrue(self.obj.history.latest().changes_display_dict["multiselect"][1] == "None",
|
||||
msg="The human readable text 'None' is displayed.")
|
||||
self.obj.multiselect = ChoicesFieldModel.GREEN
|
||||
self.obj.save()
|
||||
self.assertTrue(self.obj.history.latest().changes_display_dict["multiselect"][1] == "Green",
|
||||
msg="The human readable text 'Green' is displayed.")
|
||||
|
||||
def test_changes_display_dict_multiplechoice(self):
|
||||
self.assertTrue(self.obj.history.latest().changes_display_dict["multiplechoice"][1] == "Red, Yellow, Green",
|
||||
msg="The human readable text 'Red, Yellow, Green' is displayed.")
|
||||
|
|
|
|||
|
|
@ -11,10 +11,10 @@ The repository can be found at https://github.com/jjkester/django-auditlog/.
|
|||
|
||||
**Requirements**
|
||||
|
||||
- Python 2.7, 3.4 or higher
|
||||
- Django 1.8 or higher
|
||||
- Python 3.5 or higher
|
||||
- Django 2.2 or higher
|
||||
|
||||
Auditlog is currently tested with Python 2.7 and 3.4 and Django 1.8, 1.9 and 1.10. The latest test report can be found
|
||||
Auditlog is currently tested with Python 3.5 - 3.8 and Django 2.2 and 3.0. The latest test report can be found
|
||||
at https://travis-ci.org/jjkester/django-auditlog.
|
||||
|
||||
Adding Auditlog to your Django application
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Library requirements
|
||||
django-jsonfield>=1.0.0
|
||||
python-dateutil==2.6.0
|
||||
django-jsonfield
|
||||
python-dateutil
|
||||
|
||||
# Build requirements
|
||||
setuptools
|
||||
|
|
@ -11,8 +11,7 @@ sphinx
|
|||
sphinx_rtd_theme
|
||||
|
||||
# Test requirements
|
||||
coverage==4.3.4
|
||||
tox>=1.7.0
|
||||
codecov>=2.0.0
|
||||
django-multiselectfield==0.1.8
|
||||
coverage
|
||||
tox
|
||||
codecov
|
||||
psycopg2-binary
|
||||
|
|
|
|||
13
tox.ini
13
tox.ini
|
|
@ -1,23 +1,18 @@
|
|||
[tox]
|
||||
envlist =
|
||||
{py27,py34,py35,py36,py37}-django-111
|
||||
{py34,py35,py36,py37}-django-20
|
||||
{py35,py36,py37}-django-21
|
||||
{py35,py36,py37}-django-22
|
||||
{py35,py36,py37,py38}-django-22
|
||||
{py36,py37,py38}-django-30
|
||||
|
||||
[testenv]
|
||||
setenv =
|
||||
PYTHONPATH = {toxinidir}:{toxinidir}/auditlog
|
||||
commands = coverage run --source auditlog runtests.py
|
||||
deps =
|
||||
django-111: Django>=1.11,<2.0
|
||||
django-20: Django>=2.0,<2.1
|
||||
django-21: Django>=2.1,<2.2
|
||||
django-22: Django>=2.2,<2.3
|
||||
django-30: Django>=3.0,<3.1
|
||||
-r{toxinidir}/requirements.txt
|
||||
basepython =
|
||||
py38: python3.8
|
||||
py37: python3.7
|
||||
py36: python3.6
|
||||
py35: python3.5
|
||||
py34: python3.4
|
||||
py27: python2.7
|
||||
|
|
|
|||
Loading…
Reference in a new issue