django-bulk-hooks 0.2.50__tar.gz → 0.2.51__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-bulk-hooks might be problematic. Click here for more details.
- {django_bulk_hooks-0.2.50 → django_bulk_hooks-0.2.51}/PKG-INFO +1 -1
- {django_bulk_hooks-0.2.50 → django_bulk_hooks-0.2.51}/django_bulk_hooks/operations/bulk_executor.py +4 -2
- django_bulk_hooks-0.2.51/django_bulk_hooks/operations/field_utils.py +94 -0
- {django_bulk_hooks-0.2.50 → django_bulk_hooks-0.2.51}/django_bulk_hooks/operations/mti_handler.py +9 -18
- {django_bulk_hooks-0.2.50 → django_bulk_hooks-0.2.51}/django_bulk_hooks/operations/record_classifier.py +9 -2
- {django_bulk_hooks-0.2.50 → django_bulk_hooks-0.2.51}/pyproject.toml +1 -1
- {django_bulk_hooks-0.2.50 → django_bulk_hooks-0.2.51}/LICENSE +0 -0
- {django_bulk_hooks-0.2.50 → django_bulk_hooks-0.2.51}/README.md +0 -0
- {django_bulk_hooks-0.2.50 → django_bulk_hooks-0.2.51}/django_bulk_hooks/__init__.py +0 -0
- {django_bulk_hooks-0.2.50 → django_bulk_hooks-0.2.51}/django_bulk_hooks/changeset.py +0 -0
- {django_bulk_hooks-0.2.50 → django_bulk_hooks-0.2.51}/django_bulk_hooks/conditions.py +0 -0
- {django_bulk_hooks-0.2.50 → django_bulk_hooks-0.2.51}/django_bulk_hooks/constants.py +0 -0
- {django_bulk_hooks-0.2.50 → django_bulk_hooks-0.2.51}/django_bulk_hooks/context.py +0 -0
- {django_bulk_hooks-0.2.50 → django_bulk_hooks-0.2.51}/django_bulk_hooks/decorators.py +0 -0
- {django_bulk_hooks-0.2.50 → django_bulk_hooks-0.2.51}/django_bulk_hooks/dispatcher.py +0 -0
- {django_bulk_hooks-0.2.50 → django_bulk_hooks-0.2.51}/django_bulk_hooks/enums.py +0 -0
- {django_bulk_hooks-0.2.50 → django_bulk_hooks-0.2.51}/django_bulk_hooks/factory.py +0 -0
- {django_bulk_hooks-0.2.50 → django_bulk_hooks-0.2.51}/django_bulk_hooks/handler.py +0 -0
- {django_bulk_hooks-0.2.50 → django_bulk_hooks-0.2.51}/django_bulk_hooks/helpers.py +0 -0
- {django_bulk_hooks-0.2.50 → django_bulk_hooks-0.2.51}/django_bulk_hooks/manager.py +0 -0
- {django_bulk_hooks-0.2.50 → django_bulk_hooks-0.2.51}/django_bulk_hooks/models.py +0 -0
- {django_bulk_hooks-0.2.50 → django_bulk_hooks-0.2.51}/django_bulk_hooks/operations/__init__.py +0 -0
- {django_bulk_hooks-0.2.50 → django_bulk_hooks-0.2.51}/django_bulk_hooks/operations/analyzer.py +0 -0
- {django_bulk_hooks-0.2.50 → django_bulk_hooks-0.2.51}/django_bulk_hooks/operations/coordinator.py +0 -0
- {django_bulk_hooks-0.2.50 → django_bulk_hooks-0.2.51}/django_bulk_hooks/operations/mti_plans.py +0 -0
- {django_bulk_hooks-0.2.50 → django_bulk_hooks-0.2.51}/django_bulk_hooks/queryset.py +0 -0
- {django_bulk_hooks-0.2.50 → django_bulk_hooks-0.2.51}/django_bulk_hooks/registry.py +0 -0
{django_bulk_hooks-0.2.50 → django_bulk_hooks-0.2.51}/django_bulk_hooks/operations/bulk_executor.py
RENAMED
|
@@ -10,6 +10,8 @@ from django.db import transaction
|
|
|
10
10
|
from django.db.models import AutoField, ForeignKey, Case, When, Value
|
|
11
11
|
from django.db.models.functions import Cast
|
|
12
12
|
|
|
13
|
+
from django_bulk_hooks.operations.field_utils import get_field_value_for_db
|
|
14
|
+
|
|
13
15
|
logger = logging.getLogger(__name__)
|
|
14
16
|
|
|
15
17
|
|
|
@@ -475,8 +477,8 @@ class BulkExecutor:
|
|
|
475
477
|
if obj_pk is None:
|
|
476
478
|
continue
|
|
477
479
|
|
|
478
|
-
# Get the field value
|
|
479
|
-
value =
|
|
480
|
+
# Get the field value using centralized field extraction
|
|
481
|
+
value = get_field_value_for_db(obj, field_name, obj.__class__)
|
|
480
482
|
|
|
481
483
|
# Handle NULL values specially for ForeignKey fields
|
|
482
484
|
if is_fk and value is None:
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Utilities for field value extraction and normalization.
|
|
3
|
+
|
|
4
|
+
Single source of truth for converting model instance field values
|
|
5
|
+
to their database representation. This eliminates duplicate FK handling
|
|
6
|
+
logic scattered across multiple components.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
|
|
11
|
+
from django.db.models import ForeignKey
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def get_field_value_for_db(obj, field_name, model_cls=None):
|
|
17
|
+
"""
|
|
18
|
+
Get a field value from an object in its database-ready form.
|
|
19
|
+
|
|
20
|
+
For regular fields: returns the field value as-is
|
|
21
|
+
For FK fields: returns the PK (integer) instead of the related object
|
|
22
|
+
|
|
23
|
+
This ensures consistent handling across all operations (upsert classification,
|
|
24
|
+
bulk create, bulk update, etc.)
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
obj: Model instance
|
|
28
|
+
field_name: Name of the field to extract
|
|
29
|
+
model_cls: Model class (optional, will infer from obj if not provided)
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
Database-ready value (FK as integer, regular fields as-is)
|
|
33
|
+
"""
|
|
34
|
+
if model_cls is None:
|
|
35
|
+
model_cls = obj.__class__
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
field = model_cls._meta.get_field(field_name)
|
|
39
|
+
except Exception: # noqa: BLE001
|
|
40
|
+
# Field doesn't exist - just get attribute
|
|
41
|
+
return getattr(obj, field_name, None)
|
|
42
|
+
|
|
43
|
+
# Check if it's a ForeignKey
|
|
44
|
+
if isinstance(field, ForeignKey):
|
|
45
|
+
# For FK fields, always use attname (e.g., 'business_id' not 'business')
|
|
46
|
+
# This gets the raw FK ID value from the database field
|
|
47
|
+
return getattr(obj, field.attname, None)
|
|
48
|
+
|
|
49
|
+
# Regular field - get normally
|
|
50
|
+
return getattr(obj, field_name, None)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def get_field_values_for_db(obj, field_names, model_cls=None):
|
|
54
|
+
"""
|
|
55
|
+
Get multiple field values from an object in database-ready form.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
obj: Model instance
|
|
59
|
+
field_names: List of field names
|
|
60
|
+
model_cls: Model class (optional)
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
Dict of {field_name: db_value}
|
|
64
|
+
"""
|
|
65
|
+
if model_cls is None:
|
|
66
|
+
model_cls = obj.__class__
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
field_name: get_field_value_for_db(obj, field_name, model_cls)
|
|
70
|
+
for field_name in field_names
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def normalize_field_name_to_db(field_name, model_cls):
|
|
75
|
+
"""
|
|
76
|
+
Normalize a field name to its database column name.
|
|
77
|
+
|
|
78
|
+
For FK fields referenced by relationship name, returns the attname (e.g., 'business' -> 'business_id')
|
|
79
|
+
For regular fields, returns as-is.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
field_name: Field name (can be 'business' or 'business_id')
|
|
83
|
+
model_cls: Model class
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
Database column name
|
|
87
|
+
"""
|
|
88
|
+
try:
|
|
89
|
+
field = model_cls._meta.get_field(field_name)
|
|
90
|
+
if isinstance(field, ForeignKey):
|
|
91
|
+
return field.attname # Returns 'business_id' for 'business' field
|
|
92
|
+
return field_name
|
|
93
|
+
except Exception: # noqa: BLE001
|
|
94
|
+
return field_name
|
{django_bulk_hooks-0.2.50 → django_bulk_hooks-0.2.51}/django_bulk_hooks/operations/mti_handler.py
RENAMED
|
@@ -11,6 +11,8 @@ import logging
|
|
|
11
11
|
|
|
12
12
|
from django.db.models import AutoField
|
|
13
13
|
|
|
14
|
+
from django_bulk_hooks.operations.field_utils import get_field_value_for_db
|
|
15
|
+
|
|
14
16
|
logger = logging.getLogger(__name__)
|
|
15
17
|
|
|
16
18
|
|
|
@@ -354,19 +356,13 @@ class MTIHandler:
|
|
|
354
356
|
"""
|
|
355
357
|
parent_obj = parent_model()
|
|
356
358
|
|
|
357
|
-
# Copy field values from source
|
|
359
|
+
# Copy field values from source using centralized field extraction
|
|
358
360
|
for field in parent_model._meta.local_fields:
|
|
359
361
|
if hasattr(source_obj, field.name):
|
|
360
|
-
|
|
362
|
+
# Use centralized field value extraction for consistent FK handling
|
|
363
|
+
value = get_field_value_for_db(source_obj, field.name, source_obj.__class__)
|
|
361
364
|
if value is not None:
|
|
362
|
-
|
|
363
|
-
# Handle FK fields
|
|
364
|
-
if hasattr(value, "pk") and value.pk is not None:
|
|
365
|
-
setattr(parent_obj, field.attname, value.pk)
|
|
366
|
-
else:
|
|
367
|
-
setattr(parent_obj, field.attname, value)
|
|
368
|
-
else:
|
|
369
|
-
setattr(parent_obj, field.name, value)
|
|
365
|
+
setattr(parent_obj, field.attname, value)
|
|
370
366
|
|
|
371
367
|
# Link to parent if exists
|
|
372
368
|
if current_parent is not None:
|
|
@@ -419,15 +415,10 @@ class MTIHandler:
|
|
|
419
415
|
continue
|
|
420
416
|
|
|
421
417
|
if hasattr(source_obj, field.name):
|
|
422
|
-
|
|
418
|
+
# Use centralized field value extraction for consistent FK handling
|
|
419
|
+
value = get_field_value_for_db(source_obj, field.name, source_obj.__class__)
|
|
423
420
|
if value is not None:
|
|
424
|
-
|
|
425
|
-
if hasattr(value, "pk") and value.pk is not None:
|
|
426
|
-
setattr(child_obj, field.attname, value.pk)
|
|
427
|
-
else:
|
|
428
|
-
setattr(child_obj, field.attname, value)
|
|
429
|
-
else:
|
|
430
|
-
setattr(child_obj, field.name, value)
|
|
421
|
+
setattr(child_obj, field.attname, value)
|
|
431
422
|
|
|
432
423
|
# Copy object state
|
|
433
424
|
if hasattr(source_obj, "_state") and hasattr(child_obj, "_state"):
|
|
@@ -11,6 +11,8 @@ import logging
|
|
|
11
11
|
|
|
12
12
|
from django.db.models import Q
|
|
13
13
|
|
|
14
|
+
from django_bulk_hooks.operations.field_utils import get_field_value_for_db
|
|
15
|
+
|
|
14
16
|
logger = logging.getLogger(__name__)
|
|
15
17
|
|
|
16
18
|
|
|
@@ -61,17 +63,22 @@ class RecordClassifier:
|
|
|
61
63
|
for obj in objs:
|
|
62
64
|
# Build lookup dict for this object's unique fields
|
|
63
65
|
lookup = {}
|
|
66
|
+
normalized_values = []
|
|
67
|
+
|
|
64
68
|
for field_name in unique_fields:
|
|
65
|
-
value
|
|
69
|
+
# Use centralized field value extraction for consistent FK handling
|
|
70
|
+
value = get_field_value_for_db(obj, field_name, query_model)
|
|
66
71
|
if value is None:
|
|
67
72
|
# Can't match on None values
|
|
68
73
|
break
|
|
69
74
|
lookup[field_name] = value
|
|
75
|
+
normalized_values.append(value)
|
|
70
76
|
else:
|
|
71
77
|
# All unique fields have values, add to query
|
|
72
78
|
if lookup:
|
|
73
79
|
queries.append(Q(**lookup))
|
|
74
|
-
|
|
80
|
+
# Store normalized values for comparison with database results
|
|
81
|
+
obj_to_unique_values[id(obj)] = tuple(normalized_values)
|
|
75
82
|
|
|
76
83
|
if not queries:
|
|
77
84
|
return set(), {}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_bulk_hooks-0.2.50 → django_bulk_hooks-0.2.51}/django_bulk_hooks/operations/__init__.py
RENAMED
|
File without changes
|
{django_bulk_hooks-0.2.50 → django_bulk_hooks-0.2.51}/django_bulk_hooks/operations/analyzer.py
RENAMED
|
File without changes
|
{django_bulk_hooks-0.2.50 → django_bulk_hooks-0.2.51}/django_bulk_hooks/operations/coordinator.py
RENAMED
|
File without changes
|
{django_bulk_hooks-0.2.50 → django_bulk_hooks-0.2.51}/django_bulk_hooks/operations/mti_plans.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|