Django text search with partial sentence match update to django3
Asked Answered
A

1

0

I am trying to apply partial search in Django postgres, exactly the same as described here django-text-search-with-partial-sentence-match I found there a pretty nice solution

from psycopg2.extensions import adapt

from django.contrib.postgres.search import SearchQuery


class PrefixedPhraseQuery(SearchQuery):
"""
Alter the tsquery executed by SearchQuery
"""
    def as_sql(self, compiler, connection):
        # Or <-> available in Postgres 9.6
        value = adapt('%s:*' % ' & '.join(self.value.split()))

        if self.config:
            config_sql, config_params = compiler.compile(self.config)
            template = 'to_tsquery({}::regconfig, {})'\
                .format(config_sql, value)
            params = config_params

        else:
            template = 'to_tsquery({})'\
                .format(value)
            params = []

        if self.invert:
            template = '!!({})'.format(template)

        return template, params

It works fine for python 3.6 but does not work for 3.9. The difference is, than in 3.6 SearchQuery inherits from Value:

class SearchQuery(SearchQueryCombinable, Value):
    output_field = SearchQueryField()
    SEARCH_TYPES = {
        'plain': 'plainto_tsquery',
        'phrase': 'phraseto_tsquery',
        'raw': 'to_tsquery',
    }
    def __init__(self, value, output_field=None, *, config=None, invert=False, search_type='plain'):
        self.config = config
        self.invert = invert
        if search_type not in self.SEARCH_TYPES:
            raise ValueError("Unknown search_type argument '%s'." % search_type)
        self.search_type = search_type
        super().__init__(value, output_field=output_field)

and in python 3.9 SearchQuery inherits from Func:

class SearchQuery(SearchQueryCombinable, Func):
    output_field = SearchQueryField()
    SEARCH_TYPES = {
        'plain': 'plainto_tsquery',
        'phrase': 'phraseto_tsquery',
        'raw': 'to_tsquery',
        'websearch': 'websearch_to_tsquery',
    }

    def __init__(self, value, output_field=None, *, config=None, invert=False, search_type='plain'):
        self.function = self.SEARCH_TYPES.get(search_type)
        if self.function is None:
            raise ValueError("Unknown search_type argument '%s'." % search_type)
        if not hasattr(value, 'resolve_expression'):
            value = Value(value)
        expressions = (value,)
        self.config = SearchConfig.from_parameter(config)
        if self.config is not None:
            expressions = (self.config,) + expressions
        self.invert = invert
        super().__init__(*expressions, output_field=output_field)

In Func unlike in Value, there is no self.value

class Func(SQLiteNumericMixin, Expression):
    """An SQL function call."""
    function = None
    template = '%(function)s(%(expressions)s)'
    arg_joiner = ', '
    arity = None  # The number of arguments the function accepts.

    def __init__(self, *expressions, output_field=None, **extra):
        if self.arity is not None and len(expressions) != self.arity:
            raise TypeError(
                "'%s' takes exactly %s %s (%s given)" % (
                    self.__class__.__name__,
                    self.arity,
                    "argument" if self.arity == 1 else "arguments",
                    len(expressions),
                )
            )
        super().__init__(output_field=output_field)
        self.source_expressions = self._parse_expressions(*expressions)
        self.extra = extra

and the Value looks like that

class Value(Expression):
    """Represent a wrapped value as a node within an expression."""
    def __init__(self, value, output_field=None):
        """
        Arguments:
         * value: the value this expression represents. The value will be
           added into the sql parameter list and properly quoted.

         * output_field: an instance of the model field type that this
           expression will return, such as IntegerField() or CharField().
        """
        super().__init__(output_field=output_field)
        self.value = value

Cound anybody help me to adjust the code sample to python 3.9 or recommend any solution for similar search?

Androgen answered 29/1, 2021 at 9:2 Comment(0)
H
1

So we're clear, your problem here is not with the Python version, but with the Django version.

What you're looking for should be self.source_expressions[0].

However if I were rewriting this today, I'd start by looking at the raw search type (example) and pass my own query using the proximity operator <-> (example).

Heavyset answered 31/1, 2021 at 21:52 Comment(4)
Thanks for raw search type recomanedation. When I am trying to replace self.value by self.source_expressions[0] i get error value = adapt("%s:*" % " | ".join(self.source_expressions[0].split())) AttributeError: 'SearchConfig' object has no attribute 'split' . I tried with str(self.source_expressions[0]), but then I get return self.cursor.execute(sql, params) django.db.utils.ProgrammingError: syntax error in tsquery: "<django.contrib.postgres.search.SearchConfig | object | at | 0x7f1c461bc7f0>:*" Androgen
sorry for formating, but some rules from comment-formatting seem to have no effectAndrogen
Did you try using the raw query with the <-> operator?Heavyset
Yes. It does work. Thanks. Anyway I was curious about previous code, so what did worked for me was self.source_expressions[1]Androgen

© 2022 - 2024 — McMap. All rights reserved.