|
1 from cm.activity import register_activity |
|
2 from cm.client import jsonize, get_filter_datas, edit_comment, remove_comment, \ |
|
3 add_comment, RequestComplexEncoder, comments_thread |
|
4 from cm.cm_settings import NEW_TEXT_VERSION_ON_EDIT |
|
5 from cm.diff import text_diff as _text_diff, text_history as inner_text_history, \ |
|
6 get_colors |
|
7 from cm.exception import UnauthorizedException |
|
8 from cm.message import * |
|
9 from cm.models import * |
|
10 from cm.models_base import generate_key |
|
11 from cm.security import get_texts_with_perm, has_perm, get_viewable_comments, \ |
|
12 has_perm_on_text |
|
13 from cm.utils import get_among, get_among, get_int |
|
14 from cm.utils.comment_positioning import compute_new_comment_positions, \ |
|
15 insert_comment_markers |
|
16 from cm.utils.html import cleanup_textarea |
|
17 from cm.utils.spannifier import spannify |
|
18 from cm.views import get_keys_from_dict, get_text_by_keys_or_404, redirect |
|
19 from cm.views.export import content_export2 |
|
20 from cm.views.user import AnonUserRoleForm, cm_login |
|
21 from difflib import unified_diff |
|
22 from django import forms |
|
23 from django.conf import settings |
|
24 from django.contrib.auth import login as django_login |
|
25 from django.contrib.auth.forms import AuthenticationForm |
|
26 from django.contrib.auth.models import User |
|
27 from django.core.urlresolvers import reverse |
|
28 from django.db.models import Q |
|
29 from django.forms import ModelForm |
|
30 from django.forms.models import BaseModelFormSet, modelformset_factory |
|
31 from django.http import HttpResponse, HttpResponseRedirect, Http404 |
|
32 from django.shortcuts import render_to_response |
|
33 from django.template import RequestContext |
|
34 from django.template.loader import render_to_string |
|
35 from django.utils.translation import ugettext as _, ugettext_lazy |
|
36 from django.views.generic.list_detail import object_list |
|
37 import difflib |
|
38 import logging |
|
39 import mimetypes |
|
40 import simplejson |
|
41 import sys |
|
42 |
|
43 |
|
44 def get_text_and_admin(key, adminkey, assert_admin = False): |
|
45 """ |
|
46 assert_admin => redirect to unauthorized if not admin |
|
47 """ |
|
48 admin = False |
|
49 if adminkey: |
|
50 text = Text.objects.get(key = key, adminkey = adminkey) |
|
51 if text: |
|
52 admin = True |
|
53 else: |
|
54 text = Text.objects.get(key=key) |
|
55 if assert_admin and not admin: |
|
56 raise UnauthorizedException('Is not admin') |
|
57 return text, admin |
|
58 |
|
59 |
|
60 |
|
61 ACTIVITY_PAGINATION = 10 |
|
62 RECENT_TEXT_NB = 5 |
|
63 RECENT_COMMENT_NB = RECENT_TEXT_NB |
|
64 |
|
65 MODERATE_NB = 5 |
|
66 |
|
67 def dashboard(request): |
|
68 request.session.set_test_cookie() |
|
69 if request.user.is_authenticated(): |
|
70 act_view = { |
|
71 'view_texts' : get_int(request.GET,'view_texts',1), |
|
72 'view_comments' : get_int(request.GET,'view_comments',1), |
|
73 'view_users' : get_int(request.GET,'view_users',1), |
|
74 } |
|
75 |
|
76 paginate_by = get_int(request.GET,'paginate',ACTIVITY_PAGINATION) |
|
77 |
|
78 # texts with can_view_unapproved_comment perms |
|
79 moderator_texts = get_texts_with_perm(request, 'can_view_unapproved_comment') |
|
80 viewer_texts = get_texts_with_perm(request, 'can_view_approved_comment') |
|
81 all_texts_ids = [t.id for t in moderator_texts] + [t.id for t in viewer_texts] |
|
82 |
|
83 span = get_among(request.GET,'span',('day','month','week',),'week') |
|
84 template_dict = { |
|
85 'span' : span, |
|
86 'last_texts' : get_texts_with_perm(request, 'can_view_text').order_by('-modified')[:RECENT_TEXT_NB], |
|
87 'last_comments' : Comment.objects.filter(text_version__text__in=all_texts_ids).order_by('-created')[:RECENT_COMMENT_NB],# TODO: useful? |
|
88 #'last_users' : User.objects.all().order_by('-date_joined')[:5], |
|
89 } |
|
90 template_dict.update(act_view) |
|
91 |
|
92 all_activities = { |
|
93 'view_comments' : ['comment_created','comment_removed'], |
|
94 'view_users' : ['user_created', 'user_activated', 'user_suspended','user_enabled',], |
|
95 'view_texts' : ['text_created','text_removed', 'text_edited', 'text_edited_new_version'], |
|
96 } |
|
97 |
|
98 selected_activities = [] |
|
99 [selected_activities.extend(all_activities[k]) for k in act_view.keys() if act_view[k]] |
|
100 |
|
101 activities = Activity.objects.filter(type__in = selected_activities) |
|
102 if not has_perm(request,'can_manage_workspace'): |
|
103 texts = get_texts_with_perm(request, 'can_view_text') |
|
104 activities = activities.filter(Q(text__in=texts)) |
|
105 |
|
106 comments = [] |
|
107 [comments.extend(get_viewable_comments(request, t.last_text_version.comment_set.all(), t)) for t in texts] |
|
108 |
|
109 activities = activities.filter(Q(comment__in=comments) | Q(comment=None) ) |
|
110 template_dict['to_mod_profiles'] = [] |
|
111 else: |
|
112 template_dict['to_mod_profiles'] = UserProfile.objects.filter(user__is_active=False).filter(is_suspended=True).order_by('-user__date_joined')[:MODERATE_NB] |
|
113 template_dict['to_mod_comments'] = Comment.objects.filter(state='pending').filter(text_version__text__in=moderator_texts).order_by('-modified')[:MODERATE_NB-len(template_dict['to_mod_profiles'])] |
|
114 |
|
115 activities = activities.order_by('-created') |
|
116 return object_list(request, activities, |
|
117 template_name = 'site/dashboard.html', |
|
118 paginate_by = paginate_by, |
|
119 extra_context = template_dict, |
|
120 ) |
|
121 |
|
122 else: |
|
123 if request.method == 'POST': |
|
124 form = AuthenticationForm(request, request.POST) |
|
125 if form.is_valid(): |
|
126 user = form.get_user() |
|
127 user.backend = 'django.contrib.auth.backends.ModelBackend' |
|
128 cm_login(request, user) |
|
129 display_message(request, _(u"You're logged in!")) |
|
130 return HttpResponseRedirect(reverse('index')) |
|
131 else: |
|
132 form = AuthenticationForm() |
|
133 |
|
134 |
|
135 public_texts = get_texts_with_perm(request, 'can_view_text').order_by('-modified') |
|
136 |
|
137 template_dict = { |
|
138 'form' : form, |
|
139 'texts' : public_texts, |
|
140 } |
|
141 return render_to_response('site/non_authenticated_index.html', template_dict, context_instance=RequestContext(request)) |
|
142 |
|
143 TEXT_PAGINATION = 10 |
|
144 # security check inside view |
|
145 # TODO: set global access perm |
|
146 def text_list(request): |
|
147 paginate_by = get_int(request.GET,'paginate',TEXT_PAGINATION) |
|
148 |
|
149 order_by = get_among(request.GET,'order',('title','author','modified','-title','-author','-modified'),'-modified') |
|
150 |
|
151 if request.method == 'POST': |
|
152 action = request.POST.get('action',None) |
|
153 text_keys = get_keys_from_dict(request.POST, 'check-').keys() |
|
154 if action == 'delete': |
|
155 for text_key in text_keys: |
|
156 text = Text.objects.get(key=text_key) |
|
157 if has_perm(request, 'can_delete_text', text=text): |
|
158 text.delete() |
|
159 else: |
|
160 raise UnauthorizedException('No perm can_delete_text on comment') |
|
161 display_message(request, _(u'%(nb_texts)i text(s) deleted') %{'nb_texts':len(text_keys)}) |
|
162 return HttpResponseRedirect(reverse('text')) |
|
163 |
|
164 texts = get_texts_with_perm(request, 'can_view_text').order_by(order_by) |
|
165 return object_list(request, texts, |
|
166 template_name = 'site/text_list.html', |
|
167 paginate_by = paginate_by, |
|
168 ) |
|
169 |
|
170 @has_perm_on_text('can_view_text') |
|
171 def text_view(request, key, adminkey=None): |
|
172 |
|
173 text = get_text_by_keys_or_404(key) |
|
174 register_activity(request, "text_view", text=text) |
|
175 |
|
176 text_version = text.get_latest_version() |
|
177 template_dict = { 'text' : text, 'text_version' : text_version, 'title' : text_version.title, 'content' : text_version.get_content()} |
|
178 return render_to_response('site/text_view.html', template_dict, context_instance=RequestContext(request)) |
|
179 |
|
180 @has_perm_on_text('can_delete_text') |
|
181 def text_delete(request, key): |
|
182 text = Text.objects.get(key=key) |
|
183 if request.method != 'POST': |
|
184 raise UnauthorizedException('Unauthorized') |
|
185 display_message(request, _(u'Text %(text_title)s deleted') %{'text_title':text.title}) |
|
186 register_activity(request, "text_removed", text=text) |
|
187 text.delete() |
|
188 return HttpResponse('') # no redirect because this is called by js |
|
189 |
|
190 @has_perm_on_text('can_view_text') # only protected by text_view / comment filtering done in view |
|
191 def text_view_comments(request, key, adminkey=None): |
|
192 text = get_text_by_keys_or_404(key) |
|
193 #TODO: stupid why restrict to latest ? |
|
194 text_version = text.get_latest_version() |
|
195 comments = get_viewable_comments(request, text_version.comment_set.filter(reply_to__isnull=True),text) |
|
196 filter_datas = get_filter_datas(request, text_version, text) |
|
197 |
|
198 get_params = simplejson.dumps(request.GET) |
|
199 |
|
200 wrapped_text_version, _ , _ = spannify(text_version.get_content()) |
|
201 template_dict = {'text' : text, |
|
202 'text_version' : text_version, |
|
203 'title' : text_version.title, # TODO use it ... |
|
204 'get_params' : get_params, |
|
205 'json_comments':jsonize(comments, request), |
|
206 'json_filter_datas':jsonize(filter_datas, request), |
|
207 'content' : wrapped_text_version, |
|
208 'client_date_fmt' : settings.CLIENT_DATE_FMT, |
|
209 } |
|
210 return render_to_response('site/text_view_comments.html', |
|
211 template_dict, |
|
212 context_instance=RequestContext(request)) |
|
213 def client_exchange(request): |
|
214 ret = None |
|
215 function_name = request.POST['fun']# function called from client |
|
216 user = request.user |
|
217 if function_name == 'experiment' : |
|
218 ret = experiment() |
|
219 elif function_name == 'warn' : |
|
220 # TODO: (RBE to RBA) send mail withinfos |
|
221 ret = "warn test" |
|
222 #print request.POST |
|
223 elif request.POST: |
|
224 key = request.POST['key'] |
|
225 |
|
226 text = Text.objects.get(key=key) ; |
|
227 #TODO: stupid why restrict to latest ? |
|
228 text_version = text.get_latest_version() |
|
229 |
|
230 if (text != None) : |
|
231 if function_name in ('editComment', 'addComment', 'removeComment',) : |
|
232 if function_name == 'editComment' : |
|
233 ret = edit_comment(request=request, key=key, comment_key=request.POST['comment_key']) |
|
234 elif function_name == 'addComment' : |
|
235 ret = add_comment(request=request, key=key) |
|
236 elif function_name == 'removeComment' : |
|
237 ret = remove_comment(request=request, key=key, comment_key=request.POST['comment_key']) |
|
238 |
|
239 ret['filterData'] = get_filter_datas(request, text_version, text) |
|
240 #ret['tagCloud'] = get_tagcloud(key) |
|
241 if ret : |
|
242 if type(ret) != HttpResponseRedirect : |
|
243 ret = HttpResponse(simplejson.dumps(ret, cls=RequestComplexEncoder, request=request)) |
|
244 else : |
|
245 ret = HttpResponse(simplejson.dumps({})) |
|
246 ret.status_code = 403 |
|
247 |
|
248 return ret |
|
249 |
|
250 |
|
251 #NOTE : some arguments like : withcolor = "yes" + format = "markdown" are incompatible |
|
252 #http://localhost:8000/text/text_key_1/export/pdf/1/all/1 |
|
253 def text_export(request, key, format, download, whichcomments, withcolor, adminkey=None): |
|
254 text, admin = get_text_and_admin(key, adminkey) |
|
255 text_version = text.get_latest_version() |
|
256 original_content = text_version.content |
|
257 original_format = text_version.format # BD : html or markdown for now ... |
|
258 |
|
259 download_response = download == "1" |
|
260 with_color = withcolor == "1" |
|
261 |
|
262 comments = [] # whichcomments=="none" |
|
263 |
|
264 if whichcomments == "filtered" or whichcomments == "all": |
|
265 #comments = text_version.comment_set.filter(reply_to__isnull=True)# whichcomments=="all" |
|
266 #comments = get_viewable_comments(request, text_version.comment_set.filter(reply_to__isnull=True), text, order_by=('start_wrapper','start_offset','end_wrapper','end_offset'))# whichcomments=="all" |
|
267 comments = get_viewable_comments(request, text_version.comment_set.all(), text, order_by=('start_wrapper','start_offset','end_wrapper','end_offset'))# whichcomments=="all" |
|
268 |
|
269 if whichcomments == "filtered" : |
|
270 if request.method == 'POST' : |
|
271 ll = request.POST.get('filteredIds',[]).split(",") |
|
272 filteredIds = [ int(l) for l in ll if l] |
|
273 comments = comments.filter(id__in=filteredIds) # security ! TODO CROSS PERMISSIONS WITH POST CONTENT |
|
274 else : |
|
275 comments = [] |
|
276 |
|
277 if len(comments) == 0 : #want to bypass html conversion in this case |
|
278 return content_export2(request, original_content, text_version.title, original_format, format, False, download_response) |
|
279 else : # case comments to be added |
|
280 #comments = comments.order_by('start_wrapper','start_offset','end_wrapper','end_offset') |
|
281 html = text_version.get_content() |
|
282 wrapped_text_version, _ , _ = spannify(html) |
|
283 with_markers = True |
|
284 marked_content = insert_comment_markers(wrapped_text_version, comments, with_markers, with_color) |
|
285 |
|
286 viewable_comments = comments_thread(request, text_version, text) |
|
287 # viewable_commentsnoreply = get_viewable_comments(request, commentsnoreply, text, order_by = ('start_wrapper','start_offset','end_wrapper','end_offset')) |
|
288 # viewable_comments = [] |
|
289 # for cc in viewable_commentsnoreply : |
|
290 # viewable_comments += list_viewable_comments(request, [cc], text) |
|
291 |
|
292 # numerotation{ id --> numbered as a child} |
|
293 extended_comments = {} |
|
294 nb_children = {} |
|
295 for cc in viewable_comments : |
|
296 id = 0 #<-- all top comments are children of comment with id 0 |
|
297 if cc.is_reply() : |
|
298 id = cc.reply_to_id |
|
299 |
|
300 nb_children[id] = nb_children.get(id, 0) + 1 |
|
301 |
|
302 cc.num = "%d"%nb_children[id] |
|
303 |
|
304 extended_comments[cc.id] = cc |
|
305 |
|
306 if cc.is_reply() : |
|
307 cc.num = "%s.%s"%(extended_comments[cc.reply_to_id].num, cc.num) |
|
308 |
|
309 # viewable_comments += list_viewable_comments(request, viewable_commentsnoreply, text) |
|
310 html_comments=render_to_string('site/macros/text_comments.html',{'comments':viewable_comments }, context_instance=RequestContext(request)) |
|
311 |
|
312 content = "%s%s"%(marked_content, html_comments) |
|
313 content_format = "html" |
|
314 # impossible to satisfy because of color then no colors instead: |
|
315 if with_color and format in ('markdown', 'tex') : #TODO : add S5 |
|
316 with_color = False |
|
317 |
|
318 # decide to use pandoc or not |
|
319 if with_color : |
|
320 use_pandoc = False # pandoc wouldn't preserve comments scope background colors |
|
321 else : |
|
322 if format in ('markdown', 'tex') : |
|
323 use_pandoc = True |
|
324 elif format in ('pdf', 'odt') : |
|
325 use_pandoc = (original_format == "markdown") |
|
326 elif format in ('doc', 'html') : |
|
327 use_pandoc = False |
|
328 |
|
329 return content_export2(request, content, text_version.title, content_format, format, use_pandoc, download_response) |
|
330 |
|
331 def text_print(request, key, adminkey=None): |
|
332 text, admin = get_text_and_admin(key, adminkey) |
|
333 |
|
334 text_version = text.get_latest_version() |
|
335 |
|
336 # chosen default url behaviour is export all comments + bckcolor + pdf |
|
337 comments = Comment.objects.filter(text_version=text_version, reply_to__isnull=True) |
|
338 |
|
339 with_markers = True |
|
340 with_colors = True |
|
341 download_requested = True |
|
342 action = 'export' # client origine dialog |
|
343 requested_format = 'pdf' # requested output format |
|
344 |
|
345 if request.method == 'POST': |
|
346 # colors or not ? |
|
347 with_colors = (u'yes' == request.POST.get('p_color', u'no')) |
|
348 |
|
349 # restrict comments to ones that should be exported / printed |
|
350 p_comments = request.POST.get('p_comments') |
|
351 if p_comments == "filtered" or p_comments == "none" : |
|
352 filteredIds = [] # "none" case |
|
353 if p_comments == "filtered" : |
|
354 ll = request.POST.get('filteredIds').split(",") |
|
355 filteredIds = [ int(l) for l in ll if l] |
|
356 |
|
357 comments = comments.filter(id__in=filteredIds) |
|
358 |
|
359 # print or export ? |
|
360 action = request.POST.get('print_export_action') |
|
361 requested_format = request.POST.get('p_method') |
|
362 |
|
363 comments = comments.order_by('start_wrapper','start_offset','end_wrapper','end_offset') |
|
364 |
|
365 download_requested = (action == 'export') or (action == 'print' and requested_format != 'html') |
|
366 |
|
367 ori_format = text_version.format # BD : html or markdown for now ... |
|
368 src_format = ori_format # as expected by convert functions ... |
|
369 src = text_version.content |
|
370 |
|
371 if len(comments) > 0 and (with_markers or with_colors) : |
|
372 html = text_version.get_content() |
|
373 wrapped_text_version, _ , _ = spannify(html) |
|
374 marked_text_version = insert_comment_markers(wrapped_text_version, comments, with_markers, with_colors) |
|
375 |
|
376 src_format = 'html' |
|
377 src = marked_text_version |
|
378 html_comments=render_to_string('site/macros/text_comments.html',{'comments':comments}, context_instance=RequestContext(request)) |
|
379 src += html_comments |
|
380 |
|
381 if download_requested : |
|
382 use_pandoc = (requested_format == 'html' or requested_format == 'markdown') |
|
383 return content_export(request, src, text_version.title, src_format, requested_format, use_pandoc) |
|
384 else : # action == 'print' and requested_format == 'html' (no colors) |
|
385 template_dict = {'text' : text, |
|
386 'text_version' : text_version, |
|
387 'title' : text_version.title, # TODO use it ... |
|
388 'comments': comments, |
|
389 'content' : marked_text_version, |
|
390 'client_date_fmt' : settings.CLIENT_DATE_FMT |
|
391 } |
|
392 if admin: |
|
393 template_dict['adminkey'] = text.adminkey |
|
394 template_dict['admin'] = True |
|
395 return render_to_response('site/text_print.html', |
|
396 template_dict, |
|
397 context_instance=RequestContext(request)) |
|
398 |
|
399 @has_perm_on_text('can_view_text') |
|
400 def text_view_frame(request, key, adminkey=None): |
|
401 text = get_text_by_keys_or_404(key) |
|
402 |
|
403 text_version = text.get_latest_version() |
|
404 template_dict = {'text' : text} |
|
405 return render_to_response('site/text_view_frame.html', |
|
406 template_dict, |
|
407 context_instance=RequestContext(request)) |
|
408 |
|
409 |
|
410 @has_perm_on_text('can_view_text') |
|
411 def text_history(request, key, v1_nid=None, v2_nid=None, adminkey=False): |
|
412 text = get_text_by_keys_or_404(key) |
|
413 text_versions = text.get_versions() |
|
414 author_colors = get_colors([t.get_name() for t in text.get_inversed_versions()]) |
|
415 |
|
416 if v1_nid: |
|
417 v1_nid = int(v1_nid) |
|
418 else: |
|
419 v1_nid = text.get_versions_number() |
|
420 |
|
421 v1 = text.get_version(v1_nid) |
|
422 |
|
423 v1_id = v1.id |
|
424 |
|
425 v2_id = None |
|
426 v2 = None |
|
427 if v2_nid: |
|
428 v2_nid = int(v2_nid) |
|
429 v2 = text.get_version(v2_nid) |
|
430 v2_id = v2.id |
|
431 |
|
432 versions = text.get_inversed_versions() |
|
433 paired_versions = [] |
|
434 colors_dict = dict(author_colors) |
|
435 for index in range(len(versions)): |
|
436 vv1 = versions[index] |
|
437 if index + 1 < len(versions): |
|
438 vv2 = versions[index + 1] |
|
439 else: |
|
440 vv2 = None |
|
441 paired_versions.append((vv1, vv2, colors_dict.get(vv1.get_name(), '#D9D9D9'))) |
|
442 |
|
443 if v1_nid and not v2_nid: |
|
444 content = v1.get_content() |
|
445 else: |
|
446 content = get_uniffied_inner_diff_table(cleanup_textarea(v1.content), cleanup_textarea(v2.content)) |
|
447 |
|
448 template_dict = {'paired_versions' : paired_versions, |
|
449 'text' : text, |
|
450 'v1_nid' : v1_nid, |
|
451 'v2_nid' : v2_nid, |
|
452 'v1_id' : v1_id, |
|
453 'v2_id' : v2_id, |
|
454 'version1': v1, |
|
455 'version2': v2, |
|
456 'content' : content, |
|
457 'author_colors' : author_colors, |
|
458 } |
|
459 return render_to_response('site/text_history.html', template_dict, context_instance=RequestContext(request)) |
|
460 |
|
461 # taken from trac |
|
462 def _get_change_extent(str1, str2): |
|
463 """ |
|
464 Determines the extent of differences between two strings. Returns a tuple |
|
465 containing the offset at which the changes start, and the negative offset |
|
466 at which the changes end. If the two strings have neither a common prefix |
|
467 nor a common suffix, (0, 0) is returned. |
|
468 """ |
|
469 start = 0 |
|
470 limit = min(len(str1), len(str2)) |
|
471 while start < limit and str1[start] == str2[start]: |
|
472 start += 1 |
|
473 end = -1 |
|
474 limit = limit - start |
|
475 while - end <= limit and str1[end] == str2[end]: |
|
476 end -= 1 |
|
477 return (start, end + 1) |
|
478 |
|
479 def diff_decorate(minus, plus): |
|
480 return minus, plus |
|
481 |
|
482 def get_uniffied_inner_diff_table(text1, text2): |
|
483 """ |
|
484 Return the inner of the html table for text1 vs text2 diff |
|
485 """ |
|
486 gen = unified_diff(text1.split('\n'), text2.split('\n'), n=3) |
|
487 index = 0 |
|
488 res = ['<table class="diff"><tbody>'] |
|
489 #res.append('<tr><td width="50%" colspan="2"></td><td width="50%" colspan="2"></td></tr>') |
|
490 |
|
491 for g in gen: |
|
492 if index > 1: |
|
493 col_in = None |
|
494 if g.startswith('@@'): |
|
495 line_number = g.split(' ')[1][1:].split(',')[0] |
|
496 if index != 2: |
|
497 res.append('<tr><td></td> <td></td><td></td><td> </td></tr>') |
|
498 res.append('<tr><td class="diff-lineno" colspan="2">Line %s</td><td class="diff-separator"></td><td class="diff-lineno" colspan="2">Line %s</td></tr>' % (line_number, line_number)) |
|
499 if g.startswith(' '): |
|
500 res.append('<tr><td class="diff-marker"></td><td class="diff-context">%s</td><td class="diff-separator"></td><td class="diff-marker"></td><td class="diff-context">%s</td></tr>' % (g, g)) |
|
501 if g.startswith('-') or g.startswith('+'): |
|
502 plus = [] |
|
503 minus = [] |
|
504 while g.startswith('-') or g.startswith('+'): |
|
505 if g.startswith('-'): |
|
506 minus.append(g[1:]) |
|
507 else: |
|
508 plus.append(g[1:]) |
|
509 try: |
|
510 g = gen.next() |
|
511 except StopIteration: |
|
512 break |
|
513 minus, plus = diff_decorate(minus, plus) |
|
514 res.append('<tr><td class="diff-marker">-</td><td class="diff-deletedline">%s</td><td class="diff-separator"></td><td class="diff-marker">+</td><td class="diff-addedline">%s</td></tr>' % ('<br />'.join(minus), '<br />'.join(plus))) |
|
515 |
|
516 index += 1 |
|
517 res.append('</tbody></table>') |
|
518 return ''.join(res) |
|
519 |
|
520 @has_perm_on_text('can_view_text') |
|
521 def text_history_compare(request, key, v1_nid=None, v2_nid=None, adminkey=None): |
|
522 text = get_text_by_keys_or_404(key) |
|
523 |
|
524 vis_diff = difflib.HtmlDiff() |
|
525 v1 = text.get_version(int(v1_nid)) |
|
526 v2 = text.get_version(int(v2_nid)) |
|
527 content = _text_diff(v2.get_content(), v1.get_content()) |
|
528 #content = vis_diff.make_table(v1.content.split('\n'), v2.content.split('\n'), v1_nid, v2_nid, context=None) |
|
529 |
|
530 template_dict = { |
|
531 'text' : text, |
|
532 'content' : content, |
|
533 } |
|
534 return render_to_response('site/text_history_compare.html', template_dict, context_instance=RequestContext(request)) |
|
535 |
|
536 #def text_history_version(request, key): |
|
537 # text = get_text_by_keys_or_404(key=key) |
|
538 # return _text_history_version(request, text) |
|
539 # |
|
540 #def text_history_version_admin(request, key, adminkey): |
|
541 # text = get_text_by_keys_or_404(key=key, adminkey=adminkey) |
|
542 # return _text_history_version(request, text, True) |
|
543 # if admin: |
|
544 # template_dict['adminkey'] = text.adminkey |
|
545 # template_dict['admin'] = True |
|
546 # return render_to_response('site/text_history.html', template_dict, context_instance=RequestContext(request)) |
|
547 # |
|
548 #def _text_history_version(request, text, admin=False): |
|
549 # pass |
|
550 # |
|
551 class TextVersionForm(ModelForm): |
|
552 class Meta: |
|
553 model = TextVersion |
|
554 fields = ('title', 'content', 'format') |
|
555 |
|
556 @has_perm_on_text('can_view_text') |
|
557 def text_diff(request, key, id_v1, id_v2): |
|
558 text = get_text_by_keys_or_404(key) |
|
559 text_version_1 = TextVersion.objects.get(pk=id_v1) |
|
560 text_version_2 = TextVersion.objects.get(pk=id_v2) |
|
561 content = _text_diff(text_version_1.get_content(), text_version_2.get_content()) |
|
562 title = _text_diff(text_version_1.title, text_version_2.title) |
|
563 return render_to_response('site/text_view.html', {'text' : text, 'text_version_1' : text_version_1, 'text_version_2' : text_version_2, 'title' : title, 'content' : content}, context_instance=RequestContext(request)) |
|
564 |
|
565 |
|
566 @has_perm_on_text('can_view_text') |
|
567 def text_version(request, key, id_version): |
|
568 text = get_text_by_keys_or_404(key) |
|
569 text_version = TextVersion.objects.get(pk=id_version) |
|
570 # TODO : assert text_v in text ... |
|
571 # TODO : do not use db id |
|
572 return render_to_response('site/text_view.html', {'text' : text, 'text_version' : text_version, 'title' : text_version.title, 'content' : text_version.get_content()}, context_instance=RequestContext(request)) |
|
573 |
|
574 class EditTextForm(ModelForm): |
|
575 title = forms.CharField(label=_("Title"), widget=forms.TextInput) |
|
576 #format = forms.CharField(label=_("Format")) |
|
577 #content = forms.TextField(label=_("Content")) |
|
578 |
|
579 note = forms.CharField(label=_("Note (optional)"), |
|
580 widget=forms.TextInput, |
|
581 required=False, |
|
582 help_text=_("Add a note to explain the modifications made to the text") |
|
583 ) |
|
584 |
|
585 #tags = forms.CharField(label=_("Tags (optional)"), |
|
586 # widget=forms.TextInput, |
|
587 # required=False, |
|
588 # #help_text=_("Add a note to explain the modifications made to the text") |
|
589 # ) |
|
590 |
|
591 |
|
592 new_version = forms.BooleanField(label=_("New version (optional)"), |
|
593 required=False, |
|
594 initial=True, |
|
595 help_text=_("Create a new version of this text (recommended)") |
|
596 ) |
|
597 |
|
598 keep_comments = forms.BooleanField(label=_("Keep comments (optional)"), |
|
599 required=False, |
|
600 initial=True, |
|
601 help_text=_("Keep comments (if not affected by the edit)") |
|
602 ) |
|
603 |
|
604 class Meta: |
|
605 model = TextVersion |
|
606 fields = ('title', 'format', 'content', 'new_version', 'tags', 'note') |
|
607 |
|
608 def save_into_text(self, text, request): |
|
609 new_content = request.POST.get('content') |
|
610 new_title = request.POST.get('title') |
|
611 new_format = request.POST.get('format') |
|
612 new_note = request.POST.get('note',None) |
|
613 new_tags = request.POST.get('tags',None) |
|
614 version = text.get_latest_version() |
|
615 version.edit(new_title, new_format, new_content, new_tags, new_note, True) |
|
616 |
|
617 return version |
|
618 |
|
619 def save_new_version(self, text, request): |
|
620 new_text_version = TextVersion.objects.duplicate(text.get_latest_version(), True) |
|
621 new_text_version.user = request.user if request.user.is_authenticated() else None |
|
622 new_text_version.note = request.POST.get('note','') |
|
623 new_text_version.email = request.POST.get('email','') |
|
624 new_text_version.name = request.POST.get('name','') |
|
625 new_text_version.save() |
|
626 |
|
627 new_content = request.POST.get('content') |
|
628 new_title = request.POST.get('title') |
|
629 new_format = request.POST.get('format') |
|
630 new_note = request.POST.get('note',None) |
|
631 new_tags = request.POST.get('tags',None) |
|
632 new_text_version.edit(new_title, new_format, new_content, new_tags, new_note, True) |
|
633 |
|
634 return new_text_version |
|
635 |
|
636 @has_perm_on_text('can_edit_text') |
|
637 def text_pre_edit(request, key, adminkey=None): |
|
638 text = get_text_by_keys_or_404(key) |
|
639 |
|
640 text_version = text.get_latest_version() |
|
641 comments = text_version.get_comments() ; |
|
642 new_content = request.POST['new_content'] |
|
643 new_format = request.POST['new_format'] |
|
644 |
|
645 # TODO: RBE : si commentaire mal forme : (position non existante : boom par key error) |
|
646 _tomodify_comments, toremove_comments = compute_new_comment_positions(text_version.content, text_version.format, new_content, new_format, comments) |
|
647 return HttpResponse(simplejson.dumps({'nb_removed' : len(toremove_comments) })) |
|
648 |
|
649 class EditTextFormAnon(EditTextForm): |
|
650 name = forms.CharField(label=ugettext_lazy("Name (optional)"), widget=forms.TextInput, required=False) |
|
651 email = forms.EmailField(label=ugettext_lazy("Email (optional)"), required=False) |
|
652 content = forms.CharField(label=ugettext_lazy("Content"), required=True, widget=forms.Textarea(attrs={'rows':'30', 'cols': '70'})) |
|
653 |
|
654 class Meta: |
|
655 model = TextVersion |
|
656 fields = ('title', 'format', 'content', 'tags', 'note', 'name', 'email') |
|
657 |
|
658 @has_perm_on_text('can_edit_text') |
|
659 def text_edit(request, key, adminkey=None): |
|
660 text = get_text_by_keys_or_404(key) |
|
661 text_version = text.get_latest_version() |
|
662 if request.method == 'POST': |
|
663 if request.user.is_authenticated(): |
|
664 form = EditTextForm(request.POST) |
|
665 else: |
|
666 form = EditTextFormAnon(request.POST) |
|
667 |
|
668 if form.is_valid(): |
|
669 if request.POST.get('new_version'): |
|
670 new_version = form.save_new_version(text, request) |
|
671 register_activity(request, "text_edited_new_version", text=text, text_version=new_version) |
|
672 else: |
|
673 form.save_into_text(text, request) |
|
674 register_activity(request, "text_edited", text=text) |
|
675 return redirect(request, 'text-view', args=[text.key]) |
|
676 else: |
|
677 default_data = { |
|
678 'content': text_version.content, |
|
679 'title': text_version.title, |
|
680 'format': text_version.format, |
|
681 'tags': text_version.tags, |
|
682 'new_version': NEW_TEXT_VERSION_ON_EDIT, |
|
683 'note' : text_version.note, |
|
684 'keep_comments' : True, |
|
685 } |
|
686 if request.user.is_authenticated(): |
|
687 form = EditTextForm(default_data) |
|
688 else: |
|
689 form = EditTextFormAnon(default_data) |
|
690 |
|
691 template_dict = {'text' : text, 'form' : form} |
|
692 |
|
693 return render_to_response('site/text_edit.html', template_dict , context_instance=RequestContext(request)) |
|
694 |
|
695 # TODO: modif de la base => if POST |
|
696 @has_perm_on_text('can_edit_text') |
|
697 def text_revert(request, key, v1_nid, adminkey=None): |
|
698 text = get_text_by_keys_or_404(key) |
|
699 |
|
700 text.revert_to_version(v1_nid) |
|
701 display_message(request, _(u'A new version (copied from version %(version_id)s) has been created') % {'version_id':v1_nid}) |
|
702 |
|
703 return HttpResponseRedirect(reverse('text-history', args=[text.key])) |
|
704 |
|
705 @has_perm_on_text('can_view_text') |
|
706 def text_attach(request, key, attach_key): |
|
707 attach = Attachment.objects.get(key=attach_key, text_version__text__key=key) |
|
708 content = file(attach.data.path).read() |
|
709 mimetype, _encoding = mimetypes.guess_type(attach.data.path) |
|
710 response = HttpResponse(content, mimetype) |
|
711 return response |
|
712 |
|
713 |
|
714 |
|
715 |
|
716 def fix_anon_in_formset(formset): |
|
717 # fix role choice in formset for anon (not all role are allowed) |
|
718 role_field = [f.fields['role'] for f in formset.forms if f.instance.user == None][0] |
|
719 role_field.choices = [(u'', u'---------')] + [(r.id, str(r)) for r in Role.objects.filter(anon = True)] # limit anon choices |
|
720 |
|
721 class BaseUserRoleFormSet(BaseModelFormSet): |
|
722 def clean(self): |
|
723 """Checks that anon users are given roles with anon=True.""" |
|
724 for i in range(0, self.total_form_count()): |
|
725 form = self.forms[i] |
|
726 print form.cleaned_data |
|
727 user_role = form.cleaned_data['id'] |
|
728 if user_role.user == None: |
|
729 role = form.cleaned_data['role'] |
|
730 if not role.anon: |
|
731 # nasty stuff: cannot happen so not dealt with in tempate |
|
732 logging.warn('Cannot give such role to anon user.') |
|
733 raise forms.ValidationError, "Cannot give such role to anon user." |
|
734 |
|
735 #@has_perm_on_text('can_manage_text') |
|
736 #def xtext_share(request, key): |
|
737 # text = get_text_by_keys_or_404(key) |
|
738 # order_by = get_among(request.GET,'order',('user__username','-user__username','role__name','-role__name'),'user__username') |
|
739 # |
|
740 # UserRole.objects.create_userroles_text(text) |
|
741 # UserRoleFormSet = modelformset_factory(UserRole, fields=('role', ), extra=0, formset = BaseUserRoleFormSet) |
|
742 # |
|
743 # # put anon users on top no matter what the order says (TODO: ?) |
|
744 # userrole_queryset = UserRole.objects.filter(text=text).extra(select={'anon':'"cm_userrole"."user_id">-1'}).order_by('-anon',order_by) |
|
745 # if request.method == 'POST': |
|
746 # formset = UserRoleFormSet(request.POST, queryset = userrole_queryset) |
|
747 # |
|
748 # if formset.is_valid(): |
|
749 # formset.save() |
|
750 # display_message(request, "Sharing updated.") |
|
751 # return HttpResponseRedirect(reverse('text-share',args=[text.key])) |
|
752 # |
|
753 # else: |
|
754 # formset = UserRoleFormSet(queryset = userrole_queryset) |
|
755 # fix_anon_in_formset(formset) |
|
756 # |
|
757 # global_anon_userrole = UserRole.objects.get(text=None, user=None) |
|
758 # return render_to_response('site/text_share.html', {'text' : text, |
|
759 # 'formset' : formset, |
|
760 # 'global_anon_userrole' : global_anon_userrole, |
|
761 # } , context_instance=RequestContext(request)) |
|
762 |
|
763 # TODO: permission protection ? format value check ? |
|
764 def text_wysiwyg_preview(request, format): |
|
765 html_content = "" |
|
766 if request.POST : # if satisfied in the no html case, in html case : no POST (cf. markitup) previewTemplatePath and previewParserPath |
|
767 html_content = pandoc_convert(request.POST['data'], format, "html", full=False) |
|
768 |
|
769 return render_to_response('site/wysiwyg_preview.html', {'content':html_content} , context_instance=RequestContext(request)) |
|
770 #return HttpResponse(pandoc_convert(content, format, "html", full=False)) |
|
771 |
|
772 @has_perm_on_text('can_manage_text') |
|
773 def text_share(request, key): |
|
774 text = get_text_by_keys_or_404(key) |
|
775 order_by = get_among(request.GET,'order',('user__username', |
|
776 'user__email', |
|
777 '-user__username', |
|
778 '-user__email', |
|
779 'role__name', |
|
780 '-role__name', |
|
781 ), |
|
782 'user__username') |
|
783 paginate_by = 10 |
|
784 |
|
785 UserRole.objects.create_userroles_text(text) |
|
786 |
|
787 if request.method == 'POST': |
|
788 if 'save' in request.POST: |
|
789 user_profile_keys_roles = get_keys_from_dict(request.POST, 'user-role-') |
|
790 count = 0 |
|
791 for user_profile_key in user_profile_keys_roles: |
|
792 role_id = user_profile_keys_roles[user_profile_key] |
|
793 if not user_profile_key: |
|
794 user_role = UserRole.objects.get(user = None, text = text) |
|
795 else: |
|
796 user_role = UserRole.objects.get(user__userprofile__key = user_profile_key, text = text) |
|
797 if (role_id != u'' or user_role.role_id!=None) and role_id!=unicode(user_role.role_id): |
|
798 if role_id: |
|
799 user_role.role_id = int(role_id) |
|
800 else: |
|
801 user_role.role_id = None |
|
802 user_role.save() |
|
803 count += 1 |
|
804 display_message(request, _(u'%(count)i user(s) role modified') %{'count':count}) |
|
805 return HttpResponseRedirect(reverse('text-share', args=[text.key])) |
|
806 |
|
807 anon_role = UserRole.objects.get(user = None, text = text).role |
|
808 global_anon_role = UserRole.objects.get(user = None, text = None).role |
|
809 |
|
810 context = { |
|
811 'anon_role' : anon_role, |
|
812 'global_anon_role' : global_anon_role, |
|
813 'all_roles' : Role.objects.all(), |
|
814 'anon_roles' : Role.objects.filter(anon = True), |
|
815 'text' : text, |
|
816 } |
|
817 |
|
818 return object_list(request, UserRole.objects.filter(text=text).filter(~Q(user=None)).order_by(order_by), |
|
819 template_name = 'site/text_share.html', |
|
820 paginate_by = paginate_by, |
|
821 extra_context = context, |
|
822 ) |
|
823 |
|
824 |
|
825 class SettingsTextForm(ModelForm): |
|
826 # example name = forms.CharField(label=_("Name (optional)"), widget=forms.TextInput, required=False) |
|
827 |
|
828 class Meta: |
|
829 model = TextVersion |
|
830 fields = ('mod_posteriori',) |
|
831 |
|
832 @has_perm_on_text('can_manage_text') |
|
833 def text_settings(request, key): |
|
834 text = get_text_by_keys_or_404(key) |
|
835 |
|
836 text_version = text.get_latest_version() |
|
837 if request.method == 'POST': |
|
838 form = SettingsTextForm(request.POST, instance = text_version) |
|
839 |
|
840 if form.is_valid(): |
|
841 form.save() |
|
842 display_message(request, _(u'Text settings updated')) |
|
843 return redirect(request, 'text-view', args=[text.key]) |
|
844 else: |
|
845 form = SettingsTextForm(instance = text_version) |
|
846 |
|
847 template_dict = {'text' : text, 'form' : form} |
|
848 |
|
849 return render_to_response('site/text_settings.html', template_dict , context_instance=RequestContext(request)) |
|
850 |