Django - model数据的过滤,使用django_fil

作者: fall4u | 来源:发表于2018-01-21 14:45 被阅读669次

    问题背景

    在Web应用当中,尤其是后台管理应用,经常遇到的一个场景是,需要根据用户的输入条件,在数据库中查询相应数据并展示。 那让我们看看在Django中如何处理?

    先看看官网的介绍
    https://docs.djangoproject.com/en/1.11/topics/db/queries/

    官网介绍的很详细, 我就不重复粘贴复制了, 在这里只记录一下一个典型的使用场景.

    场景描述

    有一个关于书本信息的数据表, 包括书本的书名,价格,出版社,ISBN,作者。Model定义如下:

    class Book(models.Model):
        name = models.CharField(max_length=48)
        isbn = models.IntegerField(primary_key=True, unique=True)
        author = models.CharField(max_length=24)
        press = models.CharField(max_length=48)
        price = models.PositiveIntegerField(default=0)
    

    在书本管理后台,需要实现根据用户输入的作者信息,进行模糊查询。 前端通过ajax POST将表单信息传送给后台。Django在收到请求之后,在view当中调用如下函数就可以进行数据库的查找过滤操作呢。 其中icontains表示忽略大小写的模糊查询。

    def filter_books(objects, request):
        filter_author = request.POST['author']
        if (filter_author):
            objects = objects.filter(author__icontains=filter_author)
        return objects
    

    Django支持的查询方式有很多, 具体请查看以下官网介绍:
    https://docs.djangoproject.com/en/1.11/ref/models/querysets/#field-lookups

    一切看起来都不错,有什么不妥?

    目前看起来确实没有什么不妥,但是当定义的model多了, 要查询的表单多了之后, 相关的代码片段就变成了下面这样:

    def filter_libooks(objects, request):
        filter_status = request.POST['status']
        filter_uuid = request.POST['uuid']
        filter_isbn = request.POST['isbn']
        filter_name = request.POST['name']
        if (filter_status):
            objects = objects.filter(status=filter_status)
        if (filter_isbn):
            objects = objects.filter(book__isbn__contains=filter_isbn)
        if (filter_name):
            objects = objects.filter(book__name__contains=filter_name)
        if (filter_uuid):
            objects = objects.filter(uuid_contains=filter_uuid)        
        return objects
    
    def filter_books(objects, request):
        filter_author = request.POST['author']
        filter_press = request.POST['press']
        filter_isbn = request.POST['isbn']
        filter_name = request.POST['name']
        if (filter_author):
            objects = objects.filter(author__contains=filter_author)
        if (filter_press):
            objects = objects.filter(press__contains=filter_press)
        if (filter_isbn):
            objects = objects.filter(isbn__contains=filter_isbn)
        if (filter_name):
            objects = objects.filter(name__contains=filter_name)
        return objects
    

    代码重复的好像有点多, 虽然粘贴复制并不废什么功夫, 但是看起来心情就不是特别美丽。在这个时候, django_filters 就闪亮登场呢。

    Django_Filters

    单表查询
    Filter定义

    先看看在上文已经描述过的要进行单表查询的场景下, 使用django_filters如何来完成
    model定义不变, 定义如下Filter类

    class BookFilter(django_filters.FilterSet):
        name = django_filters.CharFilter(lookup_expr='icontains')
        author = django_filters.CharFilter(lookup_expr='icontains')
        isbn = django_filters.NumberFilter(lookup_expr='icontains')
        press = django_filters.CharFilter(lookup_expr='icontains')
    
        class Meta:
            model = Book
            fields = {'name', 'author', 'isbn', 'press'}
    

    这个类解释如下:

    • model 该类是为Model Book定义的过滤类
    • fields 该过滤类可以处理Book model中字段name,author,isbn,press的查询
    • name = django_filters.CharFilter(lookup_expr='icontains') 指定name字段的过滤条件为icontains

    值得注意的是django_filters如何只指定fields,不指定特定fields的过滤方法, 那么默认会使用exact的过滤条件进行查询。

    在view中的使用
    # filter objects according to user inputs
    objects = BookFilter(request.POST, queryset=objects)
    
    recordsFiltered = objects.qs.count()
    objects = objects.qs[start:(start + length)]
    

    在官网的介绍当中,使用的比较多场景是使用过滤器的返回值作为参数去渲染模板文件。那如果需要后端进行分页处理, 就需要使用返回值的qs属性呢
    比如示例当中的 recordsFiltered = objects.qs.count(), 将查询得到的query set的记录个数返回给前端Datatable插件。

    多表查询

    单表查询比较容易理解, 那么当我们需要使用多表查询的时候, 该怎么做呢?

    model定义
    class Book(models.Model):
        name = models.CharField(max_length=48)
        isbn = models.IntegerField(primary_key=True, unique=True)
        author = models.CharField(max_length=24)
        press = models.CharField(max_length=48)
        price = models.PositiveIntegerField(default=0)
    
    class LibBook(models.Model):
        # Relations
        book = models.ForeignKey(Book, on_delete=models.PROTECT,null=False)
        # Attributes
       
        uuid = models.UUIDField(default=uuid.uuid4, null=False)
        inDate = models.DateField(auto_now_add=True)
        dueDate = models.DateField(blank=True, null=True)
        overDays = models.PositiveIntegerField(default=0)
        LendAmount = models.PositiveIntegerField(default=0)
    

    在这个例子当中,书本信息和馆藏图书是一对多的关系,我们在LibBook当中使用ForeignKey来指定LibBook和Book之间的关系。

    问题: 如何使用书本的书名和isbn信息来查询本地图书馆的藏书信息?
    Fileter定义
    class LibBookFilter(django_filters.FilterSet):
        book__name = django_filters.CharFilter(lookup_expr='icontains')
        book__isbn = django_filters.NumberFilter(lookup_expr='icontains')
    
        class Meta:
            model = LibBook
            fields = {'book__name', 'book__isbn'}
    

    值得注意的是fields的定义, 要使用Django中规定的双下划线符号__来指定查询的字段。
    在本例中, 我们同样指定查询的条件是icontains

    View使用
    # filter objects according to user inputs
    objects = LibBookFilter(request.POST, queryset=objects)
    recordsFiltered = objects.qs.count()
    objects = objects.qs[start:(start + length)]
    

    看到这里,感觉和单表查询也差不了太多啊。 在笔者使用django_filters的过程中,最大的坑就是在这里呢, 怎么设置都不好使。后来发现问题是:

    POST 数据定义

    前端同样使用ajax将查询数据通过POST传到后台。 ajax的data必须如下定义,django_filters才能正常工作。

    "ajax": {
        "url": "#",
        "type": "POST",
        "data": function(d){
            return $.extend( {}, d, {
                "book__isbn"  : document.getElementById('isbn').value,
                "book__name"  : document.getElementById('book').value,
                });
        }
    },
    

    注意POST数据的数据名字必须和Filter中保持一致才行。

    笔者找了好久,才在这篇博文中找到答案。
    http://www.tomchristie.com/rest-framework-2-docs/api-guide/filtering

    一语惊醒梦中人大抵就是如此

    相关文章

      网友评论

        本文标题:Django - model数据的过滤,使用django_fil

        本文链接:https://www.haomeiwen.com/subject/hcktoxtx.html