Eine einfache Möglichkeit, wiederverwendbare Vorlagenkomponenten in Django zu erstellen.
Sie müssen zuerst Ihre Komponente registrieren
from django_web_components import component
@ component . register ( "card" )
class Card ( component . Component ):
template_name = "components/card.html"Die Vorlage der Komponente:
# 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 >Sie können diese Komponente jetzt mit:
{% 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 %}Dies führt dazu, dass die folgenden HTML -Renderung erfolgt:
< 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
Fügen Sie dann django_web_components zu Ihren INSTALLED_APPS hinzu.
INSTALLED_APPS = [
...,
"django_web_components" ,
] Um zu vermeiden, dass in jeder Vorlage {% load components %} verwendet werden müssen, können Sie die Tags in die Liste builtins in Ihren Einstellungen hinzufügen.
TEMPLATES = [
{
...,
"OPTIONS" : {
"context_processors" : [
...
],
"builtins" : [
"django_web_components.templatetags.components" ,
],
},
},
]Die Bibliothek unterstützt Python 3.8+ und Django 3.2+.
| Python -Version | Django -Version |
|---|---|
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 |
Es gibt zwei Ansätze zum Schreiben von Komponenten: klassenbasierte Komponenten und funktionsbasierte Komponenten.
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 ,
} Die Komponente wird durch Aufrufen der render(context) gerendert, die standardmäßig die Vorlagendatei lädt und sie rendert.
Bei winzigen Komponenten kann es sich umständlich anfühlen, sowohl die Komponentenklasse als auch die Vorlage der Komponente zu verwalten. Aus diesem Grund können Sie die Vorlage direkt aus der render -Methode definieren:
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 ) Eine Komponente kann auch als eine einzelne Funktion definiert werden, die einen context akzeptiert und eine Zeichenfolge zurückgibt:
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 )Die Beispiele in diesem Handbuch verwenden hauptsächlich funktionsbasierte Komponenten, da es einfacher ist, zu veranschaulichen, da sich der Komponentencode und die Vorlage an derselben Stelle befinden. Sie können jedoch frei auswählen, welche Methode Sie bevorzugen.
Die Bibliothek verwendet die regulären Django -Vorlagen, mit denen Sie entweder Vorlagen aus Dateien laden oder Vorlagenobjekte mit Vorlagenzeichenfolgen direkt erstellen können. Beide Methoden werden unterstützt und beide haben Vor- und Nachteile:
In Bezug auf das Zwischenspeichern enthält die Bibliothek eine CachedTemplate , die das Template , wie Sie einen name angeben, und als Cache -Schlüssel verwendet werden:
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 ) In Wirklichkeit sollte das Caching bei der Verwendung von Vorlagenzeichenfolgen kein Problem sein, da CachedTemplate genauso schnell ist wie die Verwendung des zwischengespeicherten Loaders mit Vorlagendateien.
In Bezug auf Formatierungsunterstützung und Syntax -Hervorhebung gibt es keine gute Lösung für Vorlagenzeichenfolgen. Pycharm unterstützt die Sprachinjektion, mit der Sie vor der Vorlagenzeichenfolge einen # language=html -Kommentar hinzufügen und die Syntax -Hervorhebung erhalten können. Es wird jedoch nur HTML und nicht die Django -Tags hervorgehoben, und Sie fehlen immer noch Unterstützung für die Formatierung. Vielleicht werden die Redakteure dies in Zukunft besser unterstützen, aber im Moment fehlt Ihnen die Syntax -Hervorhebung und Formatierung, wenn Sie diese Route gehen. Es gibt ein offenes Gespräch darüber im django-components -Repo, Credits an Emilstenstrom, um das Gespräch mit dem VSCODE-Team voranzutreiben.
Am Ende ist es ein Kompromiss. Verwenden Sie die Methode, die für Sie am sinnvollsten ist.
Genau wie Signale können die Komponenten überall leben, aber Sie müssen sicherstellen, dass Django sie beim Start aufnimmt. Der einfachste Weg, dies zu tun ready() besteht darin, Ihre Komponenten in einer components.py zu definieren.
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 ) Sie können auch eine vorhandene Komponente unregister oder eine Komponente aus der Registrierung erhalten:
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 ()Jede registrierte Komponente verfügt über zwei Tags für die Verwendung in Ihren Vorlagen:
{% card %} ... {% endcard %}{% #user_profile %} . Dies kann für Komponenten nützlich sein, die nicht unbedingt einen Körper benötigenStandardmäßig werden Komponenten mit den folgenden Tags registriert:
{% <component_name> %}{% end<component_name> %}{% #<component_name> %} Dieses Verhalten kann geändert werden, indem ein benutzerdefiniertes Tag -Formatierer in Ihren Einstellungen bereitgestellt wird. Um beispielsweise die Block -Tags in {% #card %} ... {% /card %} und das Inline -Tag auf {% card %} (ähnlich wie bei den Hausschuhen) zu ändern, können Sie das folgende Formatierer verwenden:
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" ,
}Sie können Daten an Komponenten mithilfe von Schlüsselwortargumenten weitergeben, die entweder festcodierte Werte oder Variablen akzeptieren:
{% with error_message="Something bad happened!" %}
{% #alert type="error" message=error_message %}
{% endwith %} Alle Attribute werden in einem attributes -Diktat hinzugefügt, das im Vorlagenkontext verfügbar sein wird:
{
"attributes" : {
"type" : " error " ,
"message" : " Something bad happened! "
}
}Sie können dann über die Vorlage Ihrer Komponente aus darauf zugreifen:
< div class =" alert alert-{{ attributes.type }} " >
{{ attributes.message }}
</ div > Sie können auch alle Attribute mit {{ attributes }} direkt rendern. Zum Beispiel, wenn Sie die folgende Komponente haben
{% alert id="alerts" class="font-bold" %} ... {% endalert %}Sie können alle Attribute verwenden
< div {{ attributes }} >
<!-- Component content -->
</ div >Dies führt dazu, dass die folgenden HTML -Renderung erfolgt:
< div id =" alerts " class =" font-bold " >
<!-- Component content -->
</ div > Sie können auch Attribute mit Sonderzeichen ( [@:_-.] ) Sowie Attributen ohne Wert übergeben:
{% button @click="handleClick" data-id="123" required %} ... {% endbutton %}Dies führt zu dem im Kontext verfügbaren Follow -DICT:
{
"attributes" : {
"@click" : "handleClick" ,
"data-id" : "123" ,
"required" : True ,
}
} Und wird von {{ attributes }} als @click="handleClick" data-id="123" required .
Manchmal müssen Sie möglicherweise Standardwerte für Attribute angeben oder zusätzliche Werte in einige der Attribute der Komponente zusammenschließen. Die Bibliothek bietet ein merge_attrs -Tag, das dabei hilft:
< div {% merge_attrs attributes class =" alert " role =" alert " %} >
<!-- Component content -->
</ div >Wenn wir davon ausgehen, dass diese Komponente so verwendet wird:
{% alert class="mb-4" %} ... {% endalert %}Das endgültige rendere HTML der Komponente erscheinen wie folgt:
< div class =" alert mb-4 " role =" alert " >
<!-- Component content -->
</ div > Bei der Verschmelzung von Attributen, die keine class sind, werden die Werte, die dem Tag merge_attrs bereitgestellt werden, als "Standard" -Werte des Attributs betrachtet. Im Gegensatz zum class werden diese Attribute jedoch nicht mit injizierten Attributwerten zusammengeführt. Stattdessen werden sie überschrieben. Beispielsweise kann die Implementierung einer button wie Folgendes aussehen:
< button {% merge_attrs attributes type =" button " %} >
{% render_slot slots.inner_block %}
</ button > Um die Schaltflächenkomponente mit einem benutzerdefinierten type zu rendern, kann er beim Verzehr der Komponente angegeben werden. Wenn kein Typ angegeben ist, wird der button verwendet:
{% button type="submit" %} Submit {% endbutton %} Die gerenderte HTML der button in diesem Beispiel wäre:
< button type =" submit " >
Submit
</ button > Sie können auch andere Attribute als "anhangsfähig" behandeln, indem Sie den += Operator verwenden:
< div {% merge_attrs attributes data-value+ =" some-value " %} >
<!-- Component content -->
</ div >Wenn wir davon ausgehen, dass diese Komponente so verwendet wird:
{% alert data-value="foo" %} ... {% endalert %}Die gerenderte HTML wird:
< div data-value =" foo some-value " >
<!-- Component content -->
</ div > Standardmäßig werden alle Attribute zu einem attributes hinzugefügt, das im Kontext diktiert ist. Dies ist jedoch möglicherweise nicht immer das, was wir wollen. Stellen Sie sich beispielsweise vor, wir möchten eine alert haben, die entlassen werden kann, während gleichzeitig zusätzliche Attribute an das Stammelement wie eine id oder class übergeben werden können. Idealerweise möchten wir in der Lage sein, eine solche Komponente wie diese zu rendern:
{% alert id="alerts" dismissible %} Something went wrong! {% endalert %}Eine naive Möglichkeit, diese Komponente zu implementieren, wäre so etwas wie folgt:
< 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 > Dies würde jedoch dazu führen, dass das dismissible Attribut in das Stammelement aufgenommen wird, was wir nicht wollen:
< div id =" alerts " dismissible >
Something went wrong!
< button type =" button " class =" btn-close " data-bs-dismiss =" alert " aria-label =" Close " > </ button >
</ div > Idealerweise möchten wir, dass das dismissible Attribut von den attributes getrennt wird, da wir es nur in der Logik verwenden möchten, es aber nicht unbedingt der Komponente übertragen.
Um dies zu erreichen, können Sie den Kontext aus Ihrer Komponente aus der Verwendung einer besseren API für die Verwendung der Komponenten manipulieren. Es gibt verschiedene Möglichkeiten, dies zu tun. Wählen Sie die Methode, die für Sie am sinnvollsten ist, zum Beispiel:
get_context_data überschreiben und das dismissible Attribut aus attributes entfernen und stattdessen im Kontext zurückgeben 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 -Methode überschreiben und den Kontext dort manipulieren 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 )Beide oben genannten Lösungen funktionieren und Sie können dasselbe für funktionsbasierte Komponenten tun. Die Vorlage der Komponente kann dann so aussehen:
< 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 >Dies sollte dazu führen, dass die korrekte HTML gerendert wird:
< div id =" alerts " >
Something went wrong!
< button type =" button " class =" btn-close " data-bs-dismiss =" alert " aria-label =" Close " > </ button >
</ div > Sie müssen häufig zusätzliche Inhalte über "Slots" an Ihre Komponenten weitergeben. Eine slots -Kontextvariable wird an Ihre Komponenten übergeben, die aus einem DIKT mit dem Schlitznamen als Schlüssel und dem Steckplatz als Wert besteht. Sie können dann die Slots in Ihren Komponenten mit dem Tag render_slot rendern.
Um dieses Konzept zu untersuchen, wollen wir uns vorstellen, dass wir einige Inhalte an eine alert übergeben möchten:
{% alert %}
< strong > Whoops! </ strong > Something went wrong!
{% endalert %} Standardmäßig wird dieser Inhalt Ihrer Komponente im Standard -Slot zur Verfügung gestellt, der als inner_block bezeichnet wird. Sie können diesen Steckplatz dann mit dem render_slot -Tag in Ihrer Komponente rendern:
{% load components %}
< div class =" alert alert-danger " >
{% render_slot slots.inner_block %}
</ div >Dies sollte dazu führen, dass die folgenden HTML -Renderung erfolgt:
< div class =" alert alert-danger " >
< strong > Whoops! </ strong > Something went wrong!
</ div >Sie können auch den Standardfenster umbenennen, indem Sie ihn in Ihren Einstellungen angeben:
# inside your settings
WEB_COMPONENTS = {
"DEFAULT_SLOT_NAME" : "inner_block" ,
}Manchmal muss eine Komponente möglicherweise mehrere verschiedene Slots an verschiedenen Stellen innerhalb der Komponente rendern. Ändern wir unsere Warnungskomponente, um die Injektion eines "Titel" -Stlot zu ermöglichen:
{% load components %}
< div class =" alert alert-danger " >
< span class =" alert-title " >
{% render_slot slots.title %}
</ span >
{% render_slot slots.inner_block %}
</ div > Sie können den Inhalt des benannten Steckplatzes mit dem slot -Tag definieren. Jeder Inhalt, der nicht in einem expliziten slot -Tag ist, werden zum Standard inner_block -Steckplatz hinzugefügt:
{% load components %}
{% alert %}
{% slot title %} Server error {% endslot %}
< strong > Whoops! </ strong > Something went wrong!
{% endalert %}Das gerenderte HTML in diesem Beispiel wäre:
< div class =" alert alert-danger " >
< span class =" alert-title " >
Server error
</ span >
< strong > Whoops! </ strong > Something went wrong!
</ div >Sie können den gleichen benannten Steckplatz mehrmals definieren:
{% unordered_list %}
{% slot item %} First item {% endslot %}
{% slot item %} Second item {% endslot %}
{% slot item %} Third item {% endslot %}
{% endunordered_list %}Sie können dann den Schlitz in Ihrer Komponente iterieren:
< ul >
{% for item in slots.item %}
< li > {% render_slot item %} </ li >
{% endfor %}
</ ul >Dies führt zu den folgenden HTML:
< ul >
< li > First item </ li >
< li > Second item </ li >
< li > Third item </ li >
</ ul > Der Slot -Inhalt hat auch Zugriff auf den Kontext der Komponente. Um dieses Konzept zu untersuchen, stellen Sie sich eine Listenkomponente vor, die ein entries akzeptiert, das eine Liste von Dingen darstellt, über die es dann iteriert und den inner_block -Steckplatz für jeden Eintrag rendert.
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 )Wir können dann die Komponente wie folgt rendern:
{% unordered_list entries=entries %}
I like {{ entry }}!
{% endunordered_list %} In diesem Beispiel stammt die entry aus dem Kontext der Komponente. Wenn wir davon ausgehen, dass entries = ["apples", "bananas", "cherries"] , wird das resultierende HTML sein:
< ul >
< li > I like apples! </ li >
< li > I like bananas! </ li >
< li > I like cherries! </ li >
</ ul > Sie können auch explizit ein zweites Argument an render_slot verabschieden:
< 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 > Beim Aufrufen der Komponente können Sie das spezielle Attribut verwenden :let Sie den Wert, der an render_slot übergeben wurde, und binden Sie an eine Variable:
{% unordered_list :let="fruit" entries=entries %}
I like {{ fruit }}!
{% endunordered_list %}Dies würde das gleiche HTML wie oben machen.
Ähnlich wie bei den Komponenten können Sie Slots zusätzliche Attribute zuweisen. Unten finden Sie eine Tabellenkomponente, die mehrere benannte Slots mit Attributen veranschaulicht:
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 )Sie können die Komponente wie SO aufrufen:
{% table rows=rows %}
{% slot column :let="user" label="Name" %}
{{ user.name }}
{% endslot %}
{% slot column :let="user" label="Age" %}
{{ user.age }}
{% endslot %}
{% endtable %} Wenn wir davon ausgehen, dass rows = [{ "name": "Jane", "age": "34" }, { "name": "Bob", "age": "51" }] , wird das folgende HTML gerendert:
< 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 >Sie können auch Komponenten nisten, um kompliziertere Elemente zu erreichen. Hier ist ein Beispiel dafür, wie Sie eine Akkordeonkomponente mit Bootstrap implementieren können:
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 )Sie können sie dann wie folgt verwenden:
{% 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 %} Das Projekt verwendet poetry , um die Abhängigkeiten zu verwalten. Schauen Sie sich die Dokumentation zur Installation von Gedichten hier an: https://python-poetry.org/docs/#installation
Installieren Sie die Abhängigkeiten
poetry installAktivieren Sie die Umgebung
poetry shellJetzt können Sie die Tests ausführen
python runtests.pyDas Projekt wurde nach gesehen, wie andere Sprachen / Frameworks mit Komponenten umgehen und einige dieser Ideen nach Django zurückbringen wollten.
django-components -Bibliothek ist bereits großartig und unterstützt die meisten Funktionen dieses Projekts, aber ich dachte<x-alert type="error">Server Error</x-alert> ) sehr ähnlich aus, aber die Lösung war viel komplizierter und ich kam zu dem Schluss, dass die Verwendung eines ähnlichen Ansatzes zu django-components viel mehr Sinn in Django machte in Django viel Sinn machte in Django viel Sinn machte in Django einen viel mehr Sinn machte, was in Django viel Sinn machteViele andere Sprachen / Frameworks verwenden dieselben Konzepte für Baukomponenten (Slots, Attribute), sodass ein Großteil des Wissens übertragbar ist, und es gibt bereits eine Menge vorhandener Komponentenbibliotheken (z. B. mit Bootstrap, Rückenwind, Materialdesign usw.). Ich empfehle dringend, einige von ihnen zu betrachten, um sich davon inspirieren zu lassen, wie Sie Ihre Komponenten erstellen / strukturieren können. Hier sind einige Beispiele: