Django源码分析-模板层源码解读(三)

1.Django模板层的核心组件

1.模板引擎(Engine)

  • django.template.engine.Engine类是Django模板引擎的核心。它负责编译和渲染模板。
  • 使用Engine类,可以创建Template对象,代表一个已编译的模板。

2.模板(Template)

  • django.template.base.Template类表示一个编译后的模板。
  • 模板中包含HTML代码和嵌入式的模板标签和变量。

3.模板标签和过滤器

  • 模板标签和过滤器是模板语言的一部分,用于在模板中执行逻辑、迭代、条件判断等操作。
  • 例如,{% for item in items %}…{% endfor %} 是一个用于迭代的标签。

4.模板上下文(Context)

  • django.template.context.Context类用于存储模板渲染时的变量和其对应的值。
  • 上下文将模板中的变量映射到实际的数值。

5.模板加载器(Loader)

  • django.template.loaders模块中的类负责从文件系统、数据库等地方加载模板源代码。
  • 默认的加载器将从应用程序的templates目录中加载模板。

6.模板响应(Response)

  • django.template.response.TemplateResponse类封装了渲染后的模板和其他相关信息,用于生成HTTP响应。

7.模板引擎设置

  • TEMPLATES设置定义了Django中使用的模板引擎的配置。
  • 可以配置多个引擎,每个引擎可以有不同的加载器、上下文处理器等。

1.Django中模板引擎加载过程源码解析

  1. 模板引擎初始化(django.template.engine.Engine):
    django.template.engine.Engine 类是负责配置和初始化模板引擎的核心类。在这个类的构造函数中,它接受 TEMPLATES 设置的值,解析其中的配置,并完成引擎的初始化。

    # 位于 django.template.engine 模块
    class Engine(BaseEngine):
        def __init__(self, dirs=None, app_dirs=False, **options):
            options = options.copy()
        options['libraries'] = options.get('libraries', {})
        options['file_charset'] = options.get('file_charset', settings.FILE_CHARSET)
    
        if app_dirs:
            if 'APP_DIRS' not in options:
                options['APP_DIRS'] = True
            if dirs is None:
                dirs = []
            for app in reversed(Apps.get_app_configs()):
                template_dir = os.path.join(app.path, 'templates')
                if template_dir not in dirs:
                    dirs.append(template_dir)
    
        super().__init__(dirs, **options)
    
    
  2. 获取模板引擎配置(django.template.loader.engine)`:
    django.template.loader.engine 模块提供了用于获取和配置模板引擎的函数。其中,find_template_engine 函数负责根据 TEMPLATES 设置中的配置找到并返回合适的模板引擎。

    # 位于 django.template.loader.engine 模块
    def find_template_engine(using=None):
        """
        Find and return the first template engine in TEMPLATES that
        matches the given 'using' argument.
        """
        for engine in engines.all():
            if engine['NAME'] == using:
                return engine
        if using is None and len(engines) > 0:
            return engines.all()[0]
        raise ImproperlyConfigured("No DjangoTemplates backend is configured.")
    
    
  3. 加载模板引擎(django.template.loader)
    django.template.loader 模块中的 get_template 函数用于加载模板,实际上会调用 find_template_engine 函数来获取合适的引擎。

    # 位于 django.template.loader 模块
    def get_template(template_name, using=None):
        """
        Load and return a template for the given name.
        Raise TemplateDoesNotExist if no such template exists.
        """
        return engines[using].get_template(template_name)
    
    

2.Context类、Engine类和Template类源码解读

  • Context类(上下文类)是 Django 模板层中的关键类,位于django/template/context.py文件中,用于存储和处理模板渲染时的变量和其对应的值。通过上下文类,可以在模板渲染的过程中传递变量、处理变量的作用域,并提供了一些额外的功能,如上下文的管理和状态的保存。

    # 从代码中截取关键部分
    
    class BaseContext:
        def __init__(self, dict_=None):
            self._reset_dicts(dict_)
    
        # ... 省略其他方法 ...
    	# push 方法:用于将一个或多个上下文字典推入堆栈,形成新的上下文。这允许在模板渲染过程中创建新的上下文,使变量的作用域得以管理。
        def push(self, *args, **kwargs):
            dicts = []
            for d in args:
                if isinstance(d, BaseContext):
                    dicts += d.dicts[1:]
                else:
                    dicts.append(d)
            return ContextDict(self, *dicts, **kwargs)
    	# push 方法:用于将当前上下文字典从堆栈中弹出。这用于结束对上下文的特定修改,回到上一个上下文状态。
        def pop(self):
            if len(self.dicts) == 1:
                raise ContextPopException
            return self.dicts.pop()
        # 通过 __getitem__、__setitem__、__delitem__ 方法,可以在当前上下文中进行变量的访问、设置和删除。   
    	def __setitem__(self, key, value):
        	self.dicts[-1][key] = value
        
        def __getitem__(self, key):
            for d in reversed(self.dicts):
                if key in d:
                    return d[key]
            raise KeyError(key)
    
    	def __delitem__(self, key):
        	del self.dicts[-1][key]
        # ... 省略其他方法 ...
    
    class Context(BaseContext):
        def __init__(self, dict_=None, autoescape=True, use_l10n=None, use_tz=None):
         #  定义了一些额外的属性,如 autoescape、use_l10n、use_tz 等,用于处理模板渲染时的相关配置
            self.autoescape = autoescape
            self.use_l10n = use_l10n
            self.use_tz = use_tz
            self.template_name = "unknown"
            self.render_context = RenderContext()
            self.template = None
            super().__init__(dict_)
    
    
  • Engine 类位于 django/template/engine.py文件中,它是负责配置和初始化模板引擎的核心类。Django支持多个模板引擎,例如django.template.backends.django.DjangoTemplates是Django自带的模板引擎。

    	class Engine:
    	   # default_builtins 属性包含了默认的内置标签和过滤器。
    	    default_builtins = [
            'django.template.defaulttags',  # django内置标签 { % for % }
            'django.template.defaultfilters',
            'django.template.loader_tags',
        ]
       # 构造函数,用于初始化模板引擎的配置。接受多个参数,包括模板目录、是否包含应用程序模板目录、上下文处理器、调试模式、加载器、字符集等。
        def __init__(self, dirs=None, app_dirs=False, context_processors=None,
                     debug=False, loaders=None, string_if_invalid='',
                     file_charset='utf-8', libraries=None, builtins=None, autoescape=True):
            ...
    
        def get_default():
          #  静态方法,返回第一个配置的 DjangoTemplates 引擎实例。用于获取默认的模板引擎
          
        @cached_property
        def template_context_processors(self):
         # cached_property 属性,返回最终的上下文处理器列表。包括内置的处理器和用户配置的处理器。
            context_processors = _builtin_context_processors
            context_processors += tuple(self.context_processors)
            return tuple(import_string(path) for path in context_processors)
    
        def get_template_builtins(self, builtins):
         #  返回最终的模板内置标签和过滤器列表
            return [import_library(x) for x in builtins]
    
        def get_template_libraries(self, libraries):
        #  返回最终的模板标签库字典
            loaded = {}
            for name, path in libraries.items():
                loaded[name] = import_library(path)
            return loaded
    
        @cached_property
        def template_loaders(self):
        #	cached_property 属性,返回模板加载器的列表。调用 get_template_loaders 方法获取加载器列表。
            return self.get_template_loaders(self.loaders)
    
        def get_template_loaders(self, template_loaders):
         #	 返回模板加载器的列表。
            loaders = []
            for template_loader in template_loaders:
                loader = self.find_template_loader(template_loader)
                if loader is not None:
                    loaders.append(loader)
            return loaders
    
        def find_template_loader(self, loader):
         #  根据给定的加载器配置返回加载器实例。
            if isinstance(loader, (tuple, list)):
                loader, *args = loader
            else:
                args = []
    
            if isinstance(loader, str):
                loader_class = import_string(loader)
                return loader_class(self, *args)
            else:
                raise ImproperlyConfigured(
                    "Invalid value in template loaders configuration: %r" % loader)
    
        def find_template(self, name, dirs=None, skip=None):
         # 查找模板并返回模板对象和模板的起源。支持查找多个目录。
            tried = []
            for loader in self.template_loaders:
                try:
                    template = loader.get_template(name, skip=skip)
                    return template, template.origin
                except TemplateDoesNotExist as e:
                    tried.extend(e.tried)
            raise TemplateDoesNotExist(name, tried=tried)
    
        def from_string(self, template_code):
         	#  返回编译后的模板对象,用于处理从字符串中生成的模板代码。
            return Template(template_code, engine=self)
    
        def get_template(self, template_name):
           # 返回编译后的模板对象,用于处理从模板名称中加载的模板。支持模板继承。
            template, origin = self.find_template(template_name)
            if not hasattr(template, 'render'):
                # template needs to be compiled
                template = Template(template, origin, template_name, engine=self)
            return template
    
        def render_to_string(self, template_name, context=None):
        #   渲染指定名称的模板,并返回渲染后的字符串。可用于测试。
            if isinstance(template_name, (list, tuple)):
                t = self.select_template(template_name)
            else:
                t = self.get_template(template_name)
            if isinstance(context, Context):
                return t.render(context)
            else:
                return t.render(Context(context, autoescape=self.autoescape))
    
        def select_template(self, template_name_list):
           #  给定模板名称列表,返回第一个可加载的模板对象
            if not template_name_list:
                raise TemplateDoesNotExist("No template names provided")
            not_found = []
            for template_name in template_name_list:
                try:
                    return self.get_template(template_name)
                except TemplateDoesNotExist as exc:
                    if exc.args[0] not in not_found:
                        not_found.append(exc.args[0])
                    continue
            # If we get here, none of the templates could be loaded
            raise TemplateDoesNotExist(', '.join(not_found))
    
    
  • Django的Template类表示已经被编译的模板,源码位于django/template/base.py文件中。

    # django.template.base module
    
    from django.template.context import Context
    from django.template.engine import Engine
    
    class Template:
    	# Template类的初始化方法接收一个模板字符串 (template_string) 和一个可选的模板引擎 (engine)。如果未提供模板引擎,则使用默认引擎。
        def __init__(self, template_string, engine=None):
            self.template_string = template_string
            self.engine = engine or Engine.get_default()
    
    	# render方法用于渲染模板,接收一个可选的上下文 (context) 和一个可选的请求对象 (request)。
        def render(self, context=None, request=None):
            if context is None:
                context = {}
            if not isinstance(context, Context):
                context = Context(context)
    
          	#  调用engine.render方法,该方法负责实际的模板编译和渲染工作。
            return self.engine.render(self.template_string, context, request)
    
    

    使用示例:

    用户通常只需直接使用Template类。
    使用Template(template_string)创建编译后的模板对象。
    使用包含渲染变量的Context调用模板对象的render()方法。

    >>> from django import template
    >>> s = '<html>{% if test %}<h1>{{ varvalue }}</h1>{% endif %}</html>'
    >>> t = template.Template(s)
    >>> c = template.Context({'test':True, 'varvalue': 'Hello'})
    >>> t.render(c)
    '<html><h1>Hello</h1></html>'
    >>> c = template.Context({'test':False, 'varvalue': 'Hello'})
    >>> t.render(c)
    '<html></html>'
    
  • 在Django中,Context类、Engine类和Template类是与模板系统相关的三个重要组件,它们之间的关系如下:

    首先,Engine加载并编译模板,然后你创建一个Context实例,将要在模板中使用的数据添加到该实例中。最后,通过调用Template类的render方法,将Context中的数据渲染到模板中,得到最终的输出。

4.Token类、Lexer类和Parser类源码解读

  • 在Django中,Token类Lexer类Parser类是与模板系统密切相关的三个组件,它们分别负责处理和解析模板内容。在模板解析过程中,Lexer类会生成一个Token实例的列表,其中每个实例都代表一个标记,Parser类接收Token列表,进行语法分析,生成Node对象的列表,模板系统最终使用这些Node对象来渲染模板,根据模板中的变量、条件和循环等逻辑生成最终的输出。

  • Django模板引擎中的Token类。源码定义位于django/template/base.py文件中。这个类主要用于表示模板中的标记,标记可以是文本、变量或块语句。

    class Token:
    	# 初始化一个 Token 实例
        def __init__(self, token_type, contents, position=None, lineno=None):
            self.token_type, self.contents = token_type, contents
            self.lineno = lineno
            self.position = position
    	# _str__ 方法返回一个可读的字符串表示 Token
        def __str__(self):
            token_name = self.token_type.name.capitalize()
            return ('<%s token: "%s...">' %
                    (token_name, self.contents[:20].replace('\n', '')))
    	# split_contents 方法用于将标记内容拆分为单独的片段
        def split_contents(self):
            split = []
            bits = smart_split(self.contents)
            for bit in bits:
                if bit.startswith(('_("', "_('")):
                    sentinel = bit[2] + ')'
                    trans_bit = [bit]
                    while not bit.endswith(sentinel):
                        bit = next(bits)
                        trans_bit.append(bit)
                    bit = ' '.join(trans_bit)
                split.append(bit)
        	return split
    
    
  • Lexer类是模板解析器中的词法分析器,负责将模板字符串转换为一系列的标记(Token实例)。源码定义位于django/template/base.py文件中。

class Lexer:
    def __init__(self, template_string):
        self.template_string = template_string
        self.verbatim = False
	
	# tokenize()方法将模板字符串拆分成标记,生成一个Token实例的列表。
    def tokenize(self):
        in_tag = False
        lineno = 1
        result = []
        for bit in tag_re.split(self.template_string):
            if bit:
                result.append(self.create_token(bit, None, lineno, in_tag))
            in_tag = not in_tag
            lineno += bit.count('\n')
        return result
  • Parser类是模板解析器中的语法分析器,负责将一系列标记(Token实例)转换为编译后的模板,即Node对象的列表,源码定义位于django/template/base.py文件中。
class Parser:
    # parse()方法将这些标记转换为编译后的模板。每个Node对象代表模板中的一部分,可以是文本、变量、条件语句或循环语句等。   
    def parse(self, parse_until=None):
    	# parse_until参数指定终止条件
        if parse_until is None:
            parse_until = []
        nodelist = NodeList() # nodelist 用于存储解析后的节点
        # 进入一个循环,遍历self.tokens中的每个标记
        while self.tokens:
            #  self.next_token()获取下一个标记。
            token = self.next_token()
            # 如果是文本标记(TokenType.TEXT),将其转换为TextNode并添加到nodelist。
            if token.token_type.value == 0:  # TokenType.TEXT 如 <html>
                self.extend_nodelist(nodelist, TextNode(token.contents), token)
            # 如果是变量标记(TokenType.VAR),将其编译为VariableNode并添加到nodelist。
            elif token.token_type.value == 1:  # TokenType.VAR 如 {{ value }}
                if not token.contents:
                    raise self.error(token, 'Empty variable tag on line %d' % token.lineno)
                try:
                    filter_expression = self.compile_filter(token.contents)
                except TemplateSyntaxError as e:
                    raise self.error(token, e)
                var_node = VariableNode(filter_expression)
                self.extend_nodelist(nodelist, var_node, token)
            #  如果是块标记(TokenType.BLOCK),解析标签名并查找相应的标签处理函数。然后将其编译为节点并添加到nodelist。
            elif token.token_type.value == 2:  # TokenType.BLOCK 如 {% if %} xxxx {% endif %}
                try:
                    # 从块标记中提取标签名。
                    command = token.contents.split()[0]
                except IndexError:
                    raise self.error(token, 'Empty block tag on line %d' % token.lineno)
                if command in parse_until:
                	# 检查提取的标签名是否在 parse_until 列表中
                    self.prepend_token(token)
                    return nodelist
                # 将当前块标记的命令(标签名)和标记添加到命令栈 command_stack 中
                self.command_stack.append((command, token))
                try:
                    compile_func = self.tags[command]
                except KeyError:
                	 #  如果未找到标签处理函数,说明遇到了未知的块标记。通过 self.invalid_block_tag() 方法抛出相应的异常。
                    self.invalid_block_tag(token, command, parse_until)
                try:
                #  用获取到的标签处理函数 compile_func 编译块标记,得到编译结果 compiled_result。
                    compiled_result = compile_func(self, token)
                except Exception as e:
                    raise self.error(token, e)
                self.extend_nodelist(nodelist, compiled_result, token)
                self.command_stack.pop()
        # 如果在解析结束时仍存在未匹配的块标记,通过 self.unclosed_block_tag(parse_until) 抛出相应的异常。
        if parse_until:
            self.unclosed_block_tag(parse_until)
        return nodelist

5.Variable类、FilterExpression类和VariableNode类源码解读

  • 在Django框架中,Variable 类通常用于在模板中表示变量。模板系统是Django中用于生成动态HTML内容的一部分,它允许在模板中使用变量、表达式和控制结构。源码定义位于django/template/base.py文件中。

    from django.template.base import Variable
    
    class Variable:
        def resolve(self, context):
            """
            解析变量并返回其值
    
            参数:
            - context: 包含模板上下文信息的字典
    
            返回:
            - 解析后的变量值
            """
            # 在给定的上下文中解析变量并返回其值
            return context.get(self.var, '')
    
    #使用示例
     >>> c = {'article': {'section':'News'}}
     >>> Variable('article.section').resolve(c)
     'News'
     >>> Variable('article').resolve(c)
     {'section': 'News'}
     >>> class AClass: pass
     >>> c = AClass()
     >>> c.article = AClass()
     >>> c.article.section = 'News'
    
  • 在Django框架中,FilterExpression 类用于表示模板中的过滤器表达式。过滤器允许你在模板中对变量进行转换或格式化。源码定义位于django/template/base.py文件中。

    # 从 django.template.base 模块导入 FilterExpression 类
    from django.template.base import FilterExpression
    
    class FilterExpression:
        def __init__(self, token, parser):
            """
            构造函数
    
            参数:
            - token: 包含过滤器表达式的 Token 对象
            - parser: 当前模板的解析器对象
            """
            self.token = token
            self.parser = parser
            self.var = parser.compile_filter(token.contents)
            self.filters = []
    
        def add(self, filter_func, args, kwargs):
            """
            添加过滤器
    
            参数:
            - filter_func: 过滤器函数
            - args: 过滤器的位置参数
            - kwargs: 过滤器的关键字参数
            """
            self.filters.append((filter_func, args, kwargs))
    
        def resolve(self, context, ignore_failures=False):
            """
            解析过滤器表达式并返回结果
    
            参数:
            - context: 包含模板上下文信息的字典
            - ignore_failures: 是否忽略解析过程中的错误
    
            返回:
            - 解析后的结果
            """
            value = self.var.resolve(context, ignore_failures)
            
            # 依次应用过滤器
            for filter_func, args, kwargs in self.filters:
                args = [arg.resolve(context, True) for arg in args]
                kwargs = {key: value.resolve(context, True) for key, value in kwargs.items()}
                value = filter_func(value, *args, **kwargs)
    
            return value
    		
    	 # 运行示例
    	 >>> token = 'variable|default:"Default value"|date:"Y-m-d"'
         >>> p = Parser('')
         >>> fe = FilterExpression(token, p)
         >>> len(fe.filters)
         2
         >>> fe.var
         <Variable: 'variable'>
    ``
    

6.do_if()函数和do_for()函数的处理逻辑解读

  • do_if() 函数在解析 {% if ... %} 标签时被调用,它的工作是处理标签的内容并返回相应的节点。这个节点将被添加到模板的节点列表中,最终构建出表示整个模板结构的树状结构。源码定义位于django/template/defaulttags.py文件中。

  • do_if()函数在哪里被调用?
    使用 @register.tag('if') 装饰器将 do_if 函数注册为一个名为 ‘if’ 的自定义标签。在 Parser 类中,当解析器遇到一个标签时,它会尝试找到该标签对应的处理函数。

    class Parser:
    	def parse(self, parse_until=None):
    	   # 省略...
            elif token.token_type.value == 2:  # TokenType.BLOCK 如 {% if %} xxxx {% endif %}      
                     # 找到tags标签对应的处理函数,即如果command为if,则为do_if()函数
                    compile_func = self.tags[command]
             		# 省略...
    		#  执行do_if()函数
             compiled_result = compile_func(self, token)
    
    # 将 do_if 函数注册为一个名为 'if' 的自定义标签处理函数。
    @register.tag('if')
    def do_if(parser, token):
        # {% if ... %}
        #  提取出标签中除了 {% if 之外的部分,即条件表达式和其它参数。
        bits = token.split_contents()[1:]
        #  解析条件表达式,该表达式可以包含多个变量、比较操作符和逻辑运算符。
        condition = TemplateIfParser(parser, bits).parse()
        # 解析标签内的内容块,包括可能的 elif、else 和 endif。
        nodelist = parser.parse(('elif', 'else', 'endif'))
        # 将解析得到的条件表达式和内容块以元组的形式添加到 conditions_nodelists 列表中。
        conditions_nodelists = [(condition, nodelist)]
         #  获取下一个标签,并检查它是否是 elif、else 或 endif。
        token = parser.next_token()
    	
    	# 如果是 elif,重复步骤上述步骤
        # {% elif ... %} (repeatable)
        while token.contents.startswith('elif'):
            bits = token.split_contents()[1:]
            condition = TemplateIfParser(parser, bits).parse()
            nodelist = parser.parse(('elif', 'else', 'endif'))
            conditions_nodelists.append((condition, nodelist))
            token = parser.next_token()
    	
    	#  如果是 else,解析 else 块。
        # {% else %} (optional)
        if token.contents == 'else':
            nodelist = parser.parse(('endif',))
            conditions_nodelists.append((None, nodelist))
            token = parser.next_token()
    	
    	# 如果是 endif,结束循环。
        # {% endif %}
        if token.contents != 'endif':
            raise TemplateSyntaxError('Malformed template tag at line {}: "{}"'.format(token.lineno, token.contents))
    	
    	# 创建并返回一个 IfNode 对象,该对象包含了所有条件表达式和对应的内容块。
        return IfNode(conditions_nodelists)
    
  • do_for()函数的作用是解析 {% for … %} 标签及其内容,构建出一个带有循环条件、循环变量等信息的数据结构(ForNode 对象),该数据结构在模板渲染时会用于执行相应的循环逻辑。这使得在模板中可以使用循环语句,遍历序列中的每个元素。源码位于django/template/defaulttags.py文件中。

    @register.tag('for')
    def do_for(parser, token):
    	 # 将标签内容拆分成一个列表,以便进一步解析。
        bits = token.split_contents()
        is_reversed = False
        #  检查是否包含一些不允许在 for 标签中使用的关键字('reversed', 'as', 'with', 'if', 'else')。
        for invalid in ('reversed', 'as', 'with', 'if', 'else'):
            if invalid in bits:
                raise TemplateSyntaxError("'%s' is not allowed in 'for' tag" % invalid)
        #  确保标签的格式符合 {% for <variable> in <sequence> %} 或 {% for <variable> in <sequence> reversed %} 的形式
        if len(bits) < 4 or len(bits) > 5:
            raise TemplateSyntaxError("'for' statements should have at least four "
                                      "words: %s" % ' '.join(bits))
        # 解析序列(sequence)和循环变量(loopvars)
        sequence = parser.compile_filter(bits[2])
        parser.next_token()
        loopvars = []
        while parser.tokens[0].contents != 'for':
            # 这里解析循环变量
            loopvars.append(parser.tokens.pop(0))
        # 判断是否有reversed关键字
        if loopvars[-1].contents == 'reversed':
            loopvars.pop()
            is_reversed = True
        # 确保至少有一个循环变量
        if not loopvars:
            raise TemplateSyntaxError("'for' statement requires at least one loop variable")
        # 存储循环变量的名字
        unpack = None
        if loopvars[-1].contents == 'as':
            loopvars.pop()
            # 解析被循环的元素
            if parser.tokens[0].contents == 'var':
                parser.tokens.pop(0)
                unpack = parser.tokens.pop(0).contents
            else:
                raise TemplateSyntaxError("'%s' is not a valid name for a sequence of values" % parser.tokens[0].contents)
        nodelist_loop = parser.parse(('empty', 'endfor'))
        token = parser.next_token()
        # Check for empty
        if token.contents == 'empty':
            # 解析空块
            nodelist_empty = parser.parse(('endfor',))
            parser.delete_first_token()
        else:
            nodelist_empty = None
        return ForNode(
            vars=loopvars,
            sequence=sequence,
            is_reversed=is_reversed,
            unpack=unpack,
            nodelist_loop=nodelist_loop,
            nodelist_empty=nodelist_empty,
        )
    

7.IfNode和ForNode的渲染追踪

  • IfNode 是用于处理 {% if %} {% elif %} {% else %} {% endif %} 这类条件语句的节点。源码位于django/template/defaulttags.py文件中。

    from django.template import Node, NodeList, Variable, VariableDoesNotExist
    
    class IfNode(Node):
        def __init__(self, conditions, nodelist_true, nodelist_false=None):
            self.conditions = conditions
            self.nodelist_true = nodelist_true
            self.nodelist_false = nodelist_false
    
        def render(self, context):
            for condition, nodelist in self.conditions:
                if condition.eval(context):
                    return nodelist.render(context)
            if self.nodelist_false:
                return self.nodelist_false.render(context)
            return ''
    
    class IfParser(Parser):
        def parse(self, parse_until=None):
            # 解析 if 语句的条件和内容
            conditions = []
            while self.tokens.current_token.type not in (TOKEN_BLOCK, TOKEN_VARIABLE):
                # 解析条件
                self.tokens.next_token()
                condition = self.parse_expression()
                self.tokens.expect(TOKEN_BLOCK, 'endif')
                # 解析条件成立时的内容
                nodelist_true = self.parse(parse_until)
                # 添加条件和对应内容到列表
                conditions.append((condition, nodelist_true))
                # 检查是否有 elif,如果有继续解析
                if self.tokens.current_token.type == TOKEN_BLOCK and self.tokens.current_token.contents == 'elif':
                    continue
                # 如果没有 elif,则检查是否有 else
                elif self.tokens.current_token.type == TOKEN_BLOCK and self.tokens.current_token.contents == 'else':
                    # 解析条件不成立时的内容
                    self.tokens.next_token()
                    nodelist_false = self.parse(parse_until)
                    # 返回 IfNode 实例
                    return IfNode(conditions, nodelist_true, nodelist_false)
                # 如果没有 elif 和 else,则返回 IfNode 实例
                else:
                    return IfNode(conditions, nodelist_true)
    
    
  • IfNode 是用于处理 {% if %} {% elif %} {% else %} {% endif %} 这类条件语句的节点。源码位于django/template/defaulttags.py文件中。

    class ForNode(Node):
        def __init__(self, loopvars, sequence, is_reversed, nodelist_loop):
            self.loopvars = loopvars
            self.sequence = sequence
            self.is_reversed = is_reversed
            self.nodelist_loop = nodelist_loop
    
        def render(self, context):
            # 获取循环的序列
            sequence = self.sequence.resolve(context, True)
            # 处理反向循环
            if self.is_reversed:
                sequence = reversed(sequence)
            # 循环处理
            values = []
            for item in sequence:
                # 将循环变量加入上下文
                context.push()
                for i, var in enumerate(self.loopvars):
                    context[var] = item[i]
                # 渲染循环体
                values.append(self.nodelist_loop.render(context))
                context.pop()
            return ''.join(values)
    
    class ForParser(Parser):
        def parse(self, parse_until=None):
            # 解析 for 语句的循环变量和序列
            self.tokens.expect(TOKEN_BLOCK, 'for')
            loopvars = self.parse_assignments()
            self.tokens.expect(TOKEN_BLOCK, 'in')
            sequence = self.parse_expression()
            # 解析循环体
            self.tokens.expect(TOKEN_BLOCK, 'endfor')
            nodelist_loop = self.parse(parse_until)
            # 返回 ForNode 实例
            return ForNode(loopvars, sequence, False, nodelist_loop)
    
    
  • 31
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值