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 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/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'] diff --git a/src/auditlog/mixins.py b/src/auditlog/mixins.py index 3bea200..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' @@ -34,7 +37,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/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/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 01dc852..6558c61 100644 --- a/src/auditlog_tests/tests.py +++ b/src/auditlog_tests/tests.py @@ -270,12 +270,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") @@ -293,7 +294,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") @@ -309,7 +310,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") @@ -325,7 +326,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") @@ -341,7 +342,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") @@ -357,7 +358,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") @@ -373,7 +374,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() localized_timestamp = timestamp.astimezone(gettz(settings.TIME_ZONE)) self.assertTrue(dtm.history.latest().changes_display_dict["timestamp"][1] == \ @@ -401,7 +402,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), @@ -426,7 +427,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), @@ -447,6 +448,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): @@ -622,3 +634,32 @@ 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), +]