整合營銷服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢熱線:

          在Django中使用Markdown

          為開發(fā)人員,我們依賴于靜態(tài)分析工具來檢查、lint(分析)和轉(zhuǎn)換我們的代碼。我們使用這些工具來幫助我們提高生產(chǎn)效率并生成更好的代碼。然而,當(dāng)我們使用markdown編寫內(nèi)容時(shí),可用的工具就很少。

          在本文中,我們將介紹如何開發(fā)一個(gè)Markdown擴(kuò)展來解決在使用Markdown管理Django站點(diǎn)中的內(nèi)容時(shí)遇到的挑戰(zhàn)。

          你認(rèn)為他們有l(wèi)inter嗎?

          照片來自Pexels,由mali maeder拍攝

          問題

          像每個(gè)網(wǎng)站一樣,我們在主頁、FAQ部分和“關(guān)于”頁面等地方都有不同類型的(大部分)靜態(tài)內(nèi)容。很長一段時(shí)間以來,我們都是在Django模板中直接管理這些內(nèi)容的。

          當(dāng)我們最終決定是時(shí)候?qū)⑦@些內(nèi)容從模板轉(zhuǎn)移到數(shù)據(jù)庫中時(shí),我們認(rèn)為最好使用Markdown。從Markdown生成HTML更安全,它提供了一定程度的控制和一致性,并且對于非技術(shù)用戶來說更容易處理。隨著我們轉(zhuǎn)移過程的進(jìn)展,我們注意到我們遺漏了一些東西:

          內(nèi)部鏈接

          當(dāng)URL更改時(shí),鏈接到內(nèi)部頁面的鏈接可能會中斷。在Django模板和視圖中,我們使用了reverseand {% url %},但是這在普通的Markdown中是不可用的。

          在不同環(huán)境之間進(jìn)行復(fù)制

          絕對內(nèi)部連接不能在不同環(huán)境之間進(jìn)行復(fù)制。這可以使用相對鏈接來解決,不過目前沒有開箱即用的增強(qiáng)這一點(diǎn)的方法。

          無效鏈接

          無效鏈接會損害用戶體驗(yàn),并導(dǎo)致用戶質(zhì)疑整個(gè)內(nèi)容的可靠性。這并不是Markdown獨(dú)有的東西,只不過HTML模板是由對URL有一定了解的開發(fā)人員維護(hù)的。另一方面,Markdown文檔是為非技術(shù)寫作人員設(shè)計(jì)的。

          前期工作

          當(dāng)我研究這個(gè)問題時(shí),我搜索了Python linters、Markdown預(yù)處理器和擴(kuò)展來幫助生成更好的Markdown。結(jié)果都不是很好。一個(gè)引人注目的方法是使用Django模板來生成Markdown文檔。

          使用Django模板預(yù)處理Markdown

          使用Django模板,你可以使用諸如url之類的模板標(biāo)記來反向查詢URL名稱,并配合使用條件、變量、日期格式和所有其他Django模板特性。這種方法本質(zhì)上是使用Django模板作為Markdown文檔的預(yù)處理程序。

          我個(gè)人認(rèn)為這可能不是非技術(shù)作家的最佳解決方案。另外,我擔(dān)心提供對Django模板標(biāo)記的訪問可能是危險(xiǎn)的。

          使用 Markdown

          對這個(gè)問題有了更好的理解之后,我們準(zhǔn)備在Python中更深入地研究Markdown。

          將Markdown轉(zhuǎn)換為HTML

          要在Python中開始使用Markdown,我們先安裝markdown包:

          接著,創(chuàng)建一個(gè)Markdown對象并使用其函數(shù)將一些Markdown轉(zhuǎn)換成HTML:

          你現(xiàn)在可以在你的模板中使用這個(gè)HTML代碼片段。

          使用Markdown擴(kuò)展

          基本的Markdown處理器提供了生成HTML內(nèi)容的基本要素。對于更“新奇”的選項(xiàng),Python markdown包包含了一些內(nèi)置擴(kuò)展。一個(gè)流行的擴(kuò)展是“extra”擴(kuò)展,除了其他東西之外,它增加了對隔離代碼塊的支持:

          為了使用我們獨(dú)特的Django功能擴(kuò)展Markdown,我們將開發(fā)自己的擴(kuò)展。

          創(chuàng)建一個(gè)Markdown擴(kuò)展來處理內(nèi)聯(lián)鏈接

          如果你查看源代碼,你將看到要將markdown轉(zhuǎn)換為HTML, Markdown會使用多種不同的處理器。一種類型的處理器是內(nèi)聯(lián)處理器。內(nèi)聯(lián)處理器會匹配特定的內(nèi)聯(lián)模式,如鏈接、反引號、粗體文本和帶下劃線的文本,并將它們轉(zhuǎn)換為HTML。

          我們的Markdown擴(kuò)展的主要目的是驗(yàn)證和轉(zhuǎn)換鏈接。因此,我們最感興趣的內(nèi)聯(lián)處理器是LinkInlineProcessor。這個(gè)處理器以[Haki的網(wǎng)站](https://hakibenito.com)的形式獲取markdown ,解析它并返回一個(gè)包含鏈接和文本的元組。

          為了擴(kuò)展該功能,我們擴(kuò)展了LinkInlineProcessor并創(chuàng)建了一個(gè)Markdown.Extension, 我們用它來處理鏈接:

          我們來將這段代碼分解一下::

          • DjangoUrlExtension擴(kuò)展注冊了一個(gè)名為DjangoLinkInlineProcessor的內(nèi)聯(lián)鏈接處理器。這個(gè)處理器將取代任何其他現(xiàn)有的鏈接處理器。

          • 內(nèi)聯(lián)處理器DjangoLinkInlineProcessor擴(kuò)展了內(nèi)置的LinkInlineProcessor,并在它處理的每個(gè)鏈接上調(diào)用clean_link函數(shù)。

          • clean_link函數(shù)接收一個(gè)鏈接和一個(gè)域名,并返回一個(gè)轉(zhuǎn)換后的鏈接。這就是我們要插入我們的實(shí)現(xiàn)的地方。

          如何獲得網(wǎng)站域名


          要識別到你自己網(wǎng)站的鏈接,你必須知道你的網(wǎng)站的域名。如果你正在使用Django的sites框架,那么你可以使用它來獲取當(dāng)前域名。


          我沒有把它包含在我的實(shí)現(xiàn)中,因?yàn)槲覀儧]有使用sites框架。相反,我們在Django設(shè)置中設(shè)置了一個(gè)變量。


          獲取當(dāng)前域名的另一種方法是使用HttpRequest對象。如果內(nèi)容只在你自己的站點(diǎn)中被編輯,你可以嘗試從請求對象中插入站點(diǎn)域名。這可能需要對你的實(shí)現(xiàn)進(jìn)行一些更改。

          要使用該擴(kuò)展,請?jiān)诔跏蓟粋€(gè)新的Markdown實(shí)例時(shí)添加它:

          太好了,這個(gè)擴(kuò)展已經(jīng)被使用了,我們準(zhǔn)備進(jìn)入有趣的部分了!

          驗(yàn)證和轉(zhuǎn)換Django鏈接

          既然我們得到了在所有鏈接上調(diào)用clean_link的擴(kuò)展,那我們可以來實(shí)現(xiàn)我們的驗(yàn)證和轉(zhuǎn)換邏輯。

          驗(yàn)證mailto鏈接

          要開始工作,我們將從一個(gè)簡單的驗(yàn)證開始。mailto鏈接對于使用預(yù)定義的收件人地址、主題甚至消息正文打開用戶的電子郵件客戶端非常有用。

          一個(gè)常見的mailto鏈接是這樣的:

          這個(gè)鏈接將打開你的電子郵件客戶端,并設(shè)置成撰寫一封主題行為“我需要幫助!”的新電子郵件給“support@service.com”。

          mailto鏈接不一定非要包含電子郵件地址。如果你看一看這篇文章底部的“分享”按鈕,你會發(fā)現(xiàn)像這樣的一個(gè)mailto鏈接:

          這個(gè)mailto鏈接沒有包含收件人,僅包含了主題行和消息正文。

          既然我們已經(jīng)很好地理解了mailto鏈接是什么樣子的,我們就可以向clean_link函數(shù)添加第一個(gè)驗(yàn)證:

          為了驗(yàn)證mailto鏈接,我們向clean_link中添加了以下代碼:

          • 檢查鏈接是否以mailto:開頭,以識別相關(guān)鏈接。

          • 使用正則表達(dá)式將鏈接分割到它的組件。

          • 從mailto鏈接中刪除實(shí)際的電子郵件地址,并使用Django的EmailValidator驗(yàn)證它。

          注意,我們還添加了一種名為InvalidMarkdown的新異常類型。我們定義了自己的自定義異常類型,以將它與markdown本身所引發(fā)的其他錯(cuò)誤區(qū)分開來。

          自定義錯(cuò)誤類

          我曾經(jīng)寫過關(guān)于自定義錯(cuò)誤類的文章,為什么它們是有用的,以及你什么時(shí)候應(yīng)該使用它們。

          在我們繼續(xù)之前,讓我們添加一些測試,看看它的實(shí)際效果:

          太棒了!按預(yù)期的運(yùn)行了。

          處理內(nèi)部和外部鏈接

          既然我們已經(jīng)了解了mailto鏈接,我們也可以處理其他類型的鏈接:

          外部鏈接

          • 我們的Django應(yīng)用程序外部的鏈接。

          • 必須包含一個(gè)頁面跳轉(zhuǎn)協(xié)議(scheme):http或https。

          • 理想情況下,我們還希望確保這些鏈接沒有被破壞,但我們現(xiàn)在不會這樣做。

          內(nèi)部鏈接

          • 到我們的Django應(yīng)用程序中的頁面的鏈接。

          • 鏈接必須是相對的:這將允許我們在不同環(huán)境之間移動內(nèi)容。

          • 使用Django的URL名稱而不是一個(gè)URL路徑:這將允許我們安全地來回移動視圖,而不必?fù)?dān)心markdown內(nèi)容中的失效鏈接。

          • 鏈接可能包含查詢參數(shù)(?)和片段(#)。

          SEO

          從SEO的角度來看,公共URL不應(yīng)該改變。當(dāng)他們這樣做的時(shí)候,你應(yīng)該使用重定向正確地處理它,否則你可能會受到搜索引擎的懲罰。

          有了這個(gè)需求列表,我們就可以開始工作了。

          解析URL名稱

          要鏈接到內(nèi)部頁面,我們希望編寫者提供一個(gè)URL名稱,而不是URL路徑。例如,假設(shè)我們有這個(gè)視圖:

          這個(gè)頁面的URL路徑是https://example.com/, URL名稱是home。我們想要在我們的markdown鏈接中使用這個(gè)URL名稱home,就像這樣:

          這將渲染到:

          我們還想支持查詢參數(shù)和散列:

          這將渲染到以下HTML:

          在使用URL名稱時(shí),如果我們更改了URL路徑,內(nèi)容中的鏈接將不會被破壞。要檢查作者提供的href是否是一個(gè)有效的url_name,我們可以嘗試reverse它:

          URL名稱“home”指向URL路徑“/”。當(dāng)沒有匹配項(xiàng)時(shí),將會引發(fā)一個(gè)異常:

          在我們繼續(xù)之前,當(dāng)URL名稱包含查詢參數(shù)或散列時(shí),會發(fā)生什么:

          這是有意義的,因?yàn)椴樵儏?shù)和散列不是URL名稱的一部分。

          要使用reverse并支持查詢參數(shù)和散列,我們首先需要清除值。然后,檢查它是一個(gè)有效的URL名稱,并返回包含查詢參數(shù)和散列的URL路徑,如果提供了的話:

          這個(gè)代碼段使用一個(gè)正則表達(dá)式來以?或#的出現(xiàn)對href進(jìn)行分割,并返回各部分。

          請確保它可以工作:

          太了不起了!作者們現(xiàn)在可以在Markdown中使用URL名稱了。它們還可以包括要添加到該URL的查詢參數(shù)和片段。

          處理外部鏈接

          要正確處理外部鏈接,我們需要檢查兩件事:

          1.外部鏈接總是提供一個(gè)跳轉(zhuǎn)協(xié)議,http:或者h(yuǎn)ttps:。

          2.阻止到我們自己網(wǎng)站的絕對鏈接。內(nèi)部鏈接應(yīng)該使用URL名稱。

          到目前為止,我們已經(jīng)處理了URL名稱和mailto鏈接。如果我們通過了這兩個(gè)檢查,這意味著href是一個(gè)URL。讓我們從檢查鏈接是否是鏈接到我們自己的網(wǎng)站開始:

          函數(shù)urlparse會返回一個(gè)命名元組,該元組包含URL的不同部分。如果netloc屬性等于site_domain,那么該鏈接就確實(shí)是一個(gè)內(nèi)部鏈接。

          如果URL實(shí)際上是內(nèi)部的,我們就需要終止。但是,請記住,作者們不一定是技術(shù)人員,因此我們希望幫助他們,并提供一個(gè)有用的錯(cuò)誤消息。我們要求該內(nèi)部鏈接使用URL名稱而不是URL路徑,所以最好讓作者們知道他們提供的路徑的URL名稱。

          要獲得一個(gè)URL路徑的URL名稱,Django為我們提供了一個(gè)名為resolve的函數(shù):

          當(dāng)找到匹配項(xiàng)時(shí),resolve會返回一個(gè)ResolverMatch對象,其中包含URL名稱和其他信息。當(dāng)沒有找到匹配項(xiàng)時(shí),它就會引發(fā)一個(gè)錯(cuò)誤:

          這實(shí)際上就是Django在底層所做的工作,用來確定在一個(gè)新請求到來時(shí)執(zhí)行哪個(gè)視圖函數(shù)。

          為了給作者們提供更好的錯(cuò)誤信息,我們可以使用來自ResolverMatch對象的URL名稱:

          當(dāng)我們識別出內(nèi)部鏈接時(shí),我們要處理兩種情況:

          • 我們沒有識別出這個(gè)URL:這個(gè)URL很可能是不正確的。請作者檢查該URL是否有錯(cuò)誤。

          • 我們識別出了這個(gè)URL: 這個(gè)URL是正確的,所以就告訴作者應(yīng)該使用什么URL名稱。

          我們來實(shí)際地看一下它:

          漂亮!外部鏈接被接受,內(nèi)部鏈接被拒絕,并帶有一個(gè)有用的消息。

          要求跳轉(zhuǎn)協(xié)議

          我們要做的最后一件事是確保外部鏈接包含一個(gè)跳轉(zhuǎn)協(xié)議,要么是http:,要么是https:。讓我們將這最后一部分添加到函數(shù)clean_link:

          使用解析后的URL,我們可以很容易地檢查跳轉(zhuǎn)協(xié)議。讓我們確保它正在工作:

          我們向這個(gè)函數(shù)提供了一個(gè)沒有跳轉(zhuǎn)協(xié)議的鏈接,但是它運(yùn)行失敗了,并顯示了一條有用的消息。太酷了!

          整合代碼

          這是clean_link函數(shù)的全部代碼:

          要了解所有這些特性的一個(gè)實(shí)際用例是什么樣子的,請看下面的內(nèi)容:

          這將產(chǎn)生以下HTML:

          不錯(cuò)!

          結(jié)論

          我們現(xiàn)在有一個(gè)很不錯(cuò)的擴(kuò)展,它可以驗(yàn)證和轉(zhuǎn)換Markdown文檔中的鏈接!現(xiàn)在,在不同環(huán)境之間移動文檔和保持內(nèi)容整潔要容易多了,最重要的是,可以保持正確和最新!

          源碼

          你可以在這個(gè)gist中找到全部源代碼。(地址:https://gist.github.com/hakib/73fccc340e855bb65f42197e298c0c7d )

          題外話

          本文中所描述的功能對我們很有用,但是你可能需要根據(jù)自己的需求對它進(jìn)行調(diào)整。

          如果你需要一些想法,那么除了這個(gè)擴(kuò)展之外,我們還創(chuàng)建了一個(gè)markdown Preprocessor,它允許作者們在markdown中使用常量。例如,我們定義了一個(gè)名為SUPPORT_EMAIL的常量,我們像這樣使用它:

          該預(yù)處理程序?qū)⒂梦覀兌x的文本替換字符串$SUPPORT_EMAIL,然后才渲染Markdown。

          英文原文:https://hakibenita.com/django-markdown
          譯者:Nothing

          長文創(chuàng)作激勵(lì)計(jì)劃#

          模板不僅僅是靜態(tài)的HTML,在渲染的過程中經(jīng)常包含著一部分變量。在Django中,通過一個(gè)類似字典的對象context,把所有模板中需要用到的變量從view傳遞到template。比如搜索結(jié)果列表和推薦列表顯示樣式和顯示信息都一樣,此種情況下,若模板根據(jù)不同變量顯示不同數(shù)據(jù)的方式進(jìn)行頁面的渲染,則需要?jiǎng)?chuàng)建不同的模板進(jìn)行顯示,會做很多重復(fù)性的工作,并造成代碼的冗余和結(jié)構(gòu)的繁雜。在模板中使用變量的便利遠(yuǎn)不止此,例如還可以根據(jù)傳遞變量類型或者值得不同顯示不同的樣式和信息。

          [!TIP]

          與其它語言不同的是,在Django中,view中的變量不會自動在template中可用,必須通過view準(zhǔn)確傳遞到template中,這樣設(shè)計(jì)的目的是為了避免破壞模板的命名空間。

          和其它語言PHP、Vue中的用法一樣,Django的模板中的變量也是通過{{ }}進(jìn)行表示,例如顯示變量name:{{ name }}。即使變量中包含一些特殊字符(如:<),也不需要擔(dān)心會影響顯示結(jié)果,因?yàn)镈jango的也會自動避免將其以HTML的形式進(jìn)行顯示。

          [!NOTE]

          如果在模板中用到了沒有傳遞的變量,Django只會在相應(yīng)位置不做任何顯示,而不會拋出異常。

          示例:

          1、以my_site項(xiàng)目為例,在Django應(yīng)用myapp中創(chuàng)建templates文件夾,并在文件夾內(nèi)創(chuàng)建welcome.html文件,其結(jié)構(gòu)和內(nèi)容如下:

          示例結(jié)構(gòu)

          2、修改views.py文件,在其中添加welcome方法,其內(nèi)容如下:

           from django.http import HttpResponse
           from django.shortcuts import render
           
           
           # Create your views here.
           def welcome(request):
               name="World"
               return render(request, "welcome.html" ,{"name":name})

          3、在myapp中的urls.py進(jìn)行配置,其內(nèi)容如下:

           from django.contrib import admin
           from django.urls import path
           from .views import welcome
           
           urlpatterns = [
               path('welcome/', welcome, name="welcome"),
           ]

          4、修改my_site項(xiàng)目的urls.py文件,引用myapp中的urls.py,其內(nèi)容如下:

           from django.contrib import admin
           from django.urls import path, include
           
           urlpatterns = [
               path('admin/', admin.site.urls),
               path("", include("myapp.urls")),
           ]

          5、通過python manage.py runserver命令運(yùn)行項(xiàng)目,其顯示如下:

          示例效果圖



          該文章作為學(xué)習(xí)筆記進(jìn)行分享和記錄,如有錯(cuò)誤或其它觀點(diǎn),請多多指教!!!

          web開發(fā)中,經(jīng)常需要顯示展示層次化的數(shù)據(jù),比如文件目錄、產(chǎn)品分類目錄、組織結(jié)構(gòu)等,這是典型的樹形數(shù)據(jù)結(jié)構(gòu)。Django是web開發(fā)中使用非常廣泛的框架,那么你知道怎么使用Django模塊渲染樹形數(shù)據(jù)結(jié)構(gòu)嗎?本文將會給出答案。

          我們知道編程語言大多都有遞歸調(diào)用的能力,遍歷樹形數(shù)據(jù)結(jié)構(gòu)往往都需要進(jìn)行遞歸調(diào)用,而Django使用模板引擎進(jìn)行頁面的渲染,而樹形結(jié)構(gòu)數(shù)據(jù)往往都是從數(shù)據(jù)庫動態(tài)獲取的,嵌套的層級深度并不是確定的,因此Django模板必須能夠做到類似遞歸調(diào)用的能力才能做到渲染樹形數(shù)據(jù)結(jié)構(gòu)。幸運(yùn)地的是,Django模板引擎是支持遞歸引用的,接下來我們通過一個(gè)簡單的例子來詳細(xì)介紹Django模板如何通過include指令遞歸引用模塊文件,最終渲染出樹形目錄結(jié)構(gòu)。這里不會從頭介紹Django如何創(chuàng)建項(xiàng)目,只針對關(guān)鍵文件進(jìn)行講解,因此需要讀者具備一定的Django基礎(chǔ)。

          準(zhǔn)備樹形結(jié)構(gòu)數(shù)據(jù)

          一般情況下,樹形結(jié)構(gòu)數(shù)據(jù)都是從數(shù)據(jù)庫動態(tài)生成的,為了突出重點(diǎn),這里僅給出最終的樹形數(shù)據(jù)結(jié)構(gòu)示例。我們把它寫在views.py文件中,tree_list為樹形數(shù)據(jù),列表中的每一項(xiàng)包含id、name、parent_id、children,其中children為子節(jié)點(diǎn),包含同樣的結(jié)構(gòu),可以一直嵌套下去。

          from django.shortcuts import render
          
          # Create your views here.
          def index(request):
              """
              tree_list數(shù)據(jù)結(jié)構(gòu)形式:[
                  {
                      "id": xx, 
                      "name": xxx, 
                      "parent_id": xxx, 
                      "children": [
                          {
                              "id": xx,
                              "name": xxx, 
                              "parent_id": xxx, 
                              "children": []
                          }
                      ]
                  },
                  ...
              ]
              """
              context = {}
              context["tree_list"] =  [{
                          "id": 2,
                          "name": "類別A",
                          "parentId": None,
                          "children": [
                              {
                                  "id": 8,
                                  "name": "類別A1",
                                  "parentId": 2,
                                  "children": [
                                      {
                                          "id": 137,
                                          "name": "類別A11",
                                          "parentId": 8,
                                      }
                                  ]
                              },
                              {
                                  "id": 221,
                                  "name": "類別A2",
                                  "parentId": 2,
                              }
                          ]
                      },
                      {
                          "id": 52,
                          "name": "類別B",
                          "parentId": None,
                          "children": [
                              {
                                  "id": 54,
                                  "name": "類別B1",
                                  "parentId": 52,
                                  "fileCount": 10,
                                  "children": [
                                      {
                                          "id": 55,
                                          "name": "類別B11",
                                          "parentId": 54,
                                          "children": [
                                              {
                                                  "id": 56,
                                                  "name": "類別B111",
                                                  "parentId": 55,
                                                  "children": [
                                                      {
                                                          "id": 57,
                                                          "name": "類別B1111",
                                                          "parentId": 56,
                                                          "children": [
                                                              {
                                                                  "id": 58,
                                                                  "name": "類別B11111",
                                                                  "parentId": 57,
                                                              }
                                                          ]
                                                      }
                                                  ]
                                              }
                                          ]
                                      }
                                  ]
                              }
                          ]
                      },
                      {
                          "id": 53,
                          "name": "類別C",
                          "parentId": None,
                          "children": [
                              {
                                  "id": 80,
                                  "name": "類別C1",
                                  "parentId": 53,
                              },
                              {
                                  "id": 224,
                                  "name": "類別C2",
                                  "parentId": 53,
                              }
                          ]
                      },
                      {
                          "id": 69,
                          "name": "類別D",
                          "parentId": None,
                          "children": [
                              {
                                  "id": 70,
                                  "name": "類別D1",
                                  "parentId": 69,
                                  "children": [
                                      {
                                          "id": 4,
                                          "name": "類別D11",
                                          "parentId": 70,
                                          "children": [
                                              {
                                                  "id": 51,
                                                  "name": "類別D111",
                                                  "parentId": 4,
                                              }
                                          ]
                                      }
                                  ]
                              },
                              {
                                  "id": 91,
                                  "name": "類別D2",
                                  "parentId": 69,
                              },
                              {
                                  "id": 102,
                                  "name": "類別D3",
                                  "parentId": 69,
                              },
                              {
                                  "id": 113,
                                  "name": "類別D4",
                                  "parentId": 69,
                              },
                              {
                                  "id": 121,
                                  "name": "類別D5",
                                  "parentId": 69,
                              },
                              {
                                  "id": 136,
                                  "name": "類別D6",
                                  "parentId": 69,
                              },
                              {
                                  "id": 140,
                                  "name": "類別D7",
                                  "parentId": 69,
                                  "children": [
                                      {
                                          "id": 142,
                                          "name": "類別D71",
                                          "parentId": 140,
                                      }
                                  ]
                              }
                          ]
                      }
                  ]
              return render(request, 'demo/index.html', context)
          

          模板文件

          templates/demo目錄下創(chuàng)建兩個(gè)模板文件index.html、children.html,index.html文件中關(guān)鍵部分的代碼為for循環(huán)指令包圍的部分,它負(fù)責(zé)遍歷上面提到的tree_list列表的每一項(xiàng),也就是數(shù)據(jù)結(jié)構(gòu)的第一級目錄,如果列表中的某一項(xiàng)children內(nèi)容不為空,則執(zhí)行指令{% include 'demo/children.html' with tree_list=item.children %},它的意思就相當(dāng)于render(request, 'demo/children.html', item.children),也就是說插入當(dāng)前項(xiàng)的子節(jié)點(diǎn)作為數(shù)據(jù)源渲染出的頁面。

          <!DOCTYPE html>
          <html lang="en">
          
          <head>
              <meta charset="UTF-8">
              <meta name="viewport" content="width=device-width, initial-scale=1.0">
              <title>django模板</title>
              <style>
                  .flex {
                      display: flex;
                  }
          
                  .list-unstyled ul {
                      padding-left: 0;
                      list-style: none;
                  }
          
                  .tree {
                      padding: 0.3rem 1rem ;
                      background-color: #f5f5f5;
                      color: #333;
                  }
          
                  .tree li li {
                      padding-left: 0.5rem;
                  }
          
                  .tree li::before {
                      content: '\0203A';
                      opacity: 0;
                  }
          
                  .tree .expand::before {
                      content: '\0203A';
                      opacity: 1;
                  }        
              </style>
          </head>
          
          <body>
              <h3>Django模板渲染樹形目錄示例</h3>
              <div class="flex">
                  <div class="tree list-unstyled">
                      <ul>
                          {% for item in tree_list %}
                              {% if item.children %}
                              <li class="expand">
                                  {{ item.name }}
                                  {% include 'demo/children.html' with tree_list=item.children %}
                              </li>
                              {% else %}
                              <li>
                                  {{ item.name }}
                              </li>
                              {% endif %}
                          {% endfor %}
                      </ul>
                  </div>
                  <div class="right"></div>
              </div>
          
          </body>
          
          </html>
          

          接下來看children.html,它看起來和前面是很類似的,只不過這里include指令中使用的模板就是自己本身,傳入的數(shù)據(jù)源逐層剝離出子節(jié)點(diǎn),這就是和編程語言的遞歸是一樣的了,最終所有children節(jié)點(diǎn)都完全遍歷到并渲染出最終的html頁面,這樣就實(shí)現(xiàn)了渲染樹形結(jié)構(gòu)數(shù)據(jù)。

          <ul>
              {% for item in tree_list %}
                  {% if item.children %}
                  <li class="expand">
                      {{ item.name }}
                      {% include 'demo/children.html' with tree_list=item.children %}
                  </li>
                  {% else %}
                  <li>
                      {{ item.name }}
                  </li>
                  {% endif %}
              {% endfor %}
          </ul>
          

          最后渲染出的樹形目錄如下, index.html中寫了一點(diǎn)css改變了默認(rèn)的樣式,你可以根據(jù)自己的需要使用成熟的UI框架來定制樹形目錄的樣式。本文到這里就結(jié)束了,希望能幫助到有需要的朋友,也歡迎大家多多關(guān)注我的公眾號【一點(diǎn)鑫得】,我將持續(xù)輸出有價(jià)值的內(nèi)容。


          主站蜘蛛池模板: 免费无码毛片一区二区APP| 国产韩国精品一区二区三区| 久久精品免费一区二区三区| 肥臀熟女一区二区三区| 波多野结衣中文字幕一区二区三区| 国产手机精品一区二区| 国产伦精品一区二区三区视频猫咪 | 国产精品免费一区二区三区| jizz免费一区二区三区| 一区二区不卡在线| 无码精品一区二区三区免费视频| 国产一区二区三区樱花动漫| 亚洲第一区二区快射影院| 色噜噜狠狠一区二区三区果冻 | 人妻aⅴ无码一区二区三区| 国产在线精品一区二区三区直播| 国产免费一区二区三区VR| 精品一区二区三人妻视频| 无码人妻精品一区二区三区99性| 久久99精品波多结衣一区| 日本不卡在线一区二区三区视频| 中文字幕国产一区| 日韩人妻无码一区二区三区久久99 | 色狠狠色噜噜Av天堂一区| 农村人乱弄一区二区| 日本成人一区二区| 51视频国产精品一区二区| 自拍日韩亚洲一区在线| 激情久久av一区av二区av三区| 在线不卡一区二区三区日韩| 波多野结衣精品一区二区三区| 国产丝袜一区二区三区在线观看| 国产嫖妓一区二区三区无码| 一区二区三区免费在线观看| 中文人妻av高清一区二区| 久久久国产精品亚洲一区| 天堂一区人妻无码| 精品国产一区二区三区AV性色| 无码日韩精品一区二区免费暖暖| 鲁大师成人一区二区三区| 国产美女露脸口爆吞精一区二区 |