Skip to content

views.export_view.export_window

ExportView

Bases: QWidget

Last wizard page: CSV + YAML → ZIP.

This window handles the final export of processed data, allowing users to: 1. Select ID and value columns. 2. Provide metadata about the dataset. 3. Export the data as a ZIP file containing CSV and YAML files.

Source code in src/views/export_view/export_window.py
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
class ExportView(QWidget):
    """
    Last wizard page: CSV + YAML → ZIP.

    This window handles the final export of processed data, allowing users to:
      1. Select ID and value columns.
      2. Provide metadata about the dataset.
      3. Export the data as a ZIP file containing CSV and YAML files.
    """

    # Signal emitted when export finishes, carrying the absolute path of the saved ZIP
    exportFinished = Signal(str)

    # --------------------------------------------------------------------------
    #  Construction / Wiring
    # --------------------------------------------------------------------------
    def __init__(self) -> None:
        """
        Initializes the export window.

        - Creates an instance of ExportLogic to handle core export operations.
        - Calls internal methods to set up UI widgets and connect signals.
        """
        super().__init__()
        self._logic = ExportLogic()
        self._setup_ui()
        self._wire_signals()

    def _setup_ui(self) -> None:
        """
        Sets up the UI components by loading the Qt Designer form.

        - Instantiates Ui_ExportWindow and applies it to this widget.
        """
        self.ui = Ui_ExportWindow()
        self.ui.setupUi(self)

    def _wire_signals(self) -> None:
        """
        Connects UI signals to their respective handlers.

        - btn_export click → _on_export to start the export routine.
        - combo_id and combo_val changes → _update_status to refresh status label.
        """
        self.ui.btn_export.clicked.connect(self._on_export)
        self.ui.combo_id.currentTextChanged.connect(self._update_status)
        self.ui.combo_val.currentTextChanged.connect(self._update_status)

    # ==============================================================================
    #  Data Initialization (called by MainWindow before page is shown)
    # ==============================================================================
    def load_data(self, df_stats: pd.DataFrame) -> None:
        """
        Populates the dropdowns with column data from the provided DataFrame.

        Steps:
          1. If df_stats is None or empty, set status to indicate no data.
          2. Otherwise, obtain ID and value column lists from ExportLogic.
          3. Block signals on combo boxes to avoid premature status updates.
          4. Clear existing items in combo boxes.
          5. Add display names to combos while storing original column names as data.
          6. Unblock signals and set defaults if available.
          7. Call _update_status to reflect initial combo state.
        """
        if df_stats is None or df_stats.empty:
            self.ui.label_status.setText("No data available for export")
            return

        # Obtain lists of ID and value columns (display names and originals)
        id_display, id_cols, val_display, val_cols = self._logic.load_data(df_stats)

        # Prevent status updates while populating combos
        self.ui.combo_id.blockSignals(True)
        self.ui.combo_val.blockSignals(True)
        self.ui.combo_id.clear()
        self.ui.combo_val.clear()

        # Populate ID combo: display name shown, original name stored as data
        for display, original in zip(id_display, id_cols):
            self.ui.combo_id.addItem(display, original)
        # Populate Value combo similarly
        for display, original in zip(val_display, val_cols):
            self.ui.combo_val.addItem(display, original)

        # Re-enable signals after population
        self.ui.combo_id.blockSignals(False)
        self.ui.combo_val.blockSignals(False)

        # Select the first items if available
        if id_cols:
            self.ui.combo_id.setCurrentIndex(0)
        if val_cols:
            self.ui.combo_val.setCurrentIndex(0)

        # Update status label based on the populated combos
        self._update_status()

    # ==============================================================================
    #  Export Routine
    # ==============================================================================
    def _on_export(self) -> None:
        """
        Handles the export button click event.

        Steps:
          1. Validate that name, description, and source fields are not empty.
             - If any is empty, update status label and return.
          2. Obtain the original column names from combo_id and combo_val.
             - If either is missing or the same, update status and return.
          3. Open a file dialog for the user to choose the ZIP save location.
             - If no path chosen, abort.
          4. Gather metadata from UI fields (name, description, source, year, data_type).
          5. Call ExportLogic to export data and metadata to the selected ZIP path.
          6. On success, set status label to indicate completion and emit exportFinished.
          7. On any exception, log error and update status label accordingly.
        """
        # Validate dataset name
        if not (name := self.ui.edit_name.text().strip()):
            self.ui.label_status.setText("Please enter a name!")
            return
        # Validate description
        if not (descr := self.ui.edit_description.toPlainText().strip()):
            self.ui.label_status.setText("Please enter a description!")
            return
        # Validate source
        if not (source := self.ui.edit_source.text().strip()):
            self.ui.label_status.setText("Please enter a source!")
            return

        # Retrieve selected ID and value columns (original names stored as data)
        id_col = self.ui.combo_id.currentData()
        val_col = self.ui.combo_val.currentData()
        # Validate that both columns are selected and differ
        if not id_col or not val_col:
            self.ui.label_status.setText("Please pick ID and value columns!")
            return
        if id_col == val_col:
            self.ui.label_status.setText("ID and value column must differ!")
            return

        # Prompt user to select target ZIP file location
        file_path, _ = QFileDialog.getSaveFileName(self, "Save ZIP File", str(Path.home() / "exported_data.zip"), "ZIP Files (*.zip)")  # type: ignore[attr-defined]
        # If user canceled the dialog, do nothing
        if not file_path:
            return

        try:
            # Collect metadata from UI fields and combo selections
            metadata = self._logic.get_metadata(
                id_col=id_col,
                val_col=val_col,
                name=name,
                description=descr,
                source=source,
                year=self.ui.spin_year.value(),
                data_type=self.ui.combo_type.currentText(),
            )

            # Perform the export operation (CSV + YAML → ZIP)
            self._logic.export_data(id_col, val_col, metadata, file_path)

            # Indicate success and emit signal
            self.ui.label_status.setText(f"Successfully exported to: {file_path}")
            self.exportFinished.emit(file_path)
        except Exception as exc:
            # Log detailed traceback and set error status
            log.error("Export failed", exc_info=True)
            self.ui.label_status.setText(f"Export failed: {exc}")

    # ==============================================================================
    #  Small UX Helper
    # ==============================================================================
    def _update_status(self) -> None:
        """
        Updates the status label based on current selections.

        - Checks that combo_id has a non-empty text.
        - Checks that combo_val has a non-empty text and is not the same as combo_id.
        - Sets label to "Ready to export" if both are valid, otherwise prompts selection.
        """
        id_ok = bool(self.ui.combo_id.currentText())
        val_ok = bool(self.ui.combo_val.currentText()) and (self.ui.combo_val.currentText() != self.ui.combo_id.currentText())
        # Display appropriate prompt or success readiness
        self.ui.label_status.setText("Ready to export" if id_ok and val_ok else "Select ID / value columns")

    # ==============================================================================
    #  Wizard Navigation Hooks
    # ==============================================================================
    # noinspection PyMethodMayBeStatic
    def can_go_next(self) -> bool:
        """
        Indicates whether the wizard can move forward from this page.

        Always returns False because this is the last page.
        """
        return False

    # noinspection PyMethodMayBeStatic
    def can_go_back(self) -> bool:
        """
        Indicates whether the wizard can move back to the previous page.

        Always returns True to allow backward navigation.
        """
        return True

__init__()

Initializes the export window.

  • Creates an instance of ExportLogic to handle core export operations.
  • Calls internal methods to set up UI widgets and connect signals.
Source code in src/views/export_view/export_window.py
44
45
46
47
48
49
50
51
52
53
54
def __init__(self) -> None:
    """
    Initializes the export window.

    - Creates an instance of ExportLogic to handle core export operations.
    - Calls internal methods to set up UI widgets and connect signals.
    """
    super().__init__()
    self._logic = ExportLogic()
    self._setup_ui()
    self._wire_signals()

can_go_back()

Indicates whether the wizard can move back to the previous page.

Always returns True to allow backward navigation.

Source code in src/views/export_view/export_window.py
226
227
228
229
230
231
232
def can_go_back(self) -> bool:
    """
    Indicates whether the wizard can move back to the previous page.

    Always returns True to allow backward navigation.
    """
    return True

can_go_next()

Indicates whether the wizard can move forward from this page.

Always returns False because this is the last page.

Source code in src/views/export_view/export_window.py
217
218
219
220
221
222
223
def can_go_next(self) -> bool:
    """
    Indicates whether the wizard can move forward from this page.

    Always returns False because this is the last page.
    """
    return False

load_data(df_stats)

Populates the dropdowns with column data from the provided DataFrame.

Steps
  1. If df_stats is None or empty, set status to indicate no data.
  2. Otherwise, obtain ID and value column lists from ExportLogic.
  3. Block signals on combo boxes to avoid premature status updates.
  4. Clear existing items in combo boxes.
  5. Add display names to combos while storing original column names as data.
  6. Unblock signals and set defaults if available.
  7. Call _update_status to reflect initial combo state.
Source code in src/views/export_view/export_window.py
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
def load_data(self, df_stats: pd.DataFrame) -> None:
    """
    Populates the dropdowns with column data from the provided DataFrame.

    Steps:
      1. If df_stats is None or empty, set status to indicate no data.
      2. Otherwise, obtain ID and value column lists from ExportLogic.
      3. Block signals on combo boxes to avoid premature status updates.
      4. Clear existing items in combo boxes.
      5. Add display names to combos while storing original column names as data.
      6. Unblock signals and set defaults if available.
      7. Call _update_status to reflect initial combo state.
    """
    if df_stats is None or df_stats.empty:
        self.ui.label_status.setText("No data available for export")
        return

    # Obtain lists of ID and value columns (display names and originals)
    id_display, id_cols, val_display, val_cols = self._logic.load_data(df_stats)

    # Prevent status updates while populating combos
    self.ui.combo_id.blockSignals(True)
    self.ui.combo_val.blockSignals(True)
    self.ui.combo_id.clear()
    self.ui.combo_val.clear()

    # Populate ID combo: display name shown, original name stored as data
    for display, original in zip(id_display, id_cols):
        self.ui.combo_id.addItem(display, original)
    # Populate Value combo similarly
    for display, original in zip(val_display, val_cols):
        self.ui.combo_val.addItem(display, original)

    # Re-enable signals after population
    self.ui.combo_id.blockSignals(False)
    self.ui.combo_val.blockSignals(False)

    # Select the first items if available
    if id_cols:
        self.ui.combo_id.setCurrentIndex(0)
    if val_cols:
        self.ui.combo_val.setCurrentIndex(0)

    # Update status label based on the populated combos
    self._update_status()