Простой способ создать многоразовые компоненты шаблона в Джанго.
Вы должны сначала зарегистрировать свой компонент
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 | Джанго версия |
|---|---|
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 , который будет кэшировать и повторно использовать объект 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 перед строкой шаблона и получить синтаксис, однако он только выделяет HTML, а не теги Django, и вам все еще не хватает поддержки форматирования. Возможно, редакторы добавят лучшую поддержку для этого в будущем, но на данный момент вам не хватит синтаксиса, выделяющего и форматирование, если вы пойдете по этому маршруту. Об этом есть открытый разговор об этом в репо django-components , зачисленные на Эмилстенстром за продвижение разговора вперед с командой VSCODE.
В конце концов, это компромисс. Используйте метод, который имеет наибольший смысл для вас.
Так же, как сигналы, компоненты могут жить где угодно, но вы должны убедиться, что Джанго поднимает их в стартапе. Самый ready() способ сделать это - определить ваши компоненты в подмодуле components.py .
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 %} , и встроенный тег на {% 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 , который будет доступен в контексте шаблона:
{
"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 %} Рендерированный HTML компонента button в этом примере будет:
< 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 и удалить dismissible атрибут из attributes и вместо этого вернуть его в контексте 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"] , полученная HTML будет:
< 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-components уже великолепна и поддерживает большинство функций, которые есть в этом проекте, но я думал, что синтаксис можно немного улучшить, чтобы почувствовать себя менее словесным, и добавить несколько дополнительных вещей, которые казались необходимыми, например, поддержку для функциональных компонентов и строков<x-alert type="error">Server Error</x-alert> ), но решение было гораздо более сложным, и я пришел к выводу, что использование аналогичного подхода к django-components стало гораздо более смыслом в Django.Многие другие языки / фреймворки используют одни и те же концепции для создания компонентов (слоты, атрибуты), поэтому многие знания могут быть переданы, и уже существует множество существующих компонентных библиотек (например, с использованием начальной загрузки, ветра, дизайна материала и т. Д.). Я настоятельно рекомендую посмотреть на некоторые из них, чтобы вдохновить, как создавать / структурировать ваши компоненты. Вот несколько примеров: