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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 18:25:25