Skip to content

Commit

Permalink
Document rich radio button feature
Browse files Browse the repository at this point in the history
  • Loading branch information
christophehenry committed Sep 19, 2024
1 parent 7b38759 commit b29bc53
Show file tree
Hide file tree
Showing 5 changed files with 299 additions and 1 deletion.
218 changes: 218 additions & 0 deletions dsfr/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,12 +170,230 @@ def additional_attributes(cls):


class ExtendedChoices(models.Choices, metaclass=_ExtendedChoicesType):
"""
Extension de [Django's `models.Choices`][1] pour supporter l'ajout d'attributs
arbitraires aux enums en utilisant un dictionnaire.
Exemple d'utilisation :
```python
>>> 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.<enum instance>.value`,
`TemperatureChoices.<enum instance>.label` et
`TemperatureChoices.<enum instance>.name`, `ExtendedChoices` ajoute une propriété
`temperature` pour chaque instance de instance d'enum.
Exemple : `TemperatureChoices.<enum instance>.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
>>> 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] :
>>> from enum import auto
>>> from django.db import models
>>> 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
>>> 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 django.conf import settings
>>> from django.db import models
>>> 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 :
>>> 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
>>> 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
"""

@staticmethod
def private_variable_name(name):
return f"__{name}"


class RichRadioButtonChoices(ExtendedChoices):
"""
Version spécialisée de `RichRadioButtonChoices` à 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
>>> class ExampleRichChoices(IntegerChoices, RichRadioButtonChoices):
... ITEM_1 = {
... "value": auto(),
... "label": "Item 1",
... "html_label": "<strong>Item 1</strong>",
... "pictogram": lazy_static("img/placeholder.1x1.png"),
... }
... ITEM_2 = {
... "value": auto(),
... "label": "Item 2",
... "html_label": "<strong>Item 2</strong>",
... "pictogram": lazy_static("img/placeholder.1x1.png"),
... }
... ITEM_3 = {
... "value": auto(),
... "label": "Item 3",
... "html_label": "<strong>Item 3</strong>",
... "pictogram": lazy_static("img/placeholder.1x1.png"),
... }
```
Voir `dsfr.widgets.RichRadioSelect` pour plus de détails.
"""

@enum_property
def pictogram(self):
return getattr(self, self.private_variable_name("pictogram"), "")
Expand Down
3 changes: 2 additions & 1 deletion dsfr/templates/dsfr/widgets/rich_radio_option.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
<input value="{{ widget.value }}"
type="{{ widget.type }}"
id="{{ widget.attrs.id }}"
name="{{ widget.name }}">
name="{{ widget.name }}"
{% include "django/forms/widgets/attrs.html" %}>
<label class="fr-label" for="{{ widget.attrs.id }}">
{{ widget.html_label }}
</label>
Expand Down
6 changes: 6 additions & 0 deletions dsfr/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,4 +134,10 @@ def dsfr_input_class_attr(bf: BoundField):


def lazy_static(path):
"""
Équivalent du tag Django `{% static %}` à utiliser dans le code.
Exemple :
>>> lazy_static("img/logo.png")
"""
return keep_lazy_text(functools.partial(static, path))
72 changes: 72 additions & 0 deletions dsfr/widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,5 +57,77 @@ def create_option(


class RichRadioSelect(_RichChoiceWidget, RadioSelect):
"""
Widget permettant de produire des boutons radio riches. Ce widget fonctionne avec
`dsfr.enums.RichRadioButtonChoices`.
`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
>>> class ExampleRichChoices(RichRadioButtonChoices, IntegerChoices):
... ITEM_1 = {
... "value": auto(),
... "label": "Item 1",
... "html_label": "<strong>Item 1</strong>",
... "pictogram": lazy_static("img/placeholder.1x1.png"),
... }
... ITEM_2 = {
... "value": auto(),
... "label": "Item 2",
... "html_label": "<strong>Item 2</strong>",
... "pictogram": lazy_static("img/placeholder.1x1.png"),
... }
... ITEM_3 = {
... "value": auto(),
... "label": "Item 3",
... "html_label": "<strong>Item 3</strong>",
... "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
`<label>`. Le code est automatiquement marqué sûr avec
[`django.utils.safestring.mark_safe`][1] et ne produira pas de
[problème d'échappement du HTML][2] dans vos templates.
Si `html_label` n'est pas déclaré par un membre de l'enum, la propriété `html_label`
renvoie la valeur de la propriété `label` à la place.
### `pictogram`
L'attribut `pictogram` peut être utilisé pour spécifier le pictogramme du bouton
radio riche. Il peut être utilisé en combinaison avec `dsfr.utils.lazy_static`
pour charger une ressource statique.
### `pictogram_alt`
L'attribut `pictogram_alt` définit la valeur à mettre dans l'attribut `atl` de la
balise `<img>` utilisée dans le bouton radio riche. S'il n'est pas déclaré par
l'enum, `RichRadioSelect` ajoute un `alt=""`.
[1]: https://docs.djangoproject.com/en/5.1/ref/utils/#django.utils.safestring.mark_safe
[2]: https://docs.djangoproject.com/en/5.1/ref/templates/language/#automatic-html-escaping
"""

template_name = "dsfr/widgets/rich_radio.html"
option_template_name = "dsfr/widgets/rich_radio_option.html"
1 change: 1 addition & 0 deletions example_app/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ def format_markdown_from_file(filename: str, ignore_first_line: bool = False) ->
md = markdown.Markdown(
extensions=[
"markdown.extensions.fenced_code",
"markdown.extensions.attr_list",
TocExtension(toc_depth="2-6"),
CodeHiliteExtension(css_class="dsfr-code"),
],
Expand Down

0 comments on commit b29bc53

Please sign in to comment.