Templates的最好实践

把模板文件放在 templates

目录结构


# 1 
templates/
├── base.html
├── ... (other sitewide templates in here)
├── freezers/
   ├── ("freezers" app templates in here)


# 2
freezers/
├── templates/
   ├── freezers/
      ├── ... ("freezers" app templates in here)
templates/
├── base.html
├── ... (other sitewide templates in here)


Templates 结构设计

flat is better than nested

2层的设计

templates/
├── base.html
├── dashboard.html # extends base.html
├── profiles/
 ├── profile_detail.html # extends base.html
 ├── profile_form.html # extends base.html

3层的设计

templates/
├──base.html
├──dashboard.html # extends base.html
├──profiles/
 ├──base_profiles.html # extends base.html
 ├──profile_detail.html # extends base_profiles.html
 ├──profile_form.html # extends base_profiles.html

限制, 减少Templates中的运算

# vouchers/models.py

from django.db import models
from django.urls import reverse

from .managers import VoucherManager

class Voucher(models.Model):
    """Vouchers for free pints of ice cream."""
    name = models.CharField(max_length=100)
    email = models.EmailField()
    address = models.TextField()
    birth_date = models.DateField(blank=True)
    sent = models.DateTimeField(null=True, default=None)
    redeemed = models.DateTimeField(null=True, default=None)
    
    objects = VoucherManager()

# vouchers/managers.py

from django.db import models
from django.utils import timezone
from dateutil.relativedelta import relativedelta

class VoucherManager(models.Manager):
    def age_breakdown(self):
    """Returns a dict of age brackets/counts."""
    age_brackets = []
    now = timezone.now()
    delta = now - relativedelta(years=18)
    count = self.model.objects.filter(birth_date__gt=delta).count()
    age_brackets.append(
        {'title': '0-17', 'count': count}
        )
    count = self.model.objects.filter(birth_date__lte=delta).count()
    age_brackets.append(
        {'title': '18+', 'count': count}
        )
    return age_brackets
  • Using Templates to Display Pre-Processed Data 将运算放在model中
{# templates/vouchers/ages.html #}
{  extends "base.html"  }
{  block content  }

<table>
    <thead>
        <tr>
            <th>Age Bracket</th>
            <th>Number of Vouchers Issued</th>
        </tr>
    </thead>
<tbody>

{  for age_bracket in age_brackets  }
    <tr>
        <td></td>
        <td></td>
    </tr>
{  endfor  }
</tbody>
</table>
{  endblock content   }

  • Filtering With Conditionals in Templates 将过滤放在model中

# bad example

<h2>Greenfelds Who Want Ice Cream</h2>
<ul>
{  for voucher in voucher_list  }
    {# Don't do this: conditional filtering in templates #}
    {  if 'greenfeld' in voucher.name.lower  }
        <li></li>
    {  endif  }
{  endfor  }
</ul>
<h2>Roys Who Want Ice Cream</h2>
<ul>
{  for voucher in voucher_list  }
    {# Don't do this: conditional filtering in templates #}
    {  if 'roy' in voucher.name.lower  }
        <li></li>
    {  endif  }
{  endfor  }
</ul>

应该把过滤等条件放在外部


# vouchers/views.py

from django.views.generic import TemplateView

from .models import Voucher

class GreenfeldRoyView(TemplateView):
    template_name = 'vouchers/views_conditional.html'
    
    def get_context_data(self, **kwargs):
        context = super(GreenfeldRoyView, self).get_context_data(**kwargs)
        context['greenfelds'] = \
        Voucher.objects.filter(name__icontains='greenfeld')
        context['roys'] = Voucher.objects.filter(name__icontains='roy')
        return context
        


# template

<h2>Greenfelds Who Want Ice Cream</h2>
<ul>
{  for voucher in greenfelds  }
    <li></li>
{  endfor  }
</ul>
<h2>Roys Who Want Ice Cream</h2>
<ul>
{  for voucher in roys  }
    <li></li>   
{  endfor  }
</ul>     

  • 连表的查询用 ORM’s `select_related()方法

# bad    User.objects.all()

{# list generated via User.objects.all() #}
<h1>Ice Cream Fans and their favorite flavors.</h1>
<ul>
{  for user in user_list  }
    <li>
    :
    {# DON'T DO THIS: Generated implicit query per user #}
    
    {# DON'T DO THIS: Second implicit query per user!!! #}
    
    </li>
{  endfor  }
</ul>



# good  User.objects.all().select_related('flavors')
{  comment  }
List generated via User.objects.all().select_related('flavors')
{  endcomment  }
<h1>Ice Cream Fans and their favorite flavors.</h1>
<ul>
{   for user in user_list  }
    <li>
    :
    
    
    </li>
{  endfor  }
</ul>

  • 减少模板中的API调用(来自第三方包的api可能会很耗时)

把所有processing task 耗时的任务, 计算 放在 views, models 或者异步的消息队列中(Celery, Django Channels)

保持模板样式


{# Use indentation/comments to ensure code quality #}
{# start of list elements #}
{  if list_type=='unordered'  }
    <ul>
{  else  }
    <ol>
{  endif  }
{  for syrup in syrup_list  }
    <li class="">
    <a href="{  url 'syrup_detail' syrup.slug  }">
    {  syrup.title  }
    </a>
</li>
{  endfor  }
{# end of list elements #}
{  if list_type=='unordered'  }
    </ul>
{  else  }
    </ol>
{  endif  }

模板继承 Template Inheritance

base.html 基础模板

{# simple base.html #}
{  load staticfiles  }
<html>
<head>
    <title>
        {  block title  }Two Scoops of Django{  endblock title  }
    </title>
    {  block stylesheets  }
        <link rel="stylesheet" type="text/css"
            href="{  static 'css/project.css'  }">
    {  endblock stylesheets  }
</head>
<body>
    <div class="content">
        {  block content  }
            <h1>Two Scoops</h1>
        {  endblock content  }
    </div>
</body>
</html>

➤ A title block containing “Two Scoops of Django”.
   标题
➤ A stylesheets block containing a link to a project.css file used across our site.
   静态文件
➤ A content block containing “<h1>Two Scoops</h1>”.
   内容

模板标签的用法

标签 目的
{ load } 引入静态文件 Loads the staticfiles built-in template tag library
{ block } 定义子类可以继承的模板块 Since base.html is a parent template, these define which child blocks can be filled in by child templates. We place links and scripts inside them so we can override if necessary
{ load } 解析静态文件 Resolves the named static media argument to the static media server.

继承模板

{  extends "base.html"  }
{  load staticfiles  }
{  block title  }About Audrey and Daniel{  endblock title  }
{  block stylesheets  }
    
    <link rel="stylesheet" type="text/css"
        href="{  static 'css/about.css'  }">
    {  endblock stylesheets  }
{  block content  }
    
    <h2>About Audrey and Daniel</h2>
    <p>They enjoy eating ice cream</p>
{  endblock content  }


# 得到的渲染结果

<html>
<head>
    <title>
        About Audrey and Daniel
    </title>
    <link rel="stylesheet" type="text/css"
        href="/static/css/project.css">
    <link rel="stylesheet" type="text/css"
        href="/static/css/about.css">
</head>
<body>
    <div class="content">
        <h1>Two Scoops</h1>
        <h2>About Audrey and Daniel</h2>
        <p>They enjoy eating ice cream</p>
    </div>
</body>
</html>


|标签|目的| |—|—| |{ extends } |继承父类模板 Informs Django that about.html is inheriting or extending from base.html |{ block }|替换子类的模板代码块| |``|把对应的block中的父类代码 放进子类 When placed in a child template’s block, it ensures that the parent’s content is also included in the block. In the content block of the about.html template, this will render <h1>Two Scoops</h1>|

使用模板需要注意的地方

  • 使用精确易懂的名称

{# templates/toppings/topping_list.html #}
{# Using implicit names, good for code reuse #}
<ol>
{  for object in object_list  }
    <li> </li>
{  endfor  }
</ol>

{# Using explicit names, good for object specific code #}
<ol>
{  for topping in topping_list  }
    <li> </li>
{  endfor  }
</ol>


  • 使用 URL Name({ url } ) 而不是hardcode path
# bad
<a href="/flavors/">

# good
<a href="{  url 'flavors:list'   }">


  • 通过 settings 设置 string_if_invalid, 获取更多的 模板错误信息
# settings/local.py
TEMPLATES = [
    {
    'BACKEND': 'django.template.backends.django.DjangoTemplates',
    'APP_DIRS': True,
    'OPTIONS':
        'string_if_invalid': 'INVALID EXPRESSION:  s'
    },
]

处理异常的 模板

使用单独的静态文件服务器处理静态文件(Nginx + Apache), web项目挂掉不会影响, 异常处理

PaaS平台上, 用户可以自己设置静态文件

Github实例

github.com/404 github.com/500

Buy me a 肥仔水!