Django URL含空格/特殊字符导致404错误求助
Django搜索栏含空格/特殊字符时404问题解决
问题表现
在Django站点开发搜索栏时,传递无空格名称一切正常,但用户输入含空格或!、?等特殊字符后,URL跳转出现404错误。尝试将URL转为UTF-8编码,Django自动将空格转为%20,但问题仍存在。
具体流程:访问doctors页面调用doctor视图,提交搜索内容后重定向到包含用户输入的URL,触发doctor_search视图,此时含空格/特殊字符的输入会报错。
原代码
urls.py
from django.urls import path from dac import views from django.conf.urls.static import static from django.conf import settings from django.urls import re_path from django.contrib import admin app_name = 'dac' urlpatterns = [ path('admin/', admin.site.urls), path('', views.index, name='index'), path('doctors/<slug:slug>', views.doctor, name='doctors'), path('doctors/search/<slug:slug>/<slug:slug2>', views.doctor_search, name='doctors_search'), re_path(r'^doctors_search/search/(?P<slug>.)/(?P<slug2>\d+)$', views.doctor_search, name='doctors_search'), path('<slug:slug>', views.doctor_detail, name='doctor_detail'), path('signup/', views.sign_up, name="sign_up"), path('login/', views.log_in, name='log_in'), path('logout/', views.log_out, name='log_out'), path('reports/', views.reports, name='reports'), ] urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
views.py
def doctor(request, slug): print("slug: ",slug) if request.method == 'POST': print("slug: ",slug) risposta=request.POST.get("textAnswer") risposta=risposta.upper() risposta_list=risposta.rsplit() print(risposta_list) actual_url=request.build_absolute_uri() url=re.sub("\d+$", "search/%s/1"%(risposta), actual_url) return HttpResponseRedirect(url.encode('utf-8')) slug=int(slug) max_slug=int(round(doctors.objects.count()/28,0)) if slug==1: prew_slug=slug else: prew_slug=slug-1 if slug==max_slug: nxt_slug=max_slug else: nxt_slug=slug+1 doc_list=doctors.objects.all()[(slug-1)*28:slug*28] {"doc_list":doc_list} return render(request, 'dac/doctors.html', locals()) def doctor_search(request, slug, slug2): slug2=int(slug2) print("slug1: ",slug) print("slug2: ",slug2) if request.method=="POST": risposta=request.POST.get("textAnswer") risposta=risposta.upper() actual_url=request.build_absolute_uri() url=actual_url.replace(slug, risposta) return HttpResponseRedirect(url.encode('utf-8')) slug=urllib.parse.unquote(slug) doc_list=doctors.objects.filter(frst_nm=slug) | doctors.objects.filter(lst_nm=slug) | doctors.objects.filter(pri_spec=slug) if doc_list.count()==0: messages.error(request, "no result") max_slug=int(round(doc_list.count()/28,0)) if slug2==1: prew_slug=slug2 else: prew_slug=slug2-1 if slug2==max_slug: nxt_slug=max_slug else: nxt_slug=slug2+1 doc_list=doc_list.all()[(slug2-1)*28:slug2*28] {"doc_list":doc_list} return render(request, 'dac/doctors_search.html', locals())
问题原因
- Slug转换器限制:Django的
<slug:slug>转换器仅允许字母、数字、下划线和连字符,空格、!、?等特殊字符(即使编码为%20)无法匹配该规则,直接导致URL路由不匹配,返回404。 - URL拼接方式错误:视图中通过
re.sub和字符串replace手动拼接URL,容易出现路径格式错误,且无法正确处理特殊字符的编码逻辑。 - 正则路由规则错误:
re_path中的(?P<slug>.)仅匹配单个字符,无法匹配包含多个字符的搜索内容,导致路由失效。
解决方案
1. 修改URL配置
将路由中的slug转换器替换为str(允许任意非空字符串),并修正正则路由的匹配规则:
from django.urls import path, re_path from dac import views from django.conf.urls.static import static from django.conf import settings from django.contrib import admin app_name = 'dac' urlpatterns = [ path('admin/', admin.site.urls), path('', views.index, name='index'), # 医生列表页用int类型更语义化,原逻辑中该参数是页码 path('doctors/<int:page>', views.doctor, name='doctors'), # 搜索页用str接收搜索关键词,int接收页码 path('doctors/search/<str:search_query>/<int:page>', views.doctor_search, name='doctors_search'), # 修正正则路由的路径和匹配规则,.*匹配任意长度字符 re_path(r'^doctors/search/(?P<search_query>.*)/(?P<page>\d+)$', views.doctor_search, name='doctors_search'), path('<slug:slug>', views.doctor_detail, name='doctor_detail'), path('signup/', views.sign_up, name="sign_up"), path('login/', views.log_in, name='log_in'), path('logout/', views.log_out, name='log_out'), path('reports/', views.reports, name='reports'), ] urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
2. 修正视图中的重定向逻辑
使用Django的reverse函数生成正确的URL,避免手动拼接,同时自动处理特殊字符编码:
from django.urls import reverse from django.shortcuts import redirect, render from django.contrib import messages def doctor(request, page): print("page: ", page) if request.method == 'POST': risposta = request.POST.get("textAnswer").upper() # 用reverse生成搜索页URL,自动处理编码 search_url = reverse('dac:doctors_search', args=[risposta, 1]) return redirect(search_url) max_page = int(round(doctors.objects.count()/28, 0)) prev_page = page if page == 1 else page - 1 next_page = max_page if page == max_page else page + 1 doc_list = doctors.objects.all()[(page-1)*28 : page*28] return render(request, 'dac/doctors.html', locals()) def doctor_search(request, search_query, page): print("search_query: ", search_query) print("page: ", page) if request.method == 'POST': risposta = request.POST.get("textAnswer").upper() # 同样用reverse生成新的搜索URL search_url = reverse('dac:doctors_search', args=[risposta, 1]) return redirect(search_url) # str转换器已自动解码%20为空格,无需手动unquote doc_list = doctors.objects.filter(frst_nm=search_query) | doctors.objects.filter(lst_nm=search_query) | doctors.objects.filter(pri_spec=search_query) if not doc_list.exists(): messages.error(request, "无匹配结果") max_page = int(round(doc_list.count()/28, 0)) prev_page = page if page == 1 else page - 1 next_page = max_page if page == max_page else page + 1 doc_list = doc_list[(page-1)*28 : page*28] return render(request, 'dac/doctors_search.html', locals())
3. 额外优化点
- 医生列表页的参数用
int:page更语义化,原逻辑中该参数是页码,而非slug。 - 使用
doc_list.exists()替代doc_list.count()判断是否有结果,性能更优。 - 移除不必要的
risposta_list变量和无效的字典定义{"doc_list":doc_list}。
内容的提问来源于stack exchange,提问作者Gold_Stygian
相关产品推荐
相关产品推荐

