mirror of
https://github.com/ankitects/anki.git
synced 2025-09-24 16:56:36 -04:00
add genCards(), previewCards(), and more unit tests
This commit is contained in:
parent
53215230b4
commit
870c80e076
5 changed files with 163 additions and 80 deletions
|
@ -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()
|
||||
|
|
149
anki/deck.py
149
anki/deck.py
|
@ -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
|
||||
##########################################################################
|
||||
|
||||
|
|
|
@ -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
|
||||
##################################################
|
||||
|
||||
|
|
|
@ -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
82
tests/test_cards.py
Normal 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
|
Loading…
Reference in a new issue