add genCards(), previewCards(), and more unit tests

This commit is contained in:
Damien Elmes 2011-03-13 18:32:03 +09:00
parent 53215230b4
commit 870c80e076
5 changed files with 163 additions and 80 deletions

View file

@ -112,7 +112,7 @@ streak=?, lapses=?, grade=?, cycles=? where id = ?""",
return self.deck.getFact(self.fid)
def template(self):
return self.deck.getTemplate(self.tid)
return self.deck.getTemplate(self.fact().mid, self.ord)
def startTimer(self):
self.timerStarted = time.time()

View file

@ -156,9 +156,8 @@ qconf=?, conf=?, data=?""",
def getFact(self, id):
return anki.facts.Fact(self, id=id)
def getTemplate(self, id):
return anki.models.Template(self, self.deck.db.first(
"select * from templates where id = ?", id))
def getTemplate(self, mid, ord):
return self.getModel(mid).templates[ord]
def getModel(self, mid):
return anki.models.Model(self, mid)
@ -198,27 +197,37 @@ qconf=?, conf=?, data=?""",
# notice any new tags
self.registerTags(fact.tags)
# if random mode, determine insertion point
isRandom = self.qconf['newCardOrder'] == NEW_CARDS_RANDOM
if isRandom:
if self.qconf['newCardOrder'] == NEW_CARDS_RANDOM:
due = random.randrange(0, 1000000)
else:
due = fact.id
# add cards
ncards = 0
for template in cms:
card = anki.cards.Card(self)
card.id = self.nextID("cid")
card.fid = fact.id
card.ord = template['ord']
card.gid = template['gid'] or gid
if isRandom:
card.due = due
else:
card.due = fact.id
card.flush()
self._newCard(fact, template, due, gid)
ncards += 1
return ncards
def _deleteFacts(self, ids):
"Bulk delete facts by ID. Don't call this directly."
if not ids:
return
strids = ids2str(ids)
self.db.execute("delete from facts where id in %s" % strids)
self.db.execute("delete from fsums where fid in %s" % strids)
def _deleteDanglingFacts(self):
"Delete any facts without cards. Don't call this directly."
ids = self.db.list("""
select id from facts where id not in (select distinct fid from cards)""")
self._deleteFacts(ids)
return ids
# Card creation
##########################################################################
def findTemplates(self, fact, checkActive=True):
"Return active, non-empty templates."
"Return (active), non-empty templates."
ok = []
for template in fact.model.templates:
if template['actv'] or not checkActive:
@ -236,67 +245,57 @@ qconf=?, conf=?, data=?""",
ok.append(template)
return ok
def genCards(self, fact, templates):
"Generate cards for templates if cards not empty."
# templates should have .ord set
ids = []
for template in self.findTemplates(fact, False):
def genCards(self, fact, templates, gid):
"Generate cards for templates if cards not empty. Return cards."
cards = []
# if random mode, determine insertion point
if self.qconf['newCardOrder'] == NEW_CARDS_RANDOM:
# if this fact has existing new cards, use their due time
due = self.db.scalar(
"select due from cards where fid = ? and queue = 2", fact.id)
due = due or random.randrange(1, 1000000)
else:
due = fact.id
for template in self.findTemplates(fact, checkActive=False):
if template not in templates:
continue
# if it doesn't already exist
if not self.db.scalar(
"select 1 from cards where fid = ? and ord = ?",
fact.id, template.ord):
card = anki.cards.Card(
fact, template,
fact.created+0.0001*template.ord)
raise Exception("incorrect; not checking selective study")
self.newAvail += 1
ids.append(card.id)
if ids:
fact.setMod(textChanged=True, deck=self)
self.setMod()
return ids
def _deleteFacts(self, ids):
"Bulk delete facts by ID. Don't call this directly."
if not ids:
return
strids = ids2str(ids)
self.db.execute("delete from facts where id in %s" % strids)
self.db.execute("delete from fsums where fid in %s" % strids)
def _deleteDanglingFacts(self):
"Delete any facts without cards. Don't call this directly."
ids = self.db.list("""
select id from facts where id not in (select distinct fid from cards)""")
self._deleteFacts(ids)
return ids
def previewFact(self, oldFact, cms=None):
"Duplicate fact and generate cards for preview. Don't add to deck."
# check we have card models available
if cms is None:
cms = self.findTemplates(oldFact, checkActive=True)
if not cms:
return []
fact = self.cloneFact(oldFact)
# proceed
cards = []
for template in cms:
card = anki.cards.Card(fact, template)
cards.append(card)
fact.setMod(textChanged=True, deck=self, media=False)
fact.id, template['ord']):
# create
cards.append(self._newCard(fact, template, due, gid))
return cards
def cloneFact(self, oldFact):
"Copy fact into new session."
model = self.db.query(Model).get(oldFact.model.id)
fact = self.newFact(model)
for field in fact.fdata:
fact[field.name] = oldFact[field.name]
fact._tags = oldFact._tags
return fact
# type 0 - when previewing in add dialog, only non-empty & active
# type 1 - when previewing edit, only existing
# type 2 - when previewing in models dialog, all
def previewCards(self, fact, type=0):
"Return uncommited cards for preview."
if type == 0:
cms = self.findTemplates(fact, checkActive=True)
elif type == 1:
cms = [c.template() for c in fact.cards()]
else:
cms = fact.model.templates
if not cms:
return []
cards = []
for template in cms:
cards.append(self._newCard(fact, template, 1, 1, flush=False))
return cards
def _newCard(self, fact, template, due, gid, flush=True):
"Create a new card."
card = anki.cards.Card(self)
card.id = self.nextID("cid")
card.fid = fact.id
card.ord = template['ord']
card.gid = template['gid'] or gid
card.due = due
if flush:
card.flush()
return card
# Cards
##########################################################################
@ -325,7 +324,7 @@ select id from facts where id not in (select distinct fid from cards)""")
self.db.list("select fid from cards where id in "+sids))
# need to handle delete of fsums/revlog remotely after sync
self.db.execute(
"update cards set crt = 0, mod = ? where id in "+sids,
"update cards set crt = 0, queue = -4, mod = ? where id in "+sids,
intTime())
self.db.execute(
"update facts set crt = 0, mod = ? where id in "+sfids,
@ -440,12 +439,6 @@ select id from cards where fid in (select id from facts where mid = ?)""",
self.conf['currentModelId'] = self.db.scalar(
"select id from models limit 1")
def modelUseCount(self, model):
"Return number of facts using model."
return self.db.scalar("select count() from facts "
"where facts.mid = :id",
id=model.id)
# Field checksums and sorting fields
##########################################################################

View file

@ -69,6 +69,9 @@ insert or replace into facts values (?, ?, ?, ?, ?, ?, ?, ?)""",
return [self.deck.getCard(id) for id in self.deck.db.list(
"select id from cards where fid = ? order by id", self.id)]
def model(self):
return self.deck.getModel(self.mid)
# Dict interface
##################################################

View file

@ -80,7 +80,12 @@ insert or replace into models values (?, ?, ?, ?, ?, ?, ?)""",
self.id = ret.lastrowid
def fids(self):
return self.deck.db.list("select id from facts where mid = ?", self.id)
return self.deck.db.list(
"select id from facts where mid = ?", self.id)
def useCount(self):
return self.deck.db.scalar(
"select count() from facts where mid = ?", self.id)
# Copying
##################################################

82
tests/test_cards.py Normal file
View file

@ -0,0 +1,82 @@
# coding: utf-8
from anki.consts import *
from tests.shared import getEmptyDeck
def test_genCards():
deck = getEmptyDeck()
f = deck.newFact()
f['Front'] = u'1'
f['Back'] = u'2'
deck.addFact(f)
cards = deck.genCards(f, f.model.templates, 1)
assert len(cards) == 1
assert cards[0].ord == 1
assert deck.cardCount() == 2
assert cards[0].due == f.id
# should work on random mode too
deck.qconf['newCardOrder'] = NEW_CARDS_RANDOM
f = deck.newFact()
f['Front'] = u'1'
f['Back'] = u'2'
deck.addFact(f)
cards = deck.genCards(f, f.model.templates, 1)
assert deck.cardCount() == 4
c = deck.db.list("select due from cards where fid = ?", f.id)
assert c[0] == c[1]
def test_previewCards():
deck = getEmptyDeck()
f = deck.newFact()
f['Front'] = u'1'
f['Back'] = u'2'
# non-empty and active
cards = deck.previewCards(f, 0)
assert len(cards) == 1
assert cards[0].ord == 0
# all templates
cards = deck.previewCards(f, 2)
assert len(cards) == 2
# add the fact, and test existing preview
deck.addFact(f)
cards = deck.previewCards(f, 1)
assert len(cards) == 1
assert cards[0].ord == 0
# make sure we haven't accidentally added cards to the db
assert deck.cardCount() == 1
def test_delete():
deck = getEmptyDeck()
f = deck.newFact()
f['Front'] = u'1'
f['Back'] = u'2'
deck.addFact(f)
cid = f.cards()[0].id
# when the schema is dirty, deletion should be immediate
assert deck.schemaDirty() == True
deck.deleteCard(cid)
assert deck.cardCount() == 0
assert deck.factCount() == 0
assert deck.db.scalar("select count() from facts") == 0
assert deck.db.scalar("select count() from cards") == 0
# add the fact back
deck.addFact(f)
assert deck.cardCount() == 1
# mark the schema as clean
deck.lastSync = deck.schema + 1
# cards/facts should go in the deletion log instead
cid = f.cards()[0].id
deck.deleteCard(cid)
assert deck.cardCount() == 0
assert deck.factCount() == 0
assert deck.db.scalar("select count() from facts") == 1
assert deck.db.scalar("select count() from cards") == 1
assert deck.db.scalar("select 1 from cards where crt = 0") == 1
assert deck.db.scalar("select 1 from facts where crt = 0") == 1
assert deck.db.scalar("select queue from cards") == -4
# modifying the schema should empty the trash
deck.modSchema()
assert deck.cardCount() == 0
assert deck.factCount() == 0
assert deck.db.scalar("select count() from facts") == 0
assert deck.db.scalar("select count() from cards") == 0