首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >DRF动态滤波

DRF动态滤波
EN

Stack Overflow用户
提问于 2021-09-15 10:42:39
回答 3查看 1.1K关注 0票数 3

我需要在DRF中使用动态过滤,它应该允许使用括号来定义操作优先级,并使用模型中可用字段的任何组合。运算是:和,或,eq (相等),ne (不相等),gt (大于),lt (小于)

示例:“(日期: eq '2016-05-01')和((number_of_calories gt 20) OR (number_of_calories lt 10))”

我怎样才能做到这一点?什么是最好的方法?

目前,我有下面的解决方案,但这不是很好的方法,因为它容易受到SQL注入的攻击

utils.py

代码语言:javascript
复制
mappings = {
    ' eq ': ' = ',
    ' ne ': ' != ',
    ' gt ': ' > ',
    ' lt ': ' < ',
    ' gte ': ' >= ',
    ' lte ': ' <= ',
}

def convert_string(query: str) -> Optional[str]:
    if query and isinstance(query, str):
        pattern_drop = re.compile(r"drop\s+table\s*\w*")
        pattern_alter = re.compile(r"alter\s+table\s+\w+")
        pattern_delete = re.compile(r"delete\s+from\s+\w+")
        pattern_update = re.compile(r"update\s+\w+\s+set\s+\w+")
        pattern_insert = re.compile(r"insert\s+into\s+\w+")
        pattern_select = re.compile(r"select\s+\w+\s+from\s+")
        query_lower = query.lower()
        if '--' in query_lower or '/*' in query_lower or \
                pattern_drop.match(query_lower) or pattern_alter.match(query_lower) or \
                pattern_update.match(query_lower) or pattern_insert.match(query_lower) or \
                pattern_delete.match(query_lower) or pattern_select.match(query_lower):
            return None
        for expression, operation in mappings.items():
            query = query.replace(expression, operation)

    return query

views.py

代码语言:javascript
复制
def get_queryset(self):
    q_string = self.request.data['query']
    # q_string = "(date eq '2016-05-01') AND ((number_of_calories gt 20) OR (number_of_calories lt 10))"

    query = convert_string(q_string)
    # just replace 'eq' with '=', 'ne' with '!=', and so on ...
    # query = "(date = '2016-05-01') AND ((number_of_calories > 20) OR (number_of_calories < 10))"

    users = Users.objects.raw('SELECT * FROM Users WHERE ' + query)
    return users
EN

回答 3

Stack Overflow用户

发布于 2021-09-20 12:00:44

用于解析查询字符串,如:

代码语言:javascript
复制
string = "((num_of_pages gt 20) OR (num_of_pages lt 10)) AND (date gt '2016-05-01')"

您可以在django Q对象中使用package (不是专家,而是非常强大的库):

  1. 解析代码:

代码语言:javascript
复制
import pyparsing as pp
import operator as op

from django.db.models import Q

word = pp.Word(pp.alphas, pp.alphanums + "_-*'")
operator = pp.oneOf('lt gt eq').setResultsName('operator')
number = pp.pyparsing_common.number()
quoted = pp.quotedString().setParseAction(pp.removeQuotes)
term = (word | number | quoted)

key = term.setResultsName('key')
value = term.setResultsName('value')

group = pp.Group(key + operator + value)

def q_item(item):
    """Helper for create django Q() object"""
    k = f'{item.key}__{item.operator}'
    v = item.value
    return Q(**{k: v})


class BaseBinary:

    def __init__(self, tokens):
        self.args = tokens[0][0::2]

    def __repr__(self):
        return f'{self.__class__.__name__}({self.symbol}):{self.args}'

    def evaluate(self):
        a = q_item(self.args[0]) if not isinstance(self.args[0], BaseBinary) else self.args[0].evaluate()
        b = q_item(self.args[1]) if not isinstance(self.args[1], BaseBinary) else self.args[1].evaluate()
        return self.op(a, b)


class BoolNotOp(BaseBinary):
    symbol = 'NOT'
    op = op.not_

    def __init__(self, tokens):
        super().__init__(tokens)
        self.args = tokens[0][1]

    def evaluate(self):
        a = q_item(self.args) if not isinstance(self.args, BaseBinary) else self.args.evaluate()
        return ~a


class BoolAndOp(BaseBinary):
    symbol = 'AND'
    op = op.and_


class BoolOrOp(BaseBinary):
    symbol = 'OR'
    op = op.or_


expr = pp.infixNotation(group,
                        [('NOT', 1, pp.opAssoc.RIGHT, BoolNotOp),
                         ('AND', 2, pp.opAssoc.LEFT, BoolAndOp),
                         ('OR', 2, pp.opAssoc.LEFT, BoolOrOp)])

现在给出的字符串如下:

代码语言:javascript
复制
string = "(date gt '2016-05-01') AND ((num_of_pages gt 20) OR (num_of_pages lt 10))"

到解析器:

代码语言:javascript
复制
parser = expr.parseString(string)[0]
print(parser.evaluate())

给我们我们的Q对象:

代码语言:javascript
复制
(AND: ('date__gt', '2016-05-01'), (OR: ('num_of_pages__gt', 20), ('num_of_pages__lt', 10)))

准备接受过滤

代码语言:javascript
复制
class Book(models.Model):
    title = models.CharField(max_length=200)
    counter = models.PositiveIntegerField(default=0)
    date = models.DateField(auto_now=True)
    num_of_pages = models.PositiveIntegerField(default=0)
    
qs = Book.objects.filter(parser.evaluate())
print(qs.query)
代码语言:javascript
复制
SELECT "core_book"."id", "core_book"."title", "core_book"."counter", "core_book"."date", "core_book"."num_of_pages" FROM "core_book" WHERE ("core_book"."date" > 2016-05-01 AND ("core_book"."num_of_pages" > 20 OR "core_book"."num_of_pages" < 10))

P.S还没有完全测试。

票数 1
EN

Stack Overflow用户

发布于 2021-09-15 17:12:42

目前,我在一个项目中广泛使用Q对象,即使用用户get参数筛选搜索结果。

这里有一个片段

代码语言:javascript
复制
some_initial_query_object = Model.objects.all()

qs_result_dates = []

qs_result_dates.append(
    Q(
        event_date__start_date_time__gte='2021-08-01',
        event_date__start_date_time__lt='2021-09-01' + datetime.timedelta(days=1)
      )
)

some_initial_query_object = some_initial_query_object.filter(qs_result_dates)

在您的场景中,可以使用用于OR和& for和

代码语言:javascript
复制
Q(date='2016-05-01')
&
Q(number_of_calories__gt=20, number_of_calories__lt=10)
票数 0
EN

Stack Overflow用户

发布于 2021-09-22 15:05:01

下面是一个使用DRF的动态过滤示例,它将get_queryset方法覆盖到我在所有项目中使用的ModelViewSet上。使用这种方法,我可以从前端充分利用Django-ORM框架的全部功能。

views.py

代码语言:javascript
复制
def BaseAPIView(...):

    ''' base view for other views to inherit '''

    def get_queryset(self):

        queryset = self.queryset

        # get filter request from client:
        filter_string = self.request.query_params.get('filter')

        # apply filters if they are passed in:
        if filters: 
            filter_dictionary = json.loads(filter_string)
            queryset = queryset.filter(**filter_dictionary)

        return queryset

请求url现在将类似于,例如:my_website.com/api/users?filter={"first_name":"John"}

它可以建造如下:

script.js

代码语言:javascript
复制
// using ajax as an example:
var filter = JSON.stringify({
  "first_name" : "John"
});

$.ajax({
   "url" : "my_website.com/api/users?filter=" + filter,
   "type" : "GET",
   ...
});

的一些优势:

everywhere

  • front

  • 不需要指定哪些字段可以在每个视图类

  • 上过滤一次,使用它, end筛选看起来就像django filtering
  • can对exclude

所做的一样

的一些缺点:

如果希望某些字段是non-filterable

  • less直观的前端代码来查询表

,则

  • 潜在的安全风险。

总的来说,这种方法对我来说比任何软件包都有用得多。

票数 -1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/69191567

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档