facts -> notes

This commit is contained in:
Damien Elmes 2011-11-23 12:37:21 +09:00
parent 10e1c1b03e
commit 6e4e8249fb
33 changed files with 638 additions and 638 deletions

View file

@ -27,10 +27,10 @@ Refresh after a change:
Edit the card:
fact = card.fact()
for (name, value) in fact.items():
fact[name] = value + " new"
fact.flush()
note = card.note()
for (name, value) in note.items():
note[name] = value + " new"
note.flush()
Save & close:

View file

@ -12,7 +12,7 @@ from anki.utils import intTime, hexifyID, timestampID
# Queue: same as above, and:
# -1=suspended, -2=user buried, -3=sched buried
# Due is used differently for different queues.
# - new queue: fact id or random int
# - new queue: note id or random int
# - rev queue: integer day
# - lrn queue: integer timestamp
@ -27,7 +27,7 @@ class Card(object):
self.id = id
self.load()
else:
# to flush, set fid, ord, and due
# to flush, set nid, ord, and due
self.id = timestampID(deck.db, "cards")
self.gid = 1
self.crt = intTime()
@ -44,7 +44,7 @@ class Card(object):
def load(self):
(self.id,
self.fid,
self.nid,
self.gid,
self.ord,
self.mod,
@ -72,7 +72,7 @@ class Card(object):
insert or replace into cards values
(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
self.id,
self.fid,
self.nid,
self.gid,
self.ord,
self.mod,
@ -108,21 +108,21 @@ lapses=?, left=?, edue=? where id = ?""",
def _getQA(self, reload=False):
if not self._qa or reload:
f = self.fact(); m = self.model()
f = self.note(); m = self.model()
data = [self.id, f.id, m['id'], self.gid, self.ord, f.stringTags(),
f.joinedFields()]
self._qa = self.deck._renderQA(data)
return self._qa
def _reviewData(self, reload=False):
"Fetch the model and fact."
"Fetch the model and note."
if not self._rd or reload:
f = self.deck.getFact(self.fid)
f = self.deck.getNote(self.nid)
m = self.deck.models.get(f.mid)
self._rd = [f, m]
return self._rd
def fact(self):
def note(self):
return self._reviewData()[0]
def model(self, reload=False):

View file

@ -24,7 +24,7 @@ REV_CARDS_NEW_FIRST = 2
# removal types
REM_CARD = 0
REM_FACT = 1
REM_NOTE = 1
REM_GROUP = 2
# count display

View file

@ -16,7 +16,7 @@ from anki.consts import *
from anki.errors import AnkiError
import anki.latex # sets up hook
import anki.cards, anki.facts, anki.template, anki.cram, anki.find
import anki.cards, anki.notes, anki.template, anki.cram, anki.find
defaultConf = {
# scheduling options
@ -29,7 +29,7 @@ defaultConf = {
'fontFamilies': [
[u' 明朝',u'ヒラギノ明朝 Pro W3',u'Kochi Mincho', u'東風明朝']
],
'sortType': "factFld",
'sortType': "noteFld",
'sortBackwards': False,
}
@ -180,7 +180,7 @@ crt=?, mod=?, scm=?, dty=?, usn=?, ls=?, conf=?""",
def beforeUpload(self):
"Called before a full upload."
tbls = "facts", "cards", "revlog", "graves"
tbls = "notes", "cards", "revlog", "graves"
for t in tbls:
self.db.execute("update %s set usn=0 where usn=-1" % t)
self._usn = 0
@ -194,8 +194,8 @@ crt=?, mod=?, scm=?, dty=?, usn=?, ls=?, conf=?""",
def getCard(self, id):
return anki.cards.Card(self, id)
def getFact(self, id):
return anki.facts.Fact(self, id=id)
def getNote(self, id):
return anki.notes.Note(self, id=id)
# Utils
##########################################################################
@ -218,23 +218,23 @@ crt=?, mod=?, scm=?, dty=?, usn=?, ls=?, conf=?""",
self.db.executemany("insert into graves values (%d, ?, %d)" % (
self.usn(), type), ([x] for x in ids))
# Facts
# Notes
##########################################################################
def factCount(self):
return self.db.scalar("select count() from facts")
def noteCount(self):
return self.db.scalar("select count() from notes")
def newFact(self):
"Return a new fact with the current model."
return anki.facts.Fact(self, self.models.current())
def newNote(self):
"Return a new note with the current model."
return anki.notes.Note(self, self.models.current())
def addFact(self, fact):
"Add a fact to the deck. Return number of new cards."
def addNote(self, note):
"Add a note to the deck. Return number of new cards."
# check we have card models available, then save
cms = self.findTemplates(fact)
cms = self.findTemplates(note)
if not cms:
return 0
fact.flush()
note.flush()
# randomize?
if self.models.randomNew():
due = self._randPos()
@ -243,65 +243,65 @@ crt=?, mod=?, scm=?, dty=?, usn=?, ls=?, conf=?""",
# add cards
ncards = 0
for template in cms:
self._newCard(fact, template, due)
self._newCard(note, template, due)
ncards += 1
return ncards
def _randPos(self):
return random.randrange(1, sys.maxint)
def remFacts(self, ids):
self.remCards(self.db.list("select id from cards where fid in "+
def remNotes(self, ids):
self.remCards(self.db.list("select id from cards where nid in "+
ids2str(ids)))
def _remFacts(self, ids):
"Bulk delete facts by ID. Don't call this directly."
def _remNotes(self, ids):
"Bulk delete notes by ID. Don't call this directly."
if not ids:
return
strids = ids2str(ids)
# we need to log these independently of cards, as one side may have
# more card templates
self._logRem(ids, REM_FACT)
self.db.execute("delete from facts where id in %s" % strids)
self.db.execute("delete from fsums where fid in %s" % strids)
self._logRem(ids, REM_NOTE)
self.db.execute("delete from notes where id in %s" % strids)
self.db.execute("delete from nsums where nid in %s" % strids)
# Card creation
##########################################################################
def findTemplates(self, fact):
def findTemplates(self, note):
"Return (active), non-empty templates."
ok = []
model = fact.model()
avail = self.models.availOrds(model, joinFields(fact.fields))
model = note.model()
avail = self.models.availOrds(model, joinFields(note.fields))
ok = []
for t in model['tmpls']:
if t['ord'] in avail:
ok.append(t)
return ok
def genCards(self, fids):
def genCards(self, nids):
"Generate cards for non-empty templates."
# build map of (fid,ord) so we don't create dupes
sfids = ids2str(fids)
# build map of (nid,ord) so we don't create dupes
snids = ids2str(nids)
have = {}
for fid, ord in self.db.execute(
"select fid, ord from cards where fid in "+sfids):
have[(fid,ord)] = True
# build cards for each fact
for nid, ord in self.db.execute(
"select nid, ord from cards where nid in "+snids):
have[(nid,ord)] = True
# build cards for each note
data = []
ts = maxID(self.db)
now = intTime()
for fid, mid, gid, flds in self.db.execute(
"select id, mid, gid, flds from facts where id in "+sfids):
for nid, mid, gid, flds in self.db.execute(
"select id, mid, gid, flds from notes where id in "+snids):
model = self.models.get(mid)
avail = self.models.availOrds(model, flds)
ok = []
for t in model['tmpls']:
if (fid,t['ord']) in have:
if (nid,t['ord']) in have:
continue
if t['ord'] in avail:
data.append((ts, fid, t['gid'] or gid, t['ord'],
now, fid))
data.append((ts, nid, t['gid'] or gid, t['ord'],
now, nid))
ts += 1
# bulk update
self.db.executemany("""
@ -311,26 +311,26 @@ insert into cards values (?,?,?,?,?,-1,0,0,?,0,0,0,0,0,0,0,"")""",
# type 0 - when previewing in add dialog, only non-empty
# type 1 - when previewing edit, only existing
# type 2 - when previewing in models dialog, all templates
def previewCards(self, fact, type=0):
def previewCards(self, note, type=0):
if type == 0:
cms = self.findTemplates(fact)
cms = self.findTemplates(note)
elif type == 1:
cms = [c.template() for c in fact.cards()]
cms = [c.template() for c in note.cards()]
else:
cms = fact.model()['tmpls']
cms = note.model()['tmpls']
if not cms:
return []
cards = []
for template in cms:
cards.append(self._newCard(fact, template, 1, flush=False))
cards.append(self._newCard(note, template, 1, flush=False))
return cards
def _newCard(self, fact, template, due, flush=True):
def _newCard(self, note, template, due, flush=True):
"Create a new card."
card = anki.cards.Card(self)
card.fid = fact.id
card.nid = note.id
card.ord = template['ord']
card.gid = template['gid'] or fact.gid
card.gid = template['gid'] or note.gid
card.due = due
if flush:
card.flush()
@ -350,42 +350,42 @@ insert into cards values (?,?,?,?,?,-1,0,0,?,0,0,0,0,0,0,0,"")""",
if not ids:
return
sids = ids2str(ids)
fids = self.db.list("select fid from cards where id in "+sids)
nids = self.db.list("select nid from cards where id in "+sids)
# remove cards
self._logRem(ids, REM_CARD)
self.db.execute("delete from cards where id in "+sids)
self.db.execute("delete from revlog where cid in "+sids)
# then facts
fids = self.db.list("""
select id from facts where id in %s and id not in (select fid from cards)""" %
ids2str(fids))
self._remFacts(fids)
# then notes
nids = self.db.list("""
select id from notes where id in %s and id not in (select nid from cards)""" %
ids2str(nids))
self._remNotes(nids)
# Field checksums and sorting fields
##########################################################################
def _fieldData(self, sfids):
def _fieldData(self, snids):
return self.db.execute(
"select id, mid, flds from facts where id in "+sfids)
"select id, mid, flds from notes where id in "+snids)
def updateFieldCache(self, fids, csum=True):
def updateFieldCache(self, nids, csum=True):
"Update field checksums and sort cache, after find&replace, etc."
sfids = ids2str(fids)
snids = ids2str(nids)
r = []
r2 = []
for (fid, mid, flds) in self._fieldData(sfids):
for (nid, mid, flds) in self._fieldData(snids):
fields = splitFields(flds)
model = self.models.get(mid)
if csum:
for f in model['flds']:
if f['uniq'] and fields[f['ord']]:
r.append((fid, mid, fieldChecksum(fields[f['ord']])))
r2.append((stripHTML(fields[self.models.sortIdx(model)]), fid))
r.append((nid, mid, fieldChecksum(fields[f['ord']])))
r2.append((stripHTML(fields[self.models.sortIdx(model)]), nid))
if csum:
self.db.execute("delete from fsums where fid in "+sfids)
self.db.executemany("insert into fsums values (?,?,?)", r)
self.db.execute("delete from nsums where nid in "+snids)
self.db.executemany("insert into nsums values (?,?,?)", r)
# rely on calling code to bump usn+mod
self.db.executemany("update facts set sfld = ? where id = ?", r2)
self.db.executemany("update notes set sfld = ? where id = ?", r2)
# Q/A generation
##########################################################################
@ -394,7 +394,7 @@ select id from facts where id in %s and id not in (select fid from cards)""" %
# gather metadata
if type == "card":
where = "and c.id in " + ids2str(ids)
elif type == "fact":
elif type == "note":
where = "and f.id in " + ids2str(ids)
elif type == "model":
where = "and m.id in " + ids2str(ids)
@ -407,7 +407,7 @@ select id from facts where id in %s and id not in (select fid from cards)""" %
def _renderQA(self, data):
"Returns hash of id, question, answer."
# data is [cid, fid, mid, gid, ord, tags, flds]
# data is [cid, nid, mid, gid, ord, tags, flds]
# unpack fields and create dict
flist = splitFields(data[6])
fields = {}
@ -437,11 +437,11 @@ select id from facts where id in %s and id not in (select fid from cards)""" %
return d
def _qaData(self, where=""):
"Return [cid, fid, mid, gid, ord, tags, flds] db query"
"Return [cid, nid, mid, gid, ord, tags, flds] db query"
return self.db.execute("""
select c.id, f.id, f.mid, c.gid, c.ord, f.tags, f.flds
from cards c, facts f
where c.fid == f.id
from cards c, notes f
where c.nid == f.id
%s""" % where)
# Finding cards
@ -450,8 +450,8 @@ where c.fid == f.id
def findCards(self, query, full=False):
return anki.find.Finder(self).findCards(query, full)
def findReplace(self, fids, src, dst, regex=None, field=None, fold=True):
return anki.find.findReplace(self, fids, src, dst, regex, field, fold)
def findReplace(self, nids, src, dst, regex=None, field=None, fold=True):
return anki.find.findReplace(self, nids, src, dst, regex, field, fold)
def findDuplicates(self, fmids):
return anki.find.findDuplicates(self, fmids)
@ -570,15 +570,15 @@ where c.fid == f.id
problems = []
self.save()
oldSize = os.stat(self.path)[stat.ST_SIZE]
# delete any facts with missing cards
# delete any notes with missing cards
ids = self.db.list("""
select id from facts where id not in (select distinct fid from cards)""")
self._remFacts(ids)
select id from notes where id not in (select distinct nid from cards)""")
self._remNotes(ids)
# tags
self.tags.registerFacts()
self.tags.registerNotes()
# field cache
for m in self.models.all():
self.updateFieldCache(self.models.fids(m))
self.updateFieldCache(self.models.nids(m))
# and finally, optimize
self.optimize()
newSize = os.stat(self.path)[stat.ST_SIZE]

View file

@ -104,23 +104,23 @@ class AnkiExporter(Exporter):
cards = self.deck.db.all("""
select id, modified from cards
where id in %s""" % cStrIds)
facts = self.deck.db.all("""
select facts.id, facts.modified from cards, facts where
facts.id = cards.factId and
notes = self.deck.db.all("""
select notes.id, notes.modified from cards, notes where
notes.id = cards.noteId and
cards.id in %s""" % cStrIds)
models = self.deck.db.all("""
select models.id, models.modified from models, facts where
facts.modelId = models.id and
facts.id in %s""" % ids2str([f[0] for f in facts]))
select models.id, models.modified from models, notes where
notes.modelId = models.id and
notes.id in %s""" % ids2str([f[0] for f in notes]))
media = self.deck.db.all("""
select id, modified from media""")
return {
# cards
"cards": cards,
"delcards": [],
# facts
"facts": facts,
"delfacts": [],
# notes
"notes": notes,
"delnotes": [],
# models
"models": models,
"delmodels": [],
@ -147,8 +147,8 @@ where cards.id in %s
order by cards.created""" % strids)
if self.includeTags:
self.cardTags = dict(self.deck.db.all("""
select cards.id, facts.tags from cards, facts
where cards.factId = facts.id
select cards.id, notes.tags from cards, notes
where cards.noteId = notes.id
and cards.id in %s
order by cards.created""" % strids))
out = u"\n".join(["%s\t%s%s" % (
@ -166,7 +166,7 @@ order by cards.created""" % strids))
return "\t" + ", ".join(parseTags(self.cardTags[id]))
return ""
class TextFactExporter(Exporter):
class TextNoteExporter(Exporter):
key = _("Text files (*.txt)")
ext = ".txt"
@ -177,20 +177,20 @@ class TextFactExporter(Exporter):
def doExport(self, file):
cardIds = self.cardIds()
facts = self.deck.db.all("""
select factId, value, facts.created from facts, fields
notes = self.deck.db.all("""
select noteId, value, notes.created from notes, fields
where
facts.id in
(select distinct factId from cards
notes.id in
(select distinct noteId from cards
where cards.id in %s)
and facts.id = fields.factId
order by factId, ordinal""" % ids2str(cardIds))
and notes.id = fields.noteId
order by noteId, ordinal""" % ids2str(cardIds))
txt = ""
if self.includeTags:
self.factTags = dict(self.deck.db.all(
"select id, tags from facts where id in %s" %
ids2str([fact[0] for fact in facts])))
groups = itertools.groupby(facts, itemgetter(0))
self.noteTags = dict(self.deck.db.all(
"select id, tags from notes where id in %s" %
ids2str([note[0] for note in notes])))
groups = itertools.groupby(notes, itemgetter(0))
groups = [[x for x in y[1]] for y in groups]
groups = [(group[0][2],
"\t".join([self.escapeText(x[1]) for x in group]) +
@ -205,7 +205,7 @@ order by factId, ordinal""" % ids2str(cardIds))
def tags(self, id):
if self.includeTags:
return "\t" + self.factTags[id]
return "\t" + self.noteTags[id]
return ""
# Export modules
@ -215,4 +215,4 @@ def exporters():
return (
(_("Anki Deck (*.anki)"), AnkiExporter),
(_("Cards in tab-separated text file (*.txt)"), TextCardExporter),
(_("Facts in tab-separated text file (*.txt)"), TextFactExporter))
(_("Notes in tab-separated text file (*.txt)"), TextNoteExporter))

View file

@ -9,7 +9,7 @@ from anki.utils import ids2str, splitFields, joinFields, stripHTML, intTime
SEARCH_TAG = 0
SEARCH_TYPE = 1
SEARCH_PHRASE = 2
SEARCH_FID = 3
SEARCH_NID = 3
SEARCH_TEMPLATE = 4
SEARCH_FIELD = 5
SEARCH_MODEL = 6
@ -54,9 +54,9 @@ class Finder(object):
def _whereClause(self):
x = []
if self.lims['fact']:
x.append("fid in (select id from facts where %s)" % " and ".join(
self.lims['fact']))
if self.lims['note']:
x.append("nid in (select id from notes where %s)" % " and ".join(
self.lims['note']))
if self.lims['card']:
x.extend(self.lims['card'])
q = " and ".join(x)
@ -68,17 +68,17 @@ class Finder(object):
type = self.deck.conf['sortType']
if not type:
return "select id from cards c where " + lim
elif type.startswith("fact"):
if type == "factCrt":
elif type.startswith("note"):
if type == "noteCrt":
sort = "f.id, c.ord"
elif type == "factMod":
elif type == "noteMod":
sort = "f.mod, c.ord"
elif type == "factFld":
elif type == "noteFld":
sort = "f.sfld collate nocase, c.ord"
else:
raise Exception()
return """
select c.id from cards c, facts f where %s and c.fid=f.id
select c.id from cards c, notes f where %s and c.nid=f.id
order by %s""" % (lim, sort)
elif type.startswith("card"):
if type == "cardMod":
@ -101,9 +101,9 @@ order by %s""" % (lim, sort)
raise Exception()
def _findLimits(self):
"Generate a list of fact/card limits for the query."
"Generate a list of note/card limits for the query."
self.lims = {
'fact': [],
'note': [],
'card': [],
'args': {},
'valid': True
@ -113,8 +113,8 @@ order by %s""" % (lim, sort)
self._findTag(token, isNeg, c)
elif type == SEARCH_TYPE:
self._findCardState(token, isNeg)
elif type == SEARCH_FID:
self._findFids(token)
elif type == SEARCH_NID:
self._findNids(token)
elif type == SEARCH_TEMPLATE:
self._findTemplate(token, isNeg)
elif type == SEARCH_FIELD:
@ -128,7 +128,7 @@ order by %s""" % (lim, sort)
def _findTag(self, val, neg, c):
if val == "none":
self.lims['fact'].append("select id from facts where tags = ''")
self.lims['note'].append("select id from notes where tags = ''")
return
extra = "not" if neg else ""
val = val.replace("*", "%")
@ -137,7 +137,7 @@ order by %s""" % (lim, sort)
if not val.endswith("%"):
val += " %"
self.lims['args']["_tag_%d" % c] = val
self.lims['fact'].append(
self.lims['note'].append(
"tags %s like :_tag_%d""" % (extra, c))
def _findCardState(self, val, neg):
@ -168,20 +168,20 @@ order by %s""" % (lim, sort)
extra = "not" if neg else ""
if not self.full:
self.lims['args']["_text_%d"%c] = "%"+val+"%"
self.lims['fact'].append("flds %s like :_text_%d escape '\\'" % (
self.lims['note'].append("flds %s like :_text_%d escape '\\'" % (
extra, c))
else:
# in the future we may want to apply this at the end to speed up
# the case where there are other limits
fids = []
for fid, flds in self.deck.db.execute(
"select id, flds from facts"):
nids = []
for nid, flds in self.deck.db.execute(
"select id, flds from notes"):
if val in stripHTML(flds):
fids.append(fid)
self.lims['fact'].append("id in " + ids2str(fids))
nids.append(nid)
self.lims['note'].append("id in " + ids2str(nids))
def _findFids(self, val):
self.lims['fact'].append("id in (%s)" % val)
def _findNids(self, val):
self.lims['note'].append("id in (%s)" % val)
def _findModel(self, val, isNeg):
extra = "not" if isNeg else ""
@ -189,7 +189,7 @@ order by %s""" % (lim, sort)
for m in self.deck.models.all():
if m['name'].lower() == val:
ids.append(m['id'])
self.lims['fact'].append("mid %s in %s" % (extra, ids2str(ids)))
self.lims['note'].append("mid %s in %s" % (extra, ids2str(ids)))
def _findGroup(self, val, isNeg):
extra = "!" if isNeg else ""
@ -214,7 +214,7 @@ order by %s""" % (lim, sort)
# template name?
elif t['name'].lower() == val.lower():
lims.append((
"(fid in (select id from facts where mid = %s) "
"(nid in (select id from notes where mid = %s) "
"and ord %s %d)") % (m['id'], comp, t['ord']))
found = True
if lims:
@ -236,11 +236,11 @@ order by %s""" % (lim, sort)
# nothing has that field
self.lims['valid'] = False
return
# gather fids
# gather nids
regex = value.replace("%", ".*")
fids = []
nids = []
for (id,mid,flds) in self.deck.db.execute("""
select id, mid, flds from facts
select id, mid, flds from notes
where mid in %s and flds like ? escape '\\'""" % (
ids2str(mods.keys())),
"%" if self.full else value):
@ -250,9 +250,9 @@ where mid in %s and flds like ? escape '\\'""" % (
if self.full:
strg = stripHTML(strg)
if re.search(regex, strg):
fids.append(id)
nids.append(id)
extra = "not" if isNeg else ""
self.lims['fact'].append("id %s in %s" % (extra, ids2str(fids)))
self.lims['note'].append("id %s in %s" % (extra, ids2str(nids)))
# Most of this function was written by Marcus
def _parseQuery(self):
@ -332,7 +332,7 @@ where mid in %s and flds like ? escape '\\'""" % (
elif token['value'].startswith("group:"):
token['value'] = token['value'][6:].lower()
type = SEARCH_GROUP
elif token['value'].startswith("fid:") and len(token['value']) > 4:
elif token['value'].startswith("nid:") and len(token['value']) > 4:
dec = token['value'][4:]
try:
int(dec)
@ -344,7 +344,7 @@ where mid in %s and flds like ? escape '\\'""" % (
token['value'] = token['value'][4:]
except:
token['value'] = "0"
type = SEARCH_FID
type = SEARCH_NID
elif token['value'].startswith("card:"):
token['value'] = token['value'][5:]
type = SEARCH_TEMPLATE
@ -370,8 +370,8 @@ where mid in %s and flds like ? escape '\\'""" % (
# Find and replace
##########################################################################
def findReplace(deck, fids, src, dst, regex=False, field=None, fold=True):
"Find and replace fields in a fact."
def findReplace(deck, nids, src, dst, regex=False, field=None, fold=True):
"Find and replace fields in a note."
mmap = {}
if field:
for m in deck.models.all():
@ -389,8 +389,8 @@ def findReplace(deck, fids, src, dst, regex=False, field=None, fold=True):
def repl(str):
return re.sub(regex, dst, str)
d = []
for fid, mid, flds in deck.db.execute(
"select id, mid, flds from facts where id in "+ids2str(fids)):
for nid, mid, flds in deck.db.execute(
"select id, mid, flds from notes where id in "+ids2str(nids)):
origFlds = flds
# does it match?
sflds = splitFields(flds)
@ -402,12 +402,12 @@ def findReplace(deck, fids, src, dst, regex=False, field=None, fold=True):
sflds[c] = repl(sflds[c])
flds = joinFields(sflds)
if flds != origFlds:
d.append(dict(fid=fid,flds=flds,u=deck.usn(),m=intTime()))
d.append(dict(nid=nid,flds=flds,u=deck.usn(),m=intTime()))
if not d:
return 0
# replace
deck.db.executemany("update facts set flds=:flds,mod=:m,usn=:u where id=:fid", d)
deck.updateFieldCache(fids)
deck.db.executemany("update notes set flds=:flds,mod=:m,usn=:u where id=:nid", d)
deck.updateFieldCache(nids)
return len(d)
# Find duplicates
@ -415,14 +415,14 @@ def findReplace(deck, fids, src, dst, regex=False, field=None, fold=True):
def findDuplicates(deck, fmids):
data = deck.db.all(
"select fid, value from fdata where fmid in %s" %
"select nid, value from fdata where fmid in %s" %
ids2str(fmids))
vals = {}
for (fid, val) in data:
for (nid, val) in data:
if not val.strip():
continue
if val not in vals:
vals[val] = [fid]
vals[val] = [nid]
else:
vals[val].append(fid)
vals[val].append(nid)
return [(k,v) for (k,v) in vals.items() if len(v) > 1]

View file

@ -15,7 +15,7 @@ from anki.lang import _
# appropriate
# notes:
# - it's difficult to enforce valid gids for models/facts/cards, as we
# - it's difficult to enforce valid gids for models/notes/cards, as we
# may update the gid locally only to have it overwritten by a more recent
# change from somewhere else. to avoid this, we allow invalid gid
# references, and treat any invalid gids as the default group.
@ -268,7 +268,7 @@ class GroupManager(object):
def sendHome(self, cids):
self.deck.db.execute("""
update cards set gid=(select gid from facts f where f.id=fid),
update cards set gid=(select gid from notes f where f.id=nid),
usn=?,mod=? where id in %s""" % ids2str(cids),
self.deck.usn(), intTime(), gid)

View file

@ -11,9 +11,9 @@ from anki.importing.base import Importer
# shared decks, and import from a packaged deck.
#
# We can't rely on internal ids, so we:
# - compare facts by guid
# - compare notes by guid
# - compare models by schema signature
# - compare cards by fact guid + ordinal
# - compare cards by note guid + ordinal
# - compare groups by name
#
@ -44,53 +44,53 @@ class Anki2Importer(Importer):
self.dst.groups.select(id)
self._prepareTS()
self._prepareModels()
self._importFacts()
self._importNotes()
self._importCards()
self._importMedia()
self._postImport()
self.dst.db.execute("vacuum")
self.dst.db.execute("analyze")
# Facts
# Notes
######################################################################
# - should note new for wizard
def _importFacts(self):
def _importNotes(self):
# build guid -> (id,mod,mid) hash
self._facts = {}
self._notes = {}
for id, guid, mod, mid in self.dst.db.execute(
"select id, guid, mod, mid from facts"):
self._facts[guid] = (id, mod, mid)
"select id, guid, mod, mid from notes"):
self._notes[guid] = (id, mod, mid)
# iterate over source deck
add = []
dirty = []
for fact in self.src.db.execute(
"select * from facts"):
for note in self.src.db.execute(
"select * from notes"):
# turn the db result into a mutable list
fact = list(fact)
guid, mid = fact[1:3]
note = list(note)
guid, mid = note[1:3]
# missing from local deck?
if guid not in self._facts:
if guid not in self._notes:
# get corresponding local model
lmid = self._mid(mid)
# rewrite internal ids, models, etc
fact[0] = self.ts()
fact[2] = lmid
fact[3] = self._gid(fact[3])
fact[4] = intTime()
fact[5] = -1 # usn
add.append(fact)
dirty.append(fact[0])
# note we have the added fact
self._facts[guid] = (fact[0], fact[4], fact[2])
note[0] = self.ts()
note[2] = lmid
note[3] = self._gid(note[3])
note[4] = intTime()
note[5] = -1 # usn
add.append(note)
dirty.append(note[0])
# note we have the added note
self._notes[guid] = (note[0], note[4], note[2])
else:
continue #raise Exception("merging facts nyi")
continue #raise Exception("merging notes nyi")
# add to deck
self.dst.db.executemany(
"insert or replace into facts values (?,?,?,?,?,?,?,?,?,?,?)",
"insert or replace into notes values (?,?,?,?,?,?,?,?,?,?,?)",
add)
self.dst.updateFieldCache(dirty)
self.dst.tags.registerFacts(dirty)
self.dst.tags.registerNotes(dirty)
# Models
######################################################################
@ -168,24 +168,24 @@ class Anki2Importer(Importer):
# build map of (guid, ord) -> cid
self._cards = {}
for guid, ord, cid in self.dst.db.execute(
"select f.guid, c.ord, c.id from cards c, facts f "
"where c.fid = f.id"):
"select f.guid, c.ord, c.id from cards c, notes f "
"where c.nid = f.id"):
self._cards[(guid, ord)] = cid
# loop through src
cards = []
revlog = []
print "fixme: need to check schema issues in card import"
for card in self.src.db.execute(
"select f.guid, f.mid, c.* from cards c, facts f "
"where c.fid = f.id"):
"select f.guid, f.mid, c.* from cards c, notes f "
"where c.nid = f.id"):
guid = card[0]
# does the card's fact exist in dst deck?
if guid not in self._facts:
# does the card's note exist in dst deck?
if guid not in self._notes:
continue
dfid = self._facts[guid]
# does the fact share the same schema?
dnid = self._notes[guid]
# does the note share the same schema?
# shash = self._srcModels[card[1]]
# mid = self._facts[guid][2]
# mid = self._notes[guid][2]
# if shash != self._dstModels[mid]:
# continue
# does the card already exist in the dst deck?
@ -193,12 +193,12 @@ class Anki2Importer(Importer):
if (guid, ord) in self._cards:
# fixme: in future, could update if newer mod time
continue
# doesn't exist. strip off fact info, and save src id for later
# doesn't exist. strip off note info, and save src id for later
card = list(card[2:])
scid = card[0]
# update cid, fid, etc
# update cid, nid, etc
card[0] = self.ts()
card[1] = self._facts[guid][0]
card[1] = self._notes[guid][0]
card[2] = self._gid(card[2])
card[4] = intTime()
cards.append(card)

View file

@ -57,15 +57,15 @@ class CardImporter(Importer):
cards = self.foreignCards()
# grab data from db
fields = self.deck.db.all("""
select factId, value from fields where fieldModelId = :id
select noteId, value from fields where fieldModelId = :id
and value != ''""",
id=self.updateKey[1])
# hash it
vhash = {}
fids = []
for (fid, val) in fields:
fids.append(fid)
vhash[val] = fid
nids = []
for (nid, val) in fields:
nids.append(nid)
vhash[val] = nid
# prepare tags
tagsIdx = None
try:
@ -82,7 +82,7 @@ and value != ''""",
if v in vhash:
# ignore empty keys
if v:
# fid, card
# nid, card
upcards.append((vhash[v], c))
else:
newcards.append(c)
@ -96,28 +96,28 @@ and value != ''""",
except ValueError:
# not mapped
continue
data = [{'fid': fid,
data = [{'nid': nid,
'fmid': fm.id,
'v': c.fields[index],
'chk': self.maybeChecksum(c.fields[index], fm.unique)}
for (fid, c) in upcards]
for (nid, c) in upcards]
self.deck.db.execute("""
update fields set value = :v, chksum = :chk where factId = :fid
update fields set value = :v, chksum = :chk where noteId = :nid
and fieldModelId = :fmid""", data)
# update tags
if tagsIdx is not None:
data = [{'fid': fid,
data = [{'nid': nid,
't': c.fields[tagsIdx]}
for (fid, c) in upcards]
for (nid, c) in upcards]
self.deck.db.execute(
"update facts set tags = :t where id = :fid",
"update notes set tags = :t where id = :nid",
data)
# rebuild caches
cids = self.deck.db.column0(
"select id from cards where factId in %s" %
ids2str(fids))
"select id from cards where noteId in %s" %
ids2str(nids))
self.deck.updateCardTags(cids)
self.deck.updateCardsFromFactIds(fids)
self.deck.updateCardsFromNoteIds(nids)
self.total = len(cards)
self.deck.setModified()
@ -166,7 +166,7 @@ and fieldModelId = :fmid""", data)
model = property(getModel, setModel)
def importCards(self, cards):
"Convert each card into a fact, apply attributes and add to deck."
"Convert each card into a note, apply attributes and add to deck."
# ensure all unique and required fields are mapped
for fm in self.model.fieldModels:
if fm.required or fm.unique:
@ -187,7 +187,7 @@ and fieldModelId = :fmid""", data)
return cards
def addCards(self, cards):
"Add facts in bulk from foreign cards."
"Add notes in bulk from foreign cards."
# map tags field to attr
try:
idx = self.mapping.index(0)
@ -195,31 +195,31 @@ and fieldModelId = :fmid""", data)
c.tags += " " + c.fields[idx]
except ValueError:
pass
# add facts
factIds = [genID() for n in range(len(cards))]
factCreated = {}
# add notes
noteIds = [genID() for n in range(len(cards))]
noteCreated = {}
def fudgeCreated(d, tmp=[]):
if not tmp:
tmp.append(time.time())
else:
tmp[0] += 0.0001
d['created'] = tmp[0]
factCreated[d['id']] = d['created']
noteCreated[d['id']] = d['created']
return d
self.deck.db.execute(factsTable.insert(),
self.deck.db.execute(notesTable.insert(),
[fudgeCreated({'modelId': self.model.id,
'tags': canonifyTags(self.tagsToAdd + " " + cards[n].tags),
'id': factIds[n]}) for n in range(len(cards))])
'id': noteIds[n]}) for n in range(len(cards))])
self.deck.db.execute("""
delete from factsDeleted
where factId in (%s)""" % ",".join([str(s) for s in factIds]))
delete from notesDeleted
where noteId in (%s)""" % ",".join([str(s) for s in noteIds]))
# add all the fields
for fm in self.model.fieldModels:
try:
index = self.mapping.index(fm)
except ValueError:
index = None
data = [{'factId': factIds[m],
data = [{'noteId': noteIds[m],
'fieldModelId': fm.id,
'ordinal': fm.ordinal,
'id': genID(),
@ -239,8 +239,8 @@ where factId in (%s)""" % ",".join([str(s) for s in factIds]))
active += 1
data = [self.addMeta({
'id': genID(),
'factId': factIds[m],
'factCreated': factCreated[factIds[m]],
'noteId': noteIds[m],
'noteCreated': noteCreated[noteIds[m]],
'cardModelId': cm.id,
'ordinal': cm.ordinal,
'question': u"",
@ -248,14 +248,14 @@ where factId in (%s)""" % ",".join([str(s) for s in factIds]))
},cards[m]) for m in range(len(cards))]
self.deck.db.execute(cardsTable.insert(),
data)
self.deck.updateCardsFromFactIds(factIds)
self.total = len(factIds)
self.deck.updateCardsFromNoteIds(noteIds)
self.total = len(noteIds)
def addMeta(self, data, card):
"Add any scheduling metadata to cards"
if 'fields' in card.__dict__:
del card.fields
t = data['factCreated'] + data['ordinal'] * 0.00001
t = data['noteCreated'] + data['ordinal'] * 0.00001
data['created'] = t
data['modified'] = t
data['due'] = t
@ -281,7 +281,7 @@ where factId in (%s)""" % ",".join([str(s) for s in factIds]))
for n in range(len(self.mapping)):
if self.mapping[n] and self.mapping[n].required:
if fieldNum <= n or not card.fields[n].strip():
self.log.append("Fact is missing field '%s': %s" %
self.log.append("Note is missing field '%s': %s" %
(self.mapping[n].name,
", ".join(card.fields)))
return False
@ -307,7 +307,7 @@ where factId in (%s)""" % ",".join([str(s) for s in factIds]))
if self.mapping[n] and self.mapping[n].unique:
if card.fields[n] in self.uniqueCache[self.mapping[n].id]:
if not self.tagDuplicates:
self.log.append("Fact has duplicate '%s': %s" %
self.log.append("Note has duplicate '%s': %s" %
(self.mapping[n].name,
", ".join(card.fields)))
return False

View file

@ -161,7 +161,7 @@ If the same name exists, compare checksums."""
def allMedia(self):
"Return a set of all referenced filenames."
files = set()
for mid, flds in self.deck.db.execute("select mid, flds from facts"):
for mid, flds in self.deck.db.execute("select mid, flds from notes"):
for f in self.filesInStr(mid, flds):
files.add(f)
return files

View file

@ -75,7 +75,7 @@ class ModelManager(object):
m['usn'] = self.deck.usn()
self._updateRequired(m)
if gencards:
self.deck.genCards(self.fids(m))
self.deck.genCards(self.nids(m))
self.changed = True
def flush(self):
@ -129,12 +129,12 @@ class ModelManager(object):
return self._add(m)
def rem(self, m):
"Delete model, and all its cards/facts."
"Delete model, and all its cards/notes."
self.deck.modSchema()
current = self.current()['id'] == m['id']
# delete facts/cards
# delete notes/cards
self.deck.remCards(self.deck.db.list("""
select id from cards where fid in (select id from facts where mid = ?)""",
select id from cards where nid in (select id from notes where mid = ?)""",
m['id']))
# then the model
del self.models[str(m['id'])]
@ -168,15 +168,15 @@ select id from cards where fid in (select id from facts where mid = ?)""",
# Tools
##################################################
def fids(self, m):
"Fact ids for M."
def nids(self, m):
"Note ids for M."
return self.deck.db.list(
"select id from facts where mid = ?", m['id'])
"select id from notes where mid = ?", m['id'])
def useCount(self, m):
"Number of fact using M."
"Number of note using M."
return self.deck.db.scalar(
"select count() from facts where mid = ?", m['id'])
"select count() from notes where mid = ?", m['id'])
def randomNew(self):
return self.current()['newOrder'] == NEW_CARDS_RANDOM
@ -212,7 +212,7 @@ select id from cards where fid in (select id from facts where mid = ?)""",
assert idx >= 0 and idx < len(m['flds'])
self.deck.modSchema()
m['sortf'] = idx
self.deck.updateFieldCache(self.fids(m), csum=False)
self.deck.updateFieldCache(self.nids(m), csum=False)
self.save(m)
def addField(self, m, field):
@ -234,7 +234,7 @@ select id from cards where fid in (select id from facts where mid = ?)""",
self._transformFields(m, delete)
if idx == self.sortIdx(m):
# need to rebuild
self.deck.updateFieldCache(self.fids(m), csum=False)
self.deck.updateFieldCache(self.nids(m), csum=False)
# saves
self.renameField(m, field, None)
@ -276,11 +276,11 @@ select id from cards where fid in (select id from facts where mid = ?)""",
self.deck.modSchema()
r = []
for (id, flds) in self.deck.db.execute(
"select id, flds from facts where mid = ?", m['id']):
"select id, flds from notes where mid = ?", m['id']):
r.append((joinFields(fn(splitFields(flds))),
intTime(), self.deck.usn(), id))
self.deck.db.executemany(
"update facts set flds=?,mod=?,usn=? where id = ?", r)
"update notes set flds=?,mod=?,usn=? where id = ?", r)
# Templates
##################################################
@ -298,18 +298,18 @@ select id from cards where fid in (select id from facts where mid = ?)""",
self.save(m)
def remTemplate(self, m, template):
"False if removing template would leave orphan facts."
"False if removing template would leave orphan notes."
# find cards using this template
ord = m['tmpls'].index(template)
cids = self.deck.db.list("""
select c.id from cards c, facts f where c.fid=f.id and mid = ? and ord = ?""",
select c.id from cards c, notes f where c.nid=f.id and mid = ? and ord = ?""",
m['id'], ord)
# all facts with this template must have at least two cards, or we
# could end up creating orphaned facts
# all notes with this template must have at least two cards, or we
# could end up creating orphaned notes
if self.deck.db.scalar("""
select fid, count() from cards where
fid in (select fid from cards where id in %s)
group by fid
select nid, count() from cards where
nid in (select nid from cards where id in %s)
group by nid
having count() < 2
limit 1""" % ids2str(cids)):
return False
@ -319,7 +319,7 @@ limit 1""" % ids2str(cids)):
# shift ordinals
self.deck.db.execute("""
update cards set ord = ord - 1, usn = ?, mod = ?
where fid in (select id from facts where mid = ?) and ord > ?""",
where nid in (select id from notes where mid = ?) and ord > ?""",
self.deck.usn(), intTime(), m['id'], ord)
m['tmpls'].remove(template)
self._updateTemplOrds(m)
@ -345,8 +345,8 @@ update cards set ord = ord - 1, usn = ?, mod = ?
# apply
self.save(m)
self.deck.db.execute("""
update cards set ord = (case %s end),usn=?,mod=? where fid in (
select id from facts where mid = ?)""" % " ".join(map),
update cards set ord = (case %s end),usn=?,mod=? where nid in (
select id from notes where mid = ?)""" % " ".join(map),
self.deck.usn(), intTime(), m['id'])
# Model changing
@ -354,19 +354,19 @@ select id from facts where mid = ?)""" % " ".join(map),
# - maps are ord->ord, and there should not be duplicate targets
# - newModel should be self if model is not changing
def change(self, m, fids, newModel, fmap, cmap):
def change(self, m, nids, newModel, fmap, cmap):
self.deck.modSchema()
assert newModel['id'] == m['id'] or (fmap and cmap)
if fmap:
self._changeFacts(fids, newModel, fmap)
self._changeNotes(nids, newModel, fmap)
if cmap:
self._changeCards(fids, newModel, cmap)
self._changeCards(nids, newModel, cmap)
def _changeFacts(self, fids, newModel, map):
def _changeNotes(self, nids, newModel, map):
d = []
nfields = len(newModel['flds'])
for (fid, flds) in self.deck.db.execute(
"select id, flds from facts where id in "+ids2str(fids)):
for (nid, flds) in self.deck.db.execute(
"select id, flds from notes where id in "+ids2str(nids)):
newflds = {}
flds = splitFields(flds)
for old, new in map.items():
@ -375,17 +375,17 @@ select id from facts where mid = ?)""" % " ".join(map),
for c in range(nfields):
flds.append(newflds.get(c, ""))
flds = joinFields(flds)
d.append(dict(fid=fid, flds=flds, mid=newModel['id'],
d.append(dict(nid=nid, flds=flds, mid=newModel['id'],
m=intTime(),u=self.deck.usn()))
self.deck.db.executemany(
"update facts set flds=:flds,mid=:mid,mod=:m,usn=:u where id = :fid", d)
self.deck.updateFieldCache(fids)
"update notes set flds=:flds,mid=:mid,mod=:m,usn=:u where id = :nid", d)
self.deck.updateFieldCache(nids)
def _changeCards(self, fids, newModel, map):
def _changeCards(self, nids, newModel, map):
d = []
deleted = []
for (cid, ord) in self.deck.db.execute(
"select id, ord from cards where fid in "+ids2str(fids)):
"select id, ord from cards where nid in "+ids2str(nids)):
if map[ord] is not None:
d.append(dict(
cid=cid,new=map[ord],u=self.deck.usn(),m=intTime()))

View file

@ -7,7 +7,7 @@ from anki.errors import AnkiError
from anki.utils import fieldChecksum, intTime, \
joinFields, splitFields, ids2str, stripHTML, timestampID, guid64
class Fact(object):
class Note(object):
def __init__(self, deck, model=None, id=None):
assert not (model and id)
@ -16,7 +16,7 @@ class Fact(object):
self.id = id
self.load()
else:
self.id = timestampID(deck.db, "facts")
self.id = timestampID(deck.db, "notes")
self.guid = guid64()
self._model = model
self.gid = model['gid']
@ -38,7 +38,7 @@ class Fact(object):
self.flags,
self.data) = self.deck.db.first("""
select guid, mid, gid, mod, usn, tags, flds, flags, data
from facts where id = ?""", self.id)
from notes where id = ?""", self.id)
self.fields = splitFields(self.fields)
self.tags = self.deck.tags.split(self.tags)
self._model = self.deck.models.get(self.mid)
@ -52,7 +52,7 @@ from facts where id = ?""", self.id)
sfld = stripHTML(self.fields[self.deck.models.sortIdx(self._model)])
tags = self.stringTags()
res = self.deck.db.execute("""
insert or replace into facts values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
insert or replace into notes values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
self.id, self.guid, self.mid, self.gid,
self.mod, self.usn, tags,
self.joinedFields(), sfld, self.flags, self.data)
@ -66,7 +66,7 @@ insert or replace into facts values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
return joinFields(self.fields)
def updateFieldChecksums(self):
self.deck.db.execute("delete from fsums where fid = ?", self.id)
self.deck.db.execute("delete from nsums where nid = ?", self.id)
d = []
for (ord, conf) in self._fmap.values():
if not conf['uniq']:
@ -75,11 +75,11 @@ insert or replace into facts values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
if not val:
continue
d.append((self.id, self.mid, fieldChecksum(val)))
self.deck.db.executemany("insert into fsums values (?, ?, ?)", d)
self.deck.db.executemany("insert into nsums values (?, ?, ?)", d)
def cards(self):
return [self.deck.getCard(id) for id in self.deck.db.list(
"select id from cards where fid = ? order by ord", self.id)]
"select id from cards where nid = ? order by ord", self.id)]
def model(self):
return self._model
@ -151,18 +151,18 @@ insert or replace into facts values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
return True
csum = fieldChecksum(val)
if self.id:
lim = "and fid != :fid"
lim = "and nid != :nid"
else:
lim = ""
fids = self.deck.db.list(
"select fid from fsums where csum = ? and fid != ? and mid = ?",
nids = self.deck.db.list(
"select nid from nsums where csum = ? and nid != ? and mid = ?",
csum, self.id or 0, self.mid)
if not fids:
if not nids:
return True
# grab facts with the same checksums, and see if they're actually
# grab notes with the same checksums, and see if they're actually
# duplicates
for flds in self.deck.db.list("select flds from facts where id in "+
ids2str(fids)):
for flds in self.deck.db.list("select flds from notes where id in "+
ids2str(nids)):
fields = splitFields(flds)
if fields[ord] == val:
return False
@ -185,19 +185,19 @@ insert or replace into facts values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
d.append((ord, None))
return [x[1] for x in sorted(d)]
# Flushing cloze facts
# Flushing cloze notes
##################################################
def _clozePreFlush(self):
self.newlyAdded = not self.deck.db.scalar(
"select 1 from cards where fid = ?", self.id)
"select 1 from cards where nid = ?", self.id)
tmpls = self.deck.findTemplates(self)
ok = []
for t in tmpls:
ok.append(t['ord'])
# check if there are cards referencing a deleted cloze
if self.deck.db.scalar(
"select 1 from cards where fid = ? and ord not in %s" %
"select 1 from cards where nid = ? and ord not in %s" %
ids2str(ok), self.id):
# there are; abort, as the UI should have handled this
raise Exception("UI should have deleted cloze")

View file

@ -269,7 +269,7 @@ select id, due from cards where gid = ? and queue = 0 limit ?""", gid, lim)
self._newQueue.insert(0, self._newQueue.pop())
n -= 1
if not n:
# we only have one fact in the queue; stop rotating
# we only have one note in the queue; stop rotating
break
self.newCount -= 1
return id
@ -605,8 +605,8 @@ gid in %s and queue = 2 and due <= :lim %s limit %d""" % (
conf = self._cardConf(card)['rev']
# find sibling positions
dues = self.deck.db.list(
"select due from cards where fid = ? and queue = 2"
" and id != ?", card.fid, card.id)
"select due from cards where nid = ? and queue = 2"
" and id != ?", card.nid, card.id)
if not dues or idealDue not in dues:
return idealIvl
else:
@ -637,7 +637,7 @@ gid in %s and queue = 2 and due <= :lim %s limit %d""" % (
if (lf >= card.lapses and
(card.lapses-lf) % (max(lf/2, 1)) == 0):
# add a leech tag
f = card.fact()
f = card.note()
f.addTag("leech")
f.flush()
# handle
@ -782,12 +782,12 @@ your short-term review workload will become."""))
"where queue = -1 and id in "+ ids2str(ids),
intTime(), self.deck.usn())
def buryFact(self, fid):
"Bury all cards for fact until next session."
def buryNote(self, nid):
"Bury all cards for note until next session."
self.deck.setDirty()
self.removeFailed(
self.deck.db.list("select id from cards where fid = ?", fid))
self.deck.db.execute("update cards set queue = -2 where fid = ?", fid)
self.deck.db.list("select id from cards where nid = ?", nid))
self.deck.db.execute("update cards set queue = -2 where nid = ?", nid)
# Resetting
##########################################################################
@ -818,18 +818,18 @@ your short-term review workload will become."""))
def sortCards(self, cids, start=1, step=1, shuffle=False, shift=False):
scids = ids2str(cids)
now = intTime()
fids = self.deck.db.list(
("select distinct fid from cards where type = 0 and id in %s "
"order by fid") % scids)
if not fids:
nids = self.deck.db.list(
("select distinct nid from cards where type = 0 and id in %s "
"order by nid") % scids)
if not nids:
# no new cards
return
# determine fid ordering
# determine nid ordering
due = {}
if shuffle:
random.shuffle(fids)
for c, fid in enumerate(fids):
due[fid] = start+c*step
random.shuffle(nids)
for c, nid in enumerate(nids):
due[nid] = start+c*step
high = start+c*step
# shift?
if shift:
@ -844,9 +844,9 @@ update cards set mod=?, usn=?, due=due+? where id not in %s
and due >= ?""" % scids, now, self.deck.usn(), shiftby, low)
# reorder cards
d = []
for id, fid in self.deck.db.execute(
"select id, fid from cards where type = 0 and id in "+scids):
d.append(dict(now=now, due=due[fid], usn=self.deck.usn(), cid=id))
for id, nid in self.deck.db.execute(
"select id, nid from cards where type = 0 and id in "+scids):
d.append(dict(now=now, due=due[nid], usn=self.deck.usn(), cid=id))
self.deck.db.executemany(
"update cards set due=:due,mod=:now,usn=:usn where id = :cid""", d)

View file

@ -50,7 +50,7 @@ class CardStats(object):
self.addLine(_("Model"), c.model()['name'])
self.addLine(_("Template"), c.template()['name'])
self.addLine(_("Current Group"), self.deck.groups.name(c.gid))
self.addLine(_("Home Group"), self.deck.groups.name(c.fact().gid))
self.addLine(_("Home Group"), self.deck.groups.name(c.note().gid))
self.txt += "</table>"
return self.txt
@ -538,10 +538,10 @@ group by hour having count() > 30 order by hour""" % lim,
# text data
i = []
(c, f) = self.deck.db.first("""
select count(id), count(distinct fid) from cards
select count(id), count(distinct nid) from cards
where gid in %s """ % self._limit())
self._line(i, _("Total cards"), c)
self._line(i, _("Total facts"), f)
self._line(i, _("Total notes"), f)
(low, avg, high) = self._factors()
if low:
self._line(i, _("Lowest ease factor"), "%d%%" % low)

View file

@ -80,7 +80,7 @@ create table if not exists deck (
tags text not null
);
create table if not exists facts (
create table if not exists notes (
id integer primary key,
guid integer not null,
mid integer not null,
@ -94,14 +94,14 @@ create table if not exists facts (
data text not null
);
create table if not exists fsums (
fid integer not null,
create table if not exists nsums (
nid integer not null,
mid integer not null,
csum integer not null
);
create table if not exists cards (
id integer primary key,
fid integer not null,
nid integer not null,
gid integer not null,
ord integer not null,
mod integer not null,
@ -168,16 +168,16 @@ def _updateIndices(db):
"Add indices to the DB."
db.executescript("""
-- syncing
create index if not exists ix_facts_usn on facts (usn);
create index if not exists ix_notes_usn on notes (usn);
create index if not exists ix_cards_usn on cards (usn);
create index if not exists ix_revlog_usn on revlog (usn);
-- card spacing, etc
create index if not exists ix_cards_fid on cards (fid);
create index if not exists ix_cards_nid on cards (nid);
-- scheduling and group limiting
create index if not exists ix_cards_sched on cards (gid, queue, due);
-- revlog by card
create index if not exists ix_revlog_cid on revlog (cid);
-- field uniqueness check
create index if not exists ix_fsums_fid on fsums (fid);
create index if not exists ix_fsums_csum on fsums (csum);
create index if not exists ix_nsums_nid on nsums (nid);
create index if not exists ix_nsums_csum on nsums (csum);
""")

View file

@ -128,10 +128,10 @@ class Syncer(object):
# some basic checks to ensure the sync went ok. this is slow, so will
# be removed before official release
assert not self.deck.db.scalar("""
select count() from cards where fid not in (select id from facts)""")
select count() from cards where nid not in (select id from notes)""")
assert not self.deck.db.scalar("""
select count() from facts where id not in (select distinct fid from cards)""")
for t in "cards", "facts", "revlog", "graves":
select count() from notes where id not in (select distinct nid from cards)""")
for t in "cards", "notes", "revlog", "graves":
assert not self.deck.db.scalar(
"select count() from %s where usn = -1" % t)
for g in self.deck.groups.all():
@ -142,9 +142,9 @@ select count() from facts where id not in (select distinct fid from cards)""")
assert m['usn'] != -1
return [
self.deck.db.scalar("select count() from cards"),
self.deck.db.scalar("select count() from facts"),
self.deck.db.scalar("select count() from notes"),
self.deck.db.scalar("select count() from revlog"),
self.deck.db.scalar("select count() from fsums"),
self.deck.db.scalar("select count() from nsums"),
self.deck.db.scalar("select count() from graves"),
len(self.deck.models.all()),
len(self.deck.tags.all()),
@ -171,7 +171,7 @@ select count() from facts where id not in (select distinct fid from cards)""")
##########################################################################
def prepareToChunk(self):
self.tablesLeft = ["revlog", "cards", "facts"]
self.tablesLeft = ["revlog", "cards", "notes"]
self.cursor = None
def cursorForTable(self, table):
@ -184,12 +184,12 @@ select id, cid, %d, ease, ivl, lastIvl, factor, time, type
from revlog where %s""" % d)
elif table == "cards":
return x("""
select id, fid, gid, ord, mod, %d, type, queue, due, ivl, factor, reps,
select id, nid, gid, ord, mod, %d, type, queue, due, ivl, factor, reps,
lapses, left, edue, flags, data from cards where %s""" % d)
else:
return x("""
select id, guid, mid, gid, mod, %d, tags, flds, '', flags, data
from facts where %s""" % d)
from notes where %s""" % d)
def chunk(self):
buf = dict(done=False)
@ -221,15 +221,15 @@ from facts where %s""" % d)
self.mergeRevlog(chunk['revlog'])
if "cards" in chunk:
self.mergeCards(chunk['cards'])
if "facts" in chunk:
self.mergeFacts(chunk['facts'])
if "notes" in chunk:
self.mergeNotes(chunk['notes'])
# Deletions
##########################################################################
def getGraves(self):
cards = []
facts = []
notes = []
groups = []
if self.deck.server:
curs = self.deck.db.execute(
@ -240,18 +240,18 @@ from facts where %s""" % d)
for oid, type in curs:
if type == REM_CARD:
cards.append(oid)
elif type == REM_FACT:
facts.append(oid)
elif type == REM_NOTE:
notes.append(oid)
else:
groups.append(oid)
if not self.deck.server:
self.deck.db.execute("update graves set usn=? where usn=-1",
self.maxUsn)
return dict(cards=cards, facts=facts, groups=groups)
return dict(cards=cards, notes=notes, groups=groups)
def mergeGraves(self, graves):
# facts first, so we don't end up with duplicate graves
self.deck._remFacts(graves['facts'])
# notes first, so we don't end up with duplicate graves
self.deck._remNotes(graves['notes'])
self.deck.remCards(graves['cards'])
for oid in graves['groups']:
self.deck.groups.rem(oid)
@ -326,7 +326,7 @@ from facts where %s""" % d)
def mergeTags(self, tags):
self.deck.tags.register(tags, usn=self.maxUsn)
# Cards/facts/revlog
# Cards/notes/revlog
##########################################################################
def mergeRevlog(self, logs):
@ -353,10 +353,10 @@ from facts where %s""" % d)
"(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
self.newerRows(cards, "cards", 4))
def mergeFacts(self, facts):
rows = self.newerRows(facts, "facts", 4)
def mergeNotes(self, notes):
rows = self.newerRows(notes, "notes", 4)
self.deck.db.executemany(
"insert or replace into facts values (?,?,?,?,?,?,?,?,?,?,?)",
"insert or replace into notes values (?,?,?,?,?,?,?,?,?,?,?)",
rows)
self.deck.updateFieldCache([f[0] for f in rows])

View file

@ -10,7 +10,7 @@ Anki maintains a cache of used tags so it can quickly present a list of tags
for autocomplete and in the browser. For efficiency, deletions are not
tracked, so unused tags can only be removed from the list with a DB check.
This module manages the tag cache and tags for facts.
This module manages the tag cache and tags for notes.
"""
class TagManager(object):
@ -45,17 +45,17 @@ class TagManager(object):
def all(self):
return self.tags.keys()
def registerFacts(self, fids=None):
"Add any missing tags from facts to the tags list."
def registerNotes(self, nids=None):
"Add any missing tags from notes to the tags list."
# when called without an argument, the old list is cleared first.
if fids:
lim = " where id in " + ids2str(fids)
if nids:
lim = " where id in " + ids2str(nids)
else:
lim = ""
self.tags = {}
self.changed = True
self.register(set(self.split(
" ".join(self.deck.db.list("select distinct tags from facts"+lim)))))
" ".join(self.deck.db.list("select distinct tags from notes"+lim)))))
def allItems(self):
return self.tags.items()
@ -63,7 +63,7 @@ class TagManager(object):
def save(self):
self.changed = True
# Bulk addition/removal from facts
# Bulk addition/removal from notes
#############################################################
def bulkAdd(self, ids, tags, add=True):
@ -73,7 +73,7 @@ class TagManager(object):
return
# cache tag names
self.register(newTags)
# find facts missing the tags
# find notes missing the tags
if add:
l = "tags not "
fn = self.addToStr
@ -83,18 +83,18 @@ class TagManager(object):
lim = " or ".join(
[l+"like :_%d" % c for c, t in enumerate(newTags)])
res = self.deck.db.all(
"select id, tags from facts where id in %s and %s" % (
"select id, tags from notes where id in %s and %s" % (
ids2str(ids), lim),
**dict([("_%d" % x, '%% %s %%' % y)
for x, y in enumerate(newTags)]))
# update tags
fids = []
nids = []
def fix(row):
fids.append(row[0])
nids.append(row[0])
return {'id': row[0], 't': fn(tags, row[1]), 'n':intTime(),
'u':self.deck.usn()}
self.deck.db.executemany(
"update facts set tags=:t,mod=:n,usn=:u where id = :id",
"update notes set tags=:t,mod=:n,usn=:u where id = :id",
[fix(row) for row in res])
def bulkRem(self, ids, tags):
@ -149,12 +149,12 @@ class TagManager(object):
# Tag-based selective study
##########################################################################
def selTagFids(self, yes, no):
def selTagNids(self, yes, no):
l = []
# find facts that match yes
# find notes that match yes
lim = ""
args = []
query = "select id from facts"
query = "select id from notes"
if not yes and not no:
pass
else:
@ -172,7 +172,7 @@ class TagManager(object):
return self.deck.db.list(query, *args)
def setGroupForTags(self, yes, no, gid):
fids = self.selTagFids(yes, no)
nids = self.selTagNids(yes, no)
self.deck.db.execute(
"update cards set gid=?,mod=?,usn=? where fid in "+ids2str(fids),
"update cards set gid=?,mod=?,usn=? where nid in "+ids2str(nids),
gid, intTime(), self.deck.usn())

View file

@ -133,7 +133,7 @@ analyze;""")
db.execute("pragma page_size = 4096")
db.execute("pragma legacy_file_format = 0")
# facts
# notes
###########
# tags should have a leading and trailing space if not empty, and not
# use commas
@ -177,7 +177,7 @@ select id, id, modelId, 1, cast(created*1000 as int), cast(modified as int),
# and put the facts into the new table
db.execute("drop table facts")
_addSchema(db, False)
db.executemany("insert into facts values (?,?,?,?,?,?,?,?,'',0,'')", data)
db.executemany("insert into notes values (?,?,?,?,?,?,?,?,'',0,'')", data)
db.execute("drop table fields")
# cards
@ -336,7 +336,7 @@ insert or replace into deck select id, cast(created as int), :t,
m['flds'] = self._fieldsForModel(row[0])
m['tmpls'] = self._templatesForModel(row[0], m['flds'])
mods[m['id']] = m
db.execute("update facts set mid = ? where mid = ?", t, row[0])
db.execute("update notes set mid = ? where mid = ?", t, row[0])
# save and clean up
db.execute("update deck set models = ?", simplejson.dumps(mods))
db.execute("drop table fieldModels")
@ -465,7 +465,7 @@ order by ordinal""", mid)):
# Media references
######################################################################
# In 2.0 we drop support for media and latex references in the template,
# since they require generating card templates to see what media a fact
# since they require generating card templates to see what media a note
# uses, and are confusing for shared deck users. To ease the upgrade
# process, we automatically convert the references to new fields.
@ -500,16 +500,16 @@ order by ordinal""", mid)):
# add the new field
f = deck.models.newField(fld)
deck.models.addField(m, f)
# loop through facts and write reference into new field
# loop through notes and write reference into new field
data = []
for id, flds in self.deck.db.execute(
"select id, flds from facts where id in "+
ids2str(deck.models.fids(m))):
"select id, flds from notes where id in "+
ids2str(deck.models.nids(m))):
sflds = splitFields(flds)
ref = all.replace(fname, pre+sflds[idx]+suf)
data.append((flds+ref, id))
# update facts
deck.db.executemany("update facts set flds=? where id=?",
# update notes
deck.db.executemany("update notes set flds=? where id=?",
data)
# note field for future
state['mflds'][fname] = fld
@ -544,7 +544,7 @@ order by ordinal""", mid)):
for t in m['tmpls']:
if not t['actv']:
if not d.db.scalar("""
select 1 from cards where fid in (select id from facts where mid = ?)
select 1 from cards where nid in (select id from notes where mid = ?)
and ord = ? limit 1""", m['id'], t['ord']):
remove.append(t)
del t['actv']
@ -582,7 +582,7 @@ and ord = ? limit 1""", m['id'], t['ord']):
deck.crt = int(time.mktime(d.timetuple()))
deck.sched._updateCutoff()
# update uniq cache
deck.updateFieldCache(deck.db.list("select id from facts"))
deck.updateFieldCache(deck.db.list("select id from notes"))
# remove old views
for v in ("failedCards", "revCardsOld", "revCardsNew",
"revCardsDue", "revCardsRandom", "acqCardsRandom",
@ -595,11 +595,11 @@ and ord = ? limit 1""", m['id'], t['ord']):
deck.db.execute("update cards set queue=-2 where queue between 3 and 5")
deck.db.execute("update cards set queue=-3 where queue between 6 and 8")
# remove old deleted tables
for t in ("cards", "facts", "models", "media"):
for t in ("cards", "notes", "models", "media"):
deck.db.execute("drop table if exists %sDeleted" % t)
# rewrite due times for new cards
deck.db.execute("""
update cards set due = fid where type=0""")
update cards set due = nid where type=0""")
# and failed cards
left = len(deck.groups.conf(1)['new']['delays'])
deck.db.execute("update cards set edue = ?, left=? where type = 1",
@ -614,7 +614,7 @@ update cards set due = cast(
if deck.models.randomNew():
deck.sched.randomizeCards()
# update insertion id
deck.conf['nextPos'] = deck.db.scalar("select max(id) from facts")+1
deck.conf['nextPos'] = deck.db.scalar("select max(id) from notes")+1
deck.save()
# optimize and finish

View file

@ -185,7 +185,7 @@ def timestampID(db, table):
def maxID(db):
"Return the first safe ID to use."
now = intTime(1000)
for tbl in "cards", "facts":
for tbl in "cards", "notes":
now = max(now, db.scalar(
"select max(id) from %s" % tbl))
return now + 1

View file

@ -15,12 +15,12 @@ def setup1():
deck = Deck()
deck.addModel(BasicModel())
deck.currentModel.cardModels[1].active = True
f = deck.newFact()
f = deck.newNote()
f['Front'] = u"foo"; f['Back'] = u"bar"; f.tags = u"tag, tag2"
deck.addFact(f)
f = deck.newFact()
deck.addNote(f)
f = deck.newNote()
f['Front'] = u"baz"; f['Back'] = u"qux"
deck.addFact(f)
deck.addNote(f)
##########################################################################
@ -53,8 +53,8 @@ def test_export_textcard():
e.exportInto(f)
@nose.with_setup(setup1)
def test_export_textfact():
e = TextFactExporter(deck)
def test_export_textnote():
e = TextNoteExporter(deck)
f = unicode(tempfile.mkstemp(prefix="ankitest")[1])
os.unlink(f)
e.exportInto(f)

View file

@ -8,7 +8,7 @@ from tests.shared import getEmptyDeck
def test_previewCards():
deck = getEmptyDeck()
f = deck.newFact()
f = deck.newNote()
f['Front'] = u'1'
f['Back'] = u'2'
# non-empty and active
@ -18,8 +18,8 @@ def test_previewCards():
# all templates
cards = deck.previewCards(f, 2)
assert len(cards) == 1
# add the fact, and test existing preview
deck.addFact(f)
# add the note, and test existing preview
deck.addNote(f)
cards = deck.previewCards(f, 1)
assert len(cards) == 1
assert cards[0].ord == 0
@ -28,29 +28,29 @@ def test_previewCards():
def test_delete():
deck = getEmptyDeck()
f = deck.newFact()
f = deck.newNote()
f['Front'] = u'1'
f['Back'] = u'2'
deck.addFact(f)
deck.addNote(f)
cid = f.cards()[0].id
deck.reset()
deck.sched.answerCard(deck.sched.getCard(), 2)
assert deck.db.scalar("select count() from revlog") == 1
deck.remCards([cid])
assert deck.cardCount() == 0
assert deck.factCount() == 0
assert deck.db.scalar("select count() from facts") == 0
assert deck.noteCount() == 0
assert deck.db.scalar("select count() from notes") == 0
assert deck.db.scalar("select count() from cards") == 0
assert deck.db.scalar("select count() from fsums") == 0
assert deck.db.scalar("select count() from nsums") == 0
assert deck.db.scalar("select count() from revlog") == 0
assert deck.db.scalar("select count() from graves") == 2
def test_misc():
d = getEmptyDeck()
f = d.newFact()
f = d.newNote()
f['Front'] = u'1'
f['Back'] = u'2'
d.addFact(f)
d.addNote(f)
c = f.cards()[0]
id = d.models.current()['id']
assert c.template()['ord'] == 0

View file

@ -41,12 +41,12 @@ def test_openReadOnly():
os.chmod(newPath, 0666)
os.unlink(newPath)
def test_factAddDelete():
def test_noteAddDelete():
deck = getEmptyDeck()
# add a fact
f = deck.newFact()
# add a note
f = deck.newNote()
f['Front'] = u"one"; f['Back'] = u"two"
n = deck.addFact(f)
n = deck.addNote(f)
assert n == 1
# test multiple cards - add another template
m = deck.models.current(); mm = deck.models
@ -61,10 +61,10 @@ def test_factAddDelete():
# should generate cards on close
mm.save(m, gencards=True)
assert deck.cardCount() == 2
# creating new facts should use both cards
f = deck.newFact()
# creating new notes should use both cards
f = deck.newNote()
f['Front'] = u"three"; f['Back'] = u"four"
n = deck.addFact(f)
n = deck.addNote(f)
assert n == 2
assert deck.cardCount() == 4
# check q/a generation
@ -74,7 +74,7 @@ def test_factAddDelete():
for p in f.problems():
assert not p
# now let's make a duplicate and test uniqueness
f2 = deck.newFact()
f2 = deck.newNote()
f2.model()['flds'][1]['req'] = True
f2['Front'] = u"one"; f2['Back'] = u""
p = f2.problems()
@ -84,66 +84,66 @@ def test_factAddDelete():
cards = f.cards()
id1 = cards[0].id; id2 = cards[1].id
assert deck.cardCount() == 4
assert deck.factCount() == 2
assert deck.noteCount() == 2
deck.remCards([id1])
assert deck.cardCount() == 3
assert deck.factCount() == 2
# and the second should clear the fact
assert deck.noteCount() == 2
# and the second should clear the note
deck.remCards([id2])
assert deck.cardCount() == 2
assert deck.factCount() == 1
assert deck.noteCount() == 1
def test_fieldChecksum():
deck = getEmptyDeck()
f = deck.newFact()
f = deck.newNote()
f['Front'] = u"new"; f['Back'] = u"new2"
deck.addFact(f)
deck.addNote(f)
assert deck.db.scalar(
"select csum from fsums") == int("c2a6b03f", 16)
"select csum from nsums") == int("c2a6b03f", 16)
# empty field should have no checksum
f['Front'] = u""
f.flush()
assert deck.db.scalar(
"select count() from fsums") == 0
"select count() from nsums") == 0
# changing the val should change the checksum
f['Front'] = u"newx"
f.flush()
assert deck.db.scalar(
"select csum from fsums") == int("302811ae", 16)
# turning off unique and modifying the fact should delete the sum
"select csum from nsums") == int("302811ae", 16)
# turning off unique and modifying the note should delete the sum
m = f.model()
m['flds'][0]['uniq'] = False
deck.models.save(m)
f.flush()
assert deck.db.scalar(
"select count() from fsums") == 0
"select count() from nsums") == 0
# and turning on both should ensure two checksums generated
m['flds'][0]['uniq'] = True
m['flds'][1]['uniq'] = True
deck.models.save(m)
f.flush()
assert deck.db.scalar(
"select count() from fsums") == 2
"select count() from nsums") == 2
def test_selective():
deck = getEmptyDeck()
f = deck.newFact()
f = deck.newNote()
f['Front'] = u"1"; f.tags = ["one", "three"]
deck.addFact(f)
f = deck.newFact()
deck.addNote(f)
f = deck.newNote()
f['Front'] = u"2"; f.tags = ["two", "three", "four"]
deck.addFact(f)
f = deck.newFact()
deck.addNote(f)
f = deck.newNote()
f['Front'] = u"3"; f.tags = ["one", "two", "three", "four"]
deck.addFact(f)
assert len(deck.tags.selTagFids(["one"], [])) == 2
assert len(deck.tags.selTagFids(["three"], [])) == 3
assert len(deck.tags.selTagFids([], ["three"])) == 0
assert len(deck.tags.selTagFids(["one"], ["three"])) == 0
assert len(deck.tags.selTagFids(["one"], ["two"])) == 1
assert len(deck.tags.selTagFids(["two", "three"], [])) == 3
assert len(deck.tags.selTagFids(["two", "three"], ["one"])) == 1
assert len(deck.tags.selTagFids(["one", "three"], ["two", "four"])) == 1
deck.addNote(f)
assert len(deck.tags.selTagNids(["one"], [])) == 2
assert len(deck.tags.selTagNids(["three"], [])) == 3
assert len(deck.tags.selTagNids([], ["three"])) == 0
assert len(deck.tags.selTagNids(["one"], ["three"])) == 0
assert len(deck.tags.selTagNids(["one"], ["two"])) == 1
assert len(deck.tags.selTagNids(["two", "three"], [])) == 3
assert len(deck.tags.selTagNids(["two", "three"], ["one"])) == 1
assert len(deck.tags.selTagNids(["one", "three"], ["two", "four"])) == 1
deck.tags.setGroupForTags(["three"], [], 3)
assert deck.db.scalar("select count() from cards where gid = 3") == 3
deck.tags.setGroupForTags(["one"], [], 2)
@ -151,12 +151,12 @@ def test_selective():
def test_addDelTags():
deck = getEmptyDeck()
f = deck.newFact()
f = deck.newNote()
f['Front'] = u"1"
deck.addFact(f)
f2 = deck.newFact()
deck.addNote(f)
f2 = deck.newNote()
f2['Front'] = u"2"
deck.addFact(f2)
deck.addNote(f2)
# adding for a given id
deck.tags.bulkAdd([f.id], "foo")
f.load(); f2.load()

View file

@ -4,25 +4,25 @@ from tests.shared import getEmptyDeck
def test_findCards():
deck = getEmptyDeck()
f = deck.newFact()
f = deck.newNote()
f['Front'] = u'dog'
f['Back'] = u'cat'
f.tags.append(u"monkey")
f1id = f.id
deck.addFact(f)
deck.addNote(f)
firstCardId = f.cards()[0].id
f = deck.newFact()
f = deck.newNote()
f['Front'] = u'goats are fun'
f['Back'] = u'sheep'
f.tags.append(u"sheep goat horse")
deck.addFact(f)
deck.addNote(f)
f2id = f.id
f = deck.newFact()
f = deck.newNote()
f['Front'] = u'cat'
f['Back'] = u'sheep'
deck.addFact(f)
deck.addNote(f)
catCard = f.cards()[0]
f = deck.newFact()
f = deck.newNote()
f['Front'] = u'template test'
f['Back'] = u'foo bar'
m = deck.models.current(); mm = deck.models
@ -31,7 +31,7 @@ def test_findCards():
t['afmt'] = "{{Front}}"
mm.addTemplate(m, t)
mm.save(m)
deck.addFact(f)
deck.addNote(f)
latestCardIds = [c.id for c in f.cards()]
# tag searches
assert not deck.findCards("tag:donkey")
@ -41,11 +41,11 @@ def test_findCards():
assert len(deck.findCards("tag:monkey")) == 1
assert len(deck.findCards("tag:sheep -tag:monkey")) == 1
assert len(deck.findCards("-tag:sheep")) == 4
deck.tags.bulkAdd(deck.db.list("select id from facts"), "foo bar")
deck.tags.bulkAdd(deck.db.list("select id from notes"), "foo bar")
assert (len(deck.findCards("tag:foo")) ==
len(deck.findCards("tag:bar")) ==
5)
deck.tags.bulkRem(deck.db.list("select id from facts"), "foo")
deck.tags.bulkRem(deck.db.list("select id from notes"), "foo")
assert len(deck.findCards("tag:foo")) == 0
assert len(deck.findCards("tag:bar")) == 5
# text searches
@ -71,10 +71,10 @@ def test_findCards():
import time; time.sleep(1)
c.flush()
assert deck.findCards("is:suspended") == [c.id]
# fids
assert deck.findCards("fid:54321") == []
assert len(deck.findCards("fid:%d"%f.id)) == 2
assert len(deck.findCards("fid:%d,%d" % (f1id, f2id))) == 2
# nids
assert deck.findCards("nid:54321") == []
assert len(deck.findCards("nid:%d"%f.id)) == 2
assert len(deck.findCards("nid:%d,%d" % (f1id, f2id))) == 2
# templates
assert len(deck.findCards("card:foo")) == 0
assert len(deck.findCards("card:forward")) == 4
@ -89,10 +89,10 @@ def test_findCards():
assert len(deck.findCards("-back:sheep")) == 3
assert len(deck.findCards("front:")) == 5
# ordering
deck.conf['sortType'] = "factCrt"
deck.conf['sortType'] = "noteCrt"
assert deck.findCards("front:")[-1] in latestCardIds
assert deck.findCards("")[-1] in latestCardIds
deck.conf['sortType'] = "factFld"
deck.conf['sortType'] = "noteFld"
assert deck.findCards("")[0] == catCard.id
assert deck.findCards("")[-1] in latestCardIds
deck.conf['sortType'] = "cardMod"
@ -109,10 +109,10 @@ def test_findCards():
assert len(deck.findCards("-group:default")) == 0
assert len(deck.findCards("-group:foo")) == 5
# full search
f = deck.newFact()
f = deck.newNote()
f['Front'] = u'hello<b>world</b>'
f['Back'] = u''
deck.addFact(f)
deck.addNote(f)
assert len(deck.findCards("helloworld")) == 0
assert len(deck.findCards("helloworld", full=True)) == 1
assert len(deck.findCards("front:helloworld")) == 0
@ -122,27 +122,27 @@ def test_findCards():
def test_findReplace():
deck = getEmptyDeck()
f = deck.newFact()
f = deck.newNote()
f['Front'] = u'foo'
f['Back'] = u'bar'
deck.addFact(f)
f2 = deck.newFact()
deck.addNote(f)
f2 = deck.newNote()
f2['Front'] = u'baz'
f2['Back'] = u'foo'
deck.addFact(f2)
fids = [f.id, f2.id]
deck.addNote(f2)
nids = [f.id, f2.id]
# should do nothing
assert deck.findReplace(fids, "abc", "123") == 0
assert deck.findReplace(nids, "abc", "123") == 0
# global replace
assert deck.findReplace(fids, "foo", "qux") == 2
assert deck.findReplace(nids, "foo", "qux") == 2
f.load(); assert f['Front'] == "qux"
f2.load(); assert f2['Back'] == "qux"
# single field replace
assert deck.findReplace(fids, "qux", "foo", field="Front") == 1
assert deck.findReplace(nids, "qux", "foo", field="Front") == 1
f.load(); assert f['Front'] == "foo"
f2.load(); assert f2['Back'] == "qux"
# regex replace
assert deck.findReplace(fids, "B.r", "reg") == 0
assert deck.findReplace(nids, "B.r", "reg") == 0
f.load(); assert f['Back'] != "reg"
assert deck.findReplace(fids, "B.r", "reg", regex=True) == 1
assert deck.findReplace(nids, "B.r", "reg", regex=True) == 1
f.load(); assert f['Back'] == "reg"

View file

@ -39,12 +39,12 @@ def test_remove():
deck = getEmptyDeck()
# can't remove the default group
assertException(AssertionError, lambda: deck.groups.rem(1))
# create a new group, and add a fact/card to it
# create a new group, and add a note/card to it
g1 = deck.groups.id("g1")
f = deck.newFact()
f = deck.newNote()
f['Front'] = u"1"
f.gid = g1
deck.addFact(f)
deck.addNote(f)
c = f.cards()[0]
assert c.gid == g1
# by default deleting the group leaves the cards with an invalid gid
@ -58,10 +58,10 @@ def test_remove():
# let's create another group and explicitly set the card to it
g2 = deck.groups.id("g2")
c.gid = g2; c.flush()
# this time we'll delete the card/fact too
# this time we'll delete the card/note too
deck.groups.rem(g2, cardsToo=True)
assert deck.cardCount() == 0
assert deck.factCount() == 0
assert deck.noteCount() == 0
def test_rename():
d = getEmptyDeck()

View file

@ -8,23 +8,23 @@ from anki.errors import *
from anki import Deck
from anki.importing import Anki1Importer, Anki2Importer, TextImporter, \
SupermemoXmlImporter
from anki.facts import Fact
from anki.notes import Note
from anki.db import *
testDir = os.path.dirname(__file__)
srcFacts=None
srcNotes=None
srcCards=None
def test_anki2():
global srcFacts, srcCards
global srcNotes, srcCards
# get the deck to import
tmp = getUpgradeDeckPath()
u = Upgrader()
src = u.upgrade(tmp)
srcpath = src.path
srcFacts = src.factCount()
srcNotes = src.noteCount()
srcCards = src.cardCount()
srcRev = src.db.scalar("select count() from revlog")
# add a media file for testing
@ -36,14 +36,14 @@ def test_anki2():
imp = Anki2Importer(dst, srcpath)
imp.run()
def check():
assert dst.factCount() == srcFacts
assert dst.noteCount() == srcNotes
assert dst.cardCount() == srcCards
assert srcRev == dst.db.scalar("select count() from revlog")
mids = [int(x) for x in dst.models.models.keys()]
assert not dst.db.scalar(
"select count() from facts where mid not in "+ids2str(mids))
"select count() from notes where mid not in "+ids2str(mids))
assert not dst.db.scalar(
"select count() from cards where fid not in (select id from facts)")
"select count() from cards where nid not in (select id from notes)")
assert not dst.db.scalar(
"select count() from revlog where cid not in (select id from cards)")
assert dst.fixIntegrity().startswith("Database rebuilt")
@ -68,7 +68,7 @@ def test_anki1():
imp = Anki1Importer(dst, tmp)
imp.run()
def check():
assert dst.factCount() == srcFacts
assert dst.noteCount() == srcNotes
assert dst.cardCount() == srcCards
assert len(os.listdir(dst.media.dir())) == 1
check()
@ -96,9 +96,9 @@ def test_csv_tags():
file = unicode(os.path.join(testDir, "importing/text-tags.txt"))
i = csvfile.TextImporter(deck, file)
i.run()
facts = deck.db.query(Fact).all()
assert len(facts) == 2
assert facts[0].tags == "baz qux" or facts[1].tags == "baz qux"
notes = deck.db.query(Note).all()
assert len(notes) == 2
assert notes[0].tags == "baz qux" or notes[1].tags == "baz qux"
deck.close()
def test_supermemo_xml_01_unicode():

View file

@ -10,10 +10,10 @@ def test_latex():
# change latex cmd to simulate broken build
import anki.latex
anki.latex.latexCmd[0] = "nolatex"
# add a fact with latex
f = d.newFact()
# add a note with latex
f = d.newNote()
f['Front'] = u"[latex]hello[/latex]"
d.addFact(f)
d.addNote(f)
# but since latex couldn't run, there's nothing there
assert len(os.listdir(d.media.dir())) == 0
# check the error message
@ -31,25 +31,25 @@ def test_latex():
d.media.check()
assert len(os.listdir(d.media.dir())) == 1
assert ".png" in f.cards()[0].q()
# adding new facts should cause generation on question display
f = d.newFact()
# adding new notes should cause generation on question display
f = d.newNote()
f['Front'] = u"[latex]world[/latex]"
d.addFact(f)
d.addNote(f)
f.cards()[0].q()
assert len(os.listdir(d.media.dir())) == 2
# another fact with the same media should reuse
f = d.newFact()
# another note with the same media should reuse
f = d.newNote()
f['Front'] = u" [latex]world[/latex]"
d.addFact(f)
d.addNote(f)
assert len(os.listdir(d.media.dir())) == 2
oldcard = f.cards()[0]
assert ".png" in oldcard.q()
# if we turn off building, then previous cards should work, but cards with
# missing media will show the latex
anki.latex.build = False
f = d.newFact()
f = d.newNote()
f['Front'] = u"[latex]foo[/latex]"
d.addFact(f)
d.addNote(f)
assert len(os.listdir(d.media.dir())) == 2
assert stripHTML(f.cards()[0].q()) == "[latex]foo[/latex]"
assert ".png" in oldcard.q()

View file

@ -46,14 +46,14 @@ def test_deckIntegration():
# put a file into it
file = unicode(os.path.join(testDir, "support/fake.png"))
d.media.addFile(file)
# add a fact which references it
f = d.newFact()
# add a note which references it
f = d.newNote()
f['Front'] = u"one"; f['Back'] = u"<img src='fake.png'>"
d.addFact(f)
d.addNote(f)
# and one which references a non-existent file
f = d.newFact()
f = d.newNote()
f['Front'] = u"one"; f['Back'] = u"<img src='fake2.png'>"
d.addFact(f)
d.addNote(f)
# and add another file which isn't used
open(os.path.join(d.media.dir(), "foo.jpg"), "wb").write("test")
# check media

View file

@ -5,10 +5,10 @@ from anki.utils import stripHTML
def test_modelDelete():
deck = getEmptyDeck()
f = deck.newFact()
f = deck.newNote()
f['Front'] = u'1'
f['Back'] = u'2'
deck.addFact(f)
deck.addNote(f)
assert deck.cardCount() == 1
deck.models.rem(deck.models.current())
assert deck.cardCount() == 0
@ -28,10 +28,10 @@ def test_modelCopy():
def test_fields():
d = getEmptyDeck()
f = d.newFact()
f = d.newNote()
f['Front'] = u'1'
f['Back'] = u'2'
d.addFact(f)
d.addNote(f)
m = d.models.current()
# make sure renaming a field updates the templates
d.models.renameField(m, m['flds'][0], "NewFront")
@ -41,37 +41,37 @@ def test_fields():
f = d.models.newField(m)
f['name'] = "foo"
d.models.addField(m, f)
assert d.getFact(d.models.fids(m)[0]).fields == ["1", "2", ""]
assert d.getNote(d.models.nids(m)[0]).fields == ["1", "2", ""]
assert d.models.scmhash(m) != h
# rename it
d.models.renameField(m, f, "bar")
assert d.getFact(d.models.fids(m)[0])['bar'] == ''
assert d.getNote(d.models.nids(m)[0])['bar'] == ''
# delete back
d.models.remField(m, m['flds'][1])
assert d.getFact(d.models.fids(m)[0]).fields == ["1", ""]
assert d.getNote(d.models.nids(m)[0]).fields == ["1", ""]
# move 0 -> 1
d.models.moveField(m, m['flds'][0], 1)
assert d.getFact(d.models.fids(m)[0]).fields == ["", "1"]
assert d.getNote(d.models.nids(m)[0]).fields == ["", "1"]
# move 1 -> 0
d.models.moveField(m, m['flds'][1], 0)
assert d.getFact(d.models.fids(m)[0]).fields == ["1", ""]
assert d.getNote(d.models.nids(m)[0]).fields == ["1", ""]
# add another and put in middle
f = d.models.newField(m)
f['name'] = "baz"
d.models.addField(m, f)
f = d.getFact(d.models.fids(m)[0])
f = d.getNote(d.models.nids(m)[0])
f['baz'] = "2"
f.flush()
assert d.getFact(d.models.fids(m)[0]).fields == ["1", "", "2"]
assert d.getNote(d.models.nids(m)[0]).fields == ["1", "", "2"]
# move 2 -> 1
d.models.moveField(m, m['flds'][2], 1)
assert d.getFact(d.models.fids(m)[0]).fields == ["1", "2", ""]
assert d.getNote(d.models.nids(m)[0]).fields == ["1", "2", ""]
# move 0 -> 2
d.models.moveField(m, m['flds'][0], 2)
assert d.getFact(d.models.fids(m)[0]).fields == ["2", "", "1"]
assert d.getNote(d.models.nids(m)[0]).fields == ["2", "", "1"]
# move 0 -> 1
d.models.moveField(m, m['flds'][0], 1)
assert d.getFact(d.models.fids(m)[0]).fields == ["", "2", "1"]
assert d.getNote(d.models.nids(m)[0]).fields == ["", "2", "1"]
def test_templates():
d = getEmptyDeck()
@ -81,10 +81,10 @@ def test_templates():
t['afmt'] = "{{Front}}"
mm.addTemplate(m, t)
mm.save(m)
f = d.newFact()
f = d.newNote()
f['Front'] = u'1'
f['Back'] = u'2'
d.addFact(f)
d.addNote(f)
assert d.cardCount() == 2
(c, c2) = f.cards()
# first card should have first ord
@ -102,7 +102,7 @@ def test_templates():
c = f.cards()[0]
assert c.ord == 0
stripHTML(c.q()) == "2"
# it shouldn't be possible to orphan facts by removing templates
# it shouldn't be possible to orphan notes by removing templates
assert not d.models.remTemplate(m, m['tmpls'][0])
def test_text():
@ -110,22 +110,22 @@ def test_text():
m = d.models.current()
m['tmpls'][0]['qfmt'] = "{{text:Front}}"
d.models.save(m)
f = d.newFact()
f = d.newNote()
f['Front'] = u'hello<b>world'
d.addFact(f)
d.addNote(f)
assert "helloworld" in f.cards()[0].q()
def test_cloze():
d = getEmptyDeck()
d.models.setCurrent(d.models.byName("Cloze"))
f = d.newFact()
f = d.newNote()
assert f.model()['name'] == "Cloze"
# a cloze model with no clozes is empty
f['Text'] = u'nothing'
assert d.addFact(f) == 0
assert d.addNote(f) == 0
# try with one cloze
f['Text'] = "hello {{c1::world}}"
assert d.addFact(f) == 1
assert d.addNote(f) == 1
assert "hello <span class=cloze>[...]</span>" in f.cards()[0].q()
# the default is no context
assert "<span class=cloze>world</span>" in f.cards()[0].a()
@ -134,15 +134,15 @@ def test_cloze():
f.model()['clozectx'] = True
assert "hello <span class=cloze>world</span>" in f.cards()[0].a()
# and with a comment
f = d.newFact()
f = d.newNote()
f['Text'] = "hello {{c1::world::typical}}"
assert d.addFact(f) == 1
assert d.addNote(f) == 1
assert "<span class=cloze>[...(typical)]</span>" in f.cards()[0].q()
assert "<span class=cloze>world</span>" in f.cards()[0].a()
# and with 2 clozes
f = d.newFact()
f = d.newNote()
f['Text'] = "hello {{c1::world}} {{c2::bar}}"
assert d.addFact(f) == 2
assert d.addNote(f) == 2
(c1, c2) = f.cards()
assert "<span class=cloze>[...]</span> bar" in c1.q()
assert "<span class=cloze>world</span> bar" in c1.a()
@ -151,21 +151,21 @@ def test_cloze():
# if there are multiple answers for a single cloze, they are given in a
# list
f.model()['clozectx'] = False
f = d.newFact()
f = d.newNote()
f['Text'] = "a {{c1::b}} {{c1::c}}"
assert d.addFact(f) == 1
assert d.addNote(f) == 1
assert "<span class=cloze>b</span>, <span class=cloze>c</span>" in (
f.cards()[0].a())
# clozes should be supported in sections too
m = d.models.current()
m['tmpls'][0]['qfmt'] = "{{#cloze:1:Text}}{{Notes}}{{/cloze:1:Text}}"
d.models.save(m)
f = d.newFact()
f = d.newNote()
f['Text'] = "hello"
f['Notes'] = "world"
assert d.addFact(f) == 0
assert d.addNote(f) == 0
f['Text'] = "hello {{c1::foo}}"
assert d.addFact(f) == 1
assert d.addNote(f) == 1
# deleting a cloze should fail; the ui should clean up invalid cards
cnt = d.cardCount()
f['Text'] = "hello"
@ -181,17 +181,17 @@ def test_modelChange():
deck = getEmptyDeck()
basic = deck.models.byName("Basic")
cloze = deck.models.byName("Cloze")
# enable second template and add a fact
# enable second template and add a note
m = deck.models.current(); mm = deck.models
t = mm.newTemplate("Reverse")
t['qfmt'] = "{{Back}}"
t['afmt'] = "{{Front}}"
mm.addTemplate(m, t)
mm.save(m)
f = deck.newFact()
f = deck.newNote()
f['Front'] = u'f'
f['Back'] = u'b'
deck.addFact(f)
deck.addNote(f)
# switch fields
map = {0: 1, 1: 0}
deck.models.change(basic, [f.id], basic, map, None)
@ -231,11 +231,11 @@ def test_modelChange():
f.load()
assert f['Front'] == ''
assert f['Back'] == 'f'
# another fact to try model conversion
f = deck.newFact()
# another note to try model conversion
f = deck.newNote()
f['Front'] = u'f2'
f['Back'] = u'b2'
deck.addFact(f)
deck.addNote(f)
assert deck.models.useCount(basic) == 2
assert deck.models.useCount(cloze) == 0
map = {0: 0, 1: 1}

View file

@ -8,7 +8,7 @@ from anki import Deck
from anki.utils import intTime
from anki.sync import Syncer, FullSyncer, LocalServer, RemoteServer, \
MediaSyncer, RemoteMediaServer
from anki.facts import Fact
from anki.notes import Note
from anki.cards import Card
from tests.shared import getEmptyDeck

View file

@ -13,10 +13,10 @@ def test_basics():
def test_new():
d = getEmptyDeck()
assert d.sched.newCount == 0
# add a fact
f = d.newFact()
# add a note
f = d.newNote()
f['Front'] = u"one"; f['Back'] = u"two"
d.addFact(f)
d.addNote(f)
d.reset()
assert d.sched.newCount == 1
# fetch it
@ -38,12 +38,12 @@ def test_new():
t['afmt'] = "{{Front}}"
mm.addTemplate(m, t)
mm.save(m)
f = d.newFact()
f = d.newNote()
f['Front'] = u"2"; f['Back'] = u"2"
d.addFact(f)
f = d.newFact()
d.addNote(f)
f = d.newNote()
f['Front'] = u"3"; f['Back'] = u"3"
d.addFact(f)
d.addNote(f)
d.reset()
qs = ("2", "3", "2", "3")
for n in range(4):
@ -53,14 +53,14 @@ def test_new():
def test_newLimits():
d = getEmptyDeck()
# add some facts
# add some notes
g2 = d.groups.id("Default::foo")
for i in range(30):
f = d.newFact()
f = d.newNote()
f['Front'] = str(i)
if i > 4:
f.gid = g2
d.addFact(f)
d.addNote(f)
# give the child group a different configuration
c2 = d.groups.confId("new conf")
d.groups.setConf(d.groups.get(g2), c2)
@ -92,11 +92,11 @@ def test_newOrder():
t['actv'] = i > 25
d.models.addTemplate(m, t)
d.models.save(m)
f = d.newFact()
f = d.newNote()
f['Front'] = u'1'
f['Back'] = u'2'
# add first half
d.addFact(f)
d.addNote(f)
# generate second half
d.db.execute("update cards set gid = random()")
d.conf['newPerDay'] = 100
@ -106,9 +106,9 @@ def test_newOrder():
def test_newBoxes():
d = getEmptyDeck()
f = d.newFact()
f = d.newNote()
f['Front'] = u"one"
d.addFact(f)
d.addNote(f)
d.reset()
c = d.sched.getCard()
d.sched._cardConf(c)['new']['delays'] = [1,2,3,4,5]
@ -119,10 +119,10 @@ def test_newBoxes():
def test_learn():
d = getEmptyDeck()
# add a fact
f = d.newFact()
# add a note
f = d.newNote()
f['Front'] = u"one"; f['Back'] = u"two"
f = d.addFact(f)
f = d.addNote(f)
# set as a learn card and rebuild queues
d.db.execute("update cards set queue=0, type=0")
d.reset()
@ -191,10 +191,10 @@ def test_learn():
def test_reviews():
d = getEmptyDeck()
# add a fact
f = d.newFact()
# add a note
f = d.newNote()
f['Front'] = u"one"; f['Back'] = u"two"
d.addFact(f)
d.addNote(f)
# set the card up as a review card, due 8 days ago
c = f.cards()[0]
c.type = 2
@ -286,9 +286,9 @@ def test_finished():
# nothing due
assert "Congratulations" in d.sched.finishedMsg()
assert "limit" not in d.sched.finishedMsg()
f = d.newFact()
f = d.newNote()
f['Front'] = u"one"; f['Back'] = u"two"
d.addFact(f)
d.addNote(f)
# have a new card
assert "new cards available" in d.sched.finishedMsg()
# turn it into a review
@ -301,9 +301,9 @@ def test_finished():
def test_nextIvl():
d = getEmptyDeck()
f = d.newFact()
f = d.newNote()
f['Front'] = u"one"; f['Back'] = u"two"
d.addFact(f)
d.addNote(f)
d.reset()
c = d.sched.getCard()
d.sched._cardConf(c)['new']['delays'] = [0.5, 3, 10]
@ -356,12 +356,12 @@ def test_nextIvl():
def test_misc():
d = getEmptyDeck()
f = d.newFact()
f = d.newNote()
f['Front'] = u"one"
d.addFact(f)
d.addNote(f)
c = f.cards()[0]
# burying
d.sched.buryFact(c.fid)
d.sched.buryNote(c.nid)
d.reset()
assert not d.sched.getCard()
d.sched.onClose()
@ -370,9 +370,9 @@ def test_misc():
def test_suspend():
d = getEmptyDeck()
f = d.newFact()
f = d.newNote()
f['Front'] = u"one"
d.addFact(f)
d.addNote(f)
c = f.cards()[0]
# suspending
d.reset()
@ -403,9 +403,9 @@ def test_cram():
print "disabled for now"
return
d = getEmptyDeck()
f = d.newFact()
f = d.newNote()
f['Front'] = u"one"
d.addFact(f)
d.addNote(f)
c = f.cards()[0]
c.ivl = 100
c.type = c.queue = 2
@ -485,9 +485,9 @@ def test_cramLimits():
d = getEmptyDeck()
# create three cards, due tomorrow, the next, etc
for i in range(3):
f = d.newFact()
f = d.newNote()
f['Front'] = str(i)
d.addFact(f)
d.addNote(f)
c = f.cards()[0]
c.type = c.queue = 2
c.due = d.sched.today + 1 + i
@ -539,10 +539,10 @@ def test_adjIvl():
t['afmt'] = "{{Back}}"
d.models.addTemplate(m, t)
d.models.save(m)
# create a new fact; it should have 4 cards
f = d.newFact()
# create a new note; it should have 4 cards
f = d.newNote()
f['Front'] = "1"; f['Back'] = "1"
d.addFact(f)
d.addNote(f)
assert d.cardCount() == 4
d.reset()
# immediately remove first; it should get ideal ivl
@ -561,10 +561,10 @@ def test_adjIvl():
c = d.sched.getCard()
d.sched.answerCard(c, 3)
assert c.ivl == 4
# try again with another fact
f = d.newFact()
# try again with another note
f = d.newNote()
f['Front'] = "2"; f['Back'] = "2"
d.addFact(f)
d.addNote(f)
d.reset()
# set a minSpacing of 0
conf = d.sched._cardConf(c)
@ -601,10 +601,10 @@ def test_ordcycle():
t['afmt'] = "{{Back}}"
mm.addTemplate(m, t)
mm.save(m)
# create a new fact; it should have 3 cards
f = d.newFact()
# create a new note; it should have 3 cards
f = d.newNote()
f['Front'] = "1"; f['Back'] = "1"
d.addFact(f)
d.addNote(f)
assert d.cardCount() == 3
d.reset()
# ordinals should arrive in order
@ -620,10 +620,10 @@ def test_cardcounts():
for type in range(3):
# and each of the groups
for gid in (1,grp):
# create a new fact
f = d.newFact()
# create a new note
f = d.newNote()
f['Front'] = u"one"
d.addFact(f)
d.addNote(f)
c = f.cards()[0]
# set type/gid
c.type = type
@ -641,9 +641,9 @@ def test_cardcounts():
def test_counts_idx():
d = getEmptyDeck()
f = d.newFact()
f = d.newNote()
f['Front'] = u"one"; f['Back'] = u"two"
d.addFact(f)
d.addNote(f)
d.reset()
assert d.sched.cardCounts() == (1, 0, 0)
c = d.sched.getCard()
@ -663,9 +663,9 @@ def test_counts_idx():
def test_repCounts():
d = getEmptyDeck()
f = d.newFact()
f = d.newNote()
f['Front'] = u"one"
d.addFact(f)
d.addNote(f)
d.reset()
# lrnReps should be accurate on pass/fail
assert d.sched.repCounts() == (1, 0, 0)
@ -681,9 +681,9 @@ def test_repCounts():
assert d.sched.repCounts() == (0, 1, 0)
d.sched.answerCard(d.sched.getCard(), 2)
assert d.sched.repCounts() == (0, 0, 0)
f = d.newFact()
f = d.newNote()
f['Front'] = u"two"
d.addFact(f)
d.addNote(f)
d.reset()
# initial pass should be correct too
d.sched.answerCard(d.sched.getCard(), 2)
@ -693,16 +693,16 @@ def test_repCounts():
d.sched.answerCard(d.sched.getCard(), 3)
assert d.sched.repCounts() == (0, 0, 0)
# immediate graduate should work
f = d.newFact()
f = d.newNote()
f['Front'] = u"three"
d.addFact(f)
d.addNote(f)
d.reset()
d.sched.answerCard(d.sched.getCard(), 3)
assert d.sched.repCounts() == (0, 0, 0)
# and failing a review should too
f = d.newFact()
f = d.newNote()
f['Front'] = u"three"
d.addFact(f)
d.addNote(f)
c = f.cards()[0]
c.type = 2
c.queue = 2
@ -717,9 +717,9 @@ def test_timing():
d = getEmptyDeck()
# add a few review cards, due today
for i in range(5):
f = d.newFact()
f = d.newNote()
f['Front'] = "num"+str(i)
d.addFact(f)
d.addNote(f)
c = f.cards()[0]
c.type = 2
c.queue = 2
@ -741,10 +741,10 @@ def test_timing():
def test_collapse():
d = getEmptyDeck()
# add a fact
f = d.newFact()
# add a note
f = d.newNote()
f['Front'] = u"one"
d.addFact(f)
d.addNote(f)
d.reset()
# test collapsing
c = d.sched.getCard()
@ -755,30 +755,30 @@ def test_collapse():
def test_groupCounts():
d = getEmptyDeck()
# add a fact with default group
f = d.newFact()
# add a note with default group
f = d.newNote()
f['Front'] = u"one"
d.addFact(f)
d.addNote(f)
# and one that's a child
f = d.newFact()
f = d.newNote()
f['Front'] = u"two"
default1 = f.gid = d.groups.id("Default::1")
d.addFact(f)
d.addNote(f)
# make it a review card
c = f.cards()[0]
c.queue = 2
c.due = 0
c.flush()
# add one more with a new group
f = d.newFact()
f = d.newNote()
f['Front'] = u"two"
foobar = f.gid = d.groups.id("foo::bar")
d.addFact(f)
d.addNote(f)
# and one that's a sibling
f = d.newFact()
f = d.newNote()
f['Front'] = u"three"
foobaz = f.gid = d.groups.id("foo::baz")
d.addFact(f)
d.addNote(f)
d.reset()
assert len(d.groups.groups) == 5
cnts = d.sched.groupCounts()
@ -815,37 +815,37 @@ def test_groupTree():
def test_groupFlow():
d = getEmptyDeck()
# add a fact with default group
f = d.newFact()
# add a note with default group
f = d.newNote()
f['Front'] = u"one"
d.addFact(f)
d.addNote(f)
# and one that's a child
f = d.newFact()
f = d.newNote()
f['Front'] = u"two"
default1 = f.gid = d.groups.id("Default::2")
d.addFact(f)
d.addNote(f)
# and another that's higher up
f = d.newFact()
f = d.newNote()
f['Front'] = u"three"
default1 = f.gid = d.groups.id("Default::1")
d.addFact(f)
d.addNote(f)
# should get top level one first, then ::1, then ::2
d.reset()
assert d.sched.cardCounts() == (3,0,0)
for i in "one", "three", "two":
c = d.sched.getCard()
assert c.fact()['Front'] == i
assert c.note()['Front'] == i
d.sched.answerCard(c, 2)
def test_reorder():
d = getEmptyDeck()
# add a fact with default group
f = d.newFact()
# add a note with default group
f = d.newNote()
f['Front'] = u"one"
d.addFact(f)
f2 = d.newFact()
d.addNote(f)
f2 = d.newNote()
f2['Front'] = u"two"
d.addFact(f2)
d.addNote(f2)
assert f2.cards()[0].due == 2
found=False
# 50/50 chance of being reordered
@ -858,12 +858,12 @@ def test_reorder():
d.sched.orderCards()
assert f.cards()[0].due == 1
# shifting
f3 = d.newFact()
f3 = d.newNote()
f3['Front'] = u"three"
d.addFact(f3)
f4 = d.newFact()
d.addNote(f3)
f4 = d.newNote()
f4['Front'] = u"four"
d.addFact(f4)
d.addNote(f4)
assert f.cards()[0].due == 1
assert f2.cards()[0].due == 2
assert f3.cards()[0].due == 3
@ -877,9 +877,9 @@ def test_reorder():
def test_forget():
d = getEmptyDeck()
f = d.newFact()
f = d.newNote()
f['Front'] = u"one"
d.addFact(f)
d.addNote(f)
c = f.cards()[0]
c.queue = 2; c.type = 2; c.ivl = 100; c.due = 0
c.flush()
@ -891,9 +891,9 @@ def test_forget():
def test_resched():
d = getEmptyDeck()
f = d.newFact()
f = d.newNote()
f['Front'] = u"one"
d.addFact(f)
d.addNote(f)
c = f.cards()[0]
d.sched.reschedCards([c.id], 0, 0)
c.load()
@ -908,9 +908,9 @@ def test_resched():
def test_revlim():
d = getEmptyDeck()
for i in range(20):
f = d.newFact()
f = d.newNote()
f['Front'] = str(i)
d.addFact(f)
d.addNote(f)
d.db.execute("update cards set due = 0, queue = 2, type = 2")
d.reset()
assert d.sched.repCounts()[2] == 20

View file

@ -7,9 +7,9 @@ from anki.hooks import addHook
def test_stats():
d = getEmptyDeck()
f = d.newFact()
f = d.newNote()
f['Front'] = "foo"
d.addFact(f)
d.addNote(f)
c = f.cards()[0]
# card stats
assert d.cardStats(c)

View file

@ -8,7 +8,7 @@ from anki import Deck
from anki.utils import intTime
from anki.sync import Syncer, FullSyncer, LocalServer, RemoteServer, \
MediaSyncer, RemoteMediaServer
from anki.facts import Fact
from anki.notes import Note
from anki.cards import Card
from tests.shared import getEmptyDeck
@ -24,17 +24,17 @@ server2=None
def setup_basic():
global deck1, deck2, client, server
deck1 = getEmptyDeck()
# add a fact to deck 1
f = deck1.newFact()
# add a note to deck 1
f = deck1.newNote()
f['Front'] = u"foo"; f['Back'] = u"bar"; f.tags = [u"foo"]
deck1.addFact(f)
deck1.addNote(f)
# answer it
deck1.reset(); deck1.sched.answerCard(deck1.sched.getCard(), 4)
# repeat for deck2
deck2 = getEmptyDeck(server=True)
f = deck2.newFact()
f = deck2.newNote()
f['Front'] = u"bar"; f['Back'] = u"bar"; f.tags = [u"bar"]
deck2.addFact(f)
deck2.addNote(f)
deck2.reset(); deck2.sched.answerCard(deck2.sched.getCard(), 4)
# start with same schema and sync time
deck1.scm = deck2.scm = 0
@ -62,7 +62,7 @@ def test_changedSchema():
def test_sync():
def check(num):
for d in deck1, deck2:
for t in ("revlog", "facts", "cards", "fsums"):
for t in ("revlog", "notes", "cards", "nsums"):
assert d.db.scalar("select count() from %s" % t) == num
assert len(d.models.all()) == num*2
# the default group and config have an id of 1, so always 1
@ -107,31 +107,31 @@ def test_models():
assert client.sync() == "fullSync"
@nose.with_setup(setup_modified)
def test_facts():
def test_notes():
test_sync()
# modifications should be synced
fid = deck1.db.scalar("select id from facts")
fact = deck1.getFact(fid)
assert fact['Front'] != "abc"
fact['Front'] = "abc"
fact.flush()
nid = deck1.db.scalar("select id from notes")
note = deck1.getNote(nid)
assert note['Front'] != "abc"
note['Front'] = "abc"
note.flush()
deck1.save()
assert client.sync() == "success"
assert deck2.getFact(fid)['Front'] == "abc"
assert deck2.getNote(nid)['Front'] == "abc"
# deletions too
assert deck1.db.scalar("select 1 from facts where id = ?", fid)
deck1.remFacts([fid])
assert deck1.db.scalar("select 1 from notes where id = ?", nid)
deck1.remNotes([nid])
deck1.save()
assert client.sync() == "success"
assert not deck1.db.scalar("select 1 from facts where id = ?", fid)
assert not deck2.db.scalar("select 1 from facts where id = ?", fid)
assert not deck1.db.scalar("select 1 from notes where id = ?", nid)
assert not deck2.db.scalar("select 1 from notes where id = ?", nid)
@nose.with_setup(setup_modified)
def test_cards():
test_sync()
fid = deck1.db.scalar("select id from facts")
fact = deck1.getFact(fid)
card = fact.cards()[0]
nid = deck1.db.scalar("select id from notes")
note = deck1.getNote(nid)
card = note.cards()[0]
# answer the card locally
card.startTimer()
deck1.sched.answerCard(card, 4)
@ -215,26 +215,26 @@ def test_threeway():
assert client2.sync() == "noChanges"
# client 1 adds a card at time 1
time.sleep(1)
f = deck1.newFact()
f = deck1.newNote()
f['Front'] = u"1";
deck1.addFact(f)
deck1.addNote(f)
deck1.save()
# at time 2, client 2 syncs to server
time.sleep(1)
deck3.save()
assert client2.sync() == "success"
# at time 3, client 1 syncs, adding the older fact
# at time 3, client 1 syncs, adding the older note
time.sleep(1)
assert client.sync() == "success"
assert deck1.factCount() == deck2.factCount()
assert deck1.noteCount() == deck2.noteCount()
# syncing client2 should pick it up
assert client2.sync() == "success"
assert deck1.factCount() == deck2.factCount() == deck3.factCount()
assert deck1.noteCount() == deck2.noteCount() == deck3.noteCount()
def _test_speed():
t = time.time()
deck1 = Deck(os.path.expanduser("~/rapid.anki"))
for tbl in "revlog", "cards", "facts", "graves":
for tbl in "revlog", "cards", "notes", "graves":
deck1.db.execute("update %s set usn = -1 where usn != -1"%tbl)
for m in deck1.models.all():
m['usn'] = -1

View file

@ -26,9 +26,9 @@ def test_op():
assert not d.undoName()
# and a review will, too
d.save("add")
f = d.newFact()
f = d.newNote()
f['Front'] = u"one"
d.addFact(f)
d.addNote(f)
d.reset()
assert d.undoName() == "add"
c = d.sched.getCard()
@ -38,9 +38,9 @@ def test_op():
def test_review():
d = getEmptyDeck()
d.conf['counts'] = COUNT_REMAINING
f = d.newFact()
f = d.newNote()
f['Front'] = u"one"
d.addFact(f)
d.addNote(f)
d.reset()
assert not d.undoName()
# answer
@ -62,7 +62,7 @@ def test_review():
assert not d.undoName()
# we should be able to undo multiple answers too
f['Front'] = u"two"
d.addFact(f)
d.addNote(f)
d.reset()
assert d.sched.cardCounts() == (2, 0, 0)
c = d.sched.getCard()