Merge branch 'master' into view-refactor
[redakcja.git] / apps / api / handlers / library_handlers.py
1 # -*- encoding: utf-8 -*-
2
3 __author__= "Ɓukasz Rekucki"
4 __date__ = "$2009-09-25 15:49:50$"
5 __doc__ = "Module documentation."
6
7 from piston.handler import BaseHandler, AnonymousBaseHandler
8
9
10 import librarian
11 import librarian.html
12 import api.forms as forms
13 from datetime import date
14
15 from django.core.urlresolvers import reverse
16
17 from wlrepo import RevisionNotFound, LibraryException, DocumentAlreadyExists
18 from librarian import dcparser
19
20 import api.response as response
21 from api.utils import validate_form, hglibrary
22
23 from explorer.models import PullRequest
24
25 #
26 # Document List Handlers
27 #
28 class BasicLibraryHandler(AnonymousBaseHandler):
29     allowed_methods = ('GET',)
30
31     @hglibrary
32     def read(self, request, lib):
33         """Return the list of documents."""       
34         document_list = [{
35             'url': reverse('document_view', args=[docid]),
36             'name': docid } for docid in lib.documents() ]
37
38         return {'documents' : document_list}
39         
40
41 class LibraryHandler(BaseHandler):
42     allowed_methods = ('GET', 'POST')
43     anonymous = BasicLibraryHandler
44
45     @hglibrary
46     def read(self, request, lib):
47         """Return the list of documents."""
48
49         document_list = [{
50             'url': reverse('document_view', args=[docid]),
51             'name': docid } for docid in lib.documents() ]
52
53         return {'documents' : document_list }        
54
55     @validate_form(forms.DocumentUploadForm, 'POST')
56     @hglibrary
57     def create(self, request, form, lib):
58         """Create a new document."""       
59
60         if form.cleaned_data['ocr_data']:
61             data = form.cleaned_data['ocr_data']
62         else:            
63             data = request.FILES['ocr_file'].read().decode('utf-8')
64
65         if form.cleaned_data['generate_dc']:
66             data = librarian.wrap_text(data, unicode(date.today()))
67
68         docid = form.cleaned_data['bookname']
69
70         try:
71             lock = lib.lock()            
72             try:
73                 print "DOCID", docid                
74                 doc = lib.document_create(docid)
75                 # document created, but no content yet
76
77                 try:
78                     doc = doc.quickwrite('xml', data.encode('utf-8'),
79                         '$AUTO$ XML data uploaded.', user=request.user.username)
80                 except Exception,e:
81                     # rollback branch creation
82                     lib._rollback()
83                     raise LibraryException("Exception occured:" + repr(e))
84
85                 url = reverse('document_view', args=[doc.id])
86
87                 return response.EntityCreated().django_response(\
88                     body = {
89                         'url': url,
90                         'name': doc.id,
91                         'revision': doc.revision },
92                     url = url )            
93             finally:
94                 lock.release()
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})
102
103 #
104 # Document Handlers
105 #
106 class BasicDocumentHandler(AnonymousBaseHandler):
107     allowed_methods = ('GET',)
108
109     @hglibrary
110     def read(self, request, docid, lib):
111         try:    
112             doc = lib.document(docid)
113         except RevisionNotFound:
114             return rc.NOT_FOUND
115
116         result = {
117             'name': doc.id,
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,
122         }
123
124         return result
125
126 #
127 # Document Meta Data
128 #
129 class DocumentHandler(BaseHandler):
130     allowed_methods = ('GET', 'PUT')
131     anonymous = BasicDocumentHandler
132
133     @hglibrary
134     def read(self, request, docid, lib):
135         """Read document's meta data"""       
136         try:
137             doc = lib.document(docid)
138             udoc = doc.take(request.user.username)
139         except RevisionNotFound:
140             return request.EnityNotFound().django_response()
141
142         # is_shared = udoc.ancestorof(doc)
143         # is_uptodate = is_shared or shared.ancestorof(document)
144
145         result = {
146             'name': udoc.id,
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,            
152         }       
153
154         return result
155
156     @hglibrary
157     def update(self, request, docid, lib):
158         """Update information about the document, like display not"""
159         return
160 #
161 #
162 #
163
164 class DocumentHTMLHandler(BaseHandler):
165     allowed_methods = ('GET', 'PUT')
166
167     @hglibrary
168     def read(self, request, docid, revision, lib):
169         """Read document as html text"""
170         try:
171             if revision == 'latest':
172                 document = lib.document(docid)
173             else:
174                 document = lib.document_for_rev(revision)
175
176             return librarian.html.transform(document.data('xml'), is_file=False)
177         except RevisionNotFound:
178             return response.EntityNotFound().django_response()
179
180 #
181 # Document Text View
182 #
183 class DocumentTextHandler(BaseHandler):
184     allowed_methods = ('GET', 'PUT')
185
186     @hglibrary
187     def read(self, request, docid, revision, lib):
188         """Read document as raw text"""               
189         try:
190             if revision == 'latest':
191                 document = lib.document(docid)
192             else:
193                 document = lib.document_for_rev(revision)
194             
195             # TODO: some finer-grained access control
196             return document.data('xml')
197         except RevisionNotFound:
198             return response.EntityNotFound().django_response()
199
200     @hglibrary
201     def update(self, request, docid, revision, lib):
202         try:
203             data = request.PUT['contents']            
204
205             if request.PUT.has_key('message'):
206                 msg = u"$USER$ " + request.PUT['message']
207             else:
208                 msg = u"$AUTO$ XML content update."
209
210             current = lib.document(docid, request.user.username)
211             orig = lib.document_for_rev(revision)
212
213             if current != orig:
214                 return response.EntityConflict().django_response({
215                         "reason": "out-of-date",
216                         "provided_revision": orig.revision,
217                         "latest_revision": current.revision })
218
219             ndoc = current.quickwrite('xml', data, msg)
220
221             try:
222                 # return the new revision number
223                 return {
224                     "document": ndoc.id,
225                     "subview": "xml",
226                     "previous_revision": current.revision,
227                     "updated_revision": ndoc.revision
228                 }
229             except Exception, e:
230                 lib._rollback()
231                 raise e        
232         except RevisionNotFound, e:
233             return response.EntityNotFound().django_response(e)
234
235 #
236 # Dublin Core handlers
237 #
238 # @requires librarian
239 #
240 class DocumentDublinCoreHandler(BaseHandler):
241     allowed_methods = ('GET', 'PUT')
242
243     @hglibrary
244     def read(self, request, docid, revision, lib):
245         """Read document as raw text"""        
246         try:
247             if revision == 'latest':
248                 doc = lib.document(docid)
249             else:
250                 doc = lib.document_for_rev(revision)
251             
252             bookinfo = dcparser.BookInfo.from_string(doc.data('xml'))
253             return bookinfo.serialize()
254         except RevisionNotFound:
255             return response.EntityNotFound().django_response()
256
257     @hglibrary
258     def update(self, request, docid, revision, lib):
259         try:
260             bi_json = request.PUT['contents']            
261             if request.PUT.has_key('message'):
262                 msg = u"$USER$ " + request.PUT['message']
263             else:
264                 msg = u"$AUTO$ Dublin core update."
265
266             current = lib.document(docid, request.user.username)
267             orig = lib.document_for_rev(revision)
268
269             if current != orig:
270                 return response.EntityConflict().django_response({
271                         "reason": "out-of-date",
272                         "provided": orig.revision,
273                         "latest": current.revision })
274
275             xmldoc = parser.WLDocument.from_string(current.data('xml'))
276             document.book_info = dcparser.BookInfo.from_json(bi_json)
277
278             # zapisz
279             ndoc = current.quickwrite('xml', \
280                 document.serialize().encode('utf-8'),\
281                 message=msg, user=request.user.username)
282
283             try:
284                 # return the new revision number
285                 return {
286                     "document": ndoc.id,
287                     "subview": "dc",
288                     "previous_revision": current.revision,
289                     "updated_revision": ndoc.revision
290                 }
291             except Exception, e:
292                 lib._rollback()
293                 raise e
294         except RevisionNotFound:
295             return response.EntityNotFound().django_response()
296
297 class MergeHandler(BaseHandler):
298     allowed_methods = ('POST',)
299
300     @validate_form(forms.MergeRequestForm, 'POST')
301     @hglibrary
302     def create(self, request, form, docid, lib):
303         """Create a new document revision from the information provided by user"""
304
305         target_rev = form.cleaned_data['target_revision']
306
307         doc = lib.document(docid)
308         udoc = doc.take(request.user.username)
309
310         if target_rev == 'latest':
311             target_rev = udoc.revision
312
313         if str(udoc.revision) != target_rev:
314             # user think doesn't know he has an old version
315             # of his own branch.
316             
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.
320
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.
324
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 })
337
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
341             prq = PullRequest(
342                 comitter=request.user,
343                 document=docid,
344                 source_revision = str(udoc.revision),
345                 status="N",
346                 comment = form.cleaned_data['comment'] or '$AUTO$ Document shared.'
347             )
348
349             prq.save()
350             return response.RequestAccepted().django_response(\
351                 ticket_status=prq.status, \
352                 ticket_uri=reverse("pullrequest_view", args=[prq.id]) )
353
354         if form.cleanded_data['type'] == 'update':
355             # update is always performed from the file branch
356             # to the user branch
357             success, changed = udoc.update(request.user.username)
358
359         if form.cleanded_data['type'] == 'share':
360             success, changed = udoc.share(form.cleaned_data['comment'])
361
362         if not success:
363             return response.EntityConflict().django_response()
364
365         if not changed:
366             return response.SuccessNoContent().django_response()
367
368         new_udoc = udoc.latest()
369
370         return response.SuccessAllOk().django_response({
371             "name": udoc.id,
372             "parent_user_resivion": udoc.revision,
373             "parent_revision": doc.revision,
374             "revision": udoc.revision,
375         })