* Simplified the *HandlerGroup to *Handler.
* Added lots of operations connected with models and notes.
This commit is contained in:
parent
93094ebb48
commit
022235ec60
@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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']
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user