1 # -*- coding: utf-8 -*-
4 from datetime import date
6 from librarian import html, parser, dcparser, wrap_text
7 from librarian import ParseError, ValidationError
9 from django.conf import settings
10 from django.contrib.auth.decorators import login_required, permission_required
12 from django.core.urlresolvers import reverse
13 from django.http import HttpResponseRedirect, HttpResponse
14 from django.utils import simplejson as json
15 from django.views.generic.simple import direct_to_template
17 from explorer import forms, models
18 from toolbar import models as toolbar_models
21 # Some useful decorators
23 def file_branch(path, user=None):
24 return ('personal_'+user.username + '_' if user is not None else '') \
28 """Open a repository for this view"""
29 def view_with_repo(request, *args, **kwargs):
30 kwargs['repo'] = hg.Repository(settings.REPOSITORY_PATH)
31 return view(request, *args, **kwargs)
35 def ajax_login_required(view):
36 """Similar ro @login_required, but instead of redirect,
37 just return some JSON stuff with error."""
38 def view_with_auth(request, *args, **kwargs):
39 if request.user.is_authenticated():
40 return view(request, *args, **kwargs)
42 return HttpResponse( json.dumps({'result': 'access_denied', 'errors': ['Brak dostępu.']}) );
49 def file_list(request, repo):
51 latest_default = repo.get_branch_tip('default')
52 files = [ f for f in repo.repo[latest_default] if not f.startswith('.')]
53 bookform = forms.BookUploadForm()
55 return direct_to_template(request, 'explorer/file_list.html', extra_context={
56 'files': files, 'bookform': bookform,
59 @permission_required('explorer.can_add_files')
61 def file_upload(request, repo):
63 if request.method == 'POST':
64 form = forms.BookUploadForm(request.POST, request.FILES)
68 f = request.FILES['file']
69 decoded = f.read().decode('utf-8')
70 path = form.cleaned_data['bookname']
72 if form.cleaned_data['autoxml']:
73 decoded = wrap_text(decoded, unicode(date.today()) )
76 repo._add_file(path, decoded.encode('utf-8') )
77 repo._commit(message="File %s uploaded by user %s" % \
78 (path, request.user.username), user=request.user.username)
80 repo.in_branch(upload_action, 'default')
82 # if everything is ok, redirect to the editor
83 return HttpResponseRedirect( reverse('editor_view',
84 kwargs={'path': path}) )
86 except hg.RepositoryException, e:
87 other_errors.append(u'Błąd repozytorium: ' + unicode(e) )
88 #except UnicodeDecodeError, e:
89 # other_errors.append(u'Niepoprawne kodowanie pliku: ' + e.reason \
90 # + u'. Żądane kodowanie: ' + e.encoding)
94 form = forms.BookUploadForm()
95 return direct_to_template(request, 'explorer/file_upload.html',\
96 extra_context = {'form' : form, 'other_errors': other_errors})
104 def file_xml(request, repo, path):
105 if request.method == 'POST':
108 form = forms.BookForm(request.POST)
110 print 'Saving whole text.', request.user.username
112 # encode it back to UTF-8, so we can put it into repo
113 encoded_data = form.cleaned_data['content'].encode('utf-8')
116 repo._add_file(path, encoded_data)
117 repo._commit(message=(form.cleaned_data['commit_message'] or 'Lokalny zapis platformy.'),\
118 user=request.user.username)
121 # wczytaj dokument z ciągu znaków -> weryfikacja
122 document = parser.WLDocument.from_string(form.cleaned_data['content'])
123 except (ParseError, ValidationError), e:
124 warnings = [u'Niepoprawny dokument XML: ' + unicode(e.message)]
126 # save to user's branch
127 repo.in_branch(save_action, file_branch(path, request.user) );
128 except UnicodeDecodeError, e:
129 errors = [u'Błąd kodowania danych przed zapisem: ' + unicode(e.message)]
130 except hg.RepositoryException, e:
131 errors = [u'Błąd repozytorium: ' + unicode(e.message)]
134 errors = dict( (field[0], field[1].as_text()) for field in form.errors.iteritems() )
136 return HttpResponse( json.dumps({'result': errors and 'error' or 'ok',
137 'errors': errors, 'warnings': warnings}) );
139 form = forms.BookForm()
140 data = repo.get_file(path, file_branch(path, request.user))
141 form.fields['content'].initial = data
142 return HttpResponse( json.dumps({'result': 'ok', 'content': data}) )
146 def file_update_local(request, path, repo):
150 wlock = repo.write_lock()
152 tipA = repo.get_branch_tip('default')
153 tipB = repo.get_branch_tip( file_branch(path, request.user) )
155 nodeA = repo.getnode(tipA)
156 nodeB = repo.getnode(tipB)
158 # do some wild checks - see file_commit() for more info
159 if (repo.common_ancestor(tipA, tipB) == nodeA) \
160 or (nodeB in nodeA.parents()):
161 result = 'nothing-to-do'
164 repo.merge_revisions(tipB, tipA, \
165 request.user.username, 'Personal branch update.')
167 except hg.UncleanMerge, e:
169 result = 'fatal-error'
170 except hg.RepositoryException, e:
172 result = 'fatal-error'
177 raise Exception("Ouch, this shouldn't happen!")
179 return HttpResponse( json.dumps({'result': result, 'errors': errors}) );
183 def file_commit(request, path, repo):
186 local_modified = False
187 if request.method == 'POST':
188 form = forms.MergeForm(request.POST)
191 wlock = repo.write_lock()
193 tipA = repo.get_branch_tip('default')
194 tipB = repo.get_branch_tip( file_branch(path, request.user) )
196 nodeA = repo.getnode(tipA)
197 nodeB = repo.getnode(tipB)
199 print repr(nodeA), repr(nodeB), repo.common_ancestor(tipA, tipB), repo.common_ancestor(tipB, tipA)
201 if repo.common_ancestor(tipB, tipA) == nodeA:
205 # * <- can also be here!
210 # The local branch has been recently updated,
211 # so we don't need to update yet again, but we need to
212 # merge down to default branch, even if there was
213 # no commit's since last update
214 repo.merge_revisions(tipA, tipB, \
215 request.user.username, form.cleaned_data['message'])
217 elif any( p.branch()==nodeB.branch() for p in nodeA.parents()):
225 # Default has no changes, to update from this branch
226 # since the last merge of local to default.
227 if nodeB not in nodeA.parents():
228 repo.merge_revisions(tipA, tipB, \
229 request.user.username, form.cleaned_data['message'])
232 result = 'nothing-to-do'
233 elif repo.common_ancestor(tipA, tipB) == nodeB:
237 # * <- this case overlaps with previos one
243 # There was a recent merge to the defaul branch and
244 # no changes to local branch recently.
246 # Use the fact, that user is prepared to see changes, to
247 # update his branch if there are any
248 if nodeB not in nodeA.parents():
249 repo.merge_revisions(tipB, tipA, \
250 request.user.username, 'Personal branch update during merge.')
251 local_modified = True
254 result = 'nothing-to-do'
256 # both branches have changes made to them, so
258 repo.merge_revisions(tipB, tipA, \
259 request.user.username, 'Personal branch update during merge.')
261 local_modified = True
264 tipB = repo.get_branch_tip( file_branch(path, request.user) )
266 # and merge back to the default
267 repo.merge_revisions(tipA, tipB, \
268 request.user.username, form.cleaned_data['message'])
270 except hg.UncleanMerge, e:
272 result = 'fatal-error'
273 except hg.RepositoryException, e:
275 result = 'fatal-error'
280 errors = [ form.errors['message'].as_text() ]
282 result = 'fatal-error'
284 return HttpResponse( json.dumps({'result': result, 'errors': errors, 'localmodified': local_modified}) );
286 return HttpResponse( json.dumps({'result': 'fatal-error', 'errors': ['No data posted']}) )
291 def file_dc(request, path, repo):
294 if request.method == 'POST':
295 form = forms.DublinCoreForm(request.POST)
300 file_contents = repo._get_file(path)
302 # wczytaj dokument z repozytorium
303 document = parser.WLDocument.from_string(file_contents)
304 document.book_info.update(form.cleaned_data)
307 repo._write_file(path, document.serialize().encode('utf-8'))
309 message=(form.cleaned_data['commit_message'] or 'Lokalny zapis platformy.'), \
310 user=request.user.username )
313 repo.in_branch(save_action, file_branch(path, request.user) )
314 except UnicodeEncodeError, e:
315 errors = ['Bład wewnętrzny: nie można zakodować pliku do utf-8']
316 except (ParseError, ValidationError), e:
320 errors = ["Pole '%s': %s\n" % (field[0], field[1].as_text()) for field in form.errors.iteritems()]
322 return HttpResponse( json.dumps({'result': errors and 'error' or 'ok', 'errors': errors}) );
324 # this is unused currently, but may come in handy
328 fulltext = repo.get_file(path, file_branch(path, request.user))
329 bookinfo = dcparser.BookInfo.from_string(fulltext)
330 content = bookinfo.to_dict()
331 except (ParseError, ValidationError), e:
334 return HttpResponse( json.dumps({'result': errors and 'error' or 'ok',
335 'errors': errors, 'content': content }) )
337 # Display the main editor view
341 def display_editor(request, path, repo):
343 # this is the only entry point where we create an autobranch for the user
344 # if it doesn't exists. All other views SHOULD fail.
345 def ensure_branch_exists():
346 parent = repo.get_branch_tip('default')
347 repo._create_branch(file_branch(path, request.user), parent)
350 repo.with_wlock(ensure_branch_exists)
352 return direct_to_template(request, 'explorer/editor.html', extra_context={
354 'panel_list': ['lewy', 'prawy'],
355 'scriptlets': toolbar_models.Scriptlet.objects.all()
358 return direct_to_template(request, 'explorer/nofile.html', \
359 extra_context = { 'path': path })
367 def xmleditor_panel(request, path, repo):
368 text = repo.get_file(path, file_branch(path, request.user))
370 return direct_to_template(request, 'explorer/panels/xmleditor.html', extra_context={
377 def gallery_panel(request, path):
378 return direct_to_template(request, 'explorer/panels/gallery.html', extra_context={
380 'form': forms.ImageFoldersForm(),
385 def htmleditor_panel(request, path, repo):
386 user_branch = file_branch(path, request.user)
388 return direct_to_template(request, 'explorer/panels/htmleditor.html', extra_context={
390 'html': html.transform(repo.get_file(path, user_branch), is_file=False),
392 except (ParseError, ValidationError), e:
393 return direct_to_template(request, 'explorer/panels/parse_error.html', extra_context={
394 'fpath': path, 'exception_type': type(e).__name__, 'exception': e, 'panel_name': 'Edytor HTML'})
398 def dceditor_panel(request, path, repo):
399 user_branch = file_branch(path, request.user)
402 doc_text = repo.get_file(path, user_branch)
403 document = parser.WLDocument.from_string(doc_text)
404 form = forms.DublinCoreForm(info=document.book_info)
405 return direct_to_template(request, 'explorer/panels/dceditor.html', extra_context={
409 except (ParseError, ValidationError), e:
410 return direct_to_template(request, 'explorer/panels/parse_error.html', extra_context={
411 'fpath': path, 'exception_type': type(e).__name__, 'exception': e,
412 'panel_name': 'Edytor DublinCore'})
418 def folder_images(request, folder):
419 return direct_to_template(request, 'explorer/folder_images.html', extra_context={
420 'images': models.get_images_from_folder(folder),
424 def _add_references(message, issues):
425 return message + " - " + ", ".join(map(lambda issue: "Refs #%d" % issue['id'], issues))
427 def _get_issues_for_file(path):
428 if not path.endswith('.xml'):
429 raise ValueError('Path must end with .xml')
435 uf = urllib2.urlopen(settings.REDMINE_URL + 'publications/issues/%s.json' % book_id)
436 return json.loads(uf.read())
437 except urllib2.HTTPError:
446 def pull_requests(request):
447 return direct_to_template(request, 'manager/pull_request.html', extra_context = {
448 'objects': models.PullRequest.objects.all()} )