在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 | 5.0 4.2 |
3.11 | 5.0 4.2 4.1 |
3.10 | 5.0 4.2 4.1 4.0 3.2 |
3.9 | 4.2 4.1 4.0 3.2 |
3.8 | 4.2 4.1 4.0 3.2 |
编写组件有两种方法:基于类的组件和基于功能的组件。
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中更有意义许多其他语言 /框架正在使用相同的概念来建筑组件(插槽,属性),因此许多知识都是可以转移的,并且已经有很多现有的组件库(例如使用引导,尾风,材料设计等)。我强烈建议您查看其中的一些,以激发如何构建 /构建组件。这里有一些例子: