mirror of
https://github.com/ankitects/anki.git
synced 2025-09-19 14:32: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):
|
def _getQA(self, reload=False):
|
||||||
if not self._qa or reload:
|
if not self._qa or reload:
|
||||||
f = self.fact(); m = self.model()
|
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()]
|
f.joinedFields()]
|
||||||
self._qa = self.deck._renderQA(self.model(), data)
|
self._qa = self.deck._renderQA(data)
|
||||||
return self._qa
|
return self._qa
|
||||||
|
|
||||||
def _withClass(self, txt, extra):
|
def _withClass(self, txt, extra):
|
||||||
|
@ -117,7 +117,7 @@ lapses=?, grade=?, cycles=?, edue=? where id = ?""",
|
||||||
"Fetch the model and fact."
|
"Fetch the model and fact."
|
||||||
if not self._rd or reload:
|
if not self._rd or reload:
|
||||||
f = self.deck.getFact(self.fid)
|
f = self.deck.getFact(self.fid)
|
||||||
m = self.deck.getModel(f.mid)
|
m = self.deck.models.get(f.mid)
|
||||||
self._rd = [f, m]
|
self._rd = [f, m]
|
||||||
return self._rd
|
return self._rd
|
||||||
|
|
||||||
|
@ -128,10 +128,10 @@ lapses=?, grade=?, cycles=?, edue=? where id = ?""",
|
||||||
return self._reviewData()[1]
|
return self._reviewData()[1]
|
||||||
|
|
||||||
def template(self):
|
def template(self):
|
||||||
return self._reviewData()[1].templates[self.ord]
|
return self._reviewData()[1]['tmpls'][self.ord]
|
||||||
|
|
||||||
def cssClass(self):
|
def cssClass(self):
|
||||||
return "cm%s-%s" % (hexifyID(self.model().id),
|
return "cm%s-%s" % (hexifyID(self.model()['id']),
|
||||||
hexifyID(self.template()['ord']))
|
hexifyID(self.template()['ord']))
|
||||||
|
|
||||||
def startTimer(self):
|
def startTimer(self):
|
||||||
|
|
106
anki/deck.py
106
anki/deck.py
|
@ -9,12 +9,13 @@ from anki.utils import parseTags, ids2str, hexifyID, \
|
||||||
splitFields
|
splitFields
|
||||||
from anki.hooks import runHook, runFilter
|
from anki.hooks import runHook, runFilter
|
||||||
from anki.sched import Scheduler
|
from anki.sched import Scheduler
|
||||||
|
from anki.models import ModelRegistry
|
||||||
from anki.media import MediaRegistry
|
from anki.media import MediaRegistry
|
||||||
from anki.consts import *
|
from anki.consts import *
|
||||||
from anki.errors import AnkiError
|
from anki.errors import AnkiError
|
||||||
|
|
||||||
import anki.latex # sets up hook
|
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
|
anki.groups, anki.find
|
||||||
|
|
||||||
# Settings related to queue building. These may be loaded without the rest of
|
# 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.path = db._path
|
||||||
self._lastSave = time.time()
|
self._lastSave = time.time()
|
||||||
self.clearUndo()
|
self.clearUndo()
|
||||||
|
self.media = MediaRegistry(self)
|
||||||
|
self.models = ModelRegistry(self)
|
||||||
self.load()
|
self.load()
|
||||||
if not self.crt:
|
if not self.crt:
|
||||||
d = datetime.datetime.today()
|
d = datetime.datetime.today()
|
||||||
|
@ -65,7 +68,6 @@ class _Deck(object):
|
||||||
self.lastSessionStart = 0
|
self.lastSessionStart = 0
|
||||||
self._stdSched = Scheduler(self)
|
self._stdSched = Scheduler(self)
|
||||||
self.sched = self._stdSched
|
self.sched = self._stdSched
|
||||||
self.media = MediaRegistry(self)
|
|
||||||
# check for improper shutdown
|
# check for improper shutdown
|
||||||
self.cleanup()
|
self.cleanup()
|
||||||
|
|
||||||
|
@ -85,16 +87,16 @@ class _Deck(object):
|
||||||
self.lastSync,
|
self.lastSync,
|
||||||
self.qconf,
|
self.qconf,
|
||||||
self.conf,
|
self.conf,
|
||||||
|
models,
|
||||||
self.groups,
|
self.groups,
|
||||||
self.gconf,
|
self.gconf) = self.db.first("""
|
||||||
self.data) = self.db.first("""
|
|
||||||
select crt, mod, scm, dty, syncName, lastSync,
|
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.qconf = simplejson.loads(self.qconf)
|
||||||
self.conf = simplejson.loads(self.conf)
|
self.conf = simplejson.loads(self.conf)
|
||||||
self.groups = simplejson.loads(self.groups)
|
self.groups = simplejson.loads(self.groups)
|
||||||
self.gconf = simplejson.loads(self.gconf)
|
self.gconf = simplejson.loads(self.gconf)
|
||||||
self.data = simplejson.loads(self.data)
|
self.models.load(models)
|
||||||
|
|
||||||
def flush(self, mod=None):
|
def flush(self, mod=None):
|
||||||
"Flush state to DB, updating mod time."
|
"Flush state to DB, updating mod time."
|
||||||
|
@ -102,11 +104,14 @@ qconf, conf, groups, gconf, data from deck""")
|
||||||
self.db.execute(
|
self.db.execute(
|
||||||
"""update deck set
|
"""update deck set
|
||||||
crt=?, mod=?, scm=?, dty=?, syncName=?, lastSync=?,
|
crt=?, mod=?, scm=?, dty=?, syncName=?, lastSync=?,
|
||||||
qconf=?, conf=?, data=?""",
|
qconf=?, conf=?, groups=?, gconf=?""",
|
||||||
self.crt, self.mod, self.scm, self.dty,
|
self.crt, self.mod, self.scm, self.dty,
|
||||||
self.syncName, self.lastSync,
|
self.syncName, self.lastSync,
|
||||||
simplejson.dumps(self.qconf),
|
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):
|
def save(self, name=None, mod=None):
|
||||||
"Flush, commit DB, and take out another write lock."
|
"Flush, commit DB, and take out another write lock."
|
||||||
|
@ -187,15 +192,6 @@ qconf=?, conf=?, data=?""",
|
||||||
def getFact(self, id):
|
def getFact(self, id):
|
||||||
return anki.facts.Fact(self, id=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
|
# Utils
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
|
@ -227,7 +223,7 @@ qconf=?, conf=?, data=?""",
|
||||||
|
|
||||||
def newFact(self):
|
def newFact(self):
|
||||||
"Return a new fact with the current model."
|
"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):
|
def addFact(self, fact):
|
||||||
"Add a fact to the deck. Return number of new cards."
|
"Add a fact to the deck. Return number of new cards."
|
||||||
|
@ -273,14 +269,14 @@ qconf=?, conf=?, data=?""",
|
||||||
"Return (active), non-empty templates."
|
"Return (active), non-empty templates."
|
||||||
ok = []
|
ok = []
|
||||||
model = fact.model()
|
model = fact.model()
|
||||||
for template in model.templates:
|
for template in model['tmpls']:
|
||||||
if template['actv'] or not checkActive:
|
if template['actv'] or not checkActive:
|
||||||
# [cid, fid, mid, gid, ord, tags, flds]
|
# [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()]
|
"", fact.joinedFields()]
|
||||||
now = self._renderQA(model, data)
|
now = self._renderQA(data)
|
||||||
data[6] = "\x1f".join([""]*len(fact.fields))
|
data[6] = "\x1f".join([""]*len(fact.fields))
|
||||||
empty = self._renderQA(model, data)
|
empty = self._renderQA(data)
|
||||||
if now['q'] == empty['q']:
|
if now['q'] == empty['q']:
|
||||||
continue
|
continue
|
||||||
if not template['emptyAns']:
|
if not template['emptyAns']:
|
||||||
|
@ -321,7 +317,7 @@ qconf=?, conf=?, data=?""",
|
||||||
elif type == 1:
|
elif type == 1:
|
||||||
cms = [c.template() for c in fact.cards()]
|
cms = [c.template() for c in fact.cards()]
|
||||||
else:
|
else:
|
||||||
cms = fact.model().templates
|
cms = fact.model()['tmpls']
|
||||||
if not cms:
|
if not cms:
|
||||||
return []
|
return []
|
||||||
cards = []
|
cards = []
|
||||||
|
@ -365,45 +361,6 @@ select id from facts where id in %s and id not in (select fid from cards)""" %
|
||||||
ids2str(fids))
|
ids2str(fids))
|
||||||
self._delFacts(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
|
# 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):
|
def updateFieldCache(self, fids, csum=True):
|
||||||
"Update field checksums and sort cache, after find&replace, etc."
|
"Update field checksums and sort cache, after find&replace, etc."
|
||||||
sfids = ids2str(fids)
|
sfids = ids2str(fids)
|
||||||
mods = self.models()
|
|
||||||
r = []
|
r = []
|
||||||
r2 = []
|
r2 = []
|
||||||
for (fid, mid, flds) in self._fieldData(sfids):
|
for (fid, mid, flds) in self._fieldData(sfids):
|
||||||
fields = splitFields(flds)
|
fields = splitFields(flds)
|
||||||
model = mods[mid]
|
model = self.models.get(mid)
|
||||||
if csum:
|
if csum:
|
||||||
for f in model.fields:
|
for f in model['flds']:
|
||||||
if f['uniq'] and fields[f['ord']]:
|
if f['uniq'] and fields[f['ord']]:
|
||||||
r.append((fid, mid, fieldChecksum(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:
|
if csum:
|
||||||
self.db.execute("delete from fsums where fid in "+sfids)
|
self.db.execute("delete from fsums where fid in "+sfids)
|
||||||
self.db.executemany("insert into fsums values (?,?,?)", r)
|
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 = ""
|
where = ""
|
||||||
else:
|
else:
|
||||||
raise Exception()
|
raise Exception()
|
||||||
mods = self.models()
|
return [self._renderQA(row)
|
||||||
return [self._renderQA(mods[row[2]], row)
|
|
||||||
for row in self._qaData(where)]
|
for row in self._qaData(where)]
|
||||||
|
|
||||||
def _renderQA(self, model, data):
|
def _renderQA(self, data):
|
||||||
"Returns hash of id, question, answer."
|
"Returns hash of id, question, answer."
|
||||||
# data is [cid, fid, mid, gid, ord, tags, flds]
|
# data is [cid, fid, mid, gid, ord, tags, flds]
|
||||||
# unpack fields and create dict
|
# unpack fields and create dict
|
||||||
flist = splitFields(data[6])
|
flist = splitFields(data[6])
|
||||||
fields = {}
|
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]
|
fields[name] = flist[idx]
|
||||||
if fields[name]:
|
if fields[name]:
|
||||||
fields[name] = '<span class="fm%s-%s">%s</span>' % (
|
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:
|
else:
|
||||||
fields[name] = ""
|
fields[name] = ""
|
||||||
fields['Tags'] = data[5]
|
fields['Tags'] = data[5]
|
||||||
fields['Model'] = model.name
|
fields['Model'] = model['name']
|
||||||
fields['Group'] = self.groupName(data[3])
|
fields['Group'] = self.groupName(data[3])
|
||||||
template = model.templates[data[4]]
|
template = model['tmpls'][data[4]]
|
||||||
fields['Template'] = template['name']
|
fields['Template'] = template['name']
|
||||||
# render q & a
|
# render q & a
|
||||||
d = dict(id=data[0])
|
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":
|
if type == "q":
|
||||||
format = format.replace("cloze:", "cq:")
|
format = format.replace("cloze:", "cq:")
|
||||||
else:
|
else:
|
||||||
if model.conf['clozectx']:
|
if model['clozectx']:
|
||||||
name = "cactx:"
|
name = "cactx:"
|
||||||
else:
|
else:
|
||||||
name = "ca:"
|
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.db.execute("delete from tags")
|
||||||
self.updateFactTags()
|
self.updateFactTags()
|
||||||
# field cache
|
# field cache
|
||||||
for m in self.models().values():
|
for m in self.models.all():
|
||||||
self.updateFieldCache(m.fids())
|
self.updateFieldCache(self.models.fids(m['id']))
|
||||||
# and finally, optimize
|
# and finally, optimize
|
||||||
self.optimize()
|
self.optimize()
|
||||||
newSize = os.stat(self.path)[stat.ST_SIZE]
|
newSize = os.stat(self.path)[stat.ST_SIZE]
|
||||||
|
|
|
@ -19,12 +19,12 @@ class Fact(object):
|
||||||
else:
|
else:
|
||||||
self.id = timestampID(deck.db, "facts")
|
self.id = timestampID(deck.db, "facts")
|
||||||
self._model = model
|
self._model = model
|
||||||
self.gid = model.conf['gid']
|
self.gid = model['gid']
|
||||||
self.mid = model.id
|
self.mid = model['id']
|
||||||
self.tags = []
|
self.tags = []
|
||||||
self.fields = [""] * len(self._model.fields)
|
self.fields = [""] * len(self._model['flds'])
|
||||||
self.data = ""
|
self.data = ""
|
||||||
self._fmap = self._model.fieldMap()
|
self._fmap = self.deck.models.fieldMap(self._model)
|
||||||
|
|
||||||
def load(self):
|
def load(self):
|
||||||
(self.mid,
|
(self.mid,
|
||||||
|
@ -36,12 +36,12 @@ class Fact(object):
|
||||||
select mid, gid, mod, tags, flds, data from facts where id = ?""", self.id)
|
select mid, gid, mod, tags, flds, data from facts where id = ?""", self.id)
|
||||||
self.fields = splitFields(self.fields)
|
self.fields = splitFields(self.fields)
|
||||||
self.tags = parseTags(self.tags)
|
self.tags = parseTags(self.tags)
|
||||||
self._model = self.deck.getModel(self.mid)
|
self._model = self.deck.models.get(self.mid)
|
||||||
self._fmap = self._model.fieldMap()
|
self._fmap = self.deck.models.fieldMap(self._model)
|
||||||
|
|
||||||
def flush(self):
|
def flush(self):
|
||||||
self.mod = intTime()
|
self.mod = intTime()
|
||||||
sfld = stripHTML(self.fields[self._model.sortIdx()])
|
sfld = stripHTML(self.fields[self.deck.models.sortIdx(self._model)])
|
||||||
tags = self.stringTags()
|
tags = self.stringTags()
|
||||||
res = self.deck.db.execute("""
|
res = self.deck.db.execute("""
|
||||||
insert or replace into facts values (?, ?, ?, ?, ?, ?, ?, ?)""",
|
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):
|
def fieldNames(deck, downcase=True):
|
||||||
fields = set()
|
fields = set()
|
||||||
names = []
|
names = []
|
||||||
for m in deck.models().values():
|
for m in deck.models.all():
|
||||||
for f in m.fields:
|
for f in m['flds']:
|
||||||
if f['name'].lower() not in fields:
|
if f['name'].lower() not in fields:
|
||||||
names.append(f['name'])
|
names.append(f['name'])
|
||||||
fields.add(f['name'].lower())
|
fields.add(f['name'].lower())
|
||||||
|
@ -119,7 +119,7 @@ order by %s""" % (lim, sort)
|
||||||
elif type == SEARCH_FIELD:
|
elif type == SEARCH_FIELD:
|
||||||
self._findField(token, isNeg)
|
self._findField(token, isNeg)
|
||||||
elif type == SEARCH_MODEL:
|
elif type == SEARCH_MODEL:
|
||||||
self._findModel(token, isNeg, c)
|
self._findModel(token, isNeg)
|
||||||
elif type == SEARCH_GROUP:
|
elif type == SEARCH_GROUP:
|
||||||
self._findGroup(token, isNeg)
|
self._findGroup(token, isNeg)
|
||||||
else:
|
else:
|
||||||
|
@ -182,12 +182,13 @@ order by %s""" % (lim, sort)
|
||||||
def _findFids(self, val):
|
def _findFids(self, val):
|
||||||
self.lims['fact'].append("id in (%s)" % 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 ""
|
extra = "not" if isNeg else ""
|
||||||
self.lims['fact'].append(
|
ids = []
|
||||||
"mid %s in (select id from models where name like :_mod_%d)" % (
|
for m in self.deck.models.all():
|
||||||
extra, c))
|
if m['name'].lower() == val:
|
||||||
self.lims['args']['_mod_%d'%c] = val
|
ids.append(m['id'])
|
||||||
|
self.lims['fact'].append("mid %s in %s" % (extra, ids2str(ids)))
|
||||||
|
|
||||||
def _findGroup(self, val, isNeg):
|
def _findGroup(self, val, isNeg):
|
||||||
extra = "!" if isNeg else ""
|
extra = "!" if isNeg else ""
|
||||||
|
@ -203,8 +204,8 @@ order by %s""" % (lim, sort)
|
||||||
except:
|
except:
|
||||||
num = None
|
num = None
|
||||||
lims = []
|
lims = []
|
||||||
for m in self.deck.models().values():
|
for m in self.deck.models.all():
|
||||||
for t in m.templates:
|
for t in m['tmpls']:
|
||||||
# ordinal number?
|
# ordinal number?
|
||||||
if num is not None and t['ord'] == num:
|
if num is not None and t['ord'] == num:
|
||||||
self.lims['card'].append("ord %s %d" % (comp, num))
|
self.lims['card'].append("ord %s %d" % (comp, num))
|
||||||
|
@ -212,8 +213,8 @@ order by %s""" % (lim, sort)
|
||||||
# template name?
|
# template name?
|
||||||
elif t['name'].lower() == val.lower():
|
elif t['name'].lower() == val.lower():
|
||||||
lims.append((
|
lims.append((
|
||||||
"(fid in (select id from facts where mid = %d) "
|
"(fid in (select id from facts where mid = %s) "
|
||||||
"and ord %s %d)") % (m.id, comp, t['ord']))
|
"and ord %s %d)") % (m['id'], comp, t['ord']))
|
||||||
found = True
|
found = True
|
||||||
if lims:
|
if lims:
|
||||||
self.lims['card'].append("(" + " or ".join(lims) + ")")
|
self.lims['card'].append("(" + " or ".join(lims) + ")")
|
||||||
|
@ -226,10 +227,10 @@ order by %s""" % (lim, sort)
|
||||||
value = "%" + parts[1].replace("*", "%") + "%"
|
value = "%" + parts[1].replace("*", "%") + "%"
|
||||||
# find models that have that field
|
# find models that have that field
|
||||||
mods = {}
|
mods = {}
|
||||||
for m in self.deck.models().values():
|
for m in self.deck.models.all():
|
||||||
for f in m.fields:
|
for f in m['flds']:
|
||||||
if f['name'].lower() == field:
|
if f['name'].lower() == field:
|
||||||
mods[m.id] = (m, f['ord'])
|
mods[m['id']] = (m, f['ord'])
|
||||||
if not mods:
|
if not mods:
|
||||||
# nothing has that field
|
# nothing has that field
|
||||||
self.lims['valid'] = False
|
self.lims['valid'] = False
|
||||||
|
@ -243,11 +244,11 @@ where mid in %s and flds like ? escape '\\'""" % (
|
||||||
ids2str(mods.keys())),
|
ids2str(mods.keys())),
|
||||||
"%" if self.full else value):
|
"%" if self.full else value):
|
||||||
flds = splitFields(flds)
|
flds = splitFields(flds)
|
||||||
ord = mods[mid][1]
|
ord = mods[str(mid)][1]
|
||||||
str = flds[ord]
|
strg = flds[ord]
|
||||||
if self.full:
|
if self.full:
|
||||||
str = stripHTML(str)
|
strg = stripHTML(strg)
|
||||||
if re.search(regex, str):
|
if re.search(regex, strg):
|
||||||
fids.append(id)
|
fids.append(id)
|
||||||
extra = "not" if isNeg else ""
|
extra = "not" if isNeg else ""
|
||||||
self.lims['fact'].append("id %s in %s" % (extra, ids2str(fids)))
|
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."
|
"Find and replace fields in a fact."
|
||||||
mmap = {}
|
mmap = {}
|
||||||
if field:
|
if field:
|
||||||
for m in deck.models().values():
|
for m in deck.models.all():
|
||||||
for f in m.fields:
|
for f in m['flds']:
|
||||||
if f['name'] == field:
|
if f['name'] == field:
|
||||||
mmap[m.id] = f['ord']
|
mmap[m['id']] = f['ord']
|
||||||
if not mmap:
|
if not mmap:
|
||||||
return 0
|
return 0
|
||||||
# find and gather replacements
|
# find and gather replacements
|
||||||
|
@ -393,7 +394,7 @@ def findReplace(deck, fids, src, dst, regex=False, field=None, fold=True):
|
||||||
# does it match?
|
# does it match?
|
||||||
sflds = splitFields(flds)
|
sflds = splitFields(flds)
|
||||||
if field:
|
if field:
|
||||||
ord = mmap[mid]
|
ord = mmap[str(mid)]
|
||||||
sflds[ord] = repl(sflds[ord])
|
sflds[ord] = repl(sflds[ord])
|
||||||
else:
|
else:
|
||||||
for c in range(len(sflds)):
|
for c in range(len(sflds)):
|
||||||
|
|
|
@ -71,9 +71,9 @@ def _latexFromHtml(deck, latex):
|
||||||
|
|
||||||
def _buildImg(deck, latex, fname, model):
|
def _buildImg(deck, latex, fname, model):
|
||||||
# add header/footer
|
# add header/footer
|
||||||
latex = (model.conf["latexPre"] + "\n" +
|
latex = (model["latexPre"] + "\n" +
|
||||||
latex + "\n" +
|
latex + "\n" +
|
||||||
model.conf["latexPost"])
|
model["latexPost"])
|
||||||
# write into a temp file
|
# write into a temp file
|
||||||
log = open(namedtmp("latex_log.txt"), "w")
|
log = open(namedtmp("latex_log.txt"), "w")
|
||||||
texfile = file(namedtmp("tmp.tex"), "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>
|
# Copyright: Damien Elmes <anki@ichi2.net>
|
||||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
# 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, \
|
from anki.utils import intTime, hexifyID, joinFields, splitFields, ids2str, \
|
||||||
timestampID
|
timestampID
|
||||||
from anki.lang import _
|
from anki.lang import _
|
||||||
|
|
||||||
# Models
|
# 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,
|
'sortf': 0,
|
||||||
'gid': 1,
|
'gid': 1,
|
||||||
'tags': [],
|
|
||||||
'clozectx': False,
|
'clozectx': False,
|
||||||
'latexPre': """\
|
'latexPre': """\
|
||||||
\\documentclass[12pt]{article}
|
\\documentclass[12pt]{article}
|
||||||
|
@ -56,82 +56,132 @@ defaultTemplate = {
|
||||||
'gid': None,
|
'gid': None,
|
||||||
}
|
}
|
||||||
|
|
||||||
class Model(object):
|
class ModelRegistry(object):
|
||||||
|
|
||||||
def __init__(self, deck, id=None):
|
# Saving/loading registry
|
||||||
|
#############################################################
|
||||||
|
|
||||||
|
def __init__(self, deck):
|
||||||
self.deck = 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):
|
def load(self, json):
|
||||||
(self.mod,
|
"Load registry from JSON."
|
||||||
self.name,
|
self.changed = False
|
||||||
self.fields,
|
self.models = simplejson.loads(json)
|
||||||
self.templates,
|
|
||||||
self.conf,
|
def save(self, m=None):
|
||||||
self.css) = self.deck.db.first("""
|
"Mark M modified if provided, and schedule registry flush."
|
||||||
select mod, name, flds, tmpls, conf, css from models where id = ?""", self.id)
|
if m:
|
||||||
self.fields = simplejson.loads(self.fields)
|
m['mod'] = intTime()
|
||||||
self.templates = simplejson.loads(self.templates)
|
m['css'] = self._css(m)
|
||||||
self.conf = simplejson.loads(self.conf)
|
self.changed = True
|
||||||
|
|
||||||
def flush(self):
|
def flush(self):
|
||||||
self.mod = intTime()
|
"Flush the registry if any models were changed."
|
||||||
self.css = self.genCSS()
|
if self.changed:
|
||||||
ret = self.deck.db.execute("""
|
self.deck.db.execute("update deck set models = ?",
|
||||||
insert or replace into models values (?, ?, ?, ?, ?, ?, ?)""",
|
simplejson.dumps(self.models))
|
||||||
self.id, self.mod, self.name,
|
|
||||||
simplejson.dumps(self.fields),
|
|
||||||
simplejson.dumps(self.templates),
|
|
||||||
simplejson.dumps(self.conf),
|
|
||||||
self.css)
|
|
||||||
self.id = ret.lastrowid
|
|
||||||
|
|
||||||
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(
|
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(
|
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
|
# Copying
|
||||||
##################################################
|
##################################################
|
||||||
|
|
||||||
def copy(self):
|
def copy(self, m):
|
||||||
"Copy, flush and return."
|
"Copy, save and return."
|
||||||
new = Model(self.deck, self.id)
|
m2 = copy.deepcopy(m)
|
||||||
new.id = None
|
m2['name'] = _("%s copy") % m2['name']
|
||||||
new.name += _(" copy")
|
return self._add(m2)
|
||||||
new.fields = [f.copy() for f in self.fields]
|
|
||||||
new.templates = [t.copy() for t in self.templates]
|
|
||||||
new.flush()
|
|
||||||
return new
|
|
||||||
|
|
||||||
# CSS generation
|
# CSS generation
|
||||||
##################################################
|
##################################################
|
||||||
|
|
||||||
def genCSS(self):
|
def _css(self, m):
|
||||||
if not self.id:
|
|
||||||
return ""
|
|
||||||
# fields
|
# fields
|
||||||
css = "".join(self._fieldCSS(
|
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']))
|
(f['font'], f['qsize'], f['qcol'], f['rtl'], f['pre']))
|
||||||
for f in self.fields)
|
for f in m['flds'])
|
||||||
# templates
|
# templates
|
||||||
css += "".join(".cm%s-%s {text-align:%s;background:%s}\n" % (
|
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'])
|
("center", "left", "right")[t['align']], t['bg'])
|
||||||
for t in self.templates)
|
for t in m['tmpls'])
|
||||||
return css
|
return css
|
||||||
|
|
||||||
def _rewriteFont(self, font):
|
def _rewriteFont(self, font):
|
||||||
|
@ -158,64 +208,66 @@ insert or replace into models values (?, ?, ?, ?, ?, ?, ?)""",
|
||||||
# Fields
|
# 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)."
|
"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):
|
def sortIdx(self, m):
|
||||||
return self.conf['sortf']
|
return m['sortf']
|
||||||
|
|
||||||
def setSortIdx(self, idx):
|
def setSortIdx(self, m, idx):
|
||||||
assert idx >= 0 and idx < len(self.fields)
|
assert idx >= 0 and idx < len(m['flds'])
|
||||||
self.deck.modSchema()
|
self.deck.modSchema()
|
||||||
self.conf['sortf'] = idx
|
m['sortf'] = idx
|
||||||
self.deck.updateFieldCache(self.fids(), csum=False)
|
self.deck.updateFieldCache(self.fids(m), csum=False)
|
||||||
self.flush()
|
self.save(m)
|
||||||
|
|
||||||
def newField(self):
|
def addField(self, m, field):
|
||||||
return defaultField.copy()
|
m['flds'].append(field)
|
||||||
|
self._updateFieldOrds(m)
|
||||||
def addField(self, field):
|
self.save(m)
|
||||||
self.fields.append(field)
|
|
||||||
self._updateFieldOrds()
|
|
||||||
self.flush()
|
|
||||||
def add(fields):
|
def add(fields):
|
||||||
fields.append("")
|
fields.append("")
|
||||||
return fields
|
return fields
|
||||||
self._transformFields(add)
|
self._transformFields(m, add)
|
||||||
|
|
||||||
def delField(self, field):
|
def delField(self, m, field):
|
||||||
idx = self.fields.index(field)
|
idx = m['flds'].index(field)
|
||||||
self.fields.remove(field)
|
m['flds'].remove(field)
|
||||||
self._updateFieldOrds()
|
self._updateFieldOrds(m)
|
||||||
def delete(fields):
|
def delete(fields):
|
||||||
del fields[idx]
|
del fields[idx]
|
||||||
return fields
|
return fields
|
||||||
self._transformFields(delete)
|
self._transformFields(m, delete)
|
||||||
if idx == self.sortIdx():
|
if idx == self.sortIdx(m):
|
||||||
# need to rebuild
|
# need to rebuild
|
||||||
self.deck.updateFieldCache(self.fids(), csum=False)
|
self.deck.updateFieldCache(self.fids(m), csum=False)
|
||||||
# flushes
|
# saves
|
||||||
self.renameField(field, None)
|
self.renameField(m, field, None)
|
||||||
|
|
||||||
def moveField(self, field, idx):
|
def moveField(self, m, field, idx):
|
||||||
oldidx = self.fields.index(field)
|
oldidx = m['flds'].index(field)
|
||||||
if oldidx == idx:
|
if oldidx == idx:
|
||||||
return
|
return
|
||||||
self.fields.remove(field)
|
m['flds'].remove(field)
|
||||||
self.fields.insert(idx, field)
|
m['flds'].insert(idx, field)
|
||||||
self._updateFieldOrds()
|
self._updateFieldOrds(m)
|
||||||
self.flush()
|
self.save(m)
|
||||||
def move(fields, oldidx=oldidx):
|
def move(fields, oldidx=oldidx):
|
||||||
val = fields[oldidx]
|
val = fields[oldidx]
|
||||||
del fields[oldidx]
|
del fields[oldidx]
|
||||||
fields.insert(idx, val)
|
fields.insert(idx, val)
|
||||||
return fields
|
return fields
|
||||||
self._transformFields(move)
|
self._transformFields(m, move)
|
||||||
|
|
||||||
def renameField(self, field, newName):
|
def renameField(self, m, field, newName):
|
||||||
self.deck.modSchema()
|
self.deck.modSchema()
|
||||||
for t in self.templates:
|
for t in m['tmpls']:
|
||||||
types = ("{{%s}}", "{{text:%s}}", "{{#%s}}",
|
types = ("{{%s}}", "{{text:%s}}", "{{#%s}}",
|
||||||
"{{^%s}}", "{{/%s}}")
|
"{{^%s}}", "{{/%s}}")
|
||||||
for type in types:
|
for type in types:
|
||||||
|
@ -226,77 +278,79 @@ insert or replace into models values (?, ?, ?, ?, ?, ?, ?)""",
|
||||||
repl = ""
|
repl = ""
|
||||||
t[fmt] = t[fmt].replace(type%field['name'], repl)
|
t[fmt] = t[fmt].replace(type%field['name'], repl)
|
||||||
field['name'] = newName
|
field['name'] = newName
|
||||||
self.flush()
|
self.save(m)
|
||||||
|
|
||||||
def _updateFieldOrds(self):
|
def _updateFieldOrds(self, m):
|
||||||
for c, f in enumerate(self.fields):
|
for c, f in enumerate(m['flds']):
|
||||||
f['ord'] = c
|
f['ord'] = c
|
||||||
|
|
||||||
def _transformFields(self, fn):
|
def _transformFields(self, m, fn):
|
||||||
self.deck.modSchema()
|
self.deck.modSchema()
|
||||||
r = []
|
r = []
|
||||||
for (id, flds) in self.deck.db.execute(
|
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))
|
r.append((joinFields(fn(splitFields(flds))), id))
|
||||||
self.deck.db.executemany("update facts set flds = ? where id = ?", r)
|
self.deck.db.executemany("update facts set flds = ? where id = ?", r)
|
||||||
|
|
||||||
# Templates
|
# Templates
|
||||||
##################################################
|
##################################################
|
||||||
|
|
||||||
def newTemplate(self):
|
def newTemplate(self, name):
|
||||||
return defaultTemplate.copy()
|
t = defaultTemplate.copy()
|
||||||
|
t['name'] = name
|
||||||
|
return t
|
||||||
|
|
||||||
def addTemplate(self, template):
|
def addTemplate(self, m, template):
|
||||||
self.deck.modSchema()
|
self.deck.modSchema()
|
||||||
self.templates.append(template)
|
m['tmpls'].append(template)
|
||||||
self._updateTemplOrds()
|
self._updateTemplOrds(m)
|
||||||
self.flush()
|
self.save(m)
|
||||||
|
|
||||||
def delTemplate(self, template):
|
def delTemplate(self, m, template):
|
||||||
self.deck.modSchema()
|
self.deck.modSchema()
|
||||||
ord = self.templates.index(template)
|
ord = m['tmpls'].index(template)
|
||||||
cids = self.deck.db.list("""
|
cids = self.deck.db.list("""
|
||||||
select c.id from cards c, facts f where c.fid=f.id and mid = ? and ord = ?""",
|
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)
|
self.deck.delCards(cids)
|
||||||
# shift ordinals
|
# shift ordinals
|
||||||
self.deck.db.execute("""
|
self.deck.db.execute("""
|
||||||
update cards set ord = ord - 1 where fid in (select id from facts
|
update cards set ord = ord - 1 where fid in (select id from facts
|
||||||
where mid = ?) and ord > ?""", self.id, ord)
|
where mid = ?) and ord > ?""", m['id'], ord)
|
||||||
self.templates.remove(template)
|
m['tmpls'].remove(template)
|
||||||
self._updateTemplOrds()
|
self._updateTemplOrds(m)
|
||||||
self.flush()
|
self.save(m)
|
||||||
|
|
||||||
def _updateTemplOrds(self):
|
def _updateTemplOrds(self, m):
|
||||||
for c, t in enumerate(self.templates):
|
for c, t in enumerate(m['tmpls']):
|
||||||
t['ord'] = c
|
t['ord'] = c
|
||||||
|
|
||||||
def moveTemplate(self, template, idx):
|
def moveTemplate(self, m, template, idx):
|
||||||
oldidx = self.templates.index(template)
|
oldidx = m['tmpls'].index(template)
|
||||||
if oldidx == idx:
|
if oldidx == idx:
|
||||||
return
|
return
|
||||||
oldidxs = dict((id(t), t['ord']) for t in self.templates)
|
oldidxs = dict((id(t), t['ord']) for t in m['tmpls'])
|
||||||
self.templates.remove(template)
|
m['tmpls'].remove(template)
|
||||||
self.templates.insert(idx, template)
|
m['tmpls'].insert(idx, template)
|
||||||
self._updateTemplOrds()
|
self._updateTemplOrds(m)
|
||||||
# generate change map
|
# generate change map
|
||||||
map = []
|
map = []
|
||||||
for t in self.templates:
|
for t in m['tmpls']:
|
||||||
map.append("when ord = %d then %d" % (oldidxs[id(t)], t['ord']))
|
map.append("when ord = %d then %d" % (oldidxs[id(t)], t['ord']))
|
||||||
# apply
|
# apply
|
||||||
self.flush()
|
self.save(m)
|
||||||
self.deck.db.execute("""
|
self.deck.db.execute("""
|
||||||
update cards set ord = (case %s end) where fid in (
|
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
|
# Model changing
|
||||||
##########################################################################
|
##########################################################################
|
||||||
# - maps are ord->ord, and there should not be duplicate targets
|
# - maps are ord->ord, and there should not be duplicate targets
|
||||||
# - newModel should be self if model is not changing
|
# - 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()
|
self.deck.modSchema()
|
||||||
assert newModel.id == self.id or (fmap and cmap)
|
assert newModel['id'] == m['id'] or (fmap and cmap)
|
||||||
if fmap:
|
if fmap:
|
||||||
self._changeFacts(fids, newModel, fmap)
|
self._changeFacts(fids, newModel, fmap)
|
||||||
if cmap:
|
if cmap:
|
||||||
|
@ -304,7 +358,7 @@ select id from facts where mid = ?)""" % " ".join(map), self.id)
|
||||||
|
|
||||||
def _changeFacts(self, fids, newModel, map):
|
def _changeFacts(self, fids, newModel, map):
|
||||||
d = []
|
d = []
|
||||||
nfields = len(newModel.fields)
|
nfields = len(newModel['flds'])
|
||||||
for (fid, flds) in self.deck.db.execute(
|
for (fid, flds) in self.deck.db.execute(
|
||||||
"select id, flds from facts where id in "+ids2str(fids)):
|
"select id, flds from facts where id in "+ids2str(fids)):
|
||||||
newflds = {}
|
newflds = {}
|
||||||
|
@ -315,7 +369,7 @@ select id from facts where mid = ?)""" % " ".join(map), self.id)
|
||||||
for c in range(nfields):
|
for c in range(nfields):
|
||||||
flds.append(newflds.get(c, ""))
|
flds.append(newflds.get(c, ""))
|
||||||
flds = joinFields(flds)
|
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(
|
self.deck.db.executemany(
|
||||||
"update facts set flds=:flds, mid=:mid where id = :fid", d)
|
"update facts set flds=:flds, mid=:mid where id = :fid", d)
|
||||||
self.deck.updateFieldCache(fids)
|
self.deck.updateFieldCache(fids)
|
||||||
|
|
|
@ -47,7 +47,7 @@ class CardStats(object):
|
||||||
self.addLine(_("Total Time"), self.time(total))
|
self.addLine(_("Total Time"), self.time(total))
|
||||||
elif c.queue == 0:
|
elif c.queue == 0:
|
||||||
self.addLine(_("Position"), c.due)
|
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(_("Template"), c.template()['name'])
|
||||||
self.addLine(_("Current Group"), self.deck.groupName(c.gid))
|
self.addLine(_("Current Group"), self.deck.groupName(c.gid))
|
||||||
self.addLine(_("Initial Group"), self.deck.groupName(c.fact().gid))
|
self.addLine(_("Initial Group"), self.deck.groupName(c.fact().gid))
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
# Copyright: Damien Elmes <anki@ichi2.net>
|
# Copyright: Damien Elmes <anki@ichi2.net>
|
||||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
from anki.models import Model
|
|
||||||
from anki.lang import _
|
from anki.lang import _
|
||||||
|
|
||||||
models = []
|
models = []
|
||||||
|
@ -10,55 +9,50 @@ models = []
|
||||||
# Basic
|
# Basic
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
def BasicModel(deck):
|
def addBasicModel(deck):
|
||||||
m = Model(deck)
|
mm = deck.models
|
||||||
m.name = _("Basic")
|
m = mm.new(_("Basic"))
|
||||||
fm = m.newField()
|
fm = mm.newField(_("Front"))
|
||||||
fm['name'] = _("Front")
|
|
||||||
fm['req'] = True
|
fm['req'] = True
|
||||||
fm['uniq'] = True
|
fm['uniq'] = True
|
||||||
m.addField(fm)
|
mm.addField(m, fm)
|
||||||
fm = m.newField()
|
fm = mm.newField(_("Back"))
|
||||||
fm['name'] = _("Back")
|
mm.addField(m, fm)
|
||||||
m.addField(fm)
|
t = mm.newTemplate(_("Forward"))
|
||||||
t = m.newTemplate()
|
|
||||||
t['name'] = _("Forward")
|
|
||||||
t['qfmt'] = "{{" + _("Front") + "}}"
|
t['qfmt'] = "{{" + _("Front") + "}}"
|
||||||
t['afmt'] = "{{" + _("Back") + "}}"
|
t['afmt'] = "{{" + _("Back") + "}}"
|
||||||
m.addTemplate(t)
|
mm.addTemplate(m, t)
|
||||||
t = m.newTemplate()
|
t = mm.newTemplate(_("Reverse"))
|
||||||
t['name'] = _("Reverse")
|
|
||||||
t['qfmt'] = "{{" + _("Back") + "}}"
|
t['qfmt'] = "{{" + _("Back") + "}}"
|
||||||
t['afmt'] = "{{" + _("Front") + "}}"
|
t['afmt'] = "{{" + _("Front") + "}}"
|
||||||
t['actv'] = False
|
t['actv'] = False
|
||||||
m.addTemplate(t)
|
mm.addTemplate(m, t)
|
||||||
|
mm.save(m)
|
||||||
return m
|
return m
|
||||||
|
|
||||||
models.append((_("Basic"), BasicModel))
|
models.append((_("Basic"), addBasicModel))
|
||||||
|
|
||||||
# Cloze
|
# Cloze
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
def ClozeModel(deck):
|
def addClozeModel(deck):
|
||||||
m = Model(deck)
|
mm = deck.models
|
||||||
m.name = _("Cloze")
|
m = mm.new(_("Cloze"))
|
||||||
fm = m.newField()
|
fm = mm.newField(_("Text"))
|
||||||
fm['name'] = _("Text")
|
|
||||||
fm['req'] = True
|
fm['req'] = True
|
||||||
fm['uniq'] = True
|
fm['uniq'] = True
|
||||||
m.addField(fm)
|
mm.addField(m, fm)
|
||||||
fm = m.newField()
|
fm = mm.newField(_("Notes"))
|
||||||
fm['name'] = _("Notes")
|
mm.addField(m, fm)
|
||||||
m.addField(fm)
|
|
||||||
for i in range(8):
|
for i in range(8):
|
||||||
n = i+1
|
n = i+1
|
||||||
t = m.newTemplate()
|
t = mm.newTemplate(_("Cloze") + " %d" % n)
|
||||||
t['name'] = _("Cloze") + " %d" % n
|
|
||||||
t['qfmt'] = ("{{#cloze:%d:Text}}<br>{{cloze:%d:%s}}<br>"+
|
t['qfmt'] = ("{{#cloze:%d:Text}}<br>{{cloze:%d:%s}}<br>"+
|
||||||
"{{/cloze:%d:Text}}") % (n, n, _("Text"), n)
|
"{{/cloze:%d:Text}}") % (n, n, _("Text"), n)
|
||||||
t['afmt'] = ("{{cloze:%d:" + _("Text") + "}}") % n
|
t['afmt'] = ("{{cloze:%d:" + _("Text") + "}}") % n
|
||||||
t['afmt'] += "<br>{{" + _("Notes") + "}}"
|
t['afmt'] += "<br>{{" + _("Notes") + "}}"
|
||||||
m.addTemplate(t)
|
mm.addTemplate(m, t)
|
||||||
|
mm.save(m)
|
||||||
return 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.utils import intTime
|
||||||
from anki.db import DB
|
from anki.db import DB
|
||||||
from anki.deck import _Deck
|
from anki.deck import _Deck
|
||||||
from anki.stdmodels import BasicModel, ClozeModel
|
from anki.stdmodels import addBasicModel, addClozeModel
|
||||||
from anki.errors import AnkiError
|
from anki.errors import AnkiError
|
||||||
from anki.hooks import runHook
|
from anki.hooks import runHook
|
||||||
|
|
||||||
|
@ -34,8 +34,8 @@ def Deck(path, queue=True, lock=True):
|
||||||
_upgradeDeck(deck, ver)
|
_upgradeDeck(deck, ver)
|
||||||
elif create:
|
elif create:
|
||||||
# add in reverse order so basic is default
|
# add in reverse order so basic is default
|
||||||
deck.addModel(ClozeModel(deck))
|
addClozeModel(deck)
|
||||||
deck.addModel(BasicModel(deck))
|
addBasicModel(deck)
|
||||||
deck.save()
|
deck.save()
|
||||||
if lock:
|
if lock:
|
||||||
deck.lock()
|
deck.lock()
|
||||||
|
@ -67,9 +67,9 @@ create table if not exists deck (
|
||||||
lastSync integer not null,
|
lastSync integer not null,
|
||||||
qconf text not null,
|
qconf text not null,
|
||||||
conf text not null,
|
conf text not null,
|
||||||
|
models text not null,
|
||||||
groups text not null,
|
groups text not null,
|
||||||
gconf text not null,
|
gconf text not null
|
||||||
data text not null
|
|
||||||
);
|
);
|
||||||
|
|
||||||
create table if not exists cards (
|
create table if not exists cards (
|
||||||
|
@ -108,16 +108,6 @@ create table if not exists fsums (
|
||||||
csum integer not null
|
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 (
|
create table if not exists graves (
|
||||||
time integer not null,
|
time integer not null,
|
||||||
oid integer not null,
|
oid integer not null,
|
||||||
|
@ -148,13 +138,14 @@ values(1,0,0,0,%(v)s,0,'',0,'','','','','');
|
||||||
import anki.groups
|
import anki.groups
|
||||||
if setDeckConf:
|
if setDeckConf:
|
||||||
db.execute("""
|
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.defaultQconf),
|
||||||
simplejson.dumps(anki.deck.defaultConf),
|
simplejson.dumps(anki.deck.defaultConf),
|
||||||
|
"{}",
|
||||||
simplejson.dumps({'1': {'name': _("Default"), 'conf': 1,
|
simplejson.dumps({'1': {'name': _("Default"), 'conf': 1,
|
||||||
'mod': intTime()}}),
|
'mod': intTime()}}),
|
||||||
simplejson.dumps({'1': anki.groups.defaultConf}),
|
simplejson.dumps({'1': anki.groups.defaultConf}))
|
||||||
"{}")
|
|
||||||
|
|
||||||
def _updateIndices(db):
|
def _updateIndices(db):
|
||||||
"Add indices to the DB."
|
"Add indices to the DB."
|
||||||
|
@ -494,7 +485,7 @@ order by modelId, ordinal"""):
|
||||||
|
|
||||||
def _fixupModels(deck):
|
def _fixupModels(deck):
|
||||||
# rewrite model/template/field ids
|
# rewrite model/template/field ids
|
||||||
models = deck.models()
|
models = deck.models.all()
|
||||||
deck.db.execute("delete from models")
|
deck.db.execute("delete from models")
|
||||||
times = {}
|
times = {}
|
||||||
for c, m in enumerate(models.values()):
|
for c, m in enumerate(models.values()):
|
||||||
|
|
|
@ -171,7 +171,7 @@ def entsToTxt(html):
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
|
||||||
def hexifyID(id):
|
def hexifyID(id):
|
||||||
return "%x" % id
|
return "%x" % int(id)
|
||||||
|
|
||||||
def dehexifyID(id):
|
def dehexifyID(id):
|
||||||
return int(id, 16)
|
return int(id, 16)
|
||||||
|
|
|
@ -12,7 +12,7 @@ def test_genCards():
|
||||||
f['Front'] = u'1'
|
f['Front'] = u'1'
|
||||||
f['Back'] = u'2'
|
f['Back'] = u'2'
|
||||||
deck.addFact(f)
|
deck.addFact(f)
|
||||||
cards = deck.genCards(f, f.model().templates)
|
cards = deck.genCards(f, f.model()['tmpls'])
|
||||||
assert len(cards) == 1
|
assert len(cards) == 1
|
||||||
assert cards[0].ord == 1
|
assert cards[0].ord == 1
|
||||||
assert deck.cardCount() == 2
|
assert deck.cardCount() == 2
|
||||||
|
@ -23,7 +23,7 @@ def test_genCards():
|
||||||
f['Front'] = u'1'
|
f['Front'] = u'1'
|
||||||
f['Back'] = u'2'
|
f['Back'] = u'2'
|
||||||
deck.addFact(f)
|
deck.addFact(f)
|
||||||
cards = deck.genCards(f, f.model().templates)
|
cards = deck.genCards(f, f.model()['tmpls'])
|
||||||
assert deck.cardCount() == 4
|
assert deck.cardCount() == 4
|
||||||
c = deck.db.list("select due from cards where fid = ?", f.id)
|
c = deck.db.list("select due from cards where fid = ?", f.id)
|
||||||
assert c[0] == c[1]
|
assert c[0] == c[1]
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
import os, re, datetime
|
import os, re, datetime
|
||||||
from tests.shared import assertException, getEmptyDeck, testDir
|
from tests.shared import assertException, getEmptyDeck, testDir
|
||||||
from anki.stdmodels import BasicModel
|
from anki.stdmodels import addBasicModel
|
||||||
|
|
||||||
from anki import Deck
|
from anki import Deck
|
||||||
|
|
||||||
|
@ -53,8 +53,8 @@ def test_factAddDelete():
|
||||||
f = deck.newFact()
|
f = deck.newFact()
|
||||||
f['Front'] = u"one"; f['Back'] = u"two"
|
f['Front'] = u"one"; f['Back'] = u"two"
|
||||||
m = f.model()
|
m = f.model()
|
||||||
m.templates[1]['actv'] = True
|
m['tmpls'][1]['actv'] = True
|
||||||
m.flush()
|
deck.models.save(m)
|
||||||
n = deck.addFact(f)
|
n = deck.addFact(f)
|
||||||
assert n == 2
|
assert n == 2
|
||||||
# check q/a generation
|
# check q/a generation
|
||||||
|
@ -65,7 +65,7 @@ def test_factAddDelete():
|
||||||
assert not p
|
assert not p
|
||||||
# now let's make a duplicate and test uniqueness
|
# now let's make a duplicate and test uniqueness
|
||||||
f2 = deck.newFact()
|
f2 = deck.newFact()
|
||||||
f2.model().fields[1]['req'] = True
|
f2.model()['flds'][1]['req'] = True
|
||||||
f2['Front'] = u"one"; f2['Back'] = u""
|
f2['Front'] = u"one"; f2['Back'] = u""
|
||||||
p = f2.problems()
|
p = f2.problems()
|
||||||
assert p[0] == "unique"
|
assert p[0] == "unique"
|
||||||
|
@ -102,15 +102,15 @@ def test_fieldChecksum():
|
||||||
"select csum from fsums") == int("4b0e5a4c", 16)
|
"select csum from fsums") == int("4b0e5a4c", 16)
|
||||||
# turning off unique and modifying the fact should delete the sum
|
# turning off unique and modifying the fact should delete the sum
|
||||||
m = f.model()
|
m = f.model()
|
||||||
m.fields[0]['uniq'] = False
|
m['flds'][0]['uniq'] = False
|
||||||
m.flush()
|
deck.models.save(m)
|
||||||
f.flush()
|
f.flush()
|
||||||
assert deck.db.scalar(
|
assert deck.db.scalar(
|
||||||
"select count() from fsums") == 0
|
"select count() from fsums") == 0
|
||||||
# and turning on both should ensure two checksums generated
|
# and turning on both should ensure two checksums generated
|
||||||
m.fields[0]['uniq'] = True
|
m['flds'][0]['uniq'] = True
|
||||||
m.fields[1]['uniq'] = True
|
m['flds'][1]['uniq'] = True
|
||||||
m.flush()
|
deck.models.save(m)
|
||||||
f.flush()
|
f.flush()
|
||||||
assert deck.db.scalar(
|
assert deck.db.scalar(
|
||||||
"select count() from fsums") == 2
|
"select count() from fsums") == 2
|
||||||
|
@ -190,8 +190,8 @@ def test_addDelTags():
|
||||||
|
|
||||||
def test_timestamps():
|
def test_timestamps():
|
||||||
deck = getEmptyDeck()
|
deck = getEmptyDeck()
|
||||||
assert len(deck.models()) == 2
|
assert len(deck.models.models) == 2
|
||||||
for i in range(100):
|
for i in range(100):
|
||||||
deck.addModel(BasicModel(deck))
|
addBasicModel(deck)
|
||||||
assert len(deck.models()) == 102
|
assert len(deck.models.models) == 102
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,7 @@ def test_findCards():
|
||||||
f = deck.newFact()
|
f = deck.newFact()
|
||||||
f['Front'] = u'template test'
|
f['Front'] = u'template test'
|
||||||
f['Back'] = u'foo bar'
|
f['Back'] = u'foo bar'
|
||||||
f.model().templates[1]['actv'] = True
|
f.model()['tmpls'][1]['actv'] = True
|
||||||
deck.addFact(f)
|
deck.addFact(f)
|
||||||
latestCardIds = [c.id for c in f.cards()]
|
latestCardIds = [c.id for c in f.cards()]
|
||||||
# tag searches
|
# tag searches
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from tests.shared import assertException, getEmptyDeck
|
from tests.shared import assertException, getEmptyDeck
|
||||||
from anki.stdmodels import BasicModel
|
|
||||||
from anki.utils import stripHTML, intTime
|
from anki.utils import stripHTML, intTime
|
||||||
from anki.hooks import addHook
|
from anki.hooks import addHook
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
|
|
||||||
from tests.shared import getEmptyDeck
|
from tests.shared import getEmptyDeck
|
||||||
from anki.models import Model
|
|
||||||
from anki.utils import stripHTML
|
from anki.utils import stripHTML
|
||||||
|
|
||||||
def test_modelDelete():
|
def test_modelDelete():
|
||||||
|
@ -11,20 +10,20 @@ def test_modelDelete():
|
||||||
f['Back'] = u'2'
|
f['Back'] = u'2'
|
||||||
deck.addFact(f)
|
deck.addFact(f)
|
||||||
assert deck.cardCount() == 1
|
assert deck.cardCount() == 1
|
||||||
deck.delModel(deck.conf['currentModelId'])
|
deck.models.del_(deck.models.get(deck.conf['currentModelId']))
|
||||||
assert deck.cardCount() == 0
|
assert deck.cardCount() == 0
|
||||||
|
|
||||||
def test_modelCopy():
|
def test_modelCopy():
|
||||||
deck = getEmptyDeck()
|
deck = getEmptyDeck()
|
||||||
m = deck.currentModel()
|
m = deck.models.current()
|
||||||
m2 = m.copy()
|
m2 = deck.models.copy(m)
|
||||||
assert m2.name == "Basic copy"
|
assert m2['name'] == "Basic copy"
|
||||||
assert m2.id != m.id
|
assert m2['id'] != m['id']
|
||||||
assert len(m2.fields) == 2
|
assert len(m2['flds']) == 2
|
||||||
assert len(m.fields) == 2
|
assert len(m['flds']) == 2
|
||||||
assert len(m2.fields) == len(m.fields)
|
assert len(m2['flds']) == len(m['flds'])
|
||||||
assert len(m.templates) == 2
|
assert len(m['tmpls']) == 2
|
||||||
assert len(m2.templates) == 2
|
assert len(m2['tmpls']) == 2
|
||||||
|
|
||||||
def test_fields():
|
def test_fields():
|
||||||
d = getEmptyDeck()
|
d = getEmptyDeck()
|
||||||
|
@ -32,50 +31,50 @@ def test_fields():
|
||||||
f['Front'] = u'1'
|
f['Front'] = u'1'
|
||||||
f['Back'] = u'2'
|
f['Back'] = u'2'
|
||||||
d.addFact(f)
|
d.addFact(f)
|
||||||
m = d.currentModel()
|
m = d.models.current()
|
||||||
# make sure renaming a field updates the templates
|
# make sure renaming a field updates the templates
|
||||||
m.renameField(m.fields[0], "NewFront")
|
d.models.renameField(m, m['flds'][0], "NewFront")
|
||||||
assert m.templates[0]['qfmt'] == "{{NewFront}}"
|
assert m['tmpls'][0]['qfmt'] == "{{NewFront}}"
|
||||||
# add a field
|
# add a field
|
||||||
f = m.newField()
|
f = d.models.newField(m)
|
||||||
f['name'] = "foo"
|
f['name'] = "foo"
|
||||||
m.addField(f)
|
d.models.addField(m, f)
|
||||||
assert d.getFact(m.fids()[0]).fields == ["1", "2", ""]
|
assert d.getFact(d.models.fids(m)[0]).fields == ["1", "2", ""]
|
||||||
# rename it
|
# rename it
|
||||||
m.renameField(f, "bar")
|
d.models.renameField(m, f, "bar")
|
||||||
assert d.getFact(m.fids()[0])['bar'] == ''
|
assert d.getFact(d.models.fids(m)[0])['bar'] == ''
|
||||||
# delete back
|
# delete back
|
||||||
m.delField(m.fields[1])
|
d.models.delField(m, m['flds'][1])
|
||||||
assert d.getFact(m.fids()[0]).fields == ["1", ""]
|
assert d.getFact(d.models.fids(m)[0]).fields == ["1", ""]
|
||||||
# move 0 -> 1
|
# move 0 -> 1
|
||||||
m.moveField(m.fields[0], 1)
|
d.models.moveField(m, m['flds'][0], 1)
|
||||||
assert d.getFact(m.fids()[0]).fields == ["", "1"]
|
assert d.getFact(d.models.fids(m)[0]).fields == ["", "1"]
|
||||||
# move 1 -> 0
|
# move 1 -> 0
|
||||||
m.moveField(m.fields[1], 0)
|
d.models.moveField(m, m['flds'][1], 0)
|
||||||
assert d.getFact(m.fids()[0]).fields == ["1", ""]
|
assert d.getFact(d.models.fids(m)[0]).fields == ["1", ""]
|
||||||
# add another and put in middle
|
# add another and put in middle
|
||||||
f = m.newField()
|
f = d.models.newField(m)
|
||||||
f['name'] = "baz"
|
f['name'] = "baz"
|
||||||
m.addField(f)
|
d.models.addField(m, f)
|
||||||
f = d.getFact(m.fids()[0])
|
f = d.getFact(d.models.fids(m)[0])
|
||||||
f['baz'] = "2"
|
f['baz'] = "2"
|
||||||
f.flush()
|
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
|
# move 2 -> 1
|
||||||
m.moveField(m.fields[2], 1)
|
d.models.moveField(m, m['flds'][2], 1)
|
||||||
assert d.getFact(m.fids()[0]).fields == ["1", "2", ""]
|
assert d.getFact(d.models.fids(m)[0]).fields == ["1", "2", ""]
|
||||||
# move 0 -> 2
|
# move 0 -> 2
|
||||||
m.moveField(m.fields[0], 2)
|
d.models.moveField(m, m['flds'][0], 2)
|
||||||
assert d.getFact(m.fids()[0]).fields == ["2", "", "1"]
|
assert d.getFact(d.models.fids(m)[0]).fields == ["2", "", "1"]
|
||||||
# move 0 -> 1
|
# move 0 -> 1
|
||||||
m.moveField(m.fields[0], 1)
|
d.models.moveField(m, m['flds'][0], 1)
|
||||||
assert d.getFact(m.fids()[0]).fields == ["", "2", "1"]
|
assert d.getFact(d.models.fids(m)[0]).fields == ["", "2", "1"]
|
||||||
|
|
||||||
def test_templates():
|
def test_templates():
|
||||||
d = getEmptyDeck()
|
d = getEmptyDeck()
|
||||||
m = d.currentModel()
|
m = d.models.current()
|
||||||
m.templates[1]['actv'] = True
|
m['tmpls'][1]['actv'] = True
|
||||||
m.flush()
|
d.models.save(m)
|
||||||
f = d.newFact()
|
f = d.newFact()
|
||||||
f['Front'] = u'1'
|
f['Front'] = u'1'
|
||||||
f['Back'] = u'2'
|
f['Back'] = u'2'
|
||||||
|
@ -86,12 +85,12 @@ def test_templates():
|
||||||
assert c.ord == 0
|
assert c.ord == 0
|
||||||
assert c2.ord == 1
|
assert c2.ord == 1
|
||||||
# switch templates
|
# switch templates
|
||||||
m.moveTemplate(c.template(), 1)
|
d.models.moveTemplate(m, c.template(), 1)
|
||||||
c.load(); c2.load()
|
c.load(); c2.load()
|
||||||
assert c.ord == 1
|
assert c.ord == 1
|
||||||
assert c2.ord == 0
|
assert c2.ord == 0
|
||||||
# removing a template should delete its cards
|
# removing a template should delete its cards
|
||||||
m.delTemplate(m.templates[0])
|
d.models.delTemplate(m, m['tmpls'][0])
|
||||||
assert d.cardCount() == 1
|
assert d.cardCount() == 1
|
||||||
# and should have updated the other cards' ordinals
|
# and should have updated the other cards' ordinals
|
||||||
c = f.cards()[0]
|
c = f.cards()[0]
|
||||||
|
@ -100,9 +99,9 @@ def test_templates():
|
||||||
|
|
||||||
def test_text():
|
def test_text():
|
||||||
d = getEmptyDeck()
|
d = getEmptyDeck()
|
||||||
m = d.currentModel()
|
m = d.models.current()
|
||||||
m.templates[0]['qfmt'] = "{{text:Front}}"
|
m['tmpls'][0]['qfmt'] = "{{text:Front}}"
|
||||||
m.flush()
|
d.models.save(m)
|
||||||
f = d.newFact()
|
f = d.newFact()
|
||||||
f['Front'] = u'hello<b>world'
|
f['Front'] = u'hello<b>world'
|
||||||
d.addFact(f)
|
d.addFact(f)
|
||||||
|
@ -110,9 +109,9 @@ def test_text():
|
||||||
|
|
||||||
def test_cloze():
|
def test_cloze():
|
||||||
d = getEmptyDeck()
|
d = getEmptyDeck()
|
||||||
d.conf['currentModelId'] = d.modelId("Cloze")
|
d.conf['currentModelId'] = d.models.byName("Cloze")['id']
|
||||||
f = d.newFact()
|
f = d.newFact()
|
||||||
assert f.model().name == "Cloze"
|
assert f.model()['name'] == "Cloze"
|
||||||
# a cloze model with no clozes is empty
|
# a cloze model with no clozes is empty
|
||||||
f['Text'] = u'nothing'
|
f['Text'] = u'nothing'
|
||||||
assert d.addFact(f) == 0
|
assert d.addFact(f) == 0
|
||||||
|
@ -124,7 +123,7 @@ def test_cloze():
|
||||||
assert "<span class=cloze>world</span>" in f.cards()[0].a()
|
assert "<span class=cloze>world</span>" in f.cards()[0].a()
|
||||||
assert "hello <span class=cloze>world</span>" not in f.cards()[0].a()
|
assert "hello <span class=cloze>world</span>" not in f.cards()[0].a()
|
||||||
# check context works too
|
# 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()
|
assert "hello <span class=cloze>world</span>" in f.cards()[0].a()
|
||||||
# and with a comment
|
# and with a comment
|
||||||
f = d.newFact()
|
f = d.newFact()
|
||||||
|
@ -143,16 +142,16 @@ def test_cloze():
|
||||||
assert "world <span class=cloze>bar</span>" in c2.a()
|
assert "world <span class=cloze>bar</span>" in c2.a()
|
||||||
# if there are multiple answers for a single cloze, they are given in a
|
# if there are multiple answers for a single cloze, they are given in a
|
||||||
# list
|
# list
|
||||||
f.model().conf['clozectx'] = False
|
f.model()['clozectx'] = False
|
||||||
f = d.newFact()
|
f = d.newFact()
|
||||||
f['Text'] = "a {{c1::b}} {{c1::c}}"
|
f['Text'] = "a {{c1::b}} {{c1::c}}"
|
||||||
assert d.addFact(f) == 1
|
assert d.addFact(f) == 1
|
||||||
assert "<span class=cloze>b</span>, <span class=cloze>c</span>" in (
|
assert "<span class=cloze>b</span>, <span class=cloze>c</span>" in (
|
||||||
f.cards()[0].a())
|
f.cards()[0].a())
|
||||||
# clozes should be supported in sections too
|
# clozes should be supported in sections too
|
||||||
m = d.currentModel()
|
m = d.models.current()
|
||||||
m.templates[0]['qfmt'] = "{{#cloze:1:Text}}{{Notes}}{{/cloze:1:Text}}"
|
m['tmpls'][0]['qfmt'] = "{{#cloze:1:Text}}{{Notes}}{{/cloze:1:Text}}"
|
||||||
m.flush()
|
d.models.save(m)
|
||||||
f = d.newFact()
|
f = d.newFact()
|
||||||
f['Text'] = "hello"
|
f['Text'] = "hello"
|
||||||
f['Notes'] = "world"
|
f['Notes'] = "world"
|
||||||
|
@ -162,18 +161,18 @@ def test_cloze():
|
||||||
|
|
||||||
def test_modelChange():
|
def test_modelChange():
|
||||||
deck = getEmptyDeck()
|
deck = getEmptyDeck()
|
||||||
basic = deck.getModel(deck.modelId("Basic"))
|
basic = deck.models.byName("Basic")
|
||||||
cloze = deck.getModel(deck.modelId("Cloze"))
|
cloze = deck.models.byName("Cloze")
|
||||||
# enable second template and add a fact
|
# enable second template and add a fact
|
||||||
basic.templates[1]['actv'] = True
|
basic['tmpls'][1]['actv'] = True
|
||||||
basic.flush()
|
deck.models.save(basic)
|
||||||
f = deck.newFact()
|
f = deck.newFact()
|
||||||
f['Front'] = u'f'
|
f['Front'] = u'f'
|
||||||
f['Back'] = u'b'
|
f['Back'] = u'b'
|
||||||
deck.addFact(f)
|
deck.addFact(f)
|
||||||
# switch fields
|
# switch fields
|
||||||
map = {0: 1, 1: 0}
|
map = {0: 1, 1: 0}
|
||||||
basic.changeModel([f.id], basic, map, None)
|
deck.models.change(basic, [f.id], basic, map, None)
|
||||||
f.load()
|
f.load()
|
||||||
assert f['Front'] == 'b'
|
assert f['Front'] == 'b'
|
||||||
assert f['Back'] == 'f'
|
assert f['Back'] == 'f'
|
||||||
|
@ -184,7 +183,7 @@ def test_modelChange():
|
||||||
assert stripHTML(c1.q()) == "f"
|
assert stripHTML(c1.q()) == "f"
|
||||||
assert c0.ord == 0
|
assert c0.ord == 0
|
||||||
assert c1.ord == 1
|
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()
|
f.load(); c0.load(); c1.load()
|
||||||
assert stripHTML(c0.q()) == "f"
|
assert stripHTML(c0.q()) == "f"
|
||||||
assert stripHTML(c1.q()) == "b"
|
assert stripHTML(c1.q()) == "b"
|
||||||
|
@ -194,7 +193,7 @@ def test_modelChange():
|
||||||
assert f.cards()[0].id == c1.id
|
assert f.cards()[0].id == c1.id
|
||||||
# delete first card
|
# delete first card
|
||||||
map = {0: None, 1: 1}
|
map = {0: None, 1: 1}
|
||||||
basic.changeModel([f.id], basic, None, map)
|
deck.models.change(basic, [f.id], basic, None, map)
|
||||||
f.load()
|
f.load()
|
||||||
c0.load()
|
c0.load()
|
||||||
try:
|
try:
|
||||||
|
@ -206,7 +205,7 @@ def test_modelChange():
|
||||||
# an unmapped field becomes blank
|
# an unmapped field becomes blank
|
||||||
assert f['Front'] == 'b'
|
assert f['Front'] == 'b'
|
||||||
assert f['Back'] == 'f'
|
assert f['Back'] == 'f'
|
||||||
basic.changeModel([f.id], basic, map, None)
|
deck.models.change(basic, [f.id], basic, map, None)
|
||||||
f.load()
|
f.load()
|
||||||
assert f['Front'] == ''
|
assert f['Front'] == ''
|
||||||
assert f['Back'] == 'f'
|
assert f['Back'] == 'f'
|
||||||
|
@ -215,12 +214,21 @@ def test_modelChange():
|
||||||
f['Front'] = u'f2'
|
f['Front'] = u'f2'
|
||||||
f['Back'] = u'b2'
|
f['Back'] = u'b2'
|
||||||
deck.addFact(f)
|
deck.addFact(f)
|
||||||
assert basic.useCount() == 2
|
assert deck.models.useCount(basic) == 2
|
||||||
assert cloze.useCount() == 0
|
assert deck.models.useCount(cloze) == 0
|
||||||
map = {0: 0, 1: 1}
|
map = {0: 0, 1: 1}
|
||||||
basic.changeModel([f.id], cloze, map, map)
|
deck.models.change(basic, [f.id], cloze, map, map)
|
||||||
f.load()
|
f.load()
|
||||||
assert f['Text'] == "f2"
|
assert f['Text'] == "f2"
|
||||||
assert f['Notes'] == "b2"
|
assert f['Notes'] == "b2"
|
||||||
assert len(f.cards()) == 2
|
assert len(f.cards()) == 2
|
||||||
assert "b2" in f.cards()[0].a()
|
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
|
import time, copy
|
||||||
from tests.shared import assertException, getEmptyDeck
|
from tests.shared import assertException, getEmptyDeck
|
||||||
from anki.stdmodels import BasicModel
|
|
||||||
from anki.utils import stripHTML, intTime
|
from anki.utils import stripHTML, intTime
|
||||||
from anki.hooks import addHook
|
from anki.hooks import addHook
|
||||||
|
|
||||||
|
@ -32,9 +31,9 @@ def test_new():
|
||||||
assert c.due >= t
|
assert c.due >= t
|
||||||
# the default order should ensure siblings are not seen together, and
|
# the default order should ensure siblings are not seen together, and
|
||||||
# should show all cards
|
# should show all cards
|
||||||
m = d.currentModel()
|
m = d.models.current()
|
||||||
m.templates[1]['actv'] = True
|
m['tmpls'][1]['actv'] = True
|
||||||
m.flush()
|
d.models.save(m)
|
||||||
f = d.newFact()
|
f = d.newFact()
|
||||||
f['Front'] = u"2"; f['Back'] = u"2"
|
f['Front'] = u"2"; f['Back'] = u"2"
|
||||||
d.addFact(f)
|
d.addFact(f)
|
||||||
|
@ -50,15 +49,15 @@ def test_new():
|
||||||
|
|
||||||
def test_newOrder():
|
def test_newOrder():
|
||||||
d = getEmptyDeck()
|
d = getEmptyDeck()
|
||||||
m = d.currentModel()
|
m = d.models.current()
|
||||||
for i in range(50):
|
for i in range(50):
|
||||||
t = m.newTemplate()
|
t = d.models.newTemplate(m)
|
||||||
t['name'] = str(i)
|
t['name'] = str(i)
|
||||||
t['qfmt'] = "{{Front}}"
|
t['qfmt'] = "{{Front}}"
|
||||||
t['afmt'] = "{{Back}}"
|
t['afmt'] = "{{Back}}"
|
||||||
t['actv'] = i > 25
|
t['actv'] = i > 25
|
||||||
m.addTemplate(t)
|
d.models.addTemplate(m, t)
|
||||||
m.flush()
|
d.models.save(m)
|
||||||
f = d.newFact()
|
f = d.newFact()
|
||||||
f['Front'] = u'1'
|
f['Front'] = u'1'
|
||||||
f['Back'] = u'2'
|
f['Back'] = u'2'
|
||||||
|
@ -495,19 +494,19 @@ def test_cramLimits():
|
||||||
def test_adjIvl():
|
def test_adjIvl():
|
||||||
d = getEmptyDeck()
|
d = getEmptyDeck()
|
||||||
# add two more templates and set second active
|
# add two more templates and set second active
|
||||||
m = d.currentModel()
|
m = d.models.current()
|
||||||
m.templates[1]['actv'] = True
|
m['tmpls'][1]['actv'] = True
|
||||||
t = m.newTemplate()
|
t = d.models.newTemplate(m)
|
||||||
t['name'] = "f2"
|
t['name'] = "f2"
|
||||||
t['qfmt'] = "{{Front}}"
|
t['qfmt'] = "{{Front}}"
|
||||||
t['afmt'] = "{{Back}}"
|
t['afmt'] = "{{Back}}"
|
||||||
m.addTemplate(t)
|
d.models.addTemplate(m, t)
|
||||||
t = m.newTemplate()
|
t = d.models.newTemplate(m)
|
||||||
t['name'] = "f3"
|
t['name'] = "f3"
|
||||||
t['qfmt'] = "{{Front}}"
|
t['qfmt'] = "{{Front}}"
|
||||||
t['afmt'] = "{{Back}}"
|
t['afmt'] = "{{Back}}"
|
||||||
m.addTemplate(t)
|
d.models.addTemplate(m, t)
|
||||||
m.flush()
|
d.models.save(m)
|
||||||
# create a new fact; it should have 4 cards
|
# create a new fact; it should have 4 cards
|
||||||
f = d.newFact()
|
f = d.newFact()
|
||||||
f['Front'] = "1"; f['Back'] = "1"
|
f['Front'] = "1"; f['Back'] = "1"
|
||||||
|
@ -560,14 +559,14 @@ def test_adjIvl():
|
||||||
def test_ordcycle():
|
def test_ordcycle():
|
||||||
d = getEmptyDeck()
|
d = getEmptyDeck()
|
||||||
# add two more templates and set second active
|
# add two more templates and set second active
|
||||||
m = d.currentModel()
|
m = d.models.current()
|
||||||
m.templates[1]['actv'] = True
|
m['tmpls'][1]['actv'] = True
|
||||||
t = m.newTemplate()
|
t = d.models.newTemplate(m)
|
||||||
t['name'] = "f2"
|
t['name'] = "f2"
|
||||||
t['qfmt'] = "{{Front}}"
|
t['qfmt'] = "{{Front}}"
|
||||||
t['afmt'] = "{{Back}}"
|
t['afmt'] = "{{Back}}"
|
||||||
m.addTemplate(t)
|
d.models.addTemplate(m, t)
|
||||||
m.flush()
|
d.models.save(m)
|
||||||
# create a new fact; it should have 4 cards
|
# create a new fact; it should have 4 cards
|
||||||
f = d.newFact()
|
f = d.newFact()
|
||||||
f['Front'] = "1"; f['Back'] = "1"
|
f['Front'] = "1"; f['Back'] = "1"
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
import time, copy, os
|
import time, copy, os
|
||||||
from tests.shared import assertException, getEmptyDeck
|
from tests.shared import assertException, getEmptyDeck
|
||||||
from anki.stdmodels import BasicModel
|
|
||||||
from anki.utils import stripHTML, intTime
|
from anki.utils import stripHTML, intTime
|
||||||
from anki.hooks import addHook
|
from anki.hooks import addHook
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,6 @@ from tests.shared import assertException
|
||||||
from anki.errors import *
|
from anki.errors import *
|
||||||
from anki import Deck
|
from anki import Deck
|
||||||
from anki.utils import intTime
|
from anki.utils import intTime
|
||||||
from anki.stdmodels import BasicModel
|
|
||||||
from anki.sync import SyncClient, SyncServer, HttpSyncServer, HttpSyncServerProxy
|
from anki.sync import SyncClient, SyncServer, HttpSyncServer, HttpSyncServerProxy
|
||||||
from anki.sync import copyLocalMedia
|
from anki.sync import copyLocalMedia
|
||||||
from anki.facts import Fact
|
from anki.facts import Fact
|
||||||
|
|
|
@ -2,8 +2,6 @@
|
||||||
|
|
||||||
import time
|
import time
|
||||||
from tests.shared import assertException, getEmptyDeck
|
from tests.shared import assertException, getEmptyDeck
|
||||||
from anki.stdmodels import BasicModel
|
|
||||||
|
|
||||||
def test_op():
|
def test_op():
|
||||||
d = getEmptyDeck()
|
d = getEmptyDeck()
|
||||||
# should have no undo by default
|
# should have no undo by default
|
||||||
|
|
Loading…
Reference in a new issue