From 4638d3de46727a6b3e26bd48f6bd06f666361928 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Sat, 12 Mar 2011 22:32:20 +0900 Subject: [PATCH] implement field add/delete/rename/move - now in model.py instead of deck.py - added unit tests --- anki/deck.py | 128 ++++++++----------------------------------- anki/facts.py | 2 +- anki/models.py | 106 +++++++++++++++++++++++++++-------- tests/test_models.py | 45 +++++++++++++++ 4 files changed, 152 insertions(+), 129 deletions(-) diff --git a/anki/deck.py b/anki/deck.py index 1f7ef3b6c..039fd11ba 100644 --- a/anki/deck.py +++ b/anki/deck.py @@ -735,87 +735,32 @@ where id in %s""" % ids2str(ids), new=new.id, ord=new.ord) ids2str(fids)) self.finishProgress() - # Fields + # Field checksums and sorting fields ########################################################################## - def allFields(self): - "Return a list of all possible fields across all models." - return self.db.list("select distinct name from fieldmodels") + def _fieldData(self, sfids): + return self.db.execute( + "select id, mid, flds from facts where id in "+sfids) - def deleteFieldModel(self, model, field): - self.startProgress() - self.modSchema() - self.db.execute("delete from fdata where fmid = :id", - id=field.id) - self.db.execute("update facts set mod = :t where mid = :id", - id=model.id, t=time.time()) - model.fields.remove(field) - # update q/a formats - for cm in model.templates: - types = ("%%(%s)s" % field.name, - "%%(text:%s)s" % field.name, - # new style - "<<%s>>" % field.name, - "<>" % field.name) - for t in types: - for fmt in ('qfmt', 'afmt'): - setattr(cm, fmt, getattr(cm, fmt).replace(t, "")) - self.updateCardsFromModel(model) - model.flush() - self.finishProgress() - - def addFieldModel(self, model, field): - "Add FIELD to MODEL and update cards." - self.modSchema() - model.addFieldModel(field) - # flush field to disk - self.db.execute(""" -insert into fdata (fid, fmid, ord, value) -select facts.id, :fmid, :ord, "" from facts -where facts.mid = :mid""", fmid=field.id, mid=model.id, ord=field.ord) - # ensure facts are marked updated - self.db.execute(""" -update facts set mod = :t where mid = :mid""" - , t=time.time(), mid=model.id) - model.flush() - - def renameFieldModel(self, model, field, newName): - "Change FIELD's name in MODEL and update FIELD in all facts." - for cm in model.templates: - types = ("%%(%s)s", - "%%(text:%s)s", - # new styles - "{{%s}}", - "{{text:%s}}", - "{{#%s}}", - "{{^%s}}", - "{{/%s}}") - for t in types: - for fmt in ('qfmt', 'afmt'): - setattr(cm, fmt, getattr(cm, fmt).replace(t%field.name, - t%newName)) - field.name = newName - model.flush() - - def fieldUseCount(self, field): - "Return the number of cards using field." - return self.db.scalar(""" -select count(id) from fdata where -fmid = :id and val != "" -""", id=field.id) - - def rebuildFieldOrds(self, mid, ids): - self.modSchema() - strids = ids2str(ids) - self.db.execute(""" -update fdata -set ord = (select ord from fields where id = fmid) -where fdata.fmid in %s""" % strids) - # dirty associated facts - self.db.execute(""" -update facts -set mod = strftime("%s", "now") -where mid = :id""", id=mid) + def updateFieldCache(self, fids, csum=True): + "Update field checksums and sort cache, after find&replace, etc." + sfids = ids2str(fids) + mods = self.models() + r = [] + r2 = [] + for (fid, mid, flds) in self._fieldData(sfids): + fields = splitFields(flds) + model = mods[mid] + if csum: + for c, f in enumerate(model.fields): + if f['uniq'] and fields[c]: + r.append((fid, mid, fieldChecksum(fields[c]))) + r2.append((stripHTML(fields[model.sortIdx()])[ + :SORT_FIELD_LEN], fid)) + if csum: + self.db.execute("delete from fsums where fid in "+sfids) + self.db.executemany("insert into fsums values (?,?,?)", r) + self.db.executemany("update facts set sfld = ? where id = ?", r2) # Card models ########################################################################## @@ -910,33 +855,6 @@ from cards c, facts f, models m, groups g where c.fid == f.id and f.mid == m.id and c.gid = g.id %s""" % where) - # Field checksums and sorting fields - ########################################################################## - - def _fieldData(self, sfids): - return self.db.execute( - "select id, mid, flds from facts where id in "+sfids) - - def updateFieldCache(self, fids, csum=True): - "Update field checksums and sort cache, after find&replace, etc." - sfids = ids2str(fids) - mods = self.models() - r = [] - r2 = [] - for (fid, mid, flds) in self._fieldData(sfids): - fields = splitFields(flds) - model = mods[mid] - if csum: - for c, f in enumerate(model.fields): - if f['uniq'] and fields[c]: - r.append((fid, mid, fieldChecksum(fields[c]))) - r2.append((stripHTML(fields[model.sortIdx()])[ - :SORT_FIELD_LEN], fid)) - if csum: - self.db.execute("delete from fsums where fid in "+sfids) - self.db.executemany("insert into fsums values (?,?,?)", r) - self.db.executemany("update facts set sfld = ? where id = ?", r2) - # Tags ########################################################################## diff --git a/anki/facts.py b/anki/facts.py index 3a6f6f38b..9d41325bc 100644 --- a/anki/facts.py +++ b/anki/facts.py @@ -34,7 +34,7 @@ class Fact(object): self._fields, self.data) = self.deck.db.first(""" select mid, crt, mod, tags, flds, data from facts where id = ?""", self.id) - self._fields = self._field.split("\x1f") + self._fields = splitFields(self._fields) self.model = self.deck.getModel(self.mid) def flush(self): diff --git a/anki/models.py b/anki/models.py index 5730710e8..02a8b2a9d 100644 --- a/anki/models.py +++ b/anki/models.py @@ -3,11 +3,12 @@ # License: GNU GPL, version 3 or later; http://www.gnu.org/copyleft/gpl.html import simplejson -from anki.utils import intTime, hexifyID +from anki.utils import intTime, hexifyID, joinFields, splitFields from anki.lang import _ # Models ########################################################################## +# GUI code should ensure no two fields have the same name. defaultConf = { 'sortf': 0, @@ -79,28 +80,6 @@ insert or replace into models values (?, ?, ?, ?, ?, ?, ?)""", def fids(self): return self.deck.db.list("select id from facts where mid = ?", self.id) - # Fields - ################################################## - - def newField(self): - return defaultField.copy() - - def addField(self, field): - self.deck.modSchema() - self.fields.append(field) - - def fieldMap(self): - "Mapping of field name -> (ord, conf)." - return dict([(f['name'], (c, f)) for c, f in enumerate(self.fields)]) - - def sortIdx(self): - return self.conf['sortf'] - - def setSortIdx(self, idx): - assert idx > 0 and idx < len(self.fields) - self.conf['sortf'] = idx - self.deck.updateFieldCache(self.fids(), csum=False) - # Templates ################################################## @@ -162,3 +141,84 @@ insert or replace into models values (?, ?, ?, ?, ?, ?, ?)""", t += "white-space:pre-wrap;" t = "%s {%s}\n" % (prefix, t) return t + + # Field basics + ################################################## + + def fieldMap(self): + "Mapping of field name -> (ord, conf)." + return dict([(f['name'], (c, f)) for c, f in enumerate(self.fields)]) + + def sortIdx(self): + return self.conf['sortf'] + + def setSortIdx(self, idx): + assert idx > 0 and idx < len(self.fields) + self.conf['sortf'] = idx + self.deck.updateFieldCache(self.fids(), csum=False) + + # Adding/deleting/moving fields + ################################################## + + def newField(self): + return defaultField.copy() + + def addField(self, field): + self.fields.append(field) + self.flush() + def add(fields): + fields.append("") + return fields + self._transformFields(add) + + def deleteField(self, field): + idx = self.fields.index(field) + self.fields.remove(field) + self.flush() + def delete(fields): + del fields[idx] + return fields + self._transformFields(delete) + if idx == self.sortIdx(): + # need to rebuild + self.deck.updateFieldCache(self.fids(), csum=False) + self.renameField(field, None) + + def moveField(self, field, idx): + oldidx = self.fields.index(field) + if oldidx == idx: + return + self.fields.remove(field) + self.fields.insert(idx, field) + self.flush() + def move(fields, oldidx=oldidx): + val = fields[oldidx] + del fields[oldidx] + fields.insert(idx, val) + return fields + self._transformFields(move) + + def renameField(self, field, newName): + self.deck.modSchema() + for t in self.templates: + types = ("{{%s}}", "{{text:%s}}", "{{#%s}}", + "{{^%s}}", "{{/%s}}") + for type in types: + for fmt in ('qfmt', 'afmt'): + if newName: + repl = type%newName + else: + repl = "" + t[fmt] = t[fmt].replace(type%field['name'], repl) + field['name'] = newName + self.flush() + + def _transformFields(self, fn): + self.deck.startProgress() + self.deck.modSchema() + r = [] + for (id, flds) in self.deck.db.execute( + "select id, flds from facts where mid = ?", self.id): + r.append((joinFields(fn(splitFields(flds))), id)) + self.deck.db.executemany("update facts set flds = ? where id = ?", r) + self.deck.finishProgress() diff --git a/tests/test_models.py b/tests/test_models.py index 8a074eca8..1fd1b286e 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -26,6 +26,51 @@ def test_modelCopy(): assert len(m.templates) == 2 assert len(m2.templates) == 2 +def test_fields(): + d = getEmptyDeck() + f = d.newFact() + f['Front'] = u'1' + f['Back'] = u'2' + d.addFact(f) + m = d.currentModel() + # make sure renaming a field updates the templates + m.renameField(m.fields[0], "NewFront") + assert m.templates[0]['qfmt'] == "{{NewFront}}" + # add a field + f = m.newField() + f['name'] = "foo" + m.addField(f) + assert d.getFact(m.fids()[0])._fields == ["1", "2", ""] + # rename it + m.renameField(f, "bar") + assert d.getFact(m.fids()[0])['bar'] == '' + # delete back + m.deleteField(m.fields[1]) + assert d.getFact(m.fids()[0])._fields == ["1", ""] + # move 0 -> 1 + m.moveField(m.fields[0], 1) + assert d.getFact(m.fids()[0])._fields == ["", "1"] + # move 1 -> 0 + m.moveField(m.fields[1], 0) + assert d.getFact(m.fids()[0])._fields == ["1", ""] + # add another and put in middle + f = m.newField() + f['name'] = "baz" + m.addField(f) + f = d.getFact(m.fids()[0]) + f['baz'] = "2" + f.flush() + assert d.getFact(m.fids()[0])._fields == ["1", "", "2"] + # move 2 -> 1 + m.moveField(m.fields[2], 1) + assert d.getFact(m.fids()[0])._fields == ["1", "2", ""] + # move 0 -> 2 + m.moveField(m.fields[0], 2) + assert d.getFact(m.fids()[0])._fields == ["2", "", "1"] + # move 0 -> 1 + m.moveField(m.fields[0], 1) + assert d.getFact(m.fids()[0])._fields == ["", "2", "1"] + def test_modelChange(): print "model change" return