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))
|
||||
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,
|
||||
"<<text:%s>>" % 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
|
||||
##########################################################################
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
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
|
||||
|
||||
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()
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue