diff --git a/anki/cards.py b/anki/cards.py
index c94cd14ee..860be6126 100644
--- a/anki/cards.py
+++ b/anki/cards.py
@@ -112,10 +112,8 @@ streak=?, lapses=?, grade=?, cycles=? where id = ?""",
def _getQA(self, reload=False):
# this is a hack at the moment
if not self._qa or reload:
- self._qa = self.deck.formatQA(
- self.id,
- self.deck._cacheFacts([self.fid])[self.fid],
- self.deck._cacheMeta("and c.id = %d" % self.id)[2][self.id])
+ self._qa = self.deck.updateCache(
+ [self.id], "card")[0]
return self._qa
def fact(self):
diff --git a/anki/deck.py b/anki/deck.py
index 84801de8d..becaa89cd 100644
--- a/anki/deck.py
+++ b/anki/deck.py
@@ -148,6 +148,11 @@ qconf=?, conf=?, data=?""",
# unsorted
##########################################################################
+ def nextID(self, type):
+ id = self.conf.get(type, 1)
+ self.conf[type] = id+1
+ return id
+
def reset(self):
self.sched.reset()
# recache css
@@ -500,14 +505,12 @@ due > :now and due < :now""", now=time.time())
ok = []
for template in fact.model.templates:
if template.active or not checkActive:
- # [cid, fid, qfmt, afmt, tags, model, template, group]
- meta = [None, template.qfmt, template.afmt,
- "", "", "", ""]
- fields = fact.fieldsWithIds()
- now = self.formatQA(None, fields, meta, False)
- for k in fields.keys():
- fields[k] = (fields[k][0], "")
- empty = self.formatQA(None, fields, meta, False)
+ # [cid, fid, mid, tid, gid, tags, flds, data]
+ data = [1, 1, fact.model.id, template.id, 1,
+ "", fact.joinedFields(), ""]
+ now = self.formatQA(fact.model, template, "", data)
+ data[6] = "\x1f".join([""]*len(fact._fields))
+ empty = self.formatQA(fact.model, template, "", data)
if now['q'] == empty['q']:
continue
if not template.conf['allowEmptyAns']:
@@ -557,7 +560,7 @@ where fid = :fid and tid = :cmid""",
return
strids = ids2str(ids)
self.db.execute("delete from facts where id in %s" % strids)
- self.db.execute("delete from fdata where fid in %s" % strids)
+ #self.db.execute("delete from fdata where fid in %s" % strids)
def _deleteDanglingFacts(self):
"Delete any facts without cards. Don't call this directly."
@@ -659,7 +662,6 @@ select id from cards where fid in (select id from facts where mid = ?)""",
# then the model
self.db.execute("delete from models where id = ?", mid)
self.db.execute("delete from templates where mid = ?", mid)
- self.db.execute("delete from fields where mid = ?", mid)
# GUI should ensure last model is not deleted
if self.conf['currentModelId'] == mid:
self.conf['currentModelId'] = self.db.scalar(
@@ -904,7 +906,7 @@ where tid in %s""" % strids, now=time.time())
# Caches: q/a, facts.cache and fdata.csum
##########################################################################
- def updateCache(self, ids, type="card"):
+ def updateCache(self, ids=None, type="card"):
"Update cache after facts or models changed."
# gather metadata
if type == "card":
@@ -913,87 +915,62 @@ where tid in %s""" % strids, now=time.time())
where = "and f.id in " + ids2str(ids)
elif type == "model":
where = "and m.id in " + ids2str(ids)
- (cids, fids, meta) = self._cacheMeta(where)
- if not cids:
- return
- # and fact info
- facts = self._cacheFacts(fids)
- # generate q/a
- pend = [self.formatQA(cids[n], facts[fids[n]], meta[cids[n]])
- for n in range(len(cids))]
- for p in pend:
- self.media.registerText(p['q'])
- self.media.registerText(p['a'])
- # fact value cache
- self._updateFieldCache(facts)
- # and checksum
- self._updateFieldChecksums(facts)
+ elif type == "all":
+ where = ""
+ else:
+ raise Exception()
+ mods = {}
+ templs = {}
+ for m in self.allModels():
+ mods[m.id] = m
+ for t in m.templates:
+ templs[t.id] = t
+ groups = dict(self.db.all("select id, name from groups"))
+ return [self.formatQA(mods[row[2]], templs[row[3]], groups[row[4]], row)
+ for row in self._qaData(where)]
+ # # and checksum
+ # self._updateFieldChecksums(facts)
- def formatQA(self, cardId, fact, meta, filters=True):
+ def formatQA(self, model, template, gname, data, filters=True):
"Returns hash of id, question, answer."
- d = {'id': cardId}
+ # data is [cid, fid, mid, tid, gid, tags, flds, data]
+ # unpack fields and create dict
+ flist = data[6].split("\x1f")
fields = {}
- tags = None
- for (k, v) in fact.items():
- if k == None:
- tags = v[1]
- continue
- fields["text:"+k] = stripHTML(v[1])
- if v[1]:
- fields[k] = '%s' % (
- hexifyID(v[0]), v[1])
+ for (name, (idx, conf)) in model.fieldMap().items():
+ fields[name] = flist[idx]
+ fields["text:"+name] = stripHTML(fields[name])
+ if fields[name]:
+ fields["text:"+name] = stripHTML(fields[name])
+ fields[name] = '%s' % (
+ hexifyID(data[2]), hexifyID(idx), fields[name])
else:
- fields[k] = u""
- fields['Tags'] = tags
- fields['Model'] = meta[3]
- fields['Template'] = meta[4]
- fields['Group'] = meta[5]
+ fields["text:"+name] = ""
+ fields[name] = ""
+ fields['Tags'] = data[5]
+ fields['Model'] = model.name
+ fields['Template'] = template.name
+ fields['Group'] = gname
# render q & a
- for (type, format) in (("q", meta[1]), ("a", meta[2])):
- if filters:
- fields = runFilter("formatQA.pre", fields, meta, self)
+ d = dict(id=data[0])
+ for (type, format) in (("q", template.qfmt), ("a", template.afmt)):
+ # if filters:
+ # fields = runFilter("formatQA.pre", fields, , self)
html = anki.template.render(format, fields)
- if filters:
- d[type] = runFilter("formatQA.post", html, fields, meta, self)
+ # if filters:
+ # d[type] = runFilter("formatQA.post", html, fields, meta, self)
+ self.media.registerText(html)
d[type] = html
return d
- def _cacheMeta(self, where=""):
- "Return cids, fids, and cid -> data hash."
- # data is [fid, qfmt, afmt, model, template, group]
- meta = {}
- cids = []
- fids = []
- for r in self.db.execute("""
-select c.id, f.id, t.qfmt, t.afmt, m.name, t.name, g.name
-from cards c, facts f, models m, templates t, groups g where
-c.fid == f.id and f.mid == m.id and
+ def _qaData(self, where=""):
+ "Return [cid, fid, mid, tid, gid, tags, flds, data] db query"
+ return self.db.execute("""
+select c.id, f.id, m.id, t.id, g.id, f.tags, f.flds, f.data
+from cards c, facts f, models m, templates t, groups g
+where c.fid == f.id and f.mid == m.id and
c.tid = t.id and c.gid = g.id
-%s""" % where):
- meta[r[0]] = r[1:]
- cids.append(r[0])
- fids.append(r[1])
- return (cids, fids, meta)
-
- def _cacheFacts(self, ids):
- "Return a hash of fid -> (name -> (id, val))."
- facts = {}
- for id, fields in groupby(self.db.all("""
-select fdata.fid, fields.name, fields.id, fdata.val
-from fdata left outer join fields on fdata.fmid = fields.id
-where fdata.fid in %s order by fdata.fid""" % ids2str(ids)), itemgetter(0)):
- facts[id] = dict([(f[1], f[2:]) for f in fields])
- return facts
-
- def _updateFieldCache(self, facts):
- "Add stripped HTML cache for searching."
- r = []
- from anki.utils import stripHTMLMedia
- [r.append((stripHTMLMedia(
- " ".join([x[1] for x in map.values()])), id))
- for (id, map) in facts.items()]
- self.db.executemany(
- "update facts set cache=? where id=?", r)
+%s""" % where)
def _updateFieldChecksums(self, facts):
print "benchmark updatefieldchecksums"
@@ -1055,26 +1032,23 @@ insert or ignore into tags (mod, name) values (%d, :t)""" % intTime(),
self.registerTags(newTags)
# find facts missing the tags
if add:
- l = "val not "
+ l = "tags not "
fn = addTags
else:
- l = "val "
+ l = "tags "
fn = deleteTags
lim = " or ".join(
[l+"like :_%d" % c for c, t in enumerate(newTags)])
res = self.db.all(
- "select fid, val from fdata where ord = -1 and " + lim,
+ "select id, tags from facts where " + lim,
**dict([("_%d" % x, '%% %s %%' % y) for x, y in enumerate(newTags)]))
# update tags
fids = []
def fix(row):
fids.append(row[0])
- return {'id': row[0], 't': fn(tags, row[1])}
+ return {'id': row[0], 't': fn(tags, row[1]), 'n':intTime()}
self.db.executemany("""
-update fdata set val = :t
-where fid = :id""", [fix(row) for row in res])
- self.db.execute("update facts set mod = ? where id in " +
- ids2str(fids), intTime())
+update facts set tags = :t, mod = :n where id = :id""", [fix(row) for row in res])
# update q/a cache
self.updateCache(fids, type="fact")
self.finishProgress()
diff --git a/anki/facts.py b/anki/facts.py
index 605fbf08d..93860d83c 100644
--- a/anki/facts.py
+++ b/anki/facts.py
@@ -24,40 +24,46 @@ class Fact(object):
self.tags = ""
self.cache = ""
self._fields = [""] * len(self.model.fields)
+ self.data = ""
self._fmap = self.model.fieldMap()
def load(self):
(self.mid,
self.crt,
- self.mod) = self.deck.db.first("""
-select mid, crt, mod from facts where id = ?""", self.id)
- self._fields = self.deck.db.list("""
-select val from fdata where fid = ? and fmid order by ord""", self.id)
- self.tags = self.deck.db.scalar("""
-select val from fdata where fid = ? and ord = -1""", self.id)
+ self.mod,
+ self.tags,
+ self._fields,
+ self.data) = self.deck.db.first("""
+select mid, crt, mod, tags, flds, data from facts where id = ?""", self.id)
+ self._fields = self._field.split("\x1f")
self.model = self.deck.getModel(self.mid)
def flush(self, cache=True):
self.mod = intTime()
# facts table
- self.cache = stripHTMLMedia(u" ".join(self._fields))
+ sfld = self._fields[self.model.sortField()]
res = self.deck.db.execute("""
-insert or replace into facts values (?, ?, ?, ?, ?)""",
- self.id, self.mid, self.crt,
- self.mod, self.cache)
+insert or replace into facts values (?, ?, ?, ?, ?, ?, ?, ?)""",
+ self.id, self.mid, self.crt,
+ self.mod, self.tags, self.joinedFields(),
+ sfld, self.data)
self.id = res.lastrowid
- # fdata table
- self.deck.db.execute("delete from fdata where fid = ?", self.id)
- d = []
- for (fmid, ord, conf) in self._fmap.values():
- val = self._fields[ord]
- d.append(dict(fid=self.id, fmid=fmid, ord=ord,
- val=val))
- d.append(dict(fid=self.id, fmid=0, ord=-1, val=self.tags))
- self.deck.db.executemany("""
-insert into fdata values (:fid, :fmid, :ord, :val, '')""", d)
- # media and caches
- self.deck.updateCache([self.id], "fact")
+
+ def joinedFields(self):
+ return "\x1f".join(self._fields)
+
+# # fdata table
+# self.deck.db.execute("delete from fdata where fid = ?", self.id)
+# d = []
+# for (fmid, ord, conf) in self._fmap.values():
+# val = self._fields[ord]
+# d.append(dict(fid=self.id, fmid=fmid, ord=ord,
+# val=val))
+# d.append(dict(fid=self.id, fmid=0, ord=-1, val=self.tags))
+# self.deck.db.executemany("""
+# insert into fdata values (:fid, :fmid, :ord, :val, '')""", d)
+# # media and caches
+# self.deck.updateCache([self.id], "fact")
def cards(self):
return [self.deck.getCard(id) for id in self.deck.db.list(
@@ -73,12 +79,12 @@ insert into fdata values (:fid, :fmid, :ord, :val, '')""", d)
return self._fields
def items(self):
- return [(k, self._fields[v])
+ return [(k, self._fields[v[0]])
for (k, v) in self._fmap.items()]
def _fieldOrd(self, key):
try:
- return self._fmap[key][1]
+ return self._fmap[key][0]
except:
raise KeyError(key)
@@ -88,10 +94,6 @@ insert into fdata values (:fid, :fmid, :ord, :val, '')""", d)
def __setitem__(self, key, value):
self._fields[self._fieldOrd(key)] = value
- def fieldsWithIds(self):
- return dict(
- [(k, (v[0], self[k])) for (k,v) in self._fmap.items()])
-
# Tags
##################################################
@@ -105,12 +107,11 @@ insert into fdata values (:fid, :fmid, :ord, :val, '')""", d)
##################################################
def fieldUnique(self, name):
- (fmid, ord, conf) = self._fmap[name]
- if not conf['unique']:
+ (ord, conf) = self._fmap[name]
+ if not conf['uniq']:
return True
val = self[name]
csum = fieldChecksum(val)
- print "in check, ", self.id
if self.id:
lim = "and fid != :fid"
else:
@@ -120,18 +121,18 @@ insert into fdata values (:fid, :fmid, :ord, :val, '')""", d)
c=csum, v=val, fid=self.id)
def fieldComplete(self, name, text=None):
- (fmid, ord, conf) = self._fmap[name]
- if not conf['required']:
+ (ord, conf) = self._fmap[name]
+ if not conf['req']:
return True
return self[name]
def problems(self):
d = []
- for k in self._fmap.keys():
+ for (k, (ord, conf)) in self._fmap.items():
if not self.fieldUnique(k):
- d.append("unique")
+ d.append((ord, "unique"))
elif not self.fieldComplete(k):
- d.append("required")
+ d.append((ord, "required"))
else:
- d.append(None)
- return d
+ d.append((ord, None))
+ return [x[1] for x in sorted(d)]
diff --git a/anki/find.py b/anki/find.py
index 4950ef322..3923df13c 100644
--- a/anki/find.py
+++ b/anki/find.py
@@ -400,8 +400,7 @@ def _findCards(deck, query):
tquery += "select id from facts except "
if token == "none":
tquery += """
-select id from cards where fid in (select fid from fdata where ord = -1 and
-val = ''"""
+select id from cards where fid in (select id from facts where tags = '')"""
else:
token = token.replace("*", "%")
if not token.startswith("%"):
@@ -410,7 +409,7 @@ val = ''"""
token += " %"
args["_tag_%d" % c] = token
tquery += """
-select fid from fdata where ord = -1 and val like :_tag_%d""" % c
+select id from facts where tags like :_tag_%d""" % c
elif type == SEARCH_TYPE:
if qquery:
if isNeg:
@@ -549,7 +548,7 @@ select id from cards where answer like :_ff_%d escape '\\'""" % c
token = token.replace("*", "%")
args["_ff_%d" % c] = "%"+token+"%"
fquery += """
-select id from facts where cache like :_ff_%d escape '\\'""" % c
+select id from facts where flds like :_ff_%d escape '\\'""" % c
return (tquery, fquery, qquery, fidquery, cmquery, sfquery,
qaquery, showdistinct, filters, args)
diff --git a/anki/media.py b/anki/media.py
index 6472dfeff..053deda70 100644
--- a/anki/media.py
+++ b/anki/media.py
@@ -177,11 +177,7 @@ If a file with the same name exists, return a unique name."""
return unicodedata.normalize('NFD', s)
return s
# generate q/a and look through all references
- (cids, fids, meta) = self.deck._cacheMeta()
- facts = self.deck._cacheFacts(fids)
- pend = [self.deck.formatQA(cids[n], facts[fids[n]], meta[cids[n]])
- for n in range(len(cids))]
- for p in pend:
+ for p in self.deck.updateCache(type="all"):
for type in ("q", "a"):
for f in self.mediaFiles(p[type]):
normrefs[norm(f)] = True
diff --git a/anki/models.py b/anki/models.py
index ecafdc01e..31813218c 100644
--- a/anki/models.py
+++ b/anki/models.py
@@ -2,12 +2,6 @@
# Copyright: Damien Elmes
# License: GNU GPL, version 3 or later; http://www.gnu.org/copyleft/gpl.html
-"""\
-Models load their templates and fields when they are loaded. If you update a
-template or field, you should call model.flush(), rather than trying to save
-the subobject directly.
-"""
-
import simplejson
from anki.utils import intTime
from anki.lang import _
@@ -36,20 +30,21 @@ class Model(object):
def load(self):
(self.mod,
self.name,
+ self.fields,
self.conf) = self.deck.db.first("""
-select mod, name, conf from models where id = ?""", self.id)
+select mod, name, flds, conf from models where id = ?""", self.id)
+ self.fields = simplejson.loads(self.fields)
self.conf = simplejson.loads(self.conf)
- self.loadFields()
self.loadTemplates()
def flush(self):
self.mod = intTime()
ret = self.deck.db.execute("""
-insert or replace into models values (?, ?, ?, ?)""",
- self.id, self.mod, self.name,
- simplejson.dumps(self.conf))
+insert or replace into models values (?, ?, ?, ?, ?)""",
+ self.id, self.mod, self.name,
+ simplejson.dumps(self.fields),
+ simplejson.dumps(self.conf))
self.id = ret.lastrowid
- [f._flush() for f in self.fields]
[t._flush() for t in self.templates]
def updateCache(self):
@@ -64,20 +59,19 @@ insert or replace into models values (?, ?, ?, ?)""",
# Fields
##################################################
- def loadFields(self):
- sql = "select * from fields where mid = ? order by ord"
- self.fields = [Field(self.deck, data)
- for data in self.deck.db.all(sql, self.id)]
+ def newField(self):
+ return defaultFieldConf.copy()
def addField(self, field):
self.deck.modSchema()
- field.mid = self._getID()
- field.ord = len(self.fields)
self.fields.append(field)
def fieldMap(self):
- "Mapping of field name -> (fmid, ord)."
- return dict([(f.name, (f.id, f.ord, f.conf)) for f in self.fields])
+ "Mapping of field name -> (ord, conf)."
+ return dict([(f['name'], (c, f)) for c, f in enumerate(self.fields)])
+
+ def sortField(self):
+ return 0
# Templates
##################################################
@@ -101,65 +95,33 @@ insert or replace into models values (?, ?, ?, ?)""",
new = Model(self.deck, self.id)
new.id = None
new.name += _(" copy")
+ new.fields = [f.copy() for f in self.fields]
# get new id
- f = new.fields; new.fields = []
t = new.templates; new.templates = []
new.flush()
# then put back
- new.fields = f
new.templates = t
- for f in new.fields:
- f.id = None
- f.mid = new.id
- f._flush()
for t in new.templates:
t.id = None
t.mid = new.id
t._flush()
return new
-# Field model object
+# Field object
##########################################################################
defaultFieldConf = {
- 'rtl': False, # features
- 'required': False,
- 'unique': False,
+ 'name': "",
+ 'rtl': False,
+ 'req': False,
+ 'uniq': False,
'font': "Arial",
- 'quizSize': 20,
- 'editSize': 20,
- 'quizColour': "#fff",
+ 'qsize': 20,
+ 'esize': 20,
+ 'qcol': "#fff",
'pre': True,
}
-class Field(object):
-
- def __init__(self, deck, data=None):
- self.deck = deck
- if data:
- self.initFromData(data)
- else:
- self.id = None
- self.numeric = 0
- self.conf = defaultFieldConf.copy()
-
- def initFromData(self, data):
- (self.id,
- self.mid,
- self.ord,
- self.name,
- self.numeric,
- self.conf) = data
- self.conf = simplejson.loads(self.conf)
-
- def _flush(self):
- ret = self.deck.db.execute("""
-insert or replace into fields values (?, ?, ?, ?, ?, ?)""",
- self.id, self.mid, self.ord,
- self.name, self.numeric,
- simplejson.dumps(self.conf))
- self.id = ret.lastrowid
-
# Template object
##########################################################################
diff --git a/anki/stdmodels.py b/anki/stdmodels.py
index e3658c784..74317e968 100644
--- a/anki/stdmodels.py
+++ b/anki/stdmodels.py
@@ -2,7 +2,7 @@
# Copyright: Damien Elmes
# License: GNU GPL, version 3 or later; http://www.gnu.org/copyleft/gpl.html
-from anki.models import Model, Template, Field
+from anki.models import Model, Template
from anki.lang import _
models = []
@@ -13,13 +13,13 @@ models = []
def BasicModel(deck):
m = Model(deck)
m.name = _("Basic")
- fm = Field(deck)
- fm.name = _("Front")
- fm.conf['required'] = True
- fm.conf['unique'] = True
+ fm = m.newField()
+ fm['name'] = _("Front")
+ fm['req'] = True
+ fm['uniq'] = True
m.addField(fm)
- fm = Field(deck)
- fm.name = _("Back")
+ fm = m.newField()
+ fm['name'] = _("Back")
m.addField(fm)
t = Template(deck)
t.name = _("Forward")
diff --git a/anki/storage.py b/anki/storage.py
index 758fdc6ab..457d46ab7 100644
--- a/anki/storage.py
+++ b/anki/storage.py
@@ -87,22 +87,17 @@ create table if not exists facts (
mid integer not null,
crt integer not null,
mod integer not null,
- cache text not null
+ tags text not null,
+ flds text not null,
+ sfld text not null,
+ data text not null
);
create table if not exists models (
id integer primary key,
mod integer not null,
name text not null,
- conf text not null
-);
-
-create table if not exists fields (
- id integer primary key,
- mid integer not null,
- ord integer not null,
- name text not null,
- numeric integer not null,
+ flds text not null,
conf text not null
);
@@ -117,14 +112,6 @@ create table if not exists templates (
conf text not null
);
-create table if not exists fdata (
- fid integer not null,
- fmid integer not null,
- ord integer not null,
- val text not null,
- csum text not null
-);
-
create table if not exists gconf (
id integer primary key,
mod integer not null,
@@ -190,9 +177,6 @@ create index if not exists ix_cards_mod on cards (mod);
create index if not exists ix_facts_mod on facts (mod);
-- card spacing, etc
create index if not exists ix_cards_fid on cards (fid);
--- fact data
-create index if not exists ix_fdata_fid on fdata (fid);
-create index if not exists ix_fdata_csum on fdata (csum);
-- revlog by card
create index if not exists ix_revlog_cid on revlog (cid);
-- media
@@ -204,10 +188,16 @@ create index if not exists ix_media_csum on media (csum);
# we don't have access to the progress handler at this point, so the GUI code
# will need to set up a progress handling window before opening a deck.
-def _moveTable(db, table, insExtra=""):
+def _moveTable(db, table, cards=False):
+ if cards:
+ insExtra = " order by created"
+ else:
+ insExtra = ""
sql = db.scalar(
"select sql from sqlite_master where name = '%s'" % table)
sql = sql.replace("TABLE "+table, "temporary table %s2" % table)
+ if cards:
+ sql = sql.replace("PRIMARY KEY (id),", "")
db.execute(sql)
db.execute("insert into %s2 select * from %s%s" % (table, table, insExtra))
db.execute("drop table "+table)
@@ -244,7 +234,7 @@ def _upgradeSchema(db):
# cards
###########
# move into temp table
- _moveTable(db, "cards", " order by created")
+ _moveTable(db, "cards", True)
# use the new order to rewrite card ids
map = dict(db.all("select id, rowid from cards2"))
_insertWithIdChange(db, map, 0, "reviewHistory", 12)
@@ -274,26 +264,36 @@ when trim(tags) == "" then ""
else " " || replace(replace(trim(tags), ",", " "), " ", " ") || " "
end)
""")
- # we store them as fields now
- db.execute("insert into fields select null, id, 0, -1, tags from facts")
- # put facts in a temporary table, sorted by created
- db.execute("""
-create table facts2
-(id, modelId, created, modified, cache)""")
- db.execute("""
-insert into facts2 select id, modelId, created, modified, spaceUntil
+ # pull facts into memory, so we can merge them with fields efficiently
+ facts = db.all("""
+select id, modelId, cast(created as int), cast(modified as int), tags
from facts order by created""")
- # use the new order to rewrite fact ids
- map = dict(db.all("select id, rowid from facts2"))
- _insertWithIdChange(db, map, 1, "fields", 5)
+ # build field hash
+ fields = {}
+ for (fid, ord, val) in db.execute(
+ "select factId, ordinal, value from fields order by factId, ordinal"):
+ if fid not in fields:
+ fields[fid] = []
+ fields[fid].append((ord, val))
+ # build insert data and transform ids, and minimize qt's
+ # bold/italics/underline cruft.
+ map = {}
+ data = []
+ from anki.utils import minimizeHTML
+ for c, row in enumerate(facts):
+ oldid = row[0]
+ map[oldid] = c+1
+ row = list(row)
+ row[0] = c+1
+ row.append(minimizeHTML("\x1f".join([x[1] for x in sorted(fields[oldid])])))
+ data.append(row)
+ # use the new order to rewrite fact ids in cards table
_insertWithIdChange(db, map, 1, "cards", 18)
# and put the facts into the new table
db.execute("drop table facts")
_addSchema(db, False)
- db.execute("""
-insert or ignore into facts select rowid, modelId,
-cast(created as int), cast(modified as int), cache from facts2""")
- db.execute("drop table facts2")
+ db.executemany("insert into facts values (?,?,?,?,?,?,'','')", data)
+ db.execute("drop table fields")
# media
###########
@@ -303,19 +303,12 @@ insert or ignore into media select filename, cast(created as int),
originalPath from media2""")
db.execute("drop table media2")
- # fields -> fdata
- ###########
- db.execute("""
-insert into fdata select factId, fieldModelId, ordinal, value, ''
-from fields order by factId, ordinal""")
- db.execute("drop table fields")
-
# models
###########
_moveTable(db, "models")
db.execute("""
insert into models select id, cast(modified as int),
-name, "{}" from models2""")
+name, "{}", "{}" from models2""")
db.execute("drop table models2")
# reviewHistory -> revlog
@@ -333,8 +326,8 @@ cast(nextFactor*1000 as int), cast(min(thinkingTime, 60)*1000 as int),
# longer migrations
###########
_migrateDeckTbl(db)
- _migrateFieldsTbl(db)
- _migrateTemplatesTbl(db)
+ mods = _migrateFieldsTbl(db)
+ _migrateTemplatesTbl(db, mods)
_updateIndices(db)
return ver
@@ -385,32 +378,36 @@ utcOffset, "", "", "" from decks""", t=intTime())
def _migrateFieldsTbl(db):
import anki.models
- db.execute("""
-insert into fields select id, modelId, ordinal, name, numeric, ''
-from fieldModels""")
dconf = anki.models.defaultFieldConf
+ mods = {}
for row in db.all("""
-select id, features, required, "unique", quizFontFamily, quizFontSize,
-quizFontColour, editFontSize from fieldModels"""):
+select id, modelId, ordinal, name, features, required, "unique",
+quizFontFamily, quizFontSize, quizFontColour, editFontSize from fieldModels"""):
conf = dconf.copy()
- (conf['rtl'],
- conf['required'],
- conf['unique'],
+ if row[1] not in mods:
+ mods[row[1]] = []
+ (conf['name'],
+ conf['rtl'],
+ conf['req'],
+ conf['uniq'],
conf['font'],
- conf['quizSize'],
- conf['quizColour'],
- conf['editSize']) = row[1:]
+ conf['qsize'],
+ conf['qcol'],
+ conf['esize']) = row[3:]
# setup bools
conf['rtl'] = not not conf['rtl']
conf['pre'] = True
- # save
- db.execute("update fields set conf = ? where id = ?",
- simplejson.dumps(conf), row[0])
+ # add to model list with ordinal for sorting
+ mods[row[1]].append((row[2], conf))
+ # now we've gathered all the info, save it into the models
+ for mid, fms in mods.items():
+ db.execute("update models set flds = ? where id = ?",
+ simplejson.dumps([x[1] for x in sorted(fms)]), mid)
# clean up
db.execute("drop table fieldModels")
+ return mods
-def _migrateTemplatesTbl(db):
- # do this after fieldModel migration
+def _migrateTemplatesTbl(db, mods):
import anki.models
db.execute("""
insert into templates select id, modelId, ordinal, name, active, qformat,
@@ -425,10 +422,11 @@ allowEmptyAnswer, typeAnswer from cardModels"""):
conf['bg'],
conf['allowEmptyAns'],
fname) = row[2:]
- # convert the field name to an id
- conf['typeAnswer'] = db.scalar(
- "select id from fields where name = ? and mid = ?",
- fname, row[1])
+ # convert the field name to an ordinal
+ for (ord, fm) in mods[row[1]]:
+ if fm['name'] == row[1]:
+ conf['typeAnswer'] = ord
+ break
# save
db.execute("update templates set conf = ? where id = ?",
simplejson.dumps(conf), row[0])
@@ -440,7 +438,6 @@ def _rewriteModelIds(deck):
models = deck.allModels()
deck.db.execute("delete from models")
deck.db.execute("delete from templates")
- deck.db.execute("delete from fields")
for c, m in enumerate(models):
old = m.id
m.id = c+1
@@ -451,13 +448,6 @@ def _rewriteModelIds(deck):
t._flush()
deck.db.execute(
"update cards set tid = ? where tid = ?", t.mid, oldT)
- for f in m.fields:
- f.mid = m.id
- oldF = f.id
- f.id = None
- f._flush()
- deck.db.execute(
- "update fdata set fmid = ? where fmid = ?", f.id, oldF)
m.flush()
deck.db.execute("update facts set mid = ? where mid = ?", m.id, old)
@@ -470,20 +460,12 @@ def _postSchemaUpgrade(deck):
"revCardsDue", "revCardsRandom", "acqCardsRandom",
"acqCardsOld", "acqCardsNew"):
deck.db.execute("drop view if exists %s" % v)
- # minimize qt's bold/italics/underline cruft. we made need to use lxml to
- # do this properly
- from anki.utils import minimizeHTML
- r = [(minimizeHTML(x[2]), x[0], x[1]) for x in deck.db.execute(
- "select fid, fmid, val from fdata")]
- deck.db.executemany("update fdata set val = ? where fid = ? and fmid = ?",
- r)
- # ensure all templates use the new style field format, and update cach
+ # ensure all templates use the new style field format
for m in deck.allModels():
for t in m.templates:
t.qfmt = re.sub("%\((.+?)\)s", "{{\\1}}", t.qfmt)
t.afmt = re.sub("%\((.+?)\)s", "{{\\1}}", t.afmt)
m.flush()
- m.updateCache()
# remove stats, as it's all in the revlog now
deck.db.execute("drop table if exists stats")
# suspended cards don't use ranges anymore
diff --git a/tests/test_deck.py b/tests/test_deck.py
index b3eb6187e..22c5252ff 100644
--- a/tests/test_deck.py
+++ b/tests/test_deck.py
@@ -64,7 +64,7 @@ def test_factAddDelete():
assert not p
# now let's make a duplicate and test uniqueness
f2 = deck.newFact()
- f2.model.fields[1].conf['required'] = True
+ f2.model.fields[1]['req'] = True
f2['Front'] = u"one"; f2['Back'] = u""
p = f2.problems()
assert p[0] == "unique"
diff --git a/tests/test_models.py b/tests/test_models.py
index a93abbcb7..2167a7f43 100644
--- a/tests/test_models.py
+++ b/tests/test_models.py
@@ -1,7 +1,7 @@
# coding: utf-8
from tests.shared import getEmptyDeck
-from anki.models import Model, Template, Field
+from anki.models import Model, Template
from anki.utils import stripHTML
def test_modelDelete():
@@ -20,7 +20,6 @@ def test_modelCopy():
m2 = m.copy()
assert m2.name == "Basic copy"
assert m2.id != m.id
- assert m2.fields[0].id != m.fields[0].id
assert m2.templates[0].id != m.templates[0].id
assert len(m2.fields) == 2
assert len(m.fields) == 2
@@ -29,24 +28,26 @@ def test_modelCopy():
assert len(m2.templates) == 2
def test_modelChange():
+ print "model change"
+ return
deck = getEmptyDeck()
m2 = deck.currentModel()
# taken from jp support plugin
m1 = Model(deck)
m1.name = "Japanese"
# field 1
- fm = Field(deck)
- fm.name = "Expression"
- fm.conf['required'] = True
- fm.conf['unique'] = True
+ fm = m1.newField()
+ fm['name'] = "Expression"
+ fm['req'] = True
+ fm['uniq'] = True
m1.addField(fm)
# field2
- fm = Field(deck)
- fm.name = "Meaning"
+ fm = m1.newField()
+ fm['name'] = "Meaning"
m1.addField(fm)
# field3
- fm = Field(deck)
- fm.name = "Reading"
+ fm = m1.newField()
+ fm['name'] = "Reading"
m1.addField(fm)
# template1
t = Template(deck)