refactor features to use hooks, update stdmodels, update findTags()

- remove description from fields, cards and models
- remove features and use field names instead
This commit is contained in:
Damien Elmes 2008-12-03 19:22:15 +09:00
parent 97caa8119f
commit 21b59408cd
10 changed files with 166 additions and 323 deletions

View file

@ -20,6 +20,7 @@ from anki.history import CardHistoryEntry
from anki.models import Model, CardModel, formatQA from anki.models import Model, CardModel, formatQA
from anki.stats import dailyStats, globalStats, genToday from anki.stats import dailyStats, globalStats, genToday
from anki.fonts import toPlatformFont from anki.fonts import toPlatformFont
import anki.features
from operator import itemgetter from operator import itemgetter
from itertools import groupby from itertools import groupby

View file

@ -13,7 +13,7 @@ from anki.db import *
from anki.errors import * from anki.errors import *
from anki.models import Model, FieldModel, fieldModelsTable, formatQA from anki.models import Model, FieldModel, fieldModelsTable, formatQA
from anki.utils import genID from anki.utils import genID
from anki.features import FeatureManager from anki.hooks import runHook
# Fields in a fact # Fields in a fact
########################################################################## ##########################################################################
@ -121,12 +121,8 @@ class Fact(object):
req += " and id != %s" % field.id req += " and id != %s" % field.id
return not s.scalar(req, val=field.value, fmid=field.fieldModel.id) return not s.scalar(req, val=field.value, fmid=field.fieldModel.id)
def onSubmit(self): def focusLost(self, field):
FeatureManager.run(self.model.features, "onSubmit", self) runHook('fact.focusLost', self, field)
def onKeyPress(self, field, value):
FeatureManager.run(self.model.features,
"onKeyPress", self, field, value)
def setModified(self, textChanged=False): def setModified(self, textChanged=False):
"Mark modified and update cards." "Mark modified and update cards."

View file

@ -3,63 +3,9 @@
# License: GNU GPL, version 3 or later; http://www.gnu.org/copyleft/gpl.html # License: GNU GPL, version 3 or later; http://www.gnu.org/copyleft/gpl.html
"""\ """\
Features - extensible features like auto-reading generation Features
=============================================================================== ===============================================================================
Features allow the deck to define specific features that are required, but
that can be resolved in real time. This includes things like automatic reading
generation, language-specific dictionary entries, etc.
""" """
from anki.lang import _
from anki.errors import *
from anki.utils import findTag, parseTags
class Feature(object):
def __init__(self, tags=None, name="", description=""):
if not tags:
tags = []
self.tags = tags
self.name = name
self.description = description
def onSubmit(self, fact):
"Apply any last-minute modifications to FACT before addition."
pass
def onKeyPress(self, fact):
"Apply any changes to fact as it's being edited for the first time."
pass
def run(self, cmd, *args):
"Run CMD."
attr = getattr(self, cmd, None)
if attr:
attr(*args)
class FeatureManager(object):
features = {}
def add(feature):
"Add a feature."
FeatureManager.features[feature.name] = feature
add = staticmethod(add)
def run(tagstr, cmd, *args):
"Run CMD on all matching features in DLIST."
tags = parseTags(tagstr)
for (name, feature) in FeatureManager.features.items():
for tag in tags:
if findTag(tag, feature.tags):
feature.run(cmd, *args)
break
run = staticmethod(run)
# Add bundled features
import japanese import japanese
FeatureManager.add(japanese.FuriganaGenerator())
import chinese import chinese
FeatureManager.add(chinese.CantoneseGenerator())
FeatureManager.add(chinese.MandarinGenerator())

View file

@ -2,9 +2,9 @@
# Copyright: Damien Elmes <anki@ichi2.net> # Copyright: Damien Elmes <anki@ichi2.net>
# License: GNU GPL, version 3 or later; http://www.gnu.org/copyleft/gpl.html # License: GNU GPL, version 3 or later; http://www.gnu.org/copyleft/gpl.html
import sys, os, pickle import sys, os
from anki.features import Feature from anki.utils import findTag, stripHTML
from anki.utils import findTag, parseTags, stripHTML from anki.hooks import addHook
from anki.db import * from anki.db import *
class UnihanController(object): class UnihanController(object):
@ -43,48 +43,37 @@ class UnihanController(object):
return m[0] return m[0]
return "{%s}" % (",".join(m)) return "{%s}" % (",".join(m))
class ChineseGenerator(Feature): # Hooks
##########################################################################
class ChineseGenerator(object):
def __init__(self): def __init__(self):
self.expressionField = "Expression" self.unihan = None
self.readingField = "Reading"
def lazyInit(self): def toReading(self, type, val):
pass if not self.unihan:
self.unihan = UnihanController(type)
else:
self.unihan.type = type
return self.unihan.reading(val)
def onKeyPress(self, fact, field, value): unihan = ChineseGenerator()
if findTag("Reading source", parseTags(field.fieldModel.features)):
dst = None
for field in fact.fields:
if findTag("Reading destination",
parseTags(field.fieldModel.features)):
dst = field
break
if not dst:
return
self.lazyInit()
reading = self.unihan.reading(value)
if not fact[dst.name]:
fact[dst.name] = reading
class CantoneseGenerator(ChineseGenerator): def onFocusLost(fact, field):
if field.name != "Expression":
return
if findTag("Cantonese", fact.model.tags):
type = "cantonese"
elif findTag("Mandarin", fact.model.tags):
type = "mandarin"
else:
return
try:
if fact['Reading']:
return
except:
return
fact['Reading'] = unihan.toReading(type, field.value)
def __init__(self): addHook('fact.focusLost', onFocusLost)
ChineseGenerator.__init__(self)
self.tags = ["Cantonese"]
self.name = "Reading generation for Cantonese"
def lazyInit(self):
if 'unihan' not in self.__dict__:
self.unihan = UnihanController("cantonese")
class MandarinGenerator(ChineseGenerator):
def __init__(self):
ChineseGenerator.__init__(self)
self.tags = ["Mandarin"]
self.name = "Reading generation for Mandarin"
def lazyInit(self):
if 'unihan' not in self.__dict__:
self.unihan = UnihanController("mandarin")

View file

@ -3,8 +3,8 @@
# License: GNU GPL, version 3 or later; http://www.gnu.org/copyleft/gpl.html # License: GNU GPL, version 3 or later; http://www.gnu.org/copyleft/gpl.html
import sys, os import sys, os
from anki.features import Feature from anki.utils import findTag, stripHTML
from anki.utils import findTag, parseTags, stripHTML from anki.hooks import addHook
class KakasiController(object): class KakasiController(object):
def __init__(self): def __init__(self):
@ -80,28 +80,25 @@ class KakasiController(object):
return True return True
return False return False
class FuriganaGenerator(Feature): # Hook
##########################################################################
def __init__(self): kakasi = KakasiController()
self.tags = ["Japanese"] if not kakasi.available():
self.name = "Furigana generation based on kakasi." kakasi = None
self.kakasi = KakasiController()
if not self.kakasi.available():
self.kakasi = None
def onKeyPress(self, fact, field, value): def onFocusLost(fact, field):
if self.kakasi and findTag("Reading source", if not kakasi:
parseTags(field.fieldModel.features)): return
reading = self.kakasi.toFurigana(value) if field.name != "Expression":
dst = None return
for field in fact.fields: if not findTag("Japanese", fact.model.tags):
if findTag("Reading destination", parseTags( return
field.fieldModel.features)): try:
dst = field if fact['Reading']:
break return
if dst: except:
if not fact[dst.name]: return
if self.kakasi.formatForKakasi(value) != reading: fact['Reading'] = kakasi.toFurigana(field.value)
fact[dst.name] = reading
else: addHook('fact.focusLost', onFocusLost)
fact[dst.name] = u""

View file

@ -37,10 +37,10 @@ fieldModelsTable = Table(
Column('ordinal', Integer, nullable=False), Column('ordinal', Integer, nullable=False),
Column('modelId', Integer, ForeignKey('models.id'), nullable=False), Column('modelId', Integer, ForeignKey('models.id'), nullable=False),
Column('name', UnicodeText, nullable=False), Column('name', UnicodeText, nullable=False),
Column('description', UnicodeText, nullable=False, default=u""), Column('description', UnicodeText, nullable=False, default=u""), # obsolete
Column('features', UnicodeText, nullable=False, default=u""), Column('features', UnicodeText, nullable=False, default=u""), # obsolete
Column('required', Boolean, nullable=False, default=True), Column('required', Boolean, nullable=False, default=True),
Column('unique', Boolean, nullable=False, default=True), Column('unique', Boolean, nullable=False, default=True), # sqlite keyword
Column('numeric', Boolean, nullable=False, default=False), Column('numeric', Boolean, nullable=False, default=False),
# display # display
Column('quizFontFamily', UnicodeText), Column('quizFontFamily', UnicodeText),
@ -52,9 +52,8 @@ fieldModelsTable = Table(
class FieldModel(object): class FieldModel(object):
"The definition of one field in a fact." "The definition of one field in a fact."
def __init__(self, name=u"", description=u"", required=True, unique=True): def __init__(self, name=u"", required=True, unique=True):
self.name = name self.name = name
self.description = description
self.required = required self.required = required
self.unique = unique self.unique = unique
self.id = genID() self.id = genID()
@ -70,7 +69,7 @@ cardModelsTable = Table(
Column('ordinal', Integer, nullable=False), Column('ordinal', Integer, nullable=False),
Column('modelId', Integer, ForeignKey('models.id'), nullable=False), Column('modelId', Integer, ForeignKey('models.id'), nullable=False),
Column('name', UnicodeText, nullable=False), Column('name', UnicodeText, nullable=False),
Column('description', UnicodeText, nullable=False, default=u""), Column('description', UnicodeText, nullable=False, default=u""), # obsolete
Column('active', Boolean, nullable=False, default=True), Column('active', Boolean, nullable=False, default=True),
# formats: question/answer/last(not used) # formats: question/answer/last(not used)
Column('qformat', UnicodeText, nullable=False), Column('qformat', UnicodeText, nullable=False),
@ -99,10 +98,8 @@ cardModelsTable = Table(
class CardModel(object): class CardModel(object):
"""Represents how to generate the front and back of a card.""" """Represents how to generate the front and back of a card."""
def __init__(self, name=u"", description=u"", def __init__(self, name=u"", qformat=u"q", aformat=u"a", active=True):
qformat=u"q", aformat=u"a", active=True):
self.name = name self.name = name
self.description = description
self.qformat = qformat self.qformat = qformat
self.aformat = aformat self.aformat = aformat
self.active = active self.active = active
@ -145,17 +142,16 @@ modelsTable = Table(
Column('modified', Float, nullable=False, default=time.time), Column('modified', Float, nullable=False, default=time.time),
Column('tags', UnicodeText, nullable=False, default=u""), Column('tags', UnicodeText, nullable=False, default=u""),
Column('name', UnicodeText, nullable=False), Column('name', UnicodeText, nullable=False),
Column('description', UnicodeText, nullable=False, default=u""), Column('description', UnicodeText, nullable=False, default=u""), # obsolete
Column('features', UnicodeText, nullable=False, default=u""), Column('features', UnicodeText, nullable=False, default=u""), # obsolete
Column('spacing', Float, nullable=False, default=0.1), Column('spacing', Float, nullable=False, default=0.1),
Column('initialSpacing', Float, nullable=False, default=600), Column('initialSpacing', Float, nullable=False, default=600),
Column('source', Integer, nullable=False, default=0)) Column('source', Integer, nullable=False, default=0))
class Model(object): class Model(object):
"Defines the way a fact behaves, what fields it can contain, etc." "Defines the way a fact behaves, what fields it can contain, etc."
def __init__(self, name=u"", description=u""): def __init__(self, name=u""):
self.name = name self.name = name
self.description = description
self.id = genID() self.id = genID()
def setModified(self): def setModified(self):

View file

@ -28,179 +28,88 @@ def names():
########################################################################## ##########################################################################
def BasicModel(): def BasicModel():
m = Model(_('Basic'), m = Model(_('Basic'))
_('A basic flashcard with a front and a back.\n' m.addFieldModel(FieldModel(u'Front', True, True))
'Questions are asked from front to back by default.\n\n' m.addFieldModel(FieldModel(u'Back', True, True))
'Please consider customizing this model, rather than\n' m.addCardModel(CardModel(u'Forward', u'%(Front)s', u'<hr>%(Back)s'))
'using it verbatim: field names like "expression" are\n' m.addCardModel(CardModel(u'Reverse', u'%(Back)s', u'<hr>%(Front)s',
'clearer than "front" and "back", and will ensure\n' active=False))
'that your entries are consistent.')) m.tags = u"Basic"
m.addFieldModel(FieldModel(u'Front', _('A question.'), True, True))
m.addFieldModel(FieldModel(u'Back', _('The answer.'), True, True))
m.addCardModel(CardModel(u'Front to back', _('Front to back'),
u'%(Front)s', u'%(Back)s'))
m.addCardModel(CardModel(u'Back to front', _('Back to front'),
u'%(Back)s', u'%(Front)s', active=False))
return m return m
models['Basic'] = BasicModel models['Basic'] = BasicModel
# Japanese # Japanese
########################################################################## ##########################################################################
def JapaneseModel(): def JapaneseModel():
m = Model(_("Japanese"), m = Model(_("Japanese"))
_("""
The reading field is automatically generated by default,
and shows the reading for the expression. For words that
are normally written in hiragana or katakana and don't
need a reading, you can put the word in the expression
field, and leave the reading field blank. A reading will
will not automatically be generated for words written
in only hiragana or katakana.
Note that the automatic generation of meaning is not
perfect, and should be checked before adding cards.""".strip()))
# expression # expression
f = FieldModel(u'Expression', f = FieldModel(u'Expression', True, True)
_('A word or expression written in Kanji.'), True, True)
font = u"Mincho" font = u"Mincho"
f.quizFontSize = 72 f.quizFontSize = 72
f.quizFontFamily = font f.quizFontFamily = font
f.editFontFamily = font f.editFontFamily = font
f.features = u"Reading source"
m.addFieldModel(f) m.addFieldModel(f)
# meaning # meaning
m.addFieldModel(FieldModel( m.addFieldModel(FieldModel(u'Meaning', True, True))
u'Meaning',
_('A description in your native language, or Japanese'), True, True))
# reading # reading
f = FieldModel(u'Reading', u"", False, False) f = FieldModel(u'Reading', False, False)
f.quizFontFamily = font f.quizFontFamily = font
f.editFontFamily = font f.editFontFamily = font
f.features = u"Reading destination"
m.addFieldModel(f) m.addFieldModel(f)
m.addCardModel(CardModel(u"Production", _( m.addCardModel(CardModel(u"Recognition",
"Actively test your recall by producing the target expression"), u"%(Expression)s",
u"<hr>%(Reading)s<br>%(Meaning)s"))
m.addCardModel(CardModel(u"Production",
u"%(Meaning)s", u"%(Meaning)s",
u"%(Expression)s<br>%(Reading)s")) u"<hr>%(Expression)s<br>%(Reading)s",
m.addCardModel(CardModel(u"Recognition", _( active=False))
"Test your ability to recognize the target expression"),
u"%(Expression)s",
u"%(Reading)s<br>%(Meaning)s"))
m.features = u"Japanese"
m.tags = u"Japanese" m.tags = u"Japanese"
return m return m
models['Japanese'] = JapaneseModel models['Japanese'] = JapaneseModel
# English # Cantonese
##########################################################################
def EnglishModel():
m = Model(_("English"),
_("""
Enter the English expression you want to learn in the 'Expression' field.
Enter a description in Japanese or English in the 'Meaning' field.""".strip()))
m.addFieldModel(FieldModel(u'Expression'))
m.addFieldModel(FieldModel(u'Meaning'))
m.addCardModel(CardModel(
u"Production", _("From the meaning to the English expression."),
u"%(Meaning)s", u"%(Expression)s"))
m.addCardModel(CardModel(
u"Recognition", _("From the English expression to the meaning."),
u"%(Expression)s", u"%(Meaning)s", active=False))
m.tags = u"English"
return m
models['English'] = EnglishModel
# Heisig
##########################################################################
def HeisigModel():
m = Model(_("Heisig"),
_("""
A format suitable for Heisig's "Remembering the Kanji".
You are tested from the keyword to the kanji.
Layout of the test is based on the great work at
http://kanji.koohii.com/
The link in the question will list user-contributed
stories. A free login is required.""".strip()))
font = u"Mincho"
f = FieldModel(u'Kanji')
f.quizFontSize = 150
f.quizFontFamily = font
f.editFontFamily = font
m.addFieldModel(f)
m.addFieldModel(FieldModel(u'Keyword'))
m.addFieldModel(FieldModel(u'Story', u"", False, False))
m.addFieldModel(FieldModel(u'Stroke count', u"", False, False))
m.addFieldModel(FieldModel(u'Heisig number', required=False))
m.addFieldModel(FieldModel(u'Lesson number', u"", False, False))
m.addCardModel(CardModel(
u"Production", _("From the keyword to the Kanji."),
u"<a href=\"http://kanji.koohii.com/study?framenum="
u"%(text:Heisig number)s\">%(Keyword)s</a><br>",
u"%(Kanji)s<br><table width=150><tr><td align=left>"
u"画数%(Stroke count)s</td><td align=right>"
u"%(Heisig number)s</td></tr></table>"))
m.tags = u"Heisig"
return m
models['Heisig'] = HeisigModel
# Chinese: Mandarin & Cantonese
########################################################################## ##########################################################################
def CantoneseModel(): def CantoneseModel():
m = Model(_("Cantonese"), m = Model(_("Cantonese"))
u"") f = FieldModel(u'Expression')
f = FieldModel(u'Expression',
_('A word or expression written in Hanzi.'))
f.quizFontSize = 72 f.quizFontSize = 72
f.features = u"Reading source"
m.addFieldModel(f) m.addFieldModel(f)
m.addFieldModel(FieldModel( m.addFieldModel(FieldModel(u'Meaning'))
u'Meaning', _('A description in your native language, or Cantonese'))) m.addFieldModel(FieldModel(u'Reading', False, False))
f = FieldModel(u'Reading', u"", False, False) m.addCardModel(CardModel(u"Recognition",
f.features = u"Reading destination"
m.addFieldModel(f)
m.addCardModel(CardModel(u"Production", _(
"Actively test your recall by producing the target expression"),
u"%(Meaning)s",
u"%(Expression)s<br>%(Reading)s"))
m.addCardModel(CardModel(u"Recognition", _(
"Test your ability to recognize the target expression"),
u"%(Expression)s", u"%(Expression)s",
u"%(Reading)s<br>%(Meaning)s")) u"<hr>%(Reading)s<br>%(Meaning)s"))
m.features = u"Cantonese" m.addCardModel(CardModel(u"Production",
u"%(Meaning)s",
u"<hr>%(Expression)s<br>%(Reading)s",
active=False))
m.tags = u"Cantonese" m.tags = u"Cantonese"
return m return m
models['Cantonese'] = CantoneseModel models['Cantonese'] = CantoneseModel
# Mandarin
##########################################################################
def MandarinModel(): def MandarinModel():
m = Model(_("Mandarin"), m = Model(_("Mandarin"))
u"") f = FieldModel(u'Expression')
f = FieldModel(u'Expression',
_('A word or expression written in Hanzi.'))
f.quizFontSize = 72 f.quizFontSize = 72
f.features = u"Reading source"
m.addFieldModel(f) m.addFieldModel(f)
m.addFieldModel(FieldModel( m.addFieldModel(FieldModel(u'Meaning'))
u'Meaning', _( m.addFieldModel(FieldModel(u'Reading', False, False))
'A description in your native language, or Mandarin'))) m.addCardModel(CardModel(u"Recognition",
f = FieldModel(u'Reading', u"", False, False)
f.features = u"Reading destination"
m.addFieldModel(f)
m.addCardModel(CardModel(u"Production", _(
"Actively test your recall by producing the target expression"),
u"%(Meaning)s",
u"%(Expression)s<br>%(Reading)s"))
m.addCardModel(CardModel(u"Recognition", _(
"Test your ability to recognize the target expression"),
u"%(Expression)s", u"%(Expression)s",
u"%(Reading)s<br>%(Meaning)s")) u"<hr>%(Reading)s<br>%(Meaning)s"))
m.features = u"Mandarin" m.addCardModel(CardModel(u"Production",
u"%(Meaning)s",
u"<hr>%(Expression)s<br>%(Reading)s",
active=False))
m.tags = u"Mandarin" m.tags = u"Mandarin"
return m return m
models['Mandarin'] = MandarinModel
models['Mandarin'] = MandarinModel

View file

@ -8,7 +8,7 @@ Miscellaneous utilities
""" """
__docformat__ = 'restructuredtext' __docformat__ = 'restructuredtext'
import re, os, random, time import re, os, random, time, types
try: try:
import hashlib import hashlib
@ -20,6 +20,9 @@ except ImportError:
from anki.db import * from anki.db import *
from anki.lang import _, ngettext from anki.lang import _, ngettext
# Time handling
##############################################################################
timeTable = { timeTable = {
"years": lambda n: ngettext("%s year", "%s years", n), "years": lambda n: ngettext("%s year", "%s years", n),
"months": lambda n: ngettext("%s month", "%s months", n), "months": lambda n: ngettext("%s month", "%s months", n),
@ -102,40 +105,8 @@ def _pluralCount(time):
return 1 return 1
return 2 return 2
def parseTags(tags): # HTML
"Parse a string and return a list of tags." ##############################################################################
tags = tags.split(",")
tags = [tag.strip() for tag in tags if tag.strip()]
return tags
def joinTags(tags):
return u", ".join(tags)
def canonifyTags(tags):
"Strip leading/trailing/superfluous commas."
return joinTags(sorted(set(parseTags(tags))))
def findTag(tag, tags):
"True if TAG is in TAGS. Ignore case."
return tag.lower() in [t.lower() for t in tags]
def addTags(tagstr, tags):
"Add tag if doesn't exist."
currentTags = parseTags(tags)
for tag in parseTags(tagstr):
if not findTag(tag, currentTags):
currentTags.append(tag)
return u", ".join(currentTags)
def deleteTags(tagstr, tags):
"Delete tag if exists."
currentTags = parseTags(tags)
for tag in parseTags(tagstr):
try:
currentTags.remove(tag)
except ValueError:
pass
return u", ".join(currentTags)
def stripHTML(s): def stripHTML(s):
s = re.sub("<.*?>", "", s) s = re.sub("<.*?>", "", s)
@ -167,6 +138,9 @@ def tidyHTML(html):
html = re.sub(u' +$', u'', html) html = re.sub(u' +$', u'', html)
return html return html
# IDs
##############################################################################
def genID(static=[]): def genID(static=[]):
"Generate a random, unique 64bit ID." "Generate a random, unique 64bit ID."
# 23 bits of randomness, 41 bits of current time # 23 bits of randomness, 41 bits of current time
@ -208,5 +182,48 @@ This is safe if you use sqlite primary key columns, which are guaranteed
to be integers.""" to be integers."""
return "(%s)" % ",".join([str(i) for i in ids]) return "(%s)" % ",".join([str(i) for i in ids])
# Tags
##############################################################################
def parseTags(tags):
"Parse a string and return a list of tags."
tags = tags.split(",")
tags = [tag.strip() for tag in tags if tag.strip()]
return tags
def joinTags(tags):
return u", ".join(tags)
def canonifyTags(tags):
"Strip leading/trailing/superfluous commas and duplicates."
return joinTags(sorted(set(parseTags(tags))))
def findTag(tag, tags):
"True if TAG is in TAGS. Ignore case."
if not isinstance(tags, types.ListType):
tags = parseTags(tags)
return tag.lower() in [t.lower() for t in tags]
def addTags(tagstr, tags):
"Add tags if they don't exist."
currentTags = parseTags(tags)
for tag in parseTags(tagstr):
if not findTag(tag, currentTags):
currentTags.append(tag)
return joinTags(currentTags)
def deleteTags(tagstr, tags):
"Delete tags if they don't exists."
currentTags = parseTags(tags)
for tag in parseTags(tagstr):
try:
currentTags.remove(tag)
except ValueError:
pass
return joinTags(currentTags)
# Misc
##############################################################################
def checksum(data): def checksum(data):
return md5(data).hexdigest() return md5(data).hexdigest()

View file

@ -118,12 +118,8 @@ def test_cardOrder():
f['Meaning'] = u'2' f['Meaning'] = u'2'
deck.addFact(f) deck.addFact(f)
card = deck.getCard() card = deck.getCard()
# production should come first # recognition should come first
assert card.cardModel.name == u"Production" assert card.cardModel.name == u"Recognition"
# if we rebuild the queue, it should be the same
deck.rebuildQueue()
card = deck.getCard()
assert card.cardModel.name == u"Production"
def test_modelAddDelete(): def test_modelAddDelete():
deck = DeckStorage.Deck() deck = DeckStorage.Deck()

View file

@ -13,10 +13,6 @@ def test_stdmodels():
deck = DeckStorage.Deck() deck = DeckStorage.Deck()
deck.addModel(JapaneseModel()) deck.addModel(JapaneseModel())
deck = DeckStorage.Deck() deck = DeckStorage.Deck()
deck.addModel(EnglishModel())
deck = DeckStorage.Deck()
deck.addModel(HeisigModel())
deck = DeckStorage.Deck()
deck.addModel(CantoneseModel()) deck.addModel(CantoneseModel())
deck = DeckStorage.Deck() deck = DeckStorage.Deck()
deck.addModel(MandarinModel()) deck.addModel(MandarinModel())