* Simplified the *HandlerGroup to *Handler.

* Added lots of operations connected with models and notes.
This commit is contained in:
David Snopek 2013-07-16 15:12:05 +01:00
parent 93094ebb48
commit 022235ec60
2 changed files with 190 additions and 44 deletions

View File

@ -23,16 +23,11 @@ def noReturnValue(func):
return func return func
class RestHandlerBase(object): class RestHandlerBase(object):
"""Parent class for single handler callbacks."""
hasReturnValue = True
def __call__(self, collection, data, ids):
pass
class RestHandlerGroupBase(object):
"""Parent class for a handler group.""" """Parent class for a handler group."""
hasReturnValue = True hasReturnValue = True
class _RestHandlerWrapper(RestHandlerBase): class _RestHandlerWrapper(RestHandlerBase):
"""Wrapper for functions that we can't modify."""
def __init__(self, func_name, func, hasReturnValue=True): def __init__(self, func_name, func, hasReturnValue=True):
self.func_name = func_name self.func_name = func_name
self.func = func self.func = func
@ -44,7 +39,9 @@ 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."""
# Defines not only the valid handler types, but their position in the URL string # Defines not only the valid handler types, but their position in the URL string
handler_types = ['collection', ['deck', 'note'], 'card'] # TODO: this broken - it allows a model to contain cards, for example.. We need to
# give a pattern for each handler type.
handler_types = ['collection', ['model', 'note', 'deck'], 'card']
def __init__(self, data_root, allowed_hosts='*', use_default_handlers=True, collection_manager=None): def __init__(self, data_root, allowed_hosts='*', use_default_handlers=True, collection_manager=None):
from AnkiServer.threading import getCollectionManager from AnkiServer.threading import getCollectionManager
@ -53,9 +50,9 @@ class RestApp(object):
self.allowed_hosts = allowed_hosts self.allowed_hosts = allowed_hosts
if collection_manager is not None: if collection_manager is not None:
self.collection_manager = collection_manager col = collection_manager
else: else:
self.collection_manager = getCollectionManager() col = getCollectionManager()
self.handlers = {} self.handlers = {}
for type_list in self.handler_types: for type_list in self.handler_types:
@ -65,10 +62,11 @@ class RestApp(object):
self.handlers[handler_type] = {} self.handlers[handler_type] = {}
if use_default_handlers: if use_default_handlers:
self.add_handler_group('collection', CollectionHandlerGroup()) self.add_handler_group('collection', CollectionHandler())
self.add_handler_group('note', NoteHandlerGroup()) self.add_handler_group('note', NoteHandler())
self.add_handler_group('deck', DeckHandlerGroup()) self.add_handler_group('model', ModelHandler())
self.add_handler_group('card', CardHandlerGroup()) self.add_handler_group('deck', DeckHandler())
self.add_handler_group('card', CardHandler())
def add_handler(self, type, name, handler): def add_handler(self, type, name, handler):
"""Adds a callback handler for a type (collection, deck, card) with a unique name. """Adds a callback handler for a type (collection, deck, card) with a unique name.
@ -77,7 +75,7 @@ class RestApp(object):
- 'name' is a unique name for the handler that gets used in the URL. - 'name' is a unique name for the handler that gets used in the URL.
- 'handler' handler can be a Python method or a subclass of the RestHandlerBase class. - 'handler' is a callable that takes (collection, data, ids).
""" """
if self.handlers[type].has_key(name): if self.handlers[type].has_key(name):
@ -85,7 +83,7 @@ class RestApp(object):
self.handlers[type][name] = handler self.handlers[type][name] = handler
def add_handler_group(self, type, group): def add_handler_group(self, type, group):
"""Adds several handlers for every public method on an object descended from RestHandlerGroup. """Adds several handlers for every public method on an object descended from RestHandlerBase.
This allows you to create a single class with several methods, so that you can quickly This allows you to create a single class with several methods, so that you can quickly
create a group of related handlers.""" create a group of related handlers."""
@ -241,37 +239,117 @@ class RestApp(object):
else: else:
return Response(json.dumps(output), content_type='application/json') return Response(json.dumps(output), content_type='application/json')
class CollectionHandlerGroup(RestHandlerGroupBase): class CollectionHandler(RestHandlerBase):
"""Default handler group for 'collection' type.""" """Default handler group for 'collection' type."""
#
# MODELS - Store fields definitions and templates for notes
#
def list_models(self, col, data, ids):
# 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):
# This is already a list of dicts, so it doesn't need to be serialized
return col.models.byName(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', '')
ids = col.findNotes(query)
if data.get('preload', False):
nodes = [NoteHandler._serialize(col.getNote(id)) for id in ids]
else:
nodes = [{'id': id} for id in ids]
return nodes
def add_note(self, col, data, ids):
from anki.notes import Note
if type(data['model']) in (str, unicode):
model = col.models.byName(data['model'])
else:
model = col.models.get(data['model'])
note = Note(col, model)
for name, value in data['fields'].items():
note[name] = value
if data.has_key('tags'):
note.setTagsFromStr(data['tags'])
col.addNote(note)
#
# DECKS - Groups of cards
#
def list_decks(self, col, data, ids): def list_decks(self, col, data, ids):
# This is already a list of dicts, so it doesn't need to be serialized
return col.decks.all() return col.decks.all()
@noReturnValue @noReturnValue
def select_deck(self, col, data, ids): def select_deck(self, col, data, ids):
col.decks.select(data['deck_id']) col.decks.select(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', '')
ids = col.findCards(query)
if data.get('preload', False):
cards = [CardHandler._serialize(col.getCard(id)) for id in ids]
else:
cards = [{'id': id} for id in ids]
return cards
#
# SCHEDULER - Controls card review, ie. intervals, what cards are due, answering a card, etc.
#
@noReturnValue @noReturnValue
def sched_reset(self, col, data, ids): def sched_reset(self, col, data, ids):
col.sched.reset() col.sched.reset()
class NoteHandlerGroup(RestHandlerGroupBase): class ModelHandler(RestHandlerBase):
"""Default handler group for 'model' type."""
def field_names(self, col, data, ids):
model = col.models.get(ids[1])
if model is None:
raise HTTPNotFound()
return col.models.fieldNames(model)
class NoteHandler(RestHandlerBase):
"""Default handler group for 'note' type.""" """Default handler group for 'note' type."""
@staticmethod @staticmethod
def _serialize_note(note): def _serialize(note):
d = { d = {
'id': note.id, 'id': note.id,
'model': note.model()['name'], 'model': note.model()['name'],
'tags': ' '.join(note.tags),
} }
# TODO: do more stuff! # TODO: do more stuff!
return d return d
def index(self, col, data, ids): def index(self, col, data, ids):
note = col.getNote(ids[1]) note = col.getNote(ids[1])
return self._serialize_note(note) return self._serialize(note)
class DeckHandlerGroup(RestHandlerGroupBase): class DeckHandler(RestHandlerBase):
"""Default handler group for 'deck' type.""" """Default handler group for 'deck' type."""
def next_card(self, col, data, ids): def next_card(self, col, data, ids):
@ -282,13 +360,13 @@ class DeckHandlerGroup(RestHandlerGroupBase):
if card is None: if card is None:
return None return None
return CardHandlerGroup._serialize_card(card) return CardHandler._serialize(card)
class CardHandlerGroup(RestHandlerGroupBase): class CardHandler(RestHandlerBase):
"""Default handler group for 'card' type.""" """Default handler group for 'card' type."""
@staticmethod @staticmethod
def _serialize_card(card): def _serialize(card):
d = { d = {
'id': card.id 'id': card.id
} }

View File

@ -11,7 +11,7 @@ from mock import MagicMock
import AnkiServer import AnkiServer
from AnkiServer.collection import CollectionManager from AnkiServer.collection import CollectionManager
from AnkiServer.apps.rest_app import RestApp, CollectionHandlerGroup, DeckHandlerGroup from AnkiServer.apps.rest_app import RestApp, CollectionHandler, NoteHandler, ModelHandler, DeckHandler, CardHandler
from webob.exc import * from webob.exc import *
@ -127,14 +127,7 @@ class CollectionTestBase(unittest.TestCase):
def add_note(self, data): def add_note(self, data):
from anki.notes import Note from anki.notes import Note
# TODO: we need to check the input for the correct keys.. Can we automate
# this somehow? Maybe using KeyError or wrapper or something?
#pprint(self.collection.models.all())
#pprint(self.collection.models.current())
model = self.collection.models.byName(data['model']) model = self.collection.models.byName(data['model'])
#pprint (self.collection.models.fieldNames(model))
note = Note(self.collection, model) note = Note(self.collection, model)
for name, value in data['fields'].items(): for name, value in data['fields'].items():
@ -143,24 +136,18 @@ class CollectionTestBase(unittest.TestCase):
if data.has_key('tags'): if data.has_key('tags'):
note.setTagsFromStr(data['tags']) note.setTagsFromStr(data['tags'])
ret = self.collection.addNote(note) self.collection.addNote(note)
def find_notes(self, data): class CollectionHandlerTest(CollectionTestBase):
query = data.get('query', '')
ids = self.collection.getNotes(query)
class CollectionHandlerGroupTest(CollectionTestBase):
def setUp(self): def setUp(self):
super(CollectionHandlerGroupTest, self).setUp() super(CollectionHandlerTest, self).setUp()
self.handler = CollectionHandlerGroup() self.handler = CollectionHandler()
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)
return func(self.collection, data, ids) return func(self.collection, data, ids)
def test_list_decks(self): def test_list_decks(self):
data = {} data = {}
ret = self.execute('list_decks', data) ret = self.execute('list_decks', data)
@ -174,10 +161,91 @@ class CollectionHandlerGroupTest(CollectionTestBase):
ret = self.execute('select_deck', data) ret = self.execute('select_deck', data)
self.assertEqual(ret, None); self.assertEqual(ret, None);
class DeckHandlerGroupTest(CollectionTestBase): def test_list_models(self):
data = {}
ret = self.execute('list_models', data)
# get a sorted name list that we can actually check
names = [model['name'] for model in ret]
names.sort()
# These are the default models created by Anki in a new collection
default_models = [
'Basic',
'Basic (and reversed card)',
'Basic (optional reversed card)',
'Cloze'
]
self.assertEqual(names, default_models)
def test_find_model_by_name(self):
data = {'model': 'Basic'}
ret = self.execute('find_model_by_name', data)
self.assertEqual(ret['name'], 'Basic')
def test_find_notes(self):
ret = self.execute('find_notes', {})
self.assertEqual(ret, [])
# add a note programatically
note = {
'model': 'Basic',
'fields': {
'Front': 'The front',
'Back': 'The back',
},
'tags': "Tag1 Tag2",
}
self.add_note(note)
# get the id for the one note on this collection
note_id = self.collection.findNotes('')[0]
ret = self.execute('find_notes', {})
self.assertEqual(ret, [{'id': note_id}])
ret = self.execute('find_notes', {'query': 'tag:Tag1'})
self.assertEqual(ret, [{'id': note_id}])
ret = self.execute('find_notes', {'query': 'tag:TagX'})
self.assertEqual(ret, [])
ret = self.execute('find_notes', {'preload': True})
self.assertEqual(len(ret), 1)
self.assertEqual(ret[0]['id'], note_id)
self.assertEqual(ret[0]['model'], 'Basic')
def test_add_note(self):
# make sure there are no notes (yet)
self.assertEqual(self.collection.findNotes(''), [])
# add a note programatically
note = {
'model': 'Basic',
'fields': {
'Front': 'The front',
'Back': 'The back',
},
'tags': "Tag1 Tag2",
}
self.execute('add_note', note)
notes = self.collection.findNotes('')
self.assertEqual(len(notes), 1)
note_id = notes[0]
note = self.collection.getNote(note_id)
self.assertEqual(note.model()['name'], 'Basic')
self.assertEqual(note['Front'], 'The front')
self.assertEqual(note['Back'], 'The back')
self.assertEqual(note.tags, ['Tag1', 'Tag2'])
class DeckHandlerTest(CollectionTestBase):
def setUp(self): def setUp(self):
super(DeckHandlerGroupTest, self).setUp() super(DeckHandlerTest, self).setUp()
self.handler = DeckHandlerGroup() self.handler = DeckHandler()
def execute(self, name, data): def execute(self, name, data):
ids = ['collection_name', '1'] ids = ['collection_name', '1']