Drop Python 2, support Django 3.0, update dependencies

This commit is contained in:
Jan-Jelle Kester 2020-04-22 22:28:29 +02:00
parent 4e7c640ba0
commit 3acab4322b
22 changed files with 53 additions and 148 deletions

View file

@ -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

View file

@ -1,5 +1,3 @@
from __future__ import unicode_literals
__version__ = '1.0a1'
default_app_config = 'auditlog.apps.AuditlogConfig'

View file

@ -1,5 +1,3 @@
from __future__ import unicode_literals
from django.apps import AppConfig

View file

@ -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

View file

@ -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()

View file

@ -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):

View file

@ -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

View file

@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations

View file

@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations

View file

@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import jsonfield.fields

View file

@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
import jsonfield.fields

View file

@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models

View file

@ -1,6 +1,3 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models

View file

@ -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:

View file

@ -1,5 +1,3 @@
from __future__ import unicode_literals
import json
from auditlog.diff import model_instance_diff

View file

@ -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):

View file

@ -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()

View file

@ -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': '',

View file

@ -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.")

View file

@ -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

View file

@ -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
View file

@ -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