mirror of
https://github.com/jazzband/django-auditlog.git
synced 2026-05-02 20:54:42 +00:00
Merge upstream 2.1.1 and partial 2.2.0
This commit is contained in:
commit
c65a0b23c4
9 changed files with 110 additions and 38 deletions
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
repos:
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 22.3.0
|
||||
rev: 22.6.0
|
||||
hooks:
|
||||
- id: black
|
||||
language_version: python3.8
|
||||
|
|
@ -9,7 +9,7 @@ repos:
|
|||
- "--target-version"
|
||||
- "py37"
|
||||
- repo: https://github.com/PyCQA/flake8
|
||||
rev: "4.0.1"
|
||||
rev: "5.0.4"
|
||||
hooks:
|
||||
- id: flake8
|
||||
args: ["--max-line-length", "110"]
|
||||
|
|
@ -18,7 +18,12 @@ repos:
|
|||
hooks:
|
||||
- id: isort
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v2.34.0
|
||||
rev: v2.37.3
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args: [--py37-plus]
|
||||
args: [--py37-plus]
|
||||
- repo: https://github.com/adamchainz/django-upgrade
|
||||
rev: 1.8.0
|
||||
hooks:
|
||||
- id: django-upgrade
|
||||
args: [--target-version, "3.2"]
|
||||
16
CHANGELOG.md
16
CHANGELOG.md
|
|
@ -1,5 +1,21 @@
|
|||
# Changes
|
||||
|
||||
#### Fixes
|
||||
|
||||
- fix: Display `created` timestamp in server timezone ([#404](https://github.com/jazzband/django-auditlog/pull/404))
|
||||
- fix: Handle port in `remote_addr` ([#417](https://github.com/jazzband/django-auditlog/pull/417))
|
||||
|
||||
## 2.1.1 (2022-07-27)
|
||||
|
||||
#### Improvements
|
||||
|
||||
- feat: Display the diff for deleted objects in the admin ([#396](https://github.com/jazzband/django-auditlog/pull/396))
|
||||
- Django: Confirm Django 4.1 support ([#406](https://github.com/jazzband/django-auditlog/pull/406))
|
||||
|
||||
#### Fixes
|
||||
|
||||
- fix: Pin `python-dateutil` to 2.7.0 or higher for compatibility with Python 3.10 ([#401](https://github.com/jazzband/django-auditlog/pull/401))
|
||||
|
||||
## 2.1.0 (2022-06-27)
|
||||
|
||||
#### Improvements
|
||||
|
|
|
|||
12
README.md
12
README.md
|
|
@ -32,8 +32,10 @@ If you have great ideas for Auditlog, or if you like to improve something, feel
|
|||
Releases
|
||||
--------
|
||||
|
||||
1. Make sure all tests on `master` are green.
|
||||
2. Create a new branch `vX.Y.Z` from master for that specific release.
|
||||
3. Bump versions in `setup.py` and `docs/source/conf.py` (docs have 2 places where the versions need to be changed!)
|
||||
4. Pull request `vX.Y.Z` -> `master`.
|
||||
5. Pull request `master` -> `stable`. This merge triggers the deploy to pypi.
|
||||
1. Make sure all tests on `master` are green
|
||||
2. Create a new branch `vX.Y.Z` from master for that specific release
|
||||
3. Update the CHANGELOG release date
|
||||
4. Pull request `vX.Y.Z` -> `master`
|
||||
5. As a project lead, once the PR is merged, create and push a tag `vX.Y.Z`: this will trigger the release build and a notification will be sent from Jazzband of the availability of two packages (tgz and wheel)
|
||||
6. Test the install
|
||||
7. Publish the release to PyPI
|
||||
|
|
|
|||
|
|
@ -12,13 +12,18 @@ class AuditlogMiddleware:
|
|||
def __init__(self, get_response=None):
|
||||
self.get_response = get_response
|
||||
|
||||
def __call__(self, request):
|
||||
|
||||
if request.META.get("HTTP_X_FORWARDED_FOR"):
|
||||
@staticmethod
|
||||
def _get_remote_addr(request):
|
||||
if request.headers.get("X-Forwarded-For"):
|
||||
# In case of proxy, set 'original' address
|
||||
remote_addr = request.META.get("HTTP_X_FORWARDED_FOR").split(",")[0]
|
||||
remote_addr = request.headers.get("X-Forwarded-For").split(",")[0]
|
||||
# Remove port number from remote_addr
|
||||
return remote_addr.split(":")[0]
|
||||
else:
|
||||
remote_addr = request.META.get("REMOTE_ADDR")
|
||||
return request.META.get("REMOTE_ADDR")
|
||||
|
||||
def __call__(self, request):
|
||||
remote_addr = self._get_remote_addr(request)
|
||||
|
||||
if hasattr(request, "user") and request.user.is_authenticated:
|
||||
context = set_actor(actor=request.user, remote_addr=remote_addr)
|
||||
|
|
|
|||
|
|
@ -64,8 +64,6 @@ class LogEntryAdminMixin:
|
|||
msg_short.short_description = "Changes"
|
||||
|
||||
def msg(self, obj):
|
||||
if obj.action == LogEntry.Action.DELETE:
|
||||
return "" # delete
|
||||
changes = json.loads(obj.changes)
|
||||
|
||||
atom_changes = {}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import json
|
|||
import warnings
|
||||
from unittest import mock
|
||||
|
||||
import freezegun
|
||||
from dateutil.tz import gettz
|
||||
from django.apps import apps
|
||||
from django.conf import settings
|
||||
|
|
@ -436,6 +437,19 @@ class MiddlewareTest(TestCase):
|
|||
|
||||
self.assert_no_listeners()
|
||||
|
||||
def test_get_remote_addr(self):
|
||||
tests = [ # (headers, expected_remote_addr)
|
||||
({}, "127.0.0.1"),
|
||||
({"HTTP_X_FORWARDED_FOR": "127.0.0.2"}, "127.0.0.2"),
|
||||
({"HTTP_X_FORWARDED_FOR": "127.0.0.3:1234"}, "127.0.0.3"),
|
||||
]
|
||||
for headers, expected_remote_addr in tests:
|
||||
with self.subTest(headers=headers):
|
||||
request = self.factory.get("/", **headers)
|
||||
self.assertEqual(
|
||||
self.middleware._get_remote_addr(request), expected_remote_addr
|
||||
)
|
||||
|
||||
|
||||
class SimpleIncludeModelTest(TestCase):
|
||||
"""Log only changes in include_fields"""
|
||||
|
|
@ -1233,31 +1247,40 @@ class PostgresArrayFieldModelTest(TestCase):
|
|||
|
||||
|
||||
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 setUp(self):
|
||||
self.user = User.objects.create_user(
|
||||
username="test_admin", is_staff=True, is_superuser=True, is_active=True
|
||||
)
|
||||
self.site = AdminSite()
|
||||
self.admin = LogEntryAdmin(LogEntry, self.site)
|
||||
with freezegun.freeze_time("2022-08-01 12:00:00Z"):
|
||||
self.obj = SimpleModel.objects.create(text="For admin logentry test")
|
||||
|
||||
def test_auditlog_admin(self):
|
||||
self.client.login(username=self.username, password=self.password)
|
||||
self.client.force_login(self.user)
|
||||
log_pk = self.obj.history.latest().pk
|
||||
res = self.client.get("/admin/auditlog/logentry/")
|
||||
assert res.status_code == 200
|
||||
self.assertEqual(res.status_code, 200)
|
||||
res = self.client.get("/admin/auditlog/logentry/add/")
|
||||
assert res.status_code == 403
|
||||
self.assertEqual(res.status_code, 403)
|
||||
res = self.client.get(f"/admin/auditlog/logentry/{log_pk}/", follow=True)
|
||||
assert res.status_code == 200
|
||||
self.assertEqual(res.status_code, 200)
|
||||
res = self.client.get(f"/admin/auditlog/logentry/{log_pk}/delete/")
|
||||
assert res.status_code == 200
|
||||
self.assertEqual(res.status_code, 200)
|
||||
res = self.client.get(f"/admin/auditlog/logentry/{log_pk}/history/")
|
||||
assert res.status_code == 200
|
||||
self.assertEqual(res.status_code, 200)
|
||||
|
||||
def test_created_timezone(self):
|
||||
log_entry = self.obj.history.latest()
|
||||
|
||||
for tz, timestamp in [
|
||||
("UTC", "2022-08-01 12:00:00"),
|
||||
("Asia/Tbilisi", "2022-08-01 16:00:00"),
|
||||
("America/Buenos_Aires", "2022-08-01 09:00:00"),
|
||||
("Asia/Kathmandu", "2022-08-01 17:45:00"),
|
||||
]:
|
||||
with self.settings(TIME_ZONE=tz):
|
||||
self.assertEqual(self.admin.created(log_entry), timestamp)
|
||||
|
||||
|
||||
class DiffMsgTest(TestCase):
|
||||
|
|
@ -1274,9 +1297,22 @@ class DiffMsgTest(TestCase):
|
|||
)
|
||||
|
||||
def test_changes_msg_delete(self):
|
||||
log_entry = self._create_log_entry(LogEntry.Action.DELETE, {})
|
||||
log_entry = self._create_log_entry(
|
||||
LogEntry.Action.DELETE,
|
||||
{"field one": ["value before deletion", None], "field two": [11, None]},
|
||||
)
|
||||
|
||||
self.assertEqual(self.admin.msg(log_entry), "")
|
||||
self.assertEqual(self.admin.msg_short(log_entry), "")
|
||||
self.assertEqual(
|
||||
self.admin.msg(log_entry),
|
||||
(
|
||||
"<table>"
|
||||
"<tr><th>#</th><th>Field</th><th>From</th><th>To</th></tr>"
|
||||
"<tr><td>1</td><td>field one</td><td>value before deletion</td><td>None</td></tr>"
|
||||
"<tr><td>2</td><td>field two</td><td>11</td><td>None</td></tr>"
|
||||
"</table>"
|
||||
),
|
||||
)
|
||||
|
||||
def test_changes_msg_create(self):
|
||||
log_entry = self._create_log_entry(
|
||||
|
|
@ -1287,6 +1323,9 @@ class DiffMsgTest(TestCase):
|
|||
},
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
self.admin.msg_short(log_entry), "2 changes: field two, field one"
|
||||
)
|
||||
self.assertEqual(
|
||||
self.admin.msg(log_entry),
|
||||
(
|
||||
|
|
@ -1307,6 +1346,9 @@ class DiffMsgTest(TestCase):
|
|||
},
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
self.admin.msg_short(log_entry), "2 changes: field two, field one"
|
||||
)
|
||||
self.assertEqual(
|
||||
self.admin.msg(log_entry),
|
||||
(
|
||||
|
|
@ -1331,6 +1373,7 @@ class DiffMsgTest(TestCase):
|
|||
},
|
||||
)
|
||||
|
||||
self.assertEqual(self.admin.msg_short(log_entry), "1 change: some_m2m_field")
|
||||
self.assertEqual(
|
||||
self.admin.msg(log_entry),
|
||||
(
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ Automatically logging changes
|
|||
Auditlog can automatically log changes to objects for you. This functionality is based on Django's signals, but linking
|
||||
your models to Auditlog is even easier than using signals.
|
||||
|
||||
Registering your model for logging can be done with a single line of code, as the following example illustrates::
|
||||
Registering your model for logging can be done with a single line of code, as the following example illustrates:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
|
|
|
|||
4
setup.py
4
setup.py
|
|
@ -28,9 +28,10 @@ setup(
|
|||
description="Audit log app for Django",
|
||||
long_description=long_description,
|
||||
long_description_content_type="text/markdown",
|
||||
python_requires=">=3.7",
|
||||
install_requires=[
|
||||
"django-admin-rangefilter>=0.8.0",
|
||||
"python-dateutil>=2.6.0",
|
||||
"python-dateutil>=2.7.0",
|
||||
],
|
||||
zip_safe=False,
|
||||
classifiers=[
|
||||
|
|
@ -42,6 +43,7 @@ setup(
|
|||
"Framework :: Django",
|
||||
"Framework :: Django :: 3.2",
|
||||
"Framework :: Django :: 4.0",
|
||||
"Framework :: Django :: 4.1",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
],
|
||||
)
|
||||
|
|
|
|||
3
tox.ini
3
tox.ini
|
|
@ -1,7 +1,7 @@
|
|||
[tox]
|
||||
envlist =
|
||||
{py37,py38,py39,py310}-django32
|
||||
{py38,py39,py310}-django{40,main}
|
||||
{py38,py39,py310}-django{40,41,main}
|
||||
py37-docs
|
||||
py38-lint
|
||||
|
||||
|
|
@ -14,6 +14,7 @@ commands =
|
|||
deps =
|
||||
django32: Django>=3.2,<3.3
|
||||
django40: Django>=4.0,<4.1
|
||||
django41: Django>=4.1,<4.2
|
||||
djangomain: https://github.com/django/django/archive/main.tar.gz
|
||||
# Test requirements
|
||||
coverage
|
||||
|
|
|
|||
Loading…
Reference in a new issue