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']
73 doc = lib.document_create(docid)
74 # document created, but no content yet
77 doc = doc.quickwrite('xml', data.encode('utf-8'),
78 '$AUTO$ XML data uploaded.', user=request.user.username)
80 # rollback branch creation
82 raise LibraryException("Exception occured:" + repr(e))
84 url = reverse('document_view', args=[doc.id])
86 return response.EntityCreated().django_response(\
90 'revision': doc.revision },
94 except LibraryException, e:
95 return response.InternalError().django_response(\
96 {'exception': repr(e) })
97 except DocumentAlreadyExists:
98 # Document is already there
99 return response.EntityConflict().django_response(\
100 {"reason": "Document %s already exists." % docid})
105 class BasicDocumentHandler(AnonymousBaseHandler):
106 allowed_methods = ('GET',)
109 def read(self, request, docid, lib):
111 doc = lib.document(docid)
112 except RevisionNotFound:
117 'html_url': reverse('dochtml_view', args=[doc.id,doc.revision]),
118 'text_url': reverse('doctext_view', args=[doc.id,doc.revision]),
119 'dc_url': reverse('docdc_view', args=[doc.id,doc.revision]),
120 'public_revision': doc.revision,
128 class DocumentHandler(BaseHandler):
129 allowed_methods = ('GET', 'PUT')
130 anonymous = BasicDocumentHandler
133 def read(self, request, docid, lib):
134 """Read document's meta data"""
136 doc = lib.document(docid)
137 udoc = doc.take(request.user.username)
138 except RevisionNotFound:
139 return request.EnityNotFound().django_response()
141 # is_shared = udoc.ancestorof(doc)
142 # is_uptodate = is_shared or shared.ancestorof(document)
146 'html_url': reverse('dochtml_view', args=[udoc.id,udoc.revision]),
147 'text_url': reverse('doctext_view', args=[udoc.id,udoc.revision]),
148 'dc_url': reverse('docdc_view', args=[udoc.id,udoc.revision]),
149 'user_revision': udoc.revision,
150 'public_revision': doc.revision,
156 def update(self, request, docid, lib):
157 """Update information about the document, like display not"""
163 class DocumentHTMLHandler(BaseHandler):
164 allowed_methods = ('GET', 'PUT')
167 def read(self, request, docid, revision, lib):
168 """Read document as html text"""
170 if revision == 'latest':
171 document = lib.document(docid)
173 document = lib.document_for_rev(revision)
175 return librarian.html.transform(document.data('xml'))
176 except RevisionNotFound:
177 return response.EntityNotFound().django_response()
182 class DocumentTextHandler(BaseHandler):
183 allowed_methods = ('GET', 'PUT')
186 def read(self, request, docid, revision, lib):
187 """Read document as raw text"""
189 if revision == 'latest':
190 document = lib.document(docid)
192 document = lib.document_for_rev(revision)
194 # TODO: some finer-grained access control
195 return document.data('xml')
196 except RevisionNotFound:
197 return response.EntityNotFound().django_response()
200 def update(self, request, docid, revision, lib):
202 data = request.PUT['contents']
204 if request.PUT.has_key('message'):
205 msg = u"$USER$ " + request.PUT['message']
207 msg = u"$AUTO$ XML content update."
209 current = lib.document(docid, request.user.username)
210 orig = lib.document_for_rev(revision)
213 return response.EntityConflict().django_response({
214 "reason": "out-of-date",
215 "provided_revision": orig.revision,
216 "latest_revision": current.revision })
218 ndoc = current.quickwrite('xml', data, msg)
221 # return the new revision number
225 "previous_revision": current.revision,
226 "updated_revision": ndoc.revision
232 except (RevisionNotFound, KeyError):
233 return response.EntityNotFound().django_response()
236 # Dublin Core handlers
238 # @requires librarian
240 class DocumentDublinCoreHandler(BaseHandler):
241 allowed_methods = ('GET', 'PUT')
244 def read(self, request, docid, revision, lib):
245 """Read document as raw text"""
247 if revision == 'latest':
248 document = lib.document(docid)
250 document = lib.document_for_rev(revision)
252 bookinfo = dcparser.BookInfo.from_string(doc.data('xml'))
253 return bookinfo.serialize()
254 except RevisionNotFound:
255 return response.EntityNotFound().django_response()
258 def update(self, request, docid, revision, lib):
260 bi_json = request.PUT['contents']
261 if request.PUT.has_key('message'):
262 msg = u"$USER$ " + request.PUT['message']
264 msg = u"$AUTO$ Dublin core update."
266 current = lib.document(docid, request.user.username)
267 orig = lib.document_for_rev(revision)
270 return response.EntityConflict().django_response({
271 "reason": "out-of-date",
272 "provided": orig.revision,
273 "latest": current.revision })
275 xmldoc = parser.WLDocument.from_string(current.data('xml'))
276 document.book_info = dcparser.BookInfo.from_json(bi_json)
279 ndoc = current.quickwrite('xml', \
280 document.serialize().encode('utf-8'),\
281 message=msg, user=request.user.username)
286 "previous_revision": prev,
287 "updated_revision": ndoc.revision
289 except (RevisionNotFound, KeyError):
290 return response.EntityNotFound().django_response()
293 class MergeHandler(BaseHandler):
294 allowed_methods = ('POST',)
296 @validate_form(forms.MergeRequestForm)
298 def create(self, request, form, docid, lib):
299 """Create a new document revision from the information provided by user"""
301 target_rev = form.cleaned_data['target_revision']
303 doc = lib.document(docid)
304 udoc = doc.take(request.user.username)
306 if target_rev == 'latest':
307 target_rev = udoc.revision
309 if udoc.revision != target_rev:
310 # user think doesn't know he has an old version
313 # Updating is teorericly ok, but we need would
314 # have to force a refresh. Sharing may be not safe,
315 # 'cause it doesn't always result in update.
317 # In other words, we can't lie about the resource's state
318 # So we should just yield and 'out-of-date' conflict
319 # and let the client ask again with updated info.
321 # NOTE: this could result in a race condition, when there
322 # are 2 instances of the same user editing the same document.
323 # Instance "A" trying to update, and instance "B" always changing
324 # the document right before "A". The anwser to this problem is
325 # for the "A" to request a merge from 'latest' and then
326 # check the parent revisions in response, if he actually
327 # merge from where he thinks he should. If not, the client SHOULD
328 # update his internal state.
329 return response.EntityConflict().django_response({
330 "reason": "out-of-date",
331 "provided": target_revision,
332 "latest": udoc.revision })
334 if not request.user.has_permission('explorer.pull_request.can_add'):
335 # User is not permitted to make a merge, right away
336 # So we instead create a pull request in the database
338 commiter=request.uset.username,
340 source_revision = udoc.revision,
342 comment = form.cleaned_data['comment']
346 return response.RequestAccepted()
348 if form.cleanded_data['type'] == 'update':
349 # update is always performed from the file branch
351 success, changed = udoc.update(request.user.username)
353 if form.cleanded_data['type'] == 'share':
354 success, changed = udoc.share(form.cleaned_data['comment'])
357 return response.EntityConflict().django_response()
360 return response.SuccessNoContent().django_response()
362 new_udoc = udoc.latest()
364 return response.SuccessAllOk().django_response({
366 "parent_user_resivion": udoc.revision,
367 "parent_revision": doc.revision,
368 "revision": udoc.revision,