Did some refactoring moving the 'next_card' stuff into the CollectionHandler.
This commit is contained in:
parent
ffde4a7ff6
commit
34cb8fe09b
@ -37,11 +37,18 @@ class _RestHandlerWrapper(RestHandlerBase):
|
|||||||
return self.func(*args, **kw)
|
return self.func(*args, **kw)
|
||||||
|
|
||||||
class RestHandlerRequest(object):
|
class RestHandlerRequest(object):
|
||||||
def __init__(self, data, ids, session):
|
def __init__(self, app, data, ids, session):
|
||||||
|
self.app = app
|
||||||
self.data = data
|
self.data = data
|
||||||
self.ids = ids
|
self.ids = ids
|
||||||
self.session = session
|
self.session = session
|
||||||
|
|
||||||
|
def copy(self):
|
||||||
|
return RestHandlerRequest(self.app, self.data.copy(), self.ids[:], self.session)
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return self.app == other.app and self.data == other.data and self.ids == other.ids and self.session == other.session
|
||||||
|
|
||||||
class RestApp(object):
|
class RestApp(object):
|
||||||
"""A WSGI app that implements RESTful operations on Collections, Decks and Cards."""
|
"""A WSGI app that implements RESTful operations on Collections, Decks and Cards."""
|
||||||
|
|
||||||
@ -106,6 +113,14 @@ class RestApp(object):
|
|||||||
method = _RestHandlerWrapper(group.__class__.__name__ + '.' + name, method, group.hasReturnValue)
|
method = _RestHandlerWrapper(group.__class__.__name__ + '.' + name, method, group.hasReturnValue)
|
||||||
self.add_handler(type, name, method)
|
self.add_handler(type, name, method)
|
||||||
|
|
||||||
|
def execute_handler(self, type, name, col, req):
|
||||||
|
"""Executes the handler with the given type and name, passing in the col and req as arguments."""
|
||||||
|
|
||||||
|
handler, hasReturnValue = self._getHandler(type, name)
|
||||||
|
ret = handler(col, req)
|
||||||
|
if hasReturnValue:
|
||||||
|
return ret
|
||||||
|
|
||||||
def _checkRequest(self, req):
|
def _checkRequest(self, req):
|
||||||
"""Raises an exception if the request isn't allowed or valid for some reason."""
|
"""Raises an exception if the request isn't allowed or valid for some reason."""
|
||||||
if self.allowed_hosts != '*':
|
if self.allowed_hosts != '*':
|
||||||
@ -245,7 +260,7 @@ class RestApp(object):
|
|||||||
|
|
||||||
# run it!
|
# run it!
|
||||||
col = self.collection_manager.get_collection(collection_path, self.setup_new_collection)
|
col = self.collection_manager.get_collection(collection_path, self.setup_new_collection)
|
||||||
handler_request = RestHandlerRequest(data, ids, session)
|
handler_request = RestHandlerRequest(self, data, ids, session)
|
||||||
try:
|
try:
|
||||||
output = col.execute(handler, [handler_request], {}, hasReturnValue)
|
output = col.execute(handler, [handler_request], {}, hasReturnValue)
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
@ -344,6 +359,49 @@ class CollectionHandler(RestHandlerBase):
|
|||||||
def reset_scheduler(self, col, req):
|
def reset_scheduler(self, col, req):
|
||||||
col.sched.reset()
|
col.sched.reset()
|
||||||
|
|
||||||
|
button_labels = ['Easy', 'Good', 'Hard']
|
||||||
|
|
||||||
|
def _get_answer_buttons(self, col, card):
|
||||||
|
l = []
|
||||||
|
|
||||||
|
# Put the correct number of buttons
|
||||||
|
cnt = col.sched.answerButtons(card)
|
||||||
|
for idx in range(0, cnt - 1):
|
||||||
|
l.append(self.button_labels[idx])
|
||||||
|
l.append('Again')
|
||||||
|
l.reverse()
|
||||||
|
|
||||||
|
# Loop through and add the ease, estimated time (in seconds) and other info
|
||||||
|
return [{
|
||||||
|
'ease': ease,
|
||||||
|
'label': label,
|
||||||
|
'string_label': t(label),
|
||||||
|
'interval': col.sched.nextIvl(card, ease),
|
||||||
|
'string_interval': col.sched.nextIvlStr(card, ease),
|
||||||
|
} for ease, label in enumerate(l, 1)]
|
||||||
|
|
||||||
|
def next_card(self, col, req):
|
||||||
|
if req.data.has_key('deck'):
|
||||||
|
deck = DeckHandler._get_deck(col, req.data['deck'])
|
||||||
|
col.decks.select(deck['id'])
|
||||||
|
|
||||||
|
card = col.sched.getCard()
|
||||||
|
if card is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# put it into the card cache to be removed when we answer it
|
||||||
|
#if not req.session.has_key('cards'):
|
||||||
|
# req.session['cards'] = {}
|
||||||
|
#req.session['cards'][long(card.id)] = card
|
||||||
|
|
||||||
|
card.startTimer()
|
||||||
|
|
||||||
|
result = CardHandler._serialize(card)
|
||||||
|
result['answer_buttons'] = self._get_answer_buttons(col, card)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
@noReturnValue
|
||||||
def answer_card(self, col, req):
|
def answer_card(self, col, req):
|
||||||
import time
|
import time
|
||||||
|
|
||||||
@ -444,61 +502,26 @@ class NoteHandler(RestHandlerBase):
|
|||||||
class DeckHandler(RestHandlerBase):
|
class DeckHandler(RestHandlerBase):
|
||||||
"""Default handler group for 'deck' type."""
|
"""Default handler group for 'deck' type."""
|
||||||
|
|
||||||
button_labels = ['Easy', 'Good', 'Hard']
|
@staticmethod
|
||||||
|
def _get_deck(col, val):
|
||||||
def _get_deck(self, col, ids):
|
|
||||||
try:
|
try:
|
||||||
did = long(ids[1])
|
did = long(val)
|
||||||
deck = col.decks.get(did, False)
|
deck = col.decks.get(did, False)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
deck = col.decks.byName(ids[1])
|
deck = col.decks.byName(val)
|
||||||
|
|
||||||
if deck is None:
|
if deck is None:
|
||||||
raise HTTPNotFound('No deck with id or name: ' + str(ids[1]))
|
raise HTTPNotFound('No deck with id or name: ' + str(val))
|
||||||
|
|
||||||
return deck
|
return deck
|
||||||
|
|
||||||
# Code stolen from aqt/reviewer.py
|
|
||||||
def _get_answer_buttons(self, col, card):
|
|
||||||
l = []
|
|
||||||
|
|
||||||
# Put the correct number of buttons
|
|
||||||
cnt = col.sched.answerButtons(card)
|
|
||||||
for idx in range(0, cnt - 1):
|
|
||||||
l.append(self.button_labels[idx])
|
|
||||||
l.append('Again')
|
|
||||||
l.reverse()
|
|
||||||
|
|
||||||
# Loop through and add the ease and estimated time (in seconds)
|
|
||||||
return [{
|
|
||||||
'ease': ease,
|
|
||||||
'label': label,
|
|
||||||
'string_label': t(label),
|
|
||||||
'interval': col.sched.nextIvl(card, ease),
|
|
||||||
'string_interval': col.sched.nextIvlStr(card, ease),
|
|
||||||
} for ease, label in enumerate(l, 1)]
|
|
||||||
|
|
||||||
def next_card(self, col, req):
|
def next_card(self, col, req):
|
||||||
deck = self._get_deck(col, req.ids)
|
req_copy = req.copy()
|
||||||
|
req_copy.data['deck'] = req.ids[1]
|
||||||
|
del req_copy.ids[1]
|
||||||
|
|
||||||
# TODO: maybe this should use col.renderQA()?
|
# forward this to the CollectionHandler
|
||||||
|
return req.app.execute_handler('collection', 'next_card', col, req_copy)
|
||||||
col.decks.select(deck['id'])
|
|
||||||
card = col.sched.getCard()
|
|
||||||
if card is None:
|
|
||||||
return None
|
|
||||||
|
|
||||||
# put it into the card cache to be removed when we answer it
|
|
||||||
#if not req.session.has_key('cards'):
|
|
||||||
# req.session['cards'] = {}
|
|
||||||
#req.session['cards'][long(card.id)] = card
|
|
||||||
|
|
||||||
card.startTimer()
|
|
||||||
|
|
||||||
result = CardHandler._serialize(card)
|
|
||||||
result['answer_buttons'] = self._get_answer_buttons(col, card)
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
class CardHandler(RestHandlerBase):
|
class CardHandler(RestHandlerBase):
|
||||||
"""Default handler group for 'card' type."""
|
"""Default handler group for 'card' type."""
|
||||||
|
|||||||
@ -119,11 +119,13 @@ class CollectionTestBase(unittest.TestCase):
|
|||||||
self.temp_dir = tempfile.mkdtemp()
|
self.temp_dir = tempfile.mkdtemp()
|
||||||
self.collection_path = os.path.join(self.temp_dir, 'collection.anki2');
|
self.collection_path = os.path.join(self.temp_dir, 'collection.anki2');
|
||||||
self.collection = anki.storage.Collection(self.collection_path)
|
self.collection = anki.storage.Collection(self.collection_path)
|
||||||
|
self.mock_app = MagicMock()
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
self.collection.close()
|
self.collection.close()
|
||||||
self.collection = None
|
self.collection = None
|
||||||
shutil.rmtree(self.temp_dir)
|
shutil.rmtree(self.temp_dir)
|
||||||
|
self.mock_app.reset_mock()
|
||||||
|
|
||||||
def add_note(self, data):
|
def add_note(self, data):
|
||||||
from anki.notes import Note
|
from anki.notes import Note
|
||||||
@ -159,7 +161,7 @@ class CollectionHandlerTest(CollectionTestBase):
|
|||||||
def execute(self, name, data):
|
def execute(self, name, data):
|
||||||
ids = ['collection_name']
|
ids = ['collection_name']
|
||||||
func = getattr(self.handler, name)
|
func = getattr(self.handler, name)
|
||||||
req = RestHandlerRequest(data, ids, {})
|
req = RestHandlerRequest(self.mock_app, data, ids, {})
|
||||||
return func(self.collection, req)
|
return func(self.collection, req)
|
||||||
|
|
||||||
def test_list_decks(self):
|
def test_list_decks(self):
|
||||||
@ -261,23 +263,88 @@ class CollectionHandlerTest(CollectionTestBase):
|
|||||||
# return everything to normal!
|
# return everything to normal!
|
||||||
anki.lang.setLang('en')
|
anki.lang.setLang('en')
|
||||||
|
|
||||||
|
def test_next_card(self):
|
||||||
|
ret = self.execute('next_card', {})
|
||||||
|
self.assertEqual(ret, None)
|
||||||
|
|
||||||
|
# add a note programatically
|
||||||
|
self.add_default_note()
|
||||||
|
|
||||||
|
# get the id for the one card and note on this collection
|
||||||
|
note_id = self.collection.findNotes('')[0]
|
||||||
|
card_id = self.collection.findCards('')[0]
|
||||||
|
|
||||||
|
self.collection.sched.reset()
|
||||||
|
ret = self.execute('next_card', {})
|
||||||
|
self.assertEqual(ret['id'], card_id)
|
||||||
|
self.assertEqual(ret['nid'], note_id)
|
||||||
|
self.assertEqual(ret['question'], '<style>.card {\n font-family: arial;\n font-size: 20px;\n text-align: center;\n color: black;\n background-color: white;\n}\n</style>The front')
|
||||||
|
self.assertEqual(ret['answer'], '<style>.card {\n font-family: arial;\n font-size: 20px;\n text-align: center;\n color: black;\n background-color: white;\n}\n</style>The front\n\n<hr id=answer>\n\nThe back')
|
||||||
|
self.assertEqual(ret['answer_buttons'], [
|
||||||
|
{'ease': 1,
|
||||||
|
'label': 'Again',
|
||||||
|
'string_label': 'Again',
|
||||||
|
'interval': 60,
|
||||||
|
'string_interval': '<1 minute'},
|
||||||
|
{'ease': 2,
|
||||||
|
'label': 'Good',
|
||||||
|
'string_label': 'Good',
|
||||||
|
'interval': 600,
|
||||||
|
'string_interval': '<10 minutes'},
|
||||||
|
{'ease': 3,
|
||||||
|
'label': 'Easy',
|
||||||
|
'string_label': 'Easy',
|
||||||
|
'interval': 345600,
|
||||||
|
'string_interval': '4 days'}])
|
||||||
|
|
||||||
|
def test_next_card_translation(self):
|
||||||
|
# add a note programatically
|
||||||
|
self.add_default_note()
|
||||||
|
|
||||||
|
# get the card in Polish so we can test translation too
|
||||||
|
anki.lang.setLang('pl')
|
||||||
|
try:
|
||||||
|
ret = self.execute('next_card', {})
|
||||||
|
finally:
|
||||||
|
anki.lang.setLang('en')
|
||||||
|
|
||||||
|
self.assertEqual(ret['answer_buttons'], [
|
||||||
|
{'ease': 1,
|
||||||
|
'label': 'Again',
|
||||||
|
'string_label': u'Znowu',
|
||||||
|
'interval': 60,
|
||||||
|
'string_interval': '<1 minuta'},
|
||||||
|
{'ease': 2,
|
||||||
|
'label': 'Good',
|
||||||
|
'string_label': u'Dobra',
|
||||||
|
'interval': 600,
|
||||||
|
'string_interval': '<10 minut'},
|
||||||
|
{'ease': 3,
|
||||||
|
'label': 'Easy',
|
||||||
|
'string_label': u'Łatwa',
|
||||||
|
'interval': 345600,
|
||||||
|
'string_interval': '4 dni'}])
|
||||||
|
|
||||||
|
def test_next_card_five_times(self):
|
||||||
|
self.add_default_note(5)
|
||||||
|
for idx in range(0, 5):
|
||||||
|
ret = self.execute('next_card', {})
|
||||||
|
self.assertTrue(ret is not None)
|
||||||
|
|
||||||
def test_answer_card(self):
|
def test_answer_card(self):
|
||||||
import time
|
import time
|
||||||
|
|
||||||
self.add_default_note()
|
self.add_default_note()
|
||||||
|
|
||||||
# instantiate a deck handler to get the card
|
# instantiate a deck handler to get the card
|
||||||
deck_handler = DeckHandler()
|
card = self.execute('next_card', {})
|
||||||
deck_request = RestHandlerRequest({}, ['c', '1'], {})
|
|
||||||
card = deck_handler.next_card(self.collection, deck_request)
|
|
||||||
self.assertEqual(card['reps'], 0)
|
self.assertEqual(card['reps'], 0)
|
||||||
|
|
||||||
|
|
||||||
self.execute('answer_card', {'id': card['id'], 'ease': 2, 'timerStarted': time.time()})
|
self.execute('answer_card', {'id': card['id'], 'ease': 2, 'timerStarted': time.time()})
|
||||||
|
|
||||||
# reset the scheduler and try to get the next card again - there should be none!
|
# reset the scheduler and try to get the next card again - there should be none!
|
||||||
self.collection.sched.reset()
|
self.collection.sched.reset()
|
||||||
card = deck_handler.next_card(self.collection, deck_request)
|
card = self.execute('next_card', {})
|
||||||
self.assertEqual(card['reps'], 1)
|
self.assertEqual(card['reps'], 1)
|
||||||
|
|
||||||
class ImportExportHandlerTest(CollectionTestBase):
|
class ImportExportHandlerTest(CollectionTestBase):
|
||||||
@ -293,7 +360,7 @@ class ImportExportHandlerTest(CollectionTestBase):
|
|||||||
def execute(self, name, data):
|
def execute(self, name, data):
|
||||||
ids = ['collection_name']
|
ids = ['collection_name']
|
||||||
func = getattr(self.handler, name)
|
func = getattr(self.handler, name)
|
||||||
req = RestHandlerRequest(data, ids, {})
|
req = RestHandlerRequest(self.mock_app, data, ids, {})
|
||||||
return func(self.collection, req)
|
return func(self.collection, req)
|
||||||
|
|
||||||
def generate_text_export(self):
|
def generate_text_export(self):
|
||||||
@ -345,55 +412,15 @@ class DeckHandlerTest(CollectionTestBase):
|
|||||||
def execute(self, name, data):
|
def execute(self, name, data):
|
||||||
ids = ['collection_name', '1']
|
ids = ['collection_name', '1']
|
||||||
func = getattr(self.handler, name)
|
func = getattr(self.handler, name)
|
||||||
req = RestHandlerRequest(data, ids, {})
|
req = RestHandlerRequest(self.mock_app, data, ids, {})
|
||||||
return func(self.collection, req)
|
return func(self.collection, req)
|
||||||
|
|
||||||
def test_next_card(self):
|
def test_next_card(self):
|
||||||
|
self.mock_app.execute_handler.return_value = None
|
||||||
|
|
||||||
ret = self.execute('next_card', {})
|
ret = self.execute('next_card', {})
|
||||||
self.assertEqual(ret, None)
|
self.assertEqual(ret, None)
|
||||||
|
self.mock_app.execute_handler.assert_called_with('collection', 'next_card', self.collection, RestHandlerRequest(self.mock_app, {'deck': '1'}, ['collection_name'], {}))
|
||||||
# add a note programatically
|
|
||||||
self.add_default_note()
|
|
||||||
|
|
||||||
# get the id for the one card and note on this collection
|
|
||||||
note_id = self.collection.findNotes('')[0]
|
|
||||||
card_id = self.collection.findCards('')[0]
|
|
||||||
|
|
||||||
self.collection.sched.reset()
|
|
||||||
|
|
||||||
# get the card in Polish so we can test translation too
|
|
||||||
anki.lang.setLang('pl')
|
|
||||||
try:
|
|
||||||
ret = self.execute('next_card', {})
|
|
||||||
finally:
|
|
||||||
anki.lang.setLang('en')
|
|
||||||
|
|
||||||
self.assertEqual(ret['id'], card_id)
|
|
||||||
self.assertEqual(ret['nid'], note_id)
|
|
||||||
self.assertEqual(ret['question'], '<style>.card {\n font-family: arial;\n font-size: 20px;\n text-align: center;\n color: black;\n background-color: white;\n}\n</style>The front')
|
|
||||||
self.assertEqual(ret['answer'], '<style>.card {\n font-family: arial;\n font-size: 20px;\n text-align: center;\n color: black;\n background-color: white;\n}\n</style>The front\n\n<hr id=answer>\n\nThe back')
|
|
||||||
self.assertEqual(ret['answer_buttons'], [
|
|
||||||
{'ease': 1,
|
|
||||||
'label': 'Again',
|
|
||||||
'string_label': u'Znowu',
|
|
||||||
'interval': 60,
|
|
||||||
'string_interval': '<1 minuta'},
|
|
||||||
{'ease': 2,
|
|
||||||
'label': 'Good',
|
|
||||||
'string_label': u'Dobra',
|
|
||||||
'interval': 600,
|
|
||||||
'string_interval': '<10 minut'},
|
|
||||||
{'ease': 3,
|
|
||||||
'label': 'Easy',
|
|
||||||
'string_label': u'Łatwa',
|
|
||||||
'interval': 345600,
|
|
||||||
'string_interval': '4 dni'}])
|
|
||||||
|
|
||||||
def test_next_card_five_times(self):
|
|
||||||
self.add_default_note(5)
|
|
||||||
for idx in range(0, 5):
|
|
||||||
ret = self.execute('next_card', {})
|
|
||||||
self.assertTrue(ret is not None)
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user