From 96f2f3d93e165b02eebad618f13035a209f6cd4c Mon Sep 17 00:00:00 2001 From: Denis Date: Thu, 4 Jan 2018 17:49:39 +0100 Subject: [PATCH 1/5] Fixed resource_url to work with non-id models (#147) * Fixed resource_url to work with non-id models * Add tests for auditlog admin --- src/auditlog/mixins.py | 3 ++- src/auditlog_tests/test_settings.py | 30 ++++++++++++++++++++++++++--- src/auditlog_tests/tests.py | 28 +++++++++++++++++++++++++++ src/auditlog_tests/urls.py | 13 +++++++++++++ 4 files changed, 70 insertions(+), 4 deletions(-) create mode 100644 src/auditlog_tests/urls.py diff --git a/src/auditlog/mixins.py b/src/auditlog/mixins.py index 3bea200..469c008 100644 --- a/src/auditlog/mixins.py +++ b/src/auditlog/mixins.py @@ -34,7 +34,8 @@ class LogEntryAdminMixin(object): app_label, model = obj.content_type.app_label, obj.content_type.model viewname = 'admin:%s_%s_change' % (app_label, model) try: - link = urlresolvers.reverse(viewname, args=[obj.object_id]) + args = [obj.object_pk] if obj.object_id is None else [obj.object_id] + link = urlresolvers.reverse(viewname, args=args) except NoReverseMatch: return obj.object_repr else: diff --git a/src/auditlog_tests/test_settings.py b/src/auditlog_tests/test_settings.py index 59541a4..04b443a 100644 --- a/src/auditlog_tests/test_settings.py +++ b/src/auditlog_tests/test_settings.py @@ -1,6 +1,7 @@ """ Settings file for the Auditlog test suite. """ +import os import django SECRET_KEY = 'test' @@ -8,18 +9,27 @@ SECRET_KEY = 'test' INSTALLED_APPS = [ 'django.contrib.auth', 'django.contrib.contenttypes', + 'django.contrib.messages', 'django.contrib.sessions', + 'django.contrib.admin', 'auditlog', 'auditlog_tests', 'multiselectfield', ] -MIDDLEWARE_CLASSES = ( +middlewares = ( 'django.middleware.common.CommonMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware' + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', '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: @@ -42,6 +52,20 @@ DATABASES = { } } -ROOT_URLCONF = [] +TEMPLATES = [ + { + 'APP_DIRS': True, + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'OPTIONS': { + 'context_processors': [ + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ] + }, + }, +] + +ROOT_URLCONF = 'auditlog_tests.urls' USE_TZ = True diff --git a/src/auditlog_tests/tests.py b/src/auditlog_tests/tests.py index 3ad6e7e..fc958f6 100644 --- a/src/auditlog_tests/tests.py +++ b/src/auditlog_tests/tests.py @@ -619,3 +619,31 @@ class CompatibilityTest(TestCase): else: assert not self.user.is_anonymous assert compat.is_authenticated(self.user) + + +class AdminPanelTest(TestCase): + @classmethod + def setUpTestData(cls): + cls.username = "test_admin" + cls.password = User.objects.make_random_password() + cls.user, created = User.objects.get_or_create(username=cls.username) + cls.user.set_password(cls.password) + cls.user.is_staff = True + cls.user.is_superuser = True + cls.user.is_active = True + cls.user.save() + cls.obj = SimpleModel.objects.create(text='For admin logentry test') + + def test_auditlog_admin(self): + self.client.login(username=self.username, password=self.password) + log_pk = self.obj.history.latest().pk + res = self.client.get("/admin/auditlog/logentry/") + assert res.status_code == 200 + res = self.client.get("/admin/auditlog/logentry/add/") + assert res.status_code == 200 + res = self.client.get("/admin/auditlog/logentry/{}/".format(log_pk), follow=True) + assert res.status_code == 200 + res = self.client.get("/admin/auditlog/logentry/{}/delete/".format(log_pk)) + assert res.status_code == 200 + res = self.client.get("/admin/auditlog/logentry/{}/history/".format(log_pk)) + assert res.status_code == 200 diff --git a/src/auditlog_tests/urls.py b/src/auditlog_tests/urls.py new file mode 100644 index 0000000..c9346f2 --- /dev/null +++ b/src/auditlog_tests/urls.py @@ -0,0 +1,13 @@ +import django +from django.conf.urls import include, url +from django.contrib import admin + + +if django.VERSION < (1, 9): + admin_urls = include(admin.site.urls) +else: + admin_urls = admin.site.urls + +urlpatterns = [ + url(r'^admin/', admin_urls), +] From 31db99497a74c78dc815cf7384febbb4652c7d82 Mon Sep 17 00:00:00 2001 From: sven Date: Thu, 4 Jan 2018 19:43:23 +0100 Subject: [PATCH 2/5] (bugfix) Naive Datetimes Crashing (#107) * Timezone naive fix --- src/auditlog/diff.py | 2 +- src/auditlog_tests/models.py | 1 + src/auditlog_tests/tests.py | 31 ++++++++++++++++++++++--------- 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/src/auditlog/diff.py b/src/auditlog/diff.py index e73a15a..9f996f6 100644 --- a/src/auditlog/diff.py +++ b/src/auditlog/diff.py @@ -69,7 +69,7 @@ def get_field_value(obj, field): # to its naive form before we can accuratly compare them for changes. try: value = field.to_python(getattr(obj, field.name, None)) - if value is not None and settings.USE_TZ: + if value is not None and settings.USE_TZ and not timezone.is_naive(value): value = timezone.make_naive(value, timezone=timezone.utc) except ObjectDoesNotExist: value = field.default if field.default is not NOT_PROVIDED else None diff --git a/src/auditlog_tests/models.py b/src/auditlog_tests/models.py index d938bfe..dfb7198 100644 --- a/src/auditlog_tests/models.py +++ b/src/auditlog_tests/models.py @@ -150,6 +150,7 @@ class DateTimeFieldModel(models.Model): timestamp = models.DateTimeField() date = models.DateField() time = models.TimeField() + naive_dt = models.DateTimeField(null=True, blank=True) history = AuditlogHistoryField() diff --git a/src/auditlog_tests/tests.py b/src/auditlog_tests/tests.py index fc958f6..928bbc5 100644 --- a/src/auditlog_tests/tests.py +++ b/src/auditlog_tests/tests.py @@ -269,12 +269,13 @@ class DateTimeFieldModelTest(TestCase): """Tests if DateTimeField changes are recognised correctly""" utc_plus_one = timezone.get_fixed_timezone(datetime.timedelta(hours=1)) + now = timezone.now() def test_model_with_same_time(self): timestamp = datetime.datetime(2017, 1, 10, 12, 0, tzinfo=timezone.utc) date = datetime.date(2017, 1, 10) time = datetime.time(12, 0) - dtm = DateTimeFieldModel(label='DateTimeField model', timestamp=timestamp, date=date, time=time) + dtm = DateTimeFieldModel(label='DateTimeField model', timestamp=timestamp, date=date, time=time, naive_dt=self.now) dtm.save() self.assertTrue(dtm.history.count() == 1, msg="There is one log entry") @@ -292,7 +293,7 @@ class DateTimeFieldModelTest(TestCase): timestamp = datetime.datetime(2017, 1, 10, 12, 0, tzinfo=timezone.utc) date = datetime.date(2017, 1, 10) time = datetime.time(12, 0) - dtm = DateTimeFieldModel(label='DateTimeField model', timestamp=timestamp, date=date, time=time) + dtm = DateTimeFieldModel(label='DateTimeField model', timestamp=timestamp, date=date, time=time, naive_dt=self.now) dtm.save() self.assertTrue(dtm.history.count() == 1, msg="There is one log entry") @@ -308,7 +309,7 @@ class DateTimeFieldModelTest(TestCase): timestamp = datetime.datetime(2017, 1, 10, 12, 0, tzinfo=timezone.utc) date = datetime.date(2017, 1, 10) time = datetime.time(12, 0) - dtm = DateTimeFieldModel(label='DateTimeField model', timestamp=timestamp, date=date, time=time) + dtm = DateTimeFieldModel(label='DateTimeField model', timestamp=timestamp, date=date, time=time, naive_dt=self.now) dtm.save() self.assertTrue(dtm.history.count() == 1, msg="There is one log entry") @@ -324,7 +325,7 @@ class DateTimeFieldModelTest(TestCase): timestamp = datetime.datetime(2017, 1, 10, 12, 0, tzinfo=timezone.utc) date = datetime.date(2017, 1, 10) time = datetime.time(12, 0) - dtm = DateTimeFieldModel(label='DateTimeField model', timestamp=timestamp, date=date, time=time) + dtm = DateTimeFieldModel(label='DateTimeField model', timestamp=timestamp, date=date, time=time, naive_dt=self.now) dtm.save() self.assertTrue(dtm.history.count() == 1, msg="There is one log entry") @@ -340,7 +341,7 @@ class DateTimeFieldModelTest(TestCase): timestamp = datetime.datetime(2017, 1, 10, 12, 0, tzinfo=timezone.utc) date = datetime.date(2017, 1, 10) time = datetime.time(12, 0) - dtm = DateTimeFieldModel(label='DateTimeField model', timestamp=timestamp, date=date, time=time) + dtm = DateTimeFieldModel(label='DateTimeField model', timestamp=timestamp, date=date, time=time, naive_dt=self.now) dtm.save() self.assertTrue(dtm.history.count() == 1, msg="There is one log entry") @@ -356,7 +357,7 @@ class DateTimeFieldModelTest(TestCase): timestamp = datetime.datetime(2017, 1, 10, 12, 0, tzinfo=timezone.utc) date = datetime.date(2017, 1, 10) time = datetime.time(12, 0) - dtm = DateTimeFieldModel(label='DateTimeField model', timestamp=timestamp, date=date, time=time) + dtm = DateTimeFieldModel(label='DateTimeField model', timestamp=timestamp, date=date, time=time, naive_dt=self.now) dtm.save() self.assertTrue(dtm.history.count() == 1, msg="There is one log entry") @@ -372,7 +373,7 @@ class DateTimeFieldModelTest(TestCase): timestamp = datetime.datetime(2017, 1, 10, 15, 0, tzinfo=timezone.utc) date = datetime.date(2017, 1, 10) time = datetime.time(12, 0) - dtm = DateTimeFieldModel(label='DateTimeField model', timestamp=timestamp, date=date, time=time) + dtm = DateTimeFieldModel(label='DateTimeField model', timestamp=timestamp, date=date, time=time, naive_dt=self.now) dtm.save() self.assertTrue(dtm.history.latest().changes_display_dict["timestamp"][1] == \ dateformat.format(timestamp, settings.DATETIME_FORMAT), @@ -398,7 +399,7 @@ class DateTimeFieldModelTest(TestCase): timestamp = datetime.datetime(2017, 1, 10, 15, 0, tzinfo=timezone.utc) date = datetime.date(2017, 1, 10) time = datetime.time(12, 0) - dtm = DateTimeFieldModel(label='DateTimeField model', timestamp=timestamp, date=date, time=time) + dtm = DateTimeFieldModel(label='DateTimeField model', timestamp=timestamp, date=date, time=time, naive_dt=self.now) dtm.save() self.assertTrue(dtm.history.latest().changes_display_dict["date"][1] == \ dateformat.format(date, settings.DATE_FORMAT), @@ -423,7 +424,7 @@ class DateTimeFieldModelTest(TestCase): timestamp = datetime.datetime(2017, 1, 10, 15, 0, tzinfo=timezone.utc) date = datetime.date(2017, 1, 10) time = datetime.time(12, 0) - dtm = DateTimeFieldModel(label='DateTimeField model', timestamp=timestamp, date=date, time=time) + dtm = DateTimeFieldModel(label='DateTimeField model', timestamp=timestamp, date=date, time=time, naive_dt=self.now) dtm.save() self.assertTrue(dtm.history.latest().changes_display_dict["time"][1] == \ dateformat.format(time, settings.TIME_FORMAT), @@ -444,6 +445,17 @@ class DateTimeFieldModelTest(TestCase): msg=("The time should be formatted according to Django's settings for" " USE_L10N is True with a different LANGUAGE_CODE.")) + def test_update_naive_dt(self): + timestamp = datetime.datetime(2017, 1, 10, 15, 0, tzinfo=timezone.utc) + date = datetime.date(2017, 1, 10) + time = datetime.time(12, 0) + dtm = DateTimeFieldModel(label='DateTimeField model', timestamp=timestamp, date=date, time=time, naive_dt=self.now) + dtm.save() + + # Change with naive field doesnt raise error + dtm.naive_dt = timezone.make_naive(timezone.now(), timezone=timezone.utc) + dtm.save() + class UnregisterTest(TestCase): def setUp(self): @@ -647,3 +659,4 @@ class AdminPanelTest(TestCase): assert res.status_code == 200 res = self.client.get("/admin/auditlog/logentry/{}/history/".format(log_pk)) assert res.status_code == 200 + From 881b7326bd3d8e8e5fd79d0daecf2e2d8a8a90fe Mon Sep 17 00:00:00 2001 From: Stephen Mc Guinness Date: Thu, 4 Jan 2018 19:07:13 +0000 Subject: [PATCH 3/5] (bugfix) Adds hasattr check before threadlocal.auditlog access in set_actor middleware.set_actor was inconsistent with other methods in checking for hasattr(threadlocal, 'auditlog') --- src/auditlog/middleware.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/auditlog/middleware.py b/src/auditlog/middleware.py index a266fee..176de05 100644 --- a/src/auditlog/middleware.py +++ b/src/auditlog/middleware.py @@ -70,14 +70,15 @@ class AuditlogMiddleware(MiddlewareMixin): Signal receiver with an extra, required 'user' kwarg. This method becomes a real (valid) signal receiver when it is curried with the actor. """ - if signal_duid != threadlocal.auditlog['signal_duid']: - return - try: - app_label, model_name = settings.AUTH_USER_MODEL.split('.') - auth_user_model = apps.get_model(app_label, model_name) - except ValueError: - auth_user_model = apps.get_model('auth', 'user') - if sender == LogEntry and isinstance(user, auth_user_model) and instance.actor is None: - instance.actor = user if hasattr(threadlocal, 'auditlog'): + if signal_duid != threadlocal.auditlog['signal_duid']: + return + try: + app_label, model_name = settings.AUTH_USER_MODEL.split('.') + auth_user_model = apps.get_model(app_label, model_name) + except ValueError: + auth_user_model = apps.get_model('auth', 'user') + if sender == LogEntry and isinstance(user, auth_user_model) and instance.actor is None: + instance.actor = user + instance.remote_addr = threadlocal.auditlog['remote_addr'] From 560e2028c6df8d1233cdaefda26fbd7ec04647aa Mon Sep 17 00:00:00 2001 From: AmirAli Akbari Date: Thu, 4 Jan 2018 22:56:52 +0330 Subject: [PATCH 4/5] (bugfix) Fall back on actor repr when reverse url fails --- src/auditlog/mixins.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/auditlog/mixins.py b/src/auditlog/mixins.py index 469c008..dd6df70 100644 --- a/src/auditlog/mixins.py +++ b/src/auditlog/mixins.py @@ -23,7 +23,10 @@ class LogEntryAdminMixin(object): if obj.actor: app_label, model = settings.AUTH_USER_MODEL.split('.') viewname = 'admin:%s_%s_change' % (app_label, model.lower()) - link = urlresolvers.reverse(viewname, args=[obj.actor.id]) + try: + link = urlresolvers.reverse(viewname, args=[obj.actor.id]) + except NoReverseMatch: + return u'%s' % (obj.actor) return u'%s' % (link, obj.actor) return 'system' From b410b405975d751a7247e322b35348d550d27c01 Mon Sep 17 00:00:00 2001 From: Ryan Castner Date: Mon, 8 Jan 2018 09:39:45 -0500 Subject: [PATCH 5/5] Add missing django 2.0 tox tests (#156) --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 435609c..9b87320 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,6 +21,9 @@ env: - TOX_ENV=py35-django-111 - TOX_ENV=py34-django-111 - TOX_ENV=py27-django-111 + - TOX_ENV=py36-django-20 + - TOX_ENV=py35-django-20 + - TOX_ENV=py34-django-20 matrix: fast_finish: true