Django应用处理捷克语特殊字符时触发UnicodeEncodeError求助
问题:Django应用处理捷克语特殊字符筛选时出现编码错误
错误现象
基于Django开发的员工表格展示应用,基础功能正常,但通过HTTP GET请求携带含捷克语特殊字符(如Ř/ř、Ě/ě)的姓名/姓名片段筛选时,触发编码错误:
UnicodeEncodeError: 'latin-1' codec can't encode character '\u0161' in position 324: ordinal not in range(256)
报错栈信息
[20/Oct/2022 14:07:03] "GET /tracker/us HTTP/1.1" 200 14078 Traceback (most recent call last): File "C:\Users\myname\AppData\Local\Programs\Python\Python310\lib\wsgiref\handlers.py", line 138, in run self.finish_response() File "C:\Users\myname\AppData\Local\Programs\Python\Python310\lib\wsgiref\handlers.py", line 184, in finish_response self.write(data) File "C:\Users\myname\AppData\Local\Programs\Python\Python310\lib\wsgiref\handlers.py", line 288, in write self.send_headers() File "C:\Users\myname\AppData\Local\Programs\Python\Python310\lib\wsgiref\handlers.py", line 347, in send_headers self._write(bytes(self.headers)) File "C:\Users\myname\AppData\Local\Programs\Python\Python310\lib\wsgiref\headers.py", line 142, in __bytes__ return str(self).encode('iso-8859-1') UnicodeEncodeError: 'latin-1' codec can't encode character '\u0161' in position 290: ordinal not in range(256)
相关代码
视图函数
def track1(request): year = int(request.COOKIES.get('year', str(date.today().year))) month = int(request.COOKIES.get('month', str(date.today().month - 1))) name = request.COOKIES.get('name', '') project = request.COOKIES.get('project', '') if month == 0: month = 12 sql_query = """select p.name project, p.id projectid, i.title issue, i.id issueid, u.id userid, u.name, replace(ROUND(t.time_spent/3600.0, 1)::text, '.', ',') as spent, TO_CHAR(t.spent_at + interval '2h', 'dd.mm.yyyy HH24:MI:SS') date_spent, substring(n.note for 300) note from issues i left join projects p on p.id = i.project_id left join timelogs t on t.issue_id = i.id left join users u on u.id = t.user_id left join notes n on n.id = t.note_id""" if(request.method=="GET"): form = UserSpentOnProjectForm(request.GET) if form.is_valid(): year = form.cleaned_data['year'] month = form.cleaned_data['month'] name = form.cleaned_data['name'] project = form.cleaned_data['project'] user_spent_on_project = Userspentonprojects.objects.raw(sql_query + ''' where (t.spent_at + interval '2h') between '%s-%s-01' and '%s-%s-%s 23:59:59' order by 8''', [year, month, year, month, calendar.monthrange(year=year, month=month)[1]] ) if name is not "" and project is "": name = '%' + name + '%' user_spent_on_project = Userspentonprojects.objects.raw(sql_query + ''' where (t.spent_at + interval '2h') between '%s-%s-01' and '%s-%s-%s 23:59:59' and u.name LIKE %s order by 7, 1, 3''', [year, month, year, month, calendar.monthrange(year=year, month=month)[1], name] ) if project is not "" and name is "": project = '%' + project + '%' user_spent_on_project = Userspentonprojects.objects.raw(sql_query + ''' where (t.spent_at + interval '2h') between '%s-%s-01' and '%s-%s-%s 23:59:59' and p.name LIKE %s order by 7, 1, 3''', [year, month, year, month, calendar.monthrange(year=year, month=month)[1], project] ) if project and name is not "": project = '%' + project + '%' name = '%' + name + '%' user_spent_on_project = Userspentonprojects.objects.raw(sql_query + ''' where (t.spent_at + interval '2h') between '%s-%s-01' and '%s-%s-%s 23:59:59' and p.name LIKE %s and u.name LIKE %s order by 7, 1, 3''', [year, month, year, month, calendar.monthrange(year=year, month=month)[1], project, name] ) form = UserSpentOnProjectForm(initial={ 'year': year, 'month': month, 'name': name.strip("%"), 'project': project.strip("%") }) spent_sum = 0 for record in user_spent_on_project: spent_sum = spent_sum + float(record.spent.replace(",", ".")) context = { 'user_spent_on_project' : user_spent_on_project, 'form' : form, 'name': name.strip("%"), 'project': project.strip("%"), 'month': month, 'year': year, 'spent_sum' : round(spent_sum, 2) } response = render(request, 'trackApp/track1.html', context=context) response.set_cookie('year', year) response.set_cookie('month', month) response.set_cookie('project', project.strip("%")) response.set_cookie('name', name.strip("%")) return response
数据库配置(settings.py)
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql', # NASTAVENI PRO LOKALNI TESTOVANI 'NAME': 'dumptables', 'USER': 'postgres', 'PASSWORD': 'admin', 'HOST': 'localhost', 'PORT': 5432, } }
解决方法
1. 修复Cookie存储的编码问题
报错根源是WSGI规范要求响应头(包含Cookie)只能使用latin-1编码,而捷克语特殊字符不在该编码范围内。需要对Cookie的值进行URL编码后存储,读取时解码:
修改视图函数中Cookie的读写逻辑:
from urllib.parse import quote, unquote # 读取Cookie时解码 year = int(unquote(request.COOKIES.get('year', str(date.today().year)))) month = int(unquote(request.COOKIES.get('month', str(date.today().month - 1)))) name = unquote(request.COOKIES.get('name', '')) project = unquote(request.COOKIES.get('project', '')) # 存储Cookie时编码 response = render(request, 'trackApp/track1.html', context=context) response.set_cookie('year', quote(str(year))) response.set_cookie('month', quote(str(month))) response.set_cookie('project', quote(project.strip("%"))) response.set_cookie('name', quote(name.strip("%")))
2. 明确数据库连接的UTF-8编码
在PostgreSQL数据库配置中添加client_encoding选项,确保数据库交互使用UTF-8:
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql', 'NAME': 'dumptables', 'USER': 'postgres', 'PASSWORD': 'admin', 'HOST': 'localhost', 'PORT': 5432, 'OPTIONS': { 'client_encoding': 'UTF8', }, } }
3. 推荐:使用Django ORM替代原生SQL
原生SQL拼接容易引发编码问题,改用Django ORM构建查询更安全且能自动处理编码:
import calendar from datetime import date from django.db.models import Q, F from django.db.models.functions import Round, Replace, ToChar from django.shortcuts import render def track1(request): # 读取并解码Cookie from urllib.parse import quote, unquote year = int(unquote(request.COOKIES.get('year', str(date.today().year)))) month = int(unquote(request.COOKIES.get('month', str(date.today().month - 1)))) name = unquote(request.COOKIES.get('name', '')) project = unquote(request.COOKIES.get('project', '')) if month == 0: month = 12 if request.method == "GET": form = UserSpentOnProjectForm(request.GET) if form.is_valid(): year = form.cleaned_data['year'] month = form.cleaned_data['month'] name = form.cleaned_data['name'] project = form.cleaned_data['project'] # 使用ORM构建查询 from .models import Timelogs queryset = Timelogs.objects.select_related('issue__project', 'user', 'note') \ .annotate( project=F('issue__project__name'), projectid=F('issue__project__id'), issue=F('issue__title'), issueid=F('issue__id'), userid=F('user__id'), username=F('user__name'), spent=Replace(Round(F('time_spent')/3600.0, 1), '.', ','), date_spent=ToChar(F('spent_at') + F('interval 2 hours'), 'dd.mm.yyyy HH24:MI:SS'), note=F('note__note')[:300] ) # 时间范围过滤 last_day = calendar.monthrange(year, month)[1] queryset = queryset.filter( spent_at__gte=f"{year}-{month}-01", spent_at__lte=f"{year}-{month}-{last_day} 23:59:59" ) # 姓名和项目过滤 if name: queryset = queryset.filter(user__name__icontains=name) if project: queryset = queryset.filter(issue__project__name__icontains=project) # 排序 queryset = queryset.order_by('date_spent', 'project', 'issue') form = UserSpentOnProjectForm(initial={ 'year': year, 'month': month, 'name': name, 'project': project }) # 计算总耗时 spent_sum = 0 for record in queryset: spent_sum += float(record.spent.replace(",", ".")) spent_sum = round(spent_sum, 2) context = { 'user_spent_on_project': queryset, 'form': form, 'name': name, 'project': project, 'month': month, 'year': year, 'spent_sum': spent_sum } # 存储编码后的Cookie response = render(request, 'trackApp/track1.html', context=context) response.set_cookie('year', quote(str(year))) response.set_cookie('month', quote(str(month))) response.set_cookie('project', quote(project)) response.set_cookie('name', quote(name)) return response
4. 生产环境部署提示
开发环境使用的wsgiref服务器对响应头编码限制严格,生产环境使用Gunicorn、uWSGI等专业WSGI服务器时,该问题通常会自动处理,但仍建议保持Cookie编码的处理逻辑以兼容所有场景。
内容的提问来源于stack exchange,提问作者Bergerino
相关产品推荐
相关产品推荐

