有没有办法检测 Django 项目中未使用的模板?
在 Django 1.3 之前,使用像这样的简单字符串匹配函数就可以做到这一点。template_name
但是从 1.3 开始,如果您不覆盖它(例如 DetailView),则存在自动生成的基于通用类的视图。
此外,如果您覆盖第 3 方模块模板,则这些模板不会直接在您的视图中的任何位置使用。
也许可以通过抓取所有 URL 定义、加载相应的视图并template_name
从中获取来完成?
有没有办法检测 Django 项目中未使用的模板?
在 Django 1.3 之前,使用像这样的简单字符串匹配函数就可以做到这一点。template_name
但是从 1.3 开始,如果您不覆盖它(例如 DetailView),则存在自动生成的基于通用类的视图。
此外,如果您覆盖第 3 方模块模板,则这些模板不会直接在您的视图中的任何位置使用。
也许可以通过抓取所有 URL 定义、加载相应的视图并template_name
从中获取来完成?
我很好奇你是否可以通过猴子修补/装饰 get_template 来做到这一点。我想你可以,虽然你必须找到所有的模板加载函数(我在下面的例子中有两个)。
当我注意到它不仅仅是 loader.get_template 时,我使用了wrapt ,但它似乎很好。当然,让这 50000 公里远离 prod,但是......
现在,还要遵循的是我正在使用单元测试和鼻子测试来驱动它,所以,如果您使用 Python 代码对模板进行了完整的分支覆盖,您应该能够获得大多数模板(假设我没有错过任何get_template 类型函数)。
在settings.py
这是修补 get_template & co 的“大脑”。
import wrapt
import django.template.loader
import django.template.engine
def wrapper(wrapped, instance, args, kwargs):
#concatenate the args vector into a string.
# print "\n\n\n\n%s\nI am a wrapper \nusage:%s\n%s\n\n\n\n\n" % ("*"*80, usage, "*"*80)
try:
return wrapped(*args, **kwargs)
finally:
usage = ",".join([unicode(arg) for arg in args if arg])
track_usage(usage)
#you have to wrap whatever is loading templates...
#imported django module + class/method/function path of what needs to be
#wrapped within that module. comment those 2 lines out and you are back to
#normal
wrapt.wrap_function_wrapper(django.template.loader, 'get_template', wrapper)
wrapt.wrap_function_wrapper(django.template.engine, 'Engine.find_template', wrapper)
有关 wrapt 的更多详细信息,请参见安全应用猴子补丁在 python中。实际上比理解文档更容易使用,装饰器让我的大脑受伤。
此外,为了跟踪哪些 django 函数正在执行实际加载,我故意在代码和模板中拼错了一些模板名称,对其运行单元测试并查看堆栈跟踪以查找丢失的模板异常。
这是我写得很糟糕的函数,它添加到集合中并将其放入 json 输出中......
def track_usage(usage):
fnp_usage = "./usage.json"
try:
with open(fnp_usage, "r") as fi:
data = fi.read()
#read the set of used templates from the json file
j_data = json.loads(data)
s_used_file = set(j_data.get("li_used"))
except (IOError,),e:
s_used_file = set()
j_data = dict()
s_used_file.add(usage)
#convert the set back to a list for json compatibility
j_data["li_used"] = list(s_used_file)
with open(fnp_usage, "w") as fo:
json.dump(j_data, fo)
和输出(使用脚本格式化):
import sys
import json
fnp_usage = sys.argv[1]
with open(fnp_usage, "r") as fi:
data = fi.read()
#read the set of used templates from the json file
j_data = json.loads(data)
li_used_file = j_data.get("li_used")
li_used_file.sort()
print "\n\nused templates:"
for t in li_used_file:
print(t)
通过包装上面的 2 个函数,它似乎捕获了 extends、%includes 和直接 get_templates,以及基于类的视图使用的列表类型模板。它甚至捕获了我动态生成的模板,这些模板甚至不在文件系统上,而是通过自定义加载器加载。
used templates:
bootstrap/display_form.html
bootstrap/errors.html
bootstrap/field.html
bootstrap/layout/baseinput.html
bootstrap/layout/checkboxselectmultiple.html
bootstrap/layout/field_errors.html
bootstrap/layout/field_errors_block.html
bootstrap/layout/help_text.html
bootstrap/layout/help_text_and_errors.html
bootstrap/layout/radioselect.html
bootstrap/whole_uni_form.html
django_tables2/table.html
dynamic_template:db:testdb:name:pssecurity/directive.PrimaryDetails.json
uni_form/layout/div.html
uni_form/layout/fieldset.html
websec/__base.html
websec/__full12.html
websec/__l_right_sidebar.html
websec/bootstrapped_home.html
websec/changedb.html
websec/login.html
websec/requirejs_config.html
websec/topnav.html
websec/user_msg.html
即使没有通用视图,也无法确定检测到未使用的模板,因为您始终可以编写如下代码:
get_template(any_code_you_like()).render(context)
因此,即使在 Django 1.3 之前,您链接到的 django-unused-templates 应用程序也只能适用于尊重某种模板使用规则的项目。(例如,总是有一个字符串文字作为函数的模板参数,如get_template
和render_to_response
。)
加载所有视图也不够:视图可能在不同情况下使用不同的模板:
def my_view(request):
if request.user.is_authenticated():
return render(request, 'template1.html')
else:
return render(request, 'template2.html')
当然,模板可能根本不被视图使用,而是被系统的其他部分(例如,电子邮件)使用。