From a4e8eb8b74a5c12ba36795188006ec598d1e7756 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Sat, 22 Oct 2011 02:28:57 +0900 Subject: [PATCH] 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) --- anki/deck.py | 22 +++++----------- anki/models.py | 70 +++++++++++++++++++++++++++++++++++++++++++++++-- anki/upgrade.py | 3 +-- 3 files changed, 76 insertions(+), 19 deletions(-) diff --git a/anki/deck.py b/anki/deck.py index 359a820af..e9bb56622 100644 --- a/anki/deck.py +++ b/anki/deck.py @@ -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): diff --git a/anki/models.py b/anki/models.py index 9208ef7f9..32c79eb44 100644 --- a/anki/models.py +++ b/anki/models.py @@ -2,7 +2,7 @@ # Copyright: Damien Elmes # 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 diff --git a/anki/upgrade.py b/anki/upgrade.py index 009099954..9b87c37b9 100644 --- a/anki/upgrade.py +++ b/anki/upgrade.py @@ -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