mirror of
https://github.com/ankitects/anki.git
synced 2025-09-24 16:56:36 -04:00
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:
parent
f8eefe5ee1
commit
795cdd7d3f
13 changed files with 89 additions and 75 deletions
24
anki/deck.py
24
anki/deck.py
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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'],
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue