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.
Files changed (31) hide show
  1. drf_audit_logger-1.0.0/LICENSE.md +38 -0
  2. drf_audit_logger-1.0.0/PKG-INFO +655 -0
  3. drf_audit_logger-1.0.0/README.md +613 -0
  4. drf_audit_logger-1.0.0/drf_audit_logger/__init__.py +8 -0
  5. drf_audit_logger-1.0.0/drf_audit_logger/_local.py +49 -0
  6. drf_audit_logger-1.0.0/drf_audit_logger/admin.py +131 -0
  7. drf_audit_logger-1.0.0/drf_audit_logger/apps.py +11 -0
  8. drf_audit_logger-1.0.0/drf_audit_logger/conf.py +60 -0
  9. drf_audit_logger-1.0.0/drf_audit_logger/locale/en/LC_MESSAGES/django.mo +0 -0
  10. drf_audit_logger-1.0.0/drf_audit_logger/locale/en/LC_MESSAGES/django.po +128 -0
  11. drf_audit_logger-1.0.0/drf_audit_logger/locale/fa/LC_MESSAGES/django.mo +0 -0
  12. drf_audit_logger-1.0.0/drf_audit_logger/locale/fa/LC_MESSAGES/django.po +128 -0
  13. drf_audit_logger-1.0.0/drf_audit_logger/middleware.py +84 -0
  14. drf_audit_logger-1.0.0/drf_audit_logger/migrations/0001_initial.py +39 -0
  15. drf_audit_logger-1.0.0/drf_audit_logger/migrations/__init__.py +0 -0
  16. drf_audit_logger-1.0.0/drf_audit_logger/models.py +130 -0
  17. drf_audit_logger-1.0.0/drf_audit_logger/permissions.py +25 -0
  18. drf_audit_logger-1.0.0/drf_audit_logger/renderers.py +178 -0
  19. drf_audit_logger-1.0.0/drf_audit_logger/serializers.py +89 -0
  20. drf_audit_logger-1.0.0/drf_audit_logger/services.py +287 -0
  21. drf_audit_logger-1.0.0/drf_audit_logger/signals.py +171 -0
  22. drf_audit_logger-1.0.0/drf_audit_logger/tests.py +0 -0
  23. drf_audit_logger-1.0.0/drf_audit_logger/urls.py +30 -0
  24. drf_audit_logger-1.0.0/drf_audit_logger/views.py +142 -0
  25. drf_audit_logger-1.0.0/drf_audit_logger.egg-info/PKG-INFO +655 -0
  26. drf_audit_logger-1.0.0/drf_audit_logger.egg-info/SOURCES.txt +29 -0
  27. drf_audit_logger-1.0.0/drf_audit_logger.egg-info/dependency_links.txt +1 -0
  28. drf_audit_logger-1.0.0/drf_audit_logger.egg-info/requires.txt +8 -0
  29. drf_audit_logger-1.0.0/drf_audit_logger.egg-info/top_level.txt +1 -0
  30. drf_audit_logger-1.0.0/pyproject.toml +81 -0
  31. 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
+ [![PyPI version](https://badge.fury.io/py/drf-audit-logger.svg)](https://pypi.org/project/drf-audit-logger/)
49
+ [![Python](https://img.shields.io/badge/python-3.8%2B-blue)](https://www.python.org/)
50
+ [![Django](https://img.shields.io/badge/django-3.2%2B-green)](https://www.djangoproject.com/)
51
+ [![DRF](https://img.shields.io/badge/djangorestframework-3.12%2B-red)](https://www.django-rest-framework.org/)
52
+ [![License](https://img.shields.io/badge/license-BSD--3--Clause-blue)](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.