v = timezone.make_aware(v) if "_ctf" in self.request.query_params: self.request._ctf = { "%s__%s" % (m.group(1), m.group(2)): v } # contains should become icontains because we always # want it to do case-insensitive checks if m.group(2) == "contains": filters["%s__icontains" % flt] = v elif m.group(2) == "startswith": filters["%s__istartswith" % flt] = v # when the 'in' filters is found attempt to split the # provided search value into a list elif m.group(2) == "in": filters[k] = v.split(",") else: filters[k] = v elif k in field_names: # filter exact matches try: intyp = self.model._meta.get_field(k).get_internal_type() except: intyp = "CharField" if intyp == "ForeignKey": filters["%s_id" % k] = v elif intyp == "DateTimeField" or intyp == "DateField": filters["%s__startswith" % k] = v else: filters["%s__iexact" % k] = v
def get_queryset(self): """ Prepare the queryset """ qset = self.model.handleref.all() self.request.meta_response = {} if hasattr(self.serializer_class, "prepare_query"): try: qset, p_filters = self.serializer_class.prepare_query( qset, **self.request.query_params) except ValidationError as inst: raise RestValidationError({"detail": str(inst)}) except ValueError as inst: raise RestValidationError({"detail": str(inst)}) except TypeError as inst: raise RestValidationError({"detail": str(inst)}) except FieldError as inst: raise RestValidationError({"detail": "Invalid query"}) else: p_filters = {} try: since = int(float(self.request.query_params.get("since", 0))) except ValueError: raise RestValidationError({ "detail": "'since' needs to be a unix timestamp (epoch seconds)" }) try: skip = int(self.request.query_params.get("skip", 0)) except ValueError: raise RestValidationError( {"detail": "'skip' needs to be a number"}) try: limit = int(self.request.query_params.get("limit", 0)) except ValueError: raise RestValidationError( {"detail": "'limit' needs to be a number"}) try: depth = int(self.request.query_params.get("depth", 0)) except ValueError: raise RestValidationError( {"detail": "'depth' needs to be a number"}) field_names = dict([(fld.name, fld) for fld in self.model._meta.get_fields()] + self.serializer_class.queryable_relations()) date_fields = ["DateTimeField", "DateField"] # filters filters = {} for k, v in list(self.request.query_params.items()): v = unidecode.unidecode(v) if k[-3:] == "_id" and k not in field_names: k = k[:-3] xl = self.serializer_class.queryable_field_xl # only apply filter if the field actually exists and uses a # valid suffix m = re.match("^(.+)__(lt|lte|gt|gte|contains|startswith|in)$", k) # run queryable field translation # on the targeted field so that the filter is actually run on # a field that django orm is aware of - which in most cases is # identical to the serializer field anyways, but in some cases it # may need to be substituted if m: flt = xl(m.group(1)) k = k.replace(m.group(1), flt, 1) if flt[-3:] == "_id" and flt not in field_names: flt = flt[:-3] else: k = xl(k) flt = None # prepare db filters if m and flt in field_names: # filter by function provided in suffix try: intyp = field_names.get(flt).get_internal_type() except: intyp = "CharField" # for greater than date checks we want to force the time to 1 # msecond before midnight if intyp in date_fields: if m.group(2) in ["gt", "lte"]: if len(v) == 10: v = "%s 23:59:59.999" % v # convert to datetime and make tz aware try: v = DateTimeField().to_python(v) except ValidationError as inst: raise RestValidationError({"detail": str(inst[0])}) if timezone.is_naive(v): v = timezone.make_aware(v) if "_ctf" in self.request.query_params: self.request._ctf = { "{}__{}".format(m.group(1), m.group(2)): v } # contains should become icontains because we always # want it to do case-insensitive checks if m.group(2) == "contains": filters["%s__icontains" % flt] = v elif m.group(2) == "startswith": filters["%s__istartswith" % flt] = v # when the 'in' filters is found attempt to split the # provided search value into a list elif m.group(2) == "in": filters[k] = v.split(",") else: filters[k] = v elif k in field_names: # filter exact matches try: intyp = field_names.get(k).get_internal_type() except: intyp = "CharField" if intyp == "ForeignKey": filters["%s_id" % k] = v elif intyp == "DateTimeField" or intyp == "DateField": filters["%s__startswith" % k] = v else: filters["%s__iexact" % k] = v if filters: try: qset = qset.filter(**filters) except ValidationError as inst: raise RestValidationError({"detail": str(inst[0])}) except ValueError as inst: raise RestValidationError({"detail": str(inst[0])}) except TypeError as inst: raise RestValidationError({"detail": str(inst[0])}) except FieldError as inst: raise RestValidationError({"detail": "Invalid query"}) # check if request qualifies for a cache load filters.update(p_filters) api_cache = APICacheLoader(self, qset, filters) if api_cache.qualifies(): raise CacheRedirect(api_cache) if not self.kwargs: if since > 0: # .filter(status__in=["ok","deleted"]) qset = (qset.since( timestamp=datetime.datetime.fromtimestamp(since).replace( tzinfo=UTC()), deleted=True, ).order_by("updated").filter(status__in=["ok", "deleted"])) else: qset = qset.filter(status="ok") else: qset = qset.filter(status__in=["ok", "pending"]) if not self.kwargs: if limit > 0: qset = qset[skip:skip + limit] else: qset = qset[skip:] adrl = getattr(settings, "API_DEPTH_ROW_LIMIT", 250) row_count = qset.count() if adrl and depth > 0 and row_count > adrl: qset = qset[:adrl] self.request.meta_response["truncated"] = ( "Your search query (with depth %d) returned more than %d rows and has been truncated. Please be more specific in your filters, use the limit and skip parameters to page through the resultset or drop the depth parameter" % (depth, adrl)) if depth > 0 or self.kwargs: return self.serializer_class.prefetch_related( qset, self.request, is_list=(len(self.kwargs) == 0)) else: return qset