1 # -*- encoding: utf-8 -*-
3 __author__= "Ćukasz Rekucki"
4 __date__ = "$2009-09-25 15:49:50$"
5 __doc__ = "Module documentation."
7 from piston.handler import BaseHandler, AnonymousBaseHandler
12 import api.forms as forms
13 from datetime import date
15 from django.core.urlresolvers import reverse
17 from wlrepo import RevisionNotFound, LibraryException, DocumentAlreadyExists
18 from librarian import dcparser
20 import api.response as response
21 from api.utils import validate_form, hglibrary
23 from explorer.models import PullRequest
26 # Document List Handlers
28 class BasicLibraryHandler(AnonymousBaseHandler):
29 allowed_methods = ('GET',)
32 def read(self, request, lib):
33 """Return the list of documents."""
35 'url': reverse('document_view', args=[docid]),
36 'name': docid } for docid in lib.documents() ]
38 return {'documents' : document_list}
41 class LibraryHandler(BaseHandler):
42 allowed_methods = ('GET', 'POST')
43 anonymous = BasicLibraryHandler
46 def read(self, request, lib):
47 """Return the list of documents."""
50 'url': reverse('document_view', args=[docid]),
51 'name': docid } for docid in lib.documents() ]
53 return {'documents' : document_list }
55 @validate_form(forms.DocumentUploadForm, 'POST')
57 def create(self, request, form, lib):
58 """Create a new document."""
60 if form.cleaned_data['ocr_data']:
61 data = form.cleaned_data['ocr_data']
63 data = request.FILES['ocr_file'].read().decode('utf-8')
65 if form.cleaned_data['generate_dc']:
66 data = librarian.wrap_text(data, unicode(date.today()))
68 docid = form.cleaned_data['bookname']
75 doc = lib.document_create(docid)
76 # document created, but no content yet
79 doc = doc.quickwrite('xml', data.encode('utf-8'),
80 '$AUTO$ XML data uploaded.', user=request.user.username)
82 # rollback branch creation
84 raise LibraryException("Exception occured:" + repr(e))
86 url = reverse('document_view', args=[doc.id])
88 return response.EntityCreated().django_response(\
92 'revision': doc.revision },
96 except LibraryException, e:
97 return response.InternalError().django_response(\
98 {'exception': repr(e) })
99 except DocumentAlreadyExists:
100 # Document is already there
101 return response.EntityConflict().django_response(\
102 {"reason": "Document %s already exists." % docid})
107 class BasicDocumentHandler(AnonymousBaseHandler):
108 allowed_methods = ('GET',)
111 def read(self, request, docid, lib):
113 doc = lib.document(docid)
114 except RevisionNotFound:
119 'html_url': reverse('dochtml_view', args=[doc.id,doc.revision]),
120 'text_url': reverse('doctext_view', args=[doc.id,doc.revision]),
121 'dc_url': reverse('docdc_view', args=[doc.id,doc.revision]),
122 'public_revision': doc.revision,
130 class DocumentHandler(BaseHandler):
131 allowed_methods = ('GET', 'PUT')
132 anonymous = BasicDocumentHandler
135 def read(self, request, docid, lib):
136 """Read document's meta data"""
138 doc = lib.document(docid)
139 udoc = doc.take(request.user.username)
140 except RevisionNotFound:
141 return request.EnityNotFound().django_response()
143 # is_shared = udoc.ancestorof(doc)
144 # is_uptodate = is_shared or shared.ancestorof(document)
148 'html_url': reverse('dochtml_view', args=[udoc.id,udoc.revision]),
149 'text_url': reverse('doctext_view', args=[udoc.id,udoc.revision]),
150 'dc_url': reverse('docdc_view', args=[udoc.id,udoc.revision]),
151 'user_revision': udoc.revision,
152 'public_revision': doc.revision,
158 def update(self, request, docid, lib):
159 """Update information about the document, like display not"""
165 class DocumentHTMLHandler(BaseHandler):
166 allowed_methods = ('GET', 'PUT')
169 def read(self, request, docid, revision, lib):
170 """Read document as html text"""
172 if revision == 'latest':
173 document = lib.document(docid)
175 document = lib.document_for_rev(revision)
177 return librarian.html.transform(document.data('xml'))
178 except RevisionNotFound:
179 return response.EntityNotFound().django_response()
184 class DocumentTextHandler(BaseHandler):
185 allowed_methods = ('GET', 'PUT')
188 def read(self, request, docid, revision, lib):
189 """Read document as raw text"""
191 if revision == 'latest':
192 document = lib.document(docid)
194 document = lib.document_for_rev(revision)
196 # TODO: some finer-grained access control
197 return document.data('xml')
198 except RevisionNotFound:
199 return response.EntityNotFound().django_response()
202 def update(self, request, docid, revision, lib):
204 data = request.PUT['contents']
206 if request.PUT.has_key('message'):
207 msg = u"$USER$ " + request.PUT['message']
209 msg = u"$AUTO$ XML content update."
211 current = lib.document(docid, request.user.username)
212 orig = lib.document_for_rev(revision)
215 return response.EntityConflict().django_response({
216 "reason": "out-of-date",
217 "provided_revision": orig.revision,
218 "latest_revision": current.revision })
220 ndoc = current.quickwrite('xml', data, msg)
223 # return the new revision number
227 "previous_revision": current.revision,
228 "updated_revision": ndoc.revision
233 except RevisionNotFound, e:
234 return response.EntityNotFound().django_response(e)
237 # Dublin Core handlers
239 # @requires librarian
241 class DocumentDublinCoreHandler(BaseHandler):
242 allowed_methods = ('GET', 'PUT')
245 def read(self, request, docid, revision, lib):
246 """Read document as raw text"""
248 if revision == 'latest':
249 doc = lib.document(docid)
251 doc = lib.document_for_rev(revision)
253 bookinfo = dcparser.BookInfo.from_string(doc.data('xml'))
254 return bookinfo.serialize()
255 except RevisionNotFound:
256 return response.EntityNotFound().django_response()
259 def update(self, request, docid, revision, lib):
261 bi_json = request.PUT['contents']
262 if request.PUT.has_key('message'):
263 msg = u"$USER$ " + request.PUT['message']
265 msg = u"$AUTO$ Dublin core update."
267 current = lib.document(docid, request.user.username)
268 orig = lib.document_for_rev(revision)
271 return response.EntityConflict().django_response({
272 "reason": "out-of-date",
273 "provided": orig.revision,
274 "latest": current.revision })
276 xmldoc = parser.WLDocument.from_string(current.data('xml'))
277 document.book_info = dcparser.BookInfo.from_json(bi_json)
280 ndoc = current.quickwrite('xml', \
281 document.serialize().encode('utf-8'),\
282 message=msg, user=request.user.username)
285 # return the new revision number
289 "previous_revision": current.revision,
290 "updated_revision": ndoc.revision
295 except RevisionNotFound:
296 return response.EntityNotFound().django_response()
298 class MergeHandler(BaseHandler):
299 allowed_methods = ('POST',)
301 @validate_form(forms.MergeRequestForm, 'POST')
303 def create(self, request, form, docid, lib):
304 """Create a new document revision from the information provided by user"""
306 target_rev = form.cleaned_data['target_revision']
308 doc = lib.document(docid)
309 udoc = doc.take(request.user.username)
311 if target_rev == 'latest':
312 target_rev = udoc.revision
314 if str(udoc.revision) != target_rev:
315 # user think doesn't know he has an old version
318 # Updating is teorericly ok, but we need would
319 # have to force a refresh. Sharing may be not safe,
320 # 'cause it doesn't always result in update.
322 # In other words, we can't lie about the resource's state
323 # So we should just yield and 'out-of-date' conflict
324 # and let the client ask again with updated info.
326 # NOTE: this could result in a race condition, when there
327 # are 2 instances of the same user editing the same document.
328 # Instance "A" trying to update, and instance "B" always changing
329 # the document right before "A". The anwser to this problem is
330 # for the "A" to request a merge from 'latest' and then
331 # check the parent revisions in response, if he actually
332 # merge from where he thinks he should. If not, the client SHOULD
333 # update his internal state.
334 return response.EntityConflict().django_response({
335 "reason": "out-of-date",
336 "provided": target_rev,
337 "latest": udoc.revision })
339 if not request.user.has_perm('explorer.book.can_share'):
340 # User is not permitted to make a merge, right away
341 # So we instead create a pull request in the database
343 comitter=request.user,
345 source_revision = str(udoc.revision),
347 comment = form.cleaned_data['comment'] or '$AUTO$ Document shared.'
351 return response.RequestAccepted().django_response(\
352 ticket_status=prq.status, \
353 ticket_uri=reverse("pullrequest_view", args=[prq.id]) )
355 if form.cleanded_data['type'] == 'update':
356 # update is always performed from the file branch
358 success, changed = udoc.update(request.user.username)
360 if form.cleanded_data['type'] == 'share':
361 success, changed = udoc.share(form.cleaned_data['comment'])
364 return response.EntityConflict().django_response()
367 return response.SuccessNoContent().django_response()
369 new_udoc = udoc.latest()
371 return response.SuccessAllOk().django_response({
373 "parent_user_resivion": udoc.revision,
374 "parent_revision": doc.revision,
375 "revision": udoc.revision,