* 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
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."""
hasReturnValue = True
class _RestHandlerWrapper(RestHandlerBase):
"""Wrapper for functions that we can't modify."""
def __init__(self, func_name, func, hasReturnValue=True):
self.func_name = func_name
self.func = func
@ -44,7 +39,9 @@ class RestApp(object):
"""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
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):
from AnkiServer.threading import getCollectionManager
@ -53,9 +50,9 @@ class RestApp(object):
self.allowed_hosts = allowed_hosts
if collection_manager is not None:
self.collection_manager = collection_manager
col = collection_manager
else:
self.collection_manager = getCollectionManager()
col = getCollectionManager()
self.handlers = {}
for type_list in self.handler_types:
@ -65,10 +62,11 @@ class RestApp(object):
self.handlers[handler_type] = {}
if use_default_handlers:
self.add_handler_group('collection', CollectionHandlerGroup())
self.add_handler_group('note', NoteHandlerGroup())
self.add_handler_group('deck', DeckHandlerGroup())
self.add_handler_group('card', CardHandlerGroup())
self.add_handler_group('collection', CollectionHandler())
self.add_handler_group('note', NoteHandler())
self.add_handler_group('model', ModelHandler())
self.add_handler_group('deck', DeckHandler())
self.add_handler_group('card', CardHandler())
def add_handler(self, type, name, handler):
"""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.
- '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):
@ -85,7 +83,7 @@ class RestApp(object):
self.handlers[type][name] = handler
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
create a group of related handlers."""
@ -241,37 +239,117 @@ class RestApp(object):
else:
return Response(json.dumps(output), content_type='application/json')
class CollectionHandlerGroup(RestHandlerGroupBase):
class CollectionHandler(RestHandlerBase):
"""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):
# 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'])
#
# 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
def sched_reset(self, col, data, ids):
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."""
@staticmethod
def _serialize_note(note):
def _serialize(note):
d = {
'id': note.id,
'model': note.model()['name'],
'tags': ' '.join(note.tags),
}
# TODO: do more stuff!
return d
def index(self, col, data, ids):
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."""
def next_card(self, col, data, ids):
@ -282,13 +360,13 @@ class DeckHandlerGroup(RestHandlerGroupBase):
if card is None:
return None
return CardHandlerGroup._serialize_card(card)
return CardHandler._serialize(card)
class CardHandlerGroup(RestHandlerGroupBase):
class CardHandler(RestHandlerBase):
"""Default handler group for 'card' type."""
@staticmethod
def _serialize_card(card):
def _serialize(card):
d = {
'id': card.id
}

View File

@ -11,7 +11,7 @@ from mock import MagicMock
import AnkiServer
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 *
@ -127,14 +127,7 @@ class CollectionTestBase(unittest.TestCase):
def add_note(self, data):
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'])
#pprint (self.collection.models.fieldNames(model))
note = Note(self.collection, model)
for name, value in data['fields'].items():
@ -143,24 +136,18 @@ class CollectionTestBase(unittest.TestCase):
if data.has_key('tags'):
note.setTagsFromStr(data['tags'])
ret = self.collection.addNote(note)
self.collection.addNote(note)
def find_notes(self, data):
query = data.get('query', '')
ids = self.collection.getNotes(query)
class CollectionHandlerGroupTest(CollectionTestBase):
class CollectionHandlerTest(CollectionTestBase):
def setUp(self):
super(CollectionHandlerGroupTest, self).setUp()
self.handler = CollectionHandlerGroup()
super(CollectionHandlerTest, self).setUp()
self.handler = CollectionHandler()
def execute(self, name, data):
ids = ['collection_name']
func = getattr(self.handler, name)
return func(self.collection, data, ids)
def test_list_decks(self):
data = {}
ret = self.execute('list_decks', data)
@ -174,10 +161,91 @@ class CollectionHandlerGroupTest(CollectionTestBase):
ret = self.execute('select_deck', data)
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):
super(DeckHandlerGroupTest, self).setUp()
self.handler = DeckHandlerGroup()
super(DeckHandlerTest, self).setUp()
self.handler = DeckHandler()
def execute(self, name, data):
ids = ['collection_name', '1']