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