From 25f122bf5c76a3f531d5bc2ef3a0406d8a69a7c3 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Mon, 4 May 2020 21:52:48 +1000 Subject: [PATCH] update fields and models diags - field changes are now applied when user closes dialog with save button, in bulk - models diag now fetches note type and saves it as required, instead of holding on top a copy that can grow stale as changes are made in subdialogs - both dialogs now perform operations in the backend - note.model() now fetches the note type on the fly, instead of holding on to a copy that may become stale --- pylib/anki/models.py | 19 +++++---- pylib/anki/notes.py | 11 +++--- qt/aqt/editor.py | 2 +- qt/aqt/fields.py | 52 ++++++++++++------------- qt/aqt/main.py | 1 - qt/aqt/models.py | 93 ++++++++++++++++++++++++++------------------ qt/mypy.ini | 12 ++++-- 7 files changed, 105 insertions(+), 85 deletions(-) diff --git a/pylib/anki/models.py b/pylib/anki/models.py index 98bc2cbba..6d05552c1 100644 --- a/pylib/anki/models.py +++ b/pylib/anki/models.py @@ -360,23 +360,24 @@ class ModelManager: f["name"] = name return f - def addField(self, m: NoteType, field: Field) -> None: + def addField(self, m: NoteType, field: Field, save=True) -> None: if m["id"]: self.col.modSchema(check=True) m["flds"].append(field) - if m["id"]: + if m["id"] and save: self.save(m) - def remField(self, m: NoteType, field: Field) -> None: + def remField(self, m: NoteType, field: Field, save=True) -> None: self.col.modSchema(check=True) m["flds"].remove(field) - self.save(m) + if save: + self.save(m) - def moveField(self, m: NoteType, field: Field, idx: int) -> None: + def moveField(self, m: NoteType, field: Field, idx: int, save=True) -> None: self.col.modSchema(check=True) oldidx = m["flds"].index(field) if oldidx == idx: @@ -385,14 +386,16 @@ class ModelManager: m["flds"].remove(field) m["flds"].insert(idx, field) - self.save(m) + if save: + self.save(m) - def renameField(self, m: NoteType, field: Field, newName: str) -> None: + def renameField(self, m: NoteType, field: Field, newName: str, save=True) -> None: assert field in m["flds"] field["name"] = newName - self.save(m) + if save: + self.save(m) # Adding & changing templates ################################################## diff --git a/pylib/anki/notes.py b/pylib/anki/notes.py index a8f04d8d9..53f127f40 100644 --- a/pylib/anki/notes.py +++ b/pylib/anki/notes.py @@ -48,9 +48,7 @@ class Note: self.usn = n.usn self.tags = list(n.tags) self.fields = list(n.fields) - - self._model = self.col.models.get(self.mid) - self._fmap = self.col.models.fieldMap(self._model) + self._fmap = self.col.models.fieldMap(self.model()) # fixme: only save tags in list on save def to_backend_note(self) -> BackendNote: @@ -66,8 +64,7 @@ class Note: fields=self.fields, ) - def flush(self, mod=None) -> None: - # fixme: mod unused? + def flush(self) -> None: assert self.id != 0 self.col.backend.update_note(self.to_backend_note()) @@ -83,7 +80,9 @@ class Note: ] def model(self) -> Optional[NoteType]: - return self._model + return self.col.models.get(self.mid) + + _model = property(model) # Dict interface ################################################## diff --git a/qt/aqt/editor.py b/qt/aqt/editor.py index 3f0b38814..af911ff61 100644 --- a/qt/aqt/editor.py +++ b/qt/aqt/editor.py @@ -341,7 +341,7 @@ class Editor: def _onFields(self): from aqt.fields import FieldDialog - FieldDialog(self.mw, self.note, parent=self.parentWindow) + FieldDialog(self.mw, self.note.model(), parent=self.parentWindow) def onCardLayout(self): self.saveNow(self._onCardLayout) diff --git a/qt/aqt/fields.py b/qt/aqt/fields.py index 107ca0346..8c226cfd7 100644 --- a/qt/aqt/fields.py +++ b/qt/aqt/fields.py @@ -4,20 +4,20 @@ import aqt from anki.consts import * from anki.lang import _, ngettext +from anki.models import NoteType from anki.rsbackend import TemplateError +from aqt import AnkiQt from aqt.qt import * from aqt.utils import askUser, getOnlyText, openHelp, showWarning class FieldDialog(QDialog): - def __init__(self, mw, note, ord=0, parent=None): - QDialog.__init__(self, parent or mw) # , Qt.Window) - self.mw = aqt.mw - self.parent = parent or mw - self.note = note + def __init__(self, mw: AnkiQt, nt: NoteType, parent=None): + QDialog.__init__(self, parent or mw) + self.mw = mw.weakref() self.col = self.mw.col self.mm = self.mw.col.models - self.model = note.model() + self.model = nt self.mw.checkpoint(_("Fields")) self.form = aqt.forms.fields.Ui_Dialog() self.form.setupUi(self) @@ -87,7 +87,7 @@ class FieldDialog(QDialog): name = self._uniqueName(_("New name:"), self.currentIdx, f["name"]) if not name: return - self.mm.renameField(self.model, f, name) + self.mm.renameField(self.model, f, name, save=False) self.saveField() self.fillFields() self.form.fieldList.setCurrentRow(idx) @@ -97,10 +97,8 @@ class FieldDialog(QDialog): if not name: return self.saveField() - self.mw.progress.start() f = self.mm.newField(name) - self.mm.addField(self.model, f) - self.mw.progress.finish() + self.mm.addField(self.model, f, save=False) self.fillFields() self.form.fieldList.setCurrentRow(len(self.model["flds"]) - 1) @@ -112,9 +110,7 @@ class FieldDialog(QDialog): if not askUser(_("Delete field from %s?") % c): return f = self.model["flds"][self.form.fieldList.currentRow()] - self.mw.progress.start() - self.mm.remField(self.model, f) - self.mw.progress.finish() + self.mm.remField(self.model, f, save=False) self.fillFields() self.form.fieldList.setCurrentRow(0) @@ -140,9 +136,7 @@ class FieldDialog(QDialog): def moveField(self, pos): self.saveField() f = self.model["flds"][self.currentIdx] - self.mw.progress.start() - self.mm.moveField(self.model, f, pos - 1) - self.mw.progress.finish() + self.mm.moveField(self.model, f, pos - 1, save=False) self.fillFields() self.form.fieldList.setCurrentRow(pos - 1) @@ -174,19 +168,21 @@ class FieldDialog(QDialog): def accept(self): self.saveField() - if self.oldSortField != self.model["sortf"]: - self.mw.progress.start() - self.mw.col.updateFieldCache(self.mm.nids(self.model)) - self.mw.progress.finish() - try: - self.mm.save(self.model) - except TemplateError as e: - # fixme: i18n - showWarning("Unable to save changes: " + str(e)) - return - self.mw.reset() - QDialog.accept(self) + def save(): + self.mm.save(self.model) + + def on_done(fut): + try: + fut.result() + except TemplateError as e: + # fixme: i18n + showWarning("Unable to save changes: " + str(e)) + return + self.mw.reset() + QDialog.accept(self) + + self.mw.taskman.with_progress(save, on_done, self) def onHelp(self): openHelp("fields") diff --git a/qt/aqt/main.py b/qt/aqt/main.py index bb4fa0df0..e308e7b06 100644 --- a/qt/aqt/main.py +++ b/qt/aqt/main.py @@ -1419,7 +1419,6 @@ will be lost. Continue?""" print("\n") del note.fields del note._fmap - del note._model pprint.pprint(note.__dict__) print("\nCard:") diff --git a/qt/aqt/models.py b/qt/aqt/models.py index fac30f48b..1b934bd18 100644 --- a/qt/aqt/models.py +++ b/qt/aqt/models.py @@ -8,6 +8,7 @@ from typing import List, Optional import aqt.clayout from anki import stdmodels from anki.lang import _, ngettext +from anki.models import NoteType from anki.rsbackend import pb from aqt import AnkiQt, gui_hooks from aqt.qt import * @@ -24,11 +25,11 @@ from aqt.utils import ( class Models(QDialog): def __init__(self, mw: AnkiQt, parent=None, fromMain=False): - self.mw = mw + self.mw = mw.weakref() parent = parent or mw self.fromMain = fromMain QDialog.__init__(self, parent, Qt.Window) - self.col = mw.col + self.col = mw.col.weakref() assert self.col self.mm = self.col.models self.mw.checkpoint(_("Note Types")) @@ -61,25 +62,39 @@ class Models(QDialog): qconnect(b.clicked, self.onCards) b = box.addButton(_("Options..."), t) qconnect(b.clicked, self.onAdvanced) - qconnect(f.modelsList.currentRowChanged, self.modelChanged) qconnect(f.modelsList.itemDoubleClicked, self.onRename) - self.updateModelsList() + + def on_done(fut): + self.updateModelsList(fut.result()) + + self.mw.taskman.with_progress(self.col.models.all_use_counts, on_done, self) f.modelsList.setCurrentRow(0) maybeHideClose(box) def onRename(self): - txt = getText(_("New name:"), default=self.model["name"]) + nt = self.current_notetype() + txt = getText(_("New name:"), default=nt["name"]) if txt[1] and txt[0]: - self.model["name"] = txt[0] - self.mm.save(self.model, updateReqs=False) - self.updateModelsList() + nt["name"] = txt[0] + self.saveAndRefresh(nt) - def updateModelsList(self): + def saveAndRefresh(self, nt: NoteType) -> None: + def save(): + self.mm.save(nt) + return self.col.models.all_use_counts() + + def on_done(fut): + self.updateModelsList(fut.result()) + + self.mw.taskman.with_progress(save, on_done, self) + + def updateModelsList(self, notetypes): row = self.form.modelsList.currentRow() if row == -1: row = 0 - self.models = self.col.models.all_use_counts() self.form.modelsList.clear() + + self.models = notetypes for m in self.models: mUse = m.use_count mUse = ngettext("%d note", "%d notes", mUse) % mUse @@ -87,11 +102,9 @@ class Models(QDialog): self.form.modelsList.addItem(item) self.form.modelsList.setCurrentRow(row) - def modelChanged(self): - if self.model: - self.saveModel() - idx = self.form.modelsList.currentRow() - self.model = self.col.models.get(self.models[idx].id) + def current_notetype(self) -> NoteType: + row = self.form.modelsList.currentRow() + return self.mm.get(self.models[row].id) def onAdd(self): m = AddModel(self.mw, self).get() @@ -99,9 +112,7 @@ class Models(QDialog): txt = getText(_("Name:"), default=m["name"])[0] if txt: m["name"] = txt - self.mm.ensureNameUnique(m) - self.mm.save(m) - self.updateModelsList() + self.saveAndRefresh(m) def onDelete(self): if len(self.models) < 2: @@ -114,40 +125,50 @@ class Models(QDialog): msg = _("Delete this unused note type?") if not askUser(msg, parent=self): return - self.mm.rem(self.model) - self.model = None - self.updateModelsList() + + self.col.modSchema(check=True) + + nt = self.current_notetype() + + def save(): + self.mm.rem(nt) + return self.col.models.all_use_counts() + + def on_done(fut): + self.updateModelsList(fut.result()) + + self.mw.taskman.with_progress(save, on_done, self) def onAdvanced(self): + nt = self.current_notetype() d = QDialog(self) frm = aqt.forms.modelopts.Ui_Dialog() frm.setupUi(d) - frm.latexsvg.setChecked(self.model.get("latexsvg", False)) - frm.latexHeader.setText(self.model["latexPre"]) - frm.latexFooter.setText(self.model["latexPost"]) - d.setWindowTitle(_("Options for %s") % self.model["name"]) + frm.latexsvg.setChecked(nt.get("latexsvg", False)) + frm.latexHeader.setText(nt["latexPre"]) + frm.latexFooter.setText(nt["latexPost"]) + d.setWindowTitle(_("Options for %s") % nt["name"]) qconnect(frm.buttonBox.helpRequested, lambda: openHelp("latex")) restoreGeom(d, "modelopts") gui_hooks.models_advanced_will_show(d) d.exec_() saveGeom(d, "modelopts") - self.model["latexsvg"] = frm.latexsvg.isChecked() - self.model["latexPre"] = str(frm.latexHeader.toPlainText()) - self.model["latexPost"] = str(frm.latexFooter.toPlainText()) - - def saveModel(self): - self.mm.save(self.model, updateReqs=False) + nt["latexsvg"] = frm.latexsvg.isChecked() + nt["latexPre"] = str(frm.latexHeader.toPlainText()) + nt["latexPost"] = str(frm.latexFooter.toPlainText()) + self.saveAndRefresh(nt) def _tmpNote(self): - self.mm.setCurrent(self.model) + nt = self.current_notetype() + self.mm.setCurrent(nt) n = self.col.newNote(forDeck=False) field_names = list(n.keys()) for name in field_names: n[name] = "(" + name + ")" cloze_re = re.compile(r"{{(?:[^}:]*:)*cloze:(?:[^}:]*:)*([^}]+)}}") - q_template = self.model["tmpls"][0]["qfmt"] - a_template = self.model["tmpls"][0]["afmt"] + q_template = nt["tmpls"][0]["qfmt"] + a_template = nt["tmpls"][0]["afmt"] used_cloze_fields = [] used_cloze_fields.extend(cloze_re.findall(q_template)) @@ -161,8 +182,7 @@ class Models(QDialog): def onFields(self): from aqt.fields import FieldDialog - n = self._tmpNote() - FieldDialog(self.mw, n, parent=self) + FieldDialog(self.mw, self.current_notetype(), parent=self) def onCards(self): from aqt.clayout import CardLayout @@ -176,7 +196,6 @@ class Models(QDialog): # need to flush model on change or reject def reject(self): - self.saveModel() self.mw.reset() saveGeom(self, "models") QDialog.reject(self) diff --git a/qt/mypy.ini b/qt/mypy.ini index 7702adc82..df93e225f 100644 --- a/qt/mypy.ini +++ b/qt/mypy.ini @@ -8,12 +8,9 @@ warn_redundant_casts = True warn_unused_configs = True #check_untyped_defs = true -[mypy-aqt.forms.*] -check_untyped_defs=false -[mypy-aqt.tagedit] -check_untyped_defs=true [mypy-aqt.mpv] ignore_errors=true + [mypy-win32file] ignore_missing_imports = True [mypy-win32pipe] @@ -54,3 +51,10 @@ ignore_missing_imports = True ignore_missing_imports = True [mypy-socks] ignore_missing_imports = True + +[mypy-aqt.forms.*] +check_untyped_defs=false +[mypy-aqt.tagedit] +check_untyped_defs=true +[mypy-aqt.fields] +check_untyped_defs=true