網站框架模板(Django框架)

來源於公眾號:Python野路子一、模板介紹我們之前學習的,都是在視圖函數直接返回文本,在實際中我們更多的是帶有樣式的HTML代碼,這樣可以讓瀏覽器渲染出非常漂亮的頁面,目前市面上有非常多的模板系統,其中最常用的是DTL和Jinja2,DTL(Django Template Language),也就是Django自帶的模板語言,當然也可以配置Django支持Jinja2,但是作為Django內置的模板語言,不會產生一些不兼容的情況,最好還是使用內置的。1. DTL與普通的HTML文件區別DTL模板是一種帶有特殊語法的HTML文件,這個HTML文件可以被Django編譯,可以傳遞參數進去,實現數據動態化,在編譯完成後,生成一個普通的HTML文件,然後發送給客戶端。2. 模板渲染可以使用render函數進行渲染,將數據渲染到指定的HTML模板中。我們在之前的基礎上,再新建一個article的app,除瞭剛開始第一個應用,我們在pycharm創建項目的時候,填瞭app名字,pycharm會自動幫我們創建應用,後續如果還要再新建應用,我們都要使用命令方式才能新建app。應用創建完成瞭,接下來我們需要在配置文件settings.py裡面註冊應用app。INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'user.apps.UserConfig',   # 使用pycharm創建的應用,pycharm會自動註冊
    'article'  # 手工命令方式創建的應用app,需要手工在這註冊應用!
]
路由和視圖代碼:# 主urls.py
path('article/', include('article.urls', namespace='article'))
    
# 應用urls.py,創建的應用這個文件不會自動生成,需要我們新建

from django.urls import path
from . import views

app_name = 'article'
urlpatterns = [
    path('index/', views.index, name='index')

]

# 視圖views.py
from django.shortcuts import render

def index(request):
    return render(request, 'index.html')  # 使用render渲染模板
模板文件,項目根目錄下templates目錄(若沒有自動生成,則手工新建此文件夾)下新建index.html模板文件。<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>文章首頁</title>
</head>
<body>
    <h2>文章首頁</h2>
</body>
</html>
查看效果:使用render函數將模板成功渲染出來瞭。3. 模板文件查找路徑在項目的settings.py配置文件中,有一個TEMPLATES配置。TEMPLATES = [
    {
        # 模板引擎,就是Django內置的模板引擎,
        # 若要配置為Jinja2:django.template.backends.jinja2.Jinja2
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        # BASE_DIR,項目根目錄路徑下的templates查找
        #'DIRS': [os.path.join(BASE_DIR, 'templates')] # Django2中使用
        'DIRS': [BASE_DIR / 'templates'],  #  Django3中使用
        ,
        # 是否從app應用下的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',
            ],
        },
    },
]
這個配置包含瞭模板引擎的配置,這個配置包含瞭模板引擎的配置,模板查找路徑的配置,模板上下文的配置等,模板路徑可以2個地方配置。DIRS:這是一個列表,在這個列表中可以存放所有的模板路徑,以後在視圖中使用render渲染模板時,就會從這個列表的路徑中查找模板。APP_DIRS:默認為True,這個表示已經在INSTALLED_APPS文件中註冊過的應用app下的templates目錄下查找模板文件。查找順序:比如代碼render(request,'index.html'),先會在DIRS這個列表中依次查找路徑下有沒有這個模板,如果有,就返回,如果沒有,則會先檢查當前視圖所處的app是否已經安裝,那麼就先在當前app下的templates目錄下查找模板,如果沒有找到,就會去其他註冊的app中繼續查找,如果所有路徑下都沒有找到,則會拋出TemplateDoesNotExist。二、DTL模板語法1. 模板變量我們想要將後臺數據渲染到頁面上,這就需要用到模板變量。# 主urls.py
path('article/', include('article.urls', namespace='article'))

# 應用urls.py
path('index/', views.index, name='index')

# 視圖views.py
from django.shortcuts import render

class Article:
    def __init__(self, title):
        self.title = title

def index(request):
    context = {
        'msg': '模板變量',    # 字符串,
        'article': Article('Django框架之模板文件'),    # 實例一個對象,
        'author': {'name': 'admin'},    # 一個字典
        'tag': ['python入門', 'python進階', '數據庫']  # 列表或元組
    }

    return render(request, 'index.html', context=context)  # 傳入一個字典
模板文件:<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>文章首頁</title>
</head>
<body>
    <h2>文章首頁</h2>
    <p>{{ msg }}</p> <!– 在模板中使用變量,需要將變量放到 {{ 變量 }} 中 –>
    <h3>{{ article.title }}</h3>  <!– 要訪問對象的屬性,通過"對象.屬性名" 訪問 –>
    <p>作者:{{ author.name }}</p>  <!– 要訪問字典key對應值value,通過"字典.key" 訪問,不能[]形式 –>
    <ul>
        <li>{{ tag.0 }}</li>  <!– 要訪問列表或者元組的元素,通過"列表.索引" 訪問 –>
        <li>{{ tag.1 }}</li>
        <li>{{ tag.2 }}</li>
    </ul>
</body>
</html>
效果:在模板中使用變量:語法:{{變量名}} 1)命名由字母和數字以及下劃線組成,不能有空格和標點符號。2)不要和python或django關鍵字重名。原因:如果data是一個字典,那麼訪問data.items將會訪問data這個字典的key名為items的值,而不會訪問字典的items方法。2. 模板標簽標簽語法:{% 標簽名稱 %} {% 結束標簽名稱 %}。例:{%tag%} {%endtag%}2.1 if標簽if標簽相當於python中的if語句,有elif和else相對應,可以使用and、or、in、not、==、!=、<=、>=、is、is not,來進行判斷。視圖views.py:def index(request):
    age = random.randint(6, 20)  # 隨機生成6-20的整數

    context = {
        'age': age
    }

    return render(request, 'index.html', context=context)
模板文件:<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>文章首頁</title>
</head>
<body>
    <p>
        <span>年齡:<strong>{{ age }}</strong>,</span>
        <span>適合讀</span>
        <span>
            {% if age >= 18 %}
                【大學】書籍
            {% elif 12 < age and age < 18 %}
                【高中】書籍
            {% else %}
                【小學】書籍
            {% endif %}
        </span>
    </p>
</body>
</html>
再刷新一次:我們還可以使用ifequal/ifnotequal來比較兩個值是否相等,如:{% ifequal price 0 %}
<span class="publicimg"><img src="/images/public.png"></span>
{% else %}
<span class="publicimg"><img src="/images/vip.png"></span>
{% endifequal %}

2.2 for…in…標簽for標簽相當於python中的for循環,可以遍歷列表、元組、字符串、字典等一切可以遍歷的對象。# 視圖views.py
def index(request):
    context = {
        'articles': [              # 列表
            '21天學會Python',
            'C++從入門到入土',
            'Mysql從入門到跑路'
        ],
        'author': {             # 字典
            'name': '老P',
            'age': 18,
            'sex': 'boy'
        },
        'artile_details': [
            {'title': '21天學會Python', 'author': '小P', 'words': 1000},
            {'title': 'C++從入門到入土', 'author': '中P', 'words': 2000},
            {'title': 'Mysql從入門到跑路', 'author': '老P', 'words': 3000}
        ]
    }

    return render(request, 'index.html', context=context)
模板文件:<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>文章首頁</title>
</head>
<body>
<ul>
{% for article in articles %}
<li>{{ article }}</li>
{% endfor %}
</ul>
<p>獲取字典鍵:</p>
<ul>
{% for key in author.keys %}
<li>{{ key }}</li>
{% endfor %}
</ul>
<p>獲取字典值:</p>
<ul>
{% for value in author.values %}
<li>{{ value }}</li>
{% endfor %}
</ul>
<p>獲取字典鍵值對:</p>
<ul>
{% for key, value in author.items %}
<li>{{ key }}:{{ value }}</li>
{% endfor %}
</ul>

<p>文章信息列表:</p>
<table border="1">
<thead>
<tr>
<th>序號</th>
<th>標題</th>
<th>作者</th>
<th>字數</th>
</tr>
</thead>
<tbody>
{% for article_detail in artile_details %}
{% if forloop.first %} <!– 是否第一次遍歷 –>
<tr style="background: red;">
{% elif forloop.last %} <!– 是否最後一次遍歷 –>
<tr style="background: pink;">
{% else %}
<tr>
{% endif %}
<td>{{ forloop.counter }}</td> <!– 當前迭代的次數,下標從1開始。–>
<td>{{ article_detail.title }}</td>
<td>{{ article_detail.author }}</td>
<td>{{ article_detail.words }}</td>
</tr>
{% endfor %}
</tbody>
</table>

</body>
</html>

<!–
forloop.counter:當前迭代的次數,下標從1開始。
forloop.counter0:當前迭代的次數,下標從0開始。
forloop.first:返回bool類型,如果是第一次迭代,返回true,否則返回false。
forloop.last:返回bool類型,如果是最後一次迭代,返回True,否則返回False。

forloop.revcounter 反向循環位置(列表的最後一位是l ,列表第一位是n )
forloop.revcounter0 反向循環位置(列表的最後一位是0 , 列表第一位是n- 1 )
–>
還有個for…in…empty,在遍歷的時候如果需要遍歷的對象沒有為空,則執行empty下的操作。 {% for comment in comments %}
{{ comment }}
{# 在for循環之前檢查列表大小是常見的,當列表為空的時候給出特別提示,這是常見的,所以for支持可選empry來當為空時輸出 #}
{% empty %} <!– comments沒有或為空,則執行empty下的。–>
還沒有評論~~~
{% endfor %}
有時候可能在頁面上要用上循環幾次,可以用:{% for item in 'x'|ljust:'4' %} 循環四次
{%endfor %}
2.3 with標簽使用一個簡單地名字緩存一個復雜的變量,當你需要使用一個代價較大的方法(比如訪問數據庫)很多次的時候這是非常有用的。<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>文章首頁</title>
</head>
<body>
{{ title }}

{% with t1=title %} <!– 使用= –>
{{ t1 }}
{% endwith %}

{% with title as t2%} <!– 使用as 取別名 –>
{{ t2 }}
{% endwith %}
</body>
</html>
註意:定義的變量隻能在with語句塊中使用,在with語句塊外面使用取不到這個變量。2.4 url標簽在模板中,經常要寫一些超鏈接,比如a標簽中需要定義href屬性,當然如果通過硬編碼的方式直接將這個url寫死也是可以,但是不便於以後維護,比如需要修改url地址,那涉及到的地方都要修改,因此建議使用這種反轉的方式來實現,類似於django中的reverse一樣。# 應用urls.py
from django.urls import path
from . import views

app_name = 'article'
urlpatterns = [
    path('index/', views.index, name='index'),
    path('book/', views.book, name='book'),
    path('movie/', views.movie, name='movie'),
    path('city/', views.city, name='city'),
    path('book/hot/<int:book_id>', views.hot_book, name='hot_book')
]

# 視圖views.py
from django.shortcuts import render, HttpResponse

def index(request):
    return render(request, 'index.html')

def book(request):
    return HttpResponse('讀書頁面')

def movie(request):
    return HttpResponse('電影頁面')

def city(request):
    return HttpResponse('同城頁面')

def hot_book(request, book_id):
    return HttpResponse('最熱門文章:%d'%book_id)
模板文件:<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>文章首頁</title>
<style>
li{
list-style: none;
display: inline-block;
margin-right: 20px ;
}
</style>
</head>
<body>
<ul>
<li><a href="">首頁</a></li>
<li><a href="/article/book/">讀書</a></li> <!– url硬編碼的方式 –>
<li><a href="{% url 'article:movie' %}">電影</a></li> <!– url標簽 –>
<li><a href="{% url 'article:city' %}">同城</a></li>
<li><a href="{% url 'article:hot_book' book_id=1 %}">最火文章</a></li> <!– url標簽 傳遞參數,這個參數需要於url映射的參數名一樣,如果傳遞多個參數,則空格隔開 –>
</ul>
</body>
</html>
2.5 autoescape標簽DTL中默認已經開啟瞭自動轉義,會將哪些特殊字符進行轉義,比如會將<轉義成&lt等。def index(request):
    comment_info = '個人博客網站:<a href="http://www.qmpython.com"> 全民python</a>'
    context = {
        'info': comment_info
    }
    return render(request, 'index.html', context=context)
模板文件: <p>個人博客網站:<a href="http://www.qmpython.com"> 全民python</a></p>
 <p>
      {{ info }}   <!– DTL默認on開啟瞭自動轉義,轉換成普通字符串 –>
 </p>
 <p>
     {% autoescape off %}   <!– off關閉自動轉義,解析html標簽 –>
        {{ info }}
     {% endautoescape %}
 </p>
如果你不知道自己在幹什麼,最好是使用自動轉義,這樣網站才不容易出現XSS漏洞(比如有些網站進行評論的時候,發一些含惡意的js代碼,如果不進行自動轉義會進行渲染,而不會做成普通的字符串),比如我個人博客的評論就處理瞭。如果是可信用的,可以關閉自動轉義,比如我的文章內容是隻允許我個人發表,是可信任的,所以我直接關閉。後面可以通過過濾器safe類似處理。2.6 verbatim標簽默認在DTL模板中是會去解析那些特殊字符的,比如{% %}和{{ }}等,如果我們在某個代碼片段不想使用DTL的解析,那麼我們可以將這段代碼放在verbatim標簽中。# 視圖
def index(request):
    return render(request, 'index.html')

# 模板
<body>
    {% if msg %}
        {{ msg }}
    {% else %}
        沒消息
    {% endif %}
    <br>
    {% verbatim %}
        {% if msg %}
            {{ msg }}
        {% endif %}
    {% endverbatim %}
</body>
這個主要是用在,有些前端模板語法layui還有vue中也使用{{}}來表示變量,這些與DTL類似,這樣就容易引起混淆,我們可以使用這個標簽,來屏蔽掉DTL的解析,而讓前端模板去解析。2.7 註釋標簽我們在HTML中經常使用<!– 註釋內容 –>來進行註釋,在Django模板中,我們還可以使用其他註釋。{# 被註釋的內容 #}:將中間的內容註釋掉。隻能單行註釋。
{% comment %}被註釋的內容{% endcomment %}:可以多行註釋。
這種註釋,當我們查看網頁元素的時候是看不到的,即沒有被渲染,而<!– –>還是可以看到原樣字符串的。2.8 自定義簡單標簽1)首先,需要添加一個templatetags的文件夾, 自定義標簽必須處在已經安裝瞭的app(INSTALLED_APPS註冊瞭)中的一個名叫templatetags的包中。templatetags 文件夾名字不能修改,這是django規定的。myproject
    |——myproject
     |——__init__.py
     |——asgi.py
     |——settings.py
     |——urls.py
     |——wsgi.py
    |——templates
    |——myapp
     |——migrations
     |——templatetags
      |——__init__.py
      |——custom_tags.py
    |——manage.py

2)在templatetags下添加一個python文件,如我這裡創建一個custom_tags.py文件,在文件中添加對應的自定義標簽。from django import template

# register的名字是固定的,不可改變
register = template.Library()

#使用裝飾器註冊自定義標簽  
@register.simple_tag
def curr_date(args):#args可傳參數,根據實際需求而定
    return datetime.datetime.now().strftime(args)
3)在要使用自定義標簽的HTML模板中導入和使用自定義標簽文件:{# 加載自定義標簽所在的文件名,由於templatetags的文件名是固定的,django可以直接找到過自定義標簽文件所在的位置 #}
{% load custom_tags %}

<h3>自定義標簽</h3>
時間日期:{% curr_date "%Y-%m-%d"%}
我們再來定義簡單標簽article/templatetags/menu_tags.py:from django import template

# register的名字是固定的,不可改變
register = template.Library()
@register.simple_tag
def my_menu():
    menu_list = ['首頁','項目實戰','每日一練']  
  
    return menu_list

模板文件templates/article/my_menu.html:<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>自定義簡單標簽</title>
</head>
<body>
<h1>———–菜單欄————–</h1>
<br/>
{% load menu_tags %} <!– 導入自定義的標簽文件 –>

{% my_menu as menus %} <!– 註意:這裡需要使用as將標簽文件返回的值賦給一個變量,然後再引用!!!–>

<ul>
{% for menu in menus %} {# 企圖使用 {% for menu in my_menu %} 是沒效果的 #}
<li>{{menu}}</li>
{% endfor %}
</ul>

</body>
</html>
視圖渲染views.py:def test(request):
    return render(request, 'article/my_menu.html')
2.9 自定義包含標簽inclusion_tag一種比較普遍的tag類型隻是渲染其他模塊顯示下的內容,這樣的類型叫做inclusion_tag,常用的模板標簽是通過渲染其他模板顯示數據的。inclusion_tag作用:創建一個動態頁面文件a.html,這個頁面可以在另外一個頁面b.html中被調用,實現這個頁面a中有的功能。例如,上面一個導航頁面my_menu.html,基本在每個頁面中都要包含導航,如我的個人博客一樣:那要怎麼樣呢?例如,我們在首頁模板中,先試下用include來引入導航模板看看(include這個後面具體學習,這裡隻演示作用)。與上面自定義簡單標簽類似,在自定義標簽文件裡面自定義包含標簽:from django import template

from article.models import Column

# register的名字是固定的,不可改變
register = template.Library()
#使用裝飾器註冊自定義包含標簽
@register.inclusion_tag('article/my_menu.html') #將返回值傳給my_menu.html去,可以定義name,否則使用函數名
def my_menu():
    # menus = ['首頁','項目實戰','每日一練']
    menus = Column.objects.all()
    
    return {'menus':menus}  

my_menu.html:<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>自定義包含標簽</title>
</head>
<body>
<ul>
{% for menu in menus %}
<li> {{menu}} </li>
{% endfor %}
</ul>
</body>
</html>
調用的html頁面文件:<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>首頁</title>
</head>
<body>

{% load menu_tags %} <!– 必須要先加載創建標簽的文件–>

{% my_menu %} <!– 調用my_menu.html頁面文件,這裡使用該標簽的函數名來調用–>

<h1>首頁</h1>

</body>
</html>
視圖渲染:def test(request):

    return render(request, 'article/index.html')
2.10 人性化語義標簽除瞭上述功能性標簽外, Django 還提供瞭很多輔助性標簽,這些標簽隻是為瞭使變量輸 出變得更加可讀,下面對這些標簽進行簡單介紹。首先為瞭使用這些標簽,需要在INSTALLED_APPS 中註冊django .contrib.humanize, 然後在模板中引用humanize:{% raw %}{% load humanize % }{% endraw %}apnumber將數字1 ~ 9 轉換為英文單詞,但是其他數字不轉換,如數字10 將被原樣輸出。示例:數字1 被轉換為one ;
數字2 被轉換為two ;
數字10 仍顯示10 ;
如果當前工程語言是中文的話,數字將會被轉換為對應的漢字,例如:{% raw %}
{{ 1|apnumber }}
{{ 2|apnumber }}
{{ 5|apnurnber }}
{% endraw %}
如果當前工程語言是中文的話,數字將會被轉換為對應的漢字,例如:輸出:一


intcomma輸出以逗號分隔的數字,如4500 輸出4,500, 4500.2 輸出4,500.2 。intword以文字形式輸出數字,如1000000 輸出“ 1.0 million ”, 1200000 輸出“ 1,2 Million ” 。對於中文系統,將會輸出對應的中文,如1200000 輸出" 1.2 百萬” 。naturalday將當前日期以及前後一天輸出為today 、yesterday 和tomorrow ,而中文系統分別輸出 “今天”“昨天”和“明天” 。naturaltime對於日期時間格式,時間值與系統當前時間比較,然後輸出結果。如當前時間輸出 “ now ”, 29 秒前輸出“ 29 sec onds ago ” 。如果使用naturaltime 輸出今天、昨天、明天的話, 就會變成“現在”“ 23 小時·以後”“ 1 日之前” 。ordinal將數字轉換為序數,如l 輸出“ 1 st ”;2 輸出“ 2nd ”;3 輸出“ 3rd ” 。註意此時中文與 英文的輸出一樣。3. 模板過濾器在模板中,有時候需要對一些數據進行處理以後才能使用,一般在Python中我們是通過函數的形式來完成的,因為在DTL中,不支持函數的調用形式(),因此在模板中,則是通過過濾器來實現的,過濾器使用的是|來使用。語法:{{ 變量名|過濾器名:傳給過濾器的參數}}
3.1 常用過濾器比如使用date過濾器。# 視圖
from datetime import datetime

def index(request):
    now_time = datetime.now()
    context = {
        'now_time': now_time
    }
    return render(request, 'index.html', context=context)

# 模板文件
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>文章首頁</title>
</head>
<body>
    {{ now_time }} <!– 原始渲染 –>
    <br>
    {{ now_time | date:'Y-m-d H:i:s' }}  <!– 通過過濾器,先將日期格式化,再渲染出–>
</body>
</html>
date時間過濾器格式:格式字符描述示例Y4位數的年1999y2位數的年99m2位數的月01,09n1位數的月1,9,12d2位數的日01,09,31j1位數的日1,9,31g12小時制的一位數的小時1,9,12G24小時制的一位數小時0,8,23h12小時制的兩位數的小時01,09,12H24小時制的兩位數的小時01,13,24i分鐘00-59s秒00-59django常用過濾器:add:字符串相加,數字相加,列表相加,如果失敗,將會返回一個空字符串。default:提供一個默認值,在這個值被django認為是False的時候使用。比如:空字符串、None、[]、{}等。區別於default_if_none,這個隻有在變量為None的時候才使用默認值。first:返回列表中的第一個值。last:返回列表中的最後一個值。date:格式化日期和時間。time:格式化時間。join:跟python中的join一樣的用法。length:返回字符串或者是數組的長度。length_is:字符串或者是數組的長度是否是指定的值。lower:把所有字符串都編程小寫。truncatechars:根據後面給的參數,截斷字符,如果超過瞭用…表示。{{value|truncatechars:5}},如果value是等於北京歡迎您,那麼輸出的結果是北京…,為啥不是北京歡迎您…呢?因為三個點也占瞭三個字符,所以北京 + 三個點的字符長度就是5。truncatewords:類似truncatechars,不過不會切割html標簽,{{value|truncatewords:5}},如果value是等於<p>北京歡迎您</p>,那麼輸出的結果是<p>北京…</p>,capfirst:首字母大寫。slice:切割列表。用法跟python中的切片操作是一樣的,區間是前閉合後開放。striptags:去掉所有的html標簽。safe:關閉變量的自動轉義,解析html標簽。floatformat:浮點數格式化。更多可以查詢官方文檔:https://yiyibooks.cn/xx/Django_1.11.6/ref/templates/builtins.html英文:https://docs.djangoproject.com/en/1.11/ref/templates/builtins/過濾器總結:1、作用:對變量進行過濾。在真正渲染出來之前,過濾器會根據功能處理好變量,然後得出結果後再替換掉原來的變量展示出來。2、語法:{{greeting|lower}},變量和過濾器中間使用管道符號”|”進行使用。3、可以通過管道符號進行鏈式調用,比如實現一個功能,先把所有字符變成小寫,把第一個字符轉換成大寫,如{{message|lower|capfirst}}4、過濾器可以使用參數,在過濾器名稱後面使用冒號”:”再加上參數,比如要把一個字符串中所有的空格去掉,則可以使用cut過濾器,代碼如下{{message|cut:" "}},冒號和參數之間不能有任何空格,一定要緊挨著。3.2 自定義過濾器雖然DTL給我們內置瞭許多好用的過濾器,但是有些時候還是不能滿足我們的需求,因此Django給我們提供瞭一個接口,可以讓我們自定義過濾器,實現自己的需求。步驟:1、首先在某個應用app中,創建一個python包,叫做templatetags,註意名字不能改動,不然找不到。2、在這個templatetags包下創建一個python文件用來存儲過濾器。3、在新建的python文件中,定義過濾器(也就是函數),這個函數的第一個參數永遠是被過濾器的那個值,並且如果在使用過濾器的時候傳遞參數,那麼還可以定義另外一個參數,但是過濾器最多隻能有2個參數。4、自定義完過濾器,要使用django.template.Library.filter進行註冊。5、過濾器所在app所在app需要在settings.py中進行註冊。6、在模板中使用load標簽加載過濾器所在的python包。7、在模板中使用自定義的過濾器。我們來實現一個朋友圈,或者文章發表時間顯示的過濾器:# 自定義過濾器 date_filter.py
from django import template

from datetime import datetime, timedelta

# 將註冊類實例化為register對象
# register = template.Library() 創建一個全局register變量,它是用來註冊你自定義標簽和過濾器的,隻有向系統註冊過的tags,系統才認得你。
# register 不能做任何修改,一旦修改,該包就無法引用
register = template.Library()

# 使用裝飾器註冊自定義過濾器
@register.filter
def date_filter(value):
    # 判斷一下,是不是時間日期類型
    if not isinstance(value, datetime):
        return value

    now = datetime.now() + timedelta(hours=8)  # 將UTC時間轉為本地時間

    # total_seconds()是獲取兩個時間之間的總差
    timestamp = (now – value).total_seconds()

    if timestamp < 60:
        return '剛剛'
    elif 60 <= timestamp < 60*60:   # sec >= 60 and sec < 60*60
        mint = int(timestamp / 60)
        return '{}分鐘前'.format(mint)
    elif 60*60 <= timestamp < 60*60*24:
        hour = int(timestamp / 60 / 60)
        return '{}小時前'.format(hour)
    elif 60*60*24 <= timestamp < 60*60*24*2:
        return '昨天'
    else:
        return timestamp.strftime('%Y-%m-%d %H:%M:%S')

# 視圖views.py
from django.shortcuts import render, HttpResponse

from datetime import datetime

def index(request):
    context = {
        'public_time': datetime(year=2020, month=3, day=23, hour=23, minute=0, second=0)
    }
    print(context)
    return render(request, 'index.html', context=context)

# 模板文件
{% load date_filter %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>文章首頁</title>
</head>
<body>
    {{ public_time|date_filter }}

</body>
</html>
4. 模板結構化優化4.1 引入模板有時候一些代碼是在許多模板中都用到的,如果每次都重復的去拷貝代碼那肯定不符合項目的規范,一般我們可以把這些重復性的代碼抽取出來,就類似於Python的函數一樣,以後想要使用這些代碼的時候,就通過include包含進來,這個標簽就是include。比如大多數網頁都有頂部,中間,底部,可能進入任何頁面頂部和底部都是一樣的,隻有中間部分內容不同,這個時候頂部和底部,我們就可以通過include包含起來。# 頂部,header.html
<header>
    <ul>
        <li>首頁</li>
        <li>Python教程</li>
        <li>Python Web開發</li>
        <li>Python爬蟲</li>
    </ul>
    <p>用戶名:{{ username }}</p> <!– 用於測試是否傳參 –>
</header>

# 底部,footer.html
<footer>
    <div>©2019<a href="/">全民python</a> |
        <a href="http://beian.miit.gov.cn" rel="nofollow">粵ICP備18143893號</a> |
    </div>
</footer>

# 首頁,index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>文章首頁</title>
    <style>
        div{
            margin: 10px;
        }
    </style>
</head>
<body>
  {% include 'header.html' with username='全民python' %}  <!– 引入模板,並用with傳遞參數 –>
  <div>
    這是中間內容
  </div>
  {% include 'footer.html' %}
</body>
</html>
視圖:def index(request):
    return render(request, 'index.html')
我們使用include標簽時,如果引入的html代碼中需要傳入變量,則需要在所引入的view視圖中獲取變量,如果引入的html代碼中的變量是一個公共變量,則需要重復獲取N次,使用自定義包含標簽inclusion_tag,可以在定義文件中獲取一次即可。templates/article/my_menu.html<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>自定義簡單標簽</title>
</head>
<body>
<h1>———–菜單欄————–</h1>
<br/>
{% load menu_tags %} <!– 導入自定義的標簽文件 –>

{% my_menu as menus %} <!– 註意:這裡需要使用as將標簽文件返回的值賦給一個變量,然後再引用!!!–>

<ul>
{% for menu in menus %} {# 企圖使用 {% for menu in my_menu %} 是沒效果的 #}
<li>{{menu}}</li>
{% endfor %}
</ul>

</body>
</html>
article/templatetags/menu_tags.pyfrom django import template
from article.models import Column

# register的名字是固定的,不可改變
register = template.Library()
@register.simple_tag
def my_menu():
    #menu_list = ['首頁','項目實戰','每日一練']  
    menu_list = Column.objects.all()
  
    return menu_list
templates/article/index.html:<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>首頁</title>
</head>
<body>

{% include 'article/my_menu.html' %}

<h1>首頁</h1>

</body>
</html>
兩者感覺效果一樣。4.2 模板繼承對於模板的重復利用,除瞭使用include標簽引入,還有另外種方式來實現,那就是模板繼承。模板繼承類似於Python中的類,在父類中可以先定義好一些變量和方法,然後在子類中實現,模板繼承也可以在父模板中先定義好一些子模板需要用到的代碼,然後子模板直接繼承就可以瞭,並且因為子模板肯定有自己的不同代碼,因此可以在父模板中定義一個block接口,然後子模板再去實現。我們將上面那個例子,使用模板繼承改寫下,將固定不變的頂部和底部,放在父模板中,中間變動的部分,子模板去實現。<!– 父模板–>

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{% block title %}全民python{% endblock %}</title>
<style>
*{
margin: 0;
padding: 0;
}
header,footer{
background: black;
height: 60px;
width: 100%;
color: white;
text-align: center;
line-height: 40px;
}
footer{
height: 90px;
line-height: 90px;
position: absolute;
bottom: 0;
}
ul li{
list-style: none;
float: left;
margin: 20px;
}
#loginbox{
width: 400px;
height: 200px;
border: 1px solid red;
margin: 90px auto;
text-align: center;
}
</style>
</head>
<body>
<header>
<ul>
<li>首頁</li>
<li>編程語言</li>
<li>項目實戰</li>
<li>每日一學</li>
</ul>
</header>
<div class="content">
{% block content %}
這是子模板自己實現的部分
{% endblock %}
</div>
<footer >
這是footer部分
</footer>
</body>
</html>

<!– 子模板–>
{% extends 'base.html' %} <!– extends必須是模板中的第一個出現的標簽 –>
{% block title %}{{ block.super }}-登錄界面{% endblock %} <!– block.super繼承父類的內容,否則會覆蓋父類內容 –>
{% block content %}
<div id="loginbox">
<br/>
<h2>登錄界面</h2>
<br/>
<form>
帳 號:<input type="text">
<br/>
<br/>
密 碼:<input type="password">
<br/>
<br/>
<input type="submit" value="登錄">

<input type="button" value="註冊">
</form>
</div>
{% endblock %}
視圖函數:def index(request):
    return render(request, 'index.html')
效果展示:總結:模板繼承使用extends標簽實現,通過使用block來給子模板開放接口;extends必須是模板中第一個出現的標簽;子模板中的內容必須放在父模板定義好的block中,否則將不會被渲染;如果在某個block中需要使用父模板的內容,那麼可以使用{{ block.super }}來繼承;子模板決定替換的block塊,無須關註其它部分,沒有定義的塊即不替換,直接使用父模板的block塊;除瞭在開始的地方定義名字,我們還可以在結束的時候定義名字,如{% block title %}{% endblock %},這樣在大型模板中顯得尤其有用,能讓我們快速看到block所包含的開始和結束。模板引入與自定義inclusion_tag的區別:模板導入的頁面內容是靜態的、不變的,而通過自定義inclusion_tag導入的頁面文件可以是動態的,可動性自己掌控。5. 加載靜態文件在一個網頁中,不僅僅隻有一個html,還需要css樣式,js腳本以及一些圖片,因此在DTL中加載靜態文件是一個必須要解決的問題,在DTL中,使用static標簽來加載靜態文件,要使用static標簽,首先需要{% load static %}。1、首先,settings.py文件中,確保django.contrib.staticfiles已經添加到INSTALLED_APPS中。INSTALLED_APPS = [
    …
    'django.contrib.staticfiles',
    …
]
2、設置DEBUG調試模式。# 在settings.py中,默認值是DEBUG = True
DEBUG = True
當我們在開發django應用時如果設置瞭 DEBUG = True,那麼django便會自動幫我們對靜態文件進行路由;但是當我們設置DEBUG = False後,靜態文件找不到瞭,「img、css、js」都提示404,無法準確的訪問 static 靜態文件。3、在開發模式下(Debug=True)時,訪問靜態文件有下面兩種情況:1)在已註冊的應用app下創建一個靜態文件夾名稱static,然後再再這個static目錄下創建於app同名的 文件夾用於存放靜態文件(避免不同應用app,靜態文件名字一樣,造成沖突)。Django將通過 django.contrib.staticfiles在每個app的static文件夾中為我們自動查找這些靜態文件。2)如果有些靜態文件和應用沒什麼太大的關系,我們可以在項目根目錄下再創建static目錄,這個時候我們還需要在settings.py文件配置添加STATICFILES_DIRS,以後DTL就會在這個列表的路徑中查找靜態文件。# django2.x方式
# STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')]  # 這裡指靜態文件保存哪個目錄下,這個“static”指目錄名

# django3.x方式
# 一般用來設置通用的靜態資源,對應的目錄不放在APP下,而是放在Project下,例如:
STATICFILES_DIRS = [BASE_DIR / 'static']

然後再像上面一樣加載靜態文件使用,一般直接在根項目下新建static,然後在下面新建不同以應用名稱命名的目錄。STATICFILES_DIRS告訴django,首先到STATICFILES_DIRS裡面尋找靜態文件,其次再到各個app的static文件夾裡面找(註意,django查找靜態文件是惰性查找,查找到第一個,就停止查找瞭)。4、隻有在生產模式(Debug=False)時,STATIC_ROOT設置才生效。STATIC_ROOT 是在部署靜態文件時(pyhton manage.py collectstatic)所有的靜態文靜聚合的目錄,STATIC_ROOT要寫成絕對地址,如下例子STATIC_ROOT設為根目錄下的static_new文件,而STATICFILES_DIRS使用根目錄下的static目錄。#STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')]  # 這裡指靜態文件保存哪個路徑,這個“static”指目錄名
STATICFILES_DIRS = [BASE_DIR / 'static']

if not DEBUG:
    #STATIC_ROOT = os.path.join(BASE_DIR, "static_new") #使用 collectstatic後收集的靜態文件的存放絕對路徑
    STATIC_ROOT = [BASE_DIR / 'static_new']
當部署項目時,在終端輸入:# django會把所有的static文件都復制到STATIC_ROOT文件夾下
python manage.py collectstatic
然後可以看到根目錄下有創建瞭一個static_new文件夾,裡面有STATICFILES_DIRS裡設置的文件夾裡的靜態文件,同時也有各app下static文件夾裡的靜態文件。在部署模式(Debug=False)時,Django會從STATIC_ROOT設置的文件夾讀取靜態文件,而不再從STATICFILES_DIRS設置的文件夾或app下的static文件夾。所以在(Debug=False)時需要先python manage.py collectstatic同步一下靜態文件。說明:真實上生產,靜態文件可能就交給nginx瞭,配置好即可,就不會再從django裡面讀取靜態文件瞭。具體後續我們會細說5、上面我們已經配置好靜態文件瞭,那怎麼樣訪問呢?如果在瀏覽器是訪問,不可能輸入你的靜態文件的本地絕對地址吧,比如我的一種圖片的本地地址為 /root/src/project/QmpythonBlog/static/image/article/article_cover.png,那我們不可能在瀏覽器上直接輸入:http://127.0.0.1:5044/root/src/project/QmpythonBlog/static/image/article/article_cover.png 這樣子,瀏覽器會報錯, 沒有該頁面,那麼django是如何讓瀏覽器也可以訪問服務器上的靜態文件呢?上面已經說瞭,直接訪問服務器本地的地址是不行的,那就需要一個映射。django利用STATIC_URL來讓瀏覽器可以直接訪問靜態文件。settings.py文件中確保配置瞭STATIC_URL,STATIC_URL用於引用STATICFILES_DIRS或STATIC_ROOT所指向的靜態文件。當我們創建Django項目的時候,在setting.py中默認就已經設置瞭。# 靜態文件存儲,一般是我們的JS、css、系統的圖片文件等。
# 這個“static”指訪問靜態文件,引入時用的別名
# 通過http://127.0.0.1/static/***就可以訪問相關的靜態文件瞭。

STATIC_URL = '/static/'
例如:在應用user下新建static目錄,再在static下新建user目錄:視圖:# user應用下的視圖views.py

def index(request):
    return render(request, 'user/index.html')
模板文件:<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>用戶登錄首頁</title>
</head>
<body>
<p>用戶界面</p>
<img src="/static/user/img/logo.png"> <!– 如果配置文件是 STATIC_URL = '/abc/',則這裡url:/abc/user/img/logo.png–>
</body>
</html>
但是上面要寫絕對路徑不方便,那我們可以通過static標簽來加載靜態文件:{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>用戶登錄首頁</title>
</head>
<body>
<p>用戶界面</p>
{# <img src="/static/user/img/logo.png">#}

<img src="{% static '/user/img/logo.png' %}"> <!– 通過static標簽,django會去static目錄下尋找 –>
</body>
</html>
{% static %}這個模板變量使用的就是STATIC_URL的值,例如默認的STATIC_URL值為/static/,那麼上面模板變量引用後的值便為/static/開頭的地址。那麼STATIC_URL又是怎麼能正確的找到靜態文件地址的呢。1)在開發模式下(Debug=True)時,使用的是STATICFILES_DIRS和app下的static中的靜態文件,Django有默認的對STATIC_URL路由能自動找到放在裡面的靜態文件。2)在部署模式(Debug=False)時,使用的是STATIC_ROOT中的靜態文件,此時則沒有瞭默認的對STATIC_URL的路由,需要自己在project的urls.py中寫一個,將STATIC_URL開頭的請求轉發到STATIC_ROOT中進行查找。from django.views.static import serve
from django.conf.urls.static import static
urlpatterns = [
    re_path(r'^static/(?P<path>.*)#39;, serve, {'document_root': settings.STATIC_ROOT}, name='static'),
]
在部署生產環境時(Debug=False),通常使用另外一種方法就是使用Nginx來實現。如果你在項目中用到瞭static這個模板標簽,那一定要將nginx(或其他)服務器的/static配置到與STATIC_ROOT一致!(如果執行瞭收集的話)可以參考django部署配置:Centos7中使用Nginx+uWSGI+Django部署Web項目此時我們在應用中加載靜態文件的話就隻需要這麼來寫瞭,需要將STATIC_URL設為/static/,以img加載為例:<img src="/static/images/good.png" alt="My image"/>
額外知識點:1、如果不想在模板文件中每次都通過{% load static %}加載靜態文件。TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        #'DIRS': [os.path.join(BASE_DIR, 'templates')]  # django2寫法
         'DIRS': [BASE_DIR / 'templates'],  # django3寫法
        ,
        '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',
            ],
            'builtins': ['django.templatetags.static']  # 添加這一行,就會將static當作內置的標簽,無需再手動load
        },
    },
]
備註:一般個人傾向於,直接將static文件放到項目根目錄下,下面再根據app名字進行分門別類。#Django#

本文出自快速备案,转载时请注明出处及相应链接。

本文永久链接: https://kuaisubeian.cc/49019.html

kuaisubeian