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