django-field-audit 1.3.0__tar.gz → 1.4.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.
Potentially problematic release.
This version of django-field-audit might be problematic. Click here for more details.
- {django_field_audit-1.3.0 → django_field_audit-1.4.0}/PKG-INFO +57 -39
- {django_field_audit-1.3.0 → django_field_audit-1.4.0}/README.md +47 -18
- django_field_audit-1.4.0/field_audit/__init__.py +4 -0
- {django_field_audit-1.3.0 → django_field_audit-1.4.0}/field_audit/field_audit.py +20 -14
- {django_field_audit-1.3.0 → django_field_audit-1.4.0}/field_audit/management/commands/bootstrap_field_audit_events.py +7 -4
- {django_field_audit-1.3.0 → django_field_audit-1.4.0}/field_audit/models.py +221 -179
- django_field_audit-1.4.0/field_audit/services.py +378 -0
- {django_field_audit-1.3.0 → django_field_audit-1.4.0}/field_audit/utils.py +5 -2
- django_field_audit-1.4.0/pyproject.toml +71 -0
- django_field_audit-1.3.0/django_field_audit.egg-info/PKG-INFO +0 -344
- django_field_audit-1.3.0/django_field_audit.egg-info/SOURCES.txt +0 -32
- django_field_audit-1.3.0/django_field_audit.egg-info/dependency_links.txt +0 -1
- django_field_audit-1.3.0/django_field_audit.egg-info/top_level.txt +0 -1
- django_field_audit-1.3.0/field_audit/__init__.py +0 -3
- django_field_audit-1.3.0/setup.cfg +0 -16
- django_field_audit-1.3.0/setup.py +0 -53
- django_field_audit-1.3.0/tests/test_apps.py +0 -18
- django_field_audit-1.3.0/tests/test_auditors.py +0 -238
- django_field_audit-1.3.0/tests/test_bootstrap_field_audit_events.py +0 -144
- django_field_audit-1.3.0/tests/test_django_compat.py +0 -148
- django_field_audit-1.3.0/tests/test_field_audit.py +0 -180
- django_field_audit-1.3.0/tests/test_m2m.py +0 -229
- django_field_audit-1.3.0/tests/test_middleware.py +0 -25
- django_field_audit-1.3.0/tests/test_models.py +0 -1451
- django_field_audit-1.3.0/tests/test_utils.py +0 -83
- {django_field_audit-1.3.0 → django_field_audit-1.4.0}/LICENSE +0 -0
- {django_field_audit-1.3.0 → django_field_audit-1.4.0}/field_audit/apps.py +0 -0
- {django_field_audit-1.3.0 → django_field_audit-1.4.0}/field_audit/auditors.py +0 -0
- {django_field_audit-1.3.0 → django_field_audit-1.4.0}/field_audit/const.py +0 -0
- {django_field_audit-1.3.0 → django_field_audit-1.4.0}/field_audit/management/__init__.py +0 -0
- {django_field_audit-1.3.0 → django_field_audit-1.4.0}/field_audit/management/commands/__init__.py +0 -0
- {django_field_audit-1.3.0 → django_field_audit-1.4.0}/field_audit/middleware.py +0 -0
- {django_field_audit-1.3.0 → django_field_audit-1.4.0}/field_audit/migrations/0001_initial.py +0 -0
- {django_field_audit-1.3.0 → django_field_audit-1.4.0}/field_audit/migrations/0002_add_is_bootstrap_column.py +0 -0
- {django_field_audit-1.3.0 → django_field_audit-1.4.0}/field_audit/migrations/0003_alter_auditevent_change_context_and_more.py +0 -0
- {django_field_audit-1.3.0 → django_field_audit-1.4.0}/field_audit/migrations/__init__.py +0 -0
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: django-field-audit
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.0
|
|
4
4
|
Summary: Audit Field Changes on Django Models
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
Classifier: Development Status :: 3 - Alpha
|
|
5
|
+
Author-email: Joel Miller <jmiller@dimagi.com>, Simon Kelly <simongdkelly@gmail.com>, Graham Herceg <gherceg@dimagi.com>, Chris Smit <chris.smit@dimagi.com>, Daniel Miller <millerdev@gmail.com>
|
|
6
|
+
Requires-Python: >=3.9
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
10
9
|
Classifier: Environment :: Web Environment
|
|
11
10
|
Classifier: Framework :: Django
|
|
12
11
|
Classifier: Intended Audience :: Developers
|
|
@@ -18,22 +17,11 @@ Classifier: Programming Language :: Python :: 3.9
|
|
|
18
17
|
Classifier: Programming Language :: Python :: 3.10
|
|
19
18
|
Classifier: Programming Language :: Python :: 3.11
|
|
20
19
|
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
-
Classifier: Framework :: Django
|
|
22
|
-
Classifier: Framework :: Django :: 3
|
|
23
|
-
Classifier: Framework :: Django :: 3.2
|
|
24
|
-
Classifier: Framework :: Django :: 4
|
|
25
|
-
Classifier: Framework :: Django :: 4.2
|
|
26
20
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
27
|
-
Description-Content-Type: text/markdown
|
|
28
21
|
License-File: LICENSE
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
Dynamic: home-page
|
|
33
|
-
Dynamic: license
|
|
34
|
-
Dynamic: maintainer
|
|
35
|
-
Dynamic: maintainer-email
|
|
36
|
-
Dynamic: summary
|
|
22
|
+
Requires-Dist: django>=3.2
|
|
23
|
+
Project-URL: Homepage, https://github.com/dimagi/django-field-audit
|
|
24
|
+
Project-URL: Repository, https://github.com/dimagi/django-field-audit
|
|
37
25
|
|
|
38
26
|
# Audit Field Changes on Django Models
|
|
39
27
|
|
|
@@ -102,6 +90,42 @@ FIELD_AUDIT_AUDITORS = []
|
|
|
102
90
|
|:----------------------------------|:---------------------------------------------------------------|:------------------------
|
|
103
91
|
| `FIELD_AUDIT_AUDITEVENT_MANAGER` | A custom manager to use for the `AuditEvent` Model. | `field_audit.models.DefaultAuditEventManager`
|
|
104
92
|
| `FIELD_AUDIT_AUDITORS` | A custom list of auditors for acquiring `change_context` info. | `["field_audit.auditors.RequestAuditor", "field_audit.auditors.SystemUserAuditor"]`
|
|
93
|
+
| `FIELD_AUDIT_SERVICE_CLASS` | A custom service class for audit logic implementation. | `field_audit.services.AuditService`
|
|
94
|
+
|
|
95
|
+
### Custom Audit Service
|
|
96
|
+
|
|
97
|
+
The audit logic has been extracted into a separate `AuditService` class to improve separation of concerns and enable easier customization of audit behavior. Users can provide custom audit implementations by subclassing `AuditService` and configuring the `FIELD_AUDIT_SERVICE_CLASS` setting.
|
|
98
|
+
|
|
99
|
+
#### Creating a Custom Audit Service
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
# myapp/audit.py
|
|
103
|
+
|
|
104
|
+
from field_audit import AuditService
|
|
105
|
+
|
|
106
|
+
class CustomAuditService(AuditService):
|
|
107
|
+
def get_field_value(self, instance, field_name, bootstrap=False):
|
|
108
|
+
# Custom logic for extracting field values
|
|
109
|
+
value = super().get_field_value(instance, field_name, bootstrap)
|
|
110
|
+
|
|
111
|
+
# Example: custom serialization or transformation
|
|
112
|
+
if field_name == 'sensitive_field':
|
|
113
|
+
value = '[REDACTED]'
|
|
114
|
+
|
|
115
|
+
return value
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Then configure it in your Django settings:
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
# settings.py
|
|
122
|
+
|
|
123
|
+
FIELD_AUDIT_SERVICE_CLASS = 'myapp.audit.CustomAuditService'
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
#### Backward Compatibility
|
|
127
|
+
|
|
128
|
+
The original `AuditEvent` class methods are maintained for backward compatibility but are now deprecated in favor of the service-based approach. These methods will issue deprecation warnings and delegate to the configured audit service.
|
|
105
129
|
|
|
106
130
|
### Model Auditing
|
|
107
131
|
|
|
@@ -280,12 +304,11 @@ All feature and bug contributions are expected to be covered by tests.
|
|
|
280
304
|
|
|
281
305
|
### Setup for developers
|
|
282
306
|
|
|
283
|
-
|
|
307
|
+
This project uses [uv](https://docs.astral.sh/uv/) for dependency management. Install uv and then install the project dependencies:
|
|
284
308
|
|
|
285
309
|
```shell
|
|
286
310
|
cd django-field-audit
|
|
287
|
-
|
|
288
|
-
pip install django pynose flake8 coverage
|
|
311
|
+
uv sync
|
|
289
312
|
```
|
|
290
313
|
|
|
291
314
|
### Running tests
|
|
@@ -296,18 +319,18 @@ your local Python's `sqlite3` library ships with the `JSON1` extension enabled
|
|
|
296
319
|
|
|
297
320
|
- Tests
|
|
298
321
|
```shell
|
|
299
|
-
|
|
322
|
+
uv run pytest
|
|
300
323
|
```
|
|
301
324
|
|
|
302
325
|
- Style check
|
|
303
326
|
```shell
|
|
304
|
-
|
|
327
|
+
ruff check
|
|
305
328
|
```
|
|
306
329
|
|
|
307
330
|
- Coverage
|
|
308
331
|
```shell
|
|
309
|
-
coverage run -m
|
|
310
|
-
coverage report -m
|
|
332
|
+
uv run coverage run -m pytest
|
|
333
|
+
uv run coverage report -m
|
|
311
334
|
```
|
|
312
335
|
|
|
313
336
|
### Adding migrations
|
|
@@ -315,30 +338,25 @@ your local Python's `sqlite3` library ships with the `JSON1` extension enabled
|
|
|
315
338
|
The example `manage.py` is available for making new migrations.
|
|
316
339
|
|
|
317
340
|
```shell
|
|
318
|
-
python example/manage.py makemigrations field_audit
|
|
341
|
+
uv run python example/manage.py makemigrations field_audit
|
|
319
342
|
```
|
|
320
343
|
|
|
321
|
-
###
|
|
344
|
+
### Publishing a new version to PyPI
|
|
322
345
|
|
|
323
|
-
|
|
324
|
-
|
|
346
|
+
Push a new tag to Github using the format vX.Y.Z where X.Y.Z matches the version
|
|
347
|
+
in [`__init__.py`](field_audit/__init__.py). Also ensure that the changelog is up to date.
|
|
325
348
|
|
|
326
|
-
|
|
327
|
-
pip install -r pkg-requires.txt
|
|
328
|
-
|
|
329
|
-
python setup.py sdist bdist_wheel
|
|
330
|
-
twine upload dist/*
|
|
331
|
-
```
|
|
349
|
+
Publishing is automated with [Github Actions](.github/workflows/pypi.yml).
|
|
332
350
|
|
|
333
351
|
## TODO
|
|
334
352
|
|
|
335
353
|
- Implement auditing for the remaining "special" QuerySet write operations:
|
|
336
354
|
- `bulk_update()`
|
|
337
355
|
- Write full library documentation using github.io.
|
|
338
|
-
- Switch to `pytest` to support Python 3.10.
|
|
339
356
|
|
|
340
357
|
### Backlog
|
|
341
358
|
|
|
342
359
|
- Add to optimization for `instance.save(save_fields=[...])` [maybe].
|
|
343
360
|
- Support adding new audit fields on the same model at different times (instead
|
|
344
361
|
of raising `AlreadyAudited`) [maybe].
|
|
362
|
+
|
|
@@ -65,6 +65,42 @@ FIELD_AUDIT_AUDITORS = []
|
|
|
65
65
|
|:----------------------------------|:---------------------------------------------------------------|:------------------------
|
|
66
66
|
| `FIELD_AUDIT_AUDITEVENT_MANAGER` | A custom manager to use for the `AuditEvent` Model. | `field_audit.models.DefaultAuditEventManager`
|
|
67
67
|
| `FIELD_AUDIT_AUDITORS` | A custom list of auditors for acquiring `change_context` info. | `["field_audit.auditors.RequestAuditor", "field_audit.auditors.SystemUserAuditor"]`
|
|
68
|
+
| `FIELD_AUDIT_SERVICE_CLASS` | A custom service class for audit logic implementation. | `field_audit.services.AuditService`
|
|
69
|
+
|
|
70
|
+
### Custom Audit Service
|
|
71
|
+
|
|
72
|
+
The audit logic has been extracted into a separate `AuditService` class to improve separation of concerns and enable easier customization of audit behavior. Users can provide custom audit implementations by subclassing `AuditService` and configuring the `FIELD_AUDIT_SERVICE_CLASS` setting.
|
|
73
|
+
|
|
74
|
+
#### Creating a Custom Audit Service
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
# myapp/audit.py
|
|
78
|
+
|
|
79
|
+
from field_audit import AuditService
|
|
80
|
+
|
|
81
|
+
class CustomAuditService(AuditService):
|
|
82
|
+
def get_field_value(self, instance, field_name, bootstrap=False):
|
|
83
|
+
# Custom logic for extracting field values
|
|
84
|
+
value = super().get_field_value(instance, field_name, bootstrap)
|
|
85
|
+
|
|
86
|
+
# Example: custom serialization or transformation
|
|
87
|
+
if field_name == 'sensitive_field':
|
|
88
|
+
value = '[REDACTED]'
|
|
89
|
+
|
|
90
|
+
return value
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Then configure it in your Django settings:
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
# settings.py
|
|
97
|
+
|
|
98
|
+
FIELD_AUDIT_SERVICE_CLASS = 'myapp.audit.CustomAuditService'
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
#### Backward Compatibility
|
|
102
|
+
|
|
103
|
+
The original `AuditEvent` class methods are maintained for backward compatibility but are now deprecated in favor of the service-based approach. These methods will issue deprecation warnings and delegate to the configured audit service.
|
|
68
104
|
|
|
69
105
|
### Model Auditing
|
|
70
106
|
|
|
@@ -243,12 +279,11 @@ All feature and bug contributions are expected to be covered by tests.
|
|
|
243
279
|
|
|
244
280
|
### Setup for developers
|
|
245
281
|
|
|
246
|
-
|
|
282
|
+
This project uses [uv](https://docs.astral.sh/uv/) for dependency management. Install uv and then install the project dependencies:
|
|
247
283
|
|
|
248
284
|
```shell
|
|
249
285
|
cd django-field-audit
|
|
250
|
-
|
|
251
|
-
pip install django pynose flake8 coverage
|
|
286
|
+
uv sync
|
|
252
287
|
```
|
|
253
288
|
|
|
254
289
|
### Running tests
|
|
@@ -259,18 +294,18 @@ your local Python's `sqlite3` library ships with the `JSON1` extension enabled
|
|
|
259
294
|
|
|
260
295
|
- Tests
|
|
261
296
|
```shell
|
|
262
|
-
|
|
297
|
+
uv run pytest
|
|
263
298
|
```
|
|
264
299
|
|
|
265
300
|
- Style check
|
|
266
301
|
```shell
|
|
267
|
-
|
|
302
|
+
ruff check
|
|
268
303
|
```
|
|
269
304
|
|
|
270
305
|
- Coverage
|
|
271
306
|
```shell
|
|
272
|
-
coverage run -m
|
|
273
|
-
coverage report -m
|
|
307
|
+
uv run coverage run -m pytest
|
|
308
|
+
uv run coverage report -m
|
|
274
309
|
```
|
|
275
310
|
|
|
276
311
|
### Adding migrations
|
|
@@ -278,27 +313,21 @@ your local Python's `sqlite3` library ships with the `JSON1` extension enabled
|
|
|
278
313
|
The example `manage.py` is available for making new migrations.
|
|
279
314
|
|
|
280
315
|
```shell
|
|
281
|
-
python example/manage.py makemigrations field_audit
|
|
316
|
+
uv run python example/manage.py makemigrations field_audit
|
|
282
317
|
```
|
|
283
318
|
|
|
284
|
-
###
|
|
285
|
-
|
|
286
|
-
First bump the package version in the `field_audit/__init__.py` file. Then create a changelog entry in the CHANGELOG.md
|
|
287
|
-
file. After these changes are merged, you should tag the main branch with the new version. Then, package and upload the generated files to PyPI.
|
|
319
|
+
### Publishing a new version to PyPI
|
|
288
320
|
|
|
289
|
-
|
|
290
|
-
|
|
321
|
+
Push a new tag to Github using the format vX.Y.Z where X.Y.Z matches the version
|
|
322
|
+
in [`__init__.py`](field_audit/__init__.py). Also ensure that the changelog is up to date.
|
|
291
323
|
|
|
292
|
-
|
|
293
|
-
twine upload dist/*
|
|
294
|
-
```
|
|
324
|
+
Publishing is automated with [Github Actions](.github/workflows/pypi.yml).
|
|
295
325
|
|
|
296
326
|
## TODO
|
|
297
327
|
|
|
298
328
|
- Implement auditing for the remaining "special" QuerySet write operations:
|
|
299
329
|
- `bulk_update()`
|
|
300
330
|
- Write full library documentation using github.io.
|
|
301
|
-
- Switch to `pytest` to support Python 3.10.
|
|
302
331
|
|
|
303
332
|
### Backlog
|
|
304
333
|
|
|
@@ -57,7 +57,7 @@ def audit_fields(*field_names, class_path=None, audit_special_queryset_writes=Fa
|
|
|
57
57
|
raise AlreadyAudited(cls)
|
|
58
58
|
if not issubclass(cls, models.Model):
|
|
59
59
|
raise ValueError(f"expected Model subclass, got: {cls}")
|
|
60
|
-
|
|
60
|
+
service.attach_field_names(cls, field_names)
|
|
61
61
|
if audit_special_queryset_writes:
|
|
62
62
|
_verify_auditing_manager(cls)
|
|
63
63
|
cls.__init__ = _decorate_init(cls.__init__)
|
|
@@ -70,7 +70,8 @@ def audit_fields(*field_names, class_path=None, audit_special_queryset_writes=Fa
|
|
|
70
70
|
return cls
|
|
71
71
|
if not field_names:
|
|
72
72
|
raise ValueError("at least one field name is required")
|
|
73
|
-
from .
|
|
73
|
+
from .services import get_audit_service
|
|
74
|
+
service = get_audit_service()
|
|
74
75
|
return wrapper
|
|
75
76
|
|
|
76
77
|
|
|
@@ -101,8 +102,9 @@ def _decorate_init(init):
|
|
|
101
102
|
@wraps(init)
|
|
102
103
|
def wrapper(self, *args, **kw):
|
|
103
104
|
init(self, *args, **kw)
|
|
104
|
-
|
|
105
|
-
from .
|
|
105
|
+
service.attach_initial_values(self)
|
|
106
|
+
from .services import get_audit_service
|
|
107
|
+
service = get_audit_service()
|
|
106
108
|
return wrapper
|
|
107
109
|
|
|
108
110
|
|
|
@@ -124,7 +126,7 @@ def _decorate_db_write(func):
|
|
|
124
126
|
db = router.db_for_write(type(self))
|
|
125
127
|
with transaction.atomic(using=db):
|
|
126
128
|
ret = func(self, *args, **kw)
|
|
127
|
-
|
|
129
|
+
service.audit_field_changes(
|
|
128
130
|
self,
|
|
129
131
|
is_create,
|
|
130
132
|
is_delete,
|
|
@@ -136,7 +138,8 @@ def _decorate_db_write(func):
|
|
|
136
138
|
is_delete = func.__name__ == "delete"
|
|
137
139
|
if not is_save and not is_delete:
|
|
138
140
|
raise ValueError(f"invalid function for decoration: {func}")
|
|
139
|
-
from .
|
|
141
|
+
from .services import get_audit_service
|
|
142
|
+
service = get_audit_service()
|
|
140
143
|
return wrapper
|
|
141
144
|
|
|
142
145
|
|
|
@@ -150,10 +153,11 @@ def _decorate_refresh_from_db(func):
|
|
|
150
153
|
@wraps(func)
|
|
151
154
|
def wrapper(self, using=None, fields=None, **kwargs):
|
|
152
155
|
if fields is not None:
|
|
153
|
-
fields = set(fields) | set(
|
|
156
|
+
fields = set(fields) | set(service.get_field_names(self))
|
|
154
157
|
func(self, using, fields, **kwargs)
|
|
155
158
|
|
|
156
|
-
from .
|
|
159
|
+
from .services import get_audit_service
|
|
160
|
+
service = get_audit_service()
|
|
157
161
|
return wrapper
|
|
158
162
|
|
|
159
163
|
|
|
@@ -185,7 +189,9 @@ def _m2m_changed_handler(sender, instance, action, pk_set, **kwargs):
|
|
|
185
189
|
:param action: A string indicating the type of update
|
|
186
190
|
:param pk_set: For add/remove actions, set of primary key values
|
|
187
191
|
"""
|
|
188
|
-
from .
|
|
192
|
+
from .services import get_audit_service
|
|
193
|
+
|
|
194
|
+
service = get_audit_service()
|
|
189
195
|
|
|
190
196
|
if action not in ('post_add', 'post_remove', 'post_clear', 'pre_clear'):
|
|
191
197
|
return
|
|
@@ -206,17 +212,17 @@ def _m2m_changed_handler(sender, instance, action, pk_set, **kwargs):
|
|
|
206
212
|
field_name = field.name
|
|
207
213
|
break
|
|
208
214
|
|
|
209
|
-
if not m2m_field or field_name not in
|
|
215
|
+
if not m2m_field or field_name not in service.get_field_names(instance):
|
|
210
216
|
return
|
|
211
217
|
|
|
212
218
|
if action == 'pre_clear':
|
|
213
219
|
# `pk_set` not supplied for clear actions. Determine initial values
|
|
214
220
|
# in the `pre_clear` event
|
|
215
|
-
|
|
221
|
+
service.attach_initial_m2m_values(instance, field_name)
|
|
216
222
|
return
|
|
217
223
|
|
|
218
224
|
if action == 'post_clear':
|
|
219
|
-
initial_values =
|
|
225
|
+
initial_values = service.get_initial_m2m_values(instance, field_name)
|
|
220
226
|
if not initial_values:
|
|
221
227
|
return
|
|
222
228
|
delta = {field_name: {'remove': initial_values}}
|
|
@@ -228,13 +234,13 @@ def _m2m_changed_handler(sender, instance, action, pk_set, **kwargs):
|
|
|
228
234
|
delta = {field_name: {delta_key: list(pk_set)}}
|
|
229
235
|
|
|
230
236
|
req = request.get()
|
|
231
|
-
event =
|
|
237
|
+
event = service.create_audit_event(
|
|
232
238
|
instance.pk, instance.__class__, delta, False, False, req
|
|
233
239
|
)
|
|
234
240
|
if event is not None:
|
|
235
241
|
event.save()
|
|
236
242
|
|
|
237
|
-
|
|
243
|
+
service.clear_initial_m2m_field_values(instance, field_name)
|
|
238
244
|
|
|
239
245
|
|
|
240
246
|
def get_audited_models():
|
|
@@ -4,7 +4,7 @@ from django.core.management.base import BaseCommand, CommandError
|
|
|
4
4
|
|
|
5
5
|
from field_audit.const import BOOTSTRAP_BATCH_SIZE
|
|
6
6
|
from field_audit.field_audit import get_audited_models
|
|
7
|
-
from field_audit.
|
|
7
|
+
from field_audit.services import get_audit_service
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
class Command(BaseCommand):
|
|
@@ -64,6 +64,9 @@ class Command(BaseCommand):
|
|
|
64
64
|
if batch_size == 0:
|
|
65
65
|
batch_size = None
|
|
66
66
|
self.batch_size = batch_size
|
|
67
|
+
|
|
68
|
+
self.service = get_audit_service()
|
|
69
|
+
|
|
67
70
|
for name in models:
|
|
68
71
|
model_class = self.models[name]
|
|
69
72
|
self.operations[operation](self, model_class)
|
|
@@ -74,7 +77,7 @@ class Command(BaseCommand):
|
|
|
74
77
|
with self.bootstrap_action_log(log_head) as stream:
|
|
75
78
|
count = self.do_bootstrap(
|
|
76
79
|
model_class,
|
|
77
|
-
|
|
80
|
+
self.service.bootstrap_existing_model_records,
|
|
78
81
|
iter_records=query.iterator,
|
|
79
82
|
)
|
|
80
83
|
stream.write(f"done ({count})")
|
|
@@ -82,11 +85,11 @@ class Command(BaseCommand):
|
|
|
82
85
|
def top_up_missing(self, model_class):
|
|
83
86
|
log_head = f"top-up: {model_class} ... "
|
|
84
87
|
with self.bootstrap_action_log(log_head) as stream:
|
|
85
|
-
count = self.do_bootstrap(model_class,
|
|
88
|
+
count = self.do_bootstrap(model_class, self.service.bootstrap_top_up)
|
|
86
89
|
stream.write(f"done ({count})")
|
|
87
90
|
|
|
88
91
|
def do_bootstrap(self, model_class, bootstrap_method, **bootstrap_kw):
|
|
89
|
-
field_names =
|
|
92
|
+
field_names = self.service.get_field_names(model_class)
|
|
90
93
|
if not field_names:
|
|
91
94
|
raise CommandError(
|
|
92
95
|
f"invalid fields ({field_names!r}) for model: {model_class}"
|