django-treebeard 5.2.2__tar.gz → 5.3.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.
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/PKG-INFO +1 -1
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/django_treebeard.egg-info/PKG-INFO +1 -1
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/tests/migrations/0001_initial.py +29 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/tests/models.py +18 -6
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/tests/test_treebeard.py +72 -14
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/treebeard/__init__.py +1 -1
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/treebeard/al_tree.py +18 -1
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/treebeard/ltree/__init__.py +20 -3
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/treebeard/models.py +10 -5
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/treebeard/mp_tree.py +41 -17
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/treebeard/ns_tree.py +20 -3
- django_treebeard-5.3.0/treebeard/utils.py +38 -0
- django_treebeard-5.2.2/treebeard/utils.py +0 -46
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/AUTHORS +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/LICENSE +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/LICENSE-THIRD-PARTY +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/MANIFEST.in +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/README.md +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/django_treebeard.egg-info/SOURCES.txt +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/django_treebeard.egg-info/dependency_links.txt +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/django_treebeard.egg-info/requires.txt +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/django_treebeard.egg-info/top_level.txt +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/docs/Makefile +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/docs/README.md +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/docs/make.bat +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/docs/source/_ext/djangodocs.py +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/docs/source/_static/treebeard-admin-advanced.png +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/docs/source/_static/treebeard-admin-basic.png +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/docs/source/admin.rst +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/docs/source/al_tree.rst +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/docs/source/api.rst +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/docs/source/caveats.rst +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/docs/source/changes.rst +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/docs/source/choosing.rst +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/docs/source/conf.py +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/docs/source/exceptions.rst +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/docs/source/forms.rst +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/docs/source/index.rst +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/docs/source/install.rst +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/docs/source/ltree.rst +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/docs/source/mp_tree.rst +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/docs/source/ns_tree.rst +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/docs/source/tests.rst +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/docs/source/tutorial.rst +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/pyproject.toml +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/setup.cfg +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/tests/__init__.py +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/tests/admin.py +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/tests/conftest.py +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/tests/manage.py +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/tests/migrations/__init__.py +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/tests/settings.py +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/tests/test_benchmarks.py +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/tests/test_ltree_utils.py +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/tests/test_migrations.py +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/tests/test_numconv.py +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/tests/urls.py +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/treebeard/admin.py +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/treebeard/exceptions.py +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/treebeard/forms.py +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/treebeard/locale/de/LC_MESSAGES/django.mo +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/treebeard/locale/de/LC_MESSAGES/django.po +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/treebeard/locale/de/LC_MESSAGES/djangojs.mo +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/treebeard/locale/de/LC_MESSAGES/djangojs.po +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/treebeard/locale/es/LC_MESSAGES/django.mo +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/treebeard/locale/es/LC_MESSAGES/django.po +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/treebeard/locale/es/LC_MESSAGES/djangojs.mo +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/treebeard/locale/es/LC_MESSAGES/djangojs.po +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/treebeard/locale/fr/LC_MESSAGES/django.mo +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/treebeard/locale/fr/LC_MESSAGES/django.po +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/treebeard/locale/fr/LC_MESSAGES/djangojs.mo +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/treebeard/locale/fr/LC_MESSAGES/djangojs.po +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/treebeard/locale/hu/LC_MESSAGES/django.mo +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/treebeard/locale/hu/LC_MESSAGES/django.po +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/treebeard/locale/hu/LC_MESSAGES/djangojs.mo +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/treebeard/locale/hu/LC_MESSAGES/djangojs.po +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/treebeard/locale/nl/LC_MESSAGES/django.mo +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/treebeard/locale/nl/LC_MESSAGES/django.po +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/treebeard/locale/nl/LC_MESSAGES/djangojs.mo +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/treebeard/locale/nl/LC_MESSAGES/djangojs.po +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/treebeard/locale/pl/LC_MESSAGES/django.mo +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/treebeard/locale/pl/LC_MESSAGES/django.po +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/treebeard/locale/ru/LC_MESSAGES/django.mo +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/treebeard/locale/ru/LC_MESSAGES/django.po +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/treebeard/locale/ru/LC_MESSAGES/djangojs.mo +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/treebeard/locale/ru/LC_MESSAGES/djangojs.po +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/treebeard/ltree/fields.py +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/treebeard/numconv.py +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/treebeard/static/treebeard/expand-collapse.png +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/treebeard/static/treebeard/treebeard-admin.css +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/treebeard/static/treebeard/treebeard-admin.js +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/treebeard/templates/admin/tree_change_list.html +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/treebeard/templates/admin/tree_list.html +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/treebeard/templatetags/__init__.py +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/treebeard/templatetags/admin_tree.py +0 -0
- {django_treebeard-5.2.2 → django_treebeard-5.3.0}/treebeard/templatetags/admin_tree_list.py +0 -0
|
@@ -291,6 +291,7 @@ class Migration(migrations.Migration):
|
|
|
291
291
|
("depth", models.PositiveIntegerField(db_index=True)),
|
|
292
292
|
("desc", models.CharField(max_length=255)),
|
|
293
293
|
("related", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="tests.relatedmodel")),
|
|
294
|
+
("related_m2m", models.ManyToManyField(to="tests.relatedmodel", related_name="+")),
|
|
294
295
|
],
|
|
295
296
|
options={
|
|
296
297
|
"abstract": False,
|
|
@@ -312,6 +313,7 @@ class Migration(migrations.Migration):
|
|
|
312
313
|
("numchild", models.PositiveIntegerField(default=0)),
|
|
313
314
|
("desc", models.CharField(max_length=255)),
|
|
314
315
|
("related", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="tests.relatedmodel")),
|
|
316
|
+
("related_m2m", models.ManyToManyField(to="tests.relatedmodel", related_name="+")),
|
|
315
317
|
],
|
|
316
318
|
options={
|
|
317
319
|
"abstract": False,
|
|
@@ -361,6 +363,7 @@ class Migration(migrations.Migration):
|
|
|
361
363
|
),
|
|
362
364
|
),
|
|
363
365
|
("related", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="tests.relatedmodel")),
|
|
366
|
+
("related_m2m", models.ManyToManyField(to="tests.relatedmodel", related_name="+")),
|
|
364
367
|
],
|
|
365
368
|
options={
|
|
366
369
|
"abstract": False,
|
|
@@ -454,6 +457,32 @@ if os.environ.get("DATABASE_ENGINE") == "psql":
|
|
|
454
457
|
("node", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="tests.lt_testnode")),
|
|
455
458
|
],
|
|
456
459
|
),
|
|
460
|
+
migrations.CreateModel(
|
|
461
|
+
name="LT_TestNodeRelated",
|
|
462
|
+
fields=[
|
|
463
|
+
(
|
|
464
|
+
"id",
|
|
465
|
+
models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID"),
|
|
466
|
+
),
|
|
467
|
+
("path", treebeard.ltree.fields.PathField()),
|
|
468
|
+
("desc", models.CharField(max_length=255)),
|
|
469
|
+
(
|
|
470
|
+
"related",
|
|
471
|
+
models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="tests.relatedmodel"),
|
|
472
|
+
),
|
|
473
|
+
("related_m2m", models.ManyToManyField(to="tests.relatedmodel", related_name="+")),
|
|
474
|
+
],
|
|
475
|
+
options={
|
|
476
|
+
"abstract": False,
|
|
477
|
+
"constraints": [
|
|
478
|
+
models.UniqueConstraint(
|
|
479
|
+
deferrable=django.db.models.constraints.Deferrable["DEFERRED"],
|
|
480
|
+
fields=("path",),
|
|
481
|
+
name="tests_lt_testnoderelated_deferred_unique_path",
|
|
482
|
+
)
|
|
483
|
+
],
|
|
484
|
+
},
|
|
485
|
+
),
|
|
457
486
|
migrations.CreateModel(
|
|
458
487
|
name="LT_TestNode_Proxy",
|
|
459
488
|
fields=[],
|
|
@@ -26,6 +26,18 @@ class DescMixin(models.Model):
|
|
|
26
26
|
class RelatedModel(DescMixin): ...
|
|
27
27
|
|
|
28
28
|
|
|
29
|
+
class RelatedNodeMixin(models.Model):
|
|
30
|
+
"""
|
|
31
|
+
Mixin for nodes with relationships (foreign key, m2m)
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
related = models.ForeignKey(RelatedModel, on_delete=models.CASCADE)
|
|
35
|
+
related_m2m = models.ManyToManyField(RelatedModel, related_name="+")
|
|
36
|
+
|
|
37
|
+
class Meta:
|
|
38
|
+
abstract = True
|
|
39
|
+
|
|
40
|
+
|
|
29
41
|
class MP_TestNode(MP_Node, DescMixin):
|
|
30
42
|
steplen = 3
|
|
31
43
|
|
|
@@ -34,9 +46,8 @@ class MP_TestNodeSomeDep(models.Model):
|
|
|
34
46
|
node = models.ForeignKey(MP_TestNode, on_delete=models.CASCADE)
|
|
35
47
|
|
|
36
48
|
|
|
37
|
-
class MP_TestNodeRelated(MP_Node, DescMixin):
|
|
49
|
+
class MP_TestNodeRelated(MP_Node, DescMixin, RelatedNodeMixin):
|
|
38
50
|
steplen = 3
|
|
39
|
-
related = models.ForeignKey(RelatedModel, on_delete=models.CASCADE)
|
|
40
51
|
|
|
41
52
|
|
|
42
53
|
class MP_TestNodeInherited(MP_TestNode):
|
|
@@ -55,8 +66,7 @@ class NS_TestNodeSomeDep(models.Model):
|
|
|
55
66
|
node = models.ForeignKey(NS_TestNode, on_delete=models.CASCADE)
|
|
56
67
|
|
|
57
68
|
|
|
58
|
-
class NS_TestNodeRelated(NS_Node, DescMixin):
|
|
59
|
-
related = models.ForeignKey(RelatedModel, on_delete=models.CASCADE)
|
|
69
|
+
class NS_TestNodeRelated(NS_Node, DescMixin, RelatedNodeMixin): ...
|
|
60
70
|
|
|
61
71
|
|
|
62
72
|
class NS_TestNodeInherited(NS_TestNode):
|
|
@@ -78,7 +88,7 @@ class AL_TestNodeSomeDep(models.Model):
|
|
|
78
88
|
node = models.ForeignKey(AL_TestNode, on_delete=models.CASCADE)
|
|
79
89
|
|
|
80
90
|
|
|
81
|
-
class AL_TestNodeRelated(AL_Node, DescMixin):
|
|
91
|
+
class AL_TestNodeRelated(AL_Node, DescMixin, RelatedNodeMixin):
|
|
82
92
|
parent = models.ForeignKey(
|
|
83
93
|
"self",
|
|
84
94
|
related_name="children_set",
|
|
@@ -87,7 +97,6 @@ class AL_TestNodeRelated(AL_Node, DescMixin):
|
|
|
87
97
|
on_delete=models.CASCADE,
|
|
88
98
|
)
|
|
89
99
|
sib_order = models.PositiveIntegerField()
|
|
90
|
-
related = models.ForeignKey(RelatedModel, on_delete=models.CASCADE)
|
|
91
100
|
|
|
92
101
|
|
|
93
102
|
class AL_TestNodeInherited(AL_TestNode):
|
|
@@ -232,6 +241,8 @@ if os.environ.get("DATABASE_ENGINE", "") == "psql":
|
|
|
232
241
|
class Meta:
|
|
233
242
|
constraints = [] # Override parent class constraints
|
|
234
243
|
|
|
244
|
+
class LT_TestNodeRelated(LT_Node, DescMixin, RelatedNodeMixin): ...
|
|
245
|
+
|
|
235
246
|
class LT_TestNodeSomeDep(models.Model):
|
|
236
247
|
node = models.ForeignKey(LT_TestNode, on_delete=models.CASCADE)
|
|
237
248
|
|
|
@@ -240,6 +251,7 @@ if os.environ.get("DATABASE_ENGINE", "") == "psql":
|
|
|
240
251
|
DEP_MODELS.append((LT_TestNode, LT_TestNodeSomeDep))
|
|
241
252
|
INHERITED_MODELS.append((LT_TestNode, LT_TestNodeInherited))
|
|
242
253
|
SORTED_MODELS.append(LT_TestNodeSorted)
|
|
254
|
+
RELATED_MODELS.append(LT_TestNodeRelated)
|
|
243
255
|
INHERITED_MODELS_WITH_SORT.append((LT_TestNodeSorted, LT_TestNodeInheritedSorted))
|
|
244
256
|
LT_BASE_MODELS.append(LT_TestNode)
|
|
245
257
|
BENCHMARK_MODELS.append(LT_TestNode)
|
|
@@ -7,12 +7,15 @@ from unittest import mock
|
|
|
7
7
|
from unittest.mock import patch
|
|
8
8
|
|
|
9
9
|
import pytest
|
|
10
|
+
from django.apps import apps
|
|
10
11
|
from django.contrib.admin.options import TO_FIELD_VAR
|
|
11
12
|
from django.contrib.admin.sites import AdminSite
|
|
12
13
|
from django.contrib.admin.views.main import ChangeList
|
|
13
14
|
from django.contrib.auth.models import AnonymousUser, User
|
|
14
15
|
from django.contrib.messages.storage.fallback import FallbackStorage
|
|
16
|
+
from django.core.checks.model_checks import check_all_models
|
|
15
17
|
from django.core.exceptions import PermissionDenied
|
|
18
|
+
from django.db.models import Manager
|
|
16
19
|
from django.db.models.signals import post_save
|
|
17
20
|
from django.dispatch import receiver
|
|
18
21
|
from django.forms import ValidationError
|
|
@@ -118,6 +121,11 @@ def mp_model(request):
|
|
|
118
121
|
return request.param
|
|
119
122
|
|
|
120
123
|
|
|
124
|
+
@pytest.fixture(scope="function", params=[models.MP_TestNodeRelated])
|
|
125
|
+
def mp_relatedmodel(request):
|
|
126
|
+
return request.param
|
|
127
|
+
|
|
128
|
+
|
|
121
129
|
@pytest.fixture(scope="function", params=models.MP_SHORTPATH_MODELS)
|
|
122
130
|
def mpshort_model(request):
|
|
123
131
|
return request.param
|
|
@@ -463,32 +471,30 @@ class TestClassMethods(TestNonEmptyTree):
|
|
|
463
471
|
got = [(o.desc, o.get_depth(), o.get_children_count()) for o in model.get_tree()]
|
|
464
472
|
assert got == UNCHANGED
|
|
465
473
|
|
|
466
|
-
def
|
|
467
|
-
|
|
468
|
-
related_model.objects.all().delete()
|
|
469
|
-
related, _ = models.RelatedModel.objects.get_or_create(desc=f"Test {related_model.__name__}")
|
|
474
|
+
def test_load_and_dump_bulk_with_related_models(self, related_model):
|
|
475
|
+
related = models.RelatedModel.objects.create(desc=f"Test {related_model.__name__}")
|
|
470
476
|
|
|
471
477
|
related_data = [
|
|
472
|
-
{"data": {"desc": "1", "related": related.pk}},
|
|
478
|
+
{"data": {"desc": "1", "related": related.pk, "related_m2m": [related.pk]}},
|
|
473
479
|
{
|
|
474
|
-
"data": {"desc": "2", "related": related.pk},
|
|
480
|
+
"data": {"desc": "2", "related": related.pk, "related_m2m": []},
|
|
475
481
|
"children": [
|
|
476
|
-
{"data": {"desc": "21", "related": related.pk}},
|
|
477
|
-
{"data": {"desc": "22", "related": related.pk}},
|
|
482
|
+
{"data": {"desc": "21", "related": related.pk, "related_m2m": [related.pk]}},
|
|
483
|
+
{"data": {"desc": "22", "related": related.pk, "related_m2m": [related.pk]}},
|
|
478
484
|
{
|
|
479
|
-
"data": {"desc": "23", "related": related.pk},
|
|
485
|
+
"data": {"desc": "23", "related": related.pk, "related_m2m": []},
|
|
480
486
|
"children": [
|
|
481
|
-
{"data": {"desc": "231", "related": related.pk}},
|
|
487
|
+
{"data": {"desc": "231", "related": related.pk, "related_m2m": [related.pk]}},
|
|
482
488
|
],
|
|
483
489
|
},
|
|
484
|
-
{"data": {"desc": "24", "related": related.pk}},
|
|
490
|
+
{"data": {"desc": "24", "related": related.pk, "related_m2m": []}},
|
|
485
491
|
],
|
|
486
492
|
},
|
|
487
|
-
{"data": {"desc": "3", "related": related.pk}},
|
|
493
|
+
{"data": {"desc": "3", "related": related.pk, "related_m2m": []}},
|
|
488
494
|
{
|
|
489
|
-
"data": {"desc": "4", "related": related.pk},
|
|
495
|
+
"data": {"desc": "4", "related": related.pk, "related_m2m": []},
|
|
490
496
|
"children": [
|
|
491
|
-
{"data": {"desc": "41", "related": related.pk}},
|
|
497
|
+
{"data": {"desc": "41", "related": related.pk, "related_m2m": []}},
|
|
492
498
|
],
|
|
493
499
|
},
|
|
494
500
|
]
|
|
@@ -1752,6 +1758,15 @@ class TestDelete(TestTreeBase):
|
|
|
1752
1758
|
("nodes_deleted", delete_model, ["A", "B", "C", "D"], "default"),
|
|
1753
1759
|
]
|
|
1754
1760
|
|
|
1761
|
+
def test_delete_with_prefetch_related(self, related_model):
|
|
1762
|
+
# Regression test for https://github.com/django-treebeard/django-treebeard/issues/405
|
|
1763
|
+
# If `delete()` is run on a queryset with `prefetch_related()` set, then Treebeard's use
|
|
1764
|
+
# of `iterator()` will throw an exception unless the prefetch is cleared.
|
|
1765
|
+
related = models.RelatedModel.objects.create(desc=f"Test {related_model.__name__}")
|
|
1766
|
+
related_model.add_root(desc="A", related=related)
|
|
1767
|
+
num_deleted, _ = related_model.objects.prefetch_related("related_m2m").all().delete()
|
|
1768
|
+
assert num_deleted == 1
|
|
1769
|
+
|
|
1755
1770
|
|
|
1756
1771
|
@pytest.mark.django_db
|
|
1757
1772
|
class TestMoveErrors(TestNonEmptyTree):
|
|
@@ -3567,6 +3582,37 @@ class TestMP_TreeLoadBulk(TestTreeBase):
|
|
|
3567
3582
|
got = [(o.desc, o.get_depth(), o.get_children_count()) for o in mp_model.get_tree()]
|
|
3568
3583
|
assert got == UNCHANGED
|
|
3569
3584
|
|
|
3585
|
+
def test_load_bulk_keeping_ids_with_bulk_create_and_many_to_many(self, mp_relatedmodel):
|
|
3586
|
+
related = models.RelatedModel.objects.create(desc=f"Test {related_model.__name__}")
|
|
3587
|
+
|
|
3588
|
+
related_data = [
|
|
3589
|
+
{"data": {"desc": "1", "related": related.pk, "related_m2m": [related.pk]}},
|
|
3590
|
+
{
|
|
3591
|
+
"data": {"desc": "2", "related": related.pk, "related_m2m": []},
|
|
3592
|
+
"children": [
|
|
3593
|
+
{"data": {"desc": "21", "related": related.pk, "related_m2m": [related.pk]}},
|
|
3594
|
+
{"data": {"desc": "22", "related": related.pk, "related_m2m": [related.pk]}},
|
|
3595
|
+
{
|
|
3596
|
+
"data": {"desc": "23", "related": related.pk, "related_m2m": []},
|
|
3597
|
+
"children": [
|
|
3598
|
+
{"data": {"desc": "231", "related": related.pk, "related_m2m": []}},
|
|
3599
|
+
],
|
|
3600
|
+
},
|
|
3601
|
+
{"data": {"desc": "24", "related": related.pk, "related_m2m": []}},
|
|
3602
|
+
],
|
|
3603
|
+
},
|
|
3604
|
+
{"data": {"desc": "3", "related": related.pk, "related_m2m": []}},
|
|
3605
|
+
{
|
|
3606
|
+
"data": {"desc": "4", "related": related.pk, "related_m2m": []},
|
|
3607
|
+
"children": [
|
|
3608
|
+
{"data": {"desc": "41", "related": related.pk, "related_m2m": []}},
|
|
3609
|
+
],
|
|
3610
|
+
},
|
|
3611
|
+
]
|
|
3612
|
+
mp_relatedmodel.load_bulk(related_data, bulk_create=True)
|
|
3613
|
+
got = mp_relatedmodel.dump_bulk(keep_ids=False)
|
|
3614
|
+
assert got == related_data
|
|
3615
|
+
|
|
3570
3616
|
|
|
3571
3617
|
@pytest.mark.django_db
|
|
3572
3618
|
class TestMP_TreeSortedAutoNow(TestTreeBase):
|
|
@@ -4709,3 +4755,15 @@ class TestLT_Insertion(TestTreeBase):
|
|
|
4709
4755
|
assert signals == [
|
|
4710
4756
|
("subtree_moved_right", lt_model, "A.0", "default"),
|
|
4711
4757
|
]
|
|
4758
|
+
|
|
4759
|
+
|
|
4760
|
+
@pytest.mark.django_db
|
|
4761
|
+
class TestChecks:
|
|
4762
|
+
def test_checks_warning_if_model_manager_doesnt_subclass_treebeard_manager(self, model, monkeypatch):
|
|
4763
|
+
configs = [apps.get_app_config("tests")]
|
|
4764
|
+
assert not check_all_models(configs)
|
|
4765
|
+
# Monkey-patch default manager
|
|
4766
|
+
monkeypatch.setattr(model._meta, "default_manager", Manager())
|
|
4767
|
+
errors = check_all_models(configs)
|
|
4768
|
+
assert len(errors) == 1
|
|
4769
|
+
assert "does not subclass treebeard" in errors[0].msg
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Adjacency List"""
|
|
2
2
|
|
|
3
|
-
from django.core import serializers
|
|
3
|
+
from django.core import checks, serializers
|
|
4
4
|
from django.db import models, transaction
|
|
5
5
|
from django.db.models import Exists, Max, Min, OuterRef
|
|
6
6
|
from django.utils.translation import gettext_noop as _
|
|
@@ -353,6 +353,23 @@ class AL_Node(Node):
|
|
|
353
353
|
|
|
354
354
|
self.save()
|
|
355
355
|
|
|
356
|
+
@classmethod
|
|
357
|
+
def check(cls, **kwargs):
|
|
358
|
+
errors = super().check(**kwargs)
|
|
359
|
+
manager_cls = cls._default_manager.__class__
|
|
360
|
+
# Raise a warning if the default manager for the model doesn't subclass AL_NodeManager
|
|
361
|
+
# This will allow us to move class-level methods into the manager in future (see issue #44)
|
|
362
|
+
if not issubclass(manager_cls, AL_NodeManager):
|
|
363
|
+
errors.append(
|
|
364
|
+
checks.Warning(
|
|
365
|
+
f"{manager_cls.__module__}.{manager_cls.__name__} does not subclass "
|
|
366
|
+
"treebeard.al_tree.AL_NodeManager. This will cause an error in Treebeard 6.",
|
|
367
|
+
obj=manager_cls,
|
|
368
|
+
id="treebeard.E001",
|
|
369
|
+
)
|
|
370
|
+
)
|
|
371
|
+
return errors
|
|
372
|
+
|
|
356
373
|
class Meta:
|
|
357
374
|
"""Abstract model."""
|
|
358
375
|
|
|
@@ -7,7 +7,7 @@ import string
|
|
|
7
7
|
from collections.abc import Iterable
|
|
8
8
|
from typing import Any
|
|
9
9
|
|
|
10
|
-
from django.core import serializers
|
|
10
|
+
from django.core import checks, serializers
|
|
11
11
|
from django.db import models, transaction
|
|
12
12
|
from django.db.models import F, Func, OuterRef, Q, Subquery, Value
|
|
13
13
|
from django.db.models.functions import Concat
|
|
@@ -106,7 +106,7 @@ class LT_NodeQuerySet(models.query.QuerySet):
|
|
|
106
106
|
"""
|
|
107
107
|
# Construct the minimal list of nodes that need deleting along with their descendants
|
|
108
108
|
paths_to_remove = set()
|
|
109
|
-
for node in self.order_by("path").only("path").iterator():
|
|
109
|
+
for node in self.order_by("path").only("path").prefetch_related(None).iterator():
|
|
110
110
|
found = False
|
|
111
111
|
for depth in range(1, len(node.path)):
|
|
112
112
|
path = node.path[0:depth]
|
|
@@ -136,7 +136,7 @@ class LT_NodeManager(models.Manager):
|
|
|
136
136
|
|
|
137
137
|
def get_queryset(self):
|
|
138
138
|
"""Sets the custom queryset as the default."""
|
|
139
|
-
return LT_NodeQuerySet(self.model).order_by("path")
|
|
139
|
+
return LT_NodeQuerySet(self.model, using=self._db).order_by("path")
|
|
140
140
|
|
|
141
141
|
|
|
142
142
|
class LT_ComplexAddMoveHandler:
|
|
@@ -657,6 +657,23 @@ class LT_Node(Node):
|
|
|
657
657
|
"""
|
|
658
658
|
return LT_MoveHandler(self, target, pos).process()
|
|
659
659
|
|
|
660
|
+
@classmethod
|
|
661
|
+
def check(cls, **kwargs):
|
|
662
|
+
errors = super().check(**kwargs)
|
|
663
|
+
manager_cls = cls._default_manager.__class__
|
|
664
|
+
# Raise a warning if the default manager for the model doesn't subclass LT_NodeManager
|
|
665
|
+
# This will allow us to move class-level methods into the manager in future (see issue #44)
|
|
666
|
+
if not issubclass(manager_cls, LT_NodeManager):
|
|
667
|
+
errors.append(
|
|
668
|
+
checks.Warning(
|
|
669
|
+
f"{manager_cls.__module__}.{manager_cls.__name__} does not subclass "
|
|
670
|
+
"treebeard.ltree.LT_NodeManager. This will cause an error in Treebeard 6.",
|
|
671
|
+
obj=manager_cls,
|
|
672
|
+
id="treebeard.E001",
|
|
673
|
+
)
|
|
674
|
+
)
|
|
675
|
+
return errors
|
|
676
|
+
|
|
660
677
|
class Meta:
|
|
661
678
|
abstract = True
|
|
662
679
|
constraints = [
|
|
@@ -9,7 +9,7 @@ from django.db import models, transaction
|
|
|
9
9
|
from django.db.models import Q
|
|
10
10
|
|
|
11
11
|
from treebeard.exceptions import InvalidPosition, MissingNodeOrderBy
|
|
12
|
-
from treebeard.utils import prepare_dumpdata_for_loading
|
|
12
|
+
from treebeard.utils import prepare_dumpdata_for_loading, save_m2m
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
class Node(models.Model):
|
|
@@ -80,14 +80,19 @@ class Node(models.Model):
|
|
|
80
80
|
added = []
|
|
81
81
|
bulk_data = prepare_dumpdata_for_loading(cls, data=bulk_data, keep_ids=keep_ids)
|
|
82
82
|
# stack of nodes to analyze
|
|
83
|
-
stack = [(parent,
|
|
83
|
+
stack = [(parent, deserialized_obj) for deserialized_obj in bulk_data[::-1]]
|
|
84
84
|
|
|
85
85
|
while stack:
|
|
86
|
-
parent,
|
|
87
|
-
node_obj =
|
|
86
|
+
parent, deserialized_obj = stack.pop()
|
|
87
|
+
node_obj = deserialized_obj.object = (
|
|
88
|
+
parent.add_child(instance=deserialized_obj.object)
|
|
89
|
+
if parent
|
|
90
|
+
else cls.add_root(instance=deserialized_obj.object)
|
|
91
|
+
)
|
|
92
|
+
save_m2m(node_obj, deserialized_obj)
|
|
88
93
|
added.append(node_obj.pk)
|
|
89
94
|
# extending the stack with the current node as the parent of the new nodes
|
|
90
|
-
stack.extend([(node_obj,
|
|
95
|
+
stack.extend([(node_obj, child) for child in deserialized_obj.children[::-1]])
|
|
91
96
|
return added
|
|
92
97
|
|
|
93
98
|
@classmethod
|
|
@@ -4,7 +4,7 @@ import collections
|
|
|
4
4
|
from functools import cache
|
|
5
5
|
from typing import Any
|
|
6
6
|
|
|
7
|
-
from django.core import serializers
|
|
7
|
+
from django.core import checks, serializers
|
|
8
8
|
from django.db import connections, models, router, transaction
|
|
9
9
|
from django.db.models import F, Func, OuterRef, Q, Subquery, Value
|
|
10
10
|
from django.db.models.functions import Concat, Greatest, Length, Substr
|
|
@@ -14,7 +14,7 @@ from django.utils.translation import gettext_noop as _
|
|
|
14
14
|
from treebeard.exceptions import InvalidMoveToDescendant, NodeAlreadySaved, PathOverflow
|
|
15
15
|
from treebeard.models import Node
|
|
16
16
|
from treebeard.numconv import NumConv
|
|
17
|
-
from treebeard.utils import prepare_dumpdata_for_loading
|
|
17
|
+
from treebeard.utils import prepare_dumpdata_for_loading, save_m2m
|
|
18
18
|
|
|
19
19
|
path_updated = Signal()
|
|
20
20
|
nodes_deleted = Signal()
|
|
@@ -39,7 +39,7 @@ class MP_NodeQuerySet(models.query.QuerySet):
|
|
|
39
39
|
# to be deleted and remove nodes from the list if an ancestor is
|
|
40
40
|
# already getting removed, since that would be redundant
|
|
41
41
|
removed = {}
|
|
42
|
-
for node in self.order_by("depth", "path").only("path", "depth", "numchild").iterator():
|
|
42
|
+
for node in self.order_by("depth", "path").only("path", "depth", "numchild").prefetch_related(None).iterator():
|
|
43
43
|
found = False
|
|
44
44
|
for depth in range(1, int(len(node.path) / node.steplen)):
|
|
45
45
|
path = node._get_basepath(node.path, depth)
|
|
@@ -92,7 +92,7 @@ class MP_NodeManager(models.Manager):
|
|
|
92
92
|
|
|
93
93
|
def get_queryset(self):
|
|
94
94
|
"""Sets the custom queryset as the default."""
|
|
95
|
-
return MP_NodeQuerySet(self.model).order_by("path")
|
|
95
|
+
return MP_NodeQuerySet(self.model, using=self._db).order_by("path")
|
|
96
96
|
|
|
97
97
|
|
|
98
98
|
class MP_ComplexAddMoveHandler:
|
|
@@ -1103,31 +1103,55 @@ class MP_Node(Node):
|
|
|
1103
1103
|
child_depth = parent_node.depth + 1
|
|
1104
1104
|
|
|
1105
1105
|
for i, child in enumerate(children):
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
path=cls._get_path(parent_node.path, child_depth, i + 1),
|
|
1110
|
-
**child["data"],
|
|
1111
|
-
)
|
|
1106
|
+
child.object.depth = child_depth
|
|
1107
|
+
child.object.numchild = len(child.children)
|
|
1108
|
+
child.object.path = cls._get_path(parent_node.path, child_depth, i + 1)
|
|
1112
1109
|
|
|
1113
|
-
children_to_create.append(
|
|
1110
|
+
children_to_create.append(child)
|
|
1114
1111
|
|
|
1115
1112
|
# Recursively process grandchildren
|
|
1116
|
-
_build_children(
|
|
1113
|
+
_build_children(child.object, child.children)
|
|
1117
1114
|
|
|
1118
1115
|
# Create first level of the bulk data using standard operations, since there may be existing siblings
|
|
1119
|
-
for
|
|
1120
|
-
|
|
1121
|
-
node_obj =
|
|
1116
|
+
for deserialized_obj in bulk_data:
|
|
1117
|
+
deserialized_obj.object.numchild = len(deserialized_obj.children) # Set numchild manually
|
|
1118
|
+
node_obj = (
|
|
1119
|
+
parent.add_child(instance=deserialized_obj.object)
|
|
1120
|
+
if parent
|
|
1121
|
+
else cls.add_root(instance=deserialized_obj.object)
|
|
1122
|
+
)
|
|
1123
|
+
save_m2m(node_obj, deserialized_obj)
|
|
1122
1124
|
added.append(node_obj.pk)
|
|
1123
|
-
_build_children(node_obj,
|
|
1125
|
+
_build_children(node_obj, deserialized_obj.children)
|
|
1124
1126
|
|
|
1125
1127
|
# Bulk create descendants
|
|
1126
|
-
created = cls.objects.bulk_create(children_to_create, batch_size=batch_size)
|
|
1128
|
+
created = cls.objects.bulk_create([obj.object for obj in children_to_create], batch_size=batch_size)
|
|
1129
|
+
|
|
1130
|
+
# Save m2m relationships
|
|
1131
|
+
for obj, source in zip(created, children_to_create):
|
|
1132
|
+
save_m2m(obj, source)
|
|
1133
|
+
|
|
1127
1134
|
added.extend([obj.pk for obj in created])
|
|
1128
1135
|
|
|
1129
1136
|
return added
|
|
1130
1137
|
|
|
1138
|
+
@classmethod
|
|
1139
|
+
def check(cls, **kwargs):
|
|
1140
|
+
errors = super().check(**kwargs)
|
|
1141
|
+
manager_cls = cls._default_manager.__class__
|
|
1142
|
+
# Raise a warning if the default manager for the model doesn't subclass MP_NodeManager
|
|
1143
|
+
# This will allow us to move class-level methods into the manager in future (see issue #44)
|
|
1144
|
+
if not issubclass(manager_cls, MP_NodeManager):
|
|
1145
|
+
errors.append(
|
|
1146
|
+
checks.Warning(
|
|
1147
|
+
f"{manager_cls.__module__}.{manager_cls.__name__} does not subclass "
|
|
1148
|
+
"treebeard.mp_tree.MP_NodeManager. This will cause an error in Treebeard 6.",
|
|
1149
|
+
obj=manager_cls,
|
|
1150
|
+
id="treebeard.E001",
|
|
1151
|
+
)
|
|
1152
|
+
)
|
|
1153
|
+
return errors
|
|
1154
|
+
|
|
1131
1155
|
class Meta:
|
|
1132
1156
|
"""Abstract model."""
|
|
1133
1157
|
|
|
@@ -4,7 +4,7 @@ import operator
|
|
|
4
4
|
from functools import reduce
|
|
5
5
|
from itertools import groupby
|
|
6
6
|
|
|
7
|
-
from django.core import serializers
|
|
7
|
+
from django.core import checks, serializers
|
|
8
8
|
from django.db import models, transaction
|
|
9
9
|
from django.db.models import Case, F, Q, When
|
|
10
10
|
from django.dispatch import Signal
|
|
@@ -39,7 +39,7 @@ class NS_NodeQuerySet(models.query.QuerySet):
|
|
|
39
39
|
last_node = None
|
|
40
40
|
toremove = []
|
|
41
41
|
ranges = []
|
|
42
|
-
for node in self.order_by("tree_id", "lft").values("tree_id", "lft", "rgt").iterator():
|
|
42
|
+
for node in self.order_by("tree_id", "lft").values("tree_id", "lft", "rgt").prefetch_related(None).iterator():
|
|
43
43
|
if (
|
|
44
44
|
last_node
|
|
45
45
|
and last_node["tree_id"] == node["tree_id"]
|
|
@@ -80,7 +80,7 @@ class NS_NodeManager(models.Manager):
|
|
|
80
80
|
|
|
81
81
|
def get_queryset(self):
|
|
82
82
|
"""Sets the custom queryset as the default."""
|
|
83
|
-
return NS_NodeQuerySet(self.model).order_by("tree_id", "lft")
|
|
83
|
+
return NS_NodeQuerySet(self.model, using=self._db).order_by("tree_id", "lft")
|
|
84
84
|
|
|
85
85
|
|
|
86
86
|
class NS_Node(Node):
|
|
@@ -638,6 +638,23 @@ class NS_Node(Node):
|
|
|
638
638
|
|
|
639
639
|
return reversed_lft_rgt, overlapping_nodes, duplicate_tree_ids, wrong_depth
|
|
640
640
|
|
|
641
|
+
@classmethod
|
|
642
|
+
def check(cls, **kwargs):
|
|
643
|
+
errors = super().check(**kwargs)
|
|
644
|
+
manager_cls = cls._default_manager.__class__
|
|
645
|
+
# Raise a warning if the default manager for the model doesn't subclass NS_NodeManager
|
|
646
|
+
# This will allow us to move class-level methods into the manager in future (see issue #44)
|
|
647
|
+
if not issubclass(manager_cls, NS_NodeManager):
|
|
648
|
+
errors.append(
|
|
649
|
+
checks.Warning(
|
|
650
|
+
f"{manager_cls.__module__}.{manager_cls.__name__} does not subclass "
|
|
651
|
+
"treebeard.ns_tree.NS_NodeManager. This will cause an error in Treebeard 6.",
|
|
652
|
+
obj=manager_cls,
|
|
653
|
+
id="treebeard.E001",
|
|
654
|
+
)
|
|
655
|
+
)
|
|
656
|
+
return errors
|
|
657
|
+
|
|
641
658
|
class Meta:
|
|
642
659
|
"""Abstract model."""
|
|
643
660
|
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from typing import Any, TypedDict
|
|
2
|
+
|
|
3
|
+
from django.core import serializers
|
|
4
|
+
from django.core.serializers.base import DeserializedObject
|
|
5
|
+
from django.db import models
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class DumpData(TypedDict):
|
|
9
|
+
data: dict[str, Any]
|
|
10
|
+
children: list["DumpData"] # TODO: This should really be NotRequired. Add when Python 3.10 support is dropped
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def prepare_dumpdata_for_loading(
|
|
14
|
+
cls: type[models.Model], *, data: list[DumpData], keep_ids: bool
|
|
15
|
+
) -> list[DeserializedObject]:
|
|
16
|
+
"""
|
|
17
|
+
Given data previously dumped using dump_data, prepares a DeserializedObject for use with load_data.
|
|
18
|
+
"""
|
|
19
|
+
pk_field = cls._meta.pk.attname
|
|
20
|
+
model_identifier = str(cls._meta)
|
|
21
|
+
output = []
|
|
22
|
+
for item in data:
|
|
23
|
+
obj = {"fields": item["data"], "model": model_identifier, "pk": item[pk_field] if keep_ids else None}
|
|
24
|
+
deserialized_obj = next(serializers.deserialize("python", [obj]))
|
|
25
|
+
deserialized_obj.children = prepare_dumpdata_for_loading(cls, data=item.get("children", []), keep_ids=keep_ids)
|
|
26
|
+
output.append(deserialized_obj)
|
|
27
|
+
|
|
28
|
+
return output
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def save_m2m(node: models.Model, deserialized_obj: DeserializedObject):
|
|
32
|
+
"""
|
|
33
|
+
Saves m2m relationships stored on a DeserializedObject.
|
|
34
|
+
"""
|
|
35
|
+
if deserialized_obj.m2m_data:
|
|
36
|
+
for accessor_name, object_list in deserialized_obj.m2m_data.items():
|
|
37
|
+
getattr(node, accessor_name).set(object_list)
|
|
38
|
+
deserialized_obj.m2m_data = None # Avoid accidental reuse of m2m_data if save() is called on the object
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
from copy import deepcopy
|
|
2
|
-
from functools import cache
|
|
3
|
-
from typing import Any, TypedDict
|
|
4
|
-
|
|
5
|
-
from django.db import models
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class DumpData(TypedDict):
|
|
9
|
-
data: dict[str, Any]
|
|
10
|
-
children: list["DumpData"] # TODO: This should really be NotRequired. Add when Python 3.10 support is dropped
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class PreparedDumpData(DumpData):
|
|
14
|
-
children: list["DumpData"]
|
|
15
|
-
pk: Any # TODO: This should really be NotRequired. Add when Python 3.10 support is dropped
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
@cache
|
|
19
|
-
def get_foreign_key_fields(cls: type[models.Model]) -> set[str]:
|
|
20
|
-
return {field.name for field in cls._meta.fields if (field.one_to_one or field.many_to_one)}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
def prepare_dumpdata_for_loading(
|
|
24
|
-
cls: type[models.Model], *, data: list[DumpData], keep_ids: bool
|
|
25
|
-
) -> list[PreparedDumpData]:
|
|
26
|
-
"""
|
|
27
|
-
Given data previously dumped using dump_data, prepares the data for use with load_data.
|
|
28
|
-
|
|
29
|
-
- Modifies foreign key field names to append an `_id` suffix
|
|
30
|
-
- Adds a pk field if `keep_ids` is True.
|
|
31
|
-
"""
|
|
32
|
-
foreign_key_fields = get_foreign_key_fields(cls)
|
|
33
|
-
pk_field = cls._meta.pk.attname
|
|
34
|
-
output = []
|
|
35
|
-
for item in data:
|
|
36
|
-
prepared = deepcopy(item)
|
|
37
|
-
for field in foreign_key_fields:
|
|
38
|
-
# Append _id to field name, so that we don't need to load the foreign objects into memory
|
|
39
|
-
prepared["data"][f"{field}_id"] = prepared["data"].pop(field, None)
|
|
40
|
-
if keep_ids:
|
|
41
|
-
prepared["data"]["pk"] = item[pk_field]
|
|
42
|
-
|
|
43
|
-
prepared["children"] = prepare_dumpdata_for_loading(cls, data=item.get("children", []), keep_ids=keep_ids)
|
|
44
|
-
output.append(prepared)
|
|
45
|
-
|
|
46
|
-
return output
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_treebeard-5.2.2 → django_treebeard-5.3.0}/django_treebeard.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_treebeard-5.2.2 → django_treebeard-5.3.0}/docs/source/_static/treebeard-admin-advanced.png
RENAMED
|
File without changes
|
{django_treebeard-5.2.2 → django_treebeard-5.3.0}/docs/source/_static/treebeard-admin-basic.png
RENAMED
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_treebeard-5.2.2 → django_treebeard-5.3.0}/treebeard/locale/de/LC_MESSAGES/djangojs.mo
RENAMED
|
File without changes
|
{django_treebeard-5.2.2 → django_treebeard-5.3.0}/treebeard/locale/de/LC_MESSAGES/djangojs.po
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_treebeard-5.2.2 → django_treebeard-5.3.0}/treebeard/locale/es/LC_MESSAGES/djangojs.mo
RENAMED
|
File without changes
|
{django_treebeard-5.2.2 → django_treebeard-5.3.0}/treebeard/locale/es/LC_MESSAGES/djangojs.po
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_treebeard-5.2.2 → django_treebeard-5.3.0}/treebeard/locale/fr/LC_MESSAGES/djangojs.mo
RENAMED
|
File without changes
|
{django_treebeard-5.2.2 → django_treebeard-5.3.0}/treebeard/locale/fr/LC_MESSAGES/djangojs.po
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_treebeard-5.2.2 → django_treebeard-5.3.0}/treebeard/locale/hu/LC_MESSAGES/djangojs.mo
RENAMED
|
File without changes
|
{django_treebeard-5.2.2 → django_treebeard-5.3.0}/treebeard/locale/hu/LC_MESSAGES/djangojs.po
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_treebeard-5.2.2 → django_treebeard-5.3.0}/treebeard/locale/nl/LC_MESSAGES/djangojs.mo
RENAMED
|
File without changes
|
{django_treebeard-5.2.2 → django_treebeard-5.3.0}/treebeard/locale/nl/LC_MESSAGES/djangojs.po
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_treebeard-5.2.2 → django_treebeard-5.3.0}/treebeard/locale/ru/LC_MESSAGES/djangojs.mo
RENAMED
|
File without changes
|
{django_treebeard-5.2.2 → django_treebeard-5.3.0}/treebeard/locale/ru/LC_MESSAGES/djangojs.po
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_treebeard-5.2.2 → django_treebeard-5.3.0}/treebeard/static/treebeard/expand-collapse.png
RENAMED
|
File without changes
|
{django_treebeard-5.2.2 → django_treebeard-5.3.0}/treebeard/static/treebeard/treebeard-admin.css
RENAMED
|
File without changes
|
{django_treebeard-5.2.2 → django_treebeard-5.3.0}/treebeard/static/treebeard/treebeard-admin.js
RENAMED
|
File without changes
|
{django_treebeard-5.2.2 → django_treebeard-5.3.0}/treebeard/templates/admin/tree_change_list.html
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|