diff --git a/AnkiServer/apps/rest_app.py b/AnkiServer/apps/rest_app.py index 291f838..fa10422 100644 --- a/AnkiServer/apps/rest_app.py +++ b/AnkiServer/apps/rest_app.py @@ -37,11 +37,18 @@ class _RestHandlerWrapper(RestHandlerBase): return self.func(*args, **kw) class RestHandlerRequest(object): - def __init__(self, data, ids, session): + def __init__(self, app, data, ids, session): + self.app = app self.data = data self.ids = ids 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): """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) 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): """Raises an exception if the request isn't allowed or valid for some reason.""" if self.allowed_hosts != '*': @@ -245,7 +260,7 @@ class RestApp(object): # run it! 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: output = col.execute(handler, [handler_request], {}, hasReturnValue) except Exception, e: @@ -344,6 +359,49 @@ class CollectionHandler(RestHandlerBase): def reset_scheduler(self, col, req): 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): import time @@ -444,61 +502,26 @@ class NoteHandler(RestHandlerBase): class DeckHandler(RestHandlerBase): """Default handler group for 'deck' type.""" - button_labels = ['Easy', 'Good', 'Hard'] - - def _get_deck(self, col, ids): + @staticmethod + def _get_deck(col, val): try: - did = long(ids[1]) + did = long(val) deck = col.decks.get(did, False) except ValueError: - deck = col.decks.byName(ids[1]) + deck = col.decks.byName(val) 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 - # 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): - 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()? - - 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 + # forward this to the CollectionHandler + return req.app.execute_handler('collection', 'next_card', col, req_copy) class CardHandler(RestHandlerBase): """Default handler group for 'card' type.""" diff --git a/tests/test_rest_app.py b/tests/test_rest_app.py index 299851a..14bd6f0 100644 --- a/tests/test_rest_app.py +++ b/tests/test_rest_app.py @@ -119,11 +119,13 @@ class CollectionTestBase(unittest.TestCase): self.temp_dir = tempfile.mkdtemp() self.collection_path = os.path.join(self.temp_dir, 'collection.anki2'); self.collection = anki.storage.Collection(self.collection_path) + self.mock_app = MagicMock() def tearDown(self): self.collection.close() self.collection = None shutil.rmtree(self.temp_dir) + self.mock_app.reset_mock() def add_note(self, data): from anki.notes import Note @@ -159,7 +161,7 @@ class CollectionHandlerTest(CollectionTestBase): def execute(self, name, data): ids = ['collection_name'] func = getattr(self.handler, name) - req = RestHandlerRequest(data, ids, {}) + req = RestHandlerRequest(self.mock_app, data, ids, {}) return func(self.collection, req) def test_list_decks(self): @@ -261,23 +263,88 @@ class CollectionHandlerTest(CollectionTestBase): # return everything to normal! 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'], 'The front') + self.assertEqual(ret['answer'], 'The front\n\n