在Django中創建可重複使用的模板組件的一種簡單方法。
您必須先註冊您的組件
from django_web_components import component
@ component . register ( "card" )
class Card ( component . Component ):
template_name = "components/card.html"組件的模板:
# components/card.html
{% load components %}
< div class =" card " >
< div class =" card-header " >
{% render_slot slots.header %}
</ div >
< div class =" card-body " >
< h5 class =" card-title " >
{% render_slot slots.title %}
</ h5 >
{% render_slot slots.inner_block %}
</ div >
</ div >您現在可以使用以下方式渲染此組件
{% load components %}
{% card %}
{% slot header %} Featured {% endslot %}
{% slot title %} Card title {% endslot %}
< p > Some quick example text to build on the card title and make up the bulk of the card's content. </ p >
< a href =" # " class =" btn btn-primary " > Go somewhere </ a >
{% endcard %}這將導致以下HTML渲染:
< div class =" card " >
< div class =" card-header " >
Featured
</ div >
< div class =" card-body " >
< h5 class =" card-title " >
Card title
</ h5 >
< p > Some quick example text to build on the card title and make up the bulk of the card's content. </ p >
< a href =" # " class =" btn btn-primary " > Go somewhere </ a >
</ div >
</ div > pip install django-web-components
然後將django_web_components添加到您的INSTALLED_APPS 。
INSTALLED_APPS = [
...,
"django_web_components" ,
]為了避免在每個模板中使用{% load components %} ,您可以將標籤添加到設置內的builtins列表中。
TEMPLATES = [
{
...,
"OPTIONS" : {
"context_processors" : [
...
],
"builtins" : [
"django_web_components.templatetags.components" ,
],
},
},
]該庫支持Python 3.8+和Django 3.2+。
| Python版本 | Django版本 |
|---|---|
3.12 | |
3.11 | |
3.10 | |
3.9 | |
3.8 | |
編寫組件有兩種方法:基於類的組件和基於功能的組件。
from django_web_components import component
@ component . register ( "alert" )
class Alert ( component . Component ):
# You may also override the get_template_name() method instead
template_name = "components/alert.html"
# Extra context data will be passed to the template context
def get_context_data ( self , ** kwargs ) -> dict :
return {
"dismissible" : False ,
}組件將通過調用render(context)方法來渲染,默認情況下,該方法將加載模板文件並渲染。
對於微小的組件,管理組件類和組件的模板可能會感到麻煩。因此,您可以直接從render方法定義模板:
from django_web_components import component
from django_web_components . template import CachedTemplate
@ component . register ( "alert" )
class Alert ( component . Component ):
def render ( self , context ) -> str :
return CachedTemplate (
"""
<div class="alert alert-primary" role="alert">
{% render_slot slots.inner_block %}
</div>
""" ,
name = "alert" ,
). render ( context )組件也可以定義為接受context並返回字符串的單個函數:
from django_web_components import component
from django_web_components . template import CachedTemplate
@ component . register
def alert ( context ):
return CachedTemplate (
"""
<div class="alert alert-primary" role="alert">
{% render_slot slots.inner_block %}
</div>
""" ,
name = "alert" ,
). render ( context )本指南中的示例主要使用基於函數的組件,因為它更容易示例,因為組件代碼和模板位於同一位置,但是您可以自由選擇任何喜歡的方法。
該庫使用常規的Django模板,該模板允許您從文件加載模板,或使用模板字符串直接創建模闆對象。兩種方法都得到了支持,並且都有優勢和缺點:
關於緩存,該庫提供了一個CachedTemplate ,只要您為其提供name ,它將緩存和重複使用Template對象,該對象將用作緩存密鑰:
from django_web_components import component
from django_web_components . template import CachedTemplate
@ component . register
def alert ( context ):
return CachedTemplate (
"""
<div class="alert alert-primary" role="alert">
{% render_slot slots.inner_block %}
</div>
""" ,
name = "alert" ,
). render ( context )因此,實際上,使用模板字符串時,緩存不應該是問題,因為CachedTemplate與使用模板文件使用緩存的加載程序一樣快。
關於格式支持和語法突出顯示,對於模板字符串沒有好的解決方案。 Pycharm支持語言注入,該語言注入使您可以在模板字符串之前添加# language=html註釋並獲得語法突出顯示,但是,它只會突出顯示HTML而不是Django標籤,並且您仍然缺少對格式化的支持。也許編輯者將來會為此提供更好的支持,但是如果您走這條路線,您將缺少語法突出顯示和格式。關於此事的django-components Repo,對Emilstenstrom進行了與VSCODE團隊進行對話的前進的信用。
最後,這是一個權衡。使用最有意義的方法。
就像信號一樣,組件可以居住在任何地方,但是您需要確保Django在啟動時選擇它們。最簡單的方法是在它們相關的應用程序的components.py子模塊中定義組件,然後將它們連接到應用程序配置類的ready()方法中。
from django . apps import AppConfig
from django_web_components import component
class MyAppConfig ( AppConfig ):
...
def ready ( self ):
# Implicitly register components decorated with @component.register
from . import components # noqa
# OR explicitly register a component
component . register ( "card" , components . Card )您也可以unregister現有組件,或從註冊表中獲取組件:
from django_web_components import component
# Unregister a component
component . registry . unregister ( "card" )
# Get a component
component . registry . get ( "card" )
# Remove all components
component . registry . clear ()
# Get all components as a dict of name: component
component . registry . all ()每個註冊的組件將在模板中使用兩個標籤:
{% card %} ... {% endcard %}{% #user_profile %} 。這對於不一定需要身體的組件很有用默認情況下,將使用以下標籤註冊組件:
{% <component_name> %}{% end<component_name> %}{% #<component_name> %}可以通過在設置中提供自定義標籤格式器來更改此行為。例如,要將塊標籤更改為{% #card %} ... {% /card %} ,然後將inline標籤更改為{% card %} (類似於拖鞋),您可以使用以下格式:
class ComponentTagFormatter :
def format_block_start_tag ( self , name ):
return f"# { name } "
def format_block_end_tag ( self , name ):
return f"/ { name } "
def format_inline_tag ( self , name ):
return name
# inside your settings
WEB_COMPONENTS = {
"DEFAULT_COMPONENT_TAG_FORMATTER" : "path.to.your.ComponentTagFormatter" ,
}您可以使用關鍵字參數將數據傳遞給組件,這些參數接受硬編碼值或變量:
{% with error_message="Something bad happened!" %}
{% #alert type="error" message=error_message %}
{% endwith %}所有屬性將在attributes dict中添加,該屬性將在模板上下文中可用:
{
"attributes" : {
"type" : " error " ,
"message" : " Something bad happened! "
}
}然後,您可以從組件的模板訪問它:
< div class =" alert alert-{{ attributes.type }} " >
{{ attributes.message }}
</ div >您也可以使用{{ attributes }}直接渲染所有屬性。例如,如果您有以下組件
{% alert id="alerts" class="font-bold" %} ... {% endalert %}您可以使用所有屬性使用
< div {{ attributes }} >
<!-- Component content -->
</ div >這將導致以下HTML渲染:
< div id =" alerts " class =" font-bold " >
<!-- Component content -->
</ div >您還可以通過特殊字符( [@:_-.] )傳遞屬性,以及沒有值的屬性:
{% button @click="handleClick" data-id="123" required %} ... {% endbutton %}這將導致下文中可用的以下內容:
{
"attributes" : {
"@click" : "handleClick" ,
"data-id" : "123" ,
"required" : True ,
}
}並將由{{ attributes }}渲染為@click="handleClick" data-id="123" required 。
有時,您可能需要為屬性指定默認值,或將其他值合併到某些組件的屬性中。該庫提供了一個有助於此的merge_attrs標籤:
< div {% merge_attrs attributes class =" alert " role =" alert " %} >
<!-- Component content -->
</ div >如果我們假設此組件像這樣使用:
{% alert class="mb-4" %} ... {% endalert %}該組件的最終渲染HTML將顯示如下:
< div class =" alert mb-4 " role =" alert " >
<!-- Component content -->
</ div >當合併不是class屬性的屬性時,提供給merge_attrs標籤的值將被視為屬性的“默認”值。但是,與class屬性不同,這些屬性不會與註入的屬性值合併。相反,它們將被覆蓋。例如, button組件的實現看起來如下:
< button {% merge_attrs attributes type =" button " %} >
{% render_slot slots.inner_block %}
</ button >要使用自定義type渲染按鈕組件,可以在消費組件時指定它。如果未指定類型,將使用button類型:
{% button type="submit" %} Submit {% endbutton %}在此示例中, button組件的渲染html是:
< button type =" submit " >
Submit
</ button >您也可以使用+=操作員將其他屬性視為“附加”:
< div {% merge_attrs attributes data-value+ =" some-value " %} >
<!-- Component content -->
</ div >如果我們假設此組件像這樣使用:
{% alert data-value="foo" %} ... {% endalert %}渲染的HTML將是:
< div data-value =" foo some-value " >
<!-- Component content -->
</ div >默認情況下,所有屬性都添加到上下文中的attributes 。但是,這可能並不總是我們想要的。例如,想像一下,我們希望擁有一個可以解散的alert組件,同時也能夠將額外的屬性傳遞給根元素,例如id或class 。理想情況下,我們希望能夠呈現這樣的組件:
{% alert id="alerts" dismissible %} Something went wrong! {% endalert %}實施此組件的一種天真方法將是以下內容:
< div {{ attributes }} >
{% render_slot slots.inner_block %}
{% if attributes.dismissible %}
< button type =" button " class =" btn-close " data-bs-dismiss =" alert " aria-label =" Close " > </ button >
{% endif %}
</ div >但是,這將導致根部元素中包含的dismissible屬性,這不是我們想要的:
< div id =" alerts " dismissible >
Something went wrong!
< button type =" button " class =" btn-close " data-bs-dismiss =" alert " aria-label =" Close " > </ button >
</ div >理想情況下,我們希望dismissible屬性與attributes分開,因為我們只想在邏輯上使用它,但不一定將其渲染到組件。
為了實現這一目標,您可以從組件中操縱上下文,以便為使用組件提供更好的API。有幾種方法可以做到這一點,請選擇對您最有意義的方法,例如:
get_context_data並從attributes中刪除dismissible屬性並在上下文中返回 from django_web_components import component
@ component . register ( "alert" )
class Alert ( component . Component ):
template_name = "components/alert.html"
def get_context_data ( self , ** kwargs ):
dismissible = self . attributes . pop ( "dismissible" , False )
return {
"dismissible" : dismissible ,
}render方法並操縱那裡的上下文 from django_web_components import component
@ component . register ( "alert" )
class Alert ( component . Component ):
template_name = "components/alert.html"
def render ( self , context ):
context [ "dismissible" ] = context [ "attributes" ]. pop ( "dismissible" , False )
return super (). render ( context )以上兩個解決方案都可以正常工作,您可以為基於函數的組件做同樣的事情。然後,組件的模板可以看起來像這樣:
< div {{ attributes }} >
{% render_slot slots.inner_block %}
{% if dismissible %}
< button type =" button " class =" btn-close " data-bs-dismiss =" alert " aria-label =" Close " > </ button >
{% endif %}
</ div >這應該導致正確的HTML呈現:
< div id =" alerts " >
Something went wrong!
< button type =" button " class =" btn-close " data-bs-dismiss =" alert " aria-label =" Close " > </ button >
</ div > 您通常需要通過“插槽”將其他內容傳遞給組件。 slots上下文變量傳遞給您的組件,該變量由插槽名稱作為密鑰和插槽作為值組成。然後,您可以使用render_slot標籤渲染組件內部的插槽。
要探索這個概念,讓我們想像我們想將一些內容傳遞給alert組件:
{% alert %}
< strong > Whoops! </ strong > Something went wrong!
{% endalert %}默認情況下,該內容將在默認插槽中提供給您的組件,該插槽稱為inner_block 。然後,您可以使用組件中的render_slot標籤渲染此插槽:
{% load components %}
< div class =" alert alert-danger " >
{% render_slot slots.inner_block %}
</ div >這應該導致以下HTML渲染:
< div class =" alert alert-danger " >
< strong > Whoops! </ strong > Something went wrong!
</ div >您也可以通過在設置中指定默認插槽來重命名默認插槽:
# inside your settings
WEB_COMPONENTS = {
"DEFAULT_SLOT_NAME" : "inner_block" ,
}有時,組件可能需要在組件內的不同位置渲染多個不同的插槽。讓我們修改我們的警報組件以允許注入“標題”插槽:
{% load components %}
< div class =" alert alert-danger " >
< span class =" alert-title " >
{% render_slot slots.title %}
</ span >
{% render_slot slots.inner_block %}
</ div >您可以使用slot標籤定義命名插槽的內容。任何不在顯式slot標籤中的內容都將添加到默認的inner_block插槽中:
{% load components %}
{% alert %}
{% slot title %} Server error {% endslot %}
< strong > Whoops! </ strong > Something went wrong!
{% endalert %}在此示例中,渲染的HTML將是:
< div class =" alert alert-danger " >
< span class =" alert-title " >
Server error
</ span >
< strong > Whoops! </ strong > Something went wrong!
</ div >您可以多次定義相同的命名插槽:
{% unordered_list %}
{% slot item %} First item {% endslot %}
{% slot item %} Second item {% endslot %}
{% slot item %} Third item {% endslot %}
{% endunordered_list %}然後,您可以在組件內的插槽上迭代:
< ul >
{% for item in slots.item %}
< li > {% render_slot item %} </ li >
{% endfor %}
</ ul >這將導致以下HTML:
< ul >
< li > First item </ li >
< li > Second item </ li >
< li > Third item </ li >
</ ul >插槽內容還將可以訪問組件的上下文。要探索這個概念,請想像一個列表組件,該entries組件接受一個代表事物列表的條目屬性,然後將其迭代並為每個條目渲染inner_block插槽。
from django_web_components import component
from django_web_components . template import CachedTemplate
@ component . register
def unordered_list ( context ):
context [ "entries" ] = context [ "attributes" ]. pop ( "entries" , [])
return CachedTemplate (
"""
<ul>
{% for entry in entries %}
<li>
{% render_slot slots.inner_block %}
</li>
{% endfor %}
</ul>
""" ,
name = "unordered_list" ,
). render ( context )然後,我們可以如下渲染組件:
{% unordered_list entries=entries %}
I like {{ entry }}!
{% endunordered_list %}在此示例中, entry變量來自組件的上下文。如果我們假設entries = ["apples", "bananas", "cherries"] ,則結果為:
< ul >
< li > I like apples! </ li >
< li > I like bananas! </ li >
< li > I like cherries! </ li >
</ ul >您也可以明確將第二個參數傳遞給render_slot :
< ul >
{% for entry in entries %}
< li >
<!-- We are passing the `entry` as the second argument to render_slot -->
{% render_slot slots.inner_block entry %}
</ li >
{% endfor %}
</ ul >調用組件時,您可以使用特殊屬性:let傳遞給render_slot值並將其綁定到變量:
{% unordered_list :let="fruit" entries=entries %}
I like {{ fruit }}!
{% endunordered_list %}這將渲染與上述相同的HTML。
與組件類似,您可以為插槽分配其他屬性。下面是一個表組件,說明了具有屬性的多個命名插槽:
from django_web_components import component
from django_web_components . template import CachedTemplate
@ component . register
def table ( context ):
context [ "rows" ] = context [ "attributes" ]. pop ( "rows" , [])
return CachedTemplate (
"""
<table>
<tr>
{% for col in slots.column %}
<th>{{ col.attributes.label }}</th>
{% endfor %}
</tr>
{% for row in rows %}
<tr>
{% for col in slots.column %}
<td>
{% render_slot col row %}
</td>
{% endfor %}
</tr>
{% endfor %}
</table>
""" ,
name = "table" ,
). render ( context )您可以這樣調用組件:
{% table rows=rows %}
{% slot column :let="user" label="Name" %}
{{ user.name }}
{% endslot %}
{% slot column :let="user" label="Age" %}
{{ user.age }}
{% endslot %}
{% endtable %}如果我們假設rows = [{ "name": "Jane", "age": "34" }, { "name": "Bob", "age": "51" }] ,以下HTML將呈現:
< table >
< tr >
< th > Name </ th >
< th > Age </ th >
</ tr >
< tr >
< td > Jane </ td >
< td > 34 </ td >
</ tr >
< tr >
< td > Bob </ td >
< td > 51 </ td >
</ tr >
</ table >您也可以嵌套組件以獲得更複雜的元素。這是您如何使用Bootstrap實現手風琴組件的示例:
from django_web_components import component
from django_web_components . template import CachedTemplate
import uuid
@ component . register
def accordion ( context ):
context [ "accordion_id" ] = context [ "attributes" ]. pop ( "id" , str ( uuid . uuid4 ()))
context [ "always_open" ] = context [ "attributes" ]. pop ( "always_open" , False )
return CachedTemplate (
"""
<div class="accordion" id="{{ accordion_id }}">
{% render_slot slots.inner_block %}
</div>
""" ,
name = "accordion" ,
). render ( context )
@ component . register
def accordion_item ( context ):
context [ "id" ] = context [ "attributes" ]. pop ( "id" , str ( uuid . uuid4 ()))
context [ "open" ] = context [ "attributes" ]. pop ( "open" , False )
return CachedTemplate (
"""
<div class="accordion-item" id="{{ id }}">
<h2 class="accordion-header" id="{{ id }}-header">
<button
class="accordion-button {% if not open %}collapsed{% endif %}"
type="button"
data-bs-toggle="collapse"
data-bs-target="#{{ id }}-collapse"
aria-expanded="{% if open %}true{% else %}false{% endif %}"
aria-controls="{{ id }}-collapse"
>
{% render_slot slots.title %}
</button>
</h2>
<div
id="{{ id }}-collapse"
class="accordion-collapse collapse {% if open %}show{% endif %}"
aria-labelledby="{{ id }}-header"
{% if accordion_id and not always_open %}
data-bs-parent="#{{ accordion_id }}"
{% endif %}}
>
<div class="accordion-body">
{% render_slot slots.body %}
</div>
</div>
</div>
""" ,
name = "accordion_item" ,
). render ( context )然後,您可以使用它們如下:
{% accordion %}
{% accordion_item open %}
{% slot title %} Accordion Item #1 {% endslot %}
{% slot body %}
< strong > This is the first item's accordion body. </ strong > It is shown by default.
{% endslot %}
{% endaccordion_item %}
{% accordion_item %}
{% slot title %} Accordion Item #2 {% endslot %}
{% slot body %}
< strong > This is the second item's accordion body. </ strong > It is hidden by default.
{% endslot %}
{% endaccordion_item %}
{% accordion_item %}
{% slot title %} Accordion Item #3 {% endslot %}
{% slot body %}
< strong > This is the third item's accordion body. </ strong > It is hidden by default.
{% endslot %}
{% endaccordion_item %}
{% endaccordion %}該項目使用poetry來管理依賴關係。在此處查看有關如何安裝詩歌的文檔:https://python-poetry.org/docs/#installation
安裝依賴項
poetry install激活環境
poetry shell現在您可以運行測試
python runtests.py該項目是在看到其他語言 /框架如何處理組件的方式之後開始的,並希望將其中一些想法帶回Django。
django-components庫已經很棒,並且支持該項目具有的大多數功能,但是我認為該語法可以改進以減少冗長的速度,並添加一些似乎是必要的東西,例如對功能基於功能的組件和模板和屬性的支持,並使用屬性,並使用屬性。<x-alert type="error">Server Error</x-alert> ),但是解決方案要復雜得多,我得出的結論是,使用類似的django-components在django components中更有意義許多其他語言 /框架正在使用相同的概念來建築組件(插槽,屬性),因此許多知識都是可以轉移的,並且已經有很多現有的組件庫(例如使用引導,尾風,材料設計等)。我強烈建議您查看其中的一些,以激發如何構建 /構建組件。這裡有一些例子: