能否给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
相关产品推荐
相关产品推荐

