Copy removal
This commit is contained in:
1 parent
c36642e8de
commit
213576f83d
8 files changed
+464
-26
No files matched your search
+78
-4
@@ -921,7 +921,7 @@ class CopienatorApp(tk.Tk):
|
||||
if not step:
|
||||
return
|
||||
self._rendering = True
|
||||
if step.id in {"personal_read", "edit_scores", "general_remarks"}:
|
||||
if step.id in {"personal_read", "edit_scores", "general_remarks", "remove_copy"}:
|
||||
self.command_box.grid_remove()
|
||||
else:
|
||||
self.command_box.grid()
|
||||
@@ -997,7 +997,7 @@ class CopienatorApp(tk.Tk):
|
||||
continue
|
||||
value = values.get(spec.name, value_for_default(spec.default, evaluation_arg))
|
||||
if step.id == 'statement_persp' and spec.name == 'delete_persp':
|
||||
value = False # Deletion is a one-run choice, never a saved default.
|
||||
value = spec.default # Reset on entry; retries in this form keep completed results.
|
||||
if step.id == 'statement' and spec.name == 'restart':
|
||||
value = False
|
||||
if step.id == "final_score" and spec.name == "copy_merged_histogramme" and spec.name not in values:
|
||||
@@ -1045,6 +1045,9 @@ class CopienatorApp(tk.Tk):
|
||||
ttk.Button(self.form, text='Actualiser les barèmes présents',
|
||||
command=self._refresh_persp_inventory).grid(row=row, column=1, sticky='w')
|
||||
row += 1
|
||||
ttk.Button(self.form, text='Ouvrir le dossier Persp',
|
||||
command=self._open_persp).grid(row=row, column=1, sticky='w', pady=(8, 0))
|
||||
row += 1
|
||||
self.arg_vars['target'].trace_add('write', lambda *_args: self._refresh_persp_inventory())
|
||||
self._refresh_persp_inventory()
|
||||
|
||||
@@ -1091,6 +1094,8 @@ class CopienatorApp(tk.Tk):
|
||||
row += 1
|
||||
|
||||
self.run_button.configure(text="Make modifications" if step.id == "modify_annotations" else "Enregistrer la sélection" if step.id == "refaire_selection" else ("Marquer terminée" if step.is_manual else "Exécuter"))
|
||||
if step.id == "remove_copy":
|
||||
self.run_button.configure(text="Supprimer la copie sélectionnée")
|
||||
if step.id == "personal_create":
|
||||
self.run_button.configure(text="Marquer Réussie")
|
||||
if step.id == "personal_read":
|
||||
@@ -1118,6 +1123,31 @@ class CopienatorApp(tk.Tk):
|
||||
self.persp_inventory_label.configure(text=text)
|
||||
|
||||
def _render_context_controls(self, step: StepDefinition, row: int) -> int:
|
||||
if step.id == "remove_copy":
|
||||
from copienator.copy_removal import available_copies
|
||||
self.removal_copy_var = tk.StringVar()
|
||||
ttk.Label(self.form, text="Copie à supprimer").grid(row=row, column=0, sticky="w")
|
||||
choices = available_copies(self.evaluation) if self.evaluation else []
|
||||
combo = ttk.Combobox(self.form, textvariable=self.removal_copy_var,
|
||||
values=choices, state="readonly")
|
||||
combo.grid(row=row, column=1, sticky="ew")
|
||||
self.removal_preview = tk.Text(self.form, height=7, wrap="word", state="disabled")
|
||||
self.removal_preview.grid(row=row + 1, column=0, columnspan=2, sticky="ew", pady=8)
|
||||
def preview(*_args):
|
||||
from copienator.copy_removal import removal_plan
|
||||
try:
|
||||
plan = removal_plan(self.evaluation, self.removal_copy_var.get())
|
||||
text = "Retirer :\n" + "\n".join(str(p.relative_to(plan.root)) for p in plan.removed)
|
||||
if plan.updated:
|
||||
text += "\nMettre à jour :\n" + "\n".join(str(p.relative_to(plan.root)) for p in plan.updated)
|
||||
except (OSError, ValueError, TypeError) as error:
|
||||
text = str(error)
|
||||
self.removal_preview.configure(state="normal")
|
||||
self.removal_preview.delete("1.0", "end")
|
||||
self.removal_preview.insert("1.0", text)
|
||||
self.removal_preview.configure(state="disabled")
|
||||
combo.bind("<<ComboboxSelected>>", preview)
|
||||
return row + 2
|
||||
if step.id == "statement_groups" or (
|
||||
step.id == "annotation" and self.variant_var.get() == "grouped"
|
||||
):
|
||||
@@ -1252,7 +1282,7 @@ class CopienatorApp(tk.Tk):
|
||||
summary = str(exc)
|
||||
ttk.Label(self.form, text=summary, wraplength=600, justify="left").grid(row=row, column=0, columnspan=2, sticky="w")
|
||||
return row + 1
|
||||
if step.id in {"review_persp", "correction"}:
|
||||
if step.id == "correction":
|
||||
row = self._correction_folder_buttons(row)
|
||||
copy_selection_steps = {
|
||||
"page_splitter",
|
||||
@@ -1435,6 +1465,11 @@ class CopienatorApp(tk.Tk):
|
||||
|
||||
def _open_persp(self) -> None:
|
||||
evaluation = self.evaluation
|
||||
if self.current_step and self.current_step.id == "statement_persp":
|
||||
target = self.arg_vars.get("target")
|
||||
value = str(target.get()).strip() if target else ""
|
||||
if value:
|
||||
evaluation = (self.repository / value).resolve()
|
||||
self._open_desktop_path(evaluation / "Persp" if evaluation else None, "dossier Persp")
|
||||
|
||||
def _open_label_groups(self) -> None:
|
||||
@@ -1738,6 +1773,9 @@ class CopienatorApp(tk.Tk):
|
||||
if self.active_step_id or self.runner.running:
|
||||
messagebox.showwarning("Traitement en cours", "Interrompez le traitement actuel avant d’en lancer un autre.")
|
||||
return
|
||||
if step.id == "remove_copy":
|
||||
self._remove_selected_copy()
|
||||
return
|
||||
if step.id == "refaire_selection":
|
||||
self._save_refaire_selection()
|
||||
return
|
||||
@@ -1893,7 +1931,8 @@ class CopienatorApp(tk.Tk):
|
||||
return
|
||||
self._save_current_form()
|
||||
# Score edits already invalidate downstream steps when Set saves a change.
|
||||
if self.current_step.id not in {"edit_scores", "general_remarks"}:
|
||||
if self.current_step.id not in {"edit_scores", "general_remarks"} and not (
|
||||
status == "skipped" and self.current_step.skip_on_forward):
|
||||
self._invalidate_after_step(self.current_step, self._current_variant().id)
|
||||
self.state_store.update_step(self.current_step.id, status=status)
|
||||
history: dict[str, object] = {
|
||||
@@ -1910,6 +1949,32 @@ class CopienatorApp(tk.Tk):
|
||||
f"{self.current_step.title} : {STATUS_LABELS.get(status, status).lower()}{detail}."
|
||||
)
|
||||
|
||||
def _remove_selected_copy(self) -> None:
|
||||
from copienator.copy_removal import removal_plan, remove_copy
|
||||
copy = self.removal_copy_var.get()
|
||||
try:
|
||||
plan = removal_plan(self.evaluation, copy)
|
||||
except (OSError, ValueError, TypeError) as error:
|
||||
messagebox.showerror("Suppression impossible", str(error))
|
||||
return
|
||||
if not messagebox.askyesno(
|
||||
"Supprimer une copie",
|
||||
f"Supprimer {copy} et ses artefacts ?\n\n"
|
||||
f"{len(plan.removed)} fichiers ou dossiers retirés ; {len(plan.updated)} fichiers partagés mis à jour.\n"
|
||||
"Les originaux seront sauvegardés dans BackUp avant toute modification.\n"
|
||||
"Les autres copies ne seront pas renumérotées. Les lots de correction existants "
|
||||
"devront être recréés ; les étapes suivantes seront à revalider.",
|
||||
):
|
||||
return
|
||||
try:
|
||||
backup = remove_copy(self.evaluation, copy)
|
||||
except Exception as error:
|
||||
messagebox.showerror("Suppression impossible", str(error))
|
||||
return
|
||||
self._mark_step("success", reason=f"{copy} supprimée ; sauvegarde : {backup}")
|
||||
self._render_step()
|
||||
self.info_var.set(f"{copy} supprimée — sauvegarde : {backup}")
|
||||
|
||||
def _validate_rename_step(self) -> None:
|
||||
if (not self.current_step or self.current_step.id != "rename"
|
||||
or not self.state_store.evaluation or self.active_step_id or self.runner.running):
|
||||
@@ -2254,6 +2319,7 @@ class CopienatorApp(tk.Tk):
|
||||
self._render_step()
|
||||
if step_id == 'statement_persp':
|
||||
self._refresh_persp_inventory()
|
||||
return
|
||||
if step_id == "statement" and self._latex_warning_paths:
|
||||
self.after_idle(self._show_current_latex_warnings)
|
||||
if step_id == "personal_read":
|
||||
@@ -2394,6 +2460,14 @@ class CopienatorApp(tk.Tk):
|
||||
target = max(0, min(len(ids) - 1, index + delta))
|
||||
if target == index:
|
||||
return
|
||||
while delta > 0 and target < len(ids) - 1 and self.step_by_id[ids[target]].skip_on_forward:
|
||||
skipped = ids[target]
|
||||
entry = self.state_store.step(skipped)
|
||||
if not entry.get("status"):
|
||||
self.state_store.update_step(skipped, status="skipped", visited=True)
|
||||
if self.tree.exists(skipped):
|
||||
self.tree.item(skipped, values=(STATUS_LABELS["skipped"],))
|
||||
target += 1
|
||||
self.tree.selection_set(ids[target])
|
||||
self.tree.see(ids[target])
|
||||
|
||||
|
||||
+17
-13
@@ -53,6 +53,7 @@ class StepDefinition:
|
||||
personal: bool = False
|
||||
requires: tuple[str, ...] = ()
|
||||
artifacts: tuple[str, ...] = ()
|
||||
skip_on_forward: bool = False
|
||||
auto_start_first_visit: bool = False
|
||||
skip_for_live_correction: bool = False
|
||||
skip_without_manual_conflicts: bool = False
|
||||
@@ -194,27 +195,19 @@ def build_workflow(show_personal_steps: bool) -> list[StepDefinition]:
|
||||
optional=True, personal=True, requires=("labels", "Text2", "Sol2"),
|
||||
),
|
||||
StepDefinition(
|
||||
"statement_persp", "Prétraitement de l’énoncé", "Générer les barèmes manquants avec Gemini",
|
||||
"Génère uniquement les barèmes manquants dans Persp et conserve ceux déjà présents. "
|
||||
"statement_persp", "Prétraitement de l’énoncé", "Générer les barèmes avec Gemini",
|
||||
"Par défaut, supprime les barèmes actuels dans Persp et tous les régénère. "
|
||||
"Décochez Delete current Persp pour générer uniquement les barèmes manquants et conserver ceux déjà présents. "
|
||||
"Les fichiers vides ou contenant seulement le label sont considérés comme manquants. "
|
||||
"Après un échec, relancez cette étape pour compléter les barèmes. "
|
||||
"Cochez Delete current Persp pour supprimer les barèmes actuels au lancement et tous les régénérer.",
|
||||
"Après la génération, ouvrez Persp pour vérifier les résultats, puis passez à l’étape suivante.",
|
||||
(python("default", "Barèmes Gemini", "statement", fixed_args=("--persp-only",)),),
|
||||
arguments=(arg_target("le dossier de l’évaluation"),
|
||||
ArgumentSpec('delete_persp', 'Delete current Persp', kind='bool',
|
||||
flag='--delete-persp', default=False,
|
||||
flag='--delete-persp', default=True,
|
||||
help='Supprime tous les fichiers actuels de Persp au lancement, y compris les barèmes modifiés manuellement. Puis régénère tous les barèmes.')),
|
||||
optional=True, personal=True, requires=("labels", "label_groups", "Text2", "Sol2"),
|
||||
),
|
||||
StepDefinition(
|
||||
"review_persp",
|
||||
"Prétraitement de l’énoncé",
|
||||
"Relire les barèmes",
|
||||
"Étape manuelle facultative : vérifier et modifier les instructions de correction.",
|
||||
(manual("review"),),
|
||||
optional=True,
|
||||
requires=("Persp",),
|
||||
),
|
||||
StepDefinition(
|
||||
"rotate",
|
||||
"Prétraitement des copies",
|
||||
@@ -252,6 +245,17 @@ def build_workflow(show_personal_steps: bool) -> list[StepDefinition]:
|
||||
),
|
||||
arguments=(arg_target("le dossier de l’évaluation"),),
|
||||
),
|
||||
StepDefinition(
|
||||
"remove_copy",
|
||||
"Prétraitement des copies",
|
||||
"Supprimer une copie",
|
||||
"Étape facultative, ignorée au premier passage. Sélectionnez une copie pour retirer ses fichiers "
|
||||
"et artefacts. Chaque fichier supprimé ou modifié est sauvegardé dans BackUp, dans l’évaluation. "
|
||||
"Les autres copies gardent leur numéro. Les lots de correction partagés devront être recréés.",
|
||||
(CommandVariant("default", "Suppression avec sauvegarde", None, "manual"),),
|
||||
optional=True,
|
||||
skip_on_forward=True,
|
||||
),
|
||||
StepDefinition(
|
||||
"page_splitter",
|
||||
"Prétraitement des copies",
|
||||
|
||||
Reference in new issue
Block a user