refactored model and api to search/filter by page uri
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
from django.http import HttpResponse, Http404
|
||||
from django.core.exceptions import ValidationError
|
||||
from notes.models import Note
|
||||
import json
|
||||
import logging
|
||||
@@ -73,13 +74,19 @@ def api_format(request, response, data):
|
||||
# Exposed API actions via the resource map.
|
||||
|
||||
def index(request, course_id):
|
||||
notes = Note.objects.all()
|
||||
notes = Note.objects.filter(course_id=course_id, user=request.user)
|
||||
return [HttpResponse(), [note.as_dict() for note in notes]]
|
||||
|
||||
def create(request, course_id):
|
||||
note = Note(course_id=course_id, body=request.body, user=request.user)
|
||||
note.save()
|
||||
note = Note(course_id=course_id, user=request.user)
|
||||
|
||||
try:
|
||||
note.clean(request.body)
|
||||
except ValidationError as e:
|
||||
log.debug(e)
|
||||
return [HttpResponse('', status=500), None]
|
||||
|
||||
note.save()
|
||||
response = HttpResponse('', status=303)
|
||||
response['Location'] = note.get_absolute_url()
|
||||
|
||||
@@ -105,8 +112,13 @@ def update(request, course_id, note_id):
|
||||
if not note.user.id == request.user.id:
|
||||
return [HttpResponse('', status=403)]
|
||||
|
||||
note.body = request.body
|
||||
note.save(update_fields=['body', 'updated'])
|
||||
try:
|
||||
note.clean(request.body)
|
||||
except ValidationError as e:
|
||||
log.debug(e)
|
||||
return [HttpResponse('', status=500), None]
|
||||
|
||||
note.save(update_fields=['text', 'tags', 'updated'])
|
||||
|
||||
return [HttpResponse('', status=303), None]
|
||||
|
||||
@@ -124,7 +136,20 @@ def delete(request, course_id, note_id):
|
||||
return [HttpResponse('', status=204), None]
|
||||
|
||||
def search(request, course_id):
|
||||
return [HttpResponse(), []]
|
||||
limit = request.GET.get('limit')
|
||||
uri = request.GET.get('uri')
|
||||
|
||||
filters = {'course_id':course_id, 'user':request.user}
|
||||
if uri is not None:
|
||||
filters['uri'] = uri
|
||||
|
||||
notes = Note.objects.filter(**filters)
|
||||
#if limit is not None and limit > 0:
|
||||
#notes = notes[:limit]
|
||||
|
||||
result = {'rows': [note.as_dict() for note in notes]}
|
||||
|
||||
return [HttpResponse(), result]
|
||||
|
||||
def version(request, course_id):
|
||||
return [HttpResponse(), {'name': 'Notes API', 'version': '1.0'}]
|
||||
|
||||
@@ -1,25 +1,69 @@
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.core.exceptions import ValidationError
|
||||
|
||||
import json
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
class Note(models.Model):
|
||||
user = models.ForeignKey(User, db_index=True)
|
||||
course_id = models.CharField(max_length=255, db_index=True)
|
||||
uri = models.CharField(max_length=1024, db_index=True)
|
||||
text = models.TextField(default="")
|
||||
quote = models.TextField(default="")
|
||||
range_start = models.CharField(max_length=2048)
|
||||
range_start_offset = models.IntegerField()
|
||||
range_end = models.CharField(max_length=2048)
|
||||
range_end_offset = models.IntegerField()
|
||||
tags = models.TextField(default="") # comma-separated string
|
||||
created = models.DateTimeField(auto_now_add=True, null=True, db_index=True)
|
||||
updated = models.DateTimeField(auto_now=True, db_index=True)
|
||||
body = models.TextField()
|
||||
|
||||
def clean(self, json_body):
|
||||
if json_body is None:
|
||||
raise ValidationError('Note must have a body.')
|
||||
|
||||
body = json.loads(json_body)
|
||||
if not type(body) is dict:
|
||||
raise ValidationError('Note body must be a dictionary.')
|
||||
|
||||
self.uri = body.get('uri')
|
||||
self.text = body.get('text')
|
||||
self.quote = body.get('quote')
|
||||
|
||||
ranges = body.get('ranges')
|
||||
if ranges is None or len(ranges) != 1:
|
||||
raise ValidationError('Note must contain exactly one range.')
|
||||
|
||||
self.range_start = ranges[0]['start']
|
||||
self.range_start_offset = ranges[0]['startOffset']
|
||||
self.range_end = ranges[0]['end']
|
||||
self.range_end_offset = ranges[0]['endOffset']
|
||||
|
||||
self.tags = ""
|
||||
tags = body.get('tags', [])
|
||||
if len(tags) > 0:
|
||||
self.tags = ",".join(tags)
|
||||
|
||||
def get_absolute_url(self):
|
||||
kwargs = {'course_id': self.course_id, 'note_id': str(self.id)}
|
||||
return reverse('notes_api_note', kwargs=kwargs)
|
||||
|
||||
def as_dict(self):
|
||||
d = {}
|
||||
json_body = json.loads(self.body)
|
||||
if type(json_body) is dict:
|
||||
d.update(json_body)
|
||||
d['id'] = self.id
|
||||
d['user_id'] = self.user.id
|
||||
return d
|
||||
return {
|
||||
'id': self.id,
|
||||
'user_id': self.user.id,
|
||||
'uri': self.uri,
|
||||
'text': self.text,
|
||||
'quote': self.quote,
|
||||
'ranges': [{
|
||||
'start': self.range_start,
|
||||
'startOffset': self.range_start_offset,
|
||||
'end': self.range_end,
|
||||
'endOffset': self.range_end_offset
|
||||
}],
|
||||
'tags': self.tags.split(",")
|
||||
}
|
||||
@@ -5,5 +5,5 @@ urlpatterns = patterns('notes.api',
|
||||
url(r'^api$', 'api_request', {'resource':'root'}, name='notes_api_root'),
|
||||
url(r'^api/annotations$', 'api_request', {'resource':'notes'}, name='notes_api_notes'),
|
||||
url(r'^api/annotations/' + id_regex + r'$', 'api_request', {'resource':'note'}, name='notes_api_note'),
|
||||
url(r'^api/annotations/search$', 'api_request', {'resource':'search'}, name='notes_api_search')
|
||||
url(r'^api/search', 'api_request', {'resource':'search'}, name='notes_api_search')
|
||||
)
|
||||
|
||||
@@ -1,47 +1,74 @@
|
||||
class StudentNotes
|
||||
_debug: true
|
||||
|
||||
targets: [] # elements with annotator() instances
|
||||
targets: [] # holds elements with annotator() instances
|
||||
|
||||
# Adds a listener for "notes" events that may bubble up from descendants.
|
||||
constructor: ($, el) ->
|
||||
console.log 'student notes init', arguments, this if @_debug
|
||||
|
||||
if $(el).data('notes-ready') isnt 'yes'
|
||||
$(el).delegate '*', 'notes:init': @onInitNotes
|
||||
$(el).data('notes-ready', 'yes')
|
||||
if not $(el).data('notes-instance')
|
||||
events = 'notes:init': @onInitNotes
|
||||
$(el).delegate('*', events)
|
||||
$(el).data('notes-instance', @)
|
||||
|
||||
onInitNotes: (event, annotationData=null) =>
|
||||
# Initializes annotations on a container element in response to an init event.
|
||||
onInitNotes: (event, uri=null) =>
|
||||
event.stopPropagation()
|
||||
|
||||
storeConfig = @getStoreConfig uri
|
||||
found = @targets.some (target) -> target is event.target
|
||||
|
||||
if found
|
||||
annotator = $(event.target).data('annotator')
|
||||
store = annotator.plugins['Store']
|
||||
store.options.annotationData = annotationData if annotationData
|
||||
store.loadAnnotations()
|
||||
if annotator
|
||||
store = annotator.plugins['Store']
|
||||
$.extend(store.options, storeConfig)
|
||||
if uri
|
||||
store.loadAnnotationsFromSearch(storeConfig['loadFromSearch'])
|
||||
else
|
||||
console.log 'URI is required to load annotations'
|
||||
else
|
||||
console.log 'No annotator() instance found for target: ', event.target
|
||||
else
|
||||
$(event.target).annotator()
|
||||
.annotator('addPlugin', 'Tags')
|
||||
.annotator('addPlugin', 'Store', @getStoreConfig(annotationData))
|
||||
.annotator('addPlugin', 'Store', storeConfig)
|
||||
@targets.push(event.target)
|
||||
|
||||
getStoreConfig: (annotationData) ->
|
||||
storeConfig =
|
||||
prefix: @getPrefix()
|
||||
annotationData:
|
||||
uri: @getURIPath() # defaults to current URI path
|
||||
# Returns a JSON config object that can be passed to the annotator Store plugin
|
||||
getStoreConfig: (uri) ->
|
||||
prefix = @getPrefix()
|
||||
if uri is null
|
||||
console.log 'getURIPath()', uri, @getURIPath()
|
||||
uri = @getURIPath()
|
||||
|
||||
$.extend storeConfig.annotationData, annotationData if annotationData
|
||||
storeConfig =
|
||||
prefix: prefix
|
||||
loadFromSearch:
|
||||
uri: uri
|
||||
limit: 0
|
||||
annotationData:
|
||||
uri: uri
|
||||
storeConfig
|
||||
|
||||
# Returns the API endpoint for the annotation store
|
||||
getPrefix: () ->
|
||||
re = /^(\/courses\/[^/]+\/[^/]+\/[^/]+)/
|
||||
match = re.exec(@getURIPath())
|
||||
prefix = (if match then match[1] else '')
|
||||
return "#{prefix}/notes/api"
|
||||
|
||||
# Returns the URI path of the current page for filtering annotations
|
||||
getURIPath: () ->
|
||||
window.location.href.toString().split(window.location.host)[1]
|
||||
|
||||
$(document).ready ($) -> new StudentNotes($, this)
|
||||
|
||||
# Enable notes by default on the document root.
|
||||
# To initialize annotations on a container element in the document:
|
||||
#
|
||||
# $('#myElement').trigger('notes:init');
|
||||
#
|
||||
# Comment this line to disable notes.
|
||||
|
||||
$(document).ready ($) -> new StudentNotes $, @
|
||||
@@ -33,8 +33,7 @@
|
||||
|
||||
var onComplete = function(url) {
|
||||
return function() {
|
||||
var annotationData = { 'uri': url }
|
||||
$('#viewerContainer').trigger('notes:init', [annotationData]);
|
||||
$('#viewerContainer').trigger('notes:init', [url]);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user