diff --git a/emeis/core/migrations/0010_scope_full_name.py b/emeis/core/migrations/0010_scope_full_name.py index f129104..7c015b4 100644 --- a/emeis/core/migrations/0010_scope_full_name.py +++ b/emeis/core/migrations/0010_scope_full_name.py @@ -10,7 +10,7 @@ def set_full_name(apps, schema_editor): scope_model = apps.get_model("emeis_core.scope") for scope in scope_model.objects.all().iterator(): # explicitly trigger the set_full_name signal handler - models.set_full_name(instance=scope, sender=set_full_name) + models.set_full_name_and_parents(instance=scope, sender=set_full_name) scope.save() diff --git a/emeis/core/migrations/0012_parents_as_arrayfield.py b/emeis/core/migrations/0012_parents_as_arrayfield.py new file mode 100644 index 0000000..4c88529 --- /dev/null +++ b/emeis/core/migrations/0012_parents_as_arrayfield.py @@ -0,0 +1,54 @@ +# Generated by Django 3.2.25 on 2024-06-13 07:05 + +import django.contrib.postgres.fields +import django.contrib.postgres.indexes +from django.db import migrations, models +import django.db.models.deletion + + +def set_all_parents(apps, schema_editor): + from emeis.core.models import set_full_name_and_parents + + scope_model = apps.get_model("emeis_core.scope") + for scope in scope_model.objects.all().iterator(): + # explicitly trigger the set_full_name signal handler + set_full_name_and_parents(instance=scope, sender=set_all_parents) + scope.save() + + +class Migration(migrations.Migration): + dependencies = [ + ("emeis_core", "0011_mptt_to_tree_queries"), + ] + + operations = [ + migrations.AlterModelOptions( + name="scope", + options={"ordering": ["name"]}, + ), + migrations.AddField( + model_name="scope", + name="all_parents", + field=django.contrib.postgres.fields.ArrayField( + base_field=models.UUIDField(), default=list, size=None + ), + ), + migrations.AlterField( + model_name="scope", + name="parent", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="children", + to="emeis_core.scope", + ), + ), + migrations.AddIndex( + model_name="scope", + index=django.contrib.postgres.indexes.GinIndex( + fields=["all_parents"], name="emeis_core__all_par_f8231c_gin" + ), + ), + migrations.RunPython(set_all_parents, migrations.RunPython.noop), + ] diff --git a/emeis/core/models.py b/emeis/core/models.py index 8c42b94..f3298be 100644 --- a/emeis/core/models.py +++ b/emeis/core/models.py @@ -1,18 +1,20 @@ -import operator import unicodedata import uuid -from functools import reduce from django.conf import settings from django.contrib.auth.models import AbstractBaseUser, UserManager from django.contrib.auth.validators import UnicodeUsernameValidator +from django.contrib.postgres.aggregates import ArrayAgg +from django.contrib.postgres.fields import ArrayField +from django.contrib.postgres.indexes import GinIndex +from django.core.exceptions import ValidationError from django.db import models +from django.db.models import Exists, OuterRef, Q, Subquery from django.db.models.signals import pre_save from django.dispatch import receiver from django.utils import timezone, translation from django.utils.translation import gettext_lazy as _ from localized_fields.fields import LocalizedCharField, LocalizedTextField -from tree_queries.models import TreeNode, TreeQuerySet def make_uuid(): @@ -160,59 +162,34 @@ def is_authenticated(self): return True -class ScopeQuerySet(TreeQuerySet): - # django-tree-queries sadly does not (yet?) support ancestors query - # for QS - only for single nodes. So we're providing all_descendants() - # and all_ancestors() queryset methods. - +class ScopeQuerySet(models.QuerySet): def all_descendants(self, include_self=False): - """Return a QS that contains all descendants of the given QS. + """Return a QS that contains all descendants.""" + expr = Q(all_parents__overlap=self.aggregate(all_pks=ArrayAgg("pk"))["all_pks"]) - This is a workaround for django-tree-queries, which currently does - not support this query (it can only do it on single nodes). + if include_self: + expr = expr | Q(pk__in=self) - This is in contrast to .descendants(), which can only give the descendants - of one model instance. - """ - descendants_q = reduce( - operator.or_, - [ - models.Q( - pk__in=entry.descendants(include_self=include_self).values("pk") - ) - for entry in self - ], - models.Q(), - ) - return self.model.objects.filter(descendants_q) + return Scope.objects.filter(expr) def all_ancestors(self, include_self=False): - """Return a QS that contains all ancestors of the given QS. + """Return a QS that contains all ancestors.""" - This is a workaround for django-tree-queries, which currently does - not support this query (it can only do it on single nodes). + filter_qs = self.filter(all_parents__contains=[OuterRef("pk")]) - This is in contrast to .ancestors(), which can only give the ancestors - of one model instance. - """ + new_qs = Scope.objects.all().annotate(_is_ancestor=Exists(Subquery(filter_qs))) + expr = Q(_is_ancestor=True) - descendants_q = reduce( - operator.or_, - [ - models.Q(pk__in=entry.ancestors(include_self=include_self).values("pk")) - for entry in self - ], - models.Q(), - ) - return self.model.objects.filter(descendants_q) + if include_self: + expr = expr | Q(pk__in=self) + + return new_qs.filter(expr) def all_roots(self): - return Scope.objects.all().filter( - pk__in=[scope.ancestors(include_self=True).first().pk for scope in self] - ) + return self.all_ancestors(include_self=True).filter(parent__isnull=True) -class Scope(TreeNode, UUIDModel): +class Scope(UUIDModel): name = LocalizedCharField(_("scope name"), blank=False, null=False, required=False) full_name = LocalizedCharField( @@ -224,26 +201,67 @@ class Scope(TreeNode, UUIDModel): ) is_active = models.BooleanField(default=True) - objects = ScopeQuerySet.as_manager(with_tree_fields=True) + objects = ScopeQuerySet.as_manager() + + parent = models.ForeignKey( + "Scope", + null=True, + blank=True, + on_delete=models.CASCADE, + related_name="children", + ) + + all_parents = ArrayField(models.UUIDField(null=False), default=list) + + def ancestors(self, include_self=False): + expr = Q(pk__in=self.all_parents) + if include_self: + expr = expr | Q(pk=self.pk) + return Scope.objects.all().filter(expr) + + def descendants(self, include_self=False): + expr = Q(all_parents__contains=[self.pk]) + + if include_self: + expr = expr | Q(pk=self.pk) + + return Scope.objects.all().filter(expr) def get_root(self): - return self.ancestors(include_self=True).first() + if self.parent_id: + return Scope.objects.get(pk=self.all_parents[0]) + else: + return self def save(self, *args, **kwargs): - # django-tree-queries does validation in TreeNode.clean(), which is not - # called by DRF (only by django forms), so we have to do this here - self.clean() + self._ensure_no_loop() return super().save(*args, **kwargs) + def _ensure_no_loop(self): + parent = self.parent + while parent: + if parent == self: + raise ValidationError( + "A node cannot be made a descendant or parent of itself" + ) + parent = parent.parent + def __str__(self): return f"{type(self).__name__} ({self.full_name}, pk={self.pk})" class Meta: ordering = ["name"] + indexes = [GinIndex(fields=["all_parents"])] @receiver(pre_save, sender=Scope) -def set_full_name(instance, sender, **kwargs): +def set_full_name_and_parents(instance, sender, **kwargs): + """Update the `full_name` and `all_parents` properties of the Scope. + + The full name depends on the complete list of parents of the Scope. + And to ensure correct behaviour in the queries, the `all_parents` + attribute needs to be updated as well + """ if kwargs.get("raw"): # pragma: no cover # Raw is set while loading fixtures. In those # cases we don't want to mess with data that @@ -255,6 +273,9 @@ def set_full_name(instance, sender, **kwargs): forced_lang = settings.EMEIS_FORCE_MODEL_LOCALE.get("scope", None) + old_all_parents = [*instance.all_parents] + old_full_name = {**instance.full_name} + if forced_lang: # If scope is forced monolingual, do not fill non-forced language fields languages = [forced_lang] @@ -263,25 +284,34 @@ def set_full_name(instance, sender, **kwargs): with translation.override(lang): instance.full_name[lang] = str(instance.name) + parent_ids = [] parent = instance.parent while parent: + parent_ids.append(parent.pk) for lang in languages: with translation.override(lang): new_fullname = f"{parent.name} {sep} {instance.full_name[lang]}" instance.full_name[lang] = new_fullname parent = parent.parent + # make it root-first + parent_ids.reverse() + instance.all_parents = parent_ids + if forced_lang: # Ensure only the "forced" language full_name is set, and nothing else full_name = instance.full_name[forced_lang] instance.full_name.clear() instance.full_name[forced_lang] = full_name - # Force update of all children (recursively) - for child in instance.children.all(): - # save() triggers the set_full_name signal handler, which will - # recurse all the way down, updating the full_name - child.save() + if old_all_parents != instance.all_parents or old_full_name != dict( + instance.full_name + ): + # Something changed - force update all children (recursively) + for child in instance.children.all(): + # save() triggers the signal handler, which will + # recurse all the way down, updating the full_name + child.save() class Role(SlugModel): diff --git a/emeis/core/serializers.py b/emeis/core/serializers.py index d7ed834..4d8887c 100644 --- a/emeis/core/serializers.py +++ b/emeis/core/serializers.py @@ -80,24 +80,12 @@ class ScopeSerializer(BaseSerializer): level = serializers.SerializerMethodField() def get_level(self, obj): - depth = getattr(obj, "tree_depth", None) - if depth is not None: - return depth - - # Note: This should only happen on CREATE, never in GET (Either list, - # detail, or include!) In CREATE, it's a new object that doesn't come - # from a QS - - # Sometimes, the model object may come out of a non-django-tree-queries - # QS, and thus would not have the `tree_*` attributes amended. Then we - # need to go the "slow path" if not obj.pk and obj.parent_id: - # unsaved object, sometimes used in unit tests etc + # unsaved object, sometimes used in unit tests etc. Can't rely on + # `all_parents` being set just yet return self.get_level(obj.parent) + 1 - if obj.parent_id: - return obj.ancestors().count() - return 0 + return len(obj.all_parents) class Meta: model = Scope diff --git a/emeis/core/tests/snapshots/snap_test_api.py b/emeis/core/tests/snapshots/snap_test_api.py index b257e04..5dd7e12 100644 --- a/emeis/core/tests/snapshots/snap_test_api.py +++ b/emeis/core/tests/snapshots/snap_test_api.py @@ -9,43 +9,7 @@ snapshots["test_api_create[ACLViewSet] 1"] = { "queries": [ - """ - WITH RECURSIVE __rank_table( - - "id", - "parent_id", - "rank_order" - ) AS ( - SELECT "emeis_core_scope"."id", "emeis_core_scope"."parent_id", ROW_NUMBER() OVER (ORDER BY "emeis_core_scope"."name" ASC) AS "rank_order" FROM "emeis_core_scope" - ), - __tree ( - - "tree_depth", - "tree_path", - "tree_ordering", - "tree_pk" - ) AS ( - SELECT - - 0, - array[T.id], - array[T.rank_order], - T."id" - FROM __rank_table T - WHERE T."parent_id" IS NULL - - UNION ALL - - SELECT - - __tree.tree_depth + 1, - __tree.tree_path || T.id, - __tree.tree_ordering || T.rank_order, - T."id" - FROM __rank_table T - JOIN __tree ON T."parent_id" = __tree.tree_pk - ) - SELECT (__tree.tree_depth) AS "tree_depth", (__tree.tree_path) AS "tree_path", (__tree.tree_ordering) AS "tree_ordering", "emeis_core_scope"."parent_id", "emeis_core_scope"."created_at", "emeis_core_scope"."modified_at", "emeis_core_scope"."created_by_user_id", "emeis_core_scope"."metainfo", "emeis_core_scope"."id", "emeis_core_scope"."name", "emeis_core_scope"."full_name", "emeis_core_scope"."description", "emeis_core_scope"."is_active" FROM "emeis_core_scope" , "__tree" WHERE ("emeis_core_scope"."id" = \'9336ebf2-5087-d91c-818e-e6e9ec29f8c1\'::uuid AND (__tree.tree_pk = emeis_core_scope.id)) ORDER BY ("__tree".tree_ordering) ASC LIMIT 21""", + 'SELECT "emeis_core_scope"."created_at", "emeis_core_scope"."modified_at", "emeis_core_scope"."created_by_user_id", "emeis_core_scope"."metainfo", "emeis_core_scope"."id", "emeis_core_scope"."name", "emeis_core_scope"."full_name", "emeis_core_scope"."description", "emeis_core_scope"."is_active", "emeis_core_scope"."parent_id", "emeis_core_scope"."all_parents" FROM "emeis_core_scope" WHERE "emeis_core_scope"."id" = \'9336ebf2-5087-d91c-818e-e6e9ec29f8c1\'::uuid LIMIT 21', 'SELECT "emeis_core_user"."password", "emeis_core_user"."last_login", "emeis_core_user"."created_at", "emeis_core_user"."modified_at", "emeis_core_user"."created_by_user_id", "emeis_core_user"."metainfo", "emeis_core_user"."id", "emeis_core_user"."username", "emeis_core_user"."first_name", "emeis_core_user"."last_name", "emeis_core_user"."email", "emeis_core_user"."phone", "emeis_core_user"."language", "emeis_core_user"."address", "emeis_core_user"."city", "emeis_core_user"."zip", "emeis_core_user"."is_active", "emeis_core_user"."date_joined" FROM "emeis_core_user" WHERE "emeis_core_user"."id" = \'9dd4e461-268c-8034-f5c8-564e155c67a6\'::uuid LIMIT 21', 'SELECT "emeis_core_role"."created_at", "emeis_core_role"."modified_at", "emeis_core_role"."created_by_user_id", "emeis_core_role"."metainfo", "emeis_core_role"."slug", "emeis_core_role"."name", "emeis_core_role"."description" FROM "emeis_core_role" WHERE "emeis_core_role"."slug" = \'fund-executive-most\' LIMIT 21', 'SELECT (1) AS "a" FROM "emeis_core_acl" WHERE ("emeis_core_acl"."role_id" = \'fund-executive-most\' AND "emeis_core_acl"."scope_id" = \'9336ebf2-5087-d91c-818e-e6e9ec29f8c1\'::uuid AND "emeis_core_acl"."user_id" = \'9dd4e461-268c-8034-f5c8-564e155c67a6\'::uuid) LIMIT 1', @@ -190,45 +154,9 @@ snapshots["test_api_create[ScopeViewSet] 1"] = { "queries": [ - """ - WITH RECURSIVE __rank_table( - - "id", - "parent_id", - "rank_order" - ) AS ( - SELECT "emeis_core_scope"."id", "emeis_core_scope"."parent_id", ROW_NUMBER() OVER (ORDER BY "emeis_core_scope"."name" ASC) AS "rank_order" FROM "emeis_core_scope" - ), - __tree ( - - "tree_depth", - "tree_path", - "tree_ordering", - "tree_pk" - ) AS ( - SELECT - - 0, - array[T.id], - array[T.rank_order], - T."id" - FROM __rank_table T - WHERE T."parent_id" IS NULL - - UNION ALL - - SELECT - - __tree.tree_depth + 1, - __tree.tree_path || T.id, - __tree.tree_ordering || T.rank_order, - T."id" - FROM __rank_table T - JOIN __tree ON T."parent_id" = __tree.tree_pk - ) - SELECT (__tree.tree_depth) AS "tree_depth", (__tree.tree_path) AS "tree_path", (__tree.tree_ordering) AS "tree_ordering", "emeis_core_scope"."parent_id", "emeis_core_scope"."created_at", "emeis_core_scope"."modified_at", "emeis_core_scope"."created_by_user_id", "emeis_core_scope"."metainfo", "emeis_core_scope"."id", "emeis_core_scope"."name", "emeis_core_scope"."full_name", "emeis_core_scope"."description", "emeis_core_scope"."is_active" FROM "emeis_core_scope" , "__tree" WHERE ("emeis_core_scope"."parent_id" = \'f561aaf6-ef0b-f14d-4208-bb46a4ccb3ad\'::uuid AND (__tree.tree_pk = emeis_core_scope.id)) ORDER BY ("__tree".tree_ordering) ASC""", - """INSERT INTO "emeis_core_scope" ("parent_id", "created_at", "modified_at", "created_by_user_id", "metainfo", "id", "name", "full_name", "description", "is_active") VALUES (NULL, \'2017-05-21T00:00:00+00:00\'::timestamptz, \'2017-05-21T00:00:00+00:00\'::timestamptz, \'9336ebf2-5087-d91c-818e-e6e9ec29f8c1\'::uuid, \'{}\', \'f561aaf6-ef0b-f14d-4208-bb46a4ccb3ad\'::uuid, hstore(ARRAY[\'en\',\'de\',\'fr\'], ARRAY[\'Pamela Horton\',\'\',\'\']), hstore(ARRAY[\'en\',\'de\',\'fr\'], ARRAY[\'Pamela Horton\',\'Pamela Horton\',\'Pamela Horton\']), hstore(ARRAY[\'en\',\'de\',\'fr\'], ARRAY[\'Effort meet relationship far. Option program interesting station. First where during teach country talk across. -Argue move appear catch toward help wind. Material minute ago get.','','']), true)""", + 'SELECT "emeis_core_scope"."created_at", "emeis_core_scope"."modified_at", "emeis_core_scope"."created_by_user_id", "emeis_core_scope"."metainfo", "emeis_core_scope"."id", "emeis_core_scope"."name", "emeis_core_scope"."full_name", "emeis_core_scope"."description", "emeis_core_scope"."is_active", "emeis_core_scope"."parent_id", "emeis_core_scope"."all_parents" FROM "emeis_core_scope" WHERE "emeis_core_scope"."parent_id" = \'f561aaf6-ef0b-f14d-4208-bb46a4ccb3ad\'::uuid ORDER BY "emeis_core_scope"."name" ASC', + """INSERT INTO "emeis_core_scope" ("created_at", "modified_at", "created_by_user_id", "metainfo", "id", "name", "full_name", "description", "is_active", "parent_id", "all_parents") VALUES (\'2017-05-21T00:00:00+00:00\'::timestamptz, \'2017-05-21T00:00:00+00:00\'::timestamptz, \'9336ebf2-5087-d91c-818e-e6e9ec29f8c1\'::uuid, \'{}\', \'f561aaf6-ef0b-f14d-4208-bb46a4ccb3ad\'::uuid, hstore(ARRAY[\'en\',\'de\',\'fr\'], ARRAY[\'Pamela Horton\',\'\',\'\']), hstore(ARRAY[\'en\',\'de\',\'fr\'], ARRAY[\'Pamela Horton\',\'Pamela Horton\',\'Pamela Horton\']), hstore(ARRAY[\'en\',\'de\',\'fr\'], ARRAY[\'Effort meet relationship far. Option program interesting station. First where during teach country talk across. +Argue move appear catch toward help wind. Material minute ago get.','','']), true, NULL, '{}'::uuid[])""", ], "request": { "CONTENT_LENGTH": "614", @@ -326,43 +254,7 @@ snapshots["test_api_destroy[ACLViewSet] 1"] = { "queries": [ 'SELECT "emeis_core_acl"."created_at", "emeis_core_acl"."modified_at", "emeis_core_acl"."created_by_user_id", "emeis_core_acl"."metainfo", "emeis_core_acl"."id", "emeis_core_acl"."user_id", "emeis_core_acl"."scope_id", "emeis_core_acl"."role_id", "emeis_core_user"."password", "emeis_core_user"."last_login", "emeis_core_user"."created_at", "emeis_core_user"."modified_at", "emeis_core_user"."created_by_user_id", "emeis_core_user"."metainfo", "emeis_core_user"."id", "emeis_core_user"."username", "emeis_core_user"."first_name", "emeis_core_user"."last_name", "emeis_core_user"."email", "emeis_core_user"."phone", "emeis_core_user"."language", "emeis_core_user"."address", "emeis_core_user"."city", "emeis_core_user"."zip", "emeis_core_user"."is_active", "emeis_core_user"."date_joined", "emeis_core_role"."created_at", "emeis_core_role"."modified_at", "emeis_core_role"."created_by_user_id", "emeis_core_role"."metainfo", "emeis_core_role"."slug", "emeis_core_role"."name", "emeis_core_role"."description" FROM "emeis_core_acl" INNER JOIN "emeis_core_user" ON ("emeis_core_acl"."user_id" = "emeis_core_user"."id") INNER JOIN "emeis_core_role" ON ("emeis_core_acl"."role_id" = "emeis_core_role"."slug") WHERE "emeis_core_acl"."id" = \'f561aaf6-ef0b-f14d-4208-bb46a4ccb3ad\'::uuid LIMIT 21', - """ - WITH RECURSIVE __rank_table( - - "id", - "parent_id", - "rank_order" - ) AS ( - SELECT "emeis_core_scope"."id", "emeis_core_scope"."parent_id", ROW_NUMBER() OVER (ORDER BY "emeis_core_scope"."name" ASC) AS "rank_order" FROM "emeis_core_scope" - ), - __tree ( - - "tree_depth", - "tree_path", - "tree_ordering", - "tree_pk" - ) AS ( - SELECT - - 0, - array[T.id], - array[T.rank_order], - T."id" - FROM __rank_table T - WHERE T."parent_id" IS NULL - - UNION ALL - - SELECT - - __tree.tree_depth + 1, - __tree.tree_path || T.id, - __tree.tree_ordering || T.rank_order, - T."id" - FROM __rank_table T - JOIN __tree ON T."parent_id" = __tree.tree_pk - ) - SELECT (__tree.tree_depth) AS "tree_depth", (__tree.tree_path) AS "tree_path", (__tree.tree_ordering) AS "tree_ordering", "emeis_core_scope"."parent_id", "emeis_core_scope"."created_at", "emeis_core_scope"."modified_at", "emeis_core_scope"."created_by_user_id", "emeis_core_scope"."metainfo", "emeis_core_scope"."id", "emeis_core_scope"."name", "emeis_core_scope"."full_name", "emeis_core_scope"."description", "emeis_core_scope"."is_active" FROM "emeis_core_scope" , "__tree" WHERE ("emeis_core_scope"."id" IN (\'9336ebf2-5087-d91c-818e-e6e9ec29f8c1\'::uuid) AND (__tree.tree_pk = emeis_core_scope.id)) ORDER BY ("__tree".tree_ordering) ASC""", + 'SELECT "emeis_core_scope"."created_at", "emeis_core_scope"."modified_at", "emeis_core_scope"."created_by_user_id", "emeis_core_scope"."metainfo", "emeis_core_scope"."id", "emeis_core_scope"."name", "emeis_core_scope"."full_name", "emeis_core_scope"."description", "emeis_core_scope"."is_active", "emeis_core_scope"."parent_id", "emeis_core_scope"."all_parents" FROM "emeis_core_scope" WHERE "emeis_core_scope"."id" IN (\'9336ebf2-5087-d91c-818e-e6e9ec29f8c1\'::uuid) ORDER BY "emeis_core_scope"."name" ASC', 'DELETE FROM "emeis_core_acl" WHERE "emeis_core_acl"."id" IN (\'f561aaf6-ef0b-f14d-4208-bb46a4ccb3ad\'::uuid)', ], "request": { @@ -409,43 +301,7 @@ snapshots["test_api_destroy[ScopeViewSet] 1"] = { "queries": [ - """ - WITH RECURSIVE __rank_table( - - "id", - "parent_id", - "rank_order" - ) AS ( - SELECT "emeis_core_scope"."id", "emeis_core_scope"."parent_id", ROW_NUMBER() OVER (ORDER BY ("emeis_core_scope"."name" -> \'en\') ASC) AS "rank_order" FROM "emeis_core_scope" - ), - __tree ( - - "tree_depth", - "tree_path", - "tree_ordering", - "tree_pk" - ) AS ( - SELECT - - 0, - array[T.id], - array[T.rank_order], - T."id" - FROM __rank_table T - WHERE T."parent_id" IS NULL - - UNION ALL - - SELECT - - __tree.tree_depth + 1, - __tree.tree_path || T.id, - __tree.tree_ordering || T.rank_order, - T."id" - FROM __rank_table T - JOIN __tree ON T."parent_id" = __tree.tree_pk - ) - SELECT (__tree.tree_depth) AS "tree_depth", (__tree.tree_path) AS "tree_path", (__tree.tree_ordering) AS "tree_ordering", "emeis_core_scope"."parent_id", "emeis_core_scope"."created_at", "emeis_core_scope"."modified_at", "emeis_core_scope"."created_by_user_id", "emeis_core_scope"."metainfo", "emeis_core_scope"."id", "emeis_core_scope"."name", "emeis_core_scope"."full_name", "emeis_core_scope"."description", "emeis_core_scope"."is_active" FROM "emeis_core_scope" , "__tree" WHERE ("emeis_core_scope"."id" = \'9dd4e461-268c-8034-f5c8-564e155c67a6\'::uuid AND (__tree.tree_pk = emeis_core_scope.id)) ORDER BY ("__tree".tree_ordering) ASC LIMIT 21""", + 'SELECT "emeis_core_scope"."created_at", "emeis_core_scope"."modified_at", "emeis_core_scope"."created_by_user_id", "emeis_core_scope"."metainfo", "emeis_core_scope"."id", "emeis_core_scope"."name", "emeis_core_scope"."full_name", "emeis_core_scope"."description", "emeis_core_scope"."is_active", "emeis_core_scope"."parent_id", "emeis_core_scope"."all_parents" FROM "emeis_core_scope" WHERE "emeis_core_scope"."id" = \'9dd4e461-268c-8034-f5c8-564e155c67a6\'::uuid LIMIT 21', 'SELECT "emeis_core_acl"."created_at", "emeis_core_acl"."modified_at", "emeis_core_acl"."created_by_user_id", "emeis_core_acl"."metainfo", "emeis_core_acl"."id", "emeis_core_acl"."user_id", "emeis_core_acl"."scope_id", "emeis_core_acl"."role_id", "emeis_core_user"."password", "emeis_core_user"."last_login", "emeis_core_user"."created_at", "emeis_core_user"."modified_at", "emeis_core_user"."created_by_user_id", "emeis_core_user"."metainfo", "emeis_core_user"."id", "emeis_core_user"."username", "emeis_core_user"."first_name", "emeis_core_user"."last_name", "emeis_core_user"."email", "emeis_core_user"."phone", "emeis_core_user"."language", "emeis_core_user"."address", "emeis_core_user"."city", "emeis_core_user"."zip", "emeis_core_user"."is_active", "emeis_core_user"."date_joined", "emeis_core_role"."created_at", "emeis_core_role"."modified_at", "emeis_core_role"."created_by_user_id", "emeis_core_role"."metainfo", "emeis_core_role"."slug", "emeis_core_role"."name", "emeis_core_role"."description" FROM "emeis_core_acl" INNER JOIN "emeis_core_user" ON ("emeis_core_acl"."user_id" = "emeis_core_user"."id") INNER JOIN "emeis_core_role" ON ("emeis_core_acl"."role_id" = "emeis_core_role"."slug") WHERE "emeis_core_acl"."scope_id" IN (\'9dd4e461-268c-8034-f5c8-564e155c67a6\'::uuid)', 'SELECT "emeis_core_scope"."id" FROM "emeis_core_scope" WHERE "emeis_core_scope"."parent_id" IN (\'9dd4e461-268c-8034-f5c8-564e155c67a6\'::uuid) ORDER BY "emeis_core_scope"."name" ASC', 'DELETE FROM "emeis_core_acl" WHERE "emeis_core_acl"."scope_id" IN (\'9dd4e461-268c-8034-f5c8-564e155c67a6\'::uuid)', @@ -484,43 +340,7 @@ snapshots["test_api_detail[ACLViewSet] 1"] = { "queries": [ 'SELECT "emeis_core_acl"."created_at", "emeis_core_acl"."modified_at", "emeis_core_acl"."created_by_user_id", "emeis_core_acl"."metainfo", "emeis_core_acl"."id", "emeis_core_acl"."user_id", "emeis_core_acl"."scope_id", "emeis_core_acl"."role_id", "emeis_core_user"."password", "emeis_core_user"."last_login", "emeis_core_user"."created_at", "emeis_core_user"."modified_at", "emeis_core_user"."created_by_user_id", "emeis_core_user"."metainfo", "emeis_core_user"."id", "emeis_core_user"."username", "emeis_core_user"."first_name", "emeis_core_user"."last_name", "emeis_core_user"."email", "emeis_core_user"."phone", "emeis_core_user"."language", "emeis_core_user"."address", "emeis_core_user"."city", "emeis_core_user"."zip", "emeis_core_user"."is_active", "emeis_core_user"."date_joined", "emeis_core_role"."created_at", "emeis_core_role"."modified_at", "emeis_core_role"."created_by_user_id", "emeis_core_role"."metainfo", "emeis_core_role"."slug", "emeis_core_role"."name", "emeis_core_role"."description" FROM "emeis_core_acl" INNER JOIN "emeis_core_user" ON ("emeis_core_acl"."user_id" = "emeis_core_user"."id") INNER JOIN "emeis_core_role" ON ("emeis_core_acl"."role_id" = "emeis_core_role"."slug") WHERE "emeis_core_acl"."id" = \'f561aaf6-ef0b-f14d-4208-bb46a4ccb3ad\'::uuid LIMIT 21', - """ - WITH RECURSIVE __rank_table( - - "id", - "parent_id", - "rank_order" - ) AS ( - SELECT "emeis_core_scope"."id", "emeis_core_scope"."parent_id", ROW_NUMBER() OVER (ORDER BY "emeis_core_scope"."name" ASC) AS "rank_order" FROM "emeis_core_scope" - ), - __tree ( - - "tree_depth", - "tree_path", - "tree_ordering", - "tree_pk" - ) AS ( - SELECT - - 0, - array[T.id], - array[T.rank_order], - T."id" - FROM __rank_table T - WHERE T."parent_id" IS NULL - - UNION ALL - - SELECT - - __tree.tree_depth + 1, - __tree.tree_path || T.id, - __tree.tree_ordering || T.rank_order, - T."id" - FROM __rank_table T - JOIN __tree ON T."parent_id" = __tree.tree_pk - ) - SELECT (__tree.tree_depth) AS "tree_depth", (__tree.tree_path) AS "tree_path", (__tree.tree_ordering) AS "tree_ordering", "emeis_core_scope"."parent_id", "emeis_core_scope"."created_at", "emeis_core_scope"."modified_at", "emeis_core_scope"."created_by_user_id", "emeis_core_scope"."metainfo", "emeis_core_scope"."id", "emeis_core_scope"."name", "emeis_core_scope"."full_name", "emeis_core_scope"."description", "emeis_core_scope"."is_active" FROM "emeis_core_scope" , "__tree" WHERE ("emeis_core_scope"."id" IN (\'9336ebf2-5087-d91c-818e-e6e9ec29f8c1\'::uuid) AND (__tree.tree_pk = emeis_core_scope.id)) ORDER BY ("__tree".tree_ordering) ASC""", + 'SELECT "emeis_core_scope"."created_at", "emeis_core_scope"."modified_at", "emeis_core_scope"."created_by_user_id", "emeis_core_scope"."metainfo", "emeis_core_scope"."id", "emeis_core_scope"."name", "emeis_core_scope"."full_name", "emeis_core_scope"."description", "emeis_core_scope"."is_active", "emeis_core_scope"."parent_id", "emeis_core_scope"."all_parents" FROM "emeis_core_scope" WHERE "emeis_core_scope"."id" IN (\'9336ebf2-5087-d91c-818e-e6e9ec29f8c1\'::uuid) ORDER BY "emeis_core_scope"."name" ASC', 'SELECT "emeis_core_acl"."created_at", "emeis_core_acl"."modified_at", "emeis_core_acl"."created_by_user_id", "emeis_core_acl"."metainfo", "emeis_core_acl"."id", "emeis_core_acl"."user_id", "emeis_core_acl"."scope_id", "emeis_core_acl"."role_id" FROM "emeis_core_acl" WHERE "emeis_core_acl"."user_id" = \'9dd4e461-268c-8034-f5c8-564e155c67a6\'::uuid', 'SELECT "emeis_core_permission"."created_at", "emeis_core_permission"."modified_at", "emeis_core_permission"."created_by_user_id", "emeis_core_permission"."metainfo", "emeis_core_permission"."slug", "emeis_core_permission"."name", "emeis_core_permission"."description" FROM "emeis_core_permission" INNER JOIN "emeis_core_role_permissions" ON ("emeis_core_permission"."slug" = "emeis_core_role_permissions"."permission_id") WHERE "emeis_core_role_permissions"."role_id" = \'fund-executive-most\'', ], @@ -721,43 +541,7 @@ snapshots["test_api_detail[ScopeViewSet] 1"] = { "queries": [ - """ - WITH RECURSIVE __rank_table( - - "id", - "parent_id", - "rank_order" - ) AS ( - SELECT "emeis_core_scope"."id", "emeis_core_scope"."parent_id", ROW_NUMBER() OVER (ORDER BY ("emeis_core_scope"."name" -> \'en\') ASC) AS "rank_order" FROM "emeis_core_scope" - ), - __tree ( - - "tree_depth", - "tree_path", - "tree_ordering", - "tree_pk" - ) AS ( - SELECT - - 0, - array[T.id], - array[T.rank_order], - T."id" - FROM __rank_table T - WHERE T."parent_id" IS NULL - - UNION ALL - - SELECT - - __tree.tree_depth + 1, - __tree.tree_path || T.id, - __tree.tree_ordering || T.rank_order, - T."id" - FROM __rank_table T - JOIN __tree ON T."parent_id" = __tree.tree_pk - ) - SELECT (__tree.tree_depth) AS "tree_depth", (__tree.tree_path) AS "tree_path", (__tree.tree_ordering) AS "tree_ordering", "emeis_core_scope"."parent_id", "emeis_core_scope"."created_at", "emeis_core_scope"."modified_at", "emeis_core_scope"."created_by_user_id", "emeis_core_scope"."metainfo", "emeis_core_scope"."id", "emeis_core_scope"."name", "emeis_core_scope"."full_name", "emeis_core_scope"."description", "emeis_core_scope"."is_active" FROM "emeis_core_scope" , "__tree" WHERE ("emeis_core_scope"."id" = \'9dd4e461-268c-8034-f5c8-564e155c67a6\'::uuid AND (__tree.tree_pk = emeis_core_scope.id)) ORDER BY ("__tree".tree_ordering) ASC LIMIT 21""", + 'SELECT "emeis_core_scope"."created_at", "emeis_core_scope"."modified_at", "emeis_core_scope"."created_by_user_id", "emeis_core_scope"."metainfo", "emeis_core_scope"."id", "emeis_core_scope"."name", "emeis_core_scope"."full_name", "emeis_core_scope"."description", "emeis_core_scope"."is_active", "emeis_core_scope"."parent_id", "emeis_core_scope"."all_parents" FROM "emeis_core_scope" WHERE "emeis_core_scope"."id" = \'9dd4e461-268c-8034-f5c8-564e155c67a6\'::uuid LIMIT 21', 'SELECT "emeis_core_acl"."created_at", "emeis_core_acl"."modified_at", "emeis_core_acl"."created_by_user_id", "emeis_core_acl"."metainfo", "emeis_core_acl"."id", "emeis_core_acl"."user_id", "emeis_core_acl"."scope_id", "emeis_core_acl"."role_id", "emeis_core_user"."password", "emeis_core_user"."last_login", "emeis_core_user"."created_at", "emeis_core_user"."modified_at", "emeis_core_user"."created_by_user_id", "emeis_core_user"."metainfo", "emeis_core_user"."id", "emeis_core_user"."username", "emeis_core_user"."first_name", "emeis_core_user"."last_name", "emeis_core_user"."email", "emeis_core_user"."phone", "emeis_core_user"."language", "emeis_core_user"."address", "emeis_core_user"."city", "emeis_core_user"."zip", "emeis_core_user"."is_active", "emeis_core_user"."date_joined", "emeis_core_role"."created_at", "emeis_core_role"."modified_at", "emeis_core_role"."created_by_user_id", "emeis_core_role"."metainfo", "emeis_core_role"."slug", "emeis_core_role"."name", "emeis_core_role"."description" FROM "emeis_core_acl" INNER JOIN "emeis_core_user" ON ("emeis_core_acl"."user_id" = "emeis_core_user"."id") INNER JOIN "emeis_core_role" ON ("emeis_core_acl"."role_id" = "emeis_core_role"."slug") WHERE "emeis_core_acl"."scope_id" IN (\'9dd4e461-268c-8034-f5c8-564e155c67a6\'::uuid)', ], "request": { @@ -843,43 +627,7 @@ snapshots["test_api_list[ACLViewSet] 1"] = { "queries": [ 'SELECT "emeis_core_acl"."created_at", "emeis_core_acl"."modified_at", "emeis_core_acl"."created_by_user_id", "emeis_core_acl"."metainfo", "emeis_core_acl"."id", "emeis_core_acl"."user_id", "emeis_core_acl"."scope_id", "emeis_core_acl"."role_id", "emeis_core_user"."password", "emeis_core_user"."last_login", "emeis_core_user"."created_at", "emeis_core_user"."modified_at", "emeis_core_user"."created_by_user_id", "emeis_core_user"."metainfo", "emeis_core_user"."id", "emeis_core_user"."username", "emeis_core_user"."first_name", "emeis_core_user"."last_name", "emeis_core_user"."email", "emeis_core_user"."phone", "emeis_core_user"."language", "emeis_core_user"."address", "emeis_core_user"."city", "emeis_core_user"."zip", "emeis_core_user"."is_active", "emeis_core_user"."date_joined", "emeis_core_role"."created_at", "emeis_core_role"."modified_at", "emeis_core_role"."created_by_user_id", "emeis_core_role"."metainfo", "emeis_core_role"."slug", "emeis_core_role"."name", "emeis_core_role"."description" FROM "emeis_core_acl" INNER JOIN "emeis_core_user" ON ("emeis_core_acl"."user_id" = "emeis_core_user"."id") INNER JOIN "emeis_core_role" ON ("emeis_core_acl"."role_id" = "emeis_core_role"."slug")', - """ - WITH RECURSIVE __rank_table( - - "id", - "parent_id", - "rank_order" - ) AS ( - SELECT "emeis_core_scope"."id", "emeis_core_scope"."parent_id", ROW_NUMBER() OVER (ORDER BY "emeis_core_scope"."name" ASC) AS "rank_order" FROM "emeis_core_scope" - ), - __tree ( - - "tree_depth", - "tree_path", - "tree_ordering", - "tree_pk" - ) AS ( - SELECT - - 0, - array[T.id], - array[T.rank_order], - T."id" - FROM __rank_table T - WHERE T."parent_id" IS NULL - - UNION ALL - - SELECT - - __tree.tree_depth + 1, - __tree.tree_path || T.id, - __tree.tree_ordering || T.rank_order, - T."id" - FROM __rank_table T - JOIN __tree ON T."parent_id" = __tree.tree_pk - ) - SELECT (__tree.tree_depth) AS "tree_depth", (__tree.tree_path) AS "tree_path", (__tree.tree_ordering) AS "tree_ordering", "emeis_core_scope"."parent_id", "emeis_core_scope"."created_at", "emeis_core_scope"."modified_at", "emeis_core_scope"."created_by_user_id", "emeis_core_scope"."metainfo", "emeis_core_scope"."id", "emeis_core_scope"."name", "emeis_core_scope"."full_name", "emeis_core_scope"."description", "emeis_core_scope"."is_active" FROM "emeis_core_scope" , "__tree" WHERE ("emeis_core_scope"."id" IN (\'9336ebf2-5087-d91c-818e-e6e9ec29f8c1\'::uuid, \'dad3a37a-a9d5-0688-b515-7698acfd7aee\'::uuid, \'aba369f7-d2b2-8a90-98a0-a26feb7dc965\'::uuid) AND (__tree.tree_pk = emeis_core_scope.id)) ORDER BY ("__tree".tree_ordering) ASC""", + 'SELECT "emeis_core_scope"."created_at", "emeis_core_scope"."modified_at", "emeis_core_scope"."created_by_user_id", "emeis_core_scope"."metainfo", "emeis_core_scope"."id", "emeis_core_scope"."name", "emeis_core_scope"."full_name", "emeis_core_scope"."description", "emeis_core_scope"."is_active", "emeis_core_scope"."parent_id", "emeis_core_scope"."all_parents" FROM "emeis_core_scope" WHERE "emeis_core_scope"."id" IN (\'9336ebf2-5087-d91c-818e-e6e9ec29f8c1\'::uuid, \'aba369f7-d2b2-8a90-98a0-a26feb7dc965\'::uuid, \'dad3a37a-a9d5-0688-b515-7698acfd7aee\'::uuid) ORDER BY "emeis_core_scope"."name" ASC', 'SELECT "emeis_core_acl"."created_at", "emeis_core_acl"."modified_at", "emeis_core_acl"."created_by_user_id", "emeis_core_acl"."metainfo", "emeis_core_acl"."id", "emeis_core_acl"."user_id", "emeis_core_acl"."scope_id", "emeis_core_acl"."role_id" FROM "emeis_core_acl" WHERE "emeis_core_acl"."user_id" = \'9dd4e461-268c-8034-f5c8-564e155c67a6\'::uuid', 'SELECT "emeis_core_permission"."created_at", "emeis_core_permission"."modified_at", "emeis_core_permission"."created_by_user_id", "emeis_core_permission"."metainfo", "emeis_core_permission"."slug", "emeis_core_permission"."name", "emeis_core_permission"."description" FROM "emeis_core_permission" INNER JOIN "emeis_core_role_permissions" ON ("emeis_core_permission"."slug" = "emeis_core_role_permissions"."permission_id") WHERE "emeis_core_role_permissions"."role_id" = \'fund-executive-most\'', 'SELECT "emeis_core_acl"."created_at", "emeis_core_acl"."modified_at", "emeis_core_acl"."created_by_user_id", "emeis_core_acl"."metainfo", "emeis_core_acl"."id", "emeis_core_acl"."user_id", "emeis_core_acl"."scope_id", "emeis_core_acl"."role_id" FROM "emeis_core_acl" WHERE "emeis_core_acl"."user_id" = \'fb0e22c7-9ac7-5679-e988-1e6ba183b354\'::uuid', @@ -1433,43 +1181,7 @@ snapshots["test_api_list[ScopeViewSet] 1"] = { "queries": [ - """ - WITH RECURSIVE __rank_table( - - "id", - "parent_id", - "rank_order" - ) AS ( - SELECT "emeis_core_scope"."id", "emeis_core_scope"."parent_id", ROW_NUMBER() OVER (ORDER BY ("emeis_core_scope"."name" -> \'en\') ASC) AS "rank_order" FROM "emeis_core_scope" - ), - __tree ( - - "tree_depth", - "tree_path", - "tree_ordering", - "tree_pk" - ) AS ( - SELECT - - 0, - array[T.id], - array[T.rank_order], - T."id" - FROM __rank_table T - WHERE T."parent_id" IS NULL - - UNION ALL - - SELECT - - __tree.tree_depth + 1, - __tree.tree_path || T.id, - __tree.tree_ordering || T.rank_order, - T."id" - FROM __rank_table T - JOIN __tree ON T."parent_id" = __tree.tree_pk - ) - SELECT (__tree.tree_depth) AS "tree_depth", (__tree.tree_path) AS "tree_path", (__tree.tree_ordering) AS "tree_ordering", "emeis_core_scope"."parent_id", "emeis_core_scope"."created_at", "emeis_core_scope"."modified_at", "emeis_core_scope"."created_by_user_id", "emeis_core_scope"."metainfo", "emeis_core_scope"."id", "emeis_core_scope"."name", "emeis_core_scope"."full_name", "emeis_core_scope"."description", "emeis_core_scope"."is_active" FROM "emeis_core_scope" , "__tree" WHERE (__tree.tree_pk = emeis_core_scope.id) ORDER BY ("__tree".tree_ordering) ASC""", + 'SELECT "emeis_core_scope"."created_at", "emeis_core_scope"."modified_at", "emeis_core_scope"."created_by_user_id", "emeis_core_scope"."metainfo", "emeis_core_scope"."id", "emeis_core_scope"."name", "emeis_core_scope"."full_name", "emeis_core_scope"."description", "emeis_core_scope"."is_active", "emeis_core_scope"."parent_id", "emeis_core_scope"."all_parents" FROM "emeis_core_scope" ORDER BY ("emeis_core_scope"."name" -> \'en\') ASC', 'SELECT "emeis_core_acl"."created_at", "emeis_core_acl"."modified_at", "emeis_core_acl"."created_by_user_id", "emeis_core_acl"."metainfo", "emeis_core_acl"."id", "emeis_core_acl"."user_id", "emeis_core_acl"."scope_id", "emeis_core_acl"."role_id", "emeis_core_user"."password", "emeis_core_user"."last_login", "emeis_core_user"."created_at", "emeis_core_user"."modified_at", "emeis_core_user"."created_by_user_id", "emeis_core_user"."metainfo", "emeis_core_user"."id", "emeis_core_user"."username", "emeis_core_user"."first_name", "emeis_core_user"."last_name", "emeis_core_user"."email", "emeis_core_user"."phone", "emeis_core_user"."language", "emeis_core_user"."address", "emeis_core_user"."city", "emeis_core_user"."zip", "emeis_core_user"."is_active", "emeis_core_user"."date_joined", "emeis_core_role"."created_at", "emeis_core_role"."modified_at", "emeis_core_role"."created_by_user_id", "emeis_core_role"."metainfo", "emeis_core_role"."slug", "emeis_core_role"."name", "emeis_core_role"."description" FROM "emeis_core_acl" INNER JOIN "emeis_core_user" ON ("emeis_core_acl"."user_id" = "emeis_core_user"."id") INNER JOIN "emeis_core_role" ON ("emeis_core_acl"."role_id" = "emeis_core_role"."slug") WHERE "emeis_core_acl"."scope_id" IN (\'9dd4e461-268c-8034-f5c8-564e155c67a6\'::uuid, \'ea416ed0-759d-46a8-de58-f63a59077499\'::uuid, \'f561aaf6-ef0b-f14d-4208-bb46a4ccb3ad\'::uuid)', ], "request": { @@ -1686,80 +1398,8 @@ snapshots["test_api_patch[ACLViewSet] 1"] = { "queries": [ 'SELECT "emeis_core_acl"."created_at", "emeis_core_acl"."modified_at", "emeis_core_acl"."created_by_user_id", "emeis_core_acl"."metainfo", "emeis_core_acl"."id", "emeis_core_acl"."user_id", "emeis_core_acl"."scope_id", "emeis_core_acl"."role_id", "emeis_core_user"."password", "emeis_core_user"."last_login", "emeis_core_user"."created_at", "emeis_core_user"."modified_at", "emeis_core_user"."created_by_user_id", "emeis_core_user"."metainfo", "emeis_core_user"."id", "emeis_core_user"."username", "emeis_core_user"."first_name", "emeis_core_user"."last_name", "emeis_core_user"."email", "emeis_core_user"."phone", "emeis_core_user"."language", "emeis_core_user"."address", "emeis_core_user"."city", "emeis_core_user"."zip", "emeis_core_user"."is_active", "emeis_core_user"."date_joined", "emeis_core_role"."created_at", "emeis_core_role"."modified_at", "emeis_core_role"."created_by_user_id", "emeis_core_role"."metainfo", "emeis_core_role"."slug", "emeis_core_role"."name", "emeis_core_role"."description" FROM "emeis_core_acl" INNER JOIN "emeis_core_user" ON ("emeis_core_acl"."user_id" = "emeis_core_user"."id") INNER JOIN "emeis_core_role" ON ("emeis_core_acl"."role_id" = "emeis_core_role"."slug") WHERE "emeis_core_acl"."id" = \'f561aaf6-ef0b-f14d-4208-bb46a4ccb3ad\'::uuid LIMIT 21', - """ - WITH RECURSIVE __rank_table( - - "id", - "parent_id", - "rank_order" - ) AS ( - SELECT "emeis_core_scope"."id", "emeis_core_scope"."parent_id", ROW_NUMBER() OVER (ORDER BY "emeis_core_scope"."name" ASC) AS "rank_order" FROM "emeis_core_scope" - ), - __tree ( - - "tree_depth", - "tree_path", - "tree_ordering", - "tree_pk" - ) AS ( - SELECT - - 0, - array[T.id], - array[T.rank_order], - T."id" - FROM __rank_table T - WHERE T."parent_id" IS NULL - - UNION ALL - - SELECT - - __tree.tree_depth + 1, - __tree.tree_path || T.id, - __tree.tree_ordering || T.rank_order, - T."id" - FROM __rank_table T - JOIN __tree ON T."parent_id" = __tree.tree_pk - ) - SELECT (__tree.tree_depth) AS "tree_depth", (__tree.tree_path) AS "tree_path", (__tree.tree_ordering) AS "tree_ordering", "emeis_core_scope"."parent_id", "emeis_core_scope"."created_at", "emeis_core_scope"."modified_at", "emeis_core_scope"."created_by_user_id", "emeis_core_scope"."metainfo", "emeis_core_scope"."id", "emeis_core_scope"."name", "emeis_core_scope"."full_name", "emeis_core_scope"."description", "emeis_core_scope"."is_active" FROM "emeis_core_scope" , "__tree" WHERE ("emeis_core_scope"."id" IN (\'9336ebf2-5087-d91c-818e-e6e9ec29f8c1\'::uuid) AND (__tree.tree_pk = emeis_core_scope.id)) ORDER BY ("__tree".tree_ordering) ASC""", - """ - WITH RECURSIVE __rank_table( - - "id", - "parent_id", - "rank_order" - ) AS ( - SELECT "emeis_core_scope"."id", "emeis_core_scope"."parent_id", ROW_NUMBER() OVER (ORDER BY "emeis_core_scope"."name" ASC) AS "rank_order" FROM "emeis_core_scope" - ), - __tree ( - - "tree_depth", - "tree_path", - "tree_ordering", - "tree_pk" - ) AS ( - SELECT - - 0, - array[T.id], - array[T.rank_order], - T."id" - FROM __rank_table T - WHERE T."parent_id" IS NULL - - UNION ALL - - SELECT - - __tree.tree_depth + 1, - __tree.tree_path || T.id, - __tree.tree_ordering || T.rank_order, - T."id" - FROM __rank_table T - JOIN __tree ON T."parent_id" = __tree.tree_pk - ) - SELECT (__tree.tree_depth) AS "tree_depth", (__tree.tree_path) AS "tree_path", (__tree.tree_ordering) AS "tree_ordering", "emeis_core_scope"."parent_id", "emeis_core_scope"."created_at", "emeis_core_scope"."modified_at", "emeis_core_scope"."created_by_user_id", "emeis_core_scope"."metainfo", "emeis_core_scope"."id", "emeis_core_scope"."name", "emeis_core_scope"."full_name", "emeis_core_scope"."description", "emeis_core_scope"."is_active" FROM "emeis_core_scope" , "__tree" WHERE ("emeis_core_scope"."id" = \'9336ebf2-5087-d91c-818e-e6e9ec29f8c1\'::uuid AND (__tree.tree_pk = emeis_core_scope.id)) ORDER BY ("__tree".tree_ordering) ASC LIMIT 21""", + 'SELECT "emeis_core_scope"."created_at", "emeis_core_scope"."modified_at", "emeis_core_scope"."created_by_user_id", "emeis_core_scope"."metainfo", "emeis_core_scope"."id", "emeis_core_scope"."name", "emeis_core_scope"."full_name", "emeis_core_scope"."description", "emeis_core_scope"."is_active", "emeis_core_scope"."parent_id", "emeis_core_scope"."all_parents" FROM "emeis_core_scope" WHERE "emeis_core_scope"."id" IN (\'9336ebf2-5087-d91c-818e-e6e9ec29f8c1\'::uuid) ORDER BY "emeis_core_scope"."name" ASC', + 'SELECT "emeis_core_scope"."created_at", "emeis_core_scope"."modified_at", "emeis_core_scope"."created_by_user_id", "emeis_core_scope"."metainfo", "emeis_core_scope"."id", "emeis_core_scope"."name", "emeis_core_scope"."full_name", "emeis_core_scope"."description", "emeis_core_scope"."is_active", "emeis_core_scope"."parent_id", "emeis_core_scope"."all_parents" FROM "emeis_core_scope" WHERE "emeis_core_scope"."id" = \'9336ebf2-5087-d91c-818e-e6e9ec29f8c1\'::uuid LIMIT 21', 'SELECT "emeis_core_user"."password", "emeis_core_user"."last_login", "emeis_core_user"."created_at", "emeis_core_user"."modified_at", "emeis_core_user"."created_by_user_id", "emeis_core_user"."metainfo", "emeis_core_user"."id", "emeis_core_user"."username", "emeis_core_user"."first_name", "emeis_core_user"."last_name", "emeis_core_user"."email", "emeis_core_user"."phone", "emeis_core_user"."language", "emeis_core_user"."address", "emeis_core_user"."city", "emeis_core_user"."zip", "emeis_core_user"."is_active", "emeis_core_user"."date_joined" FROM "emeis_core_user" WHERE "emeis_core_user"."id" = \'9dd4e461-268c-8034-f5c8-564e155c67a6\'::uuid LIMIT 21', 'SELECT "emeis_core_role"."created_at", "emeis_core_role"."modified_at", "emeis_core_role"."created_by_user_id", "emeis_core_role"."metainfo", "emeis_core_role"."slug", "emeis_core_role"."name", "emeis_core_role"."description" FROM "emeis_core_role" WHERE "emeis_core_role"."slug" = \'fund-executive-most\' LIMIT 21', 'SELECT (1) AS "a" FROM "emeis_core_acl" WHERE ("emeis_core_acl"."role_id" = \'fund-executive-most\' AND "emeis_core_acl"."scope_id" = \'9336ebf2-5087-d91c-818e-e6e9ec29f8c1\'::uuid AND "emeis_core_acl"."user_id" = \'9dd4e461-268c-8034-f5c8-564e155c67a6\'::uuid AND NOT ("emeis_core_acl"."id" = \'f561aaf6-ef0b-f14d-4208-bb46a4ccb3ad\'::uuid)) LIMIT 1', @@ -1893,83 +1533,10 @@ snapshots["test_api_patch[ScopeViewSet] 1"] = { "queries": [ - """ - WITH RECURSIVE __rank_table( - - "id", - "parent_id", - "rank_order" - ) AS ( - SELECT "emeis_core_scope"."id", "emeis_core_scope"."parent_id", ROW_NUMBER() OVER (ORDER BY ("emeis_core_scope"."name" -> \'en\') ASC) AS "rank_order" FROM "emeis_core_scope" - ), - __tree ( - - "tree_depth", - "tree_path", - "tree_ordering", - "tree_pk" - ) AS ( - SELECT - - 0, - array[T.id], - array[T.rank_order], - T."id" - FROM __rank_table T - WHERE T."parent_id" IS NULL - - UNION ALL - - SELECT - - __tree.tree_depth + 1, - __tree.tree_path || T.id, - __tree.tree_ordering || T.rank_order, - T."id" - FROM __rank_table T - JOIN __tree ON T."parent_id" = __tree.tree_pk - ) - SELECT (__tree.tree_depth) AS "tree_depth", (__tree.tree_path) AS "tree_path", (__tree.tree_ordering) AS "tree_ordering", "emeis_core_scope"."parent_id", "emeis_core_scope"."created_at", "emeis_core_scope"."modified_at", "emeis_core_scope"."created_by_user_id", "emeis_core_scope"."metainfo", "emeis_core_scope"."id", "emeis_core_scope"."name", "emeis_core_scope"."full_name", "emeis_core_scope"."description", "emeis_core_scope"."is_active" FROM "emeis_core_scope" , "__tree" WHERE ("emeis_core_scope"."id" = \'9dd4e461-268c-8034-f5c8-564e155c67a6\'::uuid AND (__tree.tree_pk = emeis_core_scope.id)) ORDER BY ("__tree".tree_ordering) ASC LIMIT 21""", + 'SELECT "emeis_core_scope"."created_at", "emeis_core_scope"."modified_at", "emeis_core_scope"."created_by_user_id", "emeis_core_scope"."metainfo", "emeis_core_scope"."id", "emeis_core_scope"."name", "emeis_core_scope"."full_name", "emeis_core_scope"."description", "emeis_core_scope"."is_active", "emeis_core_scope"."parent_id", "emeis_core_scope"."all_parents" FROM "emeis_core_scope" WHERE "emeis_core_scope"."id" = \'9dd4e461-268c-8034-f5c8-564e155c67a6\'::uuid LIMIT 21', 'SELECT "emeis_core_acl"."created_at", "emeis_core_acl"."modified_at", "emeis_core_acl"."created_by_user_id", "emeis_core_acl"."metainfo", "emeis_core_acl"."id", "emeis_core_acl"."user_id", "emeis_core_acl"."scope_id", "emeis_core_acl"."role_id", "emeis_core_user"."password", "emeis_core_user"."last_login", "emeis_core_user"."created_at", "emeis_core_user"."modified_at", "emeis_core_user"."created_by_user_id", "emeis_core_user"."metainfo", "emeis_core_user"."id", "emeis_core_user"."username", "emeis_core_user"."first_name", "emeis_core_user"."last_name", "emeis_core_user"."email", "emeis_core_user"."phone", "emeis_core_user"."language", "emeis_core_user"."address", "emeis_core_user"."city", "emeis_core_user"."zip", "emeis_core_user"."is_active", "emeis_core_user"."date_joined", "emeis_core_role"."created_at", "emeis_core_role"."modified_at", "emeis_core_role"."created_by_user_id", "emeis_core_role"."metainfo", "emeis_core_role"."slug", "emeis_core_role"."name", "emeis_core_role"."description" FROM "emeis_core_acl" INNER JOIN "emeis_core_user" ON ("emeis_core_acl"."user_id" = "emeis_core_user"."id") INNER JOIN "emeis_core_role" ON ("emeis_core_acl"."role_id" = "emeis_core_role"."slug") WHERE "emeis_core_acl"."scope_id" IN (\'9dd4e461-268c-8034-f5c8-564e155c67a6\'::uuid)', - """ - WITH RECURSIVE __rank_table( - - "id", - "parent_id", - "rank_order" - ) AS ( - SELECT "emeis_core_scope"."id", "emeis_core_scope"."parent_id", ROW_NUMBER() OVER (ORDER BY "emeis_core_scope"."name" ASC) AS "rank_order" FROM "emeis_core_scope" - ), - __tree ( - - "tree_depth", - "tree_path", - "tree_ordering", - "tree_pk" - ) AS ( - SELECT - - 0, - array[T.id], - array[T.rank_order], - T."id" - FROM __rank_table T - WHERE T."parent_id" IS NULL - - UNION ALL - - SELECT - - __tree.tree_depth + 1, - __tree.tree_path || T.id, - __tree.tree_ordering || T.rank_order, - T."id" - FROM __rank_table T - JOIN __tree ON T."parent_id" = __tree.tree_pk - ) - SELECT (__tree.tree_depth) AS "tree_depth", (__tree.tree_path) AS "tree_path", (__tree.tree_ordering) AS "tree_ordering", "emeis_core_scope"."parent_id", "emeis_core_scope"."created_at", "emeis_core_scope"."modified_at", "emeis_core_scope"."created_by_user_id", "emeis_core_scope"."metainfo", "emeis_core_scope"."id", "emeis_core_scope"."name", "emeis_core_scope"."full_name", "emeis_core_scope"."description", "emeis_core_scope"."is_active" FROM "emeis_core_scope" , "__tree" WHERE ("emeis_core_scope"."parent_id" = \'9dd4e461-268c-8034-f5c8-564e155c67a6\'::uuid AND (__tree.tree_pk = emeis_core_scope.id)) ORDER BY ("__tree".tree_ordering) ASC""", - """UPDATE "emeis_core_scope" SET "parent_id" = NULL, "created_at" = \'2017-05-21T00:00:00+00:00\'::timestamptz, "modified_at" = \'2017-05-21T00:00:00+00:00\'::timestamptz, "created_by_user_id" = NULL, "metainfo" = \'{}\', "name" = hstore(ARRAY[\'en\',\'de\',\'fr\'], ARRAY[\'Pamela Horton\',\'\',\'\']), "full_name" = hstore(ARRAY[\'en\',\'de\',\'fr\'], ARRAY[\'Pamela Horton\',\'Pamela Horton\',\'Pamela Horton\']), "description" = hstore(ARRAY[\'en\',\'de\',\'fr\'], ARRAY[\'Effort meet relationship far. Option program interesting station. First where during teach country talk across. -Argue move appear catch toward help wind. Material minute ago get.\',\'\',\'\']), "is_active" = true WHERE "emeis_core_scope"."id" = \'9dd4e461-268c-8034-f5c8-564e155c67a6\'::uuid""", + """UPDATE "emeis_core_scope" SET "created_at" = \'2017-05-21T00:00:00+00:00\'::timestamptz, "modified_at" = \'2017-05-21T00:00:00+00:00\'::timestamptz, "created_by_user_id" = NULL, "metainfo" = \'{}\', "name" = hstore(ARRAY[\'en\',\'de\',\'fr\'], ARRAY[\'Pamela Horton\',\'\',\'\']), "full_name" = hstore(ARRAY[\'en\',\'de\',\'fr\'], ARRAY[\'Pamela Horton\',\'Pamela Horton\',\'Pamela Horton\']), "description" = hstore(ARRAY[\'en\',\'de\',\'fr\'], ARRAY[\'Effort meet relationship far. Option program interesting station. First where during teach country talk across. +Argue move appear catch toward help wind. Material minute ago get.\',\'\',\'\']), "is_active" = true, "parent_id" = NULL, "all_parents" = \'{}\'::uuid[] WHERE "emeis_core_scope"."id" = \'9dd4e461-268c-8034-f5c8-564e155c67a6\'::uuid""", ], "request": { "CONTENT_LENGTH": "614", diff --git a/emeis/core/tests/test_api.py b/emeis/core/tests/test_api.py index c1dcd07..946894e 100644 --- a/emeis/core/tests/test_api.py +++ b/emeis/core/tests/test_api.py @@ -221,15 +221,13 @@ def test_api_destroy(fixture, admin_client, snapshot, viewset): @pytest.mark.parametrize( - "set_new_parent_to, expect_error", + "set_new_parent_to", [ - ("self", "A node cannot be made a descendant of itself."), - ("child", "A node cannot be made a descendant of itself."), + ("self"), + ("child"), ], ) -def test_validate_circular_parents( - admin_client, scope_factory, set_new_parent_to, expect_error -): +def test_validate_circular_parents(admin_client, scope_factory, set_new_parent_to): scope_1 = scope_factory() scope_2 = scope_factory(parent=scope_1) scope_3 = scope_factory(parent=scope_2) @@ -251,4 +249,4 @@ def test_validate_circular_parents( with pytest.raises(Exception) as excinfo: admin_client.patch(url, data) - assert excinfo.match(expect_error) + assert excinfo.match("A node cannot be made a descendant or parent of itself") diff --git a/emeis/core/tests/test_create_scope_command.py b/emeis/core/tests/test_create_scope_command.py index 84824ba..fd420c1 100644 --- a/emeis/core/tests/test_create_scope_command.py +++ b/emeis/core/tests/test_create_scope_command.py @@ -156,7 +156,7 @@ def test_create_success( count_before = Scope.objects.count() call_command("create_scope", *cmd_args) - new_scope = Scope.objects.all().order_siblings_by("created_at").last() + new_scope = Scope.objects.all().order_by("created_at").last() new_acl = ACL.objects.all().order_by("created_at").last() def replace_expectations(in_str): diff --git a/emeis/core/tests/test_models.py b/emeis/core/tests/test_models.py index 1916692..738ce0b 100644 --- a/emeis/core/tests/test_models.py +++ b/emeis/core/tests/test_models.py @@ -41,7 +41,7 @@ def test_scope_model(db): parent_scope.parent = scope with pytest.raises(ValidationError) as excinfo: parent_scope.save() - assert excinfo.match("A node cannot be made a descendant of itself.") + assert excinfo.match("A node cannot be made a descendant or parent of itself") def test_scope_deletion(db, scope_factory): @@ -126,7 +126,7 @@ def test_scope_fullname_when_forced_language(db, language, scope_factory, settin parent=child, name={"de": "DE GRANDCHILD", "fr": "FR GRANDCHILD"} ) - # Trigger pre_save `set_full_name()` hook + # Trigger pre_save `set_full_name_and_parents()` hook grandchild.save() assert grandchild.full_name.de == "DE ROOT » DE CHILD » DE GRANDCHILD" assert grandchild.full_name.fr == "" @@ -189,7 +189,7 @@ def simple_tree_structure(db, scope_factory): (False, 3), ], ) -def test_scope_ancestors(db, simple_tree_structure, include_self, expect_count): +def test_scope_qs_ancestors(db, simple_tree_structure, include_self, expect_count): qs = Scope.objects.filter( pk__in=[ simple_tree_structure["sub2sub2"].pk, @@ -221,7 +221,7 @@ def test_scope_ancestors(db, simple_tree_structure, include_self, expect_count): (False, 4), ], ) -def test_scope_descendants(db, simple_tree_structure, include_self, expect_count): +def test_scope_qs_descendants(db, simple_tree_structure, include_self, expect_count): qs = Scope.objects.filter( pk__in=[simple_tree_structure["sub1sub1"].pk, simple_tree_structure["root2"].pk] ) @@ -262,6 +262,8 @@ def test_get_root(db, simple_tree_structure): == simple_tree_structure["root1"] ) + assert simple_tree_structure["root1"].get_root() == simple_tree_structure["root1"] + def test_all_roots(db, simple_tree_structure): qs1 = Scope.objects.filter( @@ -282,3 +284,71 @@ def test_all_roots(db, simple_tree_structure): assert qs2.count() == 2 assert qs2.filter(pk=simple_tree_structure["root1"].pk).exists() assert qs2.filter(pk=simple_tree_structure["root2"].pk).exists() + + +def test_scope_factory_all_parents(simple_tree_structure): + # This tests more the factory than anything else, but it's important we + # validate our assumptions to guarantee the other tests do the right thing + root1 = simple_tree_structure["root1"] + root2 = simple_tree_structure["root2"] + sub1sub1 = simple_tree_structure["sub1sub1"] + sub1sub2 = simple_tree_structure["sub1sub2"] + sub1sub1sub1 = simple_tree_structure["sub1sub1sub1"] + sub1sub1sub2 = simple_tree_structure["sub1sub1sub2"] + sub2sub1 = simple_tree_structure["sub2sub1"] + sub2sub2 = simple_tree_structure["sub2sub2"] + + assert root1.all_parents == [] + assert root2.all_parents == [] + assert sub1sub1.all_parents == [root1.pk] + assert sub1sub2.all_parents == [root1.pk] + assert sub1sub1sub1.all_parents == [root1.pk, sub1sub1.pk] + assert sub1sub1sub2.all_parents == [root1.pk, sub1sub1.pk] + assert sub2sub1.all_parents == [root2.pk] + assert sub2sub2.all_parents == [root2.pk] + + +def test_scope_ancestors(simple_tree_structure): + root1 = simple_tree_structure["root1"] + sub1sub1 = simple_tree_structure["sub1sub1"] + sub1sub1sub1 = simple_tree_structure["sub1sub1sub1"] + + assert root1.ancestors().count() == 0 + assert list(root1.ancestors(include_self=True)) == [root1] + assert root1 in root1.ancestors(include_self=True) + + assert list(sub1sub1sub1.ancestors()) == [root1, sub1sub1] + assert set(sub1sub1sub1.ancestors(include_self=True)) == set( + [ + sub1sub1, + sub1sub1sub1, + root1, + ] + ) + + +def test_scope_descendants(simple_tree_structure): + root1 = simple_tree_structure["root1"] + sub1sub1 = simple_tree_structure["sub1sub1"] + sub1sub2 = simple_tree_structure["sub1sub2"] + sub1sub1sub1 = simple_tree_structure["sub1sub1sub1"] + sub1sub1sub2 = simple_tree_structure["sub1sub1sub2"] + + assert set(root1.descendants()) == set( + [ + sub1sub1, + sub1sub2, + sub1sub1sub1, + sub1sub1sub2, + ] + ) + + assert set(root1.descendants(include_self=True)) == set( + [ + root1, + sub1sub1, + sub1sub2, + sub1sub1sub1, + sub1sub1sub2, + ] + ) diff --git a/emeis/core/views.py b/emeis/core/views.py index fa5046f..fb891e5 100644 --- a/emeis/core/views.py +++ b/emeis/core/views.py @@ -189,12 +189,7 @@ def _order_field_suffix_if_needed(field_name): new_ordering = [_order_field_suffix_if_needed(field) for field in ordering] - if qs.model is models.Scope: - # django-tree-queries only supports ordering within siblings, not across - # everything. And uses another method for it - qs = qs.order_siblings_by(*new_ordering) - else: - qs = qs.order_by(*new_ordering) + qs = qs.order_by(*new_ordering) return qs diff --git a/requirements-base.txt b/requirements-base.txt index b68a590..9c6080f 100644 --- a/requirements-base.txt +++ b/requirements-base.txt @@ -5,7 +5,6 @@ django-filter>=22.1,<=23.5 django-generic-api-permissions==0.2.0 django-localized-fields==6.7 django-postgres-extra==2.0.8 -django-tree-queries==0.19.0 djangorestframework==3.14.0 djangorestframework-jsonapi==6.1.0 mozilla-django-oidc==3.0.0