mirror of
https://github.com/ankitects/anki.git
synced 2025-09-25 01:06:35 -04:00
top level groups
As discussed on the forums, moving to a single collection requires moving some deck-level configuration into groups so users can have different settings like new cards/day for each top level item. Also: - store id in groups - add mod time to gconf updates - move the limiting code that's not specific to scheduling into groups.py - store the current model id per top level group
This commit is contained in:
parent
5179d82f7f
commit
de8a5b69ed
11 changed files with 175 additions and 104 deletions
20
anki/deck.py
20
anki/deck.py
|
@ -20,19 +20,12 @@ import anki.cards, anki.facts, anki.template, anki.cram, anki.find
|
||||||
|
|
||||||
defaultConf = {
|
defaultConf = {
|
||||||
# scheduling options
|
# scheduling options
|
||||||
'groups': [],
|
'activeGroups': [],
|
||||||
'newPerDay': 20,
|
'topGroup': 1,
|
||||||
'newToday': [0, 0], # currentDay, count
|
'curGroup': None,
|
||||||
'newTodayOrder': NEW_TODAY_ORD,
|
|
||||||
'newOrder': NEW_CARDS_DUE,
|
|
||||||
'newSpread': NEW_CARDS_DISTRIBUTE,
|
|
||||||
'revOrder': REV_CARDS_RANDOM,
|
'revOrder': REV_CARDS_RANDOM,
|
||||||
'collapseTime': 1200,
|
|
||||||
'repLim': 0,
|
|
||||||
'timeLim': 600,
|
|
||||||
# other config
|
# other config
|
||||||
'nextPos': 1,
|
'nextPos': 1,
|
||||||
'mediaURL': "",
|
|
||||||
'fontFamilies': [
|
'fontFamilies': [
|
||||||
[u'MS 明朝',u'ヒラギノ明朝 Pro W3',u'Kochi Mincho', u'東風明朝']
|
[u'MS 明朝',u'ヒラギノ明朝 Pro W3',u'Kochi Mincho', u'東風明朝']
|
||||||
],
|
],
|
||||||
|
@ -228,7 +221,7 @@ crt=?, mod=?, scm=?, dty=?, syncName=?, lastSync=?, conf=?""",
|
||||||
return 0
|
return 0
|
||||||
fact.flush()
|
fact.flush()
|
||||||
# randomize?
|
# randomize?
|
||||||
if self.randomNew():
|
if self.models.randomNew():
|
||||||
due = self._randPos()
|
due = self._randPos()
|
||||||
else:
|
else:
|
||||||
due = self.nextID("pos")
|
due = self.nextID("pos")
|
||||||
|
@ -284,7 +277,7 @@ crt=?, mod=?, scm=?, dty=?, syncName=?, lastSync=?, conf=?""",
|
||||||
"Generate cards for templates if cards not empty. Return cards."
|
"Generate cards for templates if cards not empty. Return cards."
|
||||||
cards = []
|
cards = []
|
||||||
# if random mode, determine insertion point
|
# if random mode, determine insertion point
|
||||||
if self.randomNew():
|
if self.models.randomNew():
|
||||||
# if this fact has existing new cards, use their due time
|
# if this fact has existing new cards, use their due time
|
||||||
due = self.db.scalar(
|
due = self.db.scalar(
|
||||||
"select due from cards where fid = ? and queue = 0", fact.id)
|
"select due from cards where fid = ? and queue = 0", fact.id)
|
||||||
|
@ -331,9 +324,6 @@ crt=?, mod=?, scm=?, dty=?, syncName=?, lastSync=?, conf=?""",
|
||||||
card.flush()
|
card.flush()
|
||||||
return card
|
return card
|
||||||
|
|
||||||
def randomNew(self):
|
|
||||||
return self.conf['newOrder'] == NEW_CARDS_RANDOM
|
|
||||||
|
|
||||||
# Cards
|
# Cards
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,27 @@
|
||||||
|
|
||||||
import simplejson
|
import simplejson
|
||||||
from anki.utils import intTime
|
from anki.utils import intTime
|
||||||
|
from anki.consts import *
|
||||||
|
|
||||||
|
# fixmes:
|
||||||
|
# - make sure lists like new[delays] are not being shared by multiple groups
|
||||||
|
# - make sure all children have parents (create as necessary)
|
||||||
|
# - when renaming a group, top level properties should be added or removed as
|
||||||
|
# appropriate
|
||||||
|
|
||||||
|
# configuration only available to top level groups
|
||||||
|
defaultTopConf = {
|
||||||
|
'newPerDay': 20,
|
||||||
|
'newToday': [0, 0], # currentDay, count
|
||||||
|
'newTodayOrder': NEW_TODAY_ORD,
|
||||||
|
'newSpread': NEW_CARDS_DISTRIBUTE,
|
||||||
|
'collapseTime': 1200,
|
||||||
|
'repLim': 0,
|
||||||
|
'timeLim': 600,
|
||||||
|
'curModel': None,
|
||||||
|
}
|
||||||
|
|
||||||
|
# configuration available to all groups
|
||||||
defaultConf = {
|
defaultConf = {
|
||||||
'new': {
|
'new': {
|
||||||
'delays': [1, 10],
|
'delays': [1, 10],
|
||||||
|
@ -33,11 +53,7 @@ defaultConf = {
|
||||||
'minSpace': 1,
|
'minSpace': 1,
|
||||||
},
|
},
|
||||||
'maxTaken': 60,
|
'maxTaken': 60,
|
||||||
}
|
'mod': 0,
|
||||||
|
|
||||||
defaultData = {
|
|
||||||
'activeTags': None,
|
|
||||||
'inactiveTags': None,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class GroupManager(object):
|
class GroupManager(object):
|
||||||
|
@ -54,6 +70,7 @@ class GroupManager(object):
|
||||||
self.changed = False
|
self.changed = False
|
||||||
|
|
||||||
def save(self, g):
|
def save(self, g):
|
||||||
|
"Can be called with either a group or a group configuration."
|
||||||
g['mod'] = intTime()
|
g['mod'] = intTime()
|
||||||
self.changed = True
|
self.changed = True
|
||||||
|
|
||||||
|
@ -73,12 +90,20 @@ class GroupManager(object):
|
||||||
return int(id)
|
return int(id)
|
||||||
if not create:
|
if not create:
|
||||||
return None
|
return None
|
||||||
g = dict(name=name, conf=1, mod=intTime())
|
if "::" not in name:
|
||||||
|
# if it's a top level group, it gets the top level config
|
||||||
|
g = defaultTopConf.copy()
|
||||||
|
else:
|
||||||
|
# not top level. calling code should ensure parents already exist?
|
||||||
|
g = {}
|
||||||
|
g['name'] = name
|
||||||
|
g['conf'] = 1
|
||||||
while 1:
|
while 1:
|
||||||
id = str(intTime(1000))
|
id = intTime(1000)
|
||||||
if id in self.groups:
|
if str(id) in self.groups:
|
||||||
continue
|
continue
|
||||||
self.groups[id] = g
|
g['id'] = id
|
||||||
|
self.groups[str(id)] = g
|
||||||
self.save(g)
|
self.save(g)
|
||||||
return int(id)
|
return int(id)
|
||||||
|
|
||||||
|
@ -89,11 +114,15 @@ class GroupManager(object):
|
||||||
self.deck.db.execute("delete from groups where id = ?", gid)
|
self.deck.db.execute("delete from groups where id = ?", gid)
|
||||||
print "fixme: loop through models and update stale gid references"
|
print "fixme: loop through models and update stale gid references"
|
||||||
|
|
||||||
def all(self):
|
def allNames(self):
|
||||||
"An unsorted list of all group names."
|
"An unsorted list of all group names."
|
||||||
return [x['name'] for x in self.groups.values()]
|
return [x['name'] for x in self.groups.values()]
|
||||||
|
|
||||||
# Utils
|
def all(self):
|
||||||
|
"A list of all groups."
|
||||||
|
return self.groups.values()
|
||||||
|
|
||||||
|
# Group utils
|
||||||
#############################################################
|
#############################################################
|
||||||
|
|
||||||
def name(self, gid):
|
def name(self, gid):
|
||||||
|
@ -102,6 +131,46 @@ class GroupManager(object):
|
||||||
def conf(self, gid):
|
def conf(self, gid):
|
||||||
return self.gconf[str(self.groups[str(gid)]['conf'])]
|
return self.gconf[str(self.groups[str(gid)]['conf'])]
|
||||||
|
|
||||||
|
def get(self, gid):
|
||||||
|
return self.groups[str(gid)]
|
||||||
|
|
||||||
def setGroup(self, cids, gid):
|
def setGroup(self, cids, gid):
|
||||||
self.db.execute(
|
self.db.execute(
|
||||||
"update cards set gid = ? where id in "+ids2str(cids), gid)
|
"update cards set gid = ? where id in "+ids2str(cids), gid)
|
||||||
|
|
||||||
|
# Group selection
|
||||||
|
#############################################################
|
||||||
|
|
||||||
|
def top(self):
|
||||||
|
"The current top level group as an object, and marks as modified."
|
||||||
|
g = self.get(self.deck.conf['topGroup'])
|
||||||
|
self.save(g)
|
||||||
|
return g
|
||||||
|
|
||||||
|
def active(self):
|
||||||
|
"The currrently active gids."
|
||||||
|
return self.deck.conf['activeGroups']
|
||||||
|
|
||||||
|
def selected(self):
|
||||||
|
"The currently selected gid, or None if whole collection."
|
||||||
|
return self.deck.conf['curGroup']
|
||||||
|
|
||||||
|
def select(self, gid):
|
||||||
|
"Select a new group. If gid is None, select whole collection."
|
||||||
|
if not gid:
|
||||||
|
self.deck.conf['topGroup'] = 1
|
||||||
|
self.deck.conf['curGroup'] = None
|
||||||
|
self.deck.conf['activeGroups'] = []
|
||||||
|
return
|
||||||
|
# save the top level group
|
||||||
|
name = self.groups[str(gid)]['name']
|
||||||
|
path = name.split("::")
|
||||||
|
self.deck.conf['topGroup'] = self.id(path[0])
|
||||||
|
# current group
|
||||||
|
self.deck.conf['curGroup'] = gid
|
||||||
|
# and active groups (current + all children)
|
||||||
|
actv = [gid]
|
||||||
|
for g in self.all():
|
||||||
|
if g['name'].startswith(name + "::"):
|
||||||
|
actv.append(g['id'])
|
||||||
|
self.deck.conf['activeGroups'] = actv
|
||||||
|
|
|
@ -6,6 +6,7 @@ import simplejson, copy
|
||||||
from anki.utils import intTime, hexifyID, joinFields, splitFields, ids2str, \
|
from anki.utils import intTime, hexifyID, joinFields, splitFields, ids2str, \
|
||||||
timestampID
|
timestampID
|
||||||
from anki.lang import _
|
from anki.lang import _
|
||||||
|
from anki.consts import *
|
||||||
|
|
||||||
# Models
|
# Models
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
@ -16,6 +17,7 @@ defaultModel = {
|
||||||
'sortf': 0,
|
'sortf': 0,
|
||||||
'gid': 1,
|
'gid': 1,
|
||||||
'clozectx': False,
|
'clozectx': False,
|
||||||
|
'newOrder': NEW_CARDS_DUE,
|
||||||
'latexPre': """\
|
'latexPre': """\
|
||||||
\\documentclass[12pt]{article}
|
\\documentclass[12pt]{article}
|
||||||
\\special{papersize=3in,5in}
|
\\special{papersize=3in,5in}
|
||||||
|
@ -87,7 +89,13 @@ class ModelManager(object):
|
||||||
|
|
||||||
def current(self):
|
def current(self):
|
||||||
"Get current model."
|
"Get current model."
|
||||||
return self.get(self.deck.conf['currentModelId'])
|
try:
|
||||||
|
return self.get(self.deck.groups.top()['curModel'])
|
||||||
|
except:
|
||||||
|
return self.models.values()[0]
|
||||||
|
|
||||||
|
def setCurrent(self, m):
|
||||||
|
self.deck.groups.top()['curModel'] = m['id']
|
||||||
|
|
||||||
def get(self, id):
|
def get(self, id):
|
||||||
"Get model with ID."
|
"Get model with ID."
|
||||||
|
@ -117,6 +125,7 @@ class ModelManager(object):
|
||||||
def rem(self, m):
|
def rem(self, m):
|
||||||
"Delete model, and all its cards/facts."
|
"Delete model, and all its cards/facts."
|
||||||
self.deck.modSchema()
|
self.deck.modSchema()
|
||||||
|
current = self.current()['id'] == m['id']
|
||||||
# delete facts/cards
|
# delete facts/cards
|
||||||
self.deck.remCards(self.deck.db.list("""
|
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 fid in (select id from facts where mid = ?)""",
|
||||||
|
@ -125,14 +134,14 @@ select id from cards where fid in (select id from facts where mid = ?)""",
|
||||||
del self.models[m['id']]
|
del self.models[m['id']]
|
||||||
self.save()
|
self.save()
|
||||||
# GUI should ensure last model is not deleted
|
# GUI should ensure last model is not deleted
|
||||||
if self.deck.conf['currentModelId'] == m['id']:
|
if current:
|
||||||
self.deck.conf['currentModelId'] = int(self.models.keys()[0])
|
self.setCurrent(self.models.values()[0])
|
||||||
|
|
||||||
def _add(self, m):
|
def _add(self, m):
|
||||||
self._setID(m)
|
self._setID(m)
|
||||||
self.models[m['id']] = m
|
self.models[m['id']] = m
|
||||||
self.save(m)
|
self.save(m)
|
||||||
self.deck.conf['currentModelId'] = m['id']
|
self.setCurrent(m)
|
||||||
return m
|
return m
|
||||||
|
|
||||||
def _setID(self, m):
|
def _setID(self, m):
|
||||||
|
@ -155,6 +164,9 @@ select id from cards where fid in (select id from facts where mid = ?)""",
|
||||||
return self.deck.db.scalar(
|
return self.deck.db.scalar(
|
||||||
"select count() from facts where mid = ?", m['id'])
|
"select count() from facts where mid = ?", m['id'])
|
||||||
|
|
||||||
|
def randomNew(self):
|
||||||
|
return self.current()['newOrder'] == NEW_CARDS_RANDOM
|
||||||
|
|
||||||
# Copying
|
# Copying
|
||||||
##################################################
|
##################################################
|
||||||
|
|
||||||
|
|
|
@ -49,7 +49,7 @@ class Scheduler(object):
|
||||||
# put it in the learn queue
|
# put it in the learn queue
|
||||||
card.queue = 1
|
card.queue = 1
|
||||||
card.type = 1
|
card.type = 1
|
||||||
self.deck.conf['newToday'][1] += 1
|
self.deck.groups.top()['newToday'][1] += 1
|
||||||
if card.queue == 1:
|
if card.queue == 1:
|
||||||
self._answerLrnCard(card, ease)
|
self._answerLrnCard(card, ease)
|
||||||
elif card.queue == 2:
|
elif card.queue == 2:
|
||||||
|
@ -98,20 +98,6 @@ order by due""" % self._groupLimit(),
|
||||||
# Counts
|
# Counts
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
def selCounts(self):
|
|
||||||
"Return counts for selected groups, without building queue."
|
|
||||||
self._resetCounts()
|
|
||||||
return self.counts()
|
|
||||||
|
|
||||||
def allCounts(self):
|
|
||||||
"Return counts for all groups, without building queue."
|
|
||||||
conf = self.deck.conf['groups']
|
|
||||||
if conf:
|
|
||||||
self.deck.conf['groups'] = []
|
|
||||||
self._resetCounts()
|
|
||||||
self.deck.conf['groups'] = conf
|
|
||||||
return self.counts()
|
|
||||||
|
|
||||||
def _resetCounts(self):
|
def _resetCounts(self):
|
||||||
self._updateCutoff()
|
self._updateCutoff()
|
||||||
self._resetLrnCount()
|
self._resetLrnCount()
|
||||||
|
@ -212,7 +198,7 @@ from cards group by gid""", self.today):
|
||||||
# FIXME: need to keep track of reps for timebox and new card introduction
|
# FIXME: need to keep track of reps for timebox and new card introduction
|
||||||
|
|
||||||
def _resetNewCount(self):
|
def _resetNewCount(self):
|
||||||
l = self.deck.conf
|
l = self.deck.groups.top()
|
||||||
if l['newToday'][0] != self.today:
|
if l['newToday'][0] != self.today:
|
||||||
# it's a new day; reset counts
|
# it's a new day; reset counts
|
||||||
l['newToday'] = [self.today, 0]
|
l['newToday'] = [self.today, 0]
|
||||||
|
@ -241,7 +227,7 @@ queue = 0 %s order by due limit %d""" % (self._groupLimit(),
|
||||||
if self.newQueue:
|
if self.newQueue:
|
||||||
(id, due) = self.newQueue.pop()
|
(id, due) = self.newQueue.pop()
|
||||||
# move any siblings to the end?
|
# move any siblings to the end?
|
||||||
if self.deck.conf['newTodayOrder'] == NEW_TODAY_ORD:
|
if self.deck.groups.top()['newTodayOrder'] == NEW_TODAY_ORD:
|
||||||
n = len(self.newQueue)
|
n = len(self.newQueue)
|
||||||
while self.newQueue and self.newQueue[-1][1] == due:
|
while self.newQueue and self.newQueue[-1][1] == due:
|
||||||
self.newQueue.insert(0, self.newQueue.pop())
|
self.newQueue.insert(0, self.newQueue.pop())
|
||||||
|
@ -253,7 +239,7 @@ queue = 0 %s order by due limit %d""" % (self._groupLimit(),
|
||||||
return id
|
return id
|
||||||
|
|
||||||
def _updateNewCardRatio(self):
|
def _updateNewCardRatio(self):
|
||||||
if self.deck.conf['newSpread'] == NEW_CARDS_DISTRIBUTE:
|
if self.deck.groups.top()['newSpread'] == NEW_CARDS_DISTRIBUTE:
|
||||||
if self.newCount:
|
if self.newCount:
|
||||||
self.newCardModulus = (
|
self.newCardModulus = (
|
||||||
(self.newCount + self.revCount) / self.newCount)
|
(self.newCount + self.revCount) / self.newCount)
|
||||||
|
@ -267,9 +253,9 @@ queue = 0 %s order by due limit %d""" % (self._groupLimit(),
|
||||||
"True if it's time to display a new card when distributing."
|
"True if it's time to display a new card when distributing."
|
||||||
if not self.newCount:
|
if not self.newCount:
|
||||||
return False
|
return False
|
||||||
if self.deck.conf['newSpread'] == NEW_CARDS_LAST:
|
if self.deck.groups.top()['newSpread'] == NEW_CARDS_LAST:
|
||||||
return False
|
return False
|
||||||
elif self.deck.conf['newSpread'] == NEW_CARDS_FIRST:
|
elif self.deck.groups.top()['newSpread'] == NEW_CARDS_FIRST:
|
||||||
return True
|
return True
|
||||||
elif self.newCardModulus:
|
elif self.newCardModulus:
|
||||||
return self.reps and self.reps % self.newCardModulus == 0
|
return self.reps and self.reps % self.newCardModulus == 0
|
||||||
|
@ -282,7 +268,7 @@ queue = 0 %s order by due limit %d""" % (self._groupLimit(),
|
||||||
select count() from (select id from cards where
|
select count() from (select id from cards where
|
||||||
queue = 1 %s and due < ? limit %d)""" % (
|
queue = 1 %s and due < ? limit %d)""" % (
|
||||||
self._groupLimit(), self.reportLimit),
|
self._groupLimit(), self.reportLimit),
|
||||||
intTime() + self.deck.conf['collapseTime'])
|
intTime() + self.deck.groups.top()['collapseTime'])
|
||||||
|
|
||||||
def _resetLrn(self):
|
def _resetLrn(self):
|
||||||
self.lrnQueue = self.deck.db.all("""
|
self.lrnQueue = self.deck.db.all("""
|
||||||
|
@ -294,7 +280,7 @@ limit %d""" % (self._groupLimit(), self.reportLimit), lim=self.dayCutoff)
|
||||||
if self.lrnQueue:
|
if self.lrnQueue:
|
||||||
cutoff = time.time()
|
cutoff = time.time()
|
||||||
if collapse:
|
if collapse:
|
||||||
cutoff += self.deck.conf['collapseTime']
|
cutoff += self.deck.groups.top()['collapseTime']
|
||||||
if self.lrnQueue[0][0] < cutoff:
|
if self.lrnQueue[0][0] < cutoff:
|
||||||
id = heappop(self.lrnQueue)[1]
|
id = heappop(self.lrnQueue)[1]
|
||||||
self.lrnCount -= 1
|
self.lrnCount -= 1
|
||||||
|
@ -327,7 +313,7 @@ limit %d""" % (self._groupLimit(), self.reportLimit), lim=self.dayCutoff)
|
||||||
card.due = int(time.time() + delay)
|
card.due = int(time.time() + delay)
|
||||||
heappush(self.lrnQueue, (card.due, card.id))
|
heappush(self.lrnQueue, (card.due, card.id))
|
||||||
# if it's due within the cutoff, increment count
|
# if it's due within the cutoff, increment count
|
||||||
if delay <= self.deck.conf['collapseTime']:
|
if delay <= self.deck.groups.top()['collapseTime']:
|
||||||
self.lrnCount += 1
|
self.lrnCount += 1
|
||||||
self._logLrn(card, ease, conf, leaving, type)
|
self._logLrn(card, ease, conf, leaving, type)
|
||||||
|
|
||||||
|
@ -578,7 +564,7 @@ queue = 2 %s and due <= :lim order by %s limit %d""" % (
|
||||||
return self.deck.groups.conf(card.gid)
|
return self.deck.groups.conf(card.gid)
|
||||||
|
|
||||||
def _groupLimit(self):
|
def _groupLimit(self):
|
||||||
l = self.deck.conf['groups']
|
l = self.deck.groups.active()
|
||||||
if not l:
|
if not l:
|
||||||
# everything
|
# everything
|
||||||
return ""
|
return ""
|
||||||
|
@ -605,16 +591,10 @@ queue = 2 %s and due <= :lim order by %s limit %d""" % (
|
||||||
def finishedMsg(self):
|
def finishedMsg(self):
|
||||||
return (
|
return (
|
||||||
"<h1>"+_("Congratulations!")+"</h1>"+
|
"<h1>"+_("Congratulations!")+"</h1>"+
|
||||||
self._finishedSubtitle()+
|
_("You have finished the selected groups for now.") +
|
||||||
"<br><br>"+
|
"<br><br>"+
|
||||||
self._nextDueMsg())
|
self._nextDueMsg())
|
||||||
|
|
||||||
def _finishedSubtitle(self):
|
|
||||||
if self.deck.conf['groups']:
|
|
||||||
return _("You have finished the selected groups for now.")
|
|
||||||
else:
|
|
||||||
return _("You have finished the deck for now.")
|
|
||||||
|
|
||||||
def _nextDueMsg(self):
|
def _nextDueMsg(self):
|
||||||
line = []
|
line = []
|
||||||
rev = self.revTomorrow() + self.lrnTomorrow()
|
rev = self.revTomorrow() + self.lrnTomorrow()
|
||||||
|
@ -650,7 +630,7 @@ queue = 2 %s and due <= :lim order by %s limit %d""" % (
|
||||||
|
|
||||||
def newTomorrow(self):
|
def newTomorrow(self):
|
||||||
"Number of new cards tomorrow."
|
"Number of new cards tomorrow."
|
||||||
lim = self.deck.conf['newPerDay']
|
lim = self.deck.groups.top()['newPerDay']
|
||||||
return self.deck.db.scalar(
|
return self.deck.db.scalar(
|
||||||
"select count() from (select id from cards where "
|
"select count() from (select id from cards where "
|
||||||
"queue = 0 %s limit %d)" % (self._groupLimit(), lim))
|
"queue = 0 %s limit %d)" % (self._groupLimit(), lim))
|
||||||
|
@ -764,7 +744,7 @@ queue = 2 %s and due <= :lim order by %s limit %d""" % (
|
||||||
self.deck.db.execute(
|
self.deck.db.execute(
|
||||||
"update cards set type=0, queue=0, ivl=0 where id in "+ids2str(ids))
|
"update cards set type=0, queue=0, ivl=0 where id in "+ids2str(ids))
|
||||||
pmax = self.deck.db.scalar("select max(due) from cards where type=0")
|
pmax = self.deck.db.scalar("select max(due) from cards where type=0")
|
||||||
self.sortCards(ids, start=pmax+1, shuffle=self.deck.randomNew())
|
self.sortCards(ids, start=pmax+1, shuffle=self.deck.models.randomNew())
|
||||||
|
|
||||||
def reschedCards(self, ids, imin, imax):
|
def reschedCards(self, ids, imin, imax):
|
||||||
"Put cards in review queue with a new interval in days (min, max)."
|
"Put cards in review queue with a new interval in days (min, max)."
|
||||||
|
@ -816,6 +796,8 @@ and due >= ?""" % scids, now, shiftby, low)
|
||||||
self.deck.db.executemany(
|
self.deck.db.executemany(
|
||||||
"update cards set due = :due, mod = :now where id = :cid""", d)
|
"update cards set due = :due, mod = :now where id = :cid""", d)
|
||||||
|
|
||||||
|
# fixme: because it's a model property now, these should be done on a
|
||||||
|
# per-model basis
|
||||||
def randomizeCards(self):
|
def randomizeCards(self):
|
||||||
self.sortCards(self.deck.db.list("select id from cards"), shuffle=True)
|
self.sortCards(self.deck.db.list("select id from cards"), shuffle=True)
|
||||||
|
|
||||||
|
|
|
@ -675,7 +675,7 @@ $(function () {
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
def _revlogLimit(self):
|
def _revlogLimit(self):
|
||||||
lim = self.deck.conf['groups']
|
lim = self.deck.groups.active()
|
||||||
if self.selective and lim:
|
if self.selective and lim:
|
||||||
return ("cid in (select id from cards where gid in %s)" %
|
return ("cid in (select id from cards where gid in %s)" %
|
||||||
ids2str(lim))
|
ids2str(lim))
|
||||||
|
|
|
@ -131,11 +131,15 @@ values(1,0,0,0,%(v)s,0,'',0,'','{}','','','{}');
|
||||||
import anki.deck
|
import anki.deck
|
||||||
import anki.groups
|
import anki.groups
|
||||||
if setDeckConf:
|
if setDeckConf:
|
||||||
|
g = anki.groups.defaultTopConf.copy()
|
||||||
|
g['id'] = 1
|
||||||
|
g['name'] = _("Default")
|
||||||
|
g['conf'] = 1
|
||||||
|
g['mod'] = intTime()
|
||||||
db.execute("""
|
db.execute("""
|
||||||
update deck set conf = ?, groups = ?, gconf = ?""",
|
update deck set conf = ?, groups = ?, gconf = ?""",
|
||||||
simplejson.dumps(anki.deck.defaultConf),
|
simplejson.dumps(anki.deck.defaultConf),
|
||||||
simplejson.dumps({'1': {'name': _("Default"), 'conf': 1,
|
simplejson.dumps({'1': g}),
|
||||||
'mod': intTime()}}),
|
|
||||||
simplejson.dumps({'1': anki.groups.defaultConf}))
|
simplejson.dumps({'1': anki.groups.defaultConf}))
|
||||||
|
|
||||||
def _updateIndices(db):
|
def _updateIndices(db):
|
||||||
|
@ -339,28 +343,29 @@ def _migrateDeckTbl(db):
|
||||||
insert or replace into deck select id, cast(created as int), :t,
|
insert or replace into deck select id, cast(created as int), :t,
|
||||||
:t, 99, 0, ifnull(syncName, ""), cast(lastSync as int),
|
:t, 99, 0, ifnull(syncName, ""), cast(lastSync as int),
|
||||||
"", "", "", "", "" from decks""", t=intTime())
|
"", "", "", "", "" from decks""", t=intTime())
|
||||||
# update selective study
|
# prepare a group to store the old deck options
|
||||||
|
import anki.groups
|
||||||
|
g = anki.groups.defaultTopConf.copy()
|
||||||
|
g['id'] = 1
|
||||||
|
g['name'] = _("Default")
|
||||||
|
g['conf'] = 1
|
||||||
|
g['mod'] = intTime()
|
||||||
|
# and deck conf
|
||||||
conf = anki.deck.defaultConf.copy()
|
conf = anki.deck.defaultConf.copy()
|
||||||
# delete old selective study settings, which we can't auto-upgrade easily
|
# delete old selective study settings, which we can't auto-upgrade easily
|
||||||
keys = ("newActive", "newInactive", "revActive", "revInactive")
|
keys = ("newActive", "newInactive", "revActive", "revInactive")
|
||||||
for k in keys:
|
for k in keys:
|
||||||
db.execute("delete from deckVars where key=:k", k=k)
|
db.execute("delete from deckVars where key=:k", k=k)
|
||||||
# copy other settings, ignoring deck order as there's a new default
|
# copy other settings, ignoring deck order as there's a new default
|
||||||
conf['newSpread'] = db.scalar(
|
g['newSpread'] = db.scalar("select newCardSpacing from decks")
|
||||||
"select newCardSpacing from decks")
|
g['newPerDay'] = db.scalar("select newCardsPerDay from decks")
|
||||||
conf['newOrder'] = db.scalar(
|
g['repLim'] = db.scalar("select sessionRepLimit from decks")
|
||||||
"select newCardOrder from decks")
|
g['timeLim'] = db.scalar("select sessionTimeLimit from decks")
|
||||||
conf['newPerDay'] = db.scalar(
|
|
||||||
"select newCardsPerDay from decks")
|
# this needs to be placed in the model later on
|
||||||
# fetch remaining settings from decks table
|
conf['oldNewOrder'] = db.scalar("select newCardOrder from decks")
|
||||||
data = {}
|
|
||||||
keys = ("sessionRepLimit", "sessionTimeLimit")
|
|
||||||
for k in keys:
|
|
||||||
conf[k] = db.scalar("select %s from decks" % k)
|
|
||||||
# random and due options merged
|
|
||||||
conf['revOrder'] = 2
|
|
||||||
# no reverse option anymore
|
# no reverse option anymore
|
||||||
conf['newOrder'] = min(1, conf['newOrder'])
|
conf['oldNewOrder'] = min(1, conf['oldNewOrder'])
|
||||||
# add any deck vars and save
|
# add any deck vars and save
|
||||||
dkeys = ("hexCache", "cssCache")
|
dkeys = ("hexCache", "cssCache")
|
||||||
for (k, v) in db.execute("select * from deckVars").fetchall():
|
for (k, v) in db.execute("select * from deckVars").fetchall():
|
||||||
|
@ -368,10 +373,9 @@ insert or replace into deck select id, cast(created as int), :t,
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
conf[k] = v
|
conf[k] = v
|
||||||
import anki.groups
|
|
||||||
db.execute("update deck set conf=:c,groups=:g,gconf=:gc",
|
db.execute("update deck set conf=:c,groups=:g,gconf=:gc",
|
||||||
c=simplejson.dumps(conf),
|
c=simplejson.dumps(conf),
|
||||||
g=simplejson.dumps({'1': {'name': _("Default"), 'conf': 1}}),
|
g=simplejson.dumps({'1': g}),
|
||||||
gc=simplejson.dumps({'1': anki.groups.defaultConf}))
|
gc=simplejson.dumps({'1': anki.groups.defaultConf}))
|
||||||
# clean up
|
# clean up
|
||||||
db.execute("drop table decks")
|
db.execute("drop table decks")
|
||||||
|
@ -479,10 +483,12 @@ def _postSchemaUpgrade(deck):
|
||||||
"Handle the rest of the upgrade to 2.0."
|
"Handle the rest of the upgrade to 2.0."
|
||||||
import anki.deck
|
import anki.deck
|
||||||
# make sure we have a current model id
|
# make sure we have a current model id
|
||||||
deck.conf['currentModelId'] = deck.models.models.keys()[0]
|
deck.models.setCurrent(deck.models.models.values()[0])
|
||||||
# regenerate css
|
# regenerate css, and set new card order
|
||||||
for m in deck.models.all():
|
for m in deck.models.all():
|
||||||
|
m['newOrder'] = deck.conf['oldNewOrder']
|
||||||
deck.models.save(m)
|
deck.models.save(m)
|
||||||
|
del deck.conf['oldNewOrder']
|
||||||
# fix creation time
|
# fix creation time
|
||||||
deck.sched._updateCutoff()
|
deck.sched._updateCutoff()
|
||||||
d = datetime.datetime.today()
|
d = datetime.datetime.today()
|
||||||
|
@ -521,7 +527,7 @@ update cards set due = cast(
|
||||||
((due-:stamp)/86400) as int)+:today where type = 2
|
((due-:stamp)/86400) as int)+:today where type = 2
|
||||||
""", stamp=deck.sched.dayCutoff, today=deck.sched.today)
|
""", stamp=deck.sched.dayCutoff, today=deck.sched.today)
|
||||||
# possibly re-randomize
|
# possibly re-randomize
|
||||||
if deck.randomNew():
|
if deck.models.randomNew():
|
||||||
deck.sched.randomizeCards()
|
deck.sched.randomizeCards()
|
||||||
# update insertion id
|
# 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 facts")+1
|
||||||
|
|
|
@ -18,7 +18,7 @@ def test_genCards():
|
||||||
assert deck.cardCount() == 2
|
assert deck.cardCount() == 2
|
||||||
assert cards[0].due == f.id
|
assert cards[0].due == f.id
|
||||||
# should work on random mode too
|
# should work on random mode too
|
||||||
deck.conf['newOrder'] = NEW_CARDS_RANDOM
|
deck.models.current()['newOrder'] = NEW_CARDS_RANDOM
|
||||||
f = deck.newFact()
|
f = deck.newFact()
|
||||||
f['Front'] = u'1'
|
f['Front'] = u'1'
|
||||||
f['Back'] = u'2'
|
f['Back'] = u'2'
|
||||||
|
@ -74,6 +74,6 @@ def test_misc():
|
||||||
f['Back'] = u'2'
|
f['Back'] = u'2'
|
||||||
d.addFact(f)
|
d.addFact(f)
|
||||||
c = f.cards()[0]
|
c = f.cards()[0]
|
||||||
id = d.conf['currentModelId']
|
id = d.models.current()['id']
|
||||||
assert c.cssClass() == "cm%s-0" % hexifyID(id)
|
assert c.cssClass() == "cm%s-0" % hexifyID(id)
|
||||||
assert c.template()['ord'] == 0
|
assert c.template()['ord'] == 0
|
||||||
|
|
|
@ -137,13 +137,30 @@ def test_groups():
|
||||||
# it should have an id of 1
|
# it should have an id of 1
|
||||||
assert deck.groups.name(1)
|
assert deck.groups.name(1)
|
||||||
# create a new group
|
# create a new group
|
||||||
g = deck.groups.id("new group")
|
parentId = deck.groups.id("new group")
|
||||||
assert g
|
assert parentId
|
||||||
assert len(deck.groups.groups) == 2
|
assert len(deck.groups.groups) == 2
|
||||||
# should get the same id
|
# should get the same id
|
||||||
assert deck.groups.id("new group") == g
|
assert deck.groups.id("new group") == parentId
|
||||||
# by default, everything should be shown
|
# by default, everything should be shown
|
||||||
assert not deck.conf['groups']
|
assert not deck.groups.selected()
|
||||||
|
assert not deck.groups.active()
|
||||||
|
# and the default group is used
|
||||||
|
assert deck.groups.top()['id'] == 1
|
||||||
|
# we can select the default explicitly
|
||||||
|
deck.groups.select(1)
|
||||||
|
assert deck.groups.selected() == 1
|
||||||
|
assert deck.groups.active() == [1]
|
||||||
|
assert deck.groups.top()['id'] == 1
|
||||||
|
# let's create a child and select that
|
||||||
|
childId = deck.groups.id("new group::child")
|
||||||
|
deck.groups.select(childId)
|
||||||
|
assert deck.groups.selected() == childId
|
||||||
|
assert deck.groups.active() == [childId]
|
||||||
|
assert deck.groups.top()['id'] == parentId
|
||||||
|
# if we select the parent, the child gets included
|
||||||
|
deck.groups.select(parentId)
|
||||||
|
assert sorted(deck.groups.active()) == [parentId, childId]
|
||||||
|
|
||||||
def test_selective():
|
def test_selective():
|
||||||
deck = getEmptyDeck()
|
deck = getEmptyDeck()
|
||||||
|
|
|
@ -10,7 +10,7 @@ def test_modelDelete():
|
||||||
f['Back'] = u'2'
|
f['Back'] = u'2'
|
||||||
deck.addFact(f)
|
deck.addFact(f)
|
||||||
assert deck.cardCount() == 1
|
assert deck.cardCount() == 1
|
||||||
deck.models.rem(deck.models.get(deck.conf['currentModelId']))
|
deck.models.rem(deck.models.current())
|
||||||
assert deck.cardCount() == 0
|
assert deck.cardCount() == 0
|
||||||
|
|
||||||
def test_modelCopy():
|
def test_modelCopy():
|
||||||
|
@ -109,7 +109,7 @@ def test_text():
|
||||||
|
|
||||||
def test_cloze():
|
def test_cloze():
|
||||||
d = getEmptyDeck()
|
d = getEmptyDeck()
|
||||||
d.conf['currentModelId'] = d.models.byName("Cloze")['id']
|
d.models.setCurrent(d.models.byName("Cloze"))
|
||||||
f = d.newFact()
|
f = d.newFact()
|
||||||
assert f.model()['name'] == "Cloze"
|
assert f.model()['name'] == "Cloze"
|
||||||
# a cloze model with no clozes is empty
|
# a cloze model with no clozes is empty
|
||||||
|
|
|
@ -581,11 +581,11 @@ def test_ordcycle():
|
||||||
def test_counts():
|
def test_counts():
|
||||||
d = getEmptyDeck()
|
d = getEmptyDeck()
|
||||||
# add a second group
|
# add a second group
|
||||||
assert d.groups.id("new group")
|
grp = d.groups.id("new group")
|
||||||
# for each card type
|
# for each card type
|
||||||
for type in range(3):
|
for type in range(3):
|
||||||
# and each of the groups
|
# and each of the groups
|
||||||
for gid in (1,2):
|
for gid in (1,grp):
|
||||||
# create a new fact
|
# create a new fact
|
||||||
f = d.newFact()
|
f = d.newFact()
|
||||||
f['Front'] = u"one"
|
f['Front'] = u"one"
|
||||||
|
@ -601,13 +601,9 @@ def test_counts():
|
||||||
# with the default settings, there's no count limit
|
# with the default settings, there's no count limit
|
||||||
assert d.sched.counts() == (2,2,2)
|
assert d.sched.counts() == (2,2,2)
|
||||||
# check limit to one group
|
# check limit to one group
|
||||||
d.conf['groups'] = [1]
|
d.groups.select(1)
|
||||||
d.reset()
|
d.reset()
|
||||||
assert d.sched.counts() == (1,1,1)
|
assert d.sched.counts() == (1,1,1)
|
||||||
# we don't need to build the queue to get the counts
|
|
||||||
assert d.sched.allCounts() == (2,2,2)
|
|
||||||
assert d.sched.selCounts() == (1,1,1)
|
|
||||||
assert d.sched.allCounts() == (2,2,2)
|
|
||||||
|
|
||||||
def test_counts2():
|
def test_counts2():
|
||||||
d = getEmptyDeck()
|
d = getEmptyDeck()
|
||||||
|
|
|
@ -7,9 +7,8 @@ def test_op():
|
||||||
# should have no undo by default
|
# should have no undo by default
|
||||||
assert not d.undoName()
|
assert not d.undoName()
|
||||||
# let's adjust a study option
|
# let's adjust a study option
|
||||||
assert d.conf['repLim'] == 0
|
|
||||||
d.save("studyopts")
|
d.save("studyopts")
|
||||||
d.conf['repLim'] = 10
|
d.conf['revOrder'] = 5
|
||||||
# it should be listed as undoable
|
# it should be listed as undoable
|
||||||
assert d.undoName() == "studyopts"
|
assert d.undoName() == "studyopts"
|
||||||
# with about 5 minutes until it's clobbered
|
# with about 5 minutes until it's clobbered
|
||||||
|
@ -17,7 +16,7 @@ def test_op():
|
||||||
# undoing should restore the old value
|
# undoing should restore the old value
|
||||||
d.undo()
|
d.undo()
|
||||||
assert not d.undoName()
|
assert not d.undoName()
|
||||||
assert d.conf['repLim'] == 0
|
assert d.conf['revOrder'] != 5
|
||||||
# an (auto)save will clear the undo
|
# an (auto)save will clear the undo
|
||||||
d.save("foo")
|
d.save("foo")
|
||||||
assert d.undoName() == "foo"
|
assert d.undoName() == "foo"
|
||||||
|
|
Loading…
Reference in a new issue