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                 
75                 doc = lib.document_create(docid)
76                 # document created, but no content yet
77
78                 try:
79                     doc = doc.quickwrite('xml', data.encode('utf-8'),
80                         '$AUTO$ XML data uploaded.', user=request.user.username)
81                 except Exception,e:
82                     # rollback branch creation
83                     lib._rollback()
84                     raise LibraryException("Exception occured:" + repr(e))
85
86                 url = reverse('document_view', args=[doc.id])
87
88                 return response.EntityCreated().django_response(\
89                     body = {
90                         'url': url,
91                         'name': doc.id,
92                         'revision': doc.revision },
93                     url = url )            
94             finally:
95                 lock.release()
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})
103
104 #
105 # Document Handlers
106 #
107 class BasicDocumentHandler(AnonymousBaseHandler):
108     allowed_methods = ('GET',)
109
110     @hglibrary
111     def read(self, request, docid, lib):
112         try:    
113             doc = lib.document(docid)
114         except RevisionNotFound:
115             return rc.NOT_FOUND
116
117         result = {
118             'name': doc.id,
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,
123         }
124
125         return result
126
127 #
128 # Document Meta Data
129 #
130 class DocumentHandler(BaseHandler):
131     allowed_methods = ('GET', 'PUT')
132     anonymous = BasicDocumentHandler
133
134     @hglibrary
135     def read(self, request, docid, lib):
136         """Read document's meta data"""       
137         try:
138             doc = lib.document(docid)
139             udoc = doc.take(request.user.username)
140         except RevisionNotFound:
141             return request.EnityNotFound().django_response()
142
143         # is_shared = udoc.ancestorof(doc)
144         # is_uptodate = is_shared or shared.ancestorof(document)
145
146         result = {
147             'name': udoc.id,
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,            
153         }       
154
155         return result
156
157     @hglibrary
158     def update(self, request, docid, lib):
159         """Update information about the document, like display not"""
160         return
161 #
162 #
163 #
164
165 class DocumentHTMLHandler(BaseHandler):
166     allowed_methods = ('GET', 'PUT')
167
168     @hglibrary
169     def read(self, request, docid, revision, lib):
170         """Read document as html text"""
171         try:
172             if revision == 'latest':
173                 document = lib.document(docid)
174             else:
175                 document = lib.document_for_rev(revision)
176
177             return librarian.html.transform(document.data('xml'))
178         except RevisionNotFound:
179             return response.EntityNotFound().django_response()
180
181 #
182 # Document Text View
183 #
184 class DocumentTextHandler(BaseHandler):
185     allowed_methods = ('GET', 'PUT')
186
187     @hglibrary
188     def read(self, request, docid, revision, lib):
189         """Read document as raw text"""               
190         try:
191             if revision == 'latest':
192                 document = lib.document(docid)
193             else:
194                 document = lib.document_for_rev(revision)
195             
196             # TODO: some finer-grained access control
197             return document.data('xml')
198         except RevisionNotFound:
199             return response.EntityNotFound().django_response()
200
201     @hglibrary
202     def update(self, request, docid, revision, lib):
203         try:
204             data = request.PUT['contents']            
205
206             if request.PUT.has_key('message'):
207                 msg = u"$USER$ " + request.PUT['message']
208             else:
209                 msg = u"$AUTO$ XML content update."
210
211             current = lib.document(docid, request.user.username)
212             orig = lib.document_for_rev(revision)
213
214             if current != orig:
215                 return response.EntityConflict().django_response({
216                         "reason": "out-of-date",
217                         "provided_revision": orig.revision,
218                         "latest_revision": current.revision })
219
220             ndoc = current.quickwrite('xml', data, msg)
221
222             try:
223                 # return the new revision number
224                 return {
225                     "document": ndoc.id,
226                     "subview": "xml",
227                     "previous_revision": current.revision,
228                     "updated_revision": ndoc.revision
229                 }
230             except Exception, e:
231                 lib._rollback()
232                 raise e        
233         except RevisionNotFound, e:
234             return response.EntityNotFound().django_response(e)
235
236 #
237 # Dublin Core handlers
238 #
239 # @requires librarian
240 #
241 class DocumentDublinCoreHandler(BaseHandler):
242     allowed_methods = ('GET', 'PUT')
243
244     @hglibrary
245     def read(self, request, docid, revision, lib):
246         """Read document as raw text"""        
247         try:
248             if revision == 'latest':
249                 doc = lib.document(docid)
250             else:
251                 doc = lib.document_for_rev(revision)
252             
253             bookinfo = dcparser.BookInfo.from_string(doc.data('xml'))
254             return bookinfo.serialize()
255         except RevisionNotFound:
256             return response.EntityNotFound().django_response()
257
258     @hglibrary
259     def update(self, request, docid, revision, lib):
260         try:
261             bi_json = request.PUT['contents']            
262             if request.PUT.has_key('message'):
263                 msg = u"$USER$ " + request.PUT['message']
264             else:
265                 msg = u"$AUTO$ Dublin core update."
266
267             current = lib.document(docid, request.user.username)
268             orig = lib.document_for_rev(revision)
269
270             if current != orig:
271                 return response.EntityConflict().django_response({
272                         "reason": "out-of-date",
273                         "provided": orig.revision,
274                         "latest": current.revision })
275
276             xmldoc = parser.WLDocument.from_string(current.data('xml'))
277             document.book_info = dcparser.BookInfo.from_json(bi_json)
278
279             # zapisz
280             ndoc = current.quickwrite('xml', \
281                 document.serialize().encode('utf-8'),\
282                 message=msg, user=request.user.username)
283
284             try:
285                 # return the new revision number
286                 return {
287                     "document": ndoc.id,
288                     "subview": "dc",
289                     "previous_revision": current.revision,
290                     "updated_revision": ndoc.revision
291                 }
292             except Exception, e:
293                 lib._rollback()
294                 raise e
295         except RevisionNotFound:
296             return response.EntityNotFound().django_response()
297
298 class MergeHandler(BaseHandler):
299     allowed_methods = ('POST',)
300
301     @validate_form(forms.MergeRequestForm, 'POST')
302     @hglibrary
303     def create(self, request, form, docid, lib):
304         """Create a new document revision from the information provided by user"""
305
306         target_rev = form.cleaned_data['target_revision']
307
308         doc = lib.document(docid)
309         udoc = doc.take(request.user.username)
310
311         if target_rev == 'latest':
312             target_rev = udoc.revision
313
314         if str(udoc.revision) != target_rev:
315             # user think doesn't know he has an old version
316             # of his own branch.
317             
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.
321
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.
325
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 })
338
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
342             prq = PullRequest(
343                 comitter=request.user,
344                 document=docid,
345                 source_revision = str(udoc.revision),
346                 status="N",
347                 comment = form.cleaned_data['comment'] or '$AUTO$ Document shared.'
348             )
349
350             prq.save()
351             return response.RequestAccepted().django_response(\
352                 ticket_status=prq.status, \
353                 ticket_uri=reverse("pullrequest_view", args=[prq.id]) )
354
355         if form.cleanded_data['type'] == 'update':
356             # update is always performed from the file branch
357             # to the user branch
358             success, changed = udoc.update(request.user.username)
359
360         if form.cleanded_data['type'] == 'share':
361             success, changed = udoc.share(form.cleaned_data['comment'])
362
363         if not success:
364             return response.EntityConflict().django_response()
365
366         if not changed:
367             return response.SuccessNoContent().django_response()
368
369         new_udoc = udoc.latest()
370
371         return response.SuccessAllOk().django_response({
372             "name": udoc.id,
373             "parent_user_resivion": udoc.revision,
374             "parent_revision": doc.revision,
375             "revision": udoc.revision,
376         })