mirror of
https://github.com/ankitects/anki.git
synced 2025-09-23 00:12:25 -04:00
port changeModel()
This commit is contained in:
parent
1baa7bb5b6
commit
98a63285e1
2 changed files with 102 additions and 128 deletions
107
anki/models.py
107
anki/models.py
|
@ -3,7 +3,7 @@
|
||||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
import simplejson
|
import simplejson
|
||||||
from anki.utils import intTime, hexifyID, joinFields, splitFields
|
from anki.utils import intTime, hexifyID, joinFields, splitFields, ids2str
|
||||||
from anki.lang import _
|
from anki.lang import _
|
||||||
|
|
||||||
# Models
|
# Models
|
||||||
|
@ -278,68 +278,45 @@ select id from facts where mid = ?)""" % " ".join(map), self.id)
|
||||||
|
|
||||||
# Model changing
|
# Model changing
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
# - maps are ord->ord, and there should not be duplicate targets
|
||||||
|
# - newModel should be self if model is not changing
|
||||||
|
# - interface should ensure there's at least one remaining card
|
||||||
|
|
||||||
def changeModel(self, fids, newModel, fieldMap, cardMap):
|
def changeModel(self, fids, newModel, fmap, cmap):
|
||||||
raise Exception()
|
self.deck.modSchema()
|
||||||
self.modSchema()
|
assert newModel.id == self.id or (fmap and cmap)
|
||||||
sfids = ids2str(fids)
|
if fmap:
|
||||||
# field remapping
|
self._changeFacts(fids, newModel, fmap)
|
||||||
if fieldMap:
|
if cmap:
|
||||||
seen = {}
|
self._changeCards(fids, newModel, cmap)
|
||||||
for (old, new) in fieldMap.items():
|
|
||||||
seen[new] = 1
|
def _changeFacts(self, fids, newModel, map):
|
||||||
if new:
|
d = []
|
||||||
# can rename
|
nfields = len(newModel.fields)
|
||||||
self.db.execute("""
|
for (fid, flds) in self.deck.db.execute(
|
||||||
update fdata set
|
"select id, flds from facts where id in "+ids2str(fids)):
|
||||||
fmid = :new,
|
newflds = {}
|
||||||
ord = :ord
|
flds = splitFields(flds)
|
||||||
where fmid = :old
|
for old, new in map.items():
|
||||||
and fid in %s""" % sfids, new=new.id, ord=new.ord, old=old.id)
|
newflds[new] = flds[old]
|
||||||
else:
|
flds = []
|
||||||
# no longer used
|
for c in range(nfields):
|
||||||
self.db.execute("""
|
flds.append(newflds.get(c, ""))
|
||||||
delete from fdata where fid in %s
|
flds = joinFields(flds)
|
||||||
and fmid = :id""" % sfids, id=old.id)
|
d.append(dict(fid=fid, flds=flds, mid=newModel.id))
|
||||||
# new
|
self.deck.db.executemany(
|
||||||
for field in newModel.fields:
|
"update facts set flds=:flds, mid=:mid where id = :fid", d)
|
||||||
if field not in seen:
|
self.deck.updateFieldCache(fids)
|
||||||
d = [{'fid': f,
|
|
||||||
'fmid': field.id,
|
def _changeCards(self, fids, newModel, map):
|
||||||
'ord': field.ord}
|
d = []
|
||||||
for f in fids]
|
deleted = []
|
||||||
self.db.executemany('''
|
for (cid, ord) in self.deck.db.execute(
|
||||||
insert into fdata
|
"select id, ord from cards where fid in "+ids2str(fids)):
|
||||||
(fid, fmid, ord, value)
|
if map[ord] is not None:
|
||||||
values
|
d.append(dict(cid=cid, new=map[ord]))
|
||||||
(:fid, :fmid, :ord, "")''', d)
|
else:
|
||||||
# fact modtime
|
deleted.append(cid)
|
||||||
self.db.execute("""
|
self.deck.db.executemany(
|
||||||
update facts set
|
"update cards set ord=:new where id=:cid", d)
|
||||||
mod = :t,
|
self.deck.delCards(deleted)
|
||||||
mid = :id
|
|
||||||
where id in %s""" % sfids, t=time.time(), id=newModel.id)
|
|
||||||
# template remapping
|
|
||||||
toChange = []
|
|
||||||
for (old, new) in cardMap.items():
|
|
||||||
if not new:
|
|
||||||
# delete
|
|
||||||
self.db.execute("""
|
|
||||||
delete from cards
|
|
||||||
where tid = :cid and
|
|
||||||
fid in %s""" % sfids, cid=old.id)
|
|
||||||
elif old != new:
|
|
||||||
# gather ids so we can rename x->y and y->x
|
|
||||||
ids = self.db.list("""
|
|
||||||
select id from cards where
|
|
||||||
tid = :id and fid in %s""" % sfids, id=old.id)
|
|
||||||
toChange.append((new, ids))
|
|
||||||
for (new, ids) in toChange:
|
|
||||||
self.db.execute("""
|
|
||||||
update cards set
|
|
||||||
tid = :new,
|
|
||||||
ord = :ord
|
|
||||||
where id in %s""" % ids2str(ids), new=new.id, ord=new.ord)
|
|
||||||
cardIds = self.db.list(
|
|
||||||
"select id from cards where fid in %s" %
|
|
||||||
ids2str(fids))
|
|
||||||
|
|
|
@ -148,69 +148,66 @@ def test_cloze():
|
||||||
assert d.addFact(f) == 1
|
assert d.addFact(f) == 1
|
||||||
|
|
||||||
def test_modelChange():
|
def test_modelChange():
|
||||||
print "model change"
|
|
||||||
return
|
|
||||||
deck = getEmptyDeck()
|
deck = getEmptyDeck()
|
||||||
m2 = deck.currentModel()
|
basic = deck.getModel(1)
|
||||||
# taken from jp support plugin
|
cloze = deck.getModel(2)
|
||||||
m1 = Model(deck)
|
# enable second template and add a fact
|
||||||
m1.name = "Japanese"
|
basic.templates[1]['actv'] = True
|
||||||
# field 1
|
basic.flush()
|
||||||
fm = m1.newField()
|
|
||||||
fm['name'] = "Expression"
|
|
||||||
fm['req'] = True
|
|
||||||
fm['uniq'] = True
|
|
||||||
m1.addField(fm)
|
|
||||||
# field2
|
|
||||||
fm = m1.newField()
|
|
||||||
fm['name'] = "Meaning"
|
|
||||||
m1.addField(fm)
|
|
||||||
# field3
|
|
||||||
fm = m1.newField()
|
|
||||||
fm['name'] = "Reading"
|
|
||||||
m1.addField(fm)
|
|
||||||
# template1
|
|
||||||
t = Template(deck)
|
|
||||||
t.name = "Recognition"
|
|
||||||
t.qfmt = "{{Expression}}"
|
|
||||||
t.afmt = "{{Reading}}<br>{{Meaning}}"
|
|
||||||
m1.addTemplate(t)
|
|
||||||
# template2
|
|
||||||
t = Template(deck)
|
|
||||||
t.name = "Recall"
|
|
||||||
t.qfmt = "{{Meaning}}"
|
|
||||||
t.afmt = "{{Expression}}<br>{{Reading}}"
|
|
||||||
#t.active = False
|
|
||||||
m1.addTemplate(t)
|
|
||||||
deck.addModel(m1)
|
|
||||||
|
|
||||||
# add some facts
|
|
||||||
f = deck.newFact()
|
f = deck.newFact()
|
||||||
f['Expression'] = u'e'
|
f['Front'] = u'f'
|
||||||
f['Meaning'] = u'm'
|
f['Back'] = u'b'
|
||||||
f['Reading'] = u'r'
|
|
||||||
deck.addFact(f)
|
deck.addFact(f)
|
||||||
f2 = deck.newFact()
|
# switch fields
|
||||||
f2['Expression'] = u'e2'
|
map = {0: 1, 1: 0}
|
||||||
f2['Meaning'] = u'm2'
|
basic.changeModel([f.id], basic, map, None)
|
||||||
f2['Reading'] = u'r2'
|
f.load()
|
||||||
deck.addFact(f2)
|
assert f['Front'] == 'b'
|
||||||
|
assert f['Back'] == 'f'
|
||||||
# convert to basic
|
# switch cards
|
||||||
assert deck.modelUseCount(m1) == 2
|
c0 = f.cards()[0]
|
||||||
assert deck.modelUseCount(m2) == 0
|
c1 = f.cards()[1]
|
||||||
assert deck.cardCount() == 4
|
assert stripHTML(c0.q()) == "b"
|
||||||
assert deck.factCount() == 2
|
assert stripHTML(c1.q()) == "f"
|
||||||
fmap = {m1.fields[0]: m2.fields[0],
|
assert c0.ord == 0
|
||||||
m1.fields[1]: None,
|
assert c1.ord == 1
|
||||||
m1.fields[2]: m2.fields[1]}
|
basic.changeModel([f.id], basic, None, map)
|
||||||
cmap = {m1.templates[0]: m2.templates[0],
|
f.load(); c0.load(); c1.load()
|
||||||
m1.templates[1]: None}
|
assert stripHTML(c0.q()) == "f"
|
||||||
deck.changeModel([f.id], m2, fmap, cmap)
|
assert stripHTML(c1.q()) == "b"
|
||||||
assert deck.modelUseCount(m1) == 1
|
assert c0.ord == 1
|
||||||
assert deck.modelUseCount(m2) == 1
|
assert c1.ord == 0
|
||||||
assert deck.cardCount() == 3
|
# .cards() returns cards in order
|
||||||
assert deck.factCount() == 2
|
assert f.cards()[0].id == c1.id
|
||||||
c = deck.getCard(deck.db.scalar("select id from cards where fid = ?", f.id))
|
# delete first card
|
||||||
assert stripHTML(c.q()) == u"e"
|
map = {0: None, 1: 1}
|
||||||
assert stripHTML(c.a()) == u"r"
|
basic.changeModel([f.id], basic, None, map)
|
||||||
|
f.load()
|
||||||
|
c0.load()
|
||||||
|
try:
|
||||||
|
c1.load()
|
||||||
|
assert 0
|
||||||
|
except TypeError:
|
||||||
|
pass
|
||||||
|
assert len(f.cards()) == 1
|
||||||
|
# an unmapped field becomes blank
|
||||||
|
assert f['Front'] == 'b'
|
||||||
|
assert f['Back'] == 'f'
|
||||||
|
basic.changeModel([f.id], basic, map, None)
|
||||||
|
f.load()
|
||||||
|
assert f['Front'] == ''
|
||||||
|
assert f['Back'] == 'f'
|
||||||
|
# another fact to try model conversion
|
||||||
|
f = deck.newFact()
|
||||||
|
f['Front'] = u'f2'
|
||||||
|
f['Back'] = u'b2'
|
||||||
|
deck.addFact(f)
|
||||||
|
assert basic.useCount() == 2
|
||||||
|
assert cloze.useCount() == 0
|
||||||
|
map = {0: 0, 1: 1}
|
||||||
|
basic.changeModel([f.id], cloze, map, map)
|
||||||
|
f.load()
|
||||||
|
assert f['Text'] == "f2"
|
||||||
|
assert f['Notes'] == "b2"
|
||||||
|
assert len(f.cards()) == 2
|
||||||
|
assert "b2" in f.cards()[0].a()
|
||||||
|
|
Loading…
Reference in a new issue