From 6604b873ae74b64b409fa50b471d9c67b51de7b1 Mon Sep 17 00:00:00 2001 From: Pierre Raybaut <1311787+PierreRaybaut@users.noreply.github.com> Date: Fri, 30 Oct 2026 10:06:41 +0100 Subject: [PATCH] fix(dataset): pass the date value to the DateItem display callback DateWidget forwarded the raw argument of the dateTimeChanged signal to the display callback: a QDateTime object, whose type depends on the Qt binding, instead of the value of the item. Pass the Python value of the widget (datetime.date), like DateTimeWidget and the other widgets. Callbacks relying on the previous QDateTime argument must be updated. Refs #126 Assisted-by: Claude Opus 5.5 --- doc/release_notes/release_3.17.md | 1 + guidata/dataset/qtitemwidgets.py | 7 ++-- .../unit/test_datetime_display_callback.py | 39 +++++++++++++++++-- 3 files changed, 40 insertions(+), 7 deletions(-) diff --git a/doc/release_notes/release_3.17.md b/doc/release_notes/release_3.17.md index 368d33d..5fcd522 100644 --- a/doc/release_notes/release_3.17.md +++ b/doc/release_notes/release_3.17.md @@ -14,6 +14,7 @@ * **Application startup blocked by a corrupted translation file** — when a `.mo` translation catalog was corrupted or unreadable (e.g. truncated during installation), importing guidata failed with an error such as `OSError: Bad magic number`. The same applied to any application relying on `guidata.configtools.get_translation`, like PlotPy, Sigima or DataLab, so the application could not start at all. The interface now falls back to English, and a `RuntimeWarning` identifies the faulty catalog (fixes [Issue #119](https://github.com/PlotPyStack/guidata/issues/119)). * **Error when editing a dictionary field in a JSON Schema based form** — in a form generated with `guidata.dataset.dataset_to_schema` (e.g. in a web frontend), editing a `DictItem` field failed with `TypeError: DictItem.__dictedit() missing 1 required positional argument: 'parent'`: the button opening the dictionary editor in the Qt interface was handled as a callback updating the other fields. `DictItem` fields are no longer flagged with `x-guidata-has-callback`, and `resolve_dataset_callbacks` returns an empty mapping for them (fixes [Issue #123](https://github.com/PlotPyStack/guidata/issues/123)). * **Date/time callback never called** — the `display` callback of a `DateTimeItem` was never called when the date or time was changed in a dataset editor, so that the values computed from it were not updated. The callback is now called on each change, with the new value of the item (a `datetime.datetime` object), like for the other items (fixes [Issue #123](https://github.com/PlotPyStack/guidata/issues/123)). +* **Date callback received a Qt object** — the `display` callback of a `DateItem` received a Qt `QDateTime` object, whose type depends on the Qt binding (PyQt5, PyQt6 or PySide6), instead of the value of the item. It now receives the new value of the item (a `datetime.date` object), like the other items. Callbacks relying on the previous `QDateTime` argument must be updated (fixes [Issue #126](https://github.com/PlotPyStack/guidata/issues/126)). * **Plain-text "about" information** — `guidata.widgets.about.about(html=False)` (and `AboutInfo.about(html=False)`) failed with `UnboundLocalError` instead of returning the information about the package as plain text (fixes [Issue #123](https://github.com/PlotPyStack/guidata/issues/123)). * **French translations of the DataFrame editor** — when entering a number format not starting with `%`, the French error message stated the opposite of the actual rule ("must not start with '%'"), and the title of the export dialog box was shown in English. Both are now correctly translated (fixes [Issue #123](https://github.com/PlotPyStack/guidata/issues/123)). diff --git a/guidata/dataset/qtitemwidgets.py b/guidata/dataset/qtitemwidgets.py index 743d1c6..a118a1c 100644 --- a/guidata/dataset/qtitemwidgets.py +++ b/guidata/dataset/qtitemwidgets.py @@ -757,9 +757,10 @@ def __init__( qt_fmt = convert_date_format(fmt) self.dateedit.setDisplayFormat(qt_fmt) - def date_changed(self, value): - """Date changed""" - _display_callback(self, value) + def date_changed(self, _value) -> None: + """Date changed (the ``QDateTime`` argument is ignored: the callback + receives the Python value of the widget)""" + _display_callback(self, self.value()) self.notify_value_change() def get(self) -> None: diff --git a/guidata/tests/unit/test_datetime_display_callback.py b/guidata/tests/unit/test_datetime_display_callback.py index 09dec3b..731f151 100644 --- a/guidata/tests/unit/test_datetime_display_callback.py +++ b/guidata/tests/unit/test_datetime_display_callback.py @@ -4,10 +4,11 @@ # (see guidata/LICENSE for details) """ -Test the display callback of DateTimeItem +Test the display callbacks of DateItem and DateTimeItem -When the date/time is changed in a dataset editor, the ``display`` callback of a -``DateTimeItem`` must be called with the Python value of the item (``datetime``). +When the date (or date/time) is changed in a dataset editor, the ``display`` +callback of a ``DateItem`` (or ``DateTimeItem``) must be called with the Python +value of the item (``date`` or ``datetime``), not with a Qt object. """ from __future__ import annotations @@ -18,11 +19,41 @@ from qtpy import QtWidgets as QW from guidata.dataset import DataSet -from guidata.dataset.dataitems import DateTimeItem +from guidata.dataset.dataitems import DateItem, DateTimeItem from guidata.dataset.qtwidgets import DataSetEditLayout from guidata.qthelpers import qt_app_context +def test_date_display_callback() -> None: + """Changing the date calls the display callback with a date""" + calls = [] + + def callback(instance: DataSet, _item: DateItem, value) -> None: + """Record the callback value and the dataset value""" + calls.append((value, instance.date)) + + class _DateDataSet(DataSet): + """Dataset with a date item having a display callback""" + + date = DateItem("Date", default=datetime.date(2010, 10, 10)).set_prop( + "display", callback=callback + ) + + with qt_app_context(): + dialog = QW.QDialog() + dataset = _DateDataSet() + layout = DataSetEditLayout(dialog, dataset, QW.QGridLayout(dialog)) + try: + (widget,) = layout.get_terminal_widgets() + calls.clear() + widget.dateedit.setDate(QC.QDate(2024, 1, 2)) + expected = datetime.date(2024, 1, 2) + assert calls == [(expected, expected)] + assert type(calls[0][0]) is datetime.date + finally: + dialog.deleteLater() + + def test_datetime_display_callback() -> None: """Changing the date/time calls the display callback with a datetime""" calls = []