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 |
コンポーネントを作成するには、クラスベースのコンポーネントと機能ベースのコンポーネントの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を提供します。これは、 Templateオブジェクトをキャッシュして再利用し、 nameを提供している限り、キャッシュキーとして使用されます。
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コメントを追加して、構文の強調表示を取得できますが、DjangoタグではなくHTMLのみを強調しており、フォーマットのサポートが欠けています。編集者は将来これをより良いサポートを追加するかもしれませんが、現時点では、このルートに行くと構文の強調表示とフォーマットが欠落しています。 django-components Repoでこれについて公開された会話があります。これは、VSCodeチームとの会話を前進させるためのEmilstenstromへのクレジットです。
結局、それはトレードオフです。あなたにとって最も理にかなっている方法を使用してください。
信号と同様に、コンポーネントはどこにでも住むことができますが、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 ()登録された各コンポーネントには、テンプレートで使用できる2つのタグがあります。
{% card %} ... {% endcard %}{% #user_profile %} 。これは、必ずしも体を必要としないコンポーネントに役立ちますデフォルトでは、コンポーネントは次のタグを使用して登録されます。
{% <component_name> %}{% end<component_name> %}{% #<component_name> %}この動作は、設定にカスタムタグフォーマッターを提供することで変更される場合があります。たとえば、ブロックタグを{% #card %} ... {% /card %}に変更し、 {% 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 ,
}
} @click="handleClick" data-id="123" requiredと、 {{ attributes }}によってレンダリングされます。
属性のデフォルト値を指定したり、追加の値を一部のコンポーネントの属性にマージする必要がある場合があります。ライブラリは、これに役立つ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 >また、 += operator:を使用して、他の属性を「付録可能」として扱うこともできます。
< 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 DICTに追加されます。ただし、これは必ずしも私たちが望むとは限りません。たとえば、却下できる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コンテキスト変数はコンポーネントに渡されます。コンポーネントは、キーとしてスロット名を、スロットを値として描いたDICTで構成されます。次に、 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"]想定している場合、結果のHTMLは次のとおりです。
< ul >
< li > I like apples! </ li >
< li > I like bananas! </ li >
< li > I like cherries! </ li >
</ ul >また、2番目の引数を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 >また、より複雑な要素を実現するためにコンポーネントをネストすることもできます。ブートストラップを使用してアコーディオンコンポーネントを実装する方法の例を次に示します。
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ではるかに意味があるという結論に達しました。他の多くの言語 /フレームワークは、コンポーネント(スロット、属性)の構築に同じ概念を使用しているため、多くの知識が譲渡可能であり、すでに多くの既存のコンポーネントライブラリ(ブートストラップ、テールウィンド、材料デザインなどを使用)があります。コンポーネントを構築 /構築する方法に触発されるように、それらのいくつかを見ることを強くお勧めします。ここにいくつかの例があります: