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']
74 doc = lib.document_create(docid)
75 # document created, but no content yet
78 doc = doc.quickwrite('xml', data.encode('utf-8'),
79 '$AUTO$ XML data uploaded.', user=request.user.username)
81 # rollback branch creation
83 raise LibraryException("Exception occured:" + repr(e))
85 url = reverse('document_view', args=[doc.id])
87 return response.EntityCreated().django_response(\
91 'revision': doc.revision },
95 except LibraryException, e:
96 return response.InternalError().django_response(\
97 {'exception': repr(e) })
98 except DocumentAlreadyExists:
99 # Document is already there
100 return response.EntityConflict().django_response(\
101 {"reason": "Document %s already exists." % docid})
106 class BasicDocumentHandler(AnonymousBaseHandler):
107 allowed_methods = ('GET',)
110 def read(self, request, docid, lib):
112 doc = lib.document(docid)
113 except RevisionNotFound:
118 'html_url': reverse('dochtml_view', args=[doc.id,doc.revision]),
119 'text_url': reverse('doctext_view', args=[doc.id,doc.revision]),
120 'dc_url': reverse('docdc_view', args=[doc.id,doc.revision]),
121 'public_revision': doc.revision,
129 class DocumentHandler(BaseHandler):
130 allowed_methods = ('GET', 'PUT')
131 anonymous = BasicDocumentHandler
134 def read(self, request, docid, lib):
135 """Read document's meta data"""
137 doc = lib.document(docid)
138 udoc = doc.take(request.user.username)
139 except RevisionNotFound:
140 return request.EnityNotFound().django_response()
142 # is_shared = udoc.ancestorof(doc)
143 # is_uptodate = is_shared or shared.ancestorof(document)
147 'html_url': reverse('dochtml_view', args=[udoc.id,udoc.revision]),
148 'text_url': reverse('doctext_view', args=[udoc.id,udoc.revision]),
149 'dc_url': reverse('docdc_view', args=[udoc.id,udoc.revision]),
150 'user_revision': udoc.revision,
151 'public_revision': doc.revision,
157 def update(self, request, docid, lib):
158 """Update information about the document, like display not"""
164 class DocumentHTMLHandler(BaseHandler):
165 allowed_methods = ('GET', 'PUT')
168 def read(self, request, docid, revision, lib):
169 """Read document as html text"""
171 if revision == 'latest':
172 document = lib.document(docid)
174 document = lib.document_for_rev(revision)
176 return librarian.html.transform(document.data('xml'), is_file=False)
177 except RevisionNotFound:
178 return response.EntityNotFound().django_response()
183 class DocumentTextHandler(BaseHandler):
184 allowed_methods = ('GET', 'PUT')
187 def read(self, request, docid, revision, lib):
188 """Read document as raw text"""
190 if revision == 'latest':
191 document = lib.document(docid)
193 document = lib.document_for_rev(revision)
195 # TODO: some finer-grained access control
196 return document.data('xml')
197 except RevisionNotFound:
198 return response.EntityNotFound().django_response()
201 def update(self, request, docid, revision, lib):
203 data = request.PUT['contents']
205 if request.PUT.has_key('message'):
206 msg = u"$USER$ " + request.PUT['message']
208 msg = u"$AUTO$ XML content update."
210 current = lib.document(docid, request.user.username)
211 orig = lib.document_for_rev(revision)
214 return response.EntityConflict().django_response({
215 "reason": "out-of-date",
216 "provided_revision": orig.revision,
217 "latest_revision": current.revision })
219 ndoc = current.quickwrite('xml', data, msg)
222 # return the new revision number
226 "previous_revision": current.revision,
227 "updated_revision": ndoc.revision
232 except RevisionNotFound, e:
233 return response.EntityNotFound().django_response(e)
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 doc = lib.document(docid)
250 doc = 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)
284 # return the new revision number
288 "previous_revision": current.revision,
289 "updated_revision": ndoc.revision
294 except RevisionNotFound:
295 return response.EntityNotFound().django_response()
297 class MergeHandler(BaseHandler):
298 allowed_methods = ('POST',)
300 @validate_form(forms.MergeRequestForm, 'POST')
302 def create(self, request, form, docid, lib):
303 """Create a new document revision from the information provided by user"""
305 target_rev = form.cleaned_data['target_revision']
307 doc = lib.document(docid)
308 udoc = doc.take(request.user.username)
310 if target_rev == 'latest':
311 target_rev = udoc.revision
313 if str(udoc.revision) != target_rev:
314 # user think doesn't know he has an old version
317 # Updating is teorericly ok, but we need would
318 # have to force a refresh. Sharing may be not safe,
319 # 'cause it doesn't always result in update.
321 # In other words, we can't lie about the resource's state
322 # So we should just yield and 'out-of-date' conflict
323 # and let the client ask again with updated info.
325 # NOTE: this could result in a race condition, when there
326 # are 2 instances of the same user editing the same document.
327 # Instance "A" trying to update, and instance "B" always changing
328 # the document right before "A". The anwser to this problem is
329 # for the "A" to request a merge from 'latest' and then
330 # check the parent revisions in response, if he actually
331 # merge from where he thinks he should. If not, the client SHOULD
332 # update his internal state.
333 return response.EntityConflict().django_response({
334 "reason": "out-of-date",
335 "provided": target_rev,
336 "latest": udoc.revision })
338 if not request.user.has_perm('explorer.book.can_share'):
339 # User is not permitted to make a merge, right away
340 # So we instead create a pull request in the database
342 comitter=request.user,
344 source_revision = str(udoc.revision),
346 comment = form.cleaned_data['comment'] or '$AUTO$ Document shared.'
350 return response.RequestAccepted().django_response(\
351 ticket_status=prq.status, \
352 ticket_uri=reverse("pullrequest_view", args=[prq.id]) )
354 if form.cleanded_data['type'] == 'update':
355 # update is always performed from the file branch
357 success, changed = udoc.update(request.user.username)
359 if form.cleanded_data['type'] == 'share':
360 success, changed = udoc.share(form.cleaned_data['comment'])
363 return response.EntityConflict().django_response()
366 return response.SuccessNoContent().django_response()
368 new_udoc = udoc.latest()
370 return response.SuccessAllOk().django_response({
372 "parent_user_resivion": udoc.revision,
373 "parent_revision": doc.revision,
374 "revision": udoc.revision,