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=[doc.id,doc.revision]),
147 'text_url': reverse('doctext_view', args=[doc.id,doc.revision]),
148 'dc_url': reverse('docdc_view', args=[doc.id,doc.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 = doc.quickwrite('xml', data, msg)
220 # return the new revision number
224 "previous_revision": prev,
225 "updated_revision": ndoc.revision
228 except (RevisionNotFound, KeyError):
229 return response.EntityNotFound().django_response()
232 # Dublin Core handlers
234 # @requires librarian
236 class DocumentDublinCoreHandler(BaseHandler):
237 allowed_methods = ('GET', 'PUT')
240 def read(self, request, docid, revision, lib):
241 """Read document as raw text"""
243 if revision == 'latest':
244 document = lib.document(docid)
246 document = lib.document_for_rev(revision)
248 bookinfo = dcparser.BookInfo.from_string(doc.data('xml'))
249 return bookinfo.serialize()
250 except RevisionNotFound:
251 return response.EntityNotFound().django_response()
254 def update(self, request, docid, revision, lib):
256 bi_json = request.PUT['contents']
257 if request.PUT.has_key('message'):
258 msg = u"$USER$ " + request.PUT['message']
260 msg = u"$AUTO$ Dublin core update."
262 current = lib.document(docid, request.user.username)
263 orig = lib.document_for_rev(revision)
266 return response.EntityConflict().django_response({
267 "reason": "out-of-date",
268 "provided": orig.revision,
269 "latest": current.revision })
271 xmldoc = parser.WLDocument.from_string(current.data('xml'))
272 document.book_info = dcparser.BookInfo.from_json(bi_json)
275 ndoc = current.quickwrite('xml', \
276 document.serialize().encode('utf-8'),\
277 message=msg, user=request.user.username)
282 "previous_revision": prev,
283 "updated_revision": ndoc.revision
285 except (RevisionNotFound, KeyError):
286 return response.EntityNotFound().django_response()
289 class MergeHandler(BaseHandler):
290 allowed_methods = ('POST',)
292 @validate_form(forms.MergeRequestForm)
294 def create(self, request, form, docid, lib):
295 """Create a new document revision from the information provided by user"""
297 target_rev = form.cleaned_data['target_revision']
299 doc = lib.document(docid)
300 udoc = doc.take(request.user.username)
302 if target_rev == 'latest':
303 target_rev = udoc.revision
305 if udoc.revision != target_rev:
306 # user think doesn't know he has an old version
309 # Updating is teorericly ok, but we need would
310 # have to force a refresh. Sharing may be not safe,
311 # 'cause it doesn't always result in update.
313 # In other words, we can't lie about the resource's state
314 # So we should just yield and 'out-of-date' conflict
315 # and let the client ask again with updated info.
317 # NOTE: this could result in a race condition, when there
318 # are 2 instances of the same user editing the same document.
319 # Instance "A" trying to update, and instance "B" always changing
320 # the document right before "A". The anwser to this problem is
321 # for the "A" to request a merge from 'latest' and then
322 # check the parent revisions in response, if he actually
323 # merge from where he thinks he should. If not, the client SHOULD
324 # update his internal state.
325 return response.EntityConflict().django_response({
326 "reason": "out-of-date",
327 "provided": target_revision,
328 "latest": udoc.revision })
330 if not request.user.has_permission('explorer.pull_request.can_add'):
331 # User is not permitted to make a merge, right away
332 # So we instead create a pull request in the database
334 commiter=request.uset.username,
336 source_revision = udoc.revision,
338 comment = form.cleaned_data['comment']
342 return response.RequestAccepted()
344 if form.cleanded_data['type'] == 'update':
345 # update is always performed from the file branch
347 success, changed = udoc.update(request.user.username)
349 if form.cleanded_data['type'] == 'share':
350 success, changed = udoc.share(form.cleaned_data['comment'])
353 return response.EntityConflict().django_response()
356 return response.SuccessNoContent().django_response()
358 new_udoc = udoc.latest()
360 return response.SuccessAllOk().django_response({
362 "parent_user_resivion": udoc.revision,
363 "parent_revision": doc.revision,
364 "revision": udoc.revision,