Django过滤和分页

Django过滤和分页


场景描述

在编写WEB应用时,会通过类似www.example.com/books/的URL查询书本列表。如果要查询作者John的书籍呢,只要在URL后加过滤参数就好,例如www.example.com/books/?author=John

下面我们来看看Django的实现:
定义一个书本的模型,包含书本名、作者和价格。在后台不指定条件的情况下,返回所有的书籍,指定作者名,就返回该作者名下的书籍。

# models.py
class Book(models.Model):
    name = models.CharField('名称', max_length=128)
    author = models.CharField('作者', max_length=32)
    price = models.DecimalField('价格', decimal_places=2, max_digits=7)
    created_date = models.DateTimeField('添加时间', auto_now_add=True)

# views.py
from .models import Book
import json
import decimal
from datetime import datetime
from django.http import HttpResponse

class CustomEncoder(json.JSONEncoder):
    def default(self, o):
        if isinstance(o, decimal.Decimal):
            return str(o)

        if isinstance(o, datetime):
            return o.strftime('%Y-%m-%d %H:%M:%S')

def get_books(request):
    author = request.GET.get('author', '')
    if author:  # 指定作者
        books = Book.objects.filter(author=author).values('name', 'author', 'price', 'created_date')
    else:
        books = Book.objects.all().values('name', 'author', 'price', 'created_date')
    ret = {
        "total": books.count(),
        "data": list(books)
    }
    ret = json.dumps(ret, cls=CustomEncoder)
    return HttpResponse(ret)

#urls.py
from goods import views as goods_view

urlpatterns = [
    url('^books/', goods_view.get_books)
]

现在让我们通过浏览器查看效果:
在浏览器输入127.0.0.1:8000/books/返回的是提前在数据库内存储的四条记录,加了过滤参数author,返回的是作者John名下的两本书籍,是不是很简单呢。

a
b

多个参数查询

前面过滤只有一个author字段,如果有四五个参数呢,代码就会像下面一样。虽然可以实现需要的功能,但是重复的代码比较多,使用django-filter就可以简化这些操作。


def get_books(request):
    author = request.GET.get('author', '')
    created_date__gt = request.GET.get('created_date__gt', '')
    name = request.GET.get('name', '')
    books = Book.objects.all().values('name', 'author', 'price', 'created_date')

    if author:  # 指定作者
        books = books.filter(author=author)
    if created_date__gt:    # 录入时间大于created_date__gt
        books = books.filter(created_date__gt=created_date__gt)
    if name:    # 指定书名
        books = books.filter(name=name)
    ret = {
        "total": books.count(),
        "data": list(books)
    }
    ret = json.dumps(ret, cls=CustomEncoder)    # 序列化decimal和时间
    return HttpResponse(ret)

django-filter介绍

Django-filter is a generic, reusable application to alleviate writing some of the more mundane bits of view code. Specifically, it allows users to filter down a queryset based on a model’s fields, displaying the form to let them do this.

django-filter一般与django的第三方框架restful framework组合使用, 我也是在使用
restful的时候,才想到能不能单独拿出来使用。详细了解可以查询官方文档

下面是演示代码,CustomFilter继承FilterSet,使用方式类似于Django的ModelForm,在Meta中指定模型,fields则指定过滤的条件。exact指明字段相等,lt,gt则会与字段名组合成created_date__gtcreated_date__lt在filter()中使用,是不是很方便。django-filter其它功能和使用方式,在这里就不详细叙述了,感兴趣的同学自己探索使用吧。

import django_filters

class CustomFilter(django_filters.FilterSet):
    class Meta:
        model = Book
        fields = {
            'name': ['exact'],
            'created_date': ['lt', 'gt', 'year'],
            'author': ['exact'],
            'price': ['lt', 'gt']
        }

def get_books(request):
    books = Book.objects.all().values('name', 'author', 'price', 'created_date')
    f = CustomFilter(request.GET, queryset=books)
    ret = {
        "total": f.qs.count(),
        "data": list(f.qs)
    }

    ret = json.dumps(ret, cls=CustomEncoder)
    return HttpResponse(ret)

Django分页

就像上面图片展示的一样,浏览器请求127.0.0.1:8000/books/,服务器返回json格式的书本列表。目前数据库内只有四个记录,如果是10000条记录,服务端会返回10000条数据么? 服务端肯定是不会直接返回这么多条数的,怎么办呢,这个时候分页就起到作用了。类似请求如下127.0.0.1:8000/books/?page_size=1&page_num=5,假如有10000条记录,服务端根据page_size分成2000页,每页5个。终端只要控制具体显示哪一页的记录就好,人也看不过来那么多数据。

Django自带有分页器类Paginator,可以很方便的实现分页功能。代码如下:


from django.core.paginator import Paginator

def get_books(request):
    page_size = request.GET.get('page_size', 4)
    page_num = request.GET.get('page_num', 1)

    books = Book.objects.all().values('name', 'author', 'price', 'created_date')
    f = CustomFilter(request.GET, queryset=books)
    pagination = Paginator(list(f.qs), page_size)
    _list = pagination.page(page_num)
    ret = {
        "total": pagination.count,
        "data": _list.object_list
    }
    ret = json.dumps(ret, cls=CustomEncoder)
    return HttpResponse(ret)

下图显示了代码运行效果,总共四条数据,现在显示了其中两条。
c

因为每次都要写上request.GET.get('page_size', 4)这样的代码,觉得繁琐,就把django-restful-framework的相关分页代码抄过来,拿来使用

class CustomPaginator(object):
    page_size_query_param = 'page_size'
    page_size = 4
    page_num = 1
    page_query_param = 'page_num'
    last_page_strings = ('last',)
    max_page_size = 10

    def paginate_queryset(self, queryset, request):
        page_size = self.get_page_size(request)
        if not page_size:
            return None

        paginator = Paginator(queryset, page_size)
        page_number = request.GET.get(self.page_query_param, 1)
        if page_number in self.last_page_strings:
            page_number = paginator.num_pages

        try:
            self.page = paginator.page(page_number)
        except InvalidPage as exc:
            return None

        return list(self.page)

    def get_page_size(self, request):
        if self.page_size_query_param:
            try:
                return self._positive_int(request.GET[self.page_size_query_param],
                                          strict=True,
                                          cutoff=self.max_page_size)
            except (KeyError, ValueError):
                pass
        return self.page_size

    def get_paginated_response(self):
        data = {
            'total': self.page.paginator.count,
            'rows': self.page.object_list
        }
        ret = json.dumps(data, cls=CustomEncoder)
        return HttpResponse(ret)

    @staticmethod
    def _positive_int(integer_string, strict=False, cutoff=None):
        """
        Cast a string to a strictly positive integer.
        """
        ret = int(integer_string)
        if ret < 0 or (ret == 0 and strict):
            raise ValueError()
        if cutoff:
            return min(ret, cutoff)
        return ret


def get_books(request):
    # 过滤条件
    books = Book.objects.all().values('name', 'author', 'price', 'created_date')
    f = CustomFilter(request.GET, queryset=books)

    # 分页
    pagination = CustomPaginator()
     pagination.paginate_queryset(f.qs, request)
    response = pagination.get_paginated_response()
    return response

这样一个代码比较少的分页和过滤功能就实现了。

猜你喜欢

转载自blog.csdn.net/soga238/article/details/82730916
今日推荐