def make_query(params_dict):
    """Make a query from a dictionary of parameters, like a request form."""
    query_dict = {}
    groupby_criteria = getUtility(IGroupByCriteria).groupby
    cat = api.portal.get_tool("portal_catalog")

    for val in groupby_criteria.values():
        idx = val["index"]
        if idx in params_dict:
            crit = params_dict.get(idx) or EMPTY_MARKER
            cat_idx = cat._catalog.indexes.get(idx)

            idx_mod = val.get("index_modifier", None)
            crit = idx_mod(crit) if idx_mod else safe_decode(crit)

            # filter operator
            op = params_dict.get(idx + "_op", None)
            idx_has_operator = "operator" in getattr(
                cat_idx,
                "query_options",
                [
                    "query",
                ],
            )  # noqa: E501

            if op is None or not idx_has_operator:
                # add filter query
                query_dict[idx] = {"query": crit}
            else:
                if op not in ["and", "or"]:
                    op = "or"
                # add filter query
                query_dict[idx] = {"operator": op, "query": crit}

    for idx in GEOLOC_IDX:
        if idx in params_dict:
            # lat/lng query has to be float values
            try:
                query_dict[idx] = dict(
                    query=[
                        float(params_dict[idx]["query"][0]),
                        float(params_dict[idx]["query"][1]),
                    ],
                    range=params_dict[idx]["range"],
                )
            except (ValueError, TypeError):
                logger.warning("Could not apply lat/lng values to filter: %s",
                               params_dict[idx])

    if TEXT_IDX in params_dict and params_dict.get(TEXT_IDX):
        safe_text = safe_decode(params_dict.get(TEXT_IDX))
        clean_searchable_text = sanitise_search_query(safe_text)
        query_dict[TEXT_IDX] = clean_searchable_text

    if "sort_on" in params_dict:
        query_dict["sort_on"] = params_dict["sort_on"]
        query_dict["sort_order"] = params_dict.get("sort_order", "ascending")

    return query_dict
Exemple #2
0
def make_query(params_dict):
    """Make a query from a dictionary of parameters, like a request form.
    """
    query_dict = {}
    groupby_criteria = getUtility(IGroupByCriteria).groupby
    cat = api.portal.get_tool('portal_catalog')

    for val in groupby_criteria.values():
        idx = val['index']
        if idx in params_dict:
            crit = params_dict.get(idx) or EMPTY_MARKER
            cat_idx = cat._catalog.indexes.get(idx)

            idx_mod = val.get('index_modifier', None)
            crit = idx_mod(crit) if idx_mod else safe_decode(crit)

            # filter operator
            op = params_dict.get(idx + '_op', None)
            idx_has_operator = 'operator' in getattr(cat_idx, 'query_options',
                                                     [
                                                         'query',
                                                     ])  # noqa: E501

            if op is None or not idx_has_operator:
                # add filter query
                query_dict[idx] = {'query': crit}
            else:
                if op not in ['and', 'or']:
                    op = 'or'
                # add filter query
                query_dict[idx] = {'operator': op, 'query': crit}

    for idx in GEOLOC_IDX:
        if idx in params_dict:
            # lat/lng query has to be float values
            try:
                query_dict[idx] = dict(query=[
                    float(params_dict[idx]['query'][0]),
                    float(params_dict[idx]['query'][1]),
                ],
                                       range=params_dict[idx]['range'])
            except (ValueError, TypeError):
                logger.warning("Could not apply lat/lng values to filter: %s",
                               params_dict[idx])

    if TEXT_IDX in params_dict and params_dict.get(TEXT_IDX):
        query_dict[TEXT_IDX] = safe_decode(params_dict.get(TEXT_IDX))

    if 'sort_on' in params_dict:
        query_dict['sort_on'] = params_dict['sort_on']
        query_dict['sort_order'] = params_dict.get('sort_order', 'ascending')

    return query_dict
Exemple #3
0
def _build_option(filter_value, url, current_idx_value, groupby_options):
    idx = groupby_options["index"]
    # Optional modifier to set title from filter value
    display_modifier = groupby_options.get("display_modifier", None)
    # CSS modifier to set class on filter item
    css_modifier = groupby_options.get("css_modifier", None)

    # Set title from filter value with modifications,
    # e.g. uuid to title
    title = filter_value
    if filter_value is not EMPTY_MARKER and callable(display_modifier):
        title = display_modifier(filter_value, idx)
        title = safe_decode(title)

    # Set selected state
    selected = filter_value in current_idx_value
    css_class = "filterItem {0}{1} {2}".format(
        "filter-" + idnormalizer.normalize(filter_value),
        " selected" if selected else "",
        css_modifier(filter_value) if css_modifier else "",
    )

    return {
        "title": title,
        "url": url,
        "value": filter_value,
        "css_class": css_class,
        "count": 1,
        "selected": selected,
    }
def qs(result, index):
    url = get_data_by_val(result, index)["url"]
    _, _, _, _, query, _ = urlparse(url)
    result = parse_qs(query)
    del result["collectionfilter"]
    # Quick hack to get single values back from being lists
    result.update(dict([(k, v[0]) for k, v in result.items() if len(v) == 1]))
    return safe_decode(result)
Exemple #5
0
def make_query(params_dict):
    """Make a query from a dictionary of parameters, like a request form.
    """
    query_dict = {}
    groupby_criteria = getUtility(IGroupByCriteria).groupby
    for val in groupby_criteria.values():
        idx = val['index']
        if idx in params_dict:
            crit = params_dict.get(idx) or EMPTY_MARKER

            idx_mod = val.get('index_modifier', None)
            crit = idx_mod(crit) if idx_mod else safe_decode(crit)

            # filter operator
            op = params_dict.get(idx + '_op', None)
            if op is None:
                # add filter query
                query_dict[idx] = {'query': crit}
            else:
                if op not in ['and', 'or']:
                    op = 'or'
                # add filter query
                query_dict[idx] = {'operator': op, 'query': crit}

    for idx in GEOLOC_IDX:
        if idx in params_dict:
            # lat/lng query has to be float values
            try:
                query_dict[idx] = dict(query=[
                    float(params_dict[idx]['query'][0]),
                    float(params_dict[idx]['query'][1]),
                ],
                                       range=params_dict[idx]['range'])
            except (ValueError, TypeError):
                logger.warning("Could not apply lat/lng values to filter: %s",
                               params_dict[idx])

    if TEXT_IDX in params_dict and params_dict.get(TEXT_IDX):
        query_dict[TEXT_IDX] = safe_decode(params_dict.get(TEXT_IDX))

    return query_dict
Exemple #6
0
 def ajax_url(self):
     # Recursively transform all to unicode
     request_params = safe_decode(self.top_request.form)
     urlquery = base_query(request_params, extra_ignores=['SearchableText'])
     query_param = urlencode(safe_encode(urlquery), doseq=True)
     ajax_url = u'/'.join([
         it for it in [
             self.collection.getURL(), self.settings.view_name, '?' +
             query_param if query_param else None
         ] if it
     ])
     return ajax_url
    def is_available(self):
        if not self.settings.hide_if_empty:
            return True

        if self.settings.narrow_down:
            groupby_criteria = getUtility(IGroupByCriteria).groupby
            idx = groupby_criteria[self.settings.group_by]['index']
            request_params = safe_decode(self.top_request.form)
            current_idx_value = safe_iterable(request_params.get(idx))
            if current_idx_value:
                return True

        results = self.results()
        return not (results is None or len(results) <= 2
                    )  # 2 becayse we include "All"
Exemple #8
0
        def locations(self):
            custom_query = {}  # Additional query to filter the collection

            collection = uuidToObject(self.settings.target_collection)
            if not collection:
                return None

            # Recursively transform all to unicode
            request_params = safe_decode(self.top_request.form or {})

            # Get all collection results with additional filter
            # defined by urlquery
            custom_query = base_query(request_params)
            custom_query = make_query(custom_query)
            return ICollection(collection).results(batch=False,
                                                   brains=True,
                                                   custom_query=custom_query)
Exemple #9
0
def get_filter_items(target_collection,
                     group_by,
                     filter_type=DEFAULT_FILTER_TYPE,
                     narrow_down=False,
                     show_count=False,
                     view_name='',
                     cache_enabled=True,
                     request_params=None):
    request_params = request_params or {}
    custom_query = {}  # Additional query to filter the collection

    collection = uuidToObject(target_collection)
    if not collection or not group_by:
        return None
    collection_url = collection.absolute_url()

    # Recursively transform all to unicode
    request_params = safe_decode(request_params)

    # Support for the Event Listing view from plone.app.event
    collection_layout = collection.getLayout()
    default_view = collection.restrictedTraverse(collection_layout)
    if isinstance(default_view, EventListing):
        mode = request_params.get('mode', 'future')
        date = request_params.get('date', None)
        date = guess_date_from(date) if date else None
        start, end = start_end_from_mode(mode, date, collection)
        start, end = _prepare_range(collection, start, end)
        custom_query.update(start_end_query(start, end))
        # TODO: expand events. better yet, let collection.results
        #       do that

    # Get index in question and the current filter value of this index, if set.
    groupby_criteria = getUtility(IGroupByCriteria).groupby
    idx = groupby_criteria[group_by]['index']
    current_idx_value = safe_iterable(request_params.get(idx))

    extra_ignores = []
    if not narrow_down:
        # Additive filtering is about adding other filter values of the same
        # index.
        extra_ignores = [idx, idx + '_op']
    urlquery = base_query(request_params, extra_ignores)

    # Get all collection results with additional filter defined by urlquery
    custom_query.update(urlquery)
    custom_query = make_query(custom_query)
    catalog_results = ICollection(collection).results(
        batch=False, brains=True, custom_query=custom_query)
    if narrow_down and show_count:
        # we need the extra_ignores to get a true count
        # even when narrow_down filters the display of indexed values
        # count_query allows us to do that true count
        count_query = {}
        count_urlquery = base_query(request_params, [idx, idx + '_op'])
        count_query.update(count_urlquery)
        catalog_results_fullcount = ICollection(collection).results(
            batch=False, brains=True, custom_query=count_query)
    if not catalog_results:
        return None

    # Attribute name for getting filter value from brain
    metadata_attr = groupby_criteria[group_by]['metadata']
    # Optional modifier to set title from filter value
    display_modifier = groupby_criteria[group_by].get('display_modifier', None)
    # CSS modifier to set class on filter item
    css_modifier = groupby_criteria[group_by].get('css_modifier', None)
    # Value blacklist
    value_blacklist = groupby_criteria[group_by].get('value_blacklist', None)
    # Allow value_blacklist to be callables for runtime-evaluation
    value_blacklist = value_blacklist() if callable(
        value_blacklist) else value_blacklist  # noqa
    # fallback to title sorted values
    sort_key_function = groupby_criteria[group_by].get(
        'sort_key_function', lambda it: it['title'].lower())

    grouped_results = {}
    for brain in catalog_results:

        # Get filter value
        val = getattr(brain, metadata_attr, None)
        if callable(val):
            val = val()
        # decode it to unicode
        val = safe_decode(val)
        # Make sure it's iterable, as it's the case for e.g. the subject index.
        val = safe_iterable(val)

        for filter_value in val:
            if filter_value is None or isinstance(filter_value, Missing):
                continue
            if value_blacklist and filter_value in value_blacklist:
                # Do not include blacklisted
                continue
            if filter_value in grouped_results:
                # Add counter, if filter value is already present
                grouped_results[filter_value]['count'] += 1
                continue

            # Set title from filter value with modifications,
            # e.g. uuid to title
            title = filter_value
            if filter_value is not EMPTY_MARKER and callable(display_modifier):
                title = safe_decode(display_modifier(filter_value))

            # Build filter url query
            _urlquery = urlquery.copy()
            # Allow deselection
            if filter_value in current_idx_value:
                _urlquery[idx] = [
                    it for it in current_idx_value if it != filter_value
                ]
            elif filter_type != 'single':
                # additive filter behavior
                _urlquery[idx] = current_idx_value + [filter_value]
                _urlquery[idx + '_op'] = filter_type  # additive operator
            else:
                _urlquery[idx] = filter_value

            query_param = urlencode(safe_encode(_urlquery), doseq=True)
            url = '/'.join([
                it for it in [
                    collection_url, view_name, '?' +
                    query_param if query_param else None
                ] if it
            ])

            # Set selected state
            selected = filter_value in current_idx_value
            css_class = 'filterItem {0}{1} {2}'.format(
                'filter-' + idnormalizer.normalize(filter_value),
                ' selected' if selected else '',
                css_modifier(filter_value) if css_modifier else '',
            )

            grouped_results[filter_value] = {
                'title': title,
                'url': url,
                'value': filter_value,
                'css_class': css_class,
                'count': 1,
                'selected': selected
            }

    # Entry to clear all filters
    urlquery_all = {
        k: v
        for k, v in list(urlquery.items()) if k not in (idx, idx + '_op')
    }
    if narrow_down and show_count:
        catalog_results = catalog_results_fullcount
    ret = [{
        'title':
        translate(_('subject_all', default=u'All'), context=getRequest()),
        'url':
        u'{0}/?{1}'.format(collection_url,
                           urlencode(safe_encode(urlquery_all), doseq=True)),
        'value':
        'all',
        'css_class':
        'filterItem filter-all',
        'count':
        len(catalog_results),
        'selected':
        idx not in request_params
    }]

    grouped_results = list(grouped_results.values())

    if callable(sort_key_function):
        grouped_results = sorted(grouped_results, key=sort_key_function)

    ret += grouped_results

    return ret
from Products.CMFPlone.UnicodeSplitter.config import rxGlob_U

try:
    from Products.CMFPlone.browser.search import quote
except ImportError:
    # This fix was not in Plone 5.0.x
    def quote(term):
        # The terms and, or and not must be wrapped in quotes to avoid
        # being parsed as logical query atoms.
        if term.lower() in ("and", "or", "not"):
            term = '"%s"' % term
        return term


logger = getLogger("collective.collectionfilter")
ENCODED_BAD_CHARS = safe_decode(BAD_CHARS)


def sanitise_search_query(query):
    if not rxGlob_U.findall(query):
        return u""
    for char in ENCODED_BAD_CHARS:
        query = query.replace(char, " ")
    clean_query = [quote(token) for token in query.split()]
    clean_query = quote_chars(clean_query)
    return u" ".join(clean_query)


def make_query(params_dict):
    """Make a query from a dictionary of parameters, like a request form."""
    query_dict = {}
Exemple #11
0
def get_filter_items(
    target_collection,
    group_by,
    filter_type=DEFAULT_FILTER_TYPE,
    narrow_down=False,
    show_count=False,
    view_name="",
    cache_enabled=True,
    request_params=None,
    content_selector="",
    reverse=False,
):
    request_params = request_params or {}
    custom_query = {}  # Additional query to filter the collection

    collection = uuidToObject(target_collection)
    if not collection or not group_by:
        return None
    collection_url = collection.absolute_url()
    option_url = "/".join([it for it in [collection_url, view_name] if it])
    collection = ICollectionish(collection).selectContent(content_selector)
    if (
        collection is None or not collection.content_selector
    ):  # e.g. when no listing tile
        return None

    # Recursively transform all to unicode
    request_params = safe_decode(request_params)
    # Things break if sort_order is not a str
    if six.PY2 and "sort_order" in request_params:
        request_params["sort_order"] = str(request_params["sort_order"])

    # Get index in question and the current filter value of this index, if set.
    groupby_criteria = getUtility(IGroupByCriteria).groupby
    idx = groupby_criteria[group_by]["index"]
    current_idx_value = safe_iterable(request_params.get(idx))

    # Additive filtering is about adding other filter values of the same index.
    extra_ignores = [] if narrow_down else [idx, idx + "_op"]

    urlquery = base_query(request_params, extra_ignores)

    # Get all collection results with additional filter defined by urlquery
    custom_query.update(urlquery)
    custom_query = make_query(custom_query)

    catalog_results = collection.results(custom_query, request_params)
    if narrow_down and show_count:
        # we need the extra_ignores to get a true count
        # even when narrow_down filters the display of indexed values
        # count_query allows us to do that true count
        count_query = {}
        count_urlquery = base_query(request_params, [idx, idx + "_op"])
        count_query.update(count_urlquery)
        catalog_results_fullcount = collection.results(count_query, request_params)
    if not catalog_results:
        return None

    # Attribute name for getting filter value from brain
    metadata_attr = groupby_criteria[group_by]["metadata"]
    # Value blacklist
    value_blacklist = groupby_criteria[group_by].get("value_blacklist", None) or []
    # Allow value_blacklist to be callables for runtime-evaluation
    value_blacklist = (
        value_blacklist() if callable(value_blacklist) else value_blacklist
    )
    # fallback to title sorted values
    sort_key_function = groupby_criteria[group_by].get(
        "sort_key_function", lambda it: it["title"].lower()
    )

    grouped_results = {}
    for brain in catalog_results:

        # Get filter value
        val = getattr(brain, metadata_attr, None)
        val = val() if callable(val) else val
        # decode it to unicode
        val = safe_decode(val)
        # Make sure it's iterable, as it's the case for e.g. the subject index.
        val = safe_iterable(val)

        for filter_value in val:
            if (
                filter_value is None
                or isinstance(filter_value, Missing)
                or filter_value in value_blacklist
            ):
                continue
            if type(filter_value) == int:
                # if indexed value is an integer, convert to string
                filter_value = str(filter_value)
            if filter_value in grouped_results:
                # Add counter, if filter value is already present
                grouped_results[filter_value]["count"] += 1
                continue

            url = _build_url(
                collection_url=option_url,
                urlquery=urlquery,
                filter_value=filter_value,
                current_idx_value=current_idx_value,
                idx=idx,
                filter_type=filter_type,
            )
            grouped_results[filter_value] = _build_option(
                filter_value=filter_value,
                url=url,
                current_idx_value=current_idx_value,
                groupby_options=groupby_criteria[group_by],
            )

    # Entry to clear all filters
    urlquery_all = {
        k: v for k, v in list(urlquery.items()) if k not in (idx, idx + "_op")
    }
    if narrow_down and show_count:
        # TODO: catalog_results_fullcount is possibly undefined
        catalog_results = catalog_results_fullcount
    ret = [
        {
            "title": translate(
                _("subject_all", default=u"All"),
                context=getRequest(),
                target_language=plone.api.portal.get_current_language(),
            ),
            "url": u"{0}/?{1}".format(
                collection_url, urlencode(safe_encode(urlquery_all), doseq=True)
            ),
            "value": "all",
            "css_class": "filterItem filter-all",
            "count": len(catalog_results),
            "selected": idx not in request_params,
        }
    ]

    grouped_results = list(grouped_results.values())

    if callable(sort_key_function):
        grouped_results = sorted(grouped_results, key=sort_key_function)

    if reverse:
        grouped_results = reversed(grouped_results)

    ret += grouped_results

    return ret