remove the concept of non-active templates

The old template handling was too complicated, and generated frequent
questions on the forums. By dropping non-active templates we can do away with
the generate cards function, and advanced users can simulate the old behaviour
by using conditional field templates.
This commit is contained in:
Damien Elmes 2011-11-08 18:06:19 +09:00
parent f8eefe5ee1
commit 795cdd7d3f
13 changed files with 89 additions and 75 deletions

View file

@ -268,20 +268,19 @@ crt=?, mod=?, scm=?, dty=?, usn=?, ls=?, conf=?""",
# Card creation
##########################################################################
def findTemplates(self, fact, checkActive=True):
def findTemplates(self, fact):
"Return (active), non-empty templates."
ok = []
model = fact.model()
avail = self.models.availOrds(model, joinFields(fact.fields))
ok = []
for t in model['tmpls']:
if t['actv'] or not checkActive:
if t['ord'] in avail:
ok.append(t)
if t['ord'] in avail:
ok.append(t)
return ok
def genCards(self, fids, limit=None):
"Generate cards for active or limited, non-empty templates."
def genCards(self, fids):
"Generate cards for non-empty templates."
# build map of (fid,ord) so we don't create dupes
sfids = ids2str(fids)
have = {}
@ -297,11 +296,7 @@ crt=?, mod=?, scm=?, dty=?, usn=?, ls=?, conf=?""",
avail = self.models.availOrds(model, flds)
ok = []
for t in model['tmpls']:
if not limit and not t['actv']:
continue
elif limit and t not in limit:
continue
elif (fid,t['ord']) in have:
if (fid,t['ord']) in have:
continue
if t['ord'] in avail:
data.append((ts, fid, t['gid'] or gid, t['ord'],
@ -312,13 +307,12 @@ crt=?, mod=?, scm=?, dty=?, usn=?, ls=?, conf=?""",
insert into cards values (?,?,?,?,?,-1,0,0,?,0,0,0,0,0,0,0,"")""",
data)
# type 0 - when previewing in add dialog, only non-empty & active
# type 0 - when previewing in add dialog, only non-empty
# type 1 - when previewing edit, only existing
# type 2 - when previewing in models dialog, all
# type 2 - when previewing in models dialog, all templates
def previewCards(self, fact, type=0):
"Return uncommited cards for preview."
if type == 0:
cms = self.findTemplates(fact, checkActive=True)
cms = self.findTemplates(fact)
elif type == 1:
cms = [c.template() for c in fact.cards()]
else:

View file

@ -84,7 +84,7 @@ If the same name exists, compare checksums."""
# String manipulation
##########################################################################
def files(self, mid, string, includeRemote=False):
def filesInStr(self, mid, string, includeRemote=False):
l = []
# convert latex first
model = self.deck.models.get(mid)
@ -161,7 +161,7 @@ If the same name exists, compare checksums."""
"Return a set of all referenced filenames."
files = set()
for mid, flds in self.deck.db.execute("select mid, flds from facts"):
for f in self.files(mid, flds):
for f in self.filesInStr(mid, flds):
files.add(f)
return files

View file

@ -51,7 +51,6 @@ defaultField = {
defaultTemplate = {
'name': "",
'ord': None,
'actv': True,
'qfmt': "",
'afmt': "",
'hideQ': False,
@ -74,13 +73,15 @@ class ModelManager(object):
self.changed = False
self.models = simplejson.loads(json)
def save(self, m=None):
def save(self, m=None, gencards=False):
"Mark M modified if provided, and schedule registry flush."
if m:
m['mod'] = intTime()
m['usn'] = self.deck.usn()
m['css'] = self._css(m)
self._updateRequired(m)
if gencards:
self.deck.genCards(self.fids(m))
self.changed = True
def flush(self):

View file

@ -22,11 +22,6 @@ def addBasicModel(deck):
t['qfmt'] = "{{" + _("Front") + "}}"
t['afmt'] = "{{" + _("Back") + "}}"
mm.addTemplate(m, t)
t = mm.newTemplate(_("Reverse"))
t['qfmt'] = "{{" + _("Back") + "}}"
t['afmt'] = "{{" + _("Front") + "}}"
t['actv'] = False
mm.addTemplate(m, t)
mm.save(m)
return m

View file

@ -379,13 +379,12 @@ order by ordinal""", mid)):
dconf = anki.models.defaultTemplate
tmpls = []
for c, row in enumerate(db.all("""
select name, active, qformat, aformat, questionInAnswer,
select name, qformat, aformat, questionInAnswer,
questionAlign, lastFontColour, typeAnswer from cardModels
where modelId = ?
order by ordinal""", mid)):
conf = dconf.copy()
(conf['name'],
conf['actv'],
conf['qfmt'],
conf['afmt'],
conf['hideQ'],

View file

@ -6,15 +6,6 @@ from anki.consts import *
from anki.utils import hexifyID
from tests.shared import getEmptyDeck
def test_genCards():
deck = getEmptyDeck()
f = deck.newFact()
f['Front'] = u'1'
f['Back'] = u'2'
deck.addFact(f)
deck.genCards([f.id], f.model()['tmpls'])
assert deck.cardCount() == 2
def test_previewCards():
deck = getEmptyDeck()
f = deck.newFact()
@ -26,7 +17,7 @@ def test_previewCards():
assert cards[0].ord == 0
# all templates
cards = deck.previewCards(f, 2)
assert len(cards) == 2
assert len(cards) == 1
# add the fact, and test existing preview
deck.addFact(f)
cards = deck.previewCards(f, 1)

View file

@ -48,19 +48,28 @@ def test_factAddDelete():
f['Front'] = u"one"; f['Back'] = u"two"
n = deck.addFact(f)
assert n == 1
deck.rollback()
assert deck.cardCount() == 0
# try with two cards
# test multiple cards - add another template
m = deck.models.current(); mm = deck.models
t = mm.newTemplate("Reverse")
t['qfmt'] = "{{Back}}"
t['afmt'] = "{{Front}}"
mm.addTemplate(m, t)
mm.save(m)
# the default save doesn't generate cards
assert deck.cardCount() == 1
# but when templates are edited such as in the card layout screen, it
# should generate cards on close
mm.save(m, gencards=True)
assert deck.cardCount() == 2
# creating new facts should use both cards
f = deck.newFact()
f['Front'] = u"one"; f['Back'] = u"two"
m = f.model()
m['tmpls'][1]['actv'] = True
deck.models.save(m)
f['Front'] = u"three"; f['Back'] = u"four"
n = deck.addFact(f)
assert n == 2
assert deck.cardCount() == 4
# check q/a generation
c0 = f.cards()[0]
assert re.sub("</?.+?>", "", c0.q()) == u"one"
assert re.sub("</?.+?>", "", c0.q()) == u"three"
# it should not be a duplicate
for p in f.problems():
assert not p
@ -74,15 +83,15 @@ def test_factAddDelete():
# try delete the first card
cards = f.cards()
id1 = cards[0].id; id2 = cards[1].id
assert deck.cardCount() == 2
assert deck.factCount() == 1
assert deck.cardCount() == 4
assert deck.factCount() == 2
deck.remCards([id1])
assert deck.cardCount() == 1
assert deck.factCount() == 1
assert deck.cardCount() == 3
assert deck.factCount() == 2
# and the second should clear the fact
deck.remCards([id2])
assert deck.cardCount() == 0
assert deck.factCount() == 0
assert deck.cardCount() == 2
assert deck.factCount() == 1
def test_fieldChecksum():
deck = getEmptyDeck()

View file

@ -25,7 +25,12 @@ def test_findCards():
f = deck.newFact()
f['Front'] = u'template test'
f['Back'] = u'foo bar'
f.model()['tmpls'][1]['actv'] = True
m = deck.models.current(); mm = deck.models
t = mm.newTemplate("Reverse")
t['qfmt'] = "{{Back}}"
t['afmt'] = "{{Front}}"
mm.addTemplate(m, t)
mm.save(m)
deck.addFact(f)
latestCardIds = [c.id for c in f.cards()]
# tag searches

View file

@ -21,7 +21,8 @@ def test_latex():
assert "executing latex" in msg
assert "installed" in msg
# check if we have latex installed, and abort test if we don't
if not os.path.exists("/usr/bin/latex"):
if (not os.path.exists("/usr/bin/latex") and
not os.path.exists("/usr/texbin/latex")):
print "aborting test; latex is not installed"
return
# fix path
@ -30,10 +31,11 @@ def test_latex():
d.media.check()
assert len(os.listdir(d.media.dir())) == 1
assert ".png" in f.cards()[0].q()
# adding new facts should cause immediate generation
# adding new facts should cause generation on question display
f = d.newFact()
f['Front'] = u"[latex]world[/latex]"
d.addFact(f)
f.cards()[0].q()
assert len(os.listdir(d.media.dir())) == 2
# another fact with the same media should reuse
f = d.newFact()

View file

@ -21,7 +21,7 @@ def test_add():
def test_strings():
d = getEmptyDeck()
mf = d.media.files
mf = d.media.filesInStr
mid = d.models.models.keys()[0]
assert mf(mid, "aoeu") == []
assert mf(mid, "aoeu<img src='foo.jpg'>ao") == ["foo.jpg"]

View file

@ -22,8 +22,8 @@ def test_modelCopy():
assert len(m2['flds']) == 2
assert len(m['flds']) == 2
assert len(m2['flds']) == len(m['flds'])
assert len(m['tmpls']) == 2
assert len(m2['tmpls']) == 2
assert len(m['tmpls']) == 1
assert len(m2['tmpls']) == 1
assert deck.models.scmhash(m) == deck.models.scmhash(m2)
def test_fields():
@ -75,9 +75,12 @@ def test_fields():
def test_templates():
d = getEmptyDeck()
m = d.models.current()
m['tmpls'][1]['actv'] = True
d.models.save(m)
m = d.models.current(); mm = d.models
t = mm.newTemplate("Reverse")
t['qfmt'] = "{{Back}}"
t['afmt'] = "{{Front}}"
mm.addTemplate(m, t)
mm.save(m)
f = d.newFact()
f['Front'] = u'1'
f['Back'] = u'2'
@ -177,8 +180,12 @@ def test_modelChange():
basic = deck.models.byName("Basic")
cloze = deck.models.byName("Cloze")
# enable second template and add a fact
basic['tmpls'][1]['actv'] = True
deck.models.save(basic)
m = deck.models.current(); mm = deck.models
t = mm.newTemplate("Reverse")
t['qfmt'] = "{{Back}}"
t['afmt'] = "{{Front}}"
mm.addTemplate(m, t)
mm.save(m)
f = deck.newFact()
f['Front'] = u'f'
f['Back'] = u'b'

View file

@ -27,7 +27,7 @@ import anki.sync
anki.sync.SYNC_URL = "http://localhost:6543/sync/"
TEST_USER = "synctest@ichi2.net"
TEST_PASS = "synctest"
TEST_HKEY = "k14LvSaEtXFITCJz"
TEST_HKEY = "tG5CD9eZbWOru3Yw"
TEST_REMOTE = True
def setup_remote():
@ -42,7 +42,7 @@ def test_meta():
global TEST_REMOTE
try:
(mod, scm, usn, tstamp, dummy) = ts.server.meta()
except SyntaxError, e:
except Exception, e:
if e.errno == 61:
TEST_REMOTE = False
print "aborting; server offline"
@ -107,6 +107,8 @@ def setup_remoteMedia():
@nose.with_setup(setup_remoteMedia)
def test_media():
if not TEST_REMOTE:
return
ts.server.mediatest("reset")
assert len(os.listdir(ts.deck1.media.dir())) == 0
assert ts.server.mediatest("count") == 0

View file

@ -32,9 +32,12 @@ def test_new():
assert c.due >= t
# the default order should ensure siblings are not seen together, and
# should show all cards
m = d.models.current()
m['tmpls'][1]['actv'] = True
d.models.save(m)
m = d.models.current(); mm = d.models
t = mm.newTemplate("Reverse")
t['qfmt'] = "{{Back}}"
t['afmt'] = "{{Front}}"
mm.addTemplate(m, t)
mm.save(m)
f = d.newFact()
f['Front'] = u"2"; f['Back'] = u"2"
d.addFact(f)
@ -517,8 +520,12 @@ def test_cramLimits():
def test_adjIvl():
d = getEmptyDeck()
# add two more templates and set second active
m = d.models.current()
m['tmpls'][1]['actv'] = True
m = d.models.current(); mm = d.models
t = mm.newTemplate("Reverse")
t['qfmt'] = "{{Back}}"
t['afmt'] = "{{Front}}"
mm.addTemplate(m, t)
mm.save(m)
t = d.models.newTemplate(m)
t['name'] = "f2"
t['qfmt'] = "{{Front}}"
@ -582,15 +589,17 @@ def test_adjIvl():
def test_ordcycle():
d = getEmptyDeck()
# add two more templates and set second active
m = d.models.current()
m['tmpls'][1]['actv'] = True
t = d.models.newTemplate(m)
t['name'] = "f2"
m = d.models.current(); mm = d.models
t = mm.newTemplate("Reverse")
t['qfmt'] = "{{Back}}"
t['afmt'] = "{{Front}}"
mm.addTemplate(m, t)
t = mm.newTemplate("f2")
t['qfmt'] = "{{Front}}"
t['afmt'] = "{{Back}}"
d.models.addTemplate(m, t)
d.models.save(m)
# create a new fact; it should have 4 cards
mm.addTemplate(m, t)
mm.save(m)
# create a new fact; it should have 3 cards
f = d.newFact()
f['Front'] = "1"; f['Back'] = "1"
d.addFact(f)