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

能否给Django的reverse打猴子补丁以保留URL中的#符号?

解决Django 1.11中reverse将#编码为%23的全局猴子补丁问题

我帮你分析下为啥你之前的补丁没生效,再给你个靠谱的全局生效方案!

为啥你的补丁没起作用?

你把补丁放在settings.py、app的__init__.py或者urls.py里,时机都不对:

  • settings.py加载时,Django核心模块还没完全初始化,很多地方已经提前导入了reverse函数,你补丁的只是django.urls.base.reverse,但其他模块引用的还是旧的未补丁版本;
  • __init__.py和urls.py的加载时机也太早,同样会导致部分reverse引用没被覆盖到,尤其是模板标签{% url %}内部调用的reverse,可能已经提前缓存了旧函数。

正确的全局补丁方案

要让补丁全局生效,得等到Django所有模块都加载完成后再执行,最佳位置是首个已安装应用的AppConfig.ready()方法——这个方法是Django启动流程的最后一步,能确保覆盖所有reverse的引用。

步骤1:修改应用的apps.py

找到你INSTALLED_APPS里排第一个的应用,修改它的apps.py:

from django.apps import AppConfig
from django.urls import base

# 标记补丁是否已执行,避免runserver重载时重复打补丁
_reverse_patched = False

def patch_reverse(func):
    def inner(*args, **kwargs):
        # 验证补丁是否运行(Python3要加括号)
        print("inner reverse: 补丁生效了!")
        url = func(*args, **kwargs)
        # 把编码后的%23替换回#
        return url.replace("%23", "#")
    return inner

class FirstAppConfig(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    # 替换成你的应用名称
    name = 'your_first_app'

    def ready(self):
        global _reverse_patched
        if not _reverse_patched:
            # 给reverse打补丁
            base.reverse = patch_reverse(base.reverse)
            # 如果你用到了reverse_lazy,也一起补丁
            base.reverse_lazy = patch_reverse(base.reverse_lazy)
            _reverse_patched = True

步骤2:在settings.py中注册AppConfig

确保你的应用是用上面的Config类注册的,而不是直接写应用名称:

INSTALLED_APPS = [
    # 替换成你的应用Config路径
    'your_first_app.apps.FirstAppConfig',
    # 其他应用...
]

验证补丁是否生效

启动Django服务后,调用reverse或者在模板里用{% url %}生成带#的URL,看控制台会不会输出inner reverse: 补丁生效了!,同时检查生成的URL里是否保留了#而不是%23。

这样处理后,不管是视图里直接调用reverse,还是模板标签{% url %},都会经过你的补丁函数,全局替换编码后的%23为#。

内容的提问来源于stack exchange,提问作者hamstap85

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 08:48:09