drf-audit-logger 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- drf_audit_logger-1.0.0/LICENSE.md +38 -0
- drf_audit_logger-1.0.0/PKG-INFO +655 -0
- drf_audit_logger-1.0.0/README.md +613 -0
- drf_audit_logger-1.0.0/drf_audit_logger/__init__.py +8 -0
- drf_audit_logger-1.0.0/drf_audit_logger/_local.py +49 -0
- drf_audit_logger-1.0.0/drf_audit_logger/admin.py +131 -0
- drf_audit_logger-1.0.0/drf_audit_logger/apps.py +11 -0
- drf_audit_logger-1.0.0/drf_audit_logger/conf.py +60 -0
- drf_audit_logger-1.0.0/drf_audit_logger/locale/en/LC_MESSAGES/django.mo +0 -0
- drf_audit_logger-1.0.0/drf_audit_logger/locale/en/LC_MESSAGES/django.po +128 -0
- drf_audit_logger-1.0.0/drf_audit_logger/locale/fa/LC_MESSAGES/django.mo +0 -0
- drf_audit_logger-1.0.0/drf_audit_logger/locale/fa/LC_MESSAGES/django.po +128 -0
- drf_audit_logger-1.0.0/drf_audit_logger/middleware.py +84 -0
- drf_audit_logger-1.0.0/drf_audit_logger/migrations/0001_initial.py +39 -0
- drf_audit_logger-1.0.0/drf_audit_logger/migrations/__init__.py +0 -0
- drf_audit_logger-1.0.0/drf_audit_logger/models.py +130 -0
- drf_audit_logger-1.0.0/drf_audit_logger/permissions.py +25 -0
- drf_audit_logger-1.0.0/drf_audit_logger/renderers.py +178 -0
- drf_audit_logger-1.0.0/drf_audit_logger/serializers.py +89 -0
- drf_audit_logger-1.0.0/drf_audit_logger/services.py +287 -0
- drf_audit_logger-1.0.0/drf_audit_logger/signals.py +171 -0
- drf_audit_logger-1.0.0/drf_audit_logger/tests.py +0 -0
- drf_audit_logger-1.0.0/drf_audit_logger/urls.py +30 -0
- drf_audit_logger-1.0.0/drf_audit_logger/views.py +142 -0
- drf_audit_logger-1.0.0/drf_audit_logger.egg-info/PKG-INFO +655 -0
- drf_audit_logger-1.0.0/drf_audit_logger.egg-info/SOURCES.txt +29 -0
- drf_audit_logger-1.0.0/drf_audit_logger.egg-info/dependency_links.txt +1 -0
- drf_audit_logger-1.0.0/drf_audit_logger.egg-info/requires.txt +8 -0
- drf_audit_logger-1.0.0/drf_audit_logger.egg-info/top_level.txt +1 -0
- drf_audit_logger-1.0.0/pyproject.toml +81 -0
- drf_audit_logger-1.0.0/setup.cfg +4 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
License
|
|
2
|
+
Licensed under the BSD 3-Clause License. See LICENSE.md for details.
|
|
3
|
+
|
|
4
|
+
Copyright © 2025, Taha Zarei.
|
|
5
|
+
|
|
6
|
+
text
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
# License
|
|
10
|
+
|
|
11
|
+
Copyright © 2025, Taha Zarei.
|
|
12
|
+
All rights reserved.
|
|
13
|
+
|
|
14
|
+
Redistribution and use in source and binary forms, with or without
|
|
15
|
+
modification, are permitted provided that the following conditions are met:
|
|
16
|
+
|
|
17
|
+
1. Redistributions of source code must retain the above copyright notice,
|
|
18
|
+
this list of conditions and the following disclaimer.
|
|
19
|
+
|
|
20
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
21
|
+
this list of conditions and the following disclaimer in the documentation
|
|
22
|
+
and/or other materials provided with the distribution.
|
|
23
|
+
|
|
24
|
+
3. Neither the name of the copyright holder nor the names of its contributors
|
|
25
|
+
may be used to endorse or promote products derived from this software
|
|
26
|
+
without specific prior written permission.
|
|
27
|
+
|
|
28
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
29
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
30
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
31
|
+
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
|
32
|
+
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
33
|
+
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
34
|
+
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
35
|
+
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
36
|
+
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
37
|
+
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
38
|
+
POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,655 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: drf-audit-logger
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Audit logging for Django REST Framework with multilingual support
|
|
5
|
+
Author-email: Taha Zarei <tahazarei777@gmail.com>
|
|
6
|
+
Maintainer-email: Taha Zarei <tahazarei777@gmail.com>
|
|
7
|
+
License-Expression: BSD-3-Clause
|
|
8
|
+
Project-URL: Homepage, https://github.com/tahazarei777/drf-audit-logger
|
|
9
|
+
Project-URL: Repository, https://github.com/tahazarei777/drf-audit-logger.git
|
|
10
|
+
Project-URL: Bug Tracker, https://github.com/tahazarei777/drf-audit-logger/issues
|
|
11
|
+
Project-URL: Changelog, https://github.com/tahazarei777/drf-audit-logger/blob/main/CHANGELOG.md
|
|
12
|
+
Keywords: django,django-rest-framework,drf,audit,audit-log,audit-trail,logging,middleware,i18n,multilingual,api,signals
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Framework :: Django
|
|
15
|
+
Classifier: Framework :: Django :: 3.2
|
|
16
|
+
Classifier: Framework :: Django :: 4.0
|
|
17
|
+
Classifier: Framework :: Django :: 4.1
|
|
18
|
+
Classifier: Framework :: Django :: 4.2
|
|
19
|
+
Classifier: Framework :: Django :: 5.0
|
|
20
|
+
Classifier: Intended Audience :: Developers
|
|
21
|
+
Classifier: Operating System :: OS Independent
|
|
22
|
+
Classifier: Programming Language :: Python :: 3
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
25
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
26
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
27
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
28
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
29
|
+
Classifier: Topic :: System :: Logging
|
|
30
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
|
31
|
+
Requires-Python: >=3.8
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
License-File: LICENSE.md
|
|
34
|
+
Requires-Dist: Django>=3.2
|
|
35
|
+
Requires-Dist: djangorestframework>=3.12
|
|
36
|
+
Provides-Extra: dev
|
|
37
|
+
Requires-Dist: build>=1.0.0; extra == "dev"
|
|
38
|
+
Requires-Dist: twine>=4.0.0; extra == "dev"
|
|
39
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
40
|
+
Requires-Dist: pytest-django>=4.5.0; extra == "dev"
|
|
41
|
+
Dynamic: license-file
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# DRF Audit Logger
|
|
45
|
+
|
|
46
|
+
**Audit logging for Django REST Framework with multilingual support.**
|
|
47
|
+
|
|
48
|
+
[](https://pypi.org/project/drf-audit-logger/)
|
|
49
|
+
[](https://www.python.org/)
|
|
50
|
+
[](https://www.djangoproject.com/)
|
|
51
|
+
[](https://www.django-rest-framework.org/)
|
|
52
|
+
[](LICENSE.md)
|
|
53
|
+
|
|
54
|
+
## Overview
|
|
55
|
+
|
|
56
|
+
**DRF Audit Logger** is a Django + Django REST Framework package that automatically logs user actions such as login, logout, create, update, and delete events. All messages are rendered dynamically using Django's `gettext` framework, so they automatically adapt to the **active language** — no code changes needed when adding a new language.
|
|
57
|
+
|
|
58
|
+
Whether you need a simple audit trail for compliance, a debugging tool for tracking data changes, or a full activity log with REST API access, DRF Audit Logger provides it out of the box.
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Features
|
|
63
|
+
|
|
64
|
+
- ✅ **Automatic logging** via Django signals — no code changes in your models or views
|
|
65
|
+
- ✅ **Login / Logout / Failed login** tracking
|
|
66
|
+
- ✅ **Create / Update / Delete** tracking with full `changes` diff
|
|
67
|
+
- ✅ **Multilingual messages** via Django's `gettext` (add a language by dropping a `.po` file)
|
|
68
|
+
- ✅ **Dynamic model names** from `Meta.verbose_name` (auto-translated)
|
|
69
|
+
- ✅ **Dynamic field names** from `field.verbose_name` (auto-translated)
|
|
70
|
+
- ✅ **Sensitive field masking** (`password`, `token`, `api_key`, ...)
|
|
71
|
+
- ✅ **Works with any authentication system** (Session, JWT, Token, OAuth, Custom)
|
|
72
|
+
- ✅ **Custom user model support** via `AUTH_USER_MODEL`
|
|
73
|
+
- ✅ **Request metadata capture** — IP address, user agent
|
|
74
|
+
- ✅ **Configurable model exclusions** via `AUDIT_LOG_EXCLUDE_MODEL_LOGGING`
|
|
75
|
+
- ✅ **REST API** for querying and filtering logs
|
|
76
|
+
- ✅ **Django admin integration** with color-coded action badges
|
|
77
|
+
- ✅ **Database-indexed** for fast queries on large datasets
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Requirements
|
|
82
|
+
|
|
83
|
+
- Python >= 3.8
|
|
84
|
+
- Django >= 3.2
|
|
85
|
+
- djangorestframework >= 3.12
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## Installation
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
pip install drf-audit-logger
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## Setup
|
|
98
|
+
|
|
99
|
+
### 1. Add to `INSTALLED_APPS`
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
INSTALLED_APPS = [
|
|
103
|
+
# ...
|
|
104
|
+
'rest_framework',
|
|
105
|
+
'drf_audit_logger',
|
|
106
|
+
# ...
|
|
107
|
+
]
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### 2. Add the middleware
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
MIDDLEWARE = [
|
|
114
|
+
# ...
|
|
115
|
+
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
|
116
|
+
# ...
|
|
117
|
+
'drf_audit_logger.middleware.AuditLogMiddleware',
|
|
118
|
+
]
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
> ⚠️ **Important**: `AuditLogMiddleware` must be placed **after** `AuthenticationMiddleware`. If you have custom middleware that checks `request.user` (like a role-based middleware), place `AuditLogMiddleware` **before** it so it can authenticate the user with DRF's authenticators.
|
|
122
|
+
|
|
123
|
+
### 3. Add the URLs
|
|
124
|
+
|
|
125
|
+
In your project's main `urls.py`:
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
from django.urls import path, include
|
|
129
|
+
|
|
130
|
+
urlpatterns = [
|
|
131
|
+
# ...
|
|
132
|
+
path('auditlog/', include('drf_audit_logger.urls', namespace='drf_audit_logger')),
|
|
133
|
+
# ...
|
|
134
|
+
]
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### 4. Run migrations
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
python manage.py migrate drf_audit_logger
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### 5. Configure DRF authentication (if not already done)
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
REST_FRAMEWORK = {
|
|
147
|
+
'DEFAULT_AUTHENTICATION_CLASSES': [
|
|
148
|
+
'rest_framework.authentication.SessionAuthentication',
|
|
149
|
+
'rest_framework.authentication.TokenAuthentication',
|
|
150
|
+
# or JWT:
|
|
151
|
+
# 'rest_framework_simplejwt.authentication.JWTAuthentication',
|
|
152
|
+
],
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## Configuration (optional)
|
|
159
|
+
|
|
160
|
+
Add these to your `settings.py` to customize behavior:
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
# Models that should NOT be logged
|
|
164
|
+
AUDIT_LOG_EXCLUDE_MODEL_LOGGING = [
|
|
165
|
+
'admin.LogEntry',
|
|
166
|
+
'sessions.Session',
|
|
167
|
+
'contenttypes.ContentType',
|
|
168
|
+
'drf_audit_logger.AuditLog',
|
|
169
|
+
'authtoken.Token',
|
|
170
|
+
'token_blacklist.OutstandingToken',
|
|
171
|
+
'token_blacklist.BlacklistedToken',
|
|
172
|
+
]
|
|
173
|
+
|
|
174
|
+
# Fields that should be masked in `changes`
|
|
175
|
+
AUDIT_LOG_SENSITIVE_FIELDS = [
|
|
176
|
+
'password',
|
|
177
|
+
'password1',
|
|
178
|
+
'password2',
|
|
179
|
+
'token',
|
|
180
|
+
'access',
|
|
181
|
+
'refresh',
|
|
182
|
+
'secret',
|
|
183
|
+
'api_key',
|
|
184
|
+
'authorization',
|
|
185
|
+
]
|
|
186
|
+
|
|
187
|
+
# Enable / disable event types
|
|
188
|
+
AUDIT_LOG_LOG_AUTH_EVENTS = True
|
|
189
|
+
AUDIT_LOG_LOG_MODEL_EVENTS = True
|
|
190
|
+
|
|
191
|
+
# Max length of field values in `changes`
|
|
192
|
+
AUDIT_LOG_MAX_FIELD_LENGTH = 100
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## 🌍 Multilingual Support
|
|
198
|
+
|
|
199
|
+
DRF Audit Logger uses Django's `gettext` framework. Messages are rendered at **display time**, so they always reflect the currently active language.
|
|
200
|
+
|
|
201
|
+
### Supported languages out of the box
|
|
202
|
+
|
|
203
|
+
- 🇬🇧 English (`en`)
|
|
204
|
+
- 🇮🇷 Persian / Farsi (`fa`)
|
|
205
|
+
|
|
206
|
+
### How to enable multilingual support
|
|
207
|
+
|
|
208
|
+
#### Step 1: Configure your `settings.py`
|
|
209
|
+
|
|
210
|
+
```python
|
|
211
|
+
from pathlib import Path
|
|
212
|
+
|
|
213
|
+
BASE_DIR = Path(__file__).resolve().parent.parent
|
|
214
|
+
|
|
215
|
+
LANGUAGE_CODE = 'fa' # or 'en'
|
|
216
|
+
USE_I18N = True
|
|
217
|
+
USE_TZ = True
|
|
218
|
+
|
|
219
|
+
LANGUAGES = [
|
|
220
|
+
('fa', 'فارسی'),
|
|
221
|
+
('en', 'English'),
|
|
222
|
+
]
|
|
223
|
+
|
|
224
|
+
LOCALE_PATHS = [
|
|
225
|
+
BASE_DIR / 'locale', # 👈 your project's locale folder
|
|
226
|
+
]
|
|
227
|
+
|
|
228
|
+
MIDDLEWARE = [
|
|
229
|
+
'django.middleware.security.SecurityMiddleware',
|
|
230
|
+
'django.contrib.sessions.middleware.SessionMiddleware',
|
|
231
|
+
'django.middleware.locale.LocaleMiddleware', # 👈 required
|
|
232
|
+
'django.middleware.common.CommonMiddleware',
|
|
233
|
+
# ...
|
|
234
|
+
]
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
#### Step 2: Create the `locale/` folder in your project
|
|
238
|
+
|
|
239
|
+
Create this structure in your project's root (next to `manage.py`):
|
|
240
|
+
|
|
241
|
+
```
|
|
242
|
+
your_project/
|
|
243
|
+
├── manage.py
|
|
244
|
+
├── locale/
|
|
245
|
+
│ ├── fa/
|
|
246
|
+
│ │ └── LC_MESSAGES/
|
|
247
|
+
│ │ └── django.po
|
|
248
|
+
│ └── en/
|
|
249
|
+
│ └── LC_MESSAGES/
|
|
250
|
+
│ └── django.po
|
|
251
|
+
└── ...
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
#### Step 3: Translate your project's models and fields
|
|
255
|
+
|
|
256
|
+
In `locale/fa/LC_MESSAGES/django.po`:
|
|
257
|
+
|
|
258
|
+
```po
|
|
259
|
+
msgid ""
|
|
260
|
+
msgstr ""
|
|
261
|
+
"Language: fa\n"
|
|
262
|
+
"MIME-Version: 1.0\n"
|
|
263
|
+
"Content-Type: text/plain; charset=UTF-8\n"
|
|
264
|
+
"Content-Transfer-Encoding: 8bit\n"
|
|
265
|
+
|
|
266
|
+
# ---------- Model names ----------
|
|
267
|
+
msgid "Company"
|
|
268
|
+
msgstr "شرکت"
|
|
269
|
+
|
|
270
|
+
msgid "Companies"
|
|
271
|
+
msgstr "شرکتها"
|
|
272
|
+
|
|
273
|
+
msgid "Product"
|
|
274
|
+
msgstr "محصول"
|
|
275
|
+
|
|
276
|
+
msgid "Order"
|
|
277
|
+
msgstr "سفارش"
|
|
278
|
+
|
|
279
|
+
# ---------- Field names ----------
|
|
280
|
+
msgid "Title"
|
|
281
|
+
msgstr "عنوان"
|
|
282
|
+
|
|
283
|
+
msgid "Name"
|
|
284
|
+
msgstr "نام"
|
|
285
|
+
|
|
286
|
+
msgid "Price"
|
|
287
|
+
msgstr "قیمت"
|
|
288
|
+
|
|
289
|
+
msgid "Description"
|
|
290
|
+
msgstr "توضیحات"
|
|
291
|
+
|
|
292
|
+
# ---------- (Optional) Override package messages ----------
|
|
293
|
+
msgid "User %(user)s logged in"
|
|
294
|
+
msgstr "کاربر %(user)s وارد سیستم شد"
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
In `locale/en/LC_MESSAGES/django.po`:
|
|
298
|
+
|
|
299
|
+
```po
|
|
300
|
+
msgid ""
|
|
301
|
+
msgstr ""
|
|
302
|
+
"Language: en\n"
|
|
303
|
+
"MIME-Version: 1.0\n"
|
|
304
|
+
"Content-Type: text/plain; charset=UTF-8\n"
|
|
305
|
+
"Content-Transfer-Encoding: 8bit\n"
|
|
306
|
+
|
|
307
|
+
msgid "Company"
|
|
308
|
+
msgstr "Company"
|
|
309
|
+
|
|
310
|
+
msgid "Product"
|
|
311
|
+
msgstr "Product"
|
|
312
|
+
|
|
313
|
+
msgid "Title"
|
|
314
|
+
msgstr "Title"
|
|
315
|
+
|
|
316
|
+
msgid "Price"
|
|
317
|
+
msgstr "Price"
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
> ⚠️ **Important**: For translations to work, your models must use `gettext_lazy` (`_()`) for `verbose_name` and field names:
|
|
321
|
+
>
|
|
322
|
+
> ```python
|
|
323
|
+
> from django.utils.translation import gettext_lazy as _
|
|
324
|
+
>
|
|
325
|
+
> class Company(models.Model):
|
|
326
|
+
> title = models.CharField(_('Title'), max_length=255)
|
|
327
|
+
>
|
|
328
|
+
> class Meta:
|
|
329
|
+
> verbose_name = _('Company')
|
|
330
|
+
> verbose_name_plural = _('Companies')
|
|
331
|
+
> ```
|
|
332
|
+
|
|
333
|
+
#### Step 4: Compile translations
|
|
334
|
+
|
|
335
|
+
**Option A: If you have GNU gettext installed**
|
|
336
|
+
|
|
337
|
+
```bash
|
|
338
|
+
cd your_project
|
|
339
|
+
django-admin compilemessages
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
**Option B: If gettext is not installed (Windows users)**
|
|
343
|
+
|
|
344
|
+
Install GNU gettext for Windows from:
|
|
345
|
+
https://mlocati.github.io/articles/gettext-iconv-windows.html
|
|
346
|
+
|
|
347
|
+
Or compile manually with `msgfmt`:
|
|
348
|
+
|
|
349
|
+
```powershell
|
|
350
|
+
msgfmt locale\fa\LC_MESSAGES\django.po -o locale\fa\LC_MESSAGES\django.mo
|
|
351
|
+
msgfmt locale\en\LC_MESSAGES\django.po -o locale\en\LC_MESSAGES\django.mo
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
#### Step 5: Add a new language
|
|
355
|
+
|
|
356
|
+
Just create a new folder `locale/<lang_code>/LC_MESSAGES/`, add a `django.po` file with translations, and compile it. **No code changes required.**
|
|
357
|
+
|
|
358
|
+
---
|
|
359
|
+
|
|
360
|
+
### How to change the active language
|
|
361
|
+
|
|
362
|
+
You can change the active language in three ways:
|
|
363
|
+
|
|
364
|
+
#### 1. Globally in `settings.py`
|
|
365
|
+
|
|
366
|
+
```python
|
|
367
|
+
LANGUAGE_CODE = 'fa' # or 'en'
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
#### 2. Per-request via `Accept-Language` header
|
|
371
|
+
|
|
372
|
+
```bash
|
|
373
|
+
curl -H "Accept-Language: fa" \
|
|
374
|
+
-H "Authorization: Bearer TOKEN" \
|
|
375
|
+
http://localhost:8000/auditlog/api/logs/
|
|
376
|
+
|
|
377
|
+
curl -H "Accept-Language: en" \
|
|
378
|
+
-H "Authorization: Bearer TOKEN" \
|
|
379
|
+
http://localhost:8000/auditlog/api/logs/
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
#### 3. Programmatically
|
|
383
|
+
|
|
384
|
+
```python
|
|
385
|
+
from django.utils import translation
|
|
386
|
+
|
|
387
|
+
with translation.override('en'):
|
|
388
|
+
print(log.message) # in English
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
You can also fetch a log in a specific language:
|
|
392
|
+
|
|
393
|
+
```python
|
|
394
|
+
log.get_message_in_language('fa') # Persian
|
|
395
|
+
log.get_message_in_language('en') # English
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
---
|
|
399
|
+
|
|
400
|
+
## API Endpoints
|
|
401
|
+
|
|
402
|
+
All endpoints require **superuser authentication**.
|
|
403
|
+
|
|
404
|
+
| Method | Endpoint | Description |
|
|
405
|
+
| :--- | :--- | :--- |
|
|
406
|
+
| `GET` | `/auditlog/api/logs/` | List all logs with filtering |
|
|
407
|
+
| `GET` | `/auditlog/api/logs/<pk>/` | Retrieve a single log |
|
|
408
|
+
| `GET` | `/auditlog/api/logs/today/` | Today's logs |
|
|
409
|
+
| `GET` | `/auditlog/api/logs/me/` | Current user's logs |
|
|
410
|
+
| `GET` | `/auditlog/api/logs/user/<user_id>/` | Logs of a specific user |
|
|
411
|
+
| `GET` | `/auditlog/api/logs/model/<model_name>/` | Logs of a specific model |
|
|
412
|
+
| `GET` | `/auditlog/api/logs/object/<model_name>/<object_id>/` | Logs of a specific object |
|
|
413
|
+
| `GET` | `/auditlog/api/recent/` | Most recent activity |
|
|
414
|
+
| `GET` | `/auditlog/api/stats/` | Statistics |
|
|
415
|
+
| `GET` | `/auditlog/api/actions/` | Available action choices |
|
|
416
|
+
|
|
417
|
+
### Query Parameters
|
|
418
|
+
|
|
419
|
+
The list endpoint (`/auditlog/api/logs/`) supports:
|
|
420
|
+
|
|
421
|
+
| Parameter | Description | Example |
|
|
422
|
+
| :--- | :--- | :--- |
|
|
423
|
+
| `action` | Filter by action type | `?action=login` |
|
|
424
|
+
| `model` | Filter by model name | `?model=Product` |
|
|
425
|
+
| `user` | Filter by user ID | `?user=1` |
|
|
426
|
+
| `from` | Filter from date (YYYY-MM-DD) | `?from=2025-01-01` |
|
|
427
|
+
| `to` | Filter to date (YYYY-MM-DD) | `?to=2025-01-31` |
|
|
428
|
+
| `search` | Search in object, model, username | `?search=laptop` |
|
|
429
|
+
| `page` | Page number (pagination) | `?page=2` |
|
|
430
|
+
|
|
431
|
+
---
|
|
432
|
+
|
|
433
|
+
## Examples
|
|
434
|
+
|
|
435
|
+
### Login event
|
|
436
|
+
|
|
437
|
+
```json
|
|
438
|
+
{
|
|
439
|
+
"action": "login",
|
|
440
|
+
"action_display": "ورود",
|
|
441
|
+
"user_display": "علی رضایی",
|
|
442
|
+
"ip_address": "192.168.1.1",
|
|
443
|
+
"message": "کاربر علی رضایی وارد شد"
|
|
444
|
+
}
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
### Create event
|
|
448
|
+
|
|
449
|
+
```json
|
|
450
|
+
{
|
|
451
|
+
"action": "create",
|
|
452
|
+
"action_display": "ایجاد",
|
|
453
|
+
"user_display": "علی رضایی",
|
|
454
|
+
"model_name": "Product",
|
|
455
|
+
"object_repr": "لپتاپ ایسوس",
|
|
456
|
+
"changes": {
|
|
457
|
+
"name": "لپتاپ ایسوس",
|
|
458
|
+
"price": 1000,
|
|
459
|
+
"stock": 50
|
|
460
|
+
},
|
|
461
|
+
"message": "کاربر علی رضایی محصول «لپتاپ ایسوس» را ایجاد کرد"
|
|
462
|
+
}
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
### Update event (single field)
|
|
466
|
+
|
|
467
|
+
```json
|
|
468
|
+
{
|
|
469
|
+
"action": "update",
|
|
470
|
+
"action_display": "ویرایش",
|
|
471
|
+
"user_display": "علی رضایی",
|
|
472
|
+
"model_name": "Product",
|
|
473
|
+
"object_repr": "لپتاپ ایسوس",
|
|
474
|
+
"changes": {
|
|
475
|
+
"price": {
|
|
476
|
+
"old": 1000,
|
|
477
|
+
"new": 1500
|
|
478
|
+
}
|
|
479
|
+
},
|
|
480
|
+
"changes_list": [
|
|
481
|
+
{
|
|
482
|
+
"field": "price",
|
|
483
|
+
"field_verbose": "قیمت",
|
|
484
|
+
"old": 1000,
|
|
485
|
+
"new": 1500
|
|
486
|
+
}
|
|
487
|
+
],
|
|
488
|
+
"message": "کاربر علی رضایی «قیمت» محصول «لپتاپ ایسوس» را از «1000» به «1500» تغییر داد"
|
|
489
|
+
}
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
### Delete event
|
|
493
|
+
|
|
494
|
+
```json
|
|
495
|
+
{
|
|
496
|
+
"action": "delete",
|
|
497
|
+
"user_display": "علی رضایی",
|
|
498
|
+
"model_name": "Product",
|
|
499
|
+
"object_repr": "لپتاپ ایسوس",
|
|
500
|
+
"message": "کاربر علی رضایی محصول «لپتاپ ایسوس» را حذف کرد"
|
|
501
|
+
}
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
### Failed login
|
|
505
|
+
|
|
506
|
+
```json
|
|
507
|
+
{
|
|
508
|
+
"action": "login_failed",
|
|
509
|
+
"ip_address": "192.168.1.1",
|
|
510
|
+
"message": "تلاش ناموفق برای ورود از IP 192.168.1.1"
|
|
511
|
+
}
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
---
|
|
515
|
+
|
|
516
|
+
## Manual Logging
|
|
517
|
+
|
|
518
|
+
Log custom events from anywhere in your code:
|
|
519
|
+
|
|
520
|
+
```python
|
|
521
|
+
from drf_audit_logger import log
|
|
522
|
+
|
|
523
|
+
log(
|
|
524
|
+
user=request.user,
|
|
525
|
+
action='custom',
|
|
526
|
+
message_id='User %(user)s downloaded the report',
|
|
527
|
+
params={'user': request.user.get_full_name()},
|
|
528
|
+
)
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
Or use the service directly:
|
|
532
|
+
|
|
533
|
+
```python
|
|
534
|
+
from drf_audit_logger.services import AuditLogService
|
|
535
|
+
|
|
536
|
+
# Login / logout
|
|
537
|
+
AuditLogService.log_login(user=user, ip_address='192.168.1.1')
|
|
538
|
+
AuditLogService.log_logout(user=user)
|
|
539
|
+
AuditLogService.log_login_failed(username='ali', ip_address='192.168.1.1')
|
|
540
|
+
|
|
541
|
+
# Custom event
|
|
542
|
+
AuditLogService.log_custom(
|
|
543
|
+
user=request.user,
|
|
544
|
+
message_id='Report downloaded by %(user)s',
|
|
545
|
+
params={'user': request.user.username},
|
|
546
|
+
)
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
---
|
|
550
|
+
|
|
551
|
+
## Django Admin
|
|
552
|
+
|
|
553
|
+
Navigate to `/admin/drf_audit_logger/auditlog/` to browse logs with:
|
|
554
|
+
|
|
555
|
+
- Color-coded action badges (green = login, red = delete, yellow = update)
|
|
556
|
+
- Filters by action, model, timestamp, user
|
|
557
|
+
- Search across model, object, username, IP
|
|
558
|
+
- Date hierarchy navigation
|
|
559
|
+
- Read-only enforcement (logs cannot be edited)
|
|
560
|
+
|
|
561
|
+
---
|
|
562
|
+
|
|
563
|
+
## How It Works
|
|
564
|
+
|
|
565
|
+
1. `AuditLogMiddleware` authenticates the request using **DRF's configured authenticators** (JWT, Token, Session, etc.).
|
|
566
|
+
2. It stores `request.user`, `IP`, and `user-agent` in thread-local storage.
|
|
567
|
+
3. Django's `pre_save`, `post_save`, and `post_delete` signals trigger the audit service.
|
|
568
|
+
4. The service detects changes, masks sensitive fields, and stores raw data.
|
|
569
|
+
5. Messages are rendered **dynamically at display time** using `gettext`.
|
|
570
|
+
|
|
571
|
+
---
|
|
572
|
+
|
|
573
|
+
## Security
|
|
574
|
+
|
|
575
|
+
- **Sensitive field masking**: passwords, tokens, and API keys are replaced with `***MASKED***` before storage.
|
|
576
|
+
- **Superuser-only API access**: all endpoints are protected by `IsSuperUser`.
|
|
577
|
+
- **Read-only logs**: audit entries cannot be modified via admin or API.
|
|
578
|
+
- **Session-based admin**: the Django admin uses session authentication as usual.
|
|
579
|
+
|
|
580
|
+
---
|
|
581
|
+
|
|
582
|
+
## Configuration Reference
|
|
583
|
+
|
|
584
|
+
| Setting | Default | Description |
|
|
585
|
+
| :--- | :--- | :--- |
|
|
586
|
+
| `AUDIT_LOG_EXCLUDE_MODEL_LOGGING` | See above | List of models to exclude from logging |
|
|
587
|
+
| `AUDIT_LOG_SENSITIVE_FIELDS` | See above | Fields to mask in `changes` |
|
|
588
|
+
| `AUDIT_LOG_LOG_AUTH_EVENTS` | `True` | Log login / logout / failed login |
|
|
589
|
+
| `AUDIT_LOG_LOG_MODEL_EVENTS` | `True` | Log create / update / delete |
|
|
590
|
+
| `AUDIT_LOG_MAX_FIELD_LENGTH` | `100` | Max length of field values in `changes` |
|
|
591
|
+
|
|
592
|
+
---
|
|
593
|
+
|
|
594
|
+
## Troubleshooting
|
|
595
|
+
|
|
596
|
+
### Messages are not translated
|
|
597
|
+
|
|
598
|
+
1. **Check that `LocaleMiddleware` is enabled** in `MIDDLEWARE`.
|
|
599
|
+
2. **Check that `LANGUAGES` is set** in `settings.py`.
|
|
600
|
+
3. **Compile the `.po` files** with `django-admin compilemessages` or `msgfmt`.
|
|
601
|
+
4. **Restart the Django server** (translations are loaded at startup).
|
|
602
|
+
5. **Verify `.mo` files exist** next to `.po` files.
|
|
603
|
+
|
|
604
|
+
### `AuditLogMiddleware` breaks my admin login
|
|
605
|
+
|
|
606
|
+
Make sure `AuditLogMiddleware` is placed **after** `AuthenticationMiddleware` and **after** any custom middleware that runs on the admin paths. If you have a middleware that checks `request.user` before DRF authenticates, place `AuditLogMiddleware` **before** it.
|
|
607
|
+
|
|
608
|
+
### Changes are not detected on update
|
|
609
|
+
|
|
610
|
+
The package uses `pre_save` signals to capture the old state. Make sure you're using `instance.save()` (not `bulk_update` or `QuerySet.update()`, which bypass signals).
|
|
611
|
+
|
|
612
|
+
### `django-admin compilemessages` fails with "Cannot find msgfmt"
|
|
613
|
+
|
|
614
|
+
Install GNU gettext for Windows:
|
|
615
|
+
https://mlocati.github.io/articles/gettext-iconv-windows.html
|
|
616
|
+
|
|
617
|
+
Or compile manually:
|
|
618
|
+
|
|
619
|
+
```powershell
|
|
620
|
+
msgfmt locale\fa\LC_MESSAGES\django.po -o locale\fa\LC_MESSAGES\django.mo
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
---
|
|
624
|
+
|
|
625
|
+
## Contributing
|
|
626
|
+
|
|
627
|
+
Contributions are welcome! Please:
|
|
628
|
+
|
|
629
|
+
1. Fork the repository
|
|
630
|
+
2. Create a feature branch (`git checkout -b feature/my-feature`)
|
|
631
|
+
3. Commit your changes (`git commit -m "Add my feature"`)
|
|
632
|
+
4. Push to the branch (`git push origin feature/my-feature`)
|
|
633
|
+
5. Open a Pull Request
|
|
634
|
+
|
|
635
|
+
---
|
|
636
|
+
|
|
637
|
+
## Reporting Issues
|
|
638
|
+
|
|
639
|
+
Found a bug? Please open an issue at:
|
|
640
|
+
https://github.com/tahazarei777/drf-audit-logger/issues
|
|
641
|
+
|
|
642
|
+
Include:
|
|
643
|
+
|
|
644
|
+
- Your Python / Django / DRF versions
|
|
645
|
+
- Minimal reproduction steps
|
|
646
|
+
- The expected vs actual behavior
|
|
647
|
+
- Any relevant logs or tracebacks
|
|
648
|
+
|
|
649
|
+
---
|
|
650
|
+
|
|
651
|
+
## License
|
|
652
|
+
|
|
653
|
+
Licensed under the **BSD 3-Clause License**. See [LICENSE.md](LICENSE.md) for details.
|
|
654
|
+
|
|
655
|
+
Copyright © 2025, Taha Zarei.
|