diff --git a/apps/accounts/models.py b/apps/accounts/models.py index c302d2e7..5d54fba7 100644 --- a/apps/accounts/models.py +++ b/apps/accounts/models.py @@ -34,6 +34,7 @@ OrganizationRelated, ) from apps.commons.models import GroupData +from apps.commons.queryset import MultipleIdsQuerySet from apps.newsfeed.models import Event, Instruction, News from apps.organizations.models import Organization from apps.projects.models import AbstractLocation, Project @@ -99,6 +100,8 @@ class PeopleGroup( The visibility setting of the group. """ + objects = MultipleIdsQuerySet.as_manager() + auto_translated_fields: list[str] = [ "name", "html:description", diff --git a/apps/commons/queryset.py b/apps/commons/queryset.py new file mode 100644 index 00000000..81e94c81 --- /dev/null +++ b/apps/commons/queryset.py @@ -0,0 +1,49 @@ +from collections import defaultdict +from typing import Self + +from django.contrib.postgres.fields import ArrayField +from django.db import models + + +class MultipleIdsQuerySet(models.QuerySet): + """queryset/manager to filter queryset by id or slug""" + + def _get_related_field(self, model: models.Model, field: str) -> models.Field: + """traverse fields query to get real field model""" + + acutal_model = model + *traverse, last = field.split("__") + for sub in traverse: + acutal_model = acutal_model._meta.get_field(sub).related_model + return acutal_model._meta.get_field(last) + + def build_identifiers_query(self, identifiers: tuple[str | int]) -> models.Q: + query = defaultdict(set) + + for identifier in identifiers: + field = self.model.get_id_field_name(identifier) + if field == "slug" and hasattr(self.model, "outdated_slugs"): + fields = (field, "outdated_slugs") + else: + fields = (field,) + + for field in fields: + query[field].add(str(identifier)) + + final_query = models.Q() + for field, values in query.items(): + field_cls = self._get_related_field(self.model, field) + if isinstance(field_cls, ArrayField): + lookup = "__contains" + else: + lookup = "__in" + + final_query |= models.Q(**{f"{field}{lookup}": list(values)}) + + return final_query + + def slug_or_id(self, identifier: str | int) -> Self: + return self.filter(self.build_identifiers_query((identifier,))) + + def slug_or_ids(self, identifiers: tuple[str | int]) -> Self: + return self.filter(self.build_identifiers_query(identifiers)) diff --git a/apps/organizations/tests/views/test_organization.py b/apps/organizations/tests/views/test_organization.py index 0828f1cf..d004da11 100644 --- a/apps/organizations/tests/views/test_organization.py +++ b/apps/organizations/tests/views/test_organization.py @@ -780,6 +780,28 @@ def test_people_groups_hierarchy_params(self): self.assertEqual(len(data["hierarchy"]), 1) self.assertEqual(data["hierarchy"][0]["name"], self.root_group.name) + def test_people_groups_hierarchy_slug_or_id(self): + organization = self.organization + user = self.get_parameterized_test_user(TestRoles.SUPERADMIN) + self.client.force_authenticate(user) + + url = reverse( + "Organization-people-groups-hierarchy", + args=(organization.code,), + ) + parent = self.root_group.children.all().first() + parent.outdated_slugs = [f"{parent.slug}-old"] + parent.save() + response = self.client.get(url, {"depth": 0, "parent": parent.id}) + results_id = response.json() + response = self.client.get(url, {"depth": 0, "parent": parent.slug}) + results_slug = response.json() + response = self.client.get(url, {"depth": 0, "parent": f"{parent.slug}-old"}) + results_outdated_slug = response.json() + + self.assertDictEqual(results_id, results_slug) + self.assertDictEqual(results_id, results_outdated_slug) + class ValidateOrganizationTestCase(JwtAPITestCase): @classmethod diff --git a/apps/organizations/views.py b/apps/organizations/views.py index 3ed76fc3..d654f567 100644 --- a/apps/organizations/views.py +++ b/apps/organizations/views.py @@ -372,7 +372,7 @@ def remove_member(self, request, *args, **kwargs): name="parent", description="people group parents to serializer", required=False, - type=int, + type=int | str, ), PeopleGroupModules.ApiParameter(), ], @@ -392,10 +392,9 @@ def get_people_groups_hierarchy(self, request, *args, **kwargs): # get root "organization" group if parent is not set if request.query_params.get("parent"): root_group = get_object_or_404( - request.user.get_people_group_queryset().filter( - organization=organization - ), - pk=request.query_params.get("parent"), + request.user.get_people_group_queryset() + .filter(organization=organization) + .slug_or_id(request.query_params.get("parent")) ) else: root_group = PeopleGroup.update_or_create_root(organization) diff --git a/locale/ca/LC_MESSAGES/django.po b/locale/ca/LC_MESSAGES/django.po index c5764688..6cea30d1 100644 --- a/locale/ca/LC_MESSAGES/django.po +++ b/locale/ca/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-03-05 20:01+0100\n" +"POT-Creation-Date: 2026-03-18 10:17+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -110,7 +110,7 @@ msgstr "No pots assignar aquest rol a un usuari" msgid "You cannot assign this role to a user : {role}" msgstr "No pots assignar aquest rol a un usuari: {role}" -#: apps/accounts/models.py:158 apps/projects/models.py:164 +#: apps/accounts/models.py:161 apps/projects/models.py:164 msgid "visibility" msgstr "visibilitat" diff --git a/locale/de/LC_MESSAGES/django.po b/locale/de/LC_MESSAGES/django.po index 6df26b7e..b57f3fd4 100644 --- a/locale/de/LC_MESSAGES/django.po +++ b/locale/de/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-03-05 20:01+0100\n" +"POT-Creation-Date: 2026-03-18 10:17+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -112,7 +112,7 @@ msgstr "Sie können diese Rolle keinem Benutzer zuweisen" msgid "You cannot assign this role to a user : {role}" msgstr "Sie können diese Rolle keinem Benutzer zuweisen: {role}" -#: apps/accounts/models.py:158 apps/projects/models.py:164 +#: apps/accounts/models.py:161 apps/projects/models.py:164 msgid "visibility" msgstr "Sichtbarkeit" diff --git a/locale/en/LC_MESSAGES/django.po b/locale/en/LC_MESSAGES/django.po index 8d43261d..de65f8d1 100644 --- a/locale/en/LC_MESSAGES/django.po +++ b/locale/en/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-03-05 20:01+0100\n" +"POT-Creation-Date: 2026-03-18 10:17+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -108,7 +108,7 @@ msgstr "" msgid "You cannot assign this role to a user : {role}" msgstr "" -#: apps/accounts/models.py:158 apps/projects/models.py:164 +#: apps/accounts/models.py:161 apps/projects/models.py:164 msgid "visibility" msgstr "" diff --git a/locale/es/LC_MESSAGES/django.po b/locale/es/LC_MESSAGES/django.po index 5bff55b3..6f6c5efb 100644 --- a/locale/es/LC_MESSAGES/django.po +++ b/locale/es/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-03-05 20:01+0100\n" +"POT-Creation-Date: 2026-03-18 10:17+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -110,7 +110,7 @@ msgstr "No puedes asignar este rol a un usuario" msgid "You cannot assign this role to a user : {role}" msgstr "No puedes asignar este rol a un usuario: {role}" -#: apps/accounts/models.py:158 apps/projects/models.py:164 +#: apps/accounts/models.py:161 apps/projects/models.py:164 msgid "visibility" msgstr "visibilidad" diff --git a/locale/et/LC_MESSAGES/django.po b/locale/et/LC_MESSAGES/django.po index b6b95bd8..3f34af90 100644 --- a/locale/et/LC_MESSAGES/django.po +++ b/locale/et/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-03-05 20:01+0100\n" +"POT-Creation-Date: 2026-03-18 10:17+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -110,7 +110,7 @@ msgstr "Sa ei saa seda rolli kasutajale määrata" msgid "You cannot assign this role to a user : {role}" msgstr "Sa ei saa seda rolli kasutajale määrata: {role}" -#: apps/accounts/models.py:158 apps/projects/models.py:164 +#: apps/accounts/models.py:161 apps/projects/models.py:164 msgid "visibility" msgstr "nähtavus" diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index e74d24a8..cc5805df 100644 --- a/locale/fr/LC_MESSAGES/django.po +++ b/locale/fr/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-03-05 20:01+0100\n" +"POT-Creation-Date: 2026-03-18 10:17+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -112,7 +112,7 @@ msgstr "Vous ne pouvez pas assigner ce rôle à un·e utilisateur·ice" msgid "You cannot assign this role to a user : {role}" msgstr "Vous ne pouvez pas assigner ce rôle à un·e utilisateur·ice : {role}" -#: apps/accounts/models.py:158 apps/projects/models.py:164 +#: apps/accounts/models.py:161 apps/projects/models.py:164 msgid "visibility" msgstr "visibilité" diff --git a/locale/nl/LC_MESSAGES/django.po b/locale/nl/LC_MESSAGES/django.po index 367814e0..c162123c 100644 --- a/locale/nl/LC_MESSAGES/django.po +++ b/locale/nl/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-03-05 20:01+0100\n" +"POT-Creation-Date: 2026-03-18 10:17+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -112,7 +112,7 @@ msgstr "Je kunt deze rol niet toewijzen aan een gebruiker" msgid "You cannot assign this role to a user : {role}" msgstr "Je kunt deze rol niet toewijzen aan een gebruiker: {role}" -#: apps/accounts/models.py:158 apps/projects/models.py:164 +#: apps/accounts/models.py:161 apps/projects/models.py:164 msgid "visibility" msgstr "zichtbaarheid"