예제 #1
0
    def join_related(items):
        """
        Join dictionary keys to their value tuples.
        :type items: dict
        :return: list
        """
        output = []
        for key, value in items.items():
            if key is not "":
                output.extend([LOOKUP_SEP.join((key, field)) for field in value])
            else:
                output.extend(value)

        return output
예제 #2
0
    def _find_columns(self, model, prefix=None):
        """
        Find columns in the given model. Used to create the JS filter generator.

        :param model: The model to find stuff for.
        :param prefix: Query path prefix.
        :type prefix: str
        :return: Transport type for each filter; Title for each view.
        :rtype: dict[str, str | dict[int, str]], dict[str, str]
        """
        model_meta = model._meta
        prefix_non_empty = prefix if prefix is not None else ""
        extra_views = set()

        use_default_filter = prefix_non_empty not in self.query_related_filter\
            or self.query_related_filter[prefix_non_empty] == "*"

        if use_default_filter:
            # Default, i.e. everything available. This includes all possible filter fields, and view fields.
            fields = model_meta.get_all_field_names()
        else:
            # Only certain set of filter fields.
            # View fields are stored in extra_views, and added to the iterable set to get their titles.
            fields = self.query_related_filter[prefix_non_empty]

            # If view includes are defined, use them.
            if prefix_non_empty in self.view_related_filter:
                extra_views = set(self.view_related_filter[prefix_non_empty])

        # Convert to set, as fields should not be iterated twice, and extra_views may contain same names.
        fields = set(fields)

        # Iterate over union of fields and views.
        for field in fields | extra_views:
            if prefix is not None:
                full_name = LOOKUP_SEP.join((prefix, field))
            else:
                full_name = field

            field_object, mdl, direct, m2m = model_meta.get_field_by_name(field)

            if field_object.attname != field_object.name and field == field_object.attname:
                # As of Django 1.7, related fields results in two names returned from get_all_field_names():
                # 'rel' and 'rel_id', of which the 'rel_id' must be discarded to avoid dumping "the whole" database
                # into frontend. Such field objects have name == 'rel' and attname == 'rel_id'.
                continue

            if field not in fields:
                # Field is not defined in fields, it must be in views then.
                # Only the view title is added to title container.
                self._object_title(full_name, field_object)
                continue

            if isinstance(field_object, models.AutoField) or \
                    self._is_excluded(full_name, prefix_non_empty, field):
                self._add_title(field_object, prefix_non_empty, full_name)
                continue

            is_related = isinstance(field_object, related.RelatedField)
            if is_related:
                # FK, M2M, O2O
                self._find_related(field_object, full_name)
                continue

            is_reverse = isinstance(field_object, related.RelatedObject)
            if is_reverse:
                # TODO: No support for related objects (i.e. the related_name_set-things at other end of FK)
                continue

            self._add_column(field_object, full_name)
예제 #3
0
    def build_query_filter_from_spec(self, spec):
        """
        Assemble a django "Q" query filter object from a specification that consists
        of a possibly-nested list of query filter descriptions.  These descriptions
        themselves specify Django primitive query filters, along with boolean
        "and", "or", and "not" operators.  This format can be serialized and
        deserialized, allowing django queries to be composed client-side and
        sent across the wire using JSON.

        Each filter description is a list.  The first element of the list is always
        the filter operator name. This name is one of either django's filter
        operators, "eq" (a synonym for "exact"), or the boolean operators
        "and", "or", and "not".

        Primitive query filters have three elements:

        [filteroperator, fieldname, queryarg]

        "filteroperator" is a string name like "in", "range", "icontains", etc.
        "fieldname" is the django field being queried.  Any name that django
        accepts is allowed, including references to fields in foreign keys
        using the "__" syntax described in the django API reference.
        "queryarg" is the argument you'd pass to the `filter()` method in
        the Django database API.

        "and" and "or" query filters are lists that begin with the appropriate
        operator name, and include subfilters as additional list elements:

        ['or', [subfilter], ...]
        ['and', [subfilter], ...]

        "not" query filters consist of exactly two elements:

        ['not', [subfilter]]

        As a special case, the empty list "[]" or None return all elements.

        If field_mapping is specified, the field name provided in the spec
        is looked up in the field_mapping dictionary.  If there's a match,
        the result is subsitituted. Otherwise, the field name is used unchanged
        to form the query. This feature allows client-side programs to use
        "nice" names that can be mapped to more complex django names. If
        you decide to use this feature, you'll probably want to do a similar
        mapping on the field names being returned to the client.

        This function returns a Q object that can be used anywhere you'd like
        in the django query machinery.

        This function raises ValueError in case the query is malformed, or
        perhaps other errors from the underlying DB code.

        Example queries:

        ['and', ['contains', 'name', 'Django'], ['range', 'apps', [1, 4]]]
        ['not', ['in', 'tags', ['colors', 'shapes', 'animals']]]
        ['or', ['eq', 'id', 2], ['icontains', 'city', 'Boston']]
        """
        if spec is None or len(spec) == 0:
            return Q()
        cmd = spec[0]
        result_q = None

        if cmd == 'and' or cmd == 'or':
            # ["or",  [filter],[filter],[filter],...]
            # ["and", [filter],[filter],[filter],...]
            if len(spec) < 2:
                raise ValueError('"and" or "or" filters must have at least one subfilter')

            if cmd == 'and':
                q_op = lambda l, r: l & r
            else:
                q_op = lambda l, r: l | r

            for arg in spec[1:]:
                q_part = self.build_query_filter_from_spec(arg)
                if q_part is not None:
                    if result_q is None:
                        result_q = q_part
                    else:
                        result_q = q_op(result_q, q_part)

        elif cmd == 'not':
            # ["not", [query]]
            if len(spec) != 2:
                raise ValueError('"not" filters must have exactly one subfilter')
            q_part = self.build_query_filter_from_spec(spec[1])
            if q_part is not None:
                result_q = ~q_part

        else:
            # some other query, will be validated in the query machinery
            # ["cmd", "fieldname", "arg"]

            # provide an intuitive alias for exact field equality
            if cmd == 'eq':
                cmd = 'exact'

            if len(spec) != 3:
                raise ValueError('primitive filters must have two arguments (fieldname and query arg)')

            field_name = spec[1]
            value = spec[2]
            if self.field_mapping is not None:
                # see if the mapping contains an entry for the field_name
                # (for example, if you're mapping an external database name
                # to an internal django one).  If not, use the existing name.
                field_name = self.field_mapping.get(field_name, field_name)

            if field_name not in self._fields:  # "in self._query_excludes:
                # The field is excluded.
                raise ValueError("querybuilder: Trying to include an excluded field in query: {0!r}".format(field_name))

            field_def = self._fields[field_name]
            if field_def == "datetime":
                value = parse_datetime(value)
            elif field_def == "date":
                value = parse_date(value)
            elif field_def == "time":
                value = parse_time(value)

            # Q(lhs=kwarg) --> Q(field_name__cmd=kwarg)
            lhs = LOOKUP_SEP.join((field_name, cmd))  # str("%s%s%s" % (field_name, LOOKUP_SEP, cmd))
            kwarg = {lhs: value}
            result_q = Q(**kwarg)

        return result_q