From 8be0e6cccdc6c0af8769b0e534f6b663a73f6137 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Sun, 11 Dec 2011 23:35:59 +0900 Subject: [PATCH] make sure we add + delete cards after template changes --- anki/collection.py | 20 ++++++---- anki/models.py | 84 +++++++++++++++++++++++++++++----------- anki/upgrade.py | 4 +- tests/test_cards.py | 25 ++++++++++++ tests/test_collection.py | 2 +- 5 files changed, 102 insertions(+), 33 deletions(-) diff --git a/anki/collection.py b/anki/collection.py index ed28ad78b..5f98c7177 100644 --- a/anki/collection.py +++ b/anki/collection.py @@ -292,26 +292,31 @@ crt=?, mod=?, scm=?, dty=?, usn=?, ls=?, conf=?""", return ok def genCards(self, nids): - "Generate cards for non-empty templates." + "Generate cards for non-empty templates, return ids to remove." # build map of (nid,ord) so we don't create dupes snids = ids2str(nids) have = {} - for nid, ord in self.db.execute( - "select nid, ord from cards where nid in "+snids): - have[(nid,ord)] = True + for id, nid, ord in self.db.execute( + "select id, nid, ord from cards where nid in "+snids): + if nid not in have: + have[nid] = {} + have[nid][ord] = id # build cards for each note data = [] ts = maxID(self.db) now = intTime() + rem = [] for nid, mid, did, flds in self.db.execute( "select id, mid, did, flds from notes where id in "+snids): model = self.models.get(mid) avail = self.models.availOrds(model, flds) ok = [] for t in model['tmpls']: - if (nid,t['ord']) in have: - continue - if t['ord'] in avail: + # if have ord but empty, add cid to remove list + if t['ord'] in have[nid] and t['ord'] not in avail: + rem.append(have[nid][t['ord']]) + # if missing ord and is available, generate + if t['ord'] not in have[nid] and t['ord'] in avail: data.append((ts, nid, t['did'] or did, t['ord'], now, nid)) ts += 1 @@ -319,6 +324,7 @@ crt=?, mod=?, scm=?, dty=?, usn=?, ls=?, conf=?""", self.db.executemany(""" insert into cards values (?,?,?,?,?,-1,0,0,?,0,0,0,0,0,0,0,"")""", data) + return rem # type 0 - when previewing in add dialog, only non-empty # type 1 - when previewing edit, only existing diff --git a/anki/models.py b/anki/models.py index 69697c01e..f1412f9f0 100644 --- a/anki/models.py +++ b/anki/models.py @@ -62,14 +62,14 @@ class ModelManager(object): self.changed = False self.models = simplejson.loads(json) - def save(self, m=None, gencards=False): + def save(self, m=None, templates=False): "Mark M modified if provided, and schedule registry flush." if m and m['id']: m['mod'] = intTime() m['usn'] = self.col.usn() self._updateRequired(m) - if gencards: - self.col.genCards(self.nids(m)) + if templates: + self._syncTemplates(m) self.changed = True def flush(self): @@ -346,6 +346,10 @@ update cards set ord = (case %s end),usn=?,mod=? where nid in ( select id from notes where mid = ?)""" % " ".join(map), self.col.usn(), intTime(), m['id']) + def _syncTemplates(self, m): + rem = self.col.genCards(self.nids(m)) + self.col.remCards(rem) + # Model changing ########################################################################## # - maps are ord->ord, and there should not be duplicate targets @@ -412,9 +416,9 @@ select id from notes where mid = ?)""" % " ".join(map), cloze = False for t in m['tmpls']: ret = self._reqForTemplate(m, flds, t) - if ret[1]: + if ret[2]: cloze = True - req.append((t['ord'], ret[0], ret[1])) + req.append((t['ord'], ret[0], ret[1], ret[2])) m['req'] = req m['cloze'] = cloze @@ -435,42 +439,76 @@ select id from notes where mid = ?)""" % " ".join(map), for f in flds: a.append(cloze if cloze else "1") b.append("") + data = [1, 1, m['id'], 1, t['ord'], "", joinFields(a)] + full = self.col._renderQA(data)['q'] data = [1, 1, m['id'], 1, t['ord'], "", joinFields(b)] empty = self.col._renderQA(data)['q'] - start = a + # if full and empty are the same, the template is invalid and there is + # no way to satisfy it + if full == empty: + return "none", [], [] + type = 'all' req = [] for i in range(len(flds)): - a = start[:] - a[i] = "" - # blank out this field - data[6] = joinFields(a) + tmp = a[:] + tmp[i] = "" + data[6] = joinFields(tmp) # if the result is same as empty, field is required if self.col._renderQA(data)['q'] == empty: req.append(i) - return req, reqstrs + if req: + return type, req, reqstrs + # if there are no required fields, switch to any mode + type = 'any' + req = [] + for i in range(len(flds)): + tmp = b[:] + tmp[i] = "1" + data[6] = joinFields(tmp) + # if not the same as empty, this field can make the card non-blank + if self.col._renderQA(data)['q'] != empty: + req.append(i) + return type, req, reqstrs def availOrds(self, m, flds): "Given a joined field string, return available template ordinals." - have = {} + fields = {} for c, f in enumerate(splitFields(flds)): - have[c] = f.strip() + fields[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: + for ord, type, req, reqstrs in m['req']: + # unsatisfiable template + if type == "none": continue + # AND requirement? + elif type == "all": + ok = True + for idx in req: + if not fields[idx]: + # missing and was required + ok = False + break + if not ok: + continue + # OR requirement? + elif type == "any": + ok = False + for idx in req: + if fields[idx]: + ok = True + break + if not ok: + continue + # extra cloze requirement? + ok = True for s in reqstrs: if s not in flds: # required cloze string was missing ok = False break - if ok: - avail.append(ord) + if not ok: + continue + avail.append(ord) return avail # Sync handling diff --git a/anki/upgrade.py b/anki/upgrade.py index f77f4bb12..455693776 100644 --- a/anki/upgrade.py +++ b/anki/upgrade.py @@ -436,10 +436,10 @@ order by ordinal""", mid)): "white-space:pre-wrap", ] if f['rtl']: - attrs.append("direction:rtl;unicode-bidi:embed") + attrs.append("direction:rtl; unicode-bidi:embed") attrs.append() styles[f['name']] = '{{%s}}' % ( - ";".join(attrs), f['name']) + "; ".join(attrs), f['name']) # obsolete del f['qcol'] del f['qsize'] diff --git a/tests/test_cards.py b/tests/test_cards.py index ec6767fd5..c1efab053 100644 --- a/tests/test_cards.py +++ b/tests/test_cards.py @@ -53,3 +53,28 @@ def test_misc(): c = f.cards()[0] id = d.models.current()['id'] assert c.template()['ord'] == 0 + +def test_genrem(): + d = getEmptyDeck() + f = d.newNote() + f['Front'] = u'1' + f['Back'] = u'' + d.addNote(f) + assert len(f.cards()) == 1 + m = d.models.current() + mm = d.models + # adding a new template should automatically create cards + t = mm.newTemplate("rev") + t['qfmt'] = '{{Front}}' + t['afmt'] = "" + mm.addTemplate(m, t) + mm.save(m, templates=True) + assert len(f.cards()) == 2 + # if the template is changed to remove cards, they'll be removed + t['qfmt'] = "{{Back}}" + mm.save(m, templates=True) + assert len(f.cards()) == 1 + # if we add to the note, a card should be automatically generated + f['Back'] = "1" + f.flush() + assert len(f.cards()) == 2 diff --git a/tests/test_collection.py b/tests/test_collection.py index f60232276..1606426a0 100644 --- a/tests/test_collection.py +++ b/tests/test_collection.py @@ -59,7 +59,7 @@ def test_noteAddDelete(): 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) + mm.save(m, templates=True) assert deck.cardCount() == 2 # creating new notes should use both cards f = deck.newNote()