cache required fields, drop emptyAns

For importing and the deck creation wizard, we need to be able to generate
thousands of cards efficiently. So instead of requiring the creation of a fact
and rendering it, we cache the required fields and cloze references in the
model.

Also, emptyAns is dropped, as people can achieve the same behaviour by adding
the required answer fields as conditional to the question.

Todo: refactor genCards() to work in bulk, handle cloze edits intelligently
(prompt to delete invalid references, create new cards as necessary)
This commit is contained in:
Damien Elmes 2011-10-22 02:28:57 +09:00
parent 8bb26aaf74
commit a4e8eb8b74
3 changed files with 76 additions and 19 deletions

View file

@ -5,7 +5,7 @@
import time, os, random, re, stat, simplejson, datetime, copy, shutil
from anki.lang import _, ngettext
from anki.utils import ids2str, hexifyID, checksum, fieldChecksum, stripHTML, \
intTime, splitFields
intTime, splitFields, joinFields
from anki.hooks import runHook, runFilter
from anki.sched import Scheduler
from anki.models import ModelManager
@ -272,20 +272,12 @@ crt=?, mod=?, scm=?, dty=?, usn=?, ls=?, conf=?""",
"Return (active), non-empty templates."
ok = []
model = fact.model()
for template in model['tmpls']:
if template['actv'] or not checkActive:
# [cid, fid, mid, gid, ord, tags, flds]
data = [1, 1, model['id'], 1, template['ord'],
"", fact.joinedFields()]
now = self._renderQA(data)
data[6] = "\x1f".join([""]*len(fact.fields))
empty = self._renderQA(data)
if now['q'] == empty['q']:
continue
if not template['emptyAns']:
if now['a'] == empty['a']:
continue
ok.append(template)
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)
return ok
def genCards(self, fact, templates):

View file

@ -2,7 +2,7 @@
# Copyright: Damien Elmes <anki@ichi2.net>
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import simplejson, copy
import simplejson, copy, re
from anki.utils import intTime, hexifyID, joinFields, splitFields, ids2str, \
timestampID, fieldChecksum
from anki.lang import _
@ -56,7 +56,6 @@ defaultTemplate = {
'hideQ': False,
'align': 0,
'bg': "#fff",
'emptyAns': True,
'typeAns': None,
'gid': None,
}
@ -80,6 +79,7 @@ class ModelManager(object):
m['mod'] = intTime()
m['usn'] = self.deck.usn()
m['css'] = self._css(m)
self._updateRequired(m)
self.changed = True
def flush(self):
@ -430,3 +430,69 @@ select id from facts where mid = ?)""" % " ".join(map),
for t in m['tmpls']:
s += t['name']
return fieldChecksum(s)
# Required field/text cache
##########################################################################
def _updateRequired(self, m):
req = []
flds = [f['name'] for f in m['flds']]
for t in m['tmpls']:
ret = self._reqForTemplate(m, flds, t)
req.append((t['ord'], ret[0], ret[1]))
m['req'] = req
def _reqForTemplate(self, m, flds, t):
a = []
b = []
cloze = "cloze" in t['qfmt']
reqstrs = []
if cloze:
# need a cloze-specific filler
cloze = ""
nums = re.findall("\{\{#cloze:(\d+):", t['qfmt'])
for n in nums:
n = int(n)
cloze += "{{c%d::foo}}" % n
# record that we require a specific string for generation
reqstrs.append("{{c%d::" % n)
for f in flds:
a.append(cloze if cloze else "1")
b.append("")
data = [1, 1, m['id'], 1, t['ord'], "", joinFields(b)]
empty = self.deck._renderQA(data)['q']
start = a
req = []
for i in range(len(flds)):
a = start[:]
a[i] = ""
# blank out this field
data[6] = joinFields(a)
# if the result is same as empty, field is required
if self.deck._renderQA(data)['q'] == empty:
req.append(i)
return req, reqstrs
def availOrds(self, m, flds):
"Given a joined field string, return available template ordinals."
have = {}
for c, f in enumerate(splitFields(flds)):
have[c] = f.strip()
avail = []
for ord, req, reqstrs in m['req']:
ok = True
for f in req:
if not have[f]:
# missing and was required
ok = False
break
if not ok:
continue
for s in reqstrs:
if s not in flds:
# required cloze string was missing
ok = False
break
if ok:
avail.append(ord)
return avail

View file

@ -375,7 +375,7 @@ order by ordinal""", mid)):
tmpls = []
for c, row in enumerate(db.all("""
select name, active, qformat, aformat, questionInAnswer,
questionAlign, lastFontColour, allowEmptyAnswer, typeAnswer from cardModels
questionAlign, lastFontColour, typeAnswer from cardModels
where modelId = ?
order by ordinal""", mid)):
conf = dconf.copy()
@ -386,7 +386,6 @@ order by ordinal""", mid)):
conf['hideQ'],
conf['align'],
conf['bg'],
conf['emptyAns'],
conf['typeAns']) = row
conf['ord'] = c
# convert the field name to an ordinal