implement field add/delete/rename/move

- now in model.py instead of deck.py
- added unit tests
This commit is contained in:
Damien Elmes 2011-03-12 22:32:20 +09:00
parent d95cc6c44b
commit 4638d3de46
4 changed files with 152 additions and 129 deletions

View file

@ -735,87 +735,32 @@ where id in %s""" % ids2str(ids), new=new.id, ord=new.ord)
ids2str(fids)) ids2str(fids))
self.finishProgress() self.finishProgress()
# Fields # Field checksums and sorting fields
########################################################################## ##########################################################################
def allFields(self): def _fieldData(self, sfids):
"Return a list of all possible fields across all models." return self.db.execute(
return self.db.list("select distinct name from fieldmodels") "select id, mid, flds from facts where id in "+sfids)
def deleteFieldModel(self, model, field): def updateFieldCache(self, fids, csum=True):
self.startProgress() "Update field checksums and sort cache, after find&replace, etc."
self.modSchema() sfids = ids2str(fids)
self.db.execute("delete from fdata where fmid = :id", mods = self.models()
id=field.id) r = []
self.db.execute("update facts set mod = :t where mid = :id", r2 = []
id=model.id, t=time.time()) for (fid, mid, flds) in self._fieldData(sfids):
model.fields.remove(field) fields = splitFields(flds)
# update q/a formats model = mods[mid]
for cm in model.templates: if csum:
types = ("%%(%s)s" % field.name, for c, f in enumerate(model.fields):
"%%(text:%s)s" % field.name, if f['uniq'] and fields[c]:
# new style r.append((fid, mid, fieldChecksum(fields[c])))
"<<%s>>" % field.name, r2.append((stripHTML(fields[model.sortIdx()])[
"<<text:%s>>" % field.name) :SORT_FIELD_LEN], fid))
for t in types: if csum:
for fmt in ('qfmt', 'afmt'): self.db.execute("delete from fsums where fid in "+sfids)
setattr(cm, fmt, getattr(cm, fmt).replace(t, "")) self.db.executemany("insert into fsums values (?,?,?)", r)
self.updateCardsFromModel(model) self.db.executemany("update facts set sfld = ? where id = ?", r2)
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)
# Card models # 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 where c.fid == f.id and f.mid == m.id and c.gid = g.id
%s""" % where) %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 # Tags
########################################################################## ##########################################################################

View file

@ -34,7 +34,7 @@ class Fact(object):
self._fields, self._fields,
self.data) = self.deck.db.first(""" self.data) = self.deck.db.first("""
select mid, crt, mod, tags, flds, data from facts where id = ?""", self.id) 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) self.model = self.deck.getModel(self.mid)
def flush(self): def flush(self):

View file

@ -3,11 +3,12 @@
# License: GNU GPL, version 3 or later; http://www.gnu.org/copyleft/gpl.html # License: GNU GPL, version 3 or later; http://www.gnu.org/copyleft/gpl.html
import simplejson import simplejson
from anki.utils import intTime, hexifyID from anki.utils import intTime, hexifyID, joinFields, splitFields
from anki.lang import _ from anki.lang import _
# Models # Models
########################################################################## ##########################################################################
# GUI code should ensure no two fields have the same name.
defaultConf = { defaultConf = {
'sortf': 0, 'sortf': 0,
@ -79,28 +80,6 @@ insert or replace into models values (?, ?, ?, ?, ?, ?, ?)""",
def fids(self): def fids(self):
return self.deck.db.list("select id from facts where mid = ?", self.id) 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 # Templates
################################################## ##################################################
@ -162,3 +141,84 @@ insert or replace into models values (?, ?, ?, ?, ?, ?, ?)""",
t += "white-space:pre-wrap;" t += "white-space:pre-wrap;"
t = "%s {%s}\n" % (prefix, t) t = "%s {%s}\n" % (prefix, t)
return 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()

View file

@ -26,6 +26,51 @@ def test_modelCopy():
assert len(m.templates) == 2 assert len(m.templates) == 2
assert len(m2.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(): def test_modelChange():
print "model change" print "model change"
return return