def _aquifer_qs(request): """ We have a custom search which does a case insensitive substring of aquifer_name, exact match on aquifer_id, and also looks at an array of provided resources attachments of which we require one to be present if any are specified. The front-end doesn't use DjangoFilterBackend's querystring array syntax, preferring ?a=1,2 rather than ?a[]=1&a[]=2, so again we need a custom back-end implementation. @param request - the request object """ query = request.GET qs = Aquifer.objects.all() resources__section__code = query.get("resources__section__code") hydraulic = query.get('hydraulically_connected') search = query.get('search') # V2 changes to `and`-ing the filters by default unless "match_any" is explicitly set to 'true' match_any = query.get('match_any') == 'true' now = timezone.now() # build a list of filters from qs params filters = [] if hydraulic: filters.append(Q(subtype__code__in=serializers.HYDRAULIC_SUBTYPES)) # ignore missing and empty string for resources__section__code qs param if resources__section__code: for code in resources__section__code.split(','): filters.append(Q(resources__section__code=code)) if match_any: if len(filters) > 0: disjunction = filters.pop() # combine all disjunctions using `|` a.k.a. SQL `OR` for filter in filters: disjunction |= filter qs = qs.filter(disjunction) else: # calling .filter() one after another combines `Q`s using SQL `AND` for filter in filters: qs = qs.filter(filter) if search: # only search if the search query is set to something disjunction = Q(aquifer_name__icontains=search) # if a number is searched, assume it could be an Aquifer ID. if search.isdigit(): disjunction = disjunction | Q(pk=int(search)) qs = qs.filter(disjunction) # filter out any non-published aquifer if the user doesn't have the `aquifers_edit` perm if not request.user.groups.filter(name=AQUIFERS_EDIT_ROLE).exists(): qs = qs.filter(effective_date__lte=now, expiry_date__gt=now) qs = qs.select_related('demand', 'material', 'productivity', 'subtype', 'vulnerability') qs = qs.distinct() return qs
def get_filters(self, request, view): valid_params = self.get_valid_params(view) params = {self.map_param_name(view, k): self.parse_param_value(view, k, v) for k, v in request.query_params.items() if k in valid_params} filters = [] for field, values in params.items(): for config in values: filters.append(F("geo_distance", **{field: config[1], 'distance': config[0]})) return filters
def get_filters(self, filter_params): filters = [] for field, params in filter_params.items(): negate = params.pop("negate", False) if self.value_operators and field not in params: raise ParseError("No value to match for '{}'".format(field)) f = self.build_filter(field, **params) filters.append(~f if negate else f) return filters
def get_filters(self, request, view): params = self.get_params(request, view) options = self.get_options(view) filters = [] for param, value in params.iteritems(): field_config = options.get(param) for id in value.split(','): filter_config = deepcopy(field_config['config']) field = field_config.get('field') if 'field' in field_config else param cache = field_config.get('cache') if 'cache' in field_config else False filter_config['indexed_shape']['id'] = id filters.append(F("geo_shape", **{field: filter_config, '_cache': cache})) return filters
def filter_queryset(self, request, queryset, view): bins = self.get_options(view) filters = [] for bin_name, bin_options in bins.items(): if bin_name in request.query_params: options = [v.strip() for v in request.query_params.get(bin_name).split(',')] for o in options: try: filters.append(F("range", **bin_options[o])) except KeyError: raise ParseError("Invalid value '{}' for '{}'".format(o, bin_name)) if filters: return queryset.filter("bool", must=[F("bool", should=filters)]) return queryset
def build_filter(self, fields, filter_value_raw): filters = [] filter_parser = parser.build_filter_parser(fields.keys()) def require_text_fields(parser_fields, operator_name): for pf in parser_fields: if type(fields[pf.name]) not in (model_fields.TextField, model_fields.CharField): raise BadQuery( "The operator \"{0}\" is only allowed with text fields" .format(operator_name)) join_op = parser.LogicalOp('and') for q in filter_parser.parseString(filter_value_raw, parseAll=True).asList(): if isinstance(q, parser.Comparison): q_fields = q.fields left = q_fields[0] op = q.operator right = None if len(q_fields) > 1: right = F(q_fields[1].name) else: if len(q.values) != 0: right = self._value_cast(fields[left.name], q.values[0].value) # find the matching operator in djangos ORM syntax model_op = None negate = False if op.op == "=": model_op = "exact" elif op.op == "!=": model_op = "exact" negate = True elif op.op in (">", "gt"): model_op = "gt" elif op.op in (">=", "gte"): model_op = "gte" elif op.op in ("<", "lt"): model_op = "lt" elif op.op in ("<=", "lte"): model_op = "lte" elif op.op == "eq": negate = op.negate model_op = "exact" elif op.op == 'contains': negate = op.negate require_text_fields(q_fields, 'contains') model_op = 'contains' elif op.op == 'icontains': negate = op.negate require_text_fields(q_fields, 'icontains') model_op = 'icontains' elif op.op == 'startswith': negate = op.negate require_text_fields(q_fields, 'startswith') model_op = 'startswith' elif op.op == 'istartswith': negate = op.negate require_text_fields(q_fields, 'istartswith') model_op = 'istartswith' elif op.op == 'endswith': negate = op.negate require_text_fields(q_fields, 'endswith') model_op = 'endswith' elif op.op == 'iendswith': negate = op.negate require_text_fields(q_fields, 'iendswith') model_op = 'iendswith' elif op.op == 'isnull': negate = op.negate model_op = 'isnull' right = True # negation happens using ~ else: raise BadQuery("Unsupported operator: \"{0}\"".format( op.op)) f = Q(**{"{0}__{1}".format(left.name, model_op): right}) if negate: f = ~f # add the new filter to the existing filterset # "or" has precedence over "and" if join_op.op == 'or': filters[-1] = filters[-1] | f elif join_op.op == 'and': filters.append(f) else: raise BadQuery( "Unsupported logical operator \"{0}\"".format( join_op.op)) elif isinstance(q, parser.LogicalOp): join_op = q else: raise BadQuery("Unsupported element: \"{0}\"".format(type(q))) return filters