* Added sessions and refactored the handler arguments to only take the collection and (new) request object

* Got 'answer_card' actually working

 * Added some support for the translation built into Anki
This commit is contained in:
David Snopek 2013-07-22 20:11:53 +01:00
parent 9a97d6524e
commit ffde4a7ff6
2 changed files with 154 additions and 39 deletions

View File

@ -14,6 +14,9 @@ except ImportError:
import os, logging
import anki.lang
from anki.lang import _ as t
__all__ = ['RestApp', 'RestHandlerBase', 'noReturnValue']
def noReturnValue(func):
@ -33,6 +36,12 @@ class _RestHandlerWrapper(RestHandlerBase):
def __call__(self, *args, **kw):
return self.func(*args, **kw)
class RestHandlerRequest(object):
def __init__(self, data, ids, session):
self.data = data
self.ids = ids
self.session = session
class RestApp(object):
"""A WSGI app that implements RESTful operations on Collections, Decks and Cards."""
@ -67,6 +76,9 @@ class RestApp(object):
self.add_handler_group('deck', DeckHandler())
self.add_handler_group('card', CardHandler())
# hold per collection session data
self.sessions = {}
def add_handler(self, type, name, handler):
"""Adds a callback handler for a type (collection, deck, card) with a unique name.
@ -221,14 +233,21 @@ class RestApp(object):
# parse the request body
data = self._parseRequestBody(req)
# get the users session
try:
session = self.sessions[ids[0]]
except KeyError:
session = self.sessions[ids[0]] = {}
# debug
from pprint import pprint
pprint(data)
# run it!
col = self.collection_manager.get_collection(collection_path, self.setup_new_collection)
handler_request = RestHandlerRequest(data, ids, session)
try:
output = col.execute(handler, [data, ids], {}, hasReturnValue)
output = col.execute(handler, [handler_request], {}, hasReturnValue)
except Exception, e:
logging.error(e)
return HTTPInternalServerError()
@ -245,24 +264,24 @@ class CollectionHandler(RestHandlerBase):
# MODELS - Store fields definitions and templates for notes
#
def list_models(self, col, data, ids):
def list_models(self, col, req):
# This is already a list of dicts, so it doesn't need to be serialized
return col.models.all()
def find_model_by_name(self, col, data, ids):
def find_model_by_name(self, col, req):
# This is already a list of dicts, so it doesn't need to be serialized
return col.models.byName(data['model'])
return col.models.byName(req.data['model'])
#
# NOTES - Information (in fields per the model) that can generate a card
# (based on a template from the model).
#
def find_notes(self, col, data, ids):
query = data.get('query', '')
def find_notes(self, col, req):
query = req.data.get('query', '')
ids = col.findNotes(query)
if data.get('preload', False):
if req.data.get('preload', False):
nodes = [NoteHandler._serialize(col.getNote(id)) for id in ids]
else:
nodes = [{'id': id} for id in ids]
@ -270,22 +289,22 @@ class CollectionHandler(RestHandlerBase):
return nodes
@noReturnValue
def add_note(self, col, data, ids):
def add_note(self, col, req):
from anki.notes import Note
# TODO: I think this would be better with 'model' for the name
# and 'mid' for the model id.
if type(data['model']) in (str, unicode):
model = col.models.byName(data['model'])
if type(req.data['model']) in (str, unicode):
model = col.models.byName(req.data['model'])
else:
model = col.models.get(data['model'])
model = col.models.get(req.data['model'])
note = Note(col, model)
for name, value in data['fields'].items():
for name, value in req.data['fields'].items():
note[name] = value
if data.has_key('tags'):
note.setTagsFromStr(data['tags'])
if req.data.has_key('tags'):
note.setTagsFromStr(req.data['tags'])
col.addNote(note)
@ -293,27 +312,27 @@ class CollectionHandler(RestHandlerBase):
# DECKS - Groups of cards
#
def list_decks(self, col, data, ids):
def list_decks(self, col, req):
# This is already a list of dicts, so it doesn't need to be serialized
return col.decks.all()
@noReturnValue
def select_deck(self, col, data, ids):
col.decks.select(data['deck_id'])
def select_deck(self, col, req):
col.decks.select(req.data['deck_id'])
#
# CARD - A specific card in a deck with a history of review (generated from
# a note based on the template).
#
def find_cards(self, col, data, ids):
query = data.get('query', '')
def find_cards(self, col, req):
query = req.data.get('query', '')
ids = col.findCards(query)
if data.get('preload', False):
cards = [CardHandler._serialize(col.getCard(id)) for id in ids]
if req.data.get('preload', False):
cards = [CardHandler._serialize(col.getCard(id)) for id in req.ids]
else:
cards = [{'id': id} for id in ids]
cards = [{'id': id} for id in req.ids]
return cards
@ -322,9 +341,29 @@ class CollectionHandler(RestHandlerBase):
#
@noReturnValue
def reset_scheduler(self, col, data, ids):
def reset_scheduler(self, col, req):
col.sched.reset()
def answer_card(self, col, req):
import time
card_id = long(req.data['id'])
ease = int(req.data['ease'])
card = col.getCard(card_id)
if card.timerStarted is None:
card.timerStarted = float(req.data.get('timeStarted', time.time()))
col.sched.answerCard(card, ease)
#
# GLOBAL / MISC
#
@noReturnValue
def set_language(self, col, req):
anki.lang.setLang(req.data['code'])
class ImportExportHandler(RestHandlerBase):
"""Handler group for the 'collection' type, but it's not added by default."""
@ -354,15 +393,15 @@ class ImportExportHandler(RestHandlerBase):
return importer_class
def import_file(self, col, data, ids):
def import_file(self, col, req):
import AnkiServer.importer
import tempfile
# get the importer class
importer_class = self._get_importer_class(data)
importer_class = self._get_importer_class(req.data)
# get the file data
filedata = self._get_filedata(data)
filedata = self._get_filedata(req.data)
# write the file data to a temporary file
try:
@ -379,8 +418,8 @@ class ImportExportHandler(RestHandlerBase):
class ModelHandler(RestHandlerBase):
"""Default handler group for 'model' type."""
def field_names(self, col, data, ids):
model = col.models.get(ids[1])
def field_names(self, col, req):
model = col.models.get(req.ids[1])
if model is None:
raise HTTPNotFound()
return col.models.fieldNames(model)
@ -398,8 +437,8 @@ class NoteHandler(RestHandlerBase):
# TODO: do more stuff!
return d
def index(self, col, data, ids):
note = col.getNote(ids[1])
def index(self, col, req):
note = col.getNote(req.ids[1])
return self._serialize(note)
class DeckHandler(RestHandlerBase):
@ -431,16 +470,31 @@ class DeckHandler(RestHandlerBase):
l.reverse()
# Loop through and add the ease and estimated time (in seconds)
return [(ease, label, col.sched.nextIvl(card, ease)) for ease, label in enumerate(l, 1)]
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, data, ids):
deck = self._get_deck(col, ids)
def next_card(self, col, req):
deck = self._get_deck(col, req.ids)
# 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)
@ -471,11 +525,14 @@ class CardHandler(RestHandlerBase):
'reps': card.reps,
'type': card.type,
'usn': card.usn,
'timerStarted': card.timerStarted,
}
return d
# Our entry point
def make_app(global_conf, **local_conf):
# TODO: we should setup the default language from conf!
# setup the logger
from AnkiServer.utils import setup_logging
setup_logging(local_conf.get('logging.config_file'))

View File

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
import os
import shutil
@ -11,7 +12,7 @@ from mock import MagicMock
import AnkiServer
from AnkiServer.collection import CollectionManager
from AnkiServer.apps.rest_app import RestApp, CollectionHandler, ImportExportHandler, NoteHandler, ModelHandler, DeckHandler, CardHandler
from AnkiServer.apps.rest_app import RestApp, RestHandlerRequest, CollectionHandler, ImportExportHandler, NoteHandler, ModelHandler, DeckHandler, CardHandler
from webob.exc import *
@ -158,7 +159,8 @@ class CollectionHandlerTest(CollectionTestBase):
def execute(self, name, data):
ids = ['collection_name']
func = getattr(self.handler, name)
return func(self.collection, data, ids)
req = RestHandlerRequest(data, ids, {})
return func(self.collection, req)
def test_list_decks(self):
data = {}
@ -246,6 +248,38 @@ class CollectionHandlerTest(CollectionTestBase):
self.assertEqual(note['Back'], 'The back')
self.assertEqual(note.tags, ['Tag1', 'Tag2'])
def test_set_language(self):
import anki.lang
self.assertEqual(anki.lang._('Again'), 'Again')
try:
data = {'code': 'pl'}
self.execute('set_language', data)
self.assertEqual(anki.lang._('Again'), u'Znowu')
finally:
# return everything to normal!
anki.lang.setLang('en')
def test_answer_card(self):
import time
self.add_default_note()
# instantiate a deck handler to get the card
deck_handler = DeckHandler()
deck_request = RestHandlerRequest({}, ['c', '1'], {})
card = deck_handler.next_card(self.collection, deck_request)
self.assertEqual(card['reps'], 0)
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!
self.collection.sched.reset()
card = deck_handler.next_card(self.collection, deck_request)
self.assertEqual(card['reps'], 1)
class ImportExportHandlerTest(CollectionTestBase):
export_rows = [
['Card front 1', 'Card back 1', 'Tag1 Tag2'],
@ -259,7 +293,8 @@ class ImportExportHandlerTest(CollectionTestBase):
def execute(self, name, data):
ids = ['collection_name']
func = getattr(self.handler, name)
return func(self.collection, data, ids)
req = RestHandlerRequest(data, ids, {})
return func(self.collection, req)
def generate_text_export(self):
# Create a simple export file
@ -310,7 +345,8 @@ class DeckHandlerTest(CollectionTestBase):
def execute(self, name, data):
ids = ['collection_name', '1']
func = getattr(self.handler, name)
return func(self.collection, data, ids)
req = RestHandlerRequest(data, ids, {})
return func(self.collection, req)
def test_next_card(self):
ret = self.execute('next_card', {})
@ -324,12 +360,34 @@ class DeckHandlerTest(CollectionTestBase):
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'], [(1, 'Again', 60), (2, 'Good', 600), (3, 'Easy', 345600)])
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)