def _showCardValues(self): lapses = self.card.lapses # since we just failed the card, current ivl is no use interval = fmtTimeSpan(self.card.lastIvl * 86400) ease = self.card.factor // 10 statManager = CardStats(self.mw.col, self.card) secondsSpent = self.mw.col.db.first( "select sum(time)/1000 from revlog where cid = :id", id=self.card.id)[0] totalTime = statManager.time(secondsSpent) # watch on updates: calling a private method of sched for config info leechThreshold = self.mw.col.sched._lapseConf(self.card)['leechFails'] if leechThreshold == lapses: timesLeeched = "Leech for the first time." else: beyondOne = (lapses - leechThreshold) // (leechThreshold // 2) timesLeeched = "Leeched %i times." % (beyondOne + 1) txt = ("{leechtimes}<br>" "<b>Lapses</b>: {lapses}<br>" "<b>Last interval</b>: {lastivl}<br>" "<b>Ease</b>: {ease}%<br>" "<b>Total time</b>: {ttime}") self.form.label.setText( txt.format(leechtimes=timesLeeched, lapses=lapses, lastivl=interval, ease=ease, ttime=totalTime))
def _update(self): if not self.shown: return txt = "" r = self.mw.reviewer d = self.mw.col cs = CardStats(d, r.card) cc = r.card if cc: txt += _("<h3>Current</h3>") txt += d.cardStats(cc) txt += "<p>" txt += self._revlogData(cc, cs) lc = r.lastCard() if lc: txt += _("<h3>Last</h3>") txt += d.cardStats(lc) txt += "<p>" txt += self._revlogData(lc, cs) if not txt: txt = _("No current card or last card.") style = self._style() self.web.setHtml(""" <html><head> </head><style>%s</style> <body><center>%s</center></body></html>""" % (style, txt))
def _cardInfoData(self): from anki.stats import CardStats cs = CardStats(self.col, self.card) rep = cs.report() rep = "<style>table * { font-size: 12px; }</style>" + rep m = self.card.model() rep = """ <div style='width: 400px; margin: 0 auto 0; border: 1px solid #000; padding: 3px; '>%s</div>""" % rep return rep, cs
def __init__(self, parent: QWidget, mw: aqt.AnkiQt, card: Card) -> None: super().__init__(parent) disable_help_button(self) cs = CardStats(mw.col, card) info = cs.report(include_revlog=True) l = QVBoxLayout() l.setContentsMargins(0, 0, 0, 0) w = AnkiWebView(title="browser card info") l.addWidget(w) w.stdHtml(info + "<p>", context=self) bb = QDialogButtonBox(QDialogButtonBox.Close) l.addWidget(bb) qconnect(bb.rejected, self.reject) self.setLayout(l) self.setWindowModality(Qt.WindowModal) self.resize(500, 400) restoreGeom(self, "revlog") self.show()
def cardStats(self, card: Card) -> str: from anki.stats import CardStats return CardStats(self, card).report()
def onAdvBrowserLoad(self, advBrowser): """Called when the Advanced Browser add-on has finished loading. Create and add all custom columns owned by this module.""" # Store a list of CustomColumns managed by this module. We later # use this to build our part of the context menu. self.customColumns = [] # Convenience method to create lambdas without scope clobbering def getOnSort(f): return lambda: f # Dummy CardStats object so we can use the time() function without # creating the object every time. cs = CardStats(None, None) # -- Columns -- # # First review def cFirstOnData(c, n, t): first = mw.col.db.scalar( "select min(id) from revlog where cid = ?", c.id) if first: return time.strftime("%Y-%m-%d", time.localtime(first / 1000)) cc = advBrowser.newCustomColumn( type='cfirst', name='First Review', onData=cFirstOnData, onSort=lambda: "(select min(id) from revlog where cid = c.id)") self.customColumns.append(cc) # Last review def cLastOnData(c, n, t): last = mw.col.db.scalar("select max(id) from revlog where cid = ?", c.id) if last: return time.strftime("%Y-%m-%d", time.localtime(last / 1000)) cc = advBrowser.newCustomColumn( type='clast', name='Last Review', onData=cLastOnData, onSort=lambda: "(select max(id) from revlog where cid = c.id)") self.customColumns.append(cc) # Average time def cAvgtimeOnData(c, n, t): avgtime = mw.col.db.scalar( "select avg(time)/1000.0 from revlog where cid = ?", c.id) return self.timeFmt(avgtime) cc = advBrowser.newCustomColumn( type='cavgtime', name='Time (Average)', onData=cAvgtimeOnData, onSort=lambda: "(select avg(time) from revlog where cid = c.id)") self.customColumns.append(cc) # Total time def cTottimeOnData(c, n, t): tottime = mw.col.db.scalar( "select sum(time)/1000.0 from revlog where cid = ?", c.id) return self.timeFmt(tottime) cc = advBrowser.newCustomColumn( type='ctottime', name='Time (Total)', onData=cTottimeOnData, onSort=lambda: "(select sum(time) from revlog where cid = c.id)") self.customColumns.append(cc) # Fastest time def cFasttimeOnData(c, n, t): tm = mw.col.db.scalar( "select time/1000.0 from revlog where cid = ? " "order by time asc limit 1", c.id) return self.timeFmt(tm) srt = ("(select time/1000.0 from revlog where cid = c.id " "order by time asc limit 1)") cc = advBrowser.newCustomColumn(type='cfasttime', name='Fastest Review', onData=cFasttimeOnData, onSort=getOnSort(srt)) self.customColumns.append(cc) # Slowest time def cSlowtimeOnData(c, n, t): tm = mw.col.db.scalar( "select time/1000.0 from revlog where cid = ? " "order by time desc limit 1", c.id) return self.timeFmt(tm) srt = ("(select time/1000.0 from revlog where cid = c.id " "order by time desc limit 1)") cc = advBrowser.newCustomColumn(type='cslowtime', name='Slowest Review', onData=cSlowtimeOnData, onSort=getOnSort(srt)) self.customColumns.append(cc) # Tags cc = advBrowser.newCustomColumn( type='ntags', name='Tags', onData=lambda c, n, t: " ".join(unicode(tag) for tag in n.tags), # Lazy shortcut. Treat the "Tags" column as if it were a note field # (it is!) so we get all the benefits of our custom work on those # fields. onSort=lambda: "(select valueForField(mid, flds, 'Tags') " "from notes where id = c.nid)", ) self.customColumns.append(cc) # Remove the built-in tags column. advBrowser.removeColumn("noteTags") # Overdue interval def cOverdueIvl(c, n, t): val = self.valueForOverdue(c.odid, c.queue, c.type, c.due) if val: return str(val) + " day" + ('s' if val > 1 else '') srt = ("(select valueForOverdue(odid, queue, type, due) " "from cards where id = c.id)") cc = advBrowser.newCustomColumn(type='coverdueivl', name="Overdue Interval", onData=cOverdueIvl, onSort=getOnSort(srt)) self.customColumns.append(cc) # Previous interval def cPrevIvl(c, n, t): ivl = mw.col.db.scalar( "select ivl from revlog where cid = ? " "order by id desc limit 1 offset 1", c.id) if ivl is None: return elif ivl == 0: return "0 days" elif ivl > 0: return fmtTimeSpan(ivl * 86400) else: return cs.time(-ivl) srt = ("(select ivl from revlog where cid = c.id " "order by id desc limit 1 offset 1)") cc = advBrowser.newCustomColumn(type='cprevivl', name="Previous Interval", onData=cPrevIvl, onSort=getOnSort(srt)) self.customColumns.append(cc) # Percent correct def cPctCorrect(c, n, t): if c.reps > 0: return "{:2.0f}%".format(100 - ((c.lapses / float(c.reps)) * 100)) return "0%" cc = advBrowser.newCustomColumn( type='cpct', name='Percent Correct', onData=cPctCorrect, onSort=lambda: "cast(c.lapses as real)/c.reps") self.customColumns.append(cc) # Previous duration def cPrevDur(c, n, t): time = mw.col.db.scalar( "select time/1000.0 from revlog where cid = ? " "order by id desc limit 1", c.id) return self.timeFmt(time) srt = ("(select time/1000.0 from revlog where cid = c.id " "order by id desc limit 1)") cc = advBrowser.newCustomColumn(type='cprevdur', name="Previous Duration", onData=cPrevDur, onSort=getOnSort(srt)) self.customColumns.append(cc)
def _update(self): if not self.dock or not self.dock.isVisible(): return if freezeMode and self.type>10: return card = mw.reviewer.card txt = _("No Current Card") if self.type<-10: txt = _("No Last Card") card = mw.reviewer.lastCard() cs = CollectionStats(mw.col) cs.wholeCollection=True if self.type<0 else False if self.type==0: #review count txt = "<center>%s</center>" % mw.reviewer._remaining() if mw.state=="review" else '? + ? + ?' elif abs(self.type)==1: #todayStats txt = "<center>%s</center>" % cs.todayStats() elif abs(self.type)==2: #forecast chart #no filtered decks did=mw.col.decks.selected() dyn=mw.col.decks.get(did) if dyn['dyn'] and self.type>0: txt="No data for filtered decks" elif ANKI21: p=mw.mediaServer.getPort() txt = """ <script src="http://localhost:%d/_anki/jquery.js"></script> <script src="http://localhost:%d/_anki/plot.js"></script> <center>%s</center> """ % (p,p,self.dueGraph(cs)) else: txt = "<script>%s\n</script><center>%s</center>" % ( anki.js.jquery+anki.js.plot, self.dueGraph(cs)) elif abs(self.type)>=90: txt = self.customViews(card) elif mw.state!='review' and self.type>10: txt = _("No Review Card") elif card: #left or right col cs = CardStats(mw.col, card) if abs(self.type)==12: #card rev log txt = self._revlogData(card, cs) else: #card stats txt = self.deckOptionsInfo(card) txt += _("<h3>Card Stats:</h3>") txt += self.mini_card_stats(card) txt += cs.report() did = card.odid if card.odid else card.did fdid = card.did if card.odid else 0 deckName = mw.col.decks.get(did)['name'] #in case of filtered decks sis=[i for i in mw.col.db.list("select id from cards where nid=?", card.nid)] tags=mw.col.getNote(card.nid).stringTags() txt += """<table width="100%%"><tr> <td><b>Deck ID:</b></td> <td>%d</td></tr><tr> <td><b>Fil DID:</b></td> <td>%d</td></tr><tr> <td><b>oDeck:</b></td> <td>%s</td></tr><tr> <td><b>SiblingCnt:</b></td> <td>%d</td></tr><tr> <td><b>Siblings:</b></td> <td>%s</td></tr><tr> <td><b>Tags:</b></td> <td>%s</td> </tr></table>""" % (did, fdid, deckName, len(sis), str(sis), tags) style = self._style() self.web.setHtml(""" <!doctype html><html><head><style>%s</style></head> <body class="night_mode">%s</body></html>""" % (style, txt))
def onAdvBrowserLoad(self, advBrowser): """Called when the Advanced Browser add-on has finished loading. Create and add all custom columns owned by this module.""" # Store a list of CustomColumns managed by this module. We later # use this to build our part of the context menu. self.customColumns = [] # Convenience method to create lambdas without scope clobbering def getOnSort(f): return lambda: f # Dummy CardStats object so we can use the time() function without # creating the object every time. cs = CardStats(None, None) # -- Columns -- # # First review def cFirstOnData(c, n, t): first = mw.col.db.scalar( "select min(id) from revlog where cid = ?", c.id) if first: return time.strftime("%Y-%m-%d", time.localtime(first / 1000)) cc = advBrowser.newCustomColumn( type='cfirst', name='First Review', onData=cFirstOnData, onSort=lambda: "(select min(id) from revlog where cid = c.id)") self.customColumns.append(cc) # Last review def cLastOnData(c, n, t): last = mw.col.db.scalar("select max(id) from revlog where cid = ?", c.id) if last: return time.strftime("%Y-%m-%d", time.localtime(last / 1000)) cc = advBrowser.newCustomColumn( type='clast', name='Last Review', onData=cLastOnData, onSort=lambda: "(select max(id) from revlog where cid = c.id)") self.customColumns.append(cc) # Average time def cAvgtimeOnData(c, n, t): avgtime = mw.col.db.scalar( "select avg(time) from revlog where cid = ?", c.id) if avgtime: return str(round(avgtime / 1000, 1)) + "s" cc = advBrowser.newCustomColumn( type='cavgtime', name='Time (Average)', onData=cAvgtimeOnData, onSort=lambda: "(select avg(time) from revlog where cid = c.id)") self.customColumns.append(cc) # Total time def cTottimeOnDAta(c, n, t): tottime = mw.col.db.scalar( "select sum(time) from revlog where cid = ?", c.id) if tottime: return str(round(tottime / 1000, 1)) + "s" cc = advBrowser.newCustomColumn( type='ctottime', name='Time (Total)', onData=cTottimeOnDAta, onSort=lambda: "(select sum(time) from revlog where cid = c.id)") self.customColumns.append(cc) # Tags cc = advBrowser.newCustomColumn( type='ntags', name='Tags', onData=lambda c, n, t: " ".join(unicode(tag) for tag in n.tags), onSort=lambda: "n.tags") self.customColumns.append(cc) # Remove the built-in tags column. advBrowser.removeColumn("noteTags") # Overdue interval def cOverdueIvl(c, n, t): val = self.valueForOverdue(c.odid, c.queue, c.type, c.due) if val: return str(val) + " day" + ('s' if val > 1 else '') srt = ("(select valueForOverdue(odid, queue, type, due) " "from cards where id = c.id)") cc = advBrowser.newCustomColumn(type='coverdueivl', name="Overdue Interval", onData=cOverdueIvl, onSort=getOnSort(srt)) self.customColumns.append(cc) # Previous interval def cPrevIvl(c, n, t): ivl = mw.col.db.scalar( "select ivl from revlog where cid = ? " "order by id desc limit 1 offset 1", c.id) if ivl is None: return elif ivl == 0: return "0 days" elif ivl > 0: return fmtTimeSpan(ivl * 86400) else: return cs.time(-ivl) srt = ("(select ivl from revlog where cid = c.id " "order by id desc limit 1 offset 1)") cc = advBrowser.newCustomColumn(type='cprevivl', name="Previous Interval", onData=cPrevIvl, onSort=getOnSort(srt)) self.customColumns.append(cc)
def _renderQA(self, data, qfmt=None, afmt=None, _old=None): """ this function overwrites a function from collection.py The beginning of this function reads: "Returns hash of id, question, answer." # data is [cid, nid, mid, did, ord, tags, flds] # unpack fields and create dict """ data = list(data) origFieldMap = self.models.fieldMap model = self.models.get(data[2]) if data[0] is None: card = None elif data[0] == 1: card = None else: try: card = self.getCard(data[0]) except: card = None origdata = copy.copy(data) data[6] += "\x1f" addInfo = collections.OrderedDict() if card is not None: r = mw.reviewer d = mw.col cs = CardStats(d, r.card) if card.odid: conf = d.decks.confForDid(card.odid) else: conf = d.decks.confForDid(card.did) (first, last, cnt, total) = self.db.first( "select min(id), max(id), count(), sum(time)/1000 from revlog where cid = :id", id=card.id) if cnt: addInfo['FirstReview'] = time.strftime( "%a, %d %b %Y %H:%M:%S", time.localtime(first / 1000)) addInfo['LastReview'] = time.strftime("%a, %d %b %Y %H:%M:%S", time.localtime(last / 1000)) # https://docs.python.org/2/library/datetime.html #todo addInfo['TimeAvg'] = timefn(total / float(cnt)) addInfo['TimeTotal'] = timefn(total) cOverdueIvl = valueForOverdue(card.odid, card.queue, card.type, card.due, d) if cOverdueIvl: addInfo['overdue_fmt'] = str(cOverdueIvl) + " day" + ( 's' if cOverdueIvl > 1 else '') addInfo['overdue_days'] = str(cOverdueIvl) model = self.models.get(data[2]) addInfo['external_file_link'] = external_file_link(card, model) addInfo['Ord'] = data[4] addInfo['Did'] = card.did addInfo['Due'] = card.due addInfo['Id'] = card.id addInfo['Ivl'] = card.ivl addInfo['Queue'] = card.queue addInfo['Reviews'] = card.reps addInfo['Lapses'] = card.lapses addInfo['Type'] = card.type addInfo['Nid'] = card.nid addInfo['Mod'] = time.strftime("%Y-%m-%d", time.localtime(card.mod)) addInfo['Usn'] = card.usn addInfo['Factor'] = card.factor addInfo['Ease'] = int(card.factor / 10) addInfo['Review?'] = 'Review' if card.type == 2 else '' addInfo['New?'] = 'New' if card.type == 0 else '' addInfo['Learning?'] = 'Learning' if card.type == 1 else '' addInfo[ 'TodayLearning?'] = 'Learning' if card.type == 1 and card.queue == 1 else '' addInfo[ 'DayLearning?'] = 'Learning' if card.type == 1 and card.queue == 3 else '' addInfo['Young'] = 'Young' if card.type == 2 and card.ivl < 21 else '' addInfo[ 'Mature'] = 'Mature' if card.type == 2 and card.ivl > 20 else '' if gc("make_deck_options_available", False): addInfo['Options_Group_ID'] = conf['id'] addInfo['Options_Group_Name'] = conf['name'] addInfo['Ignore_answer_times_longer_than'] = conf['maxTaken'] addInfo['Show_answer_time'] = conf['timer'] addInfo['Auto_play_audio'] = conf['autoplay'] addInfo['When_answer_shown_replay_q'] = conf['replayq'] addInfo['is_filtered_deck'] = conf['dyn'] addInfo['deck_usn'] = conf['usn'] addInfo['deck_mod_time'] = conf['mod'] addInfo['new__steps_in_minutes'] = conf['new']['delays'] addInfo['new__order_of_new_cards'] = conf['new']['order'] addInfo['new__cards_per_day'] = conf['new']['perDay'] addInfo['graduating_interval'] = conf['new']['ints'][0] addInfo['easy_interval'] = conf['new']['ints'][1] addInfo['Starting_ease'] = int(conf['new']['initialFactor'] / 10) addInfo['bury_related_new_cards'] = conf['new']['bury'] addInfo['MaxiumReviewsPerDay'] = conf['rev']['perDay'] addInfo['EasyBonus'] = int(100 * conf['rev']['ease4']) addInfo['IntervalModifier'] = int(100 * conf['rev']['ivlFct']) addInfo['MaximumInterval'] = conf['rev']['maxIvl'] addInfo['bur_related_reviews_until_next_day'] = conf['rev']['bury'] addInfo['lapse_learning_steps'] = conf['lapse']['delays'] addInfo['lapse_new_ivl'] = int(100 * conf['lapse']['mult']) addInfo['lapse_min_ivl'] = conf['lapse']['minInt'] addInfo['lapse_leech_threshold'] = conf['lapse']['leechFails'] addInfo['lapse_leech_action'] = conf['lapse']['leechAction'] addInfo['Date_Created'] = time.strftime( "%Y-%m-%d %H:%M:%S", time.localtime(card.nid / 1000)) # add your additional fields here # for debugging quickly add all values to template tt = " <table>" + "\n" for k in addInfo.keys(): tt += '<tr><td align="left">%s </td><td align="left"> {{info::%s}} </td></tr> +\n' % ( k, k) tt += " </table>" + "\n" addInfo['allfields'] = tt additionalFields = [""] * len(addInfo) for i, v in enumerate(addInfo.values()): additionalFields[i] = str(v) data[6] += "\x1f".join( additionalFields) # \x1f is used as a field separator def tmpFieldMap(m): "Mapping of field name -> (ord, field)." d = dict((f['name'], (f['ord'], f)) for f in m['flds']) newFields = [] for k in addInfo.keys(): newFields.append('info::' + k) for i, f in enumerate(newFields): d[f] = (len(m['flds']) + i, 0) return d self.models.fieldMap = tmpFieldMap data = tuple(data) result = _old(self, data, qfmt, afmt) data = origdata self.models.fieldMap = origFieldMap return result
def _update(self): config = mw.addonManager.getConfig(__name__) infobar_currentReviewCount = config[ 'Card Info sidebar_ Current Review Count'] try: sidebar_PreviousCards = int( config['Card Info sidebar_ Number of previous cards to show']) except ValueError: sidebar_PreviousCards = 2 if not self.shown: return txt = "" r = self.mw.reviewer d = self.mw.col cs = CardStats(d, r.card) current_card = r.card review_count = len(self.mw.reviewer._answeredIds) styles = """<style> .title { font-family: arial; padding-bottom: 15px; font-weight: bold; }</style>""" currentReviewCount = "<div class='title'>Current Card</div><div style='font-family: courier; font-size: 10px;'>Current Review Count: {}</div>".format( review_count) if current_card: txt += styles if infobar_currentReviewCount: txt += currentReviewCount else: txt += "<div class='title'>Current Card</div>" txt += d.cardStats_mod(current_card) txt += "<p>" txt += r._revlogData_mod(current_card, cs) card2 = r.lastCard() if card2 and sidebar_PreviousCards > 1: if sidebar_PreviousCards == 2: txt += "<hr><div class='title'>Last Card</div>" else: txt += "<hr><div class='title'>Card 2</div>" txt += d.cardStats_mod(card2) txt += "<p>" txt += r._revlogData_mod(card2, cs) if sidebar_PreviousCards < 3: if infobar_currentReviewCount: txt += currentReviewCount card3 = r.lastCard2() if card3 and sidebar_PreviousCards > 2: txt += "<hr><div class='title''>Card 3</div>" txt += d.cardStats_mod(card3) txt += "<p>" txt += r._revlogData_mod(card3, cs) if sidebar_PreviousCards < 4: if infobar_currentReviewCount: txt += currentReviewCount card4 = r.lastCard3() if card4 and sidebar_PreviousCards > 3: txt += "<hr><div class='title''>Card 4</div>" txt += d.cardStats_mod(card4) txt += "<p>" txt += r._revlogData_mod(card4, cs) if infobar_currentReviewCount: txt += currentReviewCount if not txt: styles = """<style> .title { font-family: arial; padding-bottom: 15px; font-weight: bold; }</style>""" txt = styles card2 = r.lastCard() if card2 and sidebar_PreviousCards > 1: txt += "<div class='title'>Last Card</div>" txt += d.cardStats_mod(card2) txt += "<p>" txt += r._revlogData_mod(card2, cs) if sidebar_PreviousCards < 3: if infobar_currentReviewCount: txt += currentReviewCount card3 = r.lastCard2() if card3 and sidebar_PreviousCards > 2: txt += "<hr><div class='title''>Card 2</div>" txt += d.cardStats_mod(card3) txt += "<p>" txt += r._revlogData_mod(card3, cs) if sidebar_PreviousCards < 4: if infobar_currentReviewCount: txt += currentReviewCount card4 = r.lastCard3() if card4 and sidebar_PreviousCards > 3: txt += "<hr><div class='title''>Card 3</div>" txt += d.cardStats_mod(card4) txt += "<p>" txt += r._revlogData_mod(card4, cs) if infobar_currentReviewCount: txt += currentReviewCount style = self._style() self.web.setHtml(""" <html> <head> <style>%s</style> </head> <body> <center>%s</center> </body> </html> """ % (style, txt))
def cardStats_mod(self, card): from anki.stats import CardStats return CardStats(self, card).report_mod()
def _cardInfoData(self) -> Tuple[str, CardStats]: cs = CardStats(self.col, self.card) rep = cs.report(include_revlog=True) return rep, cs
def onAdvBrowserLoad(self, advBrowser): """Called when the Advanced Browser add-on has finished loading. Create and add all custom columns owned by this module.""" # Store a list of CustomColumns managed by this module. We later # use this to build our part of the context menu. self.customColumns = [] # Convenience method to create lambdas without scope clobbering def getOnSort(f): return lambda: f # Dummy CardStats object so we can use the time() function without # creating the object every time. cs = CardStats(None, None) # -- Columns -- # # First review def cFirstOnData(c, n, t): first = mw.col.db.scalar( "select min(id) from revlog where cid = ?", c.id) if first: return time.strftime("%Y-%m-%d", time.localtime(first / 1000)) cc = advBrowser.newCustomColumn( type='cfirst', name='First Review', onData=cFirstOnData, onSort=lambda: "(select min(id) from revlog where cid = c.id)" ) self.customColumns.append(cc) # Last review def cLastOnData(c, n, t): last = mw.col.db.scalar( "select max(id) from revlog where cid = ?", c.id) if last: return time.strftime("%Y-%m-%d", time.localtime(last / 1000)) cc = advBrowser.newCustomColumn( type='clast', name='Last Review', onData=cLastOnData, onSort=lambda: "(select max(id) from revlog where cid = c.id)" ) self.customColumns.append(cc) # Average time def cAvgtimeOnData(c, n, t): avgtime = mw.col.db.scalar( "select avg(time)/1000.0 from revlog where cid = ?", c.id) return self.timeFmt(avgtime) cc = advBrowser.newCustomColumn( type='cavgtime', name='Time (Average)', onData=cAvgtimeOnData, onSort=lambda: "(select avg(time) from revlog where cid = c.id)" ) self.customColumns.append(cc) # Total time def cTottimeOnData(c, n, t): tottime = mw.col.db.scalar( "select sum(time)/1000.0 from revlog where cid = ?", c.id) return self.timeFmt(tottime) cc = advBrowser.newCustomColumn( type='ctottime', name='Time (Total)', onData=cTottimeOnData, onSort=lambda: "(select sum(time) from revlog where cid = c.id)" ) self.customColumns.append(cc) # Fastest time def cFasttimeOnData(c, n, t): tm = mw.col.db.scalar( "select time/1000.0 from revlog where cid = ? " "order by time asc limit 1", c.id) return self.timeFmt(tm) srt = ("(select time/1000.0 from revlog where cid = c.id " "order by time asc limit 1)") cc = advBrowser.newCustomColumn( type='cfasttime', name='Fastest Review', onData=cFasttimeOnData, onSort=getOnSort(srt) ) self.customColumns.append(cc) # Slowest time def cSlowtimeOnData(c, n, t): tm = mw.col.db.scalar( "select time/1000.0 from revlog where cid = ? " "order by time desc limit 1", c.id) return self.timeFmt(tm) srt = ("(select time/1000.0 from revlog where cid = c.id " "order by time desc limit 1)") cc = advBrowser.newCustomColumn( type='cslowtime', name='Slowest Review', onData=cSlowtimeOnData, onSort=getOnSort(srt) ) self.customColumns.append(cc) # Overdue interval def cOverdueIvl(c, n, t): val = self.valueForOverdue(c.odid, c.queue, c.type, c.due) if val: return str(val) + " day" + ('s' if val > 1 else '') # fixme: this will need to be converted into an sql case statement srt = ("(select due " #valueForOverdue(odid, queue, type, due) " "from cards where id = c.id)") cc = advBrowser.newCustomColumn( type='coverdueivl', name="Overdue Interval", onData=cOverdueIvl, onSort=getOnSort(srt) ) self.customColumns.append(cc) # Previous interval def cPrevIvl(c, n, t): ivl = mw.col.db.scalar( "select ivl from revlog where cid = ? " "order by id desc limit 1 offset 1", c.id) if ivl is None: return elif ivl == 0: return "0 days" elif ivl > 0: return fmtTimeSpan(ivl*86400) else: return cs.time(-ivl) srt = ("(select ivl from revlog where cid = c.id " "order by id desc limit 1 offset 1)") cc = advBrowser.newCustomColumn( type='cprevivl', name="Previous Interval", onData=cPrevIvl, onSort=getOnSort(srt) ) self.customColumns.append(cc) # Percent correct def cPctCorrect(c, n, t): if c.reps > 0: return "{:2.0f}%".format( 100 - ((c.lapses / float(c.reps)) * 100)) return "0%" cc = advBrowser.newCustomColumn( type='cpct', name='Percent Correct', onData=cPctCorrect, onSort=lambda: "cast(c.lapses as real)/c.reps" ) self.customColumns.append(cc) # Previous duration def cPrevDur(c, n, t): time = mw.col.db.scalar( "select time/1000.0 from revlog where cid = ? " "order by id desc limit 1", c.id) return self.timeFmt(time) srt = ("(select time/1000.0 from revlog where cid = c.id " "order by id desc limit 1)") cc = advBrowser.newCustomColumn( type='cprevdur', name="Previous Duration", onData=cPrevDur, onSort=getOnSort(srt) ) self.customColumns.append(cc) # Date (and time) created def cDateTimeCrt(c, n, t): return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(c.note().id/1000)) cc = advBrowser.newCustomColumn( type='cdatetimecrt', name='Created', onData=cDateTimeCrt, onSort=lambda: "n.id" ) self.customColumns.append(cc) # fixme: sorting cc = advBrowser.newCustomColumn( type="cdeck", name="Current Deck", onData=lambda c, n, t: advBrowser.mw.col.decks.name(c.did), onSort=lambda: "c.did" # "nameForDeck(c.did)", ) self.customColumns.append(cc)
def get_all_fields(context: TemplateRenderContext) -> Dict[str, Any]: addInfo: Dict[str, Any] = {} card = context.card() # note: card will no longer be none, and this if statement can be removed if card is not None: r = mw.reviewer d = mw.col cs = CardStats(d, r.card) if card.odid: conf = d.decks.confForDid(card.odid) else: conf = d.decks.confForDid(card.did) (first, last, cnt, total) = mw.col.db.first( "select min(id), max(id), count(), sum(time)/1000 from revlog where cid = :id", id=card.id, ) if cnt: addInfo["FirstReview"] = time.strftime( "%a, %d %b %Y %H:%M:%S", time.localtime(first / 1000) ) addInfo["LastReview"] = time.strftime( "%a, %d %b %Y %H:%M:%S", time.localtime(last / 1000) ) # https://docs.python.org/2/library/datetime.html #todo addInfo["TimeAvg"] = timefn(total / float(cnt)) addInfo["TimeTotal"] = timefn(total) cOverdueIvl = valueForOverdue(card.odid, card.queue, card.type, card.due, d) if cOverdueIvl: addInfo["overdue_fmt"] = ( str(cOverdueIvl) + " day" + ("s" if cOverdueIvl > 1 else "") ) addInfo["overdue_days"] = str(cOverdueIvl) addInfo["external_file_link"] = external_file_link(card, context.note_type()) addInfo["Ord"] = card.ord addInfo["Did"] = card.did addInfo["Due"] = card.due addInfo["Id"] = card.id addInfo["Ivl"] = card.ivl addInfo["Queue"] = card.queue addInfo["Reviews"] = card.reps addInfo["Lapses"] = card.lapses addInfo["Type"] = card.type addInfo["Nid"] = card.nid addInfo["Mod"] = time.strftime("%Y-%m-%d", time.localtime(card.mod)) addInfo["Usn"] = card.usn addInfo["Factor"] = card.factor addInfo["Ease"] = int(card.factor / 10) addInfo["Review?"] = "Review" if card.type == 2 else "" addInfo["New?"] = "New" if card.type == 0 else "" addInfo["Learning?"] = "Learning" if card.type == 1 else "" addInfo["TodayLearning?"] = ( "Learning" if card.type == 1 and card.queue == 1 else "" ) addInfo["DayLearning?"] = ( "Learning" if card.type == 1 and card.queue == 3 else "" ) addInfo["Young"] = "Young" if card.type == 2 and card.ivl < 21 else "" addInfo["Mature"] = "Mature" if card.type == 2 and card.ivl > 20 else "" if gc("make_deck_options_available", False): addInfo["Options_Group_ID"] = conf["id"] addInfo["Options_Group_Name"] = conf["name"] addInfo["Ignore_answer_times_longer_than"] = conf["maxTaken"] addInfo["Show_answer_time"] = conf["timer"] addInfo["Auto_play_audio"] = conf["autoplay"] addInfo["When_answer_shown_replay_q"] = conf["replayq"] addInfo["is_filtered_deck"] = conf["dyn"] addInfo["deck_usn"] = conf["usn"] addInfo["deck_mod_time"] = conf["mod"] addInfo["new__steps_in_minutes"] = conf["new"]["delays"] addInfo["new__order_of_new_cards"] = conf["new"]["order"] addInfo["new__cards_per_day"] = conf["new"]["perDay"] addInfo["graduating_interval"] = conf["new"]["ints"][0] addInfo["easy_interval"] = conf["new"]["ints"][1] addInfo["Starting_ease"] = int(conf["new"]["initialFactor"] / 10) addInfo["bury_related_new_cards"] = conf["new"]["bury"] addInfo["MaxiumReviewsPerDay"] = conf["rev"]["perDay"] addInfo["EasyBonus"] = int(100 * conf["rev"]["ease4"]) addInfo["IntervalModifier"] = int(100 * conf["rev"]["ivlFct"]) addInfo["MaximumInterval"] = conf["rev"]["maxIvl"] addInfo["bur_related_reviews_until_next_day"] = conf["rev"]["bury"] addInfo["lapse_learning_steps"] = conf["lapse"]["delays"] addInfo["lapse_new_ivl"] = int(100 * conf["lapse"]["mult"]) addInfo["lapse_min_ivl"] = conf["lapse"]["minInt"] addInfo["lapse_leech_threshold"] = conf["lapse"]["leechFails"] addInfo["lapse_leech_action"] = conf["lapse"]["leechAction"] addInfo["Date_Created"] = time.strftime( "%Y-%m-%d %H:%M:%S", time.localtime(card.nid / 1000) ) # add your additional fields here # for debugging quickly tt = " <table>" + "\n" for k in sorted(addInfo.keys()): tt += '<tr><td align="left">%s </td><td align="left"> %s </td></tr> +\n' % ( k, addInfo[k], ) tt += " </table>" + "\n" addInfo["allfields"] = tt return addInfo