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