--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/cm/views/texts.py Mon Nov 23 15:14:29 2009 +0100
@@ -0,0 +1,850 @@
+from cm.activity import register_activity
+from cm.client import jsonize, get_filter_datas, edit_comment, remove_comment, \
+ add_comment, RequestComplexEncoder, comments_thread
+from cm.cm_settings import NEW_TEXT_VERSION_ON_EDIT
+from cm.diff import text_diff as _text_diff, text_history as inner_text_history, \
+ get_colors
+from cm.exception import UnauthorizedException
+from cm.message import *
+from cm.models import *
+from cm.models_base import generate_key
+from cm.security import get_texts_with_perm, has_perm, get_viewable_comments, \
+ has_perm_on_text
+from cm.utils import get_among, get_among, get_int
+from cm.utils.comment_positioning import compute_new_comment_positions, \
+ insert_comment_markers
+from cm.utils.html import cleanup_textarea
+from cm.utils.spannifier import spannify
+from cm.views import get_keys_from_dict, get_text_by_keys_or_404, redirect
+from cm.views.export import content_export2
+from cm.views.user import AnonUserRoleForm, cm_login
+from difflib import unified_diff
+from django import forms
+from django.conf import settings
+from django.contrib.auth import login as django_login
+from django.contrib.auth.forms import AuthenticationForm
+from django.contrib.auth.models import User
+from django.core.urlresolvers import reverse
+from django.db.models import Q
+from django.forms import ModelForm
+from django.forms.models import BaseModelFormSet, modelformset_factory
+from django.http import HttpResponse, HttpResponseRedirect, Http404
+from django.shortcuts import render_to_response
+from django.template import RequestContext
+from django.template.loader import render_to_string
+from django.utils.translation import ugettext as _, ugettext_lazy
+from django.views.generic.list_detail import object_list
+import difflib
+import logging
+import mimetypes
+import simplejson
+import sys
+
+
+def get_text_and_admin(key, adminkey, assert_admin = False):
+ """
+ assert_admin => redirect to unauthorized if not admin
+ """
+ admin = False
+ if adminkey:
+ text = Text.objects.get(key = key, adminkey = adminkey)
+ if text:
+ admin = True
+ else:
+ text = Text.objects.get(key=key)
+ if assert_admin and not admin:
+ raise UnauthorizedException('Is not admin')
+ return text, admin
+
+
+
+ACTIVITY_PAGINATION = 10
+RECENT_TEXT_NB = 5
+RECENT_COMMENT_NB = RECENT_TEXT_NB
+
+MODERATE_NB = 5
+
+def dashboard(request):
+ request.session.set_test_cookie()
+ if request.user.is_authenticated():
+ act_view = {
+ 'view_texts' : get_int(request.GET,'view_texts',1),
+ 'view_comments' : get_int(request.GET,'view_comments',1),
+ 'view_users' : get_int(request.GET,'view_users',1),
+ }
+
+ paginate_by = get_int(request.GET,'paginate',ACTIVITY_PAGINATION)
+
+ # texts with can_view_unapproved_comment perms
+ moderator_texts = get_texts_with_perm(request, 'can_view_unapproved_comment')
+ viewer_texts = get_texts_with_perm(request, 'can_view_approved_comment')
+ all_texts_ids = [t.id for t in moderator_texts] + [t.id for t in viewer_texts]
+
+ span = get_among(request.GET,'span',('day','month','week',),'week')
+ template_dict = {
+ 'span' : span,
+ 'last_texts' : get_texts_with_perm(request, 'can_view_text').order_by('-modified')[:RECENT_TEXT_NB],
+ 'last_comments' : Comment.objects.filter(text_version__text__in=all_texts_ids).order_by('-created')[:RECENT_COMMENT_NB],# TODO: useful?
+ #'last_users' : User.objects.all().order_by('-date_joined')[:5],
+ }
+ template_dict.update(act_view)
+
+ all_activities = {
+ 'view_comments' : ['comment_created','comment_removed'],
+ 'view_users' : ['user_created', 'user_activated', 'user_suspended','user_enabled',],
+ 'view_texts' : ['text_created','text_removed', 'text_edited', 'text_edited_new_version'],
+ }
+
+ selected_activities = []
+ [selected_activities.extend(all_activities[k]) for k in act_view.keys() if act_view[k]]
+
+ activities = Activity.objects.filter(type__in = selected_activities)
+ if not has_perm(request,'can_manage_workspace'):
+ texts = get_texts_with_perm(request, 'can_view_text')
+ activities = activities.filter(Q(text__in=texts))
+
+ comments = []
+ [comments.extend(get_viewable_comments(request, t.last_text_version.comment_set.all(), t)) for t in texts]
+
+ activities = activities.filter(Q(comment__in=comments) | Q(comment=None) )
+ template_dict['to_mod_profiles'] = []
+ else:
+ template_dict['to_mod_profiles'] = UserProfile.objects.filter(user__is_active=False).filter(is_suspended=True).order_by('-user__date_joined')[:MODERATE_NB]
+ 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'])]
+
+ activities = activities.order_by('-created')
+ return object_list(request, activities,
+ template_name = 'site/dashboard.html',
+ paginate_by = paginate_by,
+ extra_context = template_dict,
+ )
+
+ else:
+ if request.method == 'POST':
+ form = AuthenticationForm(request, request.POST)
+ if form.is_valid():
+ user = form.get_user()
+ user.backend = 'django.contrib.auth.backends.ModelBackend'
+ cm_login(request, user)
+ display_message(request, _(u"You're logged in!"))
+ return HttpResponseRedirect(reverse('index'))
+ else:
+ form = AuthenticationForm()
+
+
+ public_texts = get_texts_with_perm(request, 'can_view_text').order_by('-modified')
+
+ template_dict = {
+ 'form' : form,
+ 'texts' : public_texts,
+ }
+ return render_to_response('site/non_authenticated_index.html', template_dict, context_instance=RequestContext(request))
+
+TEXT_PAGINATION = 10
+# security check inside view
+# TODO: set global access perm
+def text_list(request):
+ paginate_by = get_int(request.GET,'paginate',TEXT_PAGINATION)
+
+ order_by = get_among(request.GET,'order',('title','author','modified','-title','-author','-modified'),'-modified')
+
+ if request.method == 'POST':
+ action = request.POST.get('action',None)
+ text_keys = get_keys_from_dict(request.POST, 'check-').keys()
+ if action == 'delete':
+ for text_key in text_keys:
+ text = Text.objects.get(key=text_key)
+ if has_perm(request, 'can_delete_text', text=text):
+ text.delete()
+ else:
+ raise UnauthorizedException('No perm can_delete_text on comment')
+ display_message(request, _(u'%(nb_texts)i text(s) deleted') %{'nb_texts':len(text_keys)})
+ return HttpResponseRedirect(reverse('text'))
+
+ texts = get_texts_with_perm(request, 'can_view_text').order_by(order_by)
+ return object_list(request, texts,
+ template_name = 'site/text_list.html',
+ paginate_by = paginate_by,
+ )
+
+@has_perm_on_text('can_view_text')
+def text_view(request, key, adminkey=None):
+
+ text = get_text_by_keys_or_404(key)
+ register_activity(request, "text_view", text=text)
+
+ text_version = text.get_latest_version()
+ template_dict = { 'text' : text, 'text_version' : text_version, 'title' : text_version.title, 'content' : text_version.get_content()}
+ return render_to_response('site/text_view.html', template_dict, context_instance=RequestContext(request))
+
+@has_perm_on_text('can_delete_text')
+def text_delete(request, key):
+ text = Text.objects.get(key=key)
+ if request.method != 'POST':
+ raise UnauthorizedException('Unauthorized')
+ display_message(request, _(u'Text %(text_title)s deleted') %{'text_title':text.title})
+ register_activity(request, "text_removed", text=text)
+ text.delete()
+ return HttpResponse('') # no redirect because this is called by js
+
+@has_perm_on_text('can_view_text') # only protected by text_view / comment filtering done in view
+def text_view_comments(request, key, adminkey=None):
+ text = get_text_by_keys_or_404(key)
+ #TODO: stupid why restrict to latest ?
+ text_version = text.get_latest_version()
+ comments = get_viewable_comments(request, text_version.comment_set.filter(reply_to__isnull=True),text)
+ filter_datas = get_filter_datas(request, text_version, text)
+
+ get_params = simplejson.dumps(request.GET)
+
+ wrapped_text_version, _ , _ = spannify(text_version.get_content())
+ template_dict = {'text' : text,
+ 'text_version' : text_version,
+ 'title' : text_version.title, # TODO use it ...
+ 'get_params' : get_params,
+ 'json_comments':jsonize(comments, request),
+ 'json_filter_datas':jsonize(filter_datas, request),
+ 'content' : wrapped_text_version,
+ 'client_date_fmt' : settings.CLIENT_DATE_FMT,
+ }
+ return render_to_response('site/text_view_comments.html',
+ template_dict,
+ context_instance=RequestContext(request))
+def client_exchange(request):
+ ret = None
+ function_name = request.POST['fun']# function called from client
+ user = request.user
+ if function_name == 'experiment' :
+ ret = experiment()
+ elif function_name == 'warn' :
+# TODO: (RBE to RBA) send mail withinfos
+ ret = "warn test"
+ #print request.POST
+ elif request.POST:
+ key = request.POST['key']
+
+ text = Text.objects.get(key=key) ;
+ #TODO: stupid why restrict to latest ?
+ text_version = text.get_latest_version()
+
+ if (text != None) :
+ if function_name in ('editComment', 'addComment', 'removeComment',) :
+ if function_name == 'editComment' :
+ ret = edit_comment(request=request, key=key, comment_key=request.POST['comment_key'])
+ elif function_name == 'addComment' :
+ ret = add_comment(request=request, key=key)
+ elif function_name == 'removeComment' :
+ ret = remove_comment(request=request, key=key, comment_key=request.POST['comment_key'])
+
+ ret['filterData'] = get_filter_datas(request, text_version, text)
+ #ret['tagCloud'] = get_tagcloud(key)
+ if ret :
+ if type(ret) != HttpResponseRedirect :
+ ret = HttpResponse(simplejson.dumps(ret, cls=RequestComplexEncoder, request=request))
+ else :
+ ret = HttpResponse(simplejson.dumps({}))
+ ret.status_code = 403
+
+ return ret
+
+
+#NOTE : some arguments like : withcolor = "yes" + format = "markdown" are incompatible
+#http://localhost:8000/text/text_key_1/export/pdf/1/all/1
+def text_export(request, key, format, download, whichcomments, withcolor, adminkey=None):
+ text, admin = get_text_and_admin(key, adminkey)
+ text_version = text.get_latest_version()
+ original_content = text_version.content
+ original_format = text_version.format # BD : html or markdown for now ...
+
+ download_response = download == "1"
+ with_color = withcolor == "1"
+
+ comments = [] # whichcomments=="none"
+
+ if whichcomments == "filtered" or whichcomments == "all":
+ #comments = text_version.comment_set.filter(reply_to__isnull=True)# whichcomments=="all"
+ #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"
+ comments = get_viewable_comments(request, text_version.comment_set.all(), text, order_by=('start_wrapper','start_offset','end_wrapper','end_offset'))# whichcomments=="all"
+
+ if whichcomments == "filtered" :
+ if request.method == 'POST' :
+ ll = request.POST.get('filteredIds',[]).split(",")
+ filteredIds = [ int(l) for l in ll if l]
+ comments = comments.filter(id__in=filteredIds) # security ! TODO CROSS PERMISSIONS WITH POST CONTENT
+ else :
+ comments = []
+
+ if len(comments) == 0 : #want to bypass html conversion in this case
+ return content_export2(request, original_content, text_version.title, original_format, format, False, download_response)
+ else : # case comments to be added
+ #comments = comments.order_by('start_wrapper','start_offset','end_wrapper','end_offset')
+ html = text_version.get_content()
+ wrapped_text_version, _ , _ = spannify(html)
+ with_markers = True
+ marked_content = insert_comment_markers(wrapped_text_version, comments, with_markers, with_color)
+
+ viewable_comments = comments_thread(request, text_version, text)
+# viewable_commentsnoreply = get_viewable_comments(request, commentsnoreply, text, order_by = ('start_wrapper','start_offset','end_wrapper','end_offset'))
+# viewable_comments = []
+# for cc in viewable_commentsnoreply :
+# viewable_comments += list_viewable_comments(request, [cc], text)
+
+ # numerotation{ id --> numbered as a child}
+ extended_comments = {}
+ nb_children = {}
+ for cc in viewable_comments :
+ id = 0 #<-- all top comments are children of comment with id 0
+ if cc.is_reply() :
+ id = cc.reply_to_id
+
+ nb_children[id] = nb_children.get(id, 0) + 1
+
+ cc.num = "%d"%nb_children[id]
+
+ extended_comments[cc.id] = cc
+
+ if cc.is_reply() :
+ cc.num = "%s.%s"%(extended_comments[cc.reply_to_id].num, cc.num)
+
+# viewable_comments += list_viewable_comments(request, viewable_commentsnoreply, text)
+ html_comments=render_to_string('site/macros/text_comments.html',{'comments':viewable_comments }, context_instance=RequestContext(request))
+
+ content = "%s%s"%(marked_content, html_comments)
+ content_format = "html"
+ # impossible to satisfy because of color then no colors instead:
+ if with_color and format in ('markdown', 'tex') : #TODO : add S5
+ with_color = False
+
+ # decide to use pandoc or not
+ if with_color :
+ use_pandoc = False # pandoc wouldn't preserve comments scope background colors
+ else :
+ if format in ('markdown', 'tex') :
+ use_pandoc = True
+ elif format in ('pdf', 'odt') :
+ use_pandoc = (original_format == "markdown")
+ elif format in ('doc', 'html') :
+ use_pandoc = False
+
+ return content_export2(request, content, text_version.title, content_format, format, use_pandoc, download_response)
+
+def text_print(request, key, adminkey=None):
+ text, admin = get_text_and_admin(key, adminkey)
+
+ text_version = text.get_latest_version()
+
+# chosen default url behaviour is export all comments + bckcolor + pdf
+ comments = Comment.objects.filter(text_version=text_version, reply_to__isnull=True)
+
+ with_markers = True
+ with_colors = True
+ download_requested = True
+ action = 'export' # client origine dialog
+ requested_format = 'pdf' # requested output format
+
+ if request.method == 'POST':
+ # colors or not ?
+ with_colors = (u'yes' == request.POST.get('p_color', u'no'))
+
+ # restrict comments to ones that should be exported / printed
+ p_comments = request.POST.get('p_comments')
+ if p_comments == "filtered" or p_comments == "none" :
+ filteredIds = [] # "none" case
+ if p_comments == "filtered" :
+ ll = request.POST.get('filteredIds').split(",")
+ filteredIds = [ int(l) for l in ll if l]
+
+ comments = comments.filter(id__in=filteredIds)
+
+ # print or export ?
+ action = request.POST.get('print_export_action')
+ requested_format = request.POST.get('p_method')
+
+ comments = comments.order_by('start_wrapper','start_offset','end_wrapper','end_offset')
+
+ download_requested = (action == 'export') or (action == 'print' and requested_format != 'html')
+
+ ori_format = text_version.format # BD : html or markdown for now ...
+ src_format = ori_format # as expected by convert functions ...
+ src = text_version.content
+
+ if len(comments) > 0 and (with_markers or with_colors) :
+ html = text_version.get_content()
+ wrapped_text_version, _ , _ = spannify(html)
+ marked_text_version = insert_comment_markers(wrapped_text_version, comments, with_markers, with_colors)
+
+ src_format = 'html'
+ src = marked_text_version
+ html_comments=render_to_string('site/macros/text_comments.html',{'comments':comments}, context_instance=RequestContext(request))
+ src += html_comments
+
+ if download_requested :
+ use_pandoc = (requested_format == 'html' or requested_format == 'markdown')
+ return content_export(request, src, text_version.title, src_format, requested_format, use_pandoc)
+ else : # action == 'print' and requested_format == 'html' (no colors)
+ template_dict = {'text' : text,
+ 'text_version' : text_version,
+ 'title' : text_version.title, # TODO use it ...
+ 'comments': comments,
+ 'content' : marked_text_version,
+ 'client_date_fmt' : settings.CLIENT_DATE_FMT
+ }
+ if admin:
+ template_dict['adminkey'] = text.adminkey
+ template_dict['admin'] = True
+ return render_to_response('site/text_print.html',
+ template_dict,
+ context_instance=RequestContext(request))
+
+@has_perm_on_text('can_view_text')
+def text_view_frame(request, key, adminkey=None):
+ text = get_text_by_keys_or_404(key)
+
+ text_version = text.get_latest_version()
+ template_dict = {'text' : text}
+ return render_to_response('site/text_view_frame.html',
+ template_dict,
+ context_instance=RequestContext(request))
+
+
+@has_perm_on_text('can_view_text')
+def text_history(request, key, v1_nid=None, v2_nid=None, adminkey=False):
+ text = get_text_by_keys_or_404(key)
+ text_versions = text.get_versions()
+ author_colors = get_colors([t.get_name() for t in text.get_inversed_versions()])
+
+ if v1_nid:
+ v1_nid = int(v1_nid)
+ else:
+ v1_nid = text.get_versions_number()
+
+ v1 = text.get_version(v1_nid)
+
+ v1_id = v1.id
+
+ v2_id = None
+ v2 = None
+ if v2_nid:
+ v2_nid = int(v2_nid)
+ v2 = text.get_version(v2_nid)
+ v2_id = v2.id
+
+ versions = text.get_inversed_versions()
+ paired_versions = []
+ colors_dict = dict(author_colors)
+ for index in range(len(versions)):
+ vv1 = versions[index]
+ if index + 1 < len(versions):
+ vv2 = versions[index + 1]
+ else:
+ vv2 = None
+ paired_versions.append((vv1, vv2, colors_dict.get(vv1.get_name(), '#D9D9D9')))
+
+ if v1_nid and not v2_nid:
+ content = v1.get_content()
+ else:
+ content = get_uniffied_inner_diff_table(cleanup_textarea(v1.content), cleanup_textarea(v2.content))
+
+ template_dict = {'paired_versions' : paired_versions,
+ 'text' : text,
+ 'v1_nid' : v1_nid,
+ 'v2_nid' : v2_nid,
+ 'v1_id' : v1_id,
+ 'v2_id' : v2_id,
+ 'version1': v1,
+ 'version2': v2,
+ 'content' : content,
+ 'author_colors' : author_colors,
+ }
+ return render_to_response('site/text_history.html', template_dict, context_instance=RequestContext(request))
+
+# taken from trac
+def _get_change_extent(str1, str2):
+ """
+ Determines the extent of differences between two strings. Returns a tuple
+ containing the offset at which the changes start, and the negative offset
+ at which the changes end. If the two strings have neither a common prefix
+ nor a common suffix, (0, 0) is returned.
+ """
+ start = 0
+ limit = min(len(str1), len(str2))
+ while start < limit and str1[start] == str2[start]:
+ start += 1
+ end = -1
+ limit = limit - start
+ while - end <= limit and str1[end] == str2[end]:
+ end -= 1
+ return (start, end + 1)
+
+def diff_decorate(minus, plus):
+ return minus, plus
+
+def get_uniffied_inner_diff_table(text1, text2):
+ """
+ Return the inner of the html table for text1 vs text2 diff
+ """
+ gen = unified_diff(text1.split('\n'), text2.split('\n'), n=3)
+ index = 0
+ res = ['<table class="diff"><tbody>']
+ #res.append('<tr><td width="50%" colspan="2"></td><td width="50%" colspan="2"></td></tr>')
+
+ for g in gen:
+ if index > 1:
+ col_in = None
+ if g.startswith('@@'):
+ line_number = g.split(' ')[1][1:].split(',')[0]
+ if index != 2:
+ res.append('<tr><td></td> <td></td><td></td><td> </td></tr>')
+ 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))
+ if g.startswith(' '):
+ 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))
+ if g.startswith('-') or g.startswith('+'):
+ plus = []
+ minus = []
+ while g.startswith('-') or g.startswith('+'):
+ if g.startswith('-'):
+ minus.append(g[1:])
+ else:
+ plus.append(g[1:])
+ try:
+ g = gen.next()
+ except StopIteration:
+ break
+ minus, plus = diff_decorate(minus, plus)
+ 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)))
+
+ index += 1
+ res.append('</tbody></table>')
+ return ''.join(res)
+
+@has_perm_on_text('can_view_text')
+def text_history_compare(request, key, v1_nid=None, v2_nid=None, adminkey=None):
+ text = get_text_by_keys_or_404(key)
+
+ vis_diff = difflib.HtmlDiff()
+ v1 = text.get_version(int(v1_nid))
+ v2 = text.get_version(int(v2_nid))
+ content = _text_diff(v2.get_content(), v1.get_content())
+ #content = vis_diff.make_table(v1.content.split('\n'), v2.content.split('\n'), v1_nid, v2_nid, context=None)
+
+ template_dict = {
+ 'text' : text,
+ 'content' : content,
+ }
+ return render_to_response('site/text_history_compare.html', template_dict, context_instance=RequestContext(request))
+
+#def text_history_version(request, key):
+# text = get_text_by_keys_or_404(key=key)
+# return _text_history_version(request, text)
+#
+#def text_history_version_admin(request, key, adminkey):
+# text = get_text_by_keys_or_404(key=key, adminkey=adminkey)
+# return _text_history_version(request, text, True)
+# if admin:
+# template_dict['adminkey'] = text.adminkey
+# template_dict['admin'] = True
+# return render_to_response('site/text_history.html', template_dict, context_instance=RequestContext(request))
+#
+#def _text_history_version(request, text, admin=False):
+# pass
+#
+class TextVersionForm(ModelForm):
+ class Meta:
+ model = TextVersion
+ fields = ('title', 'content', 'format')
+
+@has_perm_on_text('can_view_text')
+def text_diff(request, key, id_v1, id_v2):
+ text = get_text_by_keys_or_404(key)
+ text_version_1 = TextVersion.objects.get(pk=id_v1)
+ text_version_2 = TextVersion.objects.get(pk=id_v2)
+ content = _text_diff(text_version_1.get_content(), text_version_2.get_content())
+ title = _text_diff(text_version_1.title, text_version_2.title)
+ 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))
+
+
+@has_perm_on_text('can_view_text')
+def text_version(request, key, id_version):
+ text = get_text_by_keys_or_404(key)
+ text_version = TextVersion.objects.get(pk=id_version)
+ # TODO : assert text_v in text ...
+ # TODO : do not use db id
+ 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))
+
+class EditTextForm(ModelForm):
+ title = forms.CharField(label=_("Title"), widget=forms.TextInput)
+ #format = forms.CharField(label=_("Format"))
+ #content = forms.TextField(label=_("Content"))
+
+ note = forms.CharField(label=_("Note (optional)"),
+ widget=forms.TextInput,
+ required=False,
+ help_text=_("Add a note to explain the modifications made to the text")
+ )
+
+ #tags = forms.CharField(label=_("Tags (optional)"),
+ # widget=forms.TextInput,
+ # required=False,
+ # #help_text=_("Add a note to explain the modifications made to the text")
+ # )
+
+
+ new_version = forms.BooleanField(label=_("New version (optional)"),
+ required=False,
+ initial=True,
+ help_text=_("Create a new version of this text (recommended)")
+ )
+
+ keep_comments = forms.BooleanField(label=_("Keep comments (optional)"),
+ required=False,
+ initial=True,
+ help_text=_("Keep comments (if not affected by the edit)")
+ )
+
+ class Meta:
+ model = TextVersion
+ fields = ('title', 'format', 'content', 'new_version', 'tags', 'note')
+
+ def save_into_text(self, text, request):
+ new_content = request.POST.get('content')
+ new_title = request.POST.get('title')
+ new_format = request.POST.get('format')
+ new_note = request.POST.get('note',None)
+ new_tags = request.POST.get('tags',None)
+ version = text.get_latest_version()
+ version.edit(new_title, new_format, new_content, new_tags, new_note, True)
+
+ return version
+
+ def save_new_version(self, text, request):
+ new_text_version = TextVersion.objects.duplicate(text.get_latest_version(), True)
+ new_text_version.user = request.user if request.user.is_authenticated() else None
+ new_text_version.note = request.POST.get('note','')
+ new_text_version.email = request.POST.get('email','')
+ new_text_version.name = request.POST.get('name','')
+ new_text_version.save()
+
+ new_content = request.POST.get('content')
+ new_title = request.POST.get('title')
+ new_format = request.POST.get('format')
+ new_note = request.POST.get('note',None)
+ new_tags = request.POST.get('tags',None)
+ new_text_version.edit(new_title, new_format, new_content, new_tags, new_note, True)
+
+ return new_text_version
+
+@has_perm_on_text('can_edit_text')
+def text_pre_edit(request, key, adminkey=None):
+ text = get_text_by_keys_or_404(key)
+
+ text_version = text.get_latest_version()
+ comments = text_version.get_comments() ;
+ new_content = request.POST['new_content']
+ new_format = request.POST['new_format']
+
+ # TODO: RBE : si commentaire mal forme : (position non existante : boom par key error)
+ _tomodify_comments, toremove_comments = compute_new_comment_positions(text_version.content, text_version.format, new_content, new_format, comments)
+ return HttpResponse(simplejson.dumps({'nb_removed' : len(toremove_comments) }))
+
+class EditTextFormAnon(EditTextForm):
+ name = forms.CharField(label=ugettext_lazy("Name (optional)"), widget=forms.TextInput, required=False)
+ email = forms.EmailField(label=ugettext_lazy("Email (optional)"), required=False)
+ content = forms.CharField(label=ugettext_lazy("Content"), required=True, widget=forms.Textarea(attrs={'rows':'30', 'cols': '70'}))
+
+ class Meta:
+ model = TextVersion
+ fields = ('title', 'format', 'content', 'tags', 'note', 'name', 'email')
+
+@has_perm_on_text('can_edit_text')
+def text_edit(request, key, adminkey=None):
+ text = get_text_by_keys_or_404(key)
+ text_version = text.get_latest_version()
+ if request.method == 'POST':
+ if request.user.is_authenticated():
+ form = EditTextForm(request.POST)
+ else:
+ form = EditTextFormAnon(request.POST)
+
+ if form.is_valid():
+ if request.POST.get('new_version'):
+ new_version = form.save_new_version(text, request)
+ register_activity(request, "text_edited_new_version", text=text, text_version=new_version)
+ else:
+ form.save_into_text(text, request)
+ register_activity(request, "text_edited", text=text)
+ return redirect(request, 'text-view', args=[text.key])
+ else:
+ default_data = {
+ 'content': text_version.content,
+ 'title': text_version.title,
+ 'format': text_version.format,
+ 'tags': text_version.tags,
+ 'new_version': NEW_TEXT_VERSION_ON_EDIT,
+ 'note' : text_version.note,
+ 'keep_comments' : True,
+ }
+ if request.user.is_authenticated():
+ form = EditTextForm(default_data)
+ else:
+ form = EditTextFormAnon(default_data)
+
+ template_dict = {'text' : text, 'form' : form}
+
+ return render_to_response('site/text_edit.html', template_dict , context_instance=RequestContext(request))
+
+# TODO: modif de la base => if POST
+@has_perm_on_text('can_edit_text')
+def text_revert(request, key, v1_nid, adminkey=None):
+ text = get_text_by_keys_or_404(key)
+
+ text.revert_to_version(v1_nid)
+ display_message(request, _(u'A new version (copied from version %(version_id)s) has been created') % {'version_id':v1_nid})
+
+ return HttpResponseRedirect(reverse('text-history', args=[text.key]))
+
+@has_perm_on_text('can_view_text')
+def text_attach(request, key, attach_key):
+ attach = Attachment.objects.get(key=attach_key, text_version__text__key=key)
+ content = file(attach.data.path).read()
+ mimetype, _encoding = mimetypes.guess_type(attach.data.path)
+ response = HttpResponse(content, mimetype)
+ return response
+
+
+
+
+def fix_anon_in_formset(formset):
+ # fix role choice in formset for anon (not all role are allowed)
+ role_field = [f.fields['role'] for f in formset.forms if f.instance.user == None][0]
+ role_field.choices = [(u'', u'---------')] + [(r.id, str(r)) for r in Role.objects.filter(anon = True)] # limit anon choices
+
+class BaseUserRoleFormSet(BaseModelFormSet):
+ def clean(self):
+ """Checks that anon users are given roles with anon=True."""
+ for i in range(0, self.total_form_count()):
+ form = self.forms[i]
+ print form.cleaned_data
+ user_role = form.cleaned_data['id']
+ if user_role.user == None:
+ role = form.cleaned_data['role']
+ if not role.anon:
+ # nasty stuff: cannot happen so not dealt with in tempate
+ logging.warn('Cannot give such role to anon user.')
+ raise forms.ValidationError, "Cannot give such role to anon user."
+
+#@has_perm_on_text('can_manage_text')
+#def xtext_share(request, key):
+# text = get_text_by_keys_or_404(key)
+# order_by = get_among(request.GET,'order',('user__username','-user__username','role__name','-role__name'),'user__username')
+#
+# UserRole.objects.create_userroles_text(text)
+# UserRoleFormSet = modelformset_factory(UserRole, fields=('role', ), extra=0, formset = BaseUserRoleFormSet)
+#
+# # put anon users on top no matter what the order says (TODO: ?)
+# userrole_queryset = UserRole.objects.filter(text=text).extra(select={'anon':'"cm_userrole"."user_id">-1'}).order_by('-anon',order_by)
+# if request.method == 'POST':
+# formset = UserRoleFormSet(request.POST, queryset = userrole_queryset)
+#
+# if formset.is_valid():
+# formset.save()
+# display_message(request, "Sharing updated.")
+# return HttpResponseRedirect(reverse('text-share',args=[text.key]))
+#
+# else:
+# formset = UserRoleFormSet(queryset = userrole_queryset)
+# fix_anon_in_formset(formset)
+#
+# global_anon_userrole = UserRole.objects.get(text=None, user=None)
+# return render_to_response('site/text_share.html', {'text' : text,
+# 'formset' : formset,
+# 'global_anon_userrole' : global_anon_userrole,
+# } , context_instance=RequestContext(request))
+
+# TODO: permission protection ? format value check ?
+def text_wysiwyg_preview(request, format):
+ html_content = ""
+ if request.POST : # if satisfied in the no html case, in html case : no POST (cf. markitup) previewTemplatePath and previewParserPath
+ html_content = pandoc_convert(request.POST['data'], format, "html", full=False)
+
+ return render_to_response('site/wysiwyg_preview.html', {'content':html_content} , context_instance=RequestContext(request))
+ #return HttpResponse(pandoc_convert(content, format, "html", full=False))
+
+@has_perm_on_text('can_manage_text')
+def text_share(request, key):
+ text = get_text_by_keys_or_404(key)
+ order_by = get_among(request.GET,'order',('user__username',
+ 'user__email',
+ '-user__username',
+ '-user__email',
+ 'role__name',
+ '-role__name',
+ ),
+ 'user__username')
+ paginate_by = 10
+
+ UserRole.objects.create_userroles_text(text)
+
+ if request.method == 'POST':
+ if 'save' in request.POST:
+ user_profile_keys_roles = get_keys_from_dict(request.POST, 'user-role-')
+ count = 0
+ for user_profile_key in user_profile_keys_roles:
+ role_id = user_profile_keys_roles[user_profile_key]
+ if not user_profile_key:
+ user_role = UserRole.objects.get(user = None, text = text)
+ else:
+ user_role = UserRole.objects.get(user__userprofile__key = user_profile_key, text = text)
+ if (role_id != u'' or user_role.role_id!=None) and role_id!=unicode(user_role.role_id):
+ if role_id:
+ user_role.role_id = int(role_id)
+ else:
+ user_role.role_id = None
+ user_role.save()
+ count += 1
+ display_message(request, _(u'%(count)i user(s) role modified') %{'count':count})
+ return HttpResponseRedirect(reverse('text-share', args=[text.key]))
+
+ anon_role = UserRole.objects.get(user = None, text = text).role
+ global_anon_role = UserRole.objects.get(user = None, text = None).role
+
+ context = {
+ 'anon_role' : anon_role,
+ 'global_anon_role' : global_anon_role,
+ 'all_roles' : Role.objects.all(),
+ 'anon_roles' : Role.objects.filter(anon = True),
+ 'text' : text,
+ }
+
+ return object_list(request, UserRole.objects.filter(text=text).filter(~Q(user=None)).order_by(order_by),
+ template_name = 'site/text_share.html',
+ paginate_by = paginate_by,
+ extra_context = context,
+ )
+
+
+class SettingsTextForm(ModelForm):
+ # example name = forms.CharField(label=_("Name (optional)"), widget=forms.TextInput, required=False)
+
+ class Meta:
+ model = TextVersion
+ fields = ('mod_posteriori',)
+
+@has_perm_on_text('can_manage_text')
+def text_settings(request, key):
+ text = get_text_by_keys_or_404(key)
+
+ text_version = text.get_latest_version()
+ if request.method == 'POST':
+ form = SettingsTextForm(request.POST, instance = text_version)
+
+ if form.is_valid():
+ form.save()
+ display_message(request, _(u'Text settings updated'))
+ return redirect(request, 'text-view', args=[text.key])
+ else:
+ form = SettingsTextForm(instance = text_version)
+
+ template_dict = {'text' : text, 'form' : form}
+
+ return render_to_response('site/text_settings.html', template_dict , context_instance=RequestContext(request))
+