Fixed text updating for UTF-8 strings.
[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=[udoc.id,udoc.revision]),
147             'text_url': reverse('doctext_view', args=[udoc.id,udoc.revision]),
148             'dc_url': reverse('docdc_view', args=[udoc.id,udoc.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 = current.quickwrite('xml', data, msg)
219
220             try:
221                 # return the new revision number
222                 return {
223                     "document": ndoc.id,
224                     "subview": "xml",
225                     "previous_revision": current.revision,
226                     "updated_revision": ndoc.revision
227                 }
228             except Exception, e:
229                 lib.rollback()
230                 raise e
231         
232         except (RevisionNotFound, KeyError):
233             return response.EntityNotFound().django_response()
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                 document = lib.document(docid)
249             else:
250                 document = 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             return {
284                 "document": ndoc.id,
285                 "subview": "xml",
286                 "previous_revision": prev,
287                 "updated_revision": ndoc.revision
288             }
289         except (RevisionNotFound, KeyError):
290             return response.EntityNotFound().django_response()
291
292
293 class MergeHandler(BaseHandler):
294     allowed_methods = ('POST',)
295
296     @validate_form(forms.MergeRequestForm)
297     @hglibrary
298     def create(self, request, form, docid, lib):
299         """Create a new document revision from the information provided by user"""
300
301         target_rev = form.cleaned_data['target_revision']
302
303         doc = lib.document(docid)
304         udoc = doc.take(request.user.username)
305
306         if target_rev == 'latest':
307             target_rev = udoc.revision
308
309         if udoc.revision != target_rev:
310             # user think doesn't know he has an old version
311             # of his own branch.
312             
313             # Updating is teorericly ok, but we need would
314             # have to force a refresh. Sharing may be not safe,
315             # 'cause it doesn't always result in update.
316
317             # In other words, we can't lie about the resource's state
318             # So we should just yield and 'out-of-date' conflict
319             # and let the client ask again with updated info.
320
321             # NOTE: this could result in a race condition, when there
322             # are 2 instances of the same user editing the same document.
323             # Instance "A" trying to update, and instance "B" always changing
324             # the document right before "A". The anwser to this problem is
325             # for the "A" to request a merge from 'latest' and then
326             # check the parent revisions in response, if he actually
327             # merge from where he thinks he should. If not, the client SHOULD
328             # update his internal state.
329             return response.EntityConflict().django_response({
330                     "reason": "out-of-date",
331                     "provided": target_revision,
332                     "latest": udoc.revision })
333
334         if not request.user.has_permission('explorer.pull_request.can_add'):
335             # User is not permitted to make a merge, right away
336             # So we instead create a pull request in the database
337             prq = PullRequest(
338                 commiter=request.uset.username,
339                 document=docid,
340                 source_revision = udoc.revision,
341                 status="N",
342                 comment = form.cleaned_data['comment']
343             )
344
345             prq.save()
346             return response.RequestAccepted()
347
348         if form.cleanded_data['type'] == 'update':
349             # update is always performed from the file branch
350             # to the user branch
351             success, changed = udoc.update(request.user.username)
352
353         if form.cleanded_data['type'] == 'share':
354             success, changed = udoc.share(form.cleaned_data['comment'])
355
356         if not success:
357             return response.EntityConflict().django_response()
358
359         if not changed:
360             return response.SuccessNoContent().django_response()
361
362         new_udoc = udoc.latest()
363
364         return response.SuccessAllOk().django_response({
365             "name": udoc.id,
366             "parent_user_resivion": udoc.revision,
367             "parent_revision": doc.revision,
368             "revision": udoc.revision,
369         })