Un moyen simple de créer des composants de modèle réutilisables dans Django.
Vous devez d'abord enregistrer votre composant
from django_web_components import component
@ component . register ( "card" )
class Card ( component . Component ):
template_name = "components/card.html"Le modèle du composant:
# 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 >Vous pouvez désormais rendre ce composant avec:
{% 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 %}Ce qui entraînera le rendu HTML suivant:
< 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
Ensuite, ajoutez django_web_components à votre INSTALLED_APPS .
INSTALLED_APPS = [
...,
"django_web_components" ,
] Pour éviter d'avoir à utiliser {% load components %} dans chaque modèle, vous pouvez ajouter les balises à la liste builtins à l'intérieur de vos paramètres.
TEMPLATES = [
{
...,
"OPTIONS" : {
"context_processors" : [
...
],
"builtins" : [
"django_web_components.templatetags.components" ,
],
},
},
]La bibliothèque prend en charge Python 3.8+ et Django 3.2+.
| Version python | Version 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 |
Il existe deux approches pour écrire des composants: les composants basés sur la classe et les composants basés sur la fonction.
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 ,
} Le composant sera rendu en appelant la méthode render(context) , qui par défaut chargera le fichier de modèle et le rendra.
Pour les composants minuscules, il peut sembler lourd de gérer à la fois la classe des composants et le modèle du composant. Pour cette raison, vous pouvez définir le modèle directement à partir de la méthode 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 ) Un composant peut également être défini comme une seule fonction qui accepte un context et renvoie une chaîne:
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 )Les exemples de ce guide utiliseront principalement des composants basés sur les fonctions, car il est plus facile d'illustrer car le code et le modèle des composants sont au même endroit, mais vous êtes libre de choisir la méthode que vous préférez.
La bibliothèque utilise les modèles Django ordinaires, qui vous permet de charger des modèles à partir de fichiers, ou de créer des objets de modèle directement à l'aide de chaînes de modèle. Les deux méthodes sont prises en charge et les deux ont des avantages et des inconvénients:
En ce qui concerne la mise en cache, la bibliothèque fournit un CachedTemplate , qui mettra en cache et réutilisera l'objet Template tant que vous lui fournissez un name , qui sera utilisé comme clé de cache:
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 ) Ainsi, en réalité, la mise en cache ne devrait pas être un problème lors de l'utilisation de chaînes de modèle, car CachedTemplate est tout aussi rapide que d'utiliser le chargeur en cache avec des fichiers de modèle.
En ce qui concerne le support de mise en forme et la mise en évidence de la syntaxe, il n'y a pas de bonne solution pour les chaînes de modèle. PyCharm prend en charge l'injection de langage qui vous permet d'ajouter un commentaire # language=html avant la chaîne de modèle et d'obtenir la mise en évidence de la syntaxe, cependant, il ne met en évidence que HTML et non les balises Django, et vous manquez toujours de support pour le formatage. Peut-être que les éditeurs ajouteront une meilleure prise en charge de cela à l'avenir, mais pour le moment vous manquerez la syntaxe de mise en évidence et de mise en forme si vous parcourez cet itinéraire. Il y a une conversation ouverte à ce sujet sur le repo django-components , crédits à Emilstenstrom pour avoir fait avancer la conversation avec l'équipe VScode.
En fin de compte, c'est un compromis. Utilisez la méthode qui vous a le plus de sens.
Tout comme les signaux, les composants peuvent vivre n'importe où, mais vous devez vous assurer que Django les ramasse au démarrage. La façon la plus simple de le faire est de définir vos composants dans un sous-module components.py de l'application à laquelle ils se rapportent, puis les connecter à l'intérieur de la méthode ready() de votre classe de configuration d'application.
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 ) Vous pouvez également unregister un composant existant ou obtenir un composant à partir du registre:
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 ()Chaque composant enregistré aura deux balises disponibles pour une utilisation dans vos modèles:
{% card %} ... {% endcard %}{% #user_profile %} . Cela peut être utile pour les composants qui ne nécessitent pas nécessairement un corpsPar défaut, les composants seront enregistrés à l'aide des balises suivantes:
{% <component_name> %}{% end<component_name> %}{% #<component_name> %} Ce comportement peut être modifié en fournissant un formateur de balise personnalisé dans vos paramètres. Par exemple, pour modifier les balises de bloc en {% #card %} ... {% /card %} , et la balise en ligne sur {% card %} (similaire aux pantoufles), vous pouvez utiliser le formateur suivant:
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" ,
}Vous pouvez transmettre des données aux composants à l'aide d'arguments de mots clés, qui acceptent des valeurs ou des variables codées en dur:
{% with error_message="Something bad happened!" %}
{% #alert type="error" message=error_message %}
{% endwith %} Tous les attributs seront ajoutés dans un dict attributes qui sera disponible dans le contexte du modèle:
{
"attributes" : {
"type" : " error " ,
"message" : " Something bad happened! "
}
}Vous pouvez ensuite y accéder à partir du modèle de votre composant:
< div class =" alert alert-{{ attributes.type }} " >
{{ attributes.message }}
</ div > Vous pouvez également rendre tous les attributs directement à l'aide de {{ attributes }} . Par exemple, si vous avez le composant suivant
{% alert id="alerts" class="font-bold" %} ... {% endalert %}Vous pouvez rendre tous les attributs en utilisant
< div {{ attributes }} >
<!-- Component content -->
</ div >Ce qui entraînera le rendu HTML suivant:
< div id =" alerts " class =" font-bold " >
<!-- Component content -->
</ div > Vous pouvez également transmettre des attributs avec des caractères spéciaux ( [@:_-.] ), Ainsi que des attributs sans valeur:
{% button @click="handleClick" data-id="123" required %} ... {% endbutton %}Ce qui entraînera le dict de suivi disponible dans le contexte:
{
"attributes" : {
"@click" : "handleClick" ,
"data-id" : "123" ,
"required" : True ,
}
} Et sera rendu par {{ attributes }} comme @click="handleClick" data-id="123" required .
Parfois, vous devrez peut-être spécifier des valeurs par défaut pour les attributs ou fusionner des valeurs supplémentaires dans certains des attributs du composant. La bibliothèque fournit une balise merge_attrs qui aide à ceci:
< div {% merge_attrs attributes class =" alert " role =" alert " %} >
<!-- Component content -->
</ div >Si nous supposons que ce composant est utilisé comme tel:
{% alert class="mb-4" %} ... {% endalert %}Le HTML rendu final du composant apparaîtra comme ce qui suit:
< div class =" alert mb-4 " role =" alert " >
<!-- Component content -->
</ div > Lors de la fusion d'attributs qui ne sont pas des attributs class , les valeurs fournies à la balise merge_attrs seront considérées comme les valeurs "par défaut" de l'attribut. Cependant, contrairement à l'attribut class , ces attributs ne seront pas fusionnés avec des valeurs d'attribut injectées. Au lieu de cela, ils seront écrasés. Par exemple, l'implémentation d'un composant button peut ressembler à ce qui suit:
< button {% merge_attrs attributes type =" button " %} >
{% render_slot slots.inner_block %}
</ button > Pour rendre le composant bouton avec un type personnalisé, il peut être spécifié lors de la consommation du composant. Si aucun type n'est spécifié, le type button sera utilisé:
{% button type="submit" %} Submit {% endbutton %} Le HTML rendu du composant button de cet exemple serait:
< button type =" submit " >
Submit
</ button > Vous pouvez également traiter d'autres attributs comme "appelés" en utilisant l'opérateur += :
< div {% merge_attrs attributes data-value+ =" some-value " %} >
<!-- Component content -->
</ div >Si nous supposons que ce composant est utilisé comme tel:
{% alert data-value="foo" %} ... {% endalert %}Le HTML rendu sera:
< div data-value =" foo some-value " >
<!-- Component content -->
</ div > Par défaut, tous les attributs sont ajoutés à un dict attributes dans le contexte. Cependant, ce n'est peut-être pas toujours ce que nous voulons. Par exemple, imaginez que nous voulons avoir un composant alert qui peut être rejeté, tout en étant capable de transmettre des attributs supplémentaires à l'élément racine, comme un id ou class . Idéalement, nous voudrions pouvoir rendre un composant comme celui-ci:
{% alert id="alerts" dismissible %} Something went wrong! {% endalert %}Une façon naïve de mettre en œuvre ce composant serait quelque chose comme ce qui suit:
< 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 > Cependant, cela entraînerait l'attribut dismissible inclus dans l'élément racine, ce qui n'est pas ce que nous voulons:
< div id =" alerts " dismissible >
Something went wrong!
< button type =" button " class =" btn-close " data-bs-dismiss =" alert " aria-label =" Close " > </ button >
</ div > Idéalement, nous voudrions que l'attribut dismissible soit séparé des attributes car nous voulons seulement l'utiliser dans la logique, mais pas nécessairement le rendre au composant.
Pour y parvenir, vous pouvez manipuler le contexte de votre composant afin de fournir une meilleure API pour utiliser les composants. Il existe plusieurs façons de le faire, choisissez la méthode qui vous est le plus logique, par exemple:
get_context_data et supprimer l'attribut dismissible des attributes et le renvoyer dans le contexte à la place 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 et y manipuler le contexte 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 )Les deux solutions ci-dessus fonctionneront et vous pouvez faire de même pour les composants basés sur la fonction. Le modèle du composant peut alors ressembler à ceci:
< 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 >Ce qui devrait entraîner le rendu du HTML correct:
< div id =" alerts " >
Something went wrong!
< button type =" button " class =" btn-close " data-bs-dismiss =" alert " aria-label =" Close " > </ button >
</ div > Vous devrez souvent passer du contenu supplémentaire à vos composants via des "emplacements". Une variable de contexte slots est transmise à vos composants, qui se compose d'un dict avec le nom de l'emplacement comme clé et la fente comme valeur. Vous pouvez ensuite rendre les emplacements à l'intérieur de vos composants à l'aide de la balise render_slot .
Pour explorer ce concept, imaginons que nous voulons passer du contenu à un composant alert :
{% alert %}
< strong > Whoops! </ strong > Something went wrong!
{% endalert %} Par défaut, ce contenu sera mis à la disposition de votre composant dans la fente par défaut qui est appelée inner_block . Vous pouvez ensuite rendre cette fente à l'aide de la balise render_slot à l'intérieur de votre composant:
{% load components %}
< div class =" alert alert-danger " >
{% render_slot slots.inner_block %}
</ div >Ce qui devrait entraîner le rendu du HTML suivant:
< div class =" alert alert-danger " >
< strong > Whoops! </ strong > Something went wrong!
</ div >Vous pouvez également renommer le créneau par défaut en le spécifiant dans vos paramètres:
# inside your settings
WEB_COMPONENTS = {
"DEFAULT_SLOT_NAME" : "inner_block" ,
}Parfois, un composant peut avoir besoin de rendre plusieurs emplacements différents à différents endroits dans le composant. Modifions notre composant d'alerte pour permettre l'injection d'un emplacement "titre":
{% load components %}
< div class =" alert alert-danger " >
< span class =" alert-title " >
{% render_slot slots.title %}
</ span >
{% render_slot slots.inner_block %}
</ div > Vous pouvez définir le contenu de l'emplacement nommé à l'aide de la balise slot . Tout contenu non dans une balise slot explicite sera ajouté à la fente inner_block par défaut:
{% load components %}
{% alert %}
{% slot title %} Server error {% endslot %}
< strong > Whoops! </ strong > Something went wrong!
{% endalert %}Le HTML rendu dans cet exemple serait:
< div class =" alert alert-danger " >
< span class =" alert-title " >
Server error
</ span >
< strong > Whoops! </ strong > Something went wrong!
</ div >Vous pouvez définir plusieurs fois le même emplacement nommé:
{% unordered_list %}
{% slot item %} First item {% endslot %}
{% slot item %} Second item {% endslot %}
{% slot item %} Third item {% endslot %}
{% endunordered_list %}Vous pouvez ensuite itérer sur la fente à l'intérieur de votre composant:
< ul >
{% for item in slots.item %}
< li > {% render_slot item %} </ li >
{% endfor %}
</ ul >Ce qui entraînera le HTML suivant:
< ul >
< li > First item </ li >
< li > Second item </ li >
< li > Third item </ li >
</ ul > Le contenu de l'emplacement aura également accès au contexte du composant. Pour explorer ce concept, imaginez un composant de liste qui accepte un attribut entries représentant une liste de choses, qu'il ira ensuite et rendra la fente inner_block pour chaque entrée.
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 )Nous pouvons alors rendre le composant comme suit:
{% unordered_list entries=entries %}
I like {{ entry }}!
{% endunordered_list %} Dans cet exemple, la variable entry provient du contexte du composant. Si nous supposons que entries = ["apples", "bananas", "cherries"] , le HTML résultant sera:
< ul >
< li > I like apples! </ li >
< li > I like bananas! </ li >
< li > I like cherries! </ li >
</ ul > Vous pouvez également passer explicitement un deuxième argument à 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 > Lors de l'invoquer le composant, vous pouvez utiliser l'attribut spécial :let prendre la valeur qui a été transmise à render_slot et la lier à une variable:
{% unordered_list :let="fruit" entries=entries %}
I like {{ fruit }}!
{% endunordered_list %}Cela rendrait le même HTML que ci-dessus.
Semblable aux composants, vous pouvez attribuer des attributs supplémentaires aux machines à sous. Vous trouverez ci-dessous un composant de table illustrant plusieurs emplacements nommés avec des attributs:
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 )Vous pouvez invoquer le composant comme tel:
{% table rows=rows %}
{% slot column :let="user" label="Name" %}
{{ user.name }}
{% endslot %}
{% slot column :let="user" label="Age" %}
{{ user.age }}
{% endslot %}
{% endtable %} Si nous supposons que rows = [{ "name": "Jane", "age": "34" }, { "name": "Bob", "age": "51" }] , le HTML suivant sera rendu:
< 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 >Vous pouvez également nicher de composants pour obtenir des éléments plus compliqués. Voici un exemple de la façon dont vous pourriez implémenter un composant accordéon à l'aide de 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 )Vous pouvez ensuite les utiliser comme suit:
{% 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 %} Le projet utilise poetry pour gérer les dépendances. Consultez la documentation sur la façon d'installer la poésie ici: https://python-poetry.org/docs/#installation
Installer les dépendances
poetry installActiver l'environnement
poetry shellMaintenant, vous pouvez exécuter les tests
python runtests.pyLe projet est devenu après avoir vu comment d'autres langues / cadres traitent des composants et vouloir ramener certaines de ces idées à Django.
django-components existante est déjà excellente et prend en charge la plupart des fonctionnalités de ce projet, mais je pensais que la syntaxe pourrait être un peu améliorée pour se sentir moins verbeux, et ajouter quelques choses supplémentaires qui semblaient nécessaires, comme le support pour les composants et les chaînes de modèle basés sur la fonction, et travailler avec des attributs<x-alert type="error">Server Error</x-alert> ), mais la solution était beaucoup plus compliquée et je suis arrivé à la conclusion que l'utilisation d'une approche similaire aux django-components avait beaucoup plus de sens à Django à DjangoDe nombreuses autres langues / cadres utilisent les mêmes concepts pour la construction de composants (machines à sous, attributs), donc une grande partie des connaissances est transférable, et il existe déjà beaucoup de bibliothèques de composants existantes (par exemple, l'utilisation de bootstrap, du vent arrière, de la conception de matériaux, etc.). Je recommande fortement de regarder certains d'entre eux pour s'inspirer de la façon de construire / de structurer vos composants. Voici quelques exemples: