运维咖啡吧

享受技术带来的乐趣,体验生活给予的感动

Django Template 自定义全局变量

当我在编写一个高度自定义的后台系统时,我希望将网站的一些信息存储在数据库中,用户可以通过页面修改就能生效,就像下图配置一样

通常页面中用到的变量需要在view中回传,但站点名称网站中所有页面都需要用到,难道每一个view都需要回传一遍吗?这让我想到了页面中经常用到的获取用户名方法request.user.usernamerequest变量并没有在每个view中回传,但所有页面都可以调用,他是如何实现的?下文将为你详细介绍,了解之后可以通过编写自定义的全局变量,轻松解决以上问题

request哪里来的

在日常开发Django的过程中,如果你有用到默认的template,那么通常会通过request.user.username来获取登陆用户的用户名,你有没有想过这个request是哪来的?即便是后端view里不返回这边变量依然可以使用,怎么会如此神奇

这要从Django默认配置文件settings.py里的TEMPLATES配置说起,默认的TEMPLATES配置如下

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.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

其中BACKENDS指定了Djanog默认使用的模版引擎,默认的是Django自己开发的DjangoTemplates,当然你也可以替换成功能更为强大的jinja2DIRS指定了项目中模板文件的位置,APP_DIRS配置是否开启在已安装的app下查找template,OPTIONS则指定template的后端设置

之所以可以在模板中使用request的奥秘便隐藏在context_processors下,这个配置指定了Django渲染时执行的Python路径列表。当模板在渲染时,会执行context_processors列表中的所有函数,并将结果与上下文的context进行合并,也就是说模板接收到的参数除了render返回的context外,还有以上这个列表执行返回的结果

django.template.context_processors.request函数返回的字典中包含了request

def request(request):
    return {'request': request}

所以如下这样一个view,即便是返回结果中没有返回request,我们依然可以在setting.html模版中使用request.user

def setting(request):
    return render(request, 'setting.html', {})

同样的我们可以直接在模板中使用perms.opscoffee.select_user来判断用户是否拥有相应的权限,而不需要在view中返回perms,这也是因为django.contrib.auth.context_processors.auth返回了perms

def auth(request):
    """
    Return context variables required by apps that use Django's authentication
    system.

    If there is no 'user' attribute in the request, use AnonymousUser (from
    django.contrib.auth).
    """
    if hasattr(request, 'user'):
        user = request.user
    else:
        from django.contrib.auth.models import AnonymousUser
        user = AnonymousUser()

    return {
        'user': user,
        'perms': PermWrapper(user),
    }

类似requestperms这些在所有模板中都可以调用的变量,可以看作是django的全局变量了

如何自定义全局变量

上边我们知道了Django是如何定义全局变量requests的,那么我们只需照虎画猫写个类似的函数并加入context_processors就可以了,实现步骤如下

先在名为commons的APP下创建个文件context_processors.py,编写如下的函数,返回site作为全局变量

from commons.models import Setting


def site(request):
    site_name = Setting.objects.filter(key='site_name')
    site_title = Setting.objects.filter(key='site_title')

    site_name = site_name.first().value if site_name else ''
    site_title = site_title.first().value if site_title else ''

    return {'site': {'site_name': site_name, 'site_title': site_title}}

然后在TEMPLATEScontext_processors写上对应的路径

TEMPLATES = [
    {
        ...
        'OPTIONS': {
            'context_processors': [
                ...
                'commons.context_processors.site',  # 添加这一行信息
            ],
        },
    },
]

最后就可以在模板中通过site.site_title来获取站点title,以及通过site.site_name获取站点名称

<head>
  <title>{{ site.site_title }}</title>
</head>
<body>
  <div class="wrapper">
    <header class="main-header">
      <a href="/" class="logo">
        {{ site.site_name }}
      </a>
    </header>
  </div>
</body>

至此,问题顺利解决