- 創建tag方式,首先在需要使用tag的app下創建一個templatetags的python包,
- 然后在包里創建一個tag模塊,例如hellotag.py
from django import template
register = template.Library() # 注意,這里的變量名一定是register,可不是任意的名稱
@ register.simple_tag
def hello(*args):
return "hello " + " ".join(str(args))
然后就可以在需要使用tag的頁面引用自定義tag了
{% load hellotag %}
{% hello "world !" %}
然后需要重啟Django服務,再通過頁面測試結果
- 為什么只能是register,因為Django源碼里就是這么解析的
def get_package_libraries(pkg):
"""
Recursively yield template tag libraries defined in submodules of a
package.
"""
for entry in walk_packages(pkg.__path__, pkg.__name__ + '.'):
try:
module = import_module(entry[1])
except ImportError as e:
raise InvalidTemplateLibrary(
"Invalid template library specified. ImportError raised when "
"trying to load '%s': %s" % (entry[1], e)
)
if hasattr(module, 'register'):
yield entry[1]
- 為什么需要重啟tag才生效?因為Django在第一次啟動時就將一些不易變動的模塊代碼使用緩存方式的裝飾器將這些模塊緩存起來了,這樣方便加快響應速度,而不是每次返回給用戶頁面時都重新加載一次tag模塊,那樣每個用戶每訪問一個頁面,都重新導入一次響應肯定會很慢
- Django默認會加載以下tag模塊
'django.template.defaulttags',
'django.template.defaultfilters',
'django.template.loader_tags',
通過settings指定tag模塊
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.i18n',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
'cloud.api.libs.context_processors.common_processor',
],
'autoescape': True,
'debug': settings.DEBUG, # 該值就是我們自定義的settings模塊的debug變量值。
'file_charset': settings.FILE_CHARSET, # 這個settings是從全局的Django settings導入的,默認為"utf-8"
'libraries': {} # 這里是字典類型的tag模塊名稱,例如:項目cloud目錄下有一個show.py tag模塊,那么完整的模塊路徑為cloud.show,定義到這個字典的格式為{'show': 'cloud.show'}
},
},
]
注意: TEMPLATES這個列表里的每一個字典元素的key只能是這幾個,不能多,也不能少,OPTIONS這個key對應的字典元素里面的key,除了context_processors 以外,其余值都可以不寫,默認自帶的配置已經有context_processors 了,如果需要自定義其它位置的tag模塊,可以添加libraries 這個選項
- render處理過程
# view方法通過render方法返回模版,如return render(request, 'index.html', context=None, content_type=None, status=200, using=None)
# 首先調用render方法,傳入參數
# render再調用render_to_string方法去獲取模版,並渲染模版,返回一個字符串
# 我們假設只傳了一個模版文件的情況,那樣的話render_to_string就會調用get_template方法獲取指定的模版文件
# get_template 首先通過_engine_list獲取到模版引擎
# _engine_list通過EngineHandler的實例化對象得到settings.TEMPLATES的所有模版引擎相關的信息
# EngineHandler在程序啟動時就將模版引擎緩存起來了,不過這里還是先說下它的過程,由於對象本身實現了iter方法,所有我們對數據進行迭代時,會觸發該方法
# 然后找到對象的templates屬性,該屬性通過獲取settings.TEMPLATES這個列表形式的模版引擎信息,可以有多個模版引擎,每一個引擎都是字典形式存在,且必須
#包含那些KEY,並保存一個副本,將每個引擎的`BACKEND`KEY值以點形式從右開始分割2次,取倒數第二個元素為引擎的名稱,這樣默認的引擎,名稱就是django。
# 然后設置引擎的NAME、DIRS、APP_DIRS、OPTIONS的值,最終所有的模版引擎以字典形式返回,KEY為引擎的名稱
# _engine_list調用EngineHandler對象的all方法,all方法通過列表推導遍歷self對象本身,也就是在調用__iter__方法,並使用self[alias]這種語法糖形式,
# 實際調用的就是self本身實現的__getitem__方法,將值取出來,每個alias就是上面所分析的所有以KEY方法存到字典的模版引擎,通過__getitem__傳入該KEY,再調用
# self.templates[alias]方式,self.templates本身返回的是一個有序字典,所有可以這么取字典的值。通過這樣就可以得到每個模版引擎的信息。__getitem__每
# 獲取到一個引擎信息時,都將BACKEND對應的值的字符串形式的的模塊名通過import_string方法導入該具體的模塊,並將得到的模塊返回,這里的默認引擎為
# django.template.backends.django.DjangoTemplates,import_string導入時會先導入模塊,再獲取模塊的屬性DjangoTemplates,然后返回該屬性。
# 接着__getitem__通過得到的DjangoTemplates,進行實例化,並把這個引擎相關的信息參數形式傳入。
# _engine_list得到的就是一個包含所有模版引擎的對象,這里實際上就是DjangoTemplates對象,
# get_template在遍歷_engine_list返回的每一個引擎,然后調用引擎的get_template方法,並把模版名稱傳入。
# DjangoTemplates實例化時,會將所有APP下的templatetags模塊下的tag加載。每個tag必須包含`register`這個屬性,也就是你定義tag裝飾器時,取的名稱必須
# 是register = Library(),名字不能隨意,否則不會加載這種tag,
# 這些tag都賦值給libraries每個模版的引擎的option的key值。通過settings也可以手動指定其它位置的tag模塊,
# 還將這些參數傳遞給Engine進行實例化,還記得平時我們的模版不引用tag也有一些默認tag可用么,其實就是這時導入了一些默認的tag,並賦值給對象的template_builtins。
# 默認的default_builtins = [
# 'django.template.defaulttags',
# 'django.template.defaultfilters',
# 'django.template.loader_tags',
# ]
def render(request, template_name, context=None, content_type=None, status=None, using=None):
content = render_to_string(template_name, context, request, using=using)
return HttpResponse(content, content_type, status)
def render_to_string(template_name, context=None, request=None, using=None):
"""
Loads a template and renders it with a context. Returns a string.
template_name may be a string or a list of strings.
"""
if isinstance(template_name, (list, tuple)):
template = select_template(template_name, using=using)
else:
template = get_template(template_name, using=using)
return template.render(context, request)
def get_template(template_name, using=None):
"""
Loads and returns a template for the given name.
Raises TemplateDoesNotExist if no such template exists.
"""
chain = []
engines = _engine_list(using)
for engine in engines:
try:
return engine.get_template(template_name)
except TemplateDoesNotExist as e:
chain.append(e)
raise TemplateDoesNotExist(template_name, chain=chain)
def _engine_list(using=None):
return engines.all() if using is None else [engines[using]]
class EngineHandler(object):
def __init__(self, templates=None):
"""
templates is an optional list of template engine definitions
(structured like settings.TEMPLATES).
"""
self._templates = templates
self._engines = {}
@cached_property
def templates(self):
if self._templates is None:
self._templates = settings.TEMPLATES
templates = OrderedDict()
backend_names = []
for tpl in self._templates:
tpl = tpl.copy()
try:
# This will raise an exception if 'BACKEND' doesn't exist or
# isn't a string containing at least one dot.
default_name = tpl['BACKEND'].rsplit('.', 2)[-2]
except Exception:
invalid_backend = tpl.get('BACKEND', '<not defined>')
raise ImproperlyConfigured(
"Invalid BACKEND for a template engine: {}. Check "
"your TEMPLATES setting.".format(invalid_backend))
tpl.setdefault('NAME', default_name)
tpl.setdefault('DIRS', [])
tpl.setdefault('APP_DIRS', False)
tpl.setdefault('OPTIONS', {})
templates[tpl['NAME']] = tpl
backend_names.append(tpl['NAME'])
counts = Counter(backend_names)
duplicates = [alias for alias, count in counts.most_common() if count > 1]
if duplicates:
raise ImproperlyConfigured(
"Template engine aliases aren't unique, duplicates: {}. "
"Set a unique NAME for each engine in settings.TEMPLATES."
.format(", ".join(duplicates)))
return templates
def __getitem__(self, alias):
try:
return self._engines[alias]
except KeyError:
try:
params = self.templates[alias]
except KeyError:
raise InvalidTemplateEngineError(
"Could not find config for '{}' "
"in settings.TEMPLATES".format(alias))
# If importing or initializing the backend raises an exception,
# self._engines[alias] isn't set and this code may get executed
# again, so we must preserve the original params. See #24265.
params = params.copy()
backend = params.pop('BACKEND')
engine_cls = import_string(backend)
engine = engine_cls(params)
self._engines[alias] = engine
return engine
def __iter__(self):
return iter(self.templates)
def all(self):
return [self[alias] for alias in self]
engines = EngineHandler()
class DjangoTemplates(BaseEngine):
app_dirname = 'templates'
def __init__(self, params):
params = params.copy()
options = params.pop('OPTIONS').copy()
options.setdefault('autoescape', True)
options.setdefault('debug', settings.DEBUG)
options.setdefault('file_charset', settings.FILE_CHARSET)
libraries = options.get('libraries', {})
options['libraries'] = self.get_templatetag_libraries(libraries)
super(DjangoTemplates, self).__init__(params)
self.engine = Engine(self.dirs, self.app_dirs, **options)
def from_string(self, template_code):
return Template(self.engine.from_string(template_code), self)
def get_template(self, template_name):
try:
return Template(self.engine.get_template(template_name), self)
except TemplateDoesNotExist as exc:
reraise(exc, self)
def get_templatetag_libraries(self, custom_libraries):
"""
Return a collation of template tag libraries from installed
applications and the supplied custom_libraries argument.
"""
libraries = get_installed_libraries()
libraries.update(custom_libraries)
return libraries
