mirror of
https://github.com/ankitects/anki.git
synced 2025-09-25 09:16:38 -04:00
implement field add/delete/rename/move
- now in model.py instead of deck.py - added unit tests
This commit is contained in:
parent
d95cc6c44b
commit
4638d3de46
4 changed files with 152 additions and 129 deletions
128
anki/deck.py
128
anki/deck.py
|
@ -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
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
|
|
|
@ -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):
|
||||||
|
|
106
anki/models.py
106
anki/models.py
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue