При каждом api-запросе список в методе add_where добавляется, но не сбрасывается

Я пытаюсь создать пользовательский менеджер в django rest framework, который взаимодействует с athena. Но я столкнулся со странной проблемой, что каждый раз, когда я обращаюсь к конечной точке с фильтрами, они добавляются в список where_conditions=[] при каждом запросе. Этого не должно происходить, что фактически делает запрос, который преобразуется в SQL, недействительным. Я хочу сбросить список where_conditions при каждом запросе.

filter_class/base.py

from apps.analytics.models.filter.fields.filter import FilterField
from apps.analytics.models.filter.fields.ordering import OrderingField
from apps.analytics.models.filter.fields.search import SearchField
from apps.analytics.models.manager.query.utils import ModelQueryUtils


class BaseFilterClass:
    _ordering_filter_query_key = "ordering"
    _search_filter_query_key = "search"

    def __init__(self, request):
        self._request = request

    @property
    def filter_params(self):
        if getattr(self.Meta, "filter_fields", None):
            req_query_params = {**self._request.query_params}
            for i in req_query_params:
                if isinstance(req_query_params[i], list):
                    req_query_params[i] = req_query_params[i][0]
            if req_query_params and req_query_params.get("search"):
                del req_query_params["search"]
            req_query_params_key = set(req_query_params.keys())
            valid_filter_params = self.Meta.filter_fields.declared_filter_params.intersection(req_query_params_key)
            params = {key: self.Meta.filter_fields.get_processed_value(key.split('__')[-1],
                                                                       req_query_params[key]) if "__" in key else
            req_query_params[key] for key in valid_filter_params}
            return ModelQueryUtils.FilterInput(params, ModelQueryUtils.FilterInput.JoinOperator.AND)

    @property
    def ordering_params(self):
        if getattr(self.Meta, "ordering_fields", None):
            req_query_params_ordering_params = self._request.query_params and self._request.query_params.get(
                self._ordering_filter_query_key)
            if req_query_params_ordering_params:
                declared_ordering_params = self.Meta.ordering_fields.declared_ordering_params
                return ModelQueryUtils.OrderInput(
                    [str(param) for param in req_query_params_ordering_params.split(",") if
                     ((param.startswith("-") and param[1:]) or param) in declared_ordering_params])

    @property
    def search_params(self):
        if getattr(self.Meta, "search_fields", None):
            req_query_params_search_param = self._request.query_params and self._request.query_params.get(
                self._search_filter_query_key)
            params = {}
            if req_query_params_search_param:
                for key in self.Meta.search_fields.declared_search_params:
                    params[key] = req_query_params_search_param
            return ModelQueryUtils.SearchInput(params, ModelQueryUtils.FilterInput.JoinOperator.OR)

    class Meta:
        filter_fields = FilterField([])
        search_fields = SearchField([])
        ordering_fields = OrderingField([])


manager/query/base.py

class BaseModelQuery:
    __slots__ = ['_model']
    # _where_conditions = None
    _order_conditions = None
    _offset: int = None
    _limit: int = None
    _full_query_mtd_name: str = None

    def __init__(self, model):
        self._model = model
        self._where_conditions = []
        self._order_conditions = []
        self._offset = None
        self._limit = None
        self._full_query_mtd_name = None

    @property
    def count_sql(self):
        if self._full_query_mtd_name:
            return getattr(self, self._full_query_mtd_name)()["count"]
        return f"""
            SELECT
                Count(*)
            FROM
                {self._model.table_name}
            {self._where_sql}
        """

    @property
    def full_sql(self):
        if self._full_query_mtd_name:
            return getattr(self, self._full_query_mtd_name)()["full"]
        return f"""
            {self._base_sql}
            {self._where_sql}
            {self._order_by_sql}
            {self._offset_sql}
            {self._limit_sql}
        """

    @property
    def _base_sql(self):
        return f"""
            SELECT
                {','.join(field_name for field_name in self._model.fields.keys())}
            FROM
                {self._model.table_name}
        """

    @property
    def _where_sql(self):
        if self._where_conditions:
            statements = [item.get_sql_statement(self._model) for item in self._where_conditions]
            print(statements)
            statements = [f"({item})" for item in statements if item]
            if statements:
                return f"""
                WHERE
                    {" AND ".join(statements)}
                """
        return ""

    @property
    def _order_by_sql(self):
        if self._order_conditions:
            statements = [item.get_sql_statement() for item in self._order_conditions]
            statements = [item for item in statements if item]
            if statements:
                return f"""
            ORDER BY
                {", ".join(statements)}
            """
        return ""

    @property
    def _offset_sql(self):
        if self._offset is not None:
            return f"""
            OFFSET {self._offset}
            """
        return ""

    @property
    def _limit_sql(self):
        if self._limit is not None:
            return f"""
            LIMIT {self._limit}
            """
        return ""

    def add_where(self, condition):
        self._where_conditions.append(condition)

    def add_order(self, condition):
        self._order_conditions.append(condition)

    def set_offset(self, offset: int):
        self._offset = offset

    def set_limit(self, limit: int):
        self._limit = limit

    def set_full_query_mtd_name(self, name: str):
        self._full_query_mtd_name = name

manager/query/utils.py

from apps.analytics.models.fields import ModelField
from apps.analytics.models.filter.lookups import FilterFieldLookup


class ModelQueryUtils:
    class FilterInput:
        _filter_lookup_operator_mapping = {
            FilterFieldLookup.Definition.NOT_EQUAL: "<>",
            FilterFieldLookup.Definition.GREATER_THAN_EQUAL_TO: ">=",
            FilterFieldLookup.Definition.LESS_THAN_EQUAL_TO: "<=",
            FilterFieldLookup.Definition.GREATER_THAN: ">",
            FilterFieldLookup.Definition.LESS_THAN: "<",
            FilterFieldLookup.Definition.IS_IN: "IN",
            FilterFieldLookup.Definition.CONTAINS: "LIKE",
            FilterFieldLookup.Definition.ICONTAINS: "LIKE"
        }

        class JoinOperator:
            OR = "OR"
            AND = "AND"

        def __init__(self, filter_params: dict, operator: str):
            self.filter_params = filter_params
            self.operator = operator

        def get_sql_statement(self, model):
            list_conditions = []
            for key, value in self.filter_params.items():
                field, lookup = key.split("__") if "__" in key else (key, None)
                model_field: ModelField = model.fields.get(field)
                if model_field:
                    value = model_field.get_cast_statement_for_value(value)
                    list_conditions.append(
                        f"{field} {self._filter_lookup_operator_mapping.get(lookup) or '='} {value}")
            return list_conditions and f" {self.operator} ".join(list_conditions)

    class SearchInput(FilterInput):

        def get_sql_statement(self, model):
            list_conditions = []
            for key, value in self.filter_params.items():
                field, lookup = key.split("__") if "__" in key else (key, None)
                model_field: ModelField = model.fields.get(field)
                if model_field:
                    value = model_field.get_cast_statement_for_value(value)
                    value = value.strip("'")
                    list_conditions.append(
                        f"LOWER({field}) {self._filter_lookup_operator_mapping.get(lookup) or '='} LOWER('%{value}%')")
            return list_conditions and f" {self.operator} ".join(list_conditions)

    class OrderInput:
        def __init__(self, order_params: list[str]):
            self.order_params = order_params

        def get_sql_statement(self):
            return ", ".join("{}{}".format(param.lstrip("-"), " DESC" if param.startswith("-") else "") for param in
                             self.order_params)

manager/queryset/base.py

manager/init.py


class LineIntrusionAnalyticsMananger(BaseModelManager):
    def __init__(self):
        super().__init__(queryset_class=queryset.LineIntrusionAnalyticsQueryset)

Методы типа .add_where(…) не имеют особого смысла в QuerySet. Методы QuerySet в Django по сути являются неизменяемыми. Это означает, что если вы используете MyModel.objects.filter(some_filter1).filter(some_filter2), то при каждом вызове .filter(…) создается new QuerySet, который является модифицированной копией предыдущего.

Это может показаться мелочью, причем неэффективной, поскольку мы каждый раз клонируем QuerySet, но это важно. Действительно, в Django, если вы, например, сделаете ListView:

class MyListView(ListView):
    queryset = MyModel.objects.all()

после этого вы устанавливаете QuerySet в атрибут queryset, Django каждый раз вызывает .all(), чтобы клонировать QuerySet таким образом, что не использует кэш « родителя» QuerySet, и, таким образом, заставляет повторно оценивать. По той же причине, если вы .filter(…) добавите .queryset, это никак не повлияет на исходный

Таким образом, вам следует не определять методы, изменяющие состояние: каждый метод должен создавать новый QuerySet. Если этого не делать, то в конечном итоге может возникнуть «глобальное состояние», в котором вы таким образом измените «родительский» queryset.

Вернуться на верх