From 9ca96cdf5670623fe600fd80fdd3f78facf6a1bd Mon Sep 17 00:00:00 2001 From: Ivan Ivanov Date: Mon, 10 May 2021 12:47:37 +0300 Subject: [PATCH 1/2] Allow inverted search of users e.g. only within members --- .../qfieldcloud/core/querysets_utils.py | 26 ++++++++++++++----- .../qfieldcloud/core/views/users_views.py | 6 +++-- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/docker-app/qfieldcloud/core/querysets_utils.py b/docker-app/qfieldcloud/core/querysets_utils.py index daaa3b581..59d0cd11e 100644 --- a/docker-app/qfieldcloud/core/querysets_utils.py +++ b/docker-app/qfieldcloud/core/querysets_utils.py @@ -1,4 +1,6 @@ import warnings +from functools import reduce +from operator import and_, or_ from django.db.models import Q from django.db.models.manager import BaseManager @@ -69,6 +71,7 @@ def get_users( organization: Organization = None, exclude_organizations: bool = False, exclude_teams: bool = False, + invert: bool = False, ) -> BaseManager: assert ( project is None or organization is None @@ -87,21 +90,32 @@ def get_users( if exclude_teams: users = users.exclude(user_type=User.TYPE_TEAM) + # one day conditions can be more than just pk check, please keep it for now + conditions = [] # exclude the already existing collaborators and the project owner if project: collaborator_ids = ProjectCollaborator.objects.filter( project=project ).values_list("collaborator", flat=True) - users = users.exclude(pk__in=collaborator_ids) - users = users.exclude(pk=project.owner.pk) + user_ids = [*collaborator_ids, project.owner.pk] + conditions = [Q(pk__in=user_ids)] # exclude the already existing members, the organization owner and the organization itself from the returned users - if organization: + elif organization: member_ids = OrganizationMember.objects.filter( organization=organization ).values_list("member", flat=True) - users = users.exclude(pk__in=member_ids) - users = users.exclude(pk=organization.organization_owner.pk) - users = users.exclude(pk=organization.user_ptr.pk) + user_ids = [ + *member_ids, + organization.organization_owner.pk, + organization.user_ptr.pk, + ] + conditions = [Q(pk__in=user_ids)] + + if conditions: + if invert: + users = users.filter(reduce(and_, [c for c in conditions])) + else: + users = users.exclude(reduce(or_, [c for c in conditions])) return users.order_by("-username") diff --git a/docker-app/qfieldcloud/core/views/users_views.py b/docker-app/qfieldcloud/core/views/users_views.py index e1c654254..2210011b1 100644 --- a/docker-app/qfieldcloud/core/views/users_views.py +++ b/docker-app/qfieldcloud/core/views/users_views.py @@ -56,14 +56,16 @@ def get_queryset(self): # TODO : are these GET paremters documented somewhere ? Shouldn't we use something # like django_filters.rest_framework.DjangoFilterBackend so they get auto-documented # in DRF's views, or is that supposedly done with swagger ? - exclude_organizations = bool(int(params.get("exclude_organizations", 0))) - exclude_teams = bool(int(params.get("exclude_teams", 0))) + exclude_organizations = bool(int(params.get("exclude_organizations") or 0)) + exclude_teams = bool(int(params.get("exclude_teams") or 0)) + invert = bool(int(params.get("invert") or 0)) return querysets_utils.get_users( query, project=project, organization=organization, exclude_organizations=exclude_organizations, exclude_teams=exclude_teams, + invert=invert, ) From 0716a2805074a71bb6738cdd37163f402dd99d6b Mon Sep 17 00:00:00 2001 From: Ivan Ivanov Date: Mon, 10 May 2021 15:11:38 +0300 Subject: [PATCH 2/2] Enforce team members to be organization members --- docker-app/qfieldcloud/core/models.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docker-app/qfieldcloud/core/models.py b/docker-app/qfieldcloud/core/models.py index f9a22d26a..9d6bed595 100644 --- a/docker-app/qfieldcloud/core/models.py +++ b/docker-app/qfieldcloud/core/models.py @@ -404,6 +404,14 @@ class Meta: limit_choices_to=models.Q(user_type=User.TYPE_USER), ) + def clean(self) -> None: + if not self.team.team_organization.members.filter(member=self.member): + raise ValidationError( + _("Cannot add team member that is not an organization member.") + ) + + return super().clean() + def __str__(self): return self.team.username + ": " + self.member.username