diff --git a/CHANGELOG.md b/CHANGELOG.md index e2c767d..ebc3891 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 0.6.2 + + * Ensure that adding a document whilst on an older date page, uses that date as its upload date + # 0.6.1 * Consolidate some code related to opening documents using the Documents feature. diff --git a/bouquin/db.py b/bouquin/db.py index 6195feb..ea8ba5c 100644 --- a/bouquin/db.py +++ b/bouquin/db.py @@ -1333,9 +1333,16 @@ class DBManager: project_id: int, file_path: str, description: str | None = None, + uploaded_at: str | None = None, ) -> int: """ Read a file from disk and store it as a BLOB in project_documents. + + Args: + project_id: The project to attach the document to + file_path: Path to the file to upload + description: Optional description + uploaded_at: Optional date in YYYY-MM-DD format. If None, uses current date. """ path = Path(file_path) if not path.is_file(): @@ -1349,22 +1356,43 @@ class DBManager: with self.conn: cur = self.conn.cursor() - cur.execute( - """ - INSERT INTO project_documents - (project_id, file_name, mime_type, - description, size_bytes, data) - VALUES (?, ?, ?, ?, ?, ?); - """, - ( - project_id, - file_name, - mime_type, - description, - size_bytes, - Binary(data), - ), - ) + if uploaded_at is not None: + # Use explicit date + cur.execute( + """ + INSERT INTO project_documents + (project_id, file_name, mime_type, + description, size_bytes, uploaded_at, data) + VALUES (?, ?, ?, ?, ?, ?, ?); + """, + ( + project_id, + file_name, + mime_type, + description, + size_bytes, + uploaded_at, + Binary(data), + ), + ) + else: + # Let DB default to current date + cur.execute( + """ + INSERT INTO project_documents + (project_id, file_name, mime_type, + description, size_bytes, data) + VALUES (?, ?, ?, ?, ?, ?); + """, + ( + project_id, + file_name, + mime_type, + description, + size_bytes, + Binary(data), + ), + ) doc_id = cur.lastrowid or 0 return int(doc_id) @@ -1376,6 +1404,20 @@ class DBManager: (description, doc_id), ) + def update_document_uploaded_at(self, doc_id: int, uploaded_at: str) -> None: + """ + Update the uploaded_at date for a document. + + Args: + doc_id: Document ID + uploaded_at: Date in YYYY-MM-DD format + """ + with self.conn: + self.conn.execute( + "UPDATE project_documents SET uploaded_at = ? WHERE id = ?;", + (uploaded_at, doc_id), + ) + def delete_document(self, doc_id: int) -> None: with self.conn: self.conn.execute("DELETE FROM project_documents WHERE id = ?;", (doc_id,)) diff --git a/bouquin/documents.py b/bouquin/documents.py index d1acbeb..c30f31c 100644 --- a/bouquin/documents.py +++ b/bouquin/documents.py @@ -151,7 +151,7 @@ class TodaysDocumentsWidget(QFrame): def _open_documents_dialog(self) -> None: """Open the full DocumentsDialog.""" - dlg = DocumentsDialog(self._db, self) + dlg = DocumentsDialog(self._db, self, current_date=self._current_date) dlg.exec() # Refresh after any changes self.reload() @@ -179,12 +179,14 @@ class DocumentsDialog(QDialog): db: DBManager, parent: QWidget | None = None, initial_project_id: Optional[int] = None, + current_date: Optional[str] = None, ) -> None: super().__init__(parent) self._db = db self.cfg = load_db_config() self._reloading_docs = False self._search_text: str = "" + self._current_date = current_date # Store the current date for document uploads self.setWindowTitle(strings._("project_documents_title")) self.resize(900, 450) @@ -382,10 +384,9 @@ class DocumentsDialog(QDialog): desc_item = QTableWidgetItem(description or "") self.table.setItem(row_idx, self.DESC_COL, desc_item) - # Col 3: Added at (not editable) + # Col 3: Added at (editable) added_label = uploaded_at added_item = QTableWidgetItem(added_label) - added_item.setFlags(added_item.flags() & ~Qt.ItemIsEditable) self.table.setItem(row_idx, self.ADDED_COL, added_item) # Col 4: Size (not editable) @@ -422,7 +423,9 @@ class DocumentsDialog(QDialog): for path in paths: try: - self._db.add_document_from_path(proj_id, path) + self._db.add_document_from_path( + proj_id, path, uploaded_at=self._current_date + ) except Exception as e: # pragma: no cover QMessageBox.warning( self, @@ -469,7 +472,7 @@ class DocumentsDialog(QDialog): def _on_item_changed(self, item: QTableWidgetItem) -> None: """ - Handle inline edits to Description and Tags. + Handle inline edits to Description, Tags, and Added date. """ if self._reloading_docs or item is None: return @@ -524,9 +527,55 @@ class DocumentsDialog(QDialog): item.setForeground(QColor()) finally: self._reloading_docs = False + return + + # Added date column + if col == self.ADDED_COL: + date_str = item.text().strip() + + # Validate date format (YYYY-MM-DD) + if not self._validate_date_format(date_str): + QMessageBox.warning( + self, + strings._("project_documents_title"), + ( + strings._("documents_invalid_date_format") + if hasattr(strings, "_") + and callable(getattr(strings, "_")) + and "documents_invalid_date_format" in dir(strings) + else f"Invalid date format. Please use YYYY-MM-DD format.\nExample: {date_str[:4]}-01-15" + ), + ) + # Reload to reset the cell to its original value + self._reload_documents() + return + + # Update the database + self._db.update_document_uploaded_at(doc_id, date_str) + return # --- utils ------------------------------------------------------------- + def _validate_date_format(self, date_str: str) -> bool: + """ + Validate that a date string is in YYYY-MM-DD format. + + Returns True if valid, False otherwise. + """ + import re + from datetime import datetime + + # Check basic format with regex + if not re.match(r"^\d{4}-\d{2}-\d{2}$", date_str): + return False + + # Validate it's a real date + try: + datetime.strptime(date_str, "%Y-%m-%d") + return True + except ValueError: + return False + def _open_document(self, doc_id: int, file_name: str) -> None: """ Fetch BLOB from DB, write to a temporary file, and open with default app.