예제 #1
0
파일: filters.py 프로젝트: ziozzang/awx
    def filter_queryset(self, request, queryset, view):
        try:
            # Apply filters specified via query_params. Each entry in the lists
            # below is (negate, field, value).
            and_filters = []
            or_filters = []
            chain_filters = []
            role_filters = []
            search_filters = {}
            # Can only have two values: 'AND', 'OR'
            # If 'AND' is used, an iterm must satisfy all condition to show up in the results.
            # If 'OR' is used, an item just need to satisfy one condition to appear in results.
            search_filter_relation = 'OR'
            for key, values in request.query_params.lists():
                if key in self.RESERVED_NAMES:
                    continue

                # HACK: Make job event filtering by host name mostly work even
                # when not capturing job event hosts M2M.
                if queryset.model._meta.object_name == 'JobEvent' and key.startswith(
                        'hosts__name'):
                    key = key.replace('hosts__name', 'or__host__name')
                    or_filters.append((False, 'host__name__isnull', True))

                # Custom __int filter suffix (internal use only).
                q_int = False
                if key.endswith('__int'):
                    key = key[:-5]
                    q_int = True

                # RBAC filtering
                if key == 'role_level':
                    role_filters.append(values[0])
                    continue

                # Search across related objects.
                if key.endswith('__search'):
                    if values and ',' in values[0]:
                        search_filter_relation = 'AND'
                        values = reduce(lambda list1, list2: list1 + list2,
                                        [i.split(',') for i in values])
                    for value in values:
                        search_value, new_keys = self.value_to_python(
                            queryset.model, key, force_text(value))
                        assert isinstance(new_keys, list)
                        search_filters[search_value] = new_keys
                    continue

                # Custom chain__ and or__ filters, mutually exclusive (both can
                # precede not__).
                q_chain = False
                q_or = False
                if key.startswith('chain__'):
                    key = key[7:]
                    q_chain = True
                elif key.startswith('or__'):
                    key = key[4:]
                    q_or = True

                # Custom not__ filter prefix.
                q_not = False
                if key.startswith('not__'):
                    key = key[5:]
                    q_not = True

                # Make legacy v1 Job/Template fields work for backwards compatability
                # TODO: remove after API v1 deprecation period
                if queryset.model._meta.object_name in (
                        'JobTemplate', 'Job') and key in (
                            'credential', 'vault_credential',
                            'cloud_credential', 'network_credential'
                        ) or queryset.model._meta.object_name in (
                            'InventorySource',
                            'InventoryUpdate') and key == 'credential':
                    key = 'credentials'

                # Make legacy v1 Credential fields work for backwards compatability
                # TODO: remove after API v1 deprecation period
                #
                # convert v1 `Credential.kind` queries to `Credential.credential_type__pk`
                if queryset.model._meta.object_name == 'Credential' and key == 'kind':
                    key = key.replace('kind', 'credential_type')

                    if 'ssh' in values:
                        # In 3.2, SSH and Vault became separate credential types, but in the v1 API,
                        # they're both still "kind=ssh"
                        # under the hood, convert `/api/v1/credentials/?kind=ssh` to
                        # `/api/v1/credentials/?or__credential_type=<ssh_pk>&or__credential_type=<vault_pk>`
                        values = set(values)
                        values.add('vault')
                        values = list(values)
                        q_or = True

                    for i, kind in enumerate(values):
                        if kind == 'vault':
                            type_ = CredentialType.objects.get(kind=kind)
                        else:
                            type_ = CredentialType.from_v1_kind(kind)
                        if type_ is None:
                            raise ParseError(
                                _('cannot filter on kind %s') % kind)
                        values[i] = type_.pk

                # Convert value(s) to python and add to the appropriate list.
                for value in values:
                    if q_int:
                        value = int(value)
                    value, new_key = self.value_to_python(
                        queryset.model, key, value)
                    if q_chain:
                        chain_filters.append((q_not, new_key, value))
                    elif q_or:
                        or_filters.append((q_not, new_key, value))
                    else:
                        and_filters.append((q_not, new_key, value))

            # Now build Q objects for database query filter.
            if and_filters or or_filters or chain_filters or role_filters or search_filters:
                args = []
                for n, k, v in and_filters:
                    if n:
                        args.append(~Q(**{k: v}))
                    else:
                        args.append(Q(**{k: v}))
                for role_name in role_filters:
                    if not hasattr(queryset.model, 'accessible_pk_qs'):
                        raise ParseError(
                            _('Cannot apply role_level filter to this list because its model '
                              'does not use roles for access control.'))
                    args.append(
                        Q(pk__in=queryset.model.accessible_pk_qs(
                            request.user, role_name)))
                if or_filters:
                    q = Q()
                    for n, k, v in or_filters:
                        if n:
                            q |= ~Q(**{k: v})
                        else:
                            q |= Q(**{k: v})
                    args.append(q)
                if search_filters and search_filter_relation == 'OR':
                    q = Q()
                    for term, constrains in search_filters.items():
                        for constrain in constrains:
                            q |= Q(**{constrain: term})
                    args.append(q)
                elif search_filters and search_filter_relation == 'AND':
                    for term, constrains in search_filters.items():
                        q_chain = Q()
                        for constrain in constrains:
                            q_chain |= Q(**{constrain: term})
                        queryset = queryset.filter(q_chain)
                for n, k, v in chain_filters:
                    if n:
                        q = ~Q(**{k: v})
                    else:
                        q = Q(**{k: v})
                    queryset = queryset.filter(q)
                queryset = queryset.filter(*args).distinct()
            return queryset
        except (FieldError, FieldDoesNotExist, ValueError, TypeError) as e:
            raise ParseError(e.args[0])
        except ValidationError as e:
            raise ParseError(json.dumps(e.messages, ensure_ascii=False))
예제 #2
0
파일: filters.py 프로젝트: zeitounator/awx
    def filter_queryset(self, request, queryset, view):
        try:
            # Apply filters specified via query_params. Each entry in the lists
            # below is (negate, field, value).
            and_filters = []
            or_filters = []
            chain_filters = []
            role_filters = []
            search_filters = []
            for key, values in request.query_params.lists():
                if key in self.RESERVED_NAMES:
                    continue

                # HACK: Make job event filtering by host name mostly work even
                # when not capturing job event hosts M2M.
                if queryset.model._meta.object_name == 'JobEvent' and key.startswith(
                        'hosts__name'):
                    key = key.replace('hosts__name', 'or__host__name')
                    or_filters.append((False, 'host__name__isnull', True))

                # Custom __int filter suffix (internal use only).
                q_int = False
                if key.endswith('__int'):
                    key = key[:-5]
                    q_int = True

                # RBAC filtering
                if key == 'role_level':
                    role_filters.append(values[0])
                    continue

                # Search across related objects.
                if key.endswith('__search'):
                    for value in values:
                        for search_term in force_text(value).replace(
                                ',', ' ').split():
                            search_value, new_keys = self.value_to_python(
                                queryset.model, key, search_term)
                            assert isinstance(new_keys, list)
                            for new_key in new_keys:
                                search_filters.append((new_key, search_value))
                    continue

                # Custom chain__ and or__ filters, mutually exclusive (both can
                # precede not__).
                q_chain = False
                q_or = False
                if key.startswith('chain__'):
                    key = key[7:]
                    q_chain = True
                elif key.startswith('or__'):
                    key = key[4:]
                    q_or = True

                # Custom not__ filter prefix.
                q_not = False
                if key.startswith('not__'):
                    key = key[5:]
                    q_not = True

                # Make legacy v1 Job/Template fields work for backwards compatability
                # TODO: remove after API v1 deprecation period
                if queryset.model._meta.object_name in (
                        'JobTemplate',
                        'Job') and key in ('credential', 'vault_credential',
                                           'cloud_credential',
                                           'network_credential'):
                    key = 'credentials'

                # Make legacy v1 Credential fields work for backwards compatability
                # TODO: remove after API v1 deprecation period
                #
                # convert v1 `Credential.kind` queries to `Credential.credential_type__pk`
                if queryset.model._meta.object_name == 'Credential' and key == 'kind':
                    key = key.replace('kind', 'credential_type')

                    if 'ssh' in values:
                        # In 3.2, SSH and Vault became separate credential types, but in the v1 API,
                        # they're both still "kind=ssh"
                        # under the hood, convert `/api/v1/credentials/?kind=ssh` to
                        # `/api/v1/credentials/?or__credential_type=<ssh_pk>&or__credential_type=<vault_pk>`
                        values = set(values)
                        values.add('vault')
                        values = list(values)
                        q_or = True

                    for i, kind in enumerate(values):
                        if kind == 'vault':
                            type_ = CredentialType.objects.get(kind=kind)
                        else:
                            type_ = CredentialType.from_v1_kind(kind)
                        if type_ is None:
                            raise ParseError(
                                _('cannot filter on kind %s') % kind)
                        values[i] = type_.pk

                # Convert value(s) to python and add to the appropriate list.
                for value in values:
                    if q_int:
                        value = int(value)
                    value, new_key = self.value_to_python(
                        queryset.model, key, value)
                    if q_chain:
                        chain_filters.append((q_not, new_key, value))
                    elif q_or:
                        or_filters.append((q_not, new_key, value))
                    else:
                        and_filters.append((q_not, new_key, value))

            # Now build Q objects for database query filter.
            if and_filters or or_filters or chain_filters or role_filters or search_filters:
                args = []
                for n, k, v in and_filters:
                    if n:
                        args.append(~Q(**{k: v}))
                    else:
                        args.append(Q(**{k: v}))
                for role_name in role_filters:
                    args.append(
                        Q(pk__in=RoleAncestorEntry.objects.filter(
                            ancestor__in=request.user.roles.all(),
                            content_type_id=ContentType.objects.get_for_model(
                                queryset.model).id,
                            role_field=role_name).values_list(
                                'object_id').distinct()))
                if or_filters:
                    q = Q()
                    for n, k, v in or_filters:
                        if n:
                            q |= ~Q(**{k: v})
                        else:
                            q |= Q(**{k: v})
                    args.append(q)
                if search_filters:
                    q = Q()
                    for k, v in search_filters:
                        q |= Q(**{k: v})
                    args.append(q)
                for n, k, v in chain_filters:
                    if n:
                        q = ~Q(**{k: v})
                    else:
                        q = Q(**{k: v})
                    queryset = queryset.filter(q)
                queryset = queryset.filter(*args).distinct()
            return queryset
        except (FieldError, FieldDoesNotExist, ValueError, TypeError) as e:
            raise ParseError(e.args[0])
        except ValidationError as e:
            raise ParseError(json.dumps(e.messages, ensure_ascii=False))