mirror of
https://github.com/ankitects/anki.git
synced 2025-09-19 06:22:22 -04:00
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.
This commit is contained in:
parent
7afe6a9a7d
commit
d3a3edb707
19 changed files with 388 additions and 390 deletions
|
@ -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):
|
||||
|
|
106
anki/deck.py
106
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] = '<span class="fm%s-%s">%s</span>' % (
|
||||
|
@ -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]
|
||||
|
|
|
@ -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 (?, ?, ?, ?, ?, ?, ?, ?)""",
|
||||
|
|
47
anki/find.py
47
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)):
|
||||
|
|
|
@ -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")
|
||||
|
|
304
anki/models.py
304
anki/models.py
|
@ -2,19 +2,19 @@
|
|||
# Copyright: Damien Elmes <anki@ichi2.net>
|
||||
# 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)
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
# Copyright: Damien Elmes <anki@ichi2.net>
|
||||
# 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}}<br>{{cloze:%d:%s}}<br>"+
|
||||
"{{/cloze:%d:Text}}") % (n, n, _("Text"), n)
|
||||
t['afmt'] = ("{{cloze:%d:" + _("Text") + "}}") % n
|
||||
t['afmt'] += "<br>{{" + _("Notes") + "}}"
|
||||
m.addTemplate(t)
|
||||
mm.addTemplate(m, t)
|
||||
mm.save(m)
|
||||
return m
|
||||
|
||||
models.append((_("Cloze"), ClozeModel))
|
||||
models.append((_("Cloze"), addClozeModel))
|
||||
|
|
|
@ -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()):
|
||||
|
|
|
@ -171,7 +171,7 @@ def entsToTxt(html):
|
|||
##############################################################################
|
||||
|
||||
def hexifyID(id):
|
||||
return "%x" % id
|
||||
return "%x" % int(id)
|
||||
|
||||
def dehexifyID(id):
|
||||
return int(id, 16)
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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'hello<b>world'
|
||||
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 "<span class=cloze>world</span>" in f.cards()[0].a()
|
||||
assert "hello <span class=cloze>world</span>" not in f.cards()[0].a()
|
||||
# check context works too
|
||||
f.model().conf['clozectx'] = True
|
||||
f.model()['clozectx'] = True
|
||||
assert "hello <span class=cloze>world</span>" in f.cards()[0].a()
|
||||
# and with a comment
|
||||
f = d.newFact()
|
||||
|
@ -143,16 +142,16 @@ def test_cloze():
|
|||
assert "world <span class=cloze>bar</span>" 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 "<span class=cloze>b</span>, <span class=cloze>c</span>" 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']
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue