diff --git a/CHANGELOG.md b/CHANGELOG.md
index b82194d..7b270d1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,11 @@
+##### 2.6.2 (22 février 2026)
+
+- Correction du téléchargemnent buggé dans /tableau
+
+##### 2.6.1 (17 février 2026)
+
+- Corrections la création des liens canoniques (SEO)
+
#### 2.6.0 (5 février 2026)
- Suite de la refonte graphique
diff --git a/README.md b/README.md
index ecfa81b..1d71b7c 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# decp.info
-> v2.6.0
+> v2.6.2
> Outil d'exploration et de téléchargement des données essentielles de la commande publique.
=> [decp.info](https://decp.info)
diff --git a/pyproject.toml b/pyproject.toml
index 35749a7..cdca66b 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,7 +1,7 @@
[project]
name = "decp.info"
description = "Interface d'exploration et d'analyse des marchés publics français."
-version = "2.6.0"
+version = "2.6.2"
requires-python = ">= 3.10"
authors = [
{ name = "Colin Maudry", email = "colin@colmo.tech" }
@@ -28,6 +28,7 @@ dev = [
"selenium",
"webdriver-manager",
"dash[testing]",
+ "fastexcel"
]
[tool.pytest.ini_options]
@@ -38,6 +39,7 @@ testpaths = [
"tests"
]
env = [
- "DATA_FILE_PARQUET_PATH=tests/test.parquet"
+ "DATA_FILE_PARQUET_PATH=tests/test.parquet",
+ "DEVELOPMENT=true"
]
addopts = "-p no:warnings"
diff --git a/src/app.py b/src/app.py
index 3f45aed..eb1931c 100644
--- a/src/app.py
+++ b/src/app.py
@@ -87,6 +87,7 @@ app.index_string = """
{%title%}
{%favicon%}
{%css%}
+
{%app_entry%}
diff --git a/src/pages/acheteur.py b/src/pages/acheteur.py
index 90e0fe6..199c757 100644
--- a/src/pages/acheteur.py
+++ b/src/pages/acheteur.py
@@ -1,4 +1,5 @@
import datetime
+from typing import Any
import dash_bootstrap_components as dbc
import polars as pl
@@ -17,7 +18,6 @@ from dash import (
from src.callbacks import get_top_org_table
from src.figures import DataTable, make_column_picker, point_on_map
from src.utils import (
- add_canonical_link,
columns,
df,
df_acheteurs,
@@ -34,12 +34,12 @@ from src.utils import (
def get_title(acheteur_id: str = None) -> str:
- df_acheteur = df_acheteurs.filter(pl.col("acheteur_id") == acheteur_id).select(
+ acheteur_nom = df_acheteurs.filter(pl.col("acheteur_id") == acheteur_id).select(
"acheteur_nom"
)
- acheteur_nom = df_acheteur.item(0, 0)
-
- return f"Marchés publics attribués par {acheteur_nom} | decp.info"
+ if acheteur_nom.height > 0:
+ return f"Marchés publics attribués par {acheteur_nom.item(0, 0)} | decp.info"
+ return "Marchés publics attribués | decp.info"
register_page(
@@ -353,7 +353,7 @@ def get_top_titulaires(data):
)
def download_acheteur_data(
n_clicks,
- data: [dict],
+ data: list[dict[str, Any]],
acheteur_nom: str,
annee: str,
):
@@ -476,8 +476,3 @@ def toggle_acheteur_columns(click_open, click_close, is_open):
)
def reset_view(n_clicks):
return "", []
-
-
-@callback(Input("acheteur_url", "pathname"))
-def cb_add_canonical_link(pathname):
- add_canonical_link(pathname)
diff --git a/src/pages/tableau.py b/src/pages/tableau.py
index 920fc20..56fb9b8 100644
--- a/src/pages/tableau.py
+++ b/src/pages/tableau.py
@@ -22,7 +22,6 @@ from dash import (
from figures import make_column_picker
from src.figures import DataTable
from src.utils import (
- add_canonical_link,
columns,
df,
filter_table_data,
@@ -327,7 +326,7 @@ def download_data(n_clicks, filter_query, sort_by, hidden_columns: list = None):
if filter_query:
lff = filter_table_data(lff, filter_query, "tab download")
- if len(sort_by) > 0:
+ if sort_by and len(sort_by) > 0:
lff = sort_table_data(lff, sort_by)
def to_bytes(buffer):
@@ -534,14 +533,3 @@ def toggle_tableau_columns(click_open, click_close, is_open):
)
def reset_view(n_clicks):
return "", []
-
-
-@callback(Input("tableau_url", "pathname"))
-def cb_add_canonical_link(pathname):
- add_canonical_link(pathname)
-
-
-# @callback(Input("tableau_url", "pathname"), Output("btn-copy-url", "children"))
-# def cb_add_canonical_link(pathname):
-# add_canonical_link(pathname)
-#
diff --git a/src/pages/titulaire.py b/src/pages/titulaire.py
index 23c3cf6..b7e9001 100644
--- a/src/pages/titulaire.py
+++ b/src/pages/titulaire.py
@@ -1,4 +1,5 @@
import datetime
+from typing import Any
import dash_bootstrap_components as dbc
import polars as pl
@@ -17,7 +18,6 @@ from dash import (
from src.callbacks import get_top_org_table
from src.figures import DataTable, make_column_picker, point_on_map
from src.utils import (
- add_canonical_link,
columns,
df,
df_titulaires,
@@ -34,12 +34,12 @@ from src.utils import (
def get_title(titulaire_id: str = None) -> str:
- titulaire_nom = (
- df_titulaires.filter(pl.col("titulaire_id") == titulaire_id)
- .select("titulaire_nom")
- .item(0, 0)
+ titulaire_nom = df_titulaires.filter(pl.col("titulaire_id") == titulaire_id).select(
+ "titulaire_nom"
)
- return f"Marchés publics remportés par {titulaire_nom} | decp.info"
+ if titulaire_nom.height > 0:
+ return f"Marchés publics remportés par {titulaire_nom.item(0, 0)} | decp.info"
+ return "Marchés publics remportés | decp.info"
register_page(
@@ -367,7 +367,7 @@ def get_top_acheteurs(data):
)
def download_titulaire_data(
n_clicks,
- data: [dict],
+ data: list[dict[str, Any]],
titulaire_nom: str,
annee: str,
):
@@ -490,8 +490,3 @@ def toggle_titulaire_columns(click_open, click_close, is_open):
)
def reset_view(n_clicks):
return "", []
-
-
-@callback(Input("titulaire_url", "pathname"))
-def cb_add_canonical_link(pathname):
- add_canonical_link(pathname)
diff --git a/src/utils.py b/src/utils.py
index ccd50ef..8c9dae7 100644
--- a/src/utils.py
+++ b/src/utils.py
@@ -5,7 +5,6 @@ import uuid
from collections import OrderedDict
from time import localtime, sleep
-import dash
import polars as pl
import polars.selectors as cs
from dash import no_update
@@ -443,7 +442,7 @@ def get_default_hidden_columns(page):
"codeCPV",
"dureeRestanteMois",
]
- elif page == "titulaire":
+ elif page == "tableau":
displayed_columns = os.getenv("DISPLAYED_COLUMNS")
else:
displayed_columns = os.getenv("DISPLAYED_COLUMNS")
@@ -740,14 +739,6 @@ def make_org_jsonld(org_id, org_type, org_name=None, type_org_id="SIRET") -> dic
return jsonld
-def add_canonical_link(pathname):
- @dash.hooks.index()
- def update_index(html_string):
- url = f"https://{domain_name}{pathname}"
- canonical_tag = f''
- return html_string.replace("", f"{canonical_tag}\n ")
-
-
df: pl.DataFrame = get_decp_data()
schema = df.collect_schema()
diff --git a/tests/conftest.py b/tests/conftest.py
index 7531524..b14e0f8 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -43,8 +43,21 @@ def test_data():
pl.DataFrame(data).write_parquet("tests/test.parquet")
yield path
+ if os.path.exists(path):
+ os.unlink(path)
+ print(path, "deleted")
+
def pytest_setup_options():
options = Options()
- options.add_argument("--window-size=1200,800")
+ options.add_argument("--window-size=1200,1200 ")
+ options.add_experimental_option(
+ "prefs",
+ {
+ "download.default_directory": "/home/colin/git/decp.info",
+ "download.prompt_for_download": False,
+ "download.directory_upgrade": True,
+ "safebrowsing.enabled": True,
+ },
+ )
return options
diff --git a/tests/test_main.py b/tests/test_main.py
index 453a806..1b8e7db 100644
--- a/tests/test_main.py
+++ b/tests/test_main.py
@@ -1,3 +1,4 @@
+import polars as pl
from dash.testing.composite import DashComposite
from selenium.webdriver import Keys
from selenium.webdriver.common.by import By
@@ -58,3 +59,31 @@ def test_002_filter_persistence(dash_duo: DashComposite):
filter_input.send_keys(Keys.ENTER)
filter_input = open_page_and_check_filter_input()
assert filter_input.get_attribute("value") == "11"
+
+
+def test_003_tableau_download(dash_duo: DashComposite):
+ from pages.acheteur import download_acheteur_data
+ from pages.tableau import download_data
+ from pages.titulaire import download_titulaire_data
+ from src.app import app
+
+ # Juste pour instancier l'app
+ print(app.server.name)
+
+ dicts = pl.read_parquet("tests/test.parquet").to_dicts()
+
+ outputs = [
+ download_data(1, "", [], None),
+ download_acheteur_data(1, dicts, "a1", "2025"),
+ download_titulaire_data(1, dicts, "t1", "2025"),
+ ]
+ for output in outputs:
+ assert isinstance(output, dict)
+ for f in ["content", "filename", "type", "base64"]:
+ assert f in output
+ assert isinstance(output["content"], str) and len(output["content"]) > 100
+ assert isinstance(output["filename"], str) and output["filename"].startswith(
+ "decp_"
+ )
+ assert output["type"] is None
+ assert output["base64"] is True