diff --git a/doc/forms.md b/doc/forms.md index b850a55a1..2b90462a5 100644 --- a/doc/forms.md +++ b/doc/forms.md @@ -58,3 +58,329 @@ def __init__(self, *args, **kwargs): La balise `{% dsfr_form %}` est maintenant dépréciée et sera retirée à la fin de l’année 2024. Il faut donc remplacer les instances de `{% dsfr_form %}` par ``{{ form }}`` et `{% dsfr_form my_custom_form %}` par `{{ my_custom_form }}`. + +## Composants + +### `dsfr.enums.ExtendedChoices` {: #extended-choices } + +Extension de [Django's `models.Choices`][1] pour supporter l'ajout d'attributs +arbitraires aux enums en utilisant un dictionnaire. + +Exemple d'utilisation : + +```python +from dsfr.enums import ExtendedChoices + +class TemperatureChoices(ExtendedChoices): + COLD = { + "value": "COLD", + "label": "Cold", + "temperature": "<12°c" + } + OK = { + "value": "OK", + "label": "ok", + "temperature": ">=12°c,<25°c" + } + HOT = { + "value": "HOT", + "label": "Hot!", + "temperature": ">=25°c" + } + +TemperatureChoices.OK.temperature == ">=12°c,<25°c" +``` + +Dans l'exemple précédent, en plus de `TemperatureChoices..value`, +`TemperatureChoices..label` et +`TemperatureChoices..name`, `ExtendedChoices` ajoute une propriété +`temperature` pour chaque instance de instance d'enum. + +Exemple : `TemperatureChoices..temperature`. + +Notez que `"value"` est la seule clé obligatoire dans le dictionnaire. Lorsqu'un +dictionnaire ne contient que `« value »`, les exemples suivants sont équivalents : + +```python +from django.db import models +from dsfr.enums import ExtendedChoices + +class TemperatureChoices(models.Choices): + COLD = "COLD" + +class TemperatureChoices(ExtendedChoices): + COLD = {"value": "COLD"} +``` + +Voir [cette section sur la façon de fournir des valeurs par défaut pour des attributs supplémentaires](#default-values) + +#### Utilisation avec `enum.auto()` + +`ExtendedChoices` supporte l'utilisation de [`enum.auto()`][2] : + +```python +from enum import auto +from django.db import models +from dsfr.enums import ExtendedChoices + +class TemperatureChoices(ExtendedChoices, models.TextChoices): + COLD = { + "value": auto(), + "label": "Cold", + "temperature": "<12°c" + } + OK = { + "value": auto(), + "label": "ok", + "temperature": ">=12°c,<25°c" + } + HOT = { + "value": auto(), + "label": "Hot!", + "temperature": ">=25°c" + } +``` + +`ExtendedChoices` peut être utilisé en combinaison `django.db.models.TextChoices` +ou `django.db.models.IntegerChoices` pour calculer automatiquement l'attribut +`value` avec `enum.auto()` ou peut définir une méthode `_generate_next_value_` pour +fournir la valeur (voir [[2]][2]) : + +```python +from enum import auto +from dsfr.enums import ExtendedChoices + +class TemperatureChoices(ExtendedChoices): + COLD = { + "value": auto(), + "label": "Cold", + "temperature": "<12°c" + } + OK = { + "value": auto(), + "label": "ok", + "temperature": ">=12°c,<25°c" + } + HOT = { + "value": auto(), + "label": "Hot!", + "temperature": ">=25°c" + } + + def _generate_next_value_(name, start, count, last_values): + return f"{name}: {count}" +``` + +#### Fournir des valeurs par défaut aux attributs supplémentaires {: #default-values} + +Il peut arriver que vous souhaitiez fournir dynamiquement des valeurs pour un +attribut supplémentaire. Si vous ne spécifiez pas de valeur pour un attribut +supplémentaire lors de la déclaration de l'enum, vous pouvez la fournir +dynamiquement avec la méthode `dynamic_attribute_value` : + +```python +from enum import auto +from django.conf import settings +from django.db import models +from dsfr.enums import ExtendedChoices +class TemperatureChoices(ExtendedChoices, models.IntegerChoices): + COLD = { + "value": auto(), + "temperature": {"lorem": "ipsum 1"}, + } + OK = auto() + + def dynamic_attribute_value(self, name): + if name == "temperature": + return settings.TEMPERATURES[self.value] + else: + return -1 +``` + +Dans l'exemple précédent, la valeur de `temperature` n'est pas spécifiée pour +`TemperatureChoices.OK`. Accéder à la propriété `TemperatureChoices.OK.temperature` +appellera `TemperatureChoices.OK.dynamic_attribute_value("temperature")` pour . + +#### Utilisation avancée + +Par défaut, lorsque vous spécifiez des attibuts supplémentaires, `ExtendedChoices` +stocke cette valeur dans un membre de l'instance. Le nom de se membre correspond +au nom de l'attribut spécifié précédé de `__`. Exemple : + +```python +from enum import auto +from dsfr.enums import ExtendedChoices + +class TemperatureChoices(ExtendedChoices): + COLD = { + "value": auto(), + "label": "Cold", + "temperature": "<12°c" + } + OK = { + "value": auto(), + "label": "ok", + "temperature": ">=12°c,<25°c" + } + HOT = { + "value": auto(), + "label": "Hot!", + "temperature": ">=25°c" + } + +TemperatureChoices.OK.__temperature == TemperatureChoices.OK.temperature +``` + +Si, pour quelque raison que ce soit, vous souhaitez utiliser un autre nom pour +l'instance qui stocke la valeur, vous pouvez déclarer une méthode statique +`private_variable_name` : + +```python +from enum import auto +from dsfr.enums import ExtendedChoices + +class TemperatureChoices(ExtendedChoices): + COLD = { + "value": auto(), + "label": "Cold", + "temperature": "<12°c" + } + OK = { + "value": auto(), + "label": "ok", + "temperature": ">=12°c,<25°c" + } + HOT = { + "value": auto(), + "label": "Hot!", + "temperature": ">=25°c" + } + + @staticmethod + def private_variable_name(name): + return f"m_{name}" + +TemperatureChoices.OK.m_temperature == TemperatureChoices.OK.temperature +``` + +[1]: https://docs.djangoproject.com/en/5.1/ref/models/fields/#enumeration-types +[2]: https://docs.python.org/3/library/enum.html#enum.auto + +### `dsfr.enums.RichRadioButtonChoices` {: #rich-choices } + +Version spécialisée de [`RichRadioButtonChoices`](#extended-choices) à utiliser avec +`dsfr.widgets.RichRadioSelect`. Cette version déclare en plus les propriétés +`pictogram`, `pictogram_alt` et `html_label` : + +```python +from enum import auto +from django.db.models import IntegerChoices +from dsfr.utils import lazy_static +from dsfr.enums import RichRadioButtonChoices + +class ExampleRichChoices(IntegerChoices, RichRadioButtonChoices): + ITEM_1 = { + "value": auto(), + "label": "Item 1", + "html_label": "Item 1", + "pictogram": lazy_static("img/placeholder.1x1.png"), + } + ITEM_2 = { + "value": auto(), + "label": "Item 2", + "html_label": "Item 2", + "pictogram": lazy_static("img/placeholder.1x1.png"), + } + ITEM_3 = { + "value": auto(), + "label": "Item 3", + "html_label": "Item 3", + "pictogram": lazy_static("img/placeholder.1x1.png"), + } +``` + +Voir [`dsfr.widgets.RichRadioSelect`](#rich-radio-select) pour plus de détails. + +### `dsfr.widgets.RichRadioSelect` {: #rich-radio-select } + +Widget permettant de produire des boutons radio riches. Ce widget fonctionne avec +[`dsfr.enums.ExtendedChoices`](#rich-choices). + +`RichRadioSelect.__init__` prend obligatoirement un argument `rich_choices` de type +`RichRadioButtonChoices`. + +Utilisation : + +```python +from enum import auto +from django.db.models import IntegerChoices +from django import forms +from dsfr.forms import DsfrBaseForm +from dsfr.utils import lazy_static +from dsfr.enums import RichRadioButtonChoices +from dsfr.widgets import RichRadioSelect + +class ExampleRichChoices(RichRadioButtonChoices, IntegerChoices): + ITEM_1 = { + "value": auto(), + "label": "Item 1", + "html_label": "Item 1", + "pictogram": lazy_static("img/placeholder.1x1.png"), + } + ITEM_2 = { + "value": auto(), + "label": "Item 2", + "html_label": "Item 2", + "pictogram": lazy_static("img/placeholder.1x1.png"), + } + ITEM_3 = { + "value": auto(), + "label": "Item 3", + "html_label": "Item 3", + "pictogram": lazy_static("img/placeholder.1x1.png"), + } + +class ExampleForm(DsfrBaseForm): + sample_rich_radio = forms.ChoiceField( + label="Cases à cocher", + required=False, + choices=ExampleRichChoices.choices, + help_text="Exemple de boutons radios riches", + widget=RichRadioSelect(rich_choices=ExampleRichChoices), + ) +``` + +#### `html_label` + +L'attribut `html_label` peut-être utilisé pour déclarer du HTML à insérer dans +`