feat(vues): slugify + build_view_url + parsing du jeton (#112)
This commit is contained in:
@@ -1,5 +1,10 @@
|
|||||||
|
import re
|
||||||
|
|
||||||
import dash_bootstrap_components as dbc
|
import dash_bootstrap_components as dbc
|
||||||
from dash import html
|
from dash import html
|
||||||
|
from unidecode import unidecode
|
||||||
|
|
||||||
|
from src.utils import DOMAIN_NAME
|
||||||
|
|
||||||
|
|
||||||
def bar_style(has_subscription: bool) -> dict:
|
def bar_style(has_subscription: bool) -> dict:
|
||||||
@@ -10,6 +15,30 @@ def clean_view_name(name: str | None) -> str:
|
|||||||
return (name or "").strip()
|
return (name or "").strip()
|
||||||
|
|
||||||
|
|
||||||
|
def slugify(name: str | None) -> str:
|
||||||
|
"""Slug décoratif en tirets (convention web) : minuscule, translittéré ASCII,
|
||||||
|
non-alphanumériques → '-', collapse/trim. Ne contient jamais d'underscore
|
||||||
|
(qui sert de séparateur slug↔jeton dans l'URL)."""
|
||||||
|
ascii_name = unidecode(name or "").lower()
|
||||||
|
return re.sub(r"[^a-z0-9]+", "-", ascii_name).strip("-")
|
||||||
|
|
||||||
|
|
||||||
|
def build_view_url(name: str, token: str) -> str:
|
||||||
|
slug = slugify(name)
|
||||||
|
prefix = f"{slug}_" if slug else ""
|
||||||
|
return f"https://{DOMAIN_NAME}/tableau?vue={prefix}{token}"
|
||||||
|
|
||||||
|
|
||||||
|
def token_from_vue_param(value: str | None) -> str | None:
|
||||||
|
"""Extrait le jeton du paramètre ?vue=. Le jeton base62 ne contient pas d'`_`
|
||||||
|
et le slug est en tirets, donc le segment après le dernier `_` est le jeton
|
||||||
|
(`?vue=abc123` sans slug fonctionne aussi)."""
|
||||||
|
if not value:
|
||||||
|
return None
|
||||||
|
token = value.rsplit("_", 1)[-1]
|
||||||
|
return token or None
|
||||||
|
|
||||||
|
|
||||||
def prepare_view_to_save(
|
def prepare_view_to_save(
|
||||||
has_subscription: bool, name: str | None
|
has_subscription: bool, name: str | None
|
||||||
) -> tuple[str | None, str | None]:
|
) -> tuple[str | None, str | None]:
|
||||||
|
|||||||
@@ -58,3 +58,41 @@ def test_views_table_lists_views():
|
|||||||
out = ui.views_table([_view(1, "Vue A", "filtres=a")])
|
out = ui.views_table([_view(1, "Vue A", "filtres=a")])
|
||||||
text = str(out)
|
text = str(out)
|
||||||
assert "Vue A" in text
|
assert "Vue A" in text
|
||||||
|
|
||||||
|
|
||||||
|
def test_slugify_accents_spaces_case():
|
||||||
|
assert ui.slugify("Mes Marchés 2024") == "mes-marches-2024"
|
||||||
|
|
||||||
|
|
||||||
|
def test_slugify_special_chars_collapse_and_trim():
|
||||||
|
assert ui.slugify(" Éà!! --- test__ok ") == "ea-test-ok"
|
||||||
|
|
||||||
|
|
||||||
|
def test_slugify_never_contains_underscore():
|
||||||
|
assert "_" not in ui.slugify("a_b c")
|
||||||
|
|
||||||
|
|
||||||
|
def test_slugify_empty():
|
||||||
|
assert ui.slugify("") == ""
|
||||||
|
assert ui.slugify(None) == ""
|
||||||
|
|
||||||
|
|
||||||
|
def test_build_view_url_dev_domain(monkeypatch):
|
||||||
|
# DOMAIN_NAME est résolu à l'import ; on patche l'attribut du module ui.
|
||||||
|
monkeypatch.setattr(ui, "DOMAIN_NAME", "test.colibre.fr")
|
||||||
|
url = ui.build_view_url("Mes Marchés", "abc123")
|
||||||
|
assert url == "https://test.colibre.fr/tableau?vue=mes-marches_abc123"
|
||||||
|
|
||||||
|
|
||||||
|
def test_build_view_url_empty_slug_omits_prefix(monkeypatch):
|
||||||
|
monkeypatch.setattr(ui, "DOMAIN_NAME", "test.colibre.fr")
|
||||||
|
url = ui.build_view_url("!!!", "abc123")
|
||||||
|
assert url == "https://test.colibre.fr/tableau?vue=abc123"
|
||||||
|
|
||||||
|
|
||||||
|
def test_token_from_vue_param():
|
||||||
|
assert ui.token_from_vue_param("mes-marches-2024_abc123") == "abc123"
|
||||||
|
assert ui.token_from_vue_param("zzz_abc123") == "abc123"
|
||||||
|
assert ui.token_from_vue_param("abc123") == "abc123"
|
||||||
|
assert ui.token_from_vue_param("") is None
|
||||||
|
assert ui.token_from_vue_param(None) is None
|
||||||
|
|||||||
Reference in New Issue
Block a user