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

问题背景

在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

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,347评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,435评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,509评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,611评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,837评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,987评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,730评论 0 267
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,194评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,525评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,664评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,334评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,944评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,764评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,997评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,389评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,554评论 2 349

推荐阅读更多精彩内容