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.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'}]
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
\ No newline at end of file
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(",")
}
\ No newline at end of file
......@@ -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')
)
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) ->
# 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 =
prefix: @getPrefix()
prefix: prefix
loadFromSearch:
uri: uri
limit: 0
annotationData:
uri: @getURIPath() # defaults to current URI path
$.extend storeConfig.annotationData, annotationData if 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)
\ 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 @@
var onComplete = function(url) {
return function() {
var annotationData = { 'uri': url }
$('#viewerContainer').trigger('notes:init', [annotationData]);
$('#viewerContainer').trigger('notes:init', [url]);
}
};
......
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