django-activity-audit 1.3.0.dev6__tar.gz → 1.3.0.dev8__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.
- {django_activity_audit-1.3.0.dev6 → django_activity_audit-1.3.0.dev8}/PKG-INFO +9 -4
- {django_activity_audit-1.3.0.dev6 → django_activity_audit-1.3.0.dev8}/README.md +8 -3
- {django_activity_audit-1.3.0.dev6 → django_activity_audit-1.3.0.dev8}/README.rst +7 -3
- {django_activity_audit-1.3.0.dev6 → django_activity_audit-1.3.0.dev8}/activity_audit/middleware.py +2 -2
- {django_activity_audit-1.3.0.dev6 → django_activity_audit-1.3.0.dev8}/activity_audit/protocols.py +7 -7
- {django_activity_audit-1.3.0.dev6 → django_activity_audit-1.3.0.dev8}/activity_audit/settings.py +6 -3
- {django_activity_audit-1.3.0.dev6 → django_activity_audit-1.3.0.dev8}/activity_audit/signals.py +17 -32
- {django_activity_audit-1.3.0.dev6 → django_activity_audit-1.3.0.dev8}/pyproject.toml +1 -1
- {django_activity_audit-1.3.0.dev6 → django_activity_audit-1.3.0.dev8}/.gitignore +0 -0
- {django_activity_audit-1.3.0.dev6 → django_activity_audit-1.3.0.dev8}/.pre-commit-config.yaml +0 -0
- {django_activity_audit-1.3.0.dev6 → django_activity_audit-1.3.0.dev8}/LICENSE +0 -0
- {django_activity_audit-1.3.0.dev6 → django_activity_audit-1.3.0.dev8}/MANIFEST.in +0 -0
- {django_activity_audit-1.3.0.dev6 → django_activity_audit-1.3.0.dev8}/activity_audit/__init__.py +0 -0
- {django_activity_audit-1.3.0.dev6 → django_activity_audit-1.3.0.dev8}/activity_audit/apps.py +0 -0
- {django_activity_audit-1.3.0.dev6 → django_activity_audit-1.3.0.dev8}/activity_audit/constants.py +0 -0
- {django_activity_audit-1.3.0.dev6 → django_activity_audit-1.3.0.dev8}/activity_audit/formatters.py +0 -0
- {django_activity_audit-1.3.0.dev6 → django_activity_audit-1.3.0.dev8}/activity_audit/handlers.py +0 -0
- {django_activity_audit-1.3.0.dev6 → django_activity_audit-1.3.0.dev8}/activity_audit/logger_levels.py +0 -0
- {django_activity_audit-1.3.0.dev6 → django_activity_audit-1.3.0.dev8}/activity_audit/utils.py +0 -0
- {django_activity_audit-1.3.0.dev6 → django_activity_audit-1.3.0.dev8}/hatch +0 -0
- {django_activity_audit-1.3.0.dev6 → django_activity_audit-1.3.0.dev8}/pytest.ini +0 -0
- {django_activity_audit-1.3.0.dev6 → django_activity_audit-1.3.0.dev8}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: django-activity-audit
|
|
3
|
-
Version: 1.3.0.
|
|
3
|
+
Version: 1.3.0.dev8
|
|
4
4
|
Summary: A Django package for easy CRUD operation logging and container logs
|
|
5
5
|
Project-URL: Homepage, https://github.com/shree256/django-activity-audit
|
|
6
6
|
Project-URL: Repository, https://github.com/shree256/django-activity-audit
|
|
@@ -117,7 +117,12 @@ LOGGING = {
|
|
|
117
117
|
}
|
|
118
118
|
```
|
|
119
119
|
|
|
120
|
-
5.
|
|
120
|
+
5. Configure the service name in `settings.py` (optional, defaults to `"default"`):
|
|
121
|
+
```python
|
|
122
|
+
AUDIT_SERVICE_NAME = "my_service"
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
6. For external services logging, extend ```HTTPClient or SFTPClient```
|
|
121
126
|
```python
|
|
122
127
|
class ExternalService(HTTPClient):
|
|
123
128
|
def __init__(self):
|
|
@@ -128,7 +133,7 @@ class ExternalService(HTTPClient):
|
|
|
128
133
|
response = self.get(url) # sample log structure below
|
|
129
134
|
```
|
|
130
135
|
|
|
131
|
-
|
|
136
|
+
8. Create ```audit_logs``` folder in project directory
|
|
132
137
|
|
|
133
138
|
## Log Types
|
|
134
139
|
|
|
@@ -192,7 +197,7 @@ INFO 2025-04-30 08:51:10,403 /app/patients/api/utils.py utils create_patient_wit
|
|
|
192
197
|
"level": "API",
|
|
193
198
|
"name": "audit.request",
|
|
194
199
|
"message": "Audit Internal Request",
|
|
195
|
-
"service_name": "
|
|
200
|
+
"service_name": "my_service",
|
|
196
201
|
"request_type": "internal",
|
|
197
202
|
"protocol": "http",
|
|
198
203
|
"user_id": "14ab1197-ebdd-4300-a618-5910e0219936",
|
|
@@ -77,7 +77,12 @@ LOGGING = {
|
|
|
77
77
|
}
|
|
78
78
|
```
|
|
79
79
|
|
|
80
|
-
5.
|
|
80
|
+
5. Configure the service name in `settings.py` (optional, defaults to `"default"`):
|
|
81
|
+
```python
|
|
82
|
+
AUDIT_SERVICE_NAME = "my_service"
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
6. For external services logging, extend ```HTTPClient or SFTPClient```
|
|
81
86
|
```python
|
|
82
87
|
class ExternalService(HTTPClient):
|
|
83
88
|
def __init__(self):
|
|
@@ -88,7 +93,7 @@ class ExternalService(HTTPClient):
|
|
|
88
93
|
response = self.get(url) # sample log structure below
|
|
89
94
|
```
|
|
90
95
|
|
|
91
|
-
|
|
96
|
+
8. Create ```audit_logs``` folder in project directory
|
|
92
97
|
|
|
93
98
|
## Log Types
|
|
94
99
|
|
|
@@ -152,7 +157,7 @@ INFO 2025-04-30 08:51:10,403 /app/patients/api/utils.py utils create_patient_wit
|
|
|
152
157
|
"level": "API",
|
|
153
158
|
"name": "audit.request",
|
|
154
159
|
"message": "Audit Internal Request",
|
|
155
|
-
"service_name": "
|
|
160
|
+
"service_name": "my_service",
|
|
156
161
|
"request_type": "internal",
|
|
157
162
|
"protocol": "http",
|
|
158
163
|
"user_id": "14ab1197-ebdd-4300-a618-5910e0219936",
|
|
@@ -76,7 +76,11 @@ Installation
|
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
5.
|
|
79
|
+
5. Configure the service name in ``settings.py`` (optional, defaults to ``"default"``)::
|
|
80
|
+
|
|
81
|
+
AUDIT_SERVICE_NAME = "my_service"
|
|
82
|
+
|
|
83
|
+
6. For external services logging, extend ``HTTPClient`` or ``SFTPClient``::
|
|
80
84
|
|
|
81
85
|
class ExternalService(HTTPClient):
|
|
82
86
|
def __init__(self):
|
|
@@ -86,7 +90,7 @@ Installation
|
|
|
86
90
|
url = "https://www.sample.com"
|
|
87
91
|
response = self.get(url) # sample log structure below
|
|
88
92
|
|
|
89
|
-
|
|
93
|
+
7. Create ``audit_logs`` folder in project directory
|
|
90
94
|
|
|
91
95
|
Log Types
|
|
92
96
|
---------
|
|
@@ -158,7 +162,7 @@ Incoming Log Format::
|
|
|
158
162
|
"level": "API",
|
|
159
163
|
"name": "audit.request",
|
|
160
164
|
"message": "Audit Internal Request",
|
|
161
|
-
"service_name": "
|
|
165
|
+
"service_name": "my_service",
|
|
162
166
|
"request_type": "internal",
|
|
163
167
|
"protocol": "http",
|
|
164
168
|
"user_id": "14ab1197-ebdd-4300-a618-5910e0219936",
|
{django_activity_audit-1.3.0.dev6 → django_activity_audit-1.3.0.dev8}/activity_audit/middleware.py
RENAMED
|
@@ -10,7 +10,7 @@ from django.http import HttpResponse
|
|
|
10
10
|
from django.utils.deprecation import MiddlewareMixin
|
|
11
11
|
|
|
12
12
|
from .constants import REQUEST_TYPES
|
|
13
|
-
from .settings import REGISTERED_URLS, UNREGISTERED_URLS
|
|
13
|
+
from .settings import REGISTERED_URLS, SERVICE_NAME, UNREGISTERED_URLS
|
|
14
14
|
|
|
15
15
|
logger = logging.getLogger("audit.request")
|
|
16
16
|
|
|
@@ -116,7 +116,7 @@ class AuditLoggingMiddleware(MiddlewareMixin):
|
|
|
116
116
|
def __init__(self, get_response):
|
|
117
117
|
self.get_response = get_response
|
|
118
118
|
self.log_data = {
|
|
119
|
-
"service_name":
|
|
119
|
+
"service_name": SERVICE_NAME,
|
|
120
120
|
"request_type": REQUEST_TYPES[0],
|
|
121
121
|
"protocol": None,
|
|
122
122
|
"user_id": "",
|
{django_activity_audit-1.3.0.dev6 → django_activity_audit-1.3.0.dev8}/activity_audit/protocols.py
RENAMED
|
@@ -4,8 +4,8 @@ import time
|
|
|
4
4
|
from typing import Optional, Tuple
|
|
5
5
|
|
|
6
6
|
import paramiko
|
|
7
|
-
from django.conf import settings
|
|
8
7
|
|
|
8
|
+
from django.conf import settings
|
|
9
9
|
from requests.sessions import Session
|
|
10
10
|
|
|
11
11
|
from .constants import REQUEST_TYPES
|
|
@@ -218,12 +218,12 @@ class SFTPClient:
|
|
|
218
218
|
result = f"{filename} uploaded successfully to {path_to_folder}"
|
|
219
219
|
logger.info(f"{result}")
|
|
220
220
|
except Exception as e:
|
|
221
|
-
self.log_payload[
|
|
222
|
-
|
|
223
|
-
)
|
|
224
|
-
self.log_payload[
|
|
225
|
-
|
|
226
|
-
)
|
|
221
|
+
self.log_payload[
|
|
222
|
+
"error_message"
|
|
223
|
+
] = f"File upload failed. Error: {str(e)}"
|
|
224
|
+
self.log_payload[
|
|
225
|
+
"error_message"
|
|
226
|
+
] = f"Path validation failed. Error: {str(error)}"
|
|
227
227
|
else:
|
|
228
228
|
self.log_payload["error_message"] = "Connection not established"
|
|
229
229
|
|
{django_activity_audit-1.3.0.dev6 → django_activity_audit-1.3.0.dev8}/activity_audit/settings.py
RENAMED
|
@@ -44,12 +44,12 @@ UNREGISTERED_CLASSES = [
|
|
|
44
44
|
SILK_INSTALLED = apps.is_installed("silk")
|
|
45
45
|
if SILK_INSTALLED:
|
|
46
46
|
from silk.models import (
|
|
47
|
+
BaseProfile,
|
|
48
|
+
Profile,
|
|
47
49
|
Request,
|
|
48
50
|
Response,
|
|
49
|
-
SQLQueryManager,
|
|
50
51
|
SQLQuery,
|
|
51
|
-
|
|
52
|
-
Profile,
|
|
52
|
+
SQLQueryManager,
|
|
53
53
|
)
|
|
54
54
|
|
|
55
55
|
UNREGISTERED_CLASSES.extend(
|
|
@@ -78,3 +78,6 @@ UNREGISTERED_URLS.extend(getattr(settings, "AUDIT_UNREGISTERED_URLS_EXTRA", []))
|
|
|
78
78
|
|
|
79
79
|
# URL patterns to include in logging (if empty, all URLs are logged)
|
|
80
80
|
REGISTERED_URLS = getattr(settings, "AUDIT_REGISTERED_URLS", [])
|
|
81
|
+
|
|
82
|
+
# Service name for audit logs
|
|
83
|
+
SERVICE_NAME = getattr(settings, "AUDIT_SERVICE_NAME", "default")
|
{django_activity_audit-1.3.0.dev6 → django_activity_audit-1.3.0.dev8}/activity_audit/signals.py
RENAMED
|
@@ -79,25 +79,7 @@ def get_calling_model() -> Optional[str]:
|
|
|
79
79
|
return None
|
|
80
80
|
|
|
81
81
|
|
|
82
|
-
|
|
83
|
-
"""Mixin to add signal handling capabilities to models."""
|
|
84
|
-
|
|
85
|
-
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
86
|
-
super().__init__(*args, **kwargs)
|
|
87
|
-
self._original_m2m = self._get_m2m_state()
|
|
88
|
-
|
|
89
|
-
def _get_m2m_state(self) -> dict:
|
|
90
|
-
try:
|
|
91
|
-
"""Get the current state of M2M fields."""
|
|
92
|
-
return {
|
|
93
|
-
field.name: set(
|
|
94
|
-
getattr(self, field.name).all().values_list("id", flat=True)
|
|
95
|
-
)
|
|
96
|
-
for field in self._meta.many_to_many
|
|
97
|
-
}
|
|
98
|
-
except Exception as e:
|
|
99
|
-
logger.error(f"Error getting M2M state: {e}")
|
|
100
|
-
return {}
|
|
82
|
+
_patched_models: set = set()
|
|
101
83
|
|
|
102
84
|
|
|
103
85
|
def push_log(
|
|
@@ -131,12 +113,15 @@ def push_log(
|
|
|
131
113
|
logger.error(f"Failed to prepare audit log: {e}")
|
|
132
114
|
|
|
133
115
|
|
|
116
|
+
def instance_to_dict(instance: models.Model) -> dict:
|
|
117
|
+
return model_to_dict(instance, fields=[f.name for f in instance._meta.fields])
|
|
118
|
+
|
|
119
|
+
|
|
134
120
|
def patch_model_event(model_class: type[models.Model]) -> None:
|
|
135
121
|
"""Monkey patch a model to add signal handling capabilities."""
|
|
136
122
|
|
|
137
|
-
if not
|
|
138
|
-
|
|
139
|
-
model_class.__bases__ = (ModelSignalMixin,) + model_class.__bases__
|
|
123
|
+
if model_class not in _patched_models:
|
|
124
|
+
_patched_models.add(model_class)
|
|
140
125
|
|
|
141
126
|
# Store the original methods
|
|
142
127
|
original_save = model_class.save
|
|
@@ -154,7 +139,7 @@ def patch_model_event(model_class: type[models.Model]) -> None:
|
|
|
154
139
|
# Log the event
|
|
155
140
|
event_type = EVENT_TYPES[0] if is_new else EVENT_TYPES[1]
|
|
156
141
|
|
|
157
|
-
instance_repr =
|
|
142
|
+
instance_repr = instance_to_dict(self)
|
|
158
143
|
|
|
159
144
|
push_log(
|
|
160
145
|
f"{event_type} event by {model_class.__name__} (id: {self.pk})",
|
|
@@ -179,7 +164,7 @@ def patch_model_event(model_class: type[models.Model]) -> None:
|
|
|
179
164
|
|
|
180
165
|
# For new instances, we might not have a pk yet, so use a placeholder
|
|
181
166
|
instance_id = str(instance.pk) if instance.pk else "pending"
|
|
182
|
-
instance_repr =
|
|
167
|
+
instance_repr = instance_to_dict(instance)
|
|
183
168
|
|
|
184
169
|
push_log(
|
|
185
170
|
f"{event_type} event by {model_class.__name__} (id: {instance_id})",
|
|
@@ -208,7 +193,7 @@ def patch_model_event(model_class: type[models.Model]) -> None:
|
|
|
208
193
|
# Log only if this is the calling model
|
|
209
194
|
if calling_model == model_class.__name__:
|
|
210
195
|
first_obj = created_objs[0]
|
|
211
|
-
instance_repr =
|
|
196
|
+
instance_repr = instance_to_dict(first_obj)
|
|
212
197
|
|
|
213
198
|
push_log(
|
|
214
199
|
f"{EVENT_TYPES[3]} event by {model_class.__name__} (id: {first_obj.pk})",
|
|
@@ -231,17 +216,17 @@ def patch_model_event(model_class: type[models.Model]) -> None:
|
|
|
231
216
|
return original_bulk_update(self, objs, fields, batch_size)
|
|
232
217
|
|
|
233
218
|
# Call the original bulk_update method
|
|
234
|
-
original_bulk_update(self, objs, fields, batch_size)
|
|
219
|
+
bulk_update = original_bulk_update(self, objs, fields, batch_size)
|
|
235
220
|
|
|
236
221
|
# Get the calling model
|
|
237
222
|
calling_model = get_calling_model()
|
|
238
223
|
if not calling_model:
|
|
239
|
-
return
|
|
224
|
+
return bulk_update
|
|
240
225
|
|
|
241
226
|
# Log only if this is the calling model
|
|
242
227
|
if calling_model == model_class.__name__:
|
|
243
228
|
first_obj = objs[0]
|
|
244
|
-
instance_repr =
|
|
229
|
+
instance_repr = instance_to_dict(first_obj)
|
|
245
230
|
|
|
246
231
|
push_log(
|
|
247
232
|
f"{EVENT_TYPES[4]} event by {model_class.__name__}",
|
|
@@ -268,7 +253,7 @@ def patch_model_event(model_class: type[models.Model]) -> None:
|
|
|
268
253
|
if not should_audit(instance):
|
|
269
254
|
return
|
|
270
255
|
|
|
271
|
-
instance_repr =
|
|
256
|
+
instance_repr = instance_to_dict(instance)
|
|
272
257
|
|
|
273
258
|
push_log(
|
|
274
259
|
f"{EVENT_TYPES[8]} event by {model_class.__name__} (id: {instance.pk})",
|
|
@@ -283,7 +268,7 @@ def patch_model_event(model_class: type[models.Model]) -> None:
|
|
|
283
268
|
def handle_delete(
|
|
284
269
|
sender: type[models.Model], instance: models.Model, **kwargs: Any
|
|
285
270
|
) -> None:
|
|
286
|
-
instance_repr =
|
|
271
|
+
instance_repr = instance_to_dict(instance)
|
|
287
272
|
|
|
288
273
|
push_log(
|
|
289
274
|
f"{EVENT_TYPES[2]} event by {model_class.__name__} (id: {instance.pk})",
|
|
@@ -308,7 +293,7 @@ def patch_model_event(model_class: type[models.Model]) -> None:
|
|
|
308
293
|
return
|
|
309
294
|
|
|
310
295
|
field_name = kwargs.get("model", sender).__name__.lower()
|
|
311
|
-
instance_repr =
|
|
296
|
+
instance_repr = instance_to_dict(instance)
|
|
312
297
|
|
|
313
298
|
push_log(
|
|
314
299
|
f"M2M {action} event by {model_class.__name__} (id: {instance.pk})",
|
|
@@ -330,7 +315,7 @@ def setup_model_signals() -> None:
|
|
|
330
315
|
if not should_audit(model):
|
|
331
316
|
continue
|
|
332
317
|
|
|
333
|
-
if not
|
|
318
|
+
if model not in _patched_models:
|
|
334
319
|
patch_model_event(model)
|
|
335
320
|
|
|
336
321
|
|
|
File without changes
|
{django_activity_audit-1.3.0.dev6 → django_activity_audit-1.3.0.dev8}/.pre-commit-config.yaml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_activity_audit-1.3.0.dev6 → django_activity_audit-1.3.0.dev8}/activity_audit/__init__.py
RENAMED
|
File without changes
|
{django_activity_audit-1.3.0.dev6 → django_activity_audit-1.3.0.dev8}/activity_audit/apps.py
RENAMED
|
File without changes
|
{django_activity_audit-1.3.0.dev6 → django_activity_audit-1.3.0.dev8}/activity_audit/constants.py
RENAMED
|
File without changes
|
{django_activity_audit-1.3.0.dev6 → django_activity_audit-1.3.0.dev8}/activity_audit/formatters.py
RENAMED
|
File without changes
|
{django_activity_audit-1.3.0.dev6 → django_activity_audit-1.3.0.dev8}/activity_audit/handlers.py
RENAMED
|
File without changes
|
|
File without changes
|
{django_activity_audit-1.3.0.dev6 → django_activity_audit-1.3.0.dev8}/activity_audit/utils.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|