From d3a3edb7078fc1ab5e7e5444d46ea3a0ea84c8bb Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Sat, 27 Aug 2011 22:27:09 +0900 Subject: [PATCH] move models into the deck table Like the previous change, models have been moved from a separate DB table to an entry in the deck. We need them for many operations including reviewing, and it's easier to keep them in memory than half on disk with a cache that gets cleared every time we .reset(). This means they are easily serialized as well - previously they were part Python and part JSON, which made access confusing. Because the data is all pulled from JSON now, the instance methods have been moved to the model registry. Eg: model.addField(...) -> deck.models.addField(model, ...). - IDs are now timestamped as with groups et al. - The data field for plugins was also removed. Config info can be added to deck.conf; larger data should be stored externally. - Upgrading needs to be updated for the new model structure. - HexifyID() now accepts strings as well, as our IDs get converted to strings in the serialization process. --- anki/cards.py | 10 +- anki/deck.py | 106 +++++---------- anki/facts.py | 14 +- anki/find.py | 47 +++---- anki/latex.py | 4 +- anki/models.py | 304 +++++++++++++++++++++++++------------------ anki/stats.py | 2 +- anki/stdmodels.py | 54 ++++---- anki/storage.py | 29 ++--- anki/utils.py | 2 +- tests/test_cards.py | 4 +- tests/test_deck.py | 26 ++-- tests/test_find.py | 2 +- tests/test_latex.py | 1 - tests/test_models.py | 130 +++++++++--------- tests/test_sched.py | 39 +++--- tests/test_stats.py | 1 - tests/test_sync.py | 1 - tests/test_undo.py | 2 - 19 files changed, 388 insertions(+), 390 deletions(-) diff --git a/anki/cards.py b/anki/cards.py index a534fe687..5a7249e75 100644 --- a/anki/cards.py +++ b/anki/cards.py @@ -105,9 +105,9 @@ lapses=?, grade=?, cycles=?, edue=? where id = ?""", def _getQA(self, reload=False): if not self._qa or reload: f = self.fact(); m = self.model() - data = [self.id, f.id, m.id, self.gid, self.ord, f.stringTags(), + data = [self.id, f.id, m['id'], self.gid, self.ord, f.stringTags(), f.joinedFields()] - self._qa = self.deck._renderQA(self.model(), data) + self._qa = self.deck._renderQA(data) return self._qa def _withClass(self, txt, extra): @@ -117,7 +117,7 @@ lapses=?, grade=?, cycles=?, edue=? where id = ?""", "Fetch the model and fact." if not self._rd or reload: f = self.deck.getFact(self.fid) - m = self.deck.getModel(f.mid) + m = self.deck.models.get(f.mid) self._rd = [f, m] return self._rd @@ -128,10 +128,10 @@ lapses=?, grade=?, cycles=?, edue=? where id = ?""", return self._reviewData()[1] def template(self): - return self._reviewData()[1].templates[self.ord] + return self._reviewData()[1]['tmpls'][self.ord] def cssClass(self): - return "cm%s-%s" % (hexifyID(self.model().id), + return "cm%s-%s" % (hexifyID(self.model()['id']), hexifyID(self.template()['ord'])) def startTimer(self): diff --git a/anki/deck.py b/anki/deck.py index f379fa94b..9cf16a524 100644 --- a/anki/deck.py +++ b/anki/deck.py @@ -9,12 +9,13 @@ from anki.utils import parseTags, ids2str, hexifyID, \ splitFields from anki.hooks import runHook, runFilter from anki.sched import Scheduler +from anki.models import ModelRegistry from anki.media import MediaRegistry from anki.consts import * from anki.errors import AnkiError import anki.latex # sets up hook -import anki.cards, anki.facts, anki.models, anki.template, anki.cram, \ +import anki.cards, anki.facts, anki.template, anki.cram, \ anki.groups, anki.find # Settings related to queue building. These may be loaded without the rest of @@ -51,6 +52,8 @@ class _Deck(object): self.path = db._path self._lastSave = time.time() self.clearUndo() + self.media = MediaRegistry(self) + self.models = ModelRegistry(self) self.load() if not self.crt: d = datetime.datetime.today() @@ -65,7 +68,6 @@ class _Deck(object): self.lastSessionStart = 0 self._stdSched = Scheduler(self) self.sched = self._stdSched - self.media = MediaRegistry(self) # check for improper shutdown self.cleanup() @@ -85,16 +87,16 @@ class _Deck(object): self.lastSync, self.qconf, self.conf, + models, self.groups, - self.gconf, - self.data) = self.db.first(""" + self.gconf) = self.db.first(""" select crt, mod, scm, dty, syncName, lastSync, -qconf, conf, groups, gconf, data from deck""") +qconf, conf, models, groups, gconf from deck""") self.qconf = simplejson.loads(self.qconf) self.conf = simplejson.loads(self.conf) self.groups = simplejson.loads(self.groups) self.gconf = simplejson.loads(self.gconf) - self.data = simplejson.loads(self.data) + self.models.load(models) def flush(self, mod=None): "Flush state to DB, updating mod time." @@ -102,11 +104,14 @@ qconf, conf, groups, gconf, data from deck""") self.db.execute( """update deck set crt=?, mod=?, scm=?, dty=?, syncName=?, lastSync=?, -qconf=?, conf=?, data=?""", +qconf=?, conf=?, groups=?, gconf=?""", self.crt, self.mod, self.scm, self.dty, self.syncName, self.lastSync, simplejson.dumps(self.qconf), - simplejson.dumps(self.conf), simplejson.dumps(self.data)) + simplejson.dumps(self.conf), + simplejson.dumps(self.groups), + simplejson.dumps(self.gconf)) + self.models.flush() def save(self, name=None, mod=None): "Flush, commit DB, and take out another write lock." @@ -187,15 +192,6 @@ qconf=?, conf=?, data=?""", def getFact(self, id): return anki.facts.Fact(self, id=id) - def getModel(self, mid, cache=True): - "Memoizes; call .reset() to reset cache." - if cache and mid in self.modelCache: - return self.modelCache[mid] - m = anki.models.Model(self, mid) - if cache: - self.modelCache[mid] = m - return m - # Utils ########################################################################## @@ -227,7 +223,7 @@ qconf=?, conf=?, data=?""", def newFact(self): "Return a new fact with the current model." - return anki.facts.Fact(self, self.currentModel()) + return anki.facts.Fact(self, self.models.current()) def addFact(self, fact): "Add a fact to the deck. Return number of new cards." @@ -273,14 +269,14 @@ qconf=?, conf=?, data=?""", "Return (active), non-empty templates." ok = [] model = fact.model() - for template in model.templates: + 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'], + data = [1, 1, model['id'], 1, template['ord'], "", fact.joinedFields()] - now = self._renderQA(model, data) + now = self._renderQA(data) data[6] = "\x1f".join([""]*len(fact.fields)) - empty = self._renderQA(model, data) + empty = self._renderQA(data) if now['q'] == empty['q']: continue if not template['emptyAns']: @@ -321,7 +317,7 @@ qconf=?, conf=?, data=?""", elif type == 1: cms = [c.template() for c in fact.cards()] else: - cms = fact.model().templates + cms = fact.model()['tmpls'] if not cms: return [] cards = [] @@ -365,45 +361,6 @@ select id from facts where id in %s and id not in (select fid from cards)""" % ids2str(fids)) self._delFacts(fids) - # Models - ########################################################################## - - def currentModel(self): - return self.getModel(self.conf['currentModelId']) - - def models(self): - "Return a dict of mid -> model." - mods = {} - for m in [self.getModel(id) for id in self.db.list( - "select id from models")]: - mods[m.id] = m - return mods - - def addModel(self, model): - self.modSchema() - model.flush() - self.conf['currentModelId'] = model.id - - def delModel(self, mid): - "Delete MODEL, and all its cards/facts." - self.modSchema() - # delete facts/cards - self.delCards(self.db.list(""" -select id from cards where fid in (select id from facts where mid = ?)""", - mid)) - # then the model - self.db.execute("delete from models where id = ?", mid) - # GUI should ensure last model is not deleted - if self.conf['currentModelId'] == mid: - self.conf['currentModelId'] = self.db.scalar( - "select id from models limit 1") - - def allCSS(self): - return "\n".join(self.db.list("select css from models")) - - def modelId(self, name): - return self.db.scalar("select id from models where name = ?", name) - # Field checksums and sorting fields ########################################################################## @@ -414,17 +371,16 @@ select id from cards where fid in (select id from facts where mid = ?)""", def updateFieldCache(self, fids, csum=True): "Update field checksums and sort cache, after find&replace, etc." sfids = ids2str(fids) - mods = self.models() r = [] r2 = [] for (fid, mid, flds) in self._fieldData(sfids): fields = splitFields(flds) - model = mods[mid] + model = self.models.get(mid) if csum: - for f in model.fields: + for f in model['flds']: if f['uniq'] and fields[f['ord']]: r.append((fid, mid, fieldChecksum(fields[f['ord']]))) - r2.append((stripHTML(fields[model.sortIdx()]), fid)) + r2.append((stripHTML(fields[self.models.sortIdx(model)]), fid)) if csum: self.db.execute("delete from fsums where fid in "+sfids) self.db.executemany("insert into fsums values (?,?,?)", r) @@ -445,17 +401,17 @@ select id from cards where fid in (select id from facts where mid = ?)""", where = "" else: raise Exception() - mods = self.models() - return [self._renderQA(mods[row[2]], row) + return [self._renderQA(row) for row in self._qaData(where)] - def _renderQA(self, model, data): + def _renderQA(self, data): "Returns hash of id, question, answer." # data is [cid, fid, mid, gid, ord, tags, flds] # unpack fields and create dict flist = splitFields(data[6]) fields = {} - for (name, (idx, conf)) in model.fieldMap().items(): + model = self.models.get(data[2]) + for (name, (idx, conf)) in self.models.fieldMap(model).items(): fields[name] = flist[idx] if fields[name]: fields[name] = '%s' % ( @@ -463,9 +419,9 @@ select id from cards where fid in (select id from facts where mid = ?)""", else: fields[name] = "" fields['Tags'] = data[5] - fields['Model'] = model.name + fields['Model'] = model['name'] fields['Group'] = self.groupName(data[3]) - template = model.templates[data[4]] + template = model['tmpls'][data[4]] fields['Template'] = template['name'] # render q & a d = dict(id=data[0]) @@ -473,7 +429,7 @@ select id from cards where fid in (select id from facts where mid = ?)""", if type == "q": format = format.replace("cloze:", "cq:") else: - if model.conf['clozectx']: + if model['clozectx']: name = "cactx:" else: name = "ca:" @@ -770,8 +726,8 @@ select id from facts where id not in (select distinct fid from cards)""") self.db.execute("delete from tags") self.updateFactTags() # field cache - for m in self.models().values(): - self.updateFieldCache(m.fids()) + for m in self.models.all(): + self.updateFieldCache(self.models.fids(m['id'])) # and finally, optimize self.optimize() newSize = os.stat(self.path)[stat.ST_SIZE] diff --git a/anki/facts.py b/anki/facts.py index 45898a9e5..361f84eb3 100644 --- a/anki/facts.py +++ b/anki/facts.py @@ -19,12 +19,12 @@ class Fact(object): else: self.id = timestampID(deck.db, "facts") self._model = model - self.gid = model.conf['gid'] - self.mid = model.id + self.gid = model['gid'] + self.mid = model['id'] self.tags = [] - self.fields = [""] * len(self._model.fields) + self.fields = [""] * len(self._model['flds']) self.data = "" - self._fmap = self._model.fieldMap() + self._fmap = self.deck.models.fieldMap(self._model) def load(self): (self.mid, @@ -36,12 +36,12 @@ class Fact(object): select mid, gid, mod, tags, flds, data from facts where id = ?""", self.id) self.fields = splitFields(self.fields) self.tags = parseTags(self.tags) - self._model = self.deck.getModel(self.mid) - self._fmap = self._model.fieldMap() + self._model = self.deck.models.get(self.mid) + self._fmap = self.deck.models.fieldMap(self._model) def flush(self): self.mod = intTime() - sfld = stripHTML(self.fields[self._model.sortIdx()]) + sfld = stripHTML(self.fields[self.deck.models.sortIdx(self._model)]) tags = self.stringTags() res = self.deck.db.execute(""" insert or replace into facts values (?, ?, ?, ?, ?, ?, ?, ?)""", diff --git a/anki/find.py b/anki/find.py index 5959111a6..9111f1fc2 100644 --- a/anki/find.py +++ b/anki/find.py @@ -20,8 +20,8 @@ SEARCH_GROUP = 7 def fieldNames(deck, downcase=True): fields = set() names = [] - for m in deck.models().values(): - for f in m.fields: + for m in deck.models.all(): + for f in m['flds']: if f['name'].lower() not in fields: names.append(f['name']) fields.add(f['name'].lower()) @@ -119,7 +119,7 @@ order by %s""" % (lim, sort) elif type == SEARCH_FIELD: self._findField(token, isNeg) elif type == SEARCH_MODEL: - self._findModel(token, isNeg, c) + self._findModel(token, isNeg) elif type == SEARCH_GROUP: self._findGroup(token, isNeg) else: @@ -182,12 +182,13 @@ order by %s""" % (lim, sort) def _findFids(self, val): self.lims['fact'].append("id in (%s)" % val) - def _findModel(self, val, isNeg, c): + def _findModel(self, val, isNeg): extra = "not" if isNeg else "" - self.lims['fact'].append( - "mid %s in (select id from models where name like :_mod_%d)" % ( - extra, c)) - self.lims['args']['_mod_%d'%c] = val + ids = [] + for m in self.deck.models.all(): + if m['name'].lower() == val: + ids.append(m['id']) + self.lims['fact'].append("mid %s in %s" % (extra, ids2str(ids))) def _findGroup(self, val, isNeg): extra = "!" if isNeg else "" @@ -203,8 +204,8 @@ order by %s""" % (lim, sort) except: num = None lims = [] - for m in self.deck.models().values(): - for t in m.templates: + for m in self.deck.models.all(): + for t in m['tmpls']: # ordinal number? if num is not None and t['ord'] == num: self.lims['card'].append("ord %s %d" % (comp, num)) @@ -212,8 +213,8 @@ order by %s""" % (lim, sort) # template name? elif t['name'].lower() == val.lower(): lims.append(( - "(fid in (select id from facts where mid = %d) " - "and ord %s %d)") % (m.id, comp, t['ord'])) + "(fid in (select id from facts where mid = %s) " + "and ord %s %d)") % (m['id'], comp, t['ord'])) found = True if lims: self.lims['card'].append("(" + " or ".join(lims) + ")") @@ -226,10 +227,10 @@ order by %s""" % (lim, sort) value = "%" + parts[1].replace("*", "%") + "%" # find models that have that field mods = {} - for m in self.deck.models().values(): - for f in m.fields: + for m in self.deck.models.all(): + for f in m['flds']: if f['name'].lower() == field: - mods[m.id] = (m, f['ord']) + mods[m['id']] = (m, f['ord']) if not mods: # nothing has that field self.lims['valid'] = False @@ -243,11 +244,11 @@ where mid in %s and flds like ? escape '\\'""" % ( ids2str(mods.keys())), "%" if self.full else value): flds = splitFields(flds) - ord = mods[mid][1] - str = flds[ord] + ord = mods[str(mid)][1] + strg = flds[ord] if self.full: - str = stripHTML(str) - if re.search(regex, str): + strg = stripHTML(strg) + if re.search(regex, strg): fids.append(id) extra = "not" if isNeg else "" self.lims['fact'].append("id %s in %s" % (extra, ids2str(fids))) @@ -372,10 +373,10 @@ def findReplace(deck, fids, src, dst, regex=False, field=None, fold=True): "Find and replace fields in a fact." mmap = {} if field: - for m in deck.models().values(): - for f in m.fields: + for m in deck.models.all(): + for f in m['flds']: if f['name'] == field: - mmap[m.id] = f['ord'] + mmap[m['id']] = f['ord'] if not mmap: return 0 # find and gather replacements @@ -393,7 +394,7 @@ def findReplace(deck, fids, src, dst, regex=False, field=None, fold=True): # does it match? sflds = splitFields(flds) if field: - ord = mmap[mid] + ord = mmap[str(mid)] sflds[ord] = repl(sflds[ord]) else: for c in range(len(sflds)): diff --git a/anki/latex.py b/anki/latex.py index c31a11457..9cb52e1f0 100644 --- a/anki/latex.py +++ b/anki/latex.py @@ -71,9 +71,9 @@ def _latexFromHtml(deck, latex): def _buildImg(deck, latex, fname, model): # add header/footer - latex = (model.conf["latexPre"] + "\n" + + latex = (model["latexPre"] + "\n" + latex + "\n" + - model.conf["latexPost"]) + model["latexPost"]) # write into a temp file log = open(namedtmp("latex_log.txt"), "w") texfile = file(namedtmp("tmp.tex"), "w") diff --git a/anki/models.py b/anki/models.py index 05575abbd..aeb1bfa64 100644 --- a/anki/models.py +++ b/anki/models.py @@ -2,19 +2,19 @@ # Copyright: Damien Elmes # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -import simplejson +import simplejson, copy from anki.utils import intTime, hexifyID, joinFields, splitFields, ids2str, \ timestampID from anki.lang import _ # Models ########################################################################## -# gid may point to non-existent group -defaultConf = { +# careful not to add any lists/dicts/etc here, as they aren't deep copied +defaultModel = { + 'css': "", 'sortf': 0, 'gid': 1, - 'tags': [], 'clozectx': False, 'latexPre': """\ \\documentclass[12pt]{article} @@ -56,82 +56,132 @@ defaultTemplate = { 'gid': None, } -class Model(object): +class ModelRegistry(object): - def __init__(self, deck, id=None): + # Saving/loading registry + ############################################################# + + def __init__(self, deck): self.deck = deck - if id: - self.id = id - self.load() - else: - self.id = timestampID(deck.db, "models") - self.name = u"" - self.conf = defaultConf.copy() - self.css = "" - self.fields = [] - self.templates = [] - def load(self): - (self.mod, - self.name, - self.fields, - self.templates, - self.conf, - self.css) = self.deck.db.first(""" -select mod, name, flds, tmpls, conf, css from models where id = ?""", self.id) - self.fields = simplejson.loads(self.fields) - self.templates = simplejson.loads(self.templates) - self.conf = simplejson.loads(self.conf) + def load(self, json): + "Load registry from JSON." + self.changed = False + self.models = simplejson.loads(json) + + def save(self, m=None): + "Mark M modified if provided, and schedule registry flush." + if m: + m['mod'] = intTime() + m['css'] = self._css(m) + self.changed = True def flush(self): - self.mod = intTime() - self.css = self.genCSS() - ret = self.deck.db.execute(""" -insert or replace into models values (?, ?, ?, ?, ?, ?, ?)""", - self.id, self.mod, self.name, - simplejson.dumps(self.fields), - simplejson.dumps(self.templates), - simplejson.dumps(self.conf), - self.css) - self.id = ret.lastrowid + "Flush the registry if any models were changed." + if self.changed: + self.deck.db.execute("update deck set models = ?", + simplejson.dumps(self.models)) - def fids(self): + # Retrieving and creating models + ############################################################# + + def current(self): + "Get current model." + return self.get(self.deck.conf['currentModelId']) + + def get(self, id): + "Get model with ID." + return self.models[str(id)] + + def all(self): + "Get all models." + return self.models.values() + + def byName(self, name): + "Get model with NAME." + for m in self.models.values(): + if m['name'].lower() == name.lower(): + return m + + def new(self, name): + "Create a new model, save it in the registry, and return it." + # caller should call save() after modifying + m = defaultModel.copy() + m['name'] = name + m['mod'] = intTime() + m['flds'] = [] + m['tmpls'] = [] + m['tags'] = [] + return self._add(m) + + def del_(self, m): + "Delete model, and all its cards/facts." + self.deck.modSchema() + # delete facts/cards + self.deck.delCards(self.deck.db.list(""" +select id from cards where fid in (select id from facts where mid = ?)""", + m['id'])) + # then the model + del self.models[m['id']] + self.save() + # GUI should ensure last model is not deleted + if self.deck.conf['currentModelId'] == m['id']: + self.deck.conf['currentModelId'] = int(self.models.keys()[0]) + + def _add(self, m): + self._setID(m) + self.models[m['id']] = m + self.save(m) + self.deck.conf['currentModelId'] = m['id'] + return m + + def _setID(self, m): + while 1: + id = str(intTime(1000)) + if id not in self.models: + break + m['id'] = id + + # Tools + ################################################## + + def fids(self, m): + "Fact ids for M." return self.deck.db.list( - "select id from facts where mid = ?", self.id) + "select id from facts where mid = ?", m['id']) - def useCount(self): + def useCount(self, m): + "Number of fact using M." return self.deck.db.scalar( - "select count() from facts where mid = ?", self.id) + "select count() from facts where mid = ?", m['id']) + + def css(self): + "CSS for all models." + return "\n".join([m['css'] for m in self.all()]) # Copying ################################################## - def copy(self): - "Copy, flush and return." - new = Model(self.deck, self.id) - new.id = None - new.name += _(" copy") - new.fields = [f.copy() for f in self.fields] - new.templates = [t.copy() for t in self.templates] - new.flush() - return new + def copy(self, m): + "Copy, save and return." + m2 = copy.deepcopy(m) + m2['name'] = _("%s copy") % m2['name'] + return self._add(m2) # CSS generation ################################################## - def genCSS(self): - if not self.id: - return "" + def _css(self, m): # fields css = "".join(self._fieldCSS( - ".fm%s-%s" % (hexifyID(self.id), hexifyID(f['ord'])), + ".fm%s-%s" % (hexifyID(m['id']), hexifyID(f['ord'])), (f['font'], f['qsize'], f['qcol'], f['rtl'], f['pre'])) - for f in self.fields) + for f in m['flds']) # templates css += "".join(".cm%s-%s {text-align:%s;background:%s}\n" % ( - hexifyID(self.id), hexifyID(t['ord']), + hexifyID(m['id']), hexifyID(t['ord']), ("center", "left", "right")[t['align']], t['bg']) - for t in self.templates) + for t in m['tmpls']) return css def _rewriteFont(self, font): @@ -158,64 +208,66 @@ insert or replace into models values (?, ?, ?, ?, ?, ?, ?)""", # Fields ################################################## - def fieldMap(self): + def newField(self, name): + f = defaultField.copy() + f['name'] = name + return f + + def fieldMap(self, m): "Mapping of field name -> (ord, field)." - return dict((f['name'], (f['ord'], f)) for f in self.fields) + return dict((f['name'], (f['ord'], f)) for f in m['flds']) - def sortIdx(self): - return self.conf['sortf'] + def sortIdx(self, m): + return m['sortf'] - def setSortIdx(self, idx): - assert idx >= 0 and idx < len(self.fields) + def setSortIdx(self, m, idx): + assert idx >= 0 and idx < len(m['flds']) self.deck.modSchema() - self.conf['sortf'] = idx - self.deck.updateFieldCache(self.fids(), csum=False) - self.flush() + m['sortf'] = idx + self.deck.updateFieldCache(self.fids(m), csum=False) + self.save(m) - def newField(self): - return defaultField.copy() - - def addField(self, field): - self.fields.append(field) - self._updateFieldOrds() - self.flush() + def addField(self, m, field): + m['flds'].append(field) + self._updateFieldOrds(m) + self.save(m) def add(fields): fields.append("") return fields - self._transformFields(add) + self._transformFields(m, add) - def delField(self, field): - idx = self.fields.index(field) - self.fields.remove(field) - self._updateFieldOrds() + def delField(self, m, field): + idx = m['flds'].index(field) + m['flds'].remove(field) + self._updateFieldOrds(m) def delete(fields): del fields[idx] return fields - self._transformFields(delete) - if idx == self.sortIdx(): + self._transformFields(m, delete) + if idx == self.sortIdx(m): # need to rebuild - self.deck.updateFieldCache(self.fids(), csum=False) - # flushes - self.renameField(field, None) + self.deck.updateFieldCache(self.fids(m), csum=False) + # saves + self.renameField(m, field, None) - def moveField(self, field, idx): - oldidx = self.fields.index(field) + def moveField(self, m, field, idx): + oldidx = m['flds'].index(field) if oldidx == idx: return - self.fields.remove(field) - self.fields.insert(idx, field) - self._updateFieldOrds() - self.flush() + m['flds'].remove(field) + m['flds'].insert(idx, field) + self._updateFieldOrds(m) + self.save(m) def move(fields, oldidx=oldidx): val = fields[oldidx] del fields[oldidx] fields.insert(idx, val) return fields - self._transformFields(move) + self._transformFields(m, move) - def renameField(self, field, newName): + def renameField(self, m, field, newName): self.deck.modSchema() - for t in self.templates: + for t in m['tmpls']: types = ("{{%s}}", "{{text:%s}}", "{{#%s}}", "{{^%s}}", "{{/%s}}") for type in types: @@ -226,77 +278,79 @@ insert or replace into models values (?, ?, ?, ?, ?, ?, ?)""", repl = "" t[fmt] = t[fmt].replace(type%field['name'], repl) field['name'] = newName - self.flush() + self.save(m) - def _updateFieldOrds(self): - for c, f in enumerate(self.fields): + def _updateFieldOrds(self, m): + for c, f in enumerate(m['flds']): f['ord'] = c - def _transformFields(self, fn): + def _transformFields(self, m, fn): self.deck.modSchema() r = [] for (id, flds) in self.deck.db.execute( - "select id, flds from facts where mid = ?", self.id): + "select id, flds from facts where mid = ?", m['id']): r.append((joinFields(fn(splitFields(flds))), id)) self.deck.db.executemany("update facts set flds = ? where id = ?", r) # Templates ################################################## - def newTemplate(self): - return defaultTemplate.copy() + def newTemplate(self, name): + t = defaultTemplate.copy() + t['name'] = name + return t - def addTemplate(self, template): + def addTemplate(self, m, template): self.deck.modSchema() - self.templates.append(template) - self._updateTemplOrds() - self.flush() + m['tmpls'].append(template) + self._updateTemplOrds(m) + self.save(m) - def delTemplate(self, template): + def delTemplate(self, m, template): self.deck.modSchema() - ord = self.templates.index(template) + ord = m['tmpls'].index(template) cids = self.deck.db.list(""" select c.id from cards c, facts f where c.fid=f.id and mid = ? and ord = ?""", - self.id, ord) + m['id'], ord) self.deck.delCards(cids) # shift ordinals self.deck.db.execute(""" update cards set ord = ord - 1 where fid in (select id from facts -where mid = ?) and ord > ?""", self.id, ord) - self.templates.remove(template) - self._updateTemplOrds() - self.flush() +where mid = ?) and ord > ?""", m['id'], ord) + m['tmpls'].remove(template) + self._updateTemplOrds(m) + self.save(m) - def _updateTemplOrds(self): - for c, t in enumerate(self.templates): + def _updateTemplOrds(self, m): + for c, t in enumerate(m['tmpls']): t['ord'] = c - def moveTemplate(self, template, idx): - oldidx = self.templates.index(template) + def moveTemplate(self, m, template, idx): + oldidx = m['tmpls'].index(template) if oldidx == idx: return - oldidxs = dict((id(t), t['ord']) for t in self.templates) - self.templates.remove(template) - self.templates.insert(idx, template) - self._updateTemplOrds() + oldidxs = dict((id(t), t['ord']) for t in m['tmpls']) + m['tmpls'].remove(template) + m['tmpls'].insert(idx, template) + self._updateTemplOrds(m) # generate change map map = [] - for t in self.templates: + for t in m['tmpls']: map.append("when ord = %d then %d" % (oldidxs[id(t)], t['ord'])) # apply - self.flush() + self.save(m) self.deck.db.execute(""" update cards set ord = (case %s end) where fid in ( -select id from facts where mid = ?)""" % " ".join(map), self.id) +select id from facts where mid = ?)""" % " ".join(map), m['id']) # Model changing ########################################################################## # - maps are ord->ord, and there should not be duplicate targets # - newModel should be self if model is not changing - def changeModel(self, fids, newModel, fmap, cmap): + def change(self, m, fids, newModel, fmap, cmap): self.deck.modSchema() - assert newModel.id == self.id or (fmap and cmap) + assert newModel['id'] == m['id'] or (fmap and cmap) if fmap: self._changeFacts(fids, newModel, fmap) if cmap: @@ -304,7 +358,7 @@ select id from facts where mid = ?)""" % " ".join(map), self.id) def _changeFacts(self, fids, newModel, map): d = [] - nfields = len(newModel.fields) + nfields = len(newModel['flds']) for (fid, flds) in self.deck.db.execute( "select id, flds from facts where id in "+ids2str(fids)): newflds = {} @@ -315,7 +369,7 @@ select id from facts where mid = ?)""" % " ".join(map), self.id) for c in range(nfields): flds.append(newflds.get(c, "")) flds = joinFields(flds) - d.append(dict(fid=fid, flds=flds, mid=newModel.id)) + d.append(dict(fid=fid, flds=flds, mid=newModel['id'])) self.deck.db.executemany( "update facts set flds=:flds, mid=:mid where id = :fid", d) self.deck.updateFieldCache(fids) diff --git a/anki/stats.py b/anki/stats.py index 5227849f4..3ab8fb4b2 100644 --- a/anki/stats.py +++ b/anki/stats.py @@ -47,7 +47,7 @@ class CardStats(object): self.addLine(_("Total Time"), self.time(total)) elif c.queue == 0: self.addLine(_("Position"), c.due) - self.addLine(_("Model"), c.model().name) + self.addLine(_("Model"), c.model()['name']) self.addLine(_("Template"), c.template()['name']) self.addLine(_("Current Group"), self.deck.groupName(c.gid)) self.addLine(_("Initial Group"), self.deck.groupName(c.fact().gid)) diff --git a/anki/stdmodels.py b/anki/stdmodels.py index 54a719bf7..15c945d5d 100644 --- a/anki/stdmodels.py +++ b/anki/stdmodels.py @@ -2,7 +2,6 @@ # Copyright: Damien Elmes # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -from anki.models import Model from anki.lang import _ models = [] @@ -10,55 +9,50 @@ models = [] # Basic ########################################################################## -def BasicModel(deck): - m = Model(deck) - m.name = _("Basic") - fm = m.newField() - fm['name'] = _("Front") +def addBasicModel(deck): + mm = deck.models + m = mm.new(_("Basic")) + fm = mm.newField(_("Front")) fm['req'] = True fm['uniq'] = True - m.addField(fm) - fm = m.newField() - fm['name'] = _("Back") - m.addField(fm) - t = m.newTemplate() - t['name'] = _("Forward") + mm.addField(m, fm) + fm = mm.newField(_("Back")) + mm.addField(m, fm) + t = mm.newTemplate(_("Forward")) t['qfmt'] = "{{" + _("Front") + "}}" t['afmt'] = "{{" + _("Back") + "}}" - m.addTemplate(t) - t = m.newTemplate() - t['name'] = _("Reverse") + mm.addTemplate(m, t) + t = mm.newTemplate(_("Reverse")) t['qfmt'] = "{{" + _("Back") + "}}" t['afmt'] = "{{" + _("Front") + "}}" t['actv'] = False - m.addTemplate(t) + mm.addTemplate(m, t) + mm.save(m) return m -models.append((_("Basic"), BasicModel)) +models.append((_("Basic"), addBasicModel)) # Cloze ########################################################################## -def ClozeModel(deck): - m = Model(deck) - m.name = _("Cloze") - fm = m.newField() - fm['name'] = _("Text") +def addClozeModel(deck): + mm = deck.models + m = mm.new(_("Cloze")) + fm = mm.newField(_("Text")) fm['req'] = True fm['uniq'] = True - m.addField(fm) - fm = m.newField() - fm['name'] = _("Notes") - m.addField(fm) + mm.addField(m, fm) + fm = mm.newField(_("Notes")) + mm.addField(m, fm) for i in range(8): n = i+1 - t = m.newTemplate() - t['name'] = _("Cloze") + " %d" % n + t = mm.newTemplate(_("Cloze") + " %d" % n) t['qfmt'] = ("{{#cloze:%d:Text}}
{{cloze:%d:%s}}
"+ "{{/cloze:%d:Text}}") % (n, n, _("Text"), n) t['afmt'] = ("{{cloze:%d:" + _("Text") + "}}") % n t['afmt'] += "
{{" + _("Notes") + "}}" - m.addTemplate(t) + mm.addTemplate(m, t) + mm.save(m) return m -models.append((_("Cloze"), ClozeModel)) +models.append((_("Cloze"), addClozeModel)) diff --git a/anki/storage.py b/anki/storage.py index 312a4f1af..5599fcb55 100644 --- a/anki/storage.py +++ b/anki/storage.py @@ -9,7 +9,7 @@ from anki.lang import _ from anki.utils import intTime from anki.db import DB from anki.deck import _Deck -from anki.stdmodels import BasicModel, ClozeModel +from anki.stdmodels import addBasicModel, addClozeModel from anki.errors import AnkiError from anki.hooks import runHook @@ -34,8 +34,8 @@ def Deck(path, queue=True, lock=True): _upgradeDeck(deck, ver) elif create: # add in reverse order so basic is default - deck.addModel(ClozeModel(deck)) - deck.addModel(BasicModel(deck)) + addClozeModel(deck) + addBasicModel(deck) deck.save() if lock: deck.lock() @@ -67,9 +67,9 @@ create table if not exists deck ( lastSync integer not null, qconf text not null, conf text not null, + models text not null, groups text not null, - gconf text not null, - data text not null + gconf text not null ); create table if not exists cards ( @@ -108,16 +108,6 @@ create table if not exists fsums ( csum integer not null ); -create table if not exists models ( - id integer primary key, - mod integer not null, - name text not null, - flds text not null, - tmpls text not null, - conf text not null, - css text not null -); - create table if not exists graves ( time integer not null, oid integer not null, @@ -148,13 +138,14 @@ values(1,0,0,0,%(v)s,0,'',0,'','','','',''); import anki.groups if setDeckConf: db.execute(""" -update deck set qconf = ?, conf = ?, groups = ?, gconf = ?, data = ?""", +update deck set qconf = ?, conf = ?, models = ?, groups = ?, gconf = ?""", simplejson.dumps(anki.deck.defaultQconf), simplejson.dumps(anki.deck.defaultConf), + "{}", simplejson.dumps({'1': {'name': _("Default"), 'conf': 1, 'mod': intTime()}}), - simplejson.dumps({'1': anki.groups.defaultConf}), - "{}") + simplejson.dumps({'1': anki.groups.defaultConf})) + def _updateIndices(db): "Add indices to the DB." @@ -494,7 +485,7 @@ order by modelId, ordinal"""): def _fixupModels(deck): # rewrite model/template/field ids - models = deck.models() + models = deck.models.all() deck.db.execute("delete from models") times = {} for c, m in enumerate(models.values()): diff --git a/anki/utils.py b/anki/utils.py index a486f6091..18405de0b 100644 --- a/anki/utils.py +++ b/anki/utils.py @@ -171,7 +171,7 @@ def entsToTxt(html): ############################################################################## def hexifyID(id): - return "%x" % id + return "%x" % int(id) def dehexifyID(id): return int(id, 16) diff --git a/tests/test_cards.py b/tests/test_cards.py index 89658851a..8a9b39165 100644 --- a/tests/test_cards.py +++ b/tests/test_cards.py @@ -12,7 +12,7 @@ def test_genCards(): f['Front'] = u'1' f['Back'] = u'2' deck.addFact(f) - cards = deck.genCards(f, f.model().templates) + cards = deck.genCards(f, f.model()['tmpls']) assert len(cards) == 1 assert cards[0].ord == 1 assert deck.cardCount() == 2 @@ -23,7 +23,7 @@ def test_genCards(): f['Front'] = u'1' f['Back'] = u'2' deck.addFact(f) - cards = deck.genCards(f, f.model().templates) + cards = deck.genCards(f, f.model()['tmpls']) assert deck.cardCount() == 4 c = deck.db.list("select due from cards where fid = ?", f.id) assert c[0] == c[1] diff --git a/tests/test_deck.py b/tests/test_deck.py index 1f429fba1..ec47fb591 100644 --- a/tests/test_deck.py +++ b/tests/test_deck.py @@ -2,7 +2,7 @@ import os, re, datetime from tests.shared import assertException, getEmptyDeck, testDir -from anki.stdmodels import BasicModel +from anki.stdmodels import addBasicModel from anki import Deck @@ -53,8 +53,8 @@ def test_factAddDelete(): f = deck.newFact() f['Front'] = u"one"; f['Back'] = u"two" m = f.model() - m.templates[1]['actv'] = True - m.flush() + m['tmpls'][1]['actv'] = True + deck.models.save(m) n = deck.addFact(f) assert n == 2 # check q/a generation @@ -65,7 +65,7 @@ def test_factAddDelete(): assert not p # now let's make a duplicate and test uniqueness f2 = deck.newFact() - f2.model().fields[1]['req'] = True + f2.model()['flds'][1]['req'] = True f2['Front'] = u"one"; f2['Back'] = u"" p = f2.problems() assert p[0] == "unique" @@ -102,15 +102,15 @@ def test_fieldChecksum(): "select csum from fsums") == int("4b0e5a4c", 16) # turning off unique and modifying the fact should delete the sum m = f.model() - m.fields[0]['uniq'] = False - m.flush() + m['flds'][0]['uniq'] = False + deck.models.save(m) f.flush() assert deck.db.scalar( "select count() from fsums") == 0 # and turning on both should ensure two checksums generated - m.fields[0]['uniq'] = True - m.fields[1]['uniq'] = True - m.flush() + m['flds'][0]['uniq'] = True + m['flds'][1]['uniq'] = True + deck.models.save(m) f.flush() assert deck.db.scalar( "select count() from fsums") == 2 @@ -190,8 +190,8 @@ def test_addDelTags(): def test_timestamps(): deck = getEmptyDeck() - assert len(deck.models()) == 2 + assert len(deck.models.models) == 2 for i in range(100): - deck.addModel(BasicModel(deck)) - assert len(deck.models()) == 102 - + addBasicModel(deck) + assert len(deck.models.models) == 102 + diff --git a/tests/test_find.py b/tests/test_find.py index 02fd42915..93bb0bbb8 100644 --- a/tests/test_find.py +++ b/tests/test_find.py @@ -25,7 +25,7 @@ def test_findCards(): f = deck.newFact() f['Front'] = u'template test' f['Back'] = u'foo bar' - f.model().templates[1]['actv'] = True + f.model()['tmpls'][1]['actv'] = True deck.addFact(f) latestCardIds = [c.id for c in f.cards()] # tag searches diff --git a/tests/test_latex.py b/tests/test_latex.py index 8e5af7467..6f6986124 100644 --- a/tests/test_latex.py +++ b/tests/test_latex.py @@ -2,7 +2,6 @@ import os from tests.shared import assertException, getEmptyDeck -from anki.stdmodels import BasicModel from anki.utils import stripHTML, intTime from anki.hooks import addHook diff --git a/tests/test_models.py b/tests/test_models.py index 71e887974..d6e60b24b 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -1,7 +1,6 @@ # coding: utf-8 from tests.shared import getEmptyDeck -from anki.models import Model from anki.utils import stripHTML def test_modelDelete(): @@ -11,20 +10,20 @@ def test_modelDelete(): f['Back'] = u'2' deck.addFact(f) assert deck.cardCount() == 1 - deck.delModel(deck.conf['currentModelId']) + deck.models.del_(deck.models.get(deck.conf['currentModelId'])) assert deck.cardCount() == 0 def test_modelCopy(): deck = getEmptyDeck() - m = deck.currentModel() - m2 = m.copy() - assert m2.name == "Basic copy" - assert m2.id != m.id - assert len(m2.fields) == 2 - assert len(m.fields) == 2 - assert len(m2.fields) == len(m.fields) - assert len(m.templates) == 2 - assert len(m2.templates) == 2 + m = deck.models.current() + m2 = deck.models.copy(m) + assert m2['name'] == "Basic copy" + assert m2['id'] != m['id'] + 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 def test_fields(): d = getEmptyDeck() @@ -32,50 +31,50 @@ def test_fields(): f['Front'] = u'1' f['Back'] = u'2' d.addFact(f) - m = d.currentModel() + m = d.models.current() # make sure renaming a field updates the templates - m.renameField(m.fields[0], "NewFront") - assert m.templates[0]['qfmt'] == "{{NewFront}}" + d.models.renameField(m, m['flds'][0], "NewFront") + assert m['tmpls'][0]['qfmt'] == "{{NewFront}}" # add a field - f = m.newField() + f = d.models.newField(m) f['name'] = "foo" - m.addField(f) - assert d.getFact(m.fids()[0]).fields == ["1", "2", ""] + d.models.addField(m, f) + assert d.getFact(d.models.fids(m)[0]).fields == ["1", "2", ""] # rename it - m.renameField(f, "bar") - assert d.getFact(m.fids()[0])['bar'] == '' + d.models.renameField(m, f, "bar") + assert d.getFact(d.models.fids(m)[0])['bar'] == '' # delete back - m.delField(m.fields[1]) - assert d.getFact(m.fids()[0]).fields == ["1", ""] + d.models.delField(m, m['flds'][1]) + assert d.getFact(d.models.fids(m)[0]).fields == ["1", ""] # move 0 -> 1 - m.moveField(m.fields[0], 1) - assert d.getFact(m.fids()[0]).fields == ["", "1"] + d.models.moveField(m, m['flds'][0], 1) + assert d.getFact(d.models.fids(m)[0]).fields == ["", "1"] # move 1 -> 0 - m.moveField(m.fields[1], 0) - assert d.getFact(m.fids()[0]).fields == ["1", ""] + d.models.moveField(m, m['flds'][1], 0) + assert d.getFact(d.models.fids(m)[0]).fields == ["1", ""] # add another and put in middle - f = m.newField() + f = d.models.newField(m) f['name'] = "baz" - m.addField(f) - f = d.getFact(m.fids()[0]) + d.models.addField(m, f) + f = d.getFact(d.models.fids(m)[0]) f['baz'] = "2" f.flush() - assert d.getFact(m.fids()[0]).fields == ["1", "", "2"] + assert d.getFact(d.models.fids(m)[0]).fields == ["1", "", "2"] # move 2 -> 1 - m.moveField(m.fields[2], 1) - assert d.getFact(m.fids()[0]).fields == ["1", "2", ""] + d.models.moveField(m, m['flds'][2], 1) + assert d.getFact(d.models.fids(m)[0]).fields == ["1", "2", ""] # move 0 -> 2 - m.moveField(m.fields[0], 2) - assert d.getFact(m.fids()[0]).fields == ["2", "", "1"] + d.models.moveField(m, m['flds'][0], 2) + assert d.getFact(d.models.fids(m)[0]).fields == ["2", "", "1"] # move 0 -> 1 - m.moveField(m.fields[0], 1) - assert d.getFact(m.fids()[0]).fields == ["", "2", "1"] + d.models.moveField(m, m['flds'][0], 1) + assert d.getFact(d.models.fids(m)[0]).fields == ["", "2", "1"] def test_templates(): d = getEmptyDeck() - m = d.currentModel() - m.templates[1]['actv'] = True - m.flush() + m = d.models.current() + m['tmpls'][1]['actv'] = True + d.models.save(m) f = d.newFact() f['Front'] = u'1' f['Back'] = u'2' @@ -86,12 +85,12 @@ def test_templates(): assert c.ord == 0 assert c2.ord == 1 # switch templates - m.moveTemplate(c.template(), 1) + d.models.moveTemplate(m, c.template(), 1) c.load(); c2.load() assert c.ord == 1 assert c2.ord == 0 # removing a template should delete its cards - m.delTemplate(m.templates[0]) + d.models.delTemplate(m, m['tmpls'][0]) assert d.cardCount() == 1 # and should have updated the other cards' ordinals c = f.cards()[0] @@ -100,9 +99,9 @@ def test_templates(): def test_text(): d = getEmptyDeck() - m = d.currentModel() - m.templates[0]['qfmt'] = "{{text:Front}}" - m.flush() + m = d.models.current() + m['tmpls'][0]['qfmt'] = "{{text:Front}}" + d.models.save(m) f = d.newFact() f['Front'] = u'helloworld' d.addFact(f) @@ -110,9 +109,9 @@ def test_text(): def test_cloze(): d = getEmptyDeck() - d.conf['currentModelId'] = d.modelId("Cloze") + d.conf['currentModelId'] = d.models.byName("Cloze")['id'] f = d.newFact() - assert f.model().name == "Cloze" + assert f.model()['name'] == "Cloze" # a cloze model with no clozes is empty f['Text'] = u'nothing' assert d.addFact(f) == 0 @@ -124,7 +123,7 @@ def test_cloze(): assert "world" in f.cards()[0].a() assert "hello world" not in f.cards()[0].a() # check context works too - f.model().conf['clozectx'] = True + f.model()['clozectx'] = True assert "hello world" in f.cards()[0].a() # and with a comment f = d.newFact() @@ -143,16 +142,16 @@ def test_cloze(): assert "world bar" in c2.a() # if there are multiple answers for a single cloze, they are given in a # list - f.model().conf['clozectx'] = False + f.model()['clozectx'] = False f = d.newFact() f['Text'] = "a {{c1::b}} {{c1::c}}" assert d.addFact(f) == 1 assert "b, c" in ( f.cards()[0].a()) # clozes should be supported in sections too - m = d.currentModel() - m.templates[0]['qfmt'] = "{{#cloze:1:Text}}{{Notes}}{{/cloze:1:Text}}" - m.flush() + m = d.models.current() + m['tmpls'][0]['qfmt'] = "{{#cloze:1:Text}}{{Notes}}{{/cloze:1:Text}}" + d.models.save(m) f = d.newFact() f['Text'] = "hello" f['Notes'] = "world" @@ -162,18 +161,18 @@ def test_cloze(): def test_modelChange(): deck = getEmptyDeck() - basic = deck.getModel(deck.modelId("Basic")) - cloze = deck.getModel(deck.modelId("Cloze")) + basic = deck.models.byName("Basic") + cloze = deck.models.byName("Cloze") # enable second template and add a fact - basic.templates[1]['actv'] = True - basic.flush() + basic['tmpls'][1]['actv'] = True + deck.models.save(basic) f = deck.newFact() f['Front'] = u'f' f['Back'] = u'b' deck.addFact(f) # switch fields map = {0: 1, 1: 0} - basic.changeModel([f.id], basic, map, None) + deck.models.change(basic, [f.id], basic, map, None) f.load() assert f['Front'] == 'b' assert f['Back'] == 'f' @@ -184,7 +183,7 @@ def test_modelChange(): assert stripHTML(c1.q()) == "f" assert c0.ord == 0 assert c1.ord == 1 - basic.changeModel([f.id], basic, None, map) + deck.models.change(basic, [f.id], basic, None, map) f.load(); c0.load(); c1.load() assert stripHTML(c0.q()) == "f" assert stripHTML(c1.q()) == "b" @@ -194,7 +193,7 @@ def test_modelChange(): assert f.cards()[0].id == c1.id # delete first card map = {0: None, 1: 1} - basic.changeModel([f.id], basic, None, map) + deck.models.change(basic, [f.id], basic, None, map) f.load() c0.load() try: @@ -206,7 +205,7 @@ def test_modelChange(): # an unmapped field becomes blank assert f['Front'] == 'b' assert f['Back'] == 'f' - basic.changeModel([f.id], basic, map, None) + deck.models.change(basic, [f.id], basic, map, None) f.load() assert f['Front'] == '' assert f['Back'] == 'f' @@ -215,12 +214,21 @@ def test_modelChange(): f['Front'] = u'f2' f['Back'] = u'b2' deck.addFact(f) - assert basic.useCount() == 2 - assert cloze.useCount() == 0 + assert deck.models.useCount(basic) == 2 + assert deck.models.useCount(cloze) == 0 map = {0: 0, 1: 1} - basic.changeModel([f.id], cloze, map, map) + deck.models.change(basic, [f.id], cloze, map, map) f.load() assert f['Text'] == "f2" assert f['Notes'] == "b2" assert len(f.cards()) == 2 assert "b2" in f.cards()[0].a() + +def test_css(): + deck = getEmptyDeck() + basic = deck.models.byName("Basic") + assert "arial" in basic['css'] + assert "helvetica" not in basic['css'] + basic['flds'][0]['font'] = "helvetica" + deck.models.save(basic) + assert "helvetica" in basic['css'] diff --git a/tests/test_sched.py b/tests/test_sched.py index 720b01984..b329efe82 100644 --- a/tests/test_sched.py +++ b/tests/test_sched.py @@ -2,7 +2,6 @@ import time, copy from tests.shared import assertException, getEmptyDeck -from anki.stdmodels import BasicModel from anki.utils import stripHTML, intTime from anki.hooks import addHook @@ -32,9 +31,9 @@ def test_new(): assert c.due >= t # the default order should ensure siblings are not seen together, and # should show all cards - m = d.currentModel() - m.templates[1]['actv'] = True - m.flush() + m = d.models.current() + m['tmpls'][1]['actv'] = True + d.models.save(m) f = d.newFact() f['Front'] = u"2"; f['Back'] = u"2" d.addFact(f) @@ -50,15 +49,15 @@ def test_new(): def test_newOrder(): d = getEmptyDeck() - m = d.currentModel() + m = d.models.current() for i in range(50): - t = m.newTemplate() + t = d.models.newTemplate(m) t['name'] = str(i) t['qfmt'] = "{{Front}}" t['afmt'] = "{{Back}}" t['actv'] = i > 25 - m.addTemplate(t) - m.flush() + d.models.addTemplate(m, t) + d.models.save(m) f = d.newFact() f['Front'] = u'1' f['Back'] = u'2' @@ -495,19 +494,19 @@ def test_cramLimits(): def test_adjIvl(): d = getEmptyDeck() # add two more templates and set second active - m = d.currentModel() - m.templates[1]['actv'] = True - t = m.newTemplate() + m = d.models.current() + m['tmpls'][1]['actv'] = True + t = d.models.newTemplate(m) t['name'] = "f2" t['qfmt'] = "{{Front}}" t['afmt'] = "{{Back}}" - m.addTemplate(t) - t = m.newTemplate() + d.models.addTemplate(m, t) + t = d.models.newTemplate(m) t['name'] = "f3" t['qfmt'] = "{{Front}}" t['afmt'] = "{{Back}}" - m.addTemplate(t) - m.flush() + d.models.addTemplate(m, t) + d.models.save(m) # create a new fact; it should have 4 cards f = d.newFact() f['Front'] = "1"; f['Back'] = "1" @@ -560,14 +559,14 @@ def test_adjIvl(): def test_ordcycle(): d = getEmptyDeck() # add two more templates and set second active - m = d.currentModel() - m.templates[1]['actv'] = True - t = m.newTemplate() + m = d.models.current() + m['tmpls'][1]['actv'] = True + t = d.models.newTemplate(m) t['name'] = "f2" t['qfmt'] = "{{Front}}" t['afmt'] = "{{Back}}" - m.addTemplate(t) - m.flush() + d.models.addTemplate(m, t) + d.models.save(m) # create a new fact; it should have 4 cards f = d.newFact() f['Front'] = "1"; f['Back'] = "1" diff --git a/tests/test_stats.py b/tests/test_stats.py index a42202a9c..8d222c625 100644 --- a/tests/test_stats.py +++ b/tests/test_stats.py @@ -2,7 +2,6 @@ import time, copy, os from tests.shared import assertException, getEmptyDeck -from anki.stdmodels import BasicModel from anki.utils import stripHTML, intTime from anki.hooks import addHook diff --git a/tests/test_sync.py b/tests/test_sync.py index b04ed3d33..b3ed518e3 100644 --- a/tests/test_sync.py +++ b/tests/test_sync.py @@ -6,7 +6,6 @@ from tests.shared import assertException from anki.errors import * from anki import Deck from anki.utils import intTime -from anki.stdmodels import BasicModel from anki.sync import SyncClient, SyncServer, HttpSyncServer, HttpSyncServerProxy from anki.sync import copyLocalMedia from anki.facts import Fact diff --git a/tests/test_undo.py b/tests/test_undo.py index 73861f385..755e80ee6 100644 --- a/tests/test_undo.py +++ b/tests/test_undo.py @@ -2,8 +2,6 @@ import time from tests.shared import assertException, getEmptyDeck -from anki.stdmodels import BasicModel - def test_op(): d = getEmptyDeck() # should have no undo by default