Commit 131c63b6 by Arthur Barrett

refactored model and api to search/filter by page uri

parent 0574eb49
from django.http import HttpResponse, Http404 from django.http import HttpResponse, Http404
from django.core.exceptions import ValidationError
from notes.models import Note from notes.models import Note
import json import json
import logging import logging
...@@ -73,13 +74,19 @@ def api_format(request, response, data): ...@@ -73,13 +74,19 @@ def api_format(request, response, data):
# Exposed API actions via the resource map. # Exposed API actions via the resource map.
def index(request, course_id): 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]] return [HttpResponse(), [note.as_dict() for note in notes]]
def create(request, course_id): def create(request, course_id):
note = Note(course_id=course_id, body=request.body, user=request.user) note = Note(course_id=course_id, user=request.user)
note.save()
try:
note.clean(request.body)
except ValidationError as e:
log.debug(e)
return [HttpResponse('', status=500), None]
note.save()
response = HttpResponse('', status=303) response = HttpResponse('', status=303)
response['Location'] = note.get_absolute_url() response['Location'] = note.get_absolute_url()
...@@ -105,8 +112,13 @@ def update(request, course_id, note_id): ...@@ -105,8 +112,13 @@ def update(request, course_id, note_id):
if not note.user.id == request.user.id: if not note.user.id == request.user.id:
return [HttpResponse('', status=403)] return [HttpResponse('', status=403)]
note.body = request.body try:
note.save(update_fields=['body', 'updated']) 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] return [HttpResponse('', status=303), None]
...@@ -124,7 +136,20 @@ def delete(request, course_id, note_id): ...@@ -124,7 +136,20 @@ def delete(request, course_id, note_id):
return [HttpResponse('', status=204), None] return [HttpResponse('', status=204), None]
def search(request, course_id): 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): def version(request, course_id):
return [HttpResponse(), {'name': 'Notes API', 'version': '1.0'}] return [HttpResponse(), {'name': 'Notes API', 'version': '1.0'}]
from django.db import models from django.db import models
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.core.exceptions import ValidationError
import json import json
import logging
log = logging.getLogger(__name__)
class Note(models.Model): class Note(models.Model):
user = models.ForeignKey(User, db_index=True) user = models.ForeignKey(User, db_index=True)
course_id = models.CharField(max_length=255, 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) created = models.DateTimeField(auto_now_add=True, null=True, db_index=True)
updated = models.DateTimeField(auto_now=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): def get_absolute_url(self):
kwargs = {'course_id': self.course_id, 'note_id': str(self.id)} kwargs = {'course_id': self.course_id, 'note_id': str(self.id)}
return reverse('notes_api_note', kwargs=kwargs) return reverse('notes_api_note', kwargs=kwargs)
def as_dict(self): def as_dict(self):
d = {} return {
json_body = json.loads(self.body) 'id': self.id,
if type(json_body) is dict: 'user_id': self.user.id,
d.update(json_body) 'uri': self.uri,
d['id'] = self.id 'text': self.text,
d['user_id'] = self.user.id 'quote': self.quote,
return d 'ranges': [{
\ No newline at end of file 'start': self.range_start,
'startOffset': self.range_start_offset,
'end': self.range_end,
'endOffset': self.range_end_offset
}],
'tags': self.tags.split(",")
}
\ No newline at end of file
...@@ -5,5 +5,5 @@ urlpatterns = patterns('notes.api', ...@@ -5,5 +5,5 @@ urlpatterns = patterns('notes.api',
url(r'^api$', 'api_request', {'resource':'root'}, name='notes_api_root'), 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$', '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/' + 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')
) )
class StudentNotes class StudentNotes
_debug: true _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) -> constructor: ($, el) ->
console.log 'student notes init', arguments, this if @_debug console.log 'student notes init', arguments, this if @_debug
if $(el).data('notes-ready') isnt 'yes' if not $(el).data('notes-instance')
$(el).delegate '*', 'notes:init': @onInitNotes events = 'notes:init': @onInitNotes
$(el).data('notes-ready', 'yes') $(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() event.stopPropagation()
storeConfig = @getStoreConfig uri
found = @targets.some (target) -> target is event.target found = @targets.some (target) -> target is event.target
if found if found
annotator = $(event.target).data('annotator') annotator = $(event.target).data('annotator')
store = annotator.plugins['Store'] if annotator
store.options.annotationData = annotationData if annotationData store = annotator.plugins['Store']
store.loadAnnotations() $.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 else
$(event.target).annotator() $(event.target).annotator()
.annotator('addPlugin', 'Tags') .annotator('addPlugin', 'Tags')
.annotator('addPlugin', 'Store', @getStoreConfig(annotationData)) .annotator('addPlugin', 'Store', storeConfig)
@targets.push(event.target) @targets.push(event.target)
getStoreConfig: (annotationData) -> # 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()
storeConfig = storeConfig =
prefix: @getPrefix() prefix: prefix
loadFromSearch:
uri: uri
limit: 0
annotationData: annotationData:
uri: @getURIPath() # defaults to current URI path uri: uri
$.extend storeConfig.annotationData, annotationData if annotationData
storeConfig storeConfig
# Returns the API endpoint for the annotation store
getPrefix: () -> getPrefix: () ->
re = /^(\/courses\/[^/]+\/[^/]+\/[^/]+)/ re = /^(\/courses\/[^/]+\/[^/]+\/[^/]+)/
match = re.exec(@getURIPath()) match = re.exec(@getURIPath())
prefix = (if match then match[1] else '') prefix = (if match then match[1] else '')
return "#{prefix}/notes/api" return "#{prefix}/notes/api"
# Returns the URI path of the current page for filtering annotations
getURIPath: () -> getURIPath: () ->
window.location.href.toString().split(window.location.host)[1] window.location.href.toString().split(window.location.host)[1]
$(document).ready ($) -> new StudentNotes($, this)
\ No newline at end of file # 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 $, @
\ No newline at end of file
...@@ -33,8 +33,7 @@ ...@@ -33,8 +33,7 @@
var onComplete = function(url) { var onComplete = function(url) {
return function() { return function() {
var annotationData = { 'uri': url } $('#viewerContainer').trigger('notes:init', [url]);
$('#viewerContainer').trigger('notes:init', [annotationData]);
} }
}; };
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment