在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
错误原因分析
- 分页请求重复调用API:点击“下一页”会重新触发GET请求,此时
searched参数可能为空,导致API返回数据结构异常,或者res_json['RESP']不是可迭代对象,触发TypeError。 - 分页逻辑错误:创建
Paginator时提前用[:number_products]截断查询集,导致分页仅在截断后的子集内生效,同时重复查询数据库浪费资源。 - 重复数据写入:每次请求都重新调用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,提问作者Антон Нурапкин
相关产品推荐
相关产品推荐

