You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在Django中实现分页时遇TypeError错误,求解决方案

问题描述

我想在自己的网站里实现分页功能。流程是调用供应商API获取数据,反序列化后保存到模型,再取出对象展示。同时需要获取所有唯一名称来查询模型,但添加分页后,点击“下一页”完全失效,还在get_unique_names函数的names = set([ item['NAME'] for item in res_json['RESP']])行抛出TypeError。添加分页前一切正常,不确定改用ListView能不能解决问题。

现有代码

views.py

def search(request):

    url_for_search = 'http://ws.armtek.ru/api/ws_search/search?format=json'
    template = 'profiles/search.html'

    if request.method == 'GET':
        
        # API调用
        searched = request.GET.get('searched')
        headers = { 'Authorization' : basic_auth(login, password)}
        params = {some_params}
        res = requests.post(url_for_search,headers=headers,data=params)

        if 200 <= res.status_code < 300 :
            
            # 反序列化
            serializer = ProfileSerializer(data=res.json()['RESP'],many = True)
            serializer.is_valid()
            serializer.save()

            # 获取数据
            names = get_unique_names(res.json())
            number_products = len(res.json()['RESP'])
            products = Profile.objects.filter(NAME__in = names).distinct()[:number_products]

            # 分页
            p = Paginator(Profile.objects.filter(NAME__in = names).distinct()[:number_products],10)
            page = request.GET.get('page')
            page_obj = p.get_page(page)

            return render(request, template, {"products":products,
                                              "page_obj":page_obj,
                                              'searched':searched})

        else:
            return HttpResponse('Error')

get_unique_names函数

def get_unique_names(res_json):
    names = set([ item['NAME'] for item in res_json['RESP']])
    return names

错误原因分析

  1. 分页请求重复调用API:点击“下一页”会重新触发GET请求,此时searched参数可能为空,导致API返回数据结构异常,或者res_json['RESP']不是可迭代对象,触发TypeError。
  2. 分页逻辑错误:创建Paginator时提前用[:number_products]截断查询集,导致分页仅在截断后的子集内生效,同时重复查询数据库浪费资源。
  3. 重复数据写入:每次请求都重新调用API并保存数据,不仅效率低下,还可能导致数据库数据重复,分页时数据不一致引发错误。

解决方案

1. 缓存查询条件,避免重复API调用

将唯一名称缓存到Session中,仅在首次搜索或关键词变化时调用API:

def search(request):
    url_for_search = 'http://ws.armtek.ru/api/ws_search/search?format=json'
    template = 'profiles/search.html'

    if request.method == 'GET':
        searched = request.GET.get('searched')
        cached_names = request.session.get('cached_names')

        # 仅当有搜索关键词或无缓存时调用API
        if searched or not cached_names:
            headers = {'Authorization': basic_auth(login, password)}
            params = {some_params}
            res = requests.post(url_for_search, headers=headers, data=params)

            if not (200 <= res.status_code < 300):
                return HttpResponse('Error')
            
            res_json = res.json()
            # 先清理旧数据避免重复
            Profile.objects.filter(NAME__in=[item['NAME'] for item in res_json['RESP']]).delete()
            serializer = ProfileSerializer(data=res_json['RESP'], many=True)
            if serializer.is_valid():
                serializer.save()
            
            # 获取唯一名称并缓存
            names = get_unique_names(res_json)
            request.session['cached_names'] = list(names)
        else:
            names = cached_names

        # 不提前切片,直接基于完整查询集分页
        queryset = Profile.objects.filter(NAME__in=names).distinct()
        p = Paginator(queryset, 10)
        page = request.GET.get('page')
        page_obj = p.get_page(page)

        return render(request, template, {
            "page_obj": page_obj,
            'searched': searched
        })

2. 加固get_unique_names函数,避免TypeError

增加数据结构校验,防止遍历异常:

def get_unique_names(res_json):
    names = set()
    # 校验RESP字段是否存在且为可迭代类型
    if 'RESP' in res_json and isinstance(res_json['RESP'], (list, tuple)):
        for item in res_json['RESP']:
            # 校验item是否为字典且包含NAME键
            if isinstance(item, dict) and 'NAME' in item:
                names.add(item['NAME'])
    return names

3. 改用ListView简化分页逻辑

ListView内置分页支持,可减少手动代码,示例如下:

from django.views.generic import ListView

class SearchListView(ListView):
    model = Profile
    template_name = 'profiles/search.html'
    paginate_by = 10
    context_object_name = 'page_obj'

    def get_queryset(self):
        searched = self.request.GET.get('searched')
        cached_names = self.request.session.get('cached_names')

        if searched or not cached_names:
            # API调用、数据保存、缓存逻辑同前
            url_for_search = 'http://ws.armtek.ru/api/ws_search/search?format=json'
            headers = {'Authorization': basic_auth(login, password)}
            params = {some_params}
            res = requests.post(url_for_search, headers=headers, data=params)

            if not (200 <= res.status_code < 300):
                return Profile.objects.none()
            
            res_json = res.json()
            Profile.objects.filter(NAME__in=[item['NAME'] for item in res_json['RESP']]).delete()
            serializer = ProfileSerializer(data=res_json['RESP'], many=True)
            if serializer.is_valid():
                serializer.save()
            
            names = get_unique_names(res_json)
            self.request.session['cached_names'] = list(names)
        else:
            names = cached_names
        
        return Profile.objects.filter(NAME__in=names).distinct()

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['searched'] = self.request.GET.get('searched')
        return context

使用ListView更符合Django最佳实践,能大幅简化分页相关代码,推荐尝试。

内容的提问来源于stack exchange,提问作者Антон Нурапкин

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 20:38:16