# HG changeset patch # User raph # Date 1265382092 -3600 # Node ID 0f2c5744b39b7244f6279316c41e3e4d035ddf0f # Parent 03106bfa48458b753627db11b6895ca91969b51d cleanup diff files / add experimental diff diff -r 03106bfa4845 -r 0f2c5744b39b src/cm/converters/__init__.py --- a/src/cm/converters/__init__.py Fri Feb 05 15:15:46 2010 +0100 +++ b/src/cm/converters/__init__.py Fri Feb 05 16:01:32 2010 +0100 @@ -1,6 +1,6 @@ from pandoc_converters import pandoc_convert import chardet -from cm.utils.string import to_unicode +from cm.utils.string_utils import to_unicode import re # TODO: move that in text_base: save images diff -r 03106bfa4845 -r 0f2c5744b39b src/cm/converters/pandoc_converters.py --- a/src/cm/converters/pandoc_converters.py Fri Feb 05 15:15:46 2010 +0100 +++ b/src/cm/converters/pandoc_converters.py Fri Feb 05 16:01:32 2010 +0100 @@ -10,7 +10,7 @@ from tempfile import mkstemp import StringIO import tidy -from cm.utils.string import to_unicode +from cm.utils.string_utils import to_unicode PANDOC_BIN = "pandoc" PANDOC_OPTIONS = "--sanitize-html " diff -r 03106bfa4845 -r 0f2c5744b39b src/cm/diff.py --- a/src/cm/diff.py Fri Feb 05 15:15:46 2010 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,115 +0,0 @@ -import difflib, string -from django.utils.translation import ugettext as _ - -def isatag(x): return x.startswith("<") and x.endswith(">") - -def text_diff(a, b): - res = [] - a, b = createlist(a), createlist(b) - s = difflib.SequenceMatcher(isatag, a, b) - for e in s.get_opcodes(): - if e[0] == "replace": - res.append(''+''.join(a[e[1]:e[2]]) + ''+''.join(b[e[3]:e[4]])+"") - elif e[0] == "delete": - res.append(''+ ''.join(a[e[1]:e[2]]) + "") - elif e[0] == "insert": - res.append(''+''.join(b[e[3]:e[4]]) + "") - elif e[0] == "equal": - res.append(''.join(b[e[3]:e[4]])) - else: - raise "error parsing %s" %(e[0]) - return ''.join(res) - -COLORS = [ -'#FF0000', -'#EE0000', -'#DD0000', -'#CC0000', -'#BB0000', -'#AA0000', -'#990000', -'#880000', -'#770000', -] - -from colorsys import hls_to_rgb, hsv_to_rgb - -def generatePastelColors(n): - s = .4 - v = .99 - return ['#'+''.join((hex(int(r*255))[2:],hex(int(g*255))[2:],hex(int(b*255))[2:])) for r,g,b in [hsv_to_rgb(h/200.,s,v) for h in range(1,100,100/n)]] - - -DEFAULT_COLOR = '#D9D9D9' -def get_colors(authors, last_white = False): - """ - returns a dict for authors's colors (with last one white if 'last_white') - """ - authors = set(authors) - #new_colors = list(tuple(COLORS)) - new_colors = generatePastelColors(len(set(authors))) - res = [] - if None in authors or '' in authors: - res = [(_(u'unknown'),DEFAULT_COLOR) ] - res.extend([(author,new_colors.pop()) for author in authors if author]) - #if authors[-1]: - # res[authors[-1]] = '#FFFFFF' - return res - - -def text_history(versions, authors): - res = versions[0] - colors = get_colors(authors) - for ind in range(len(versions)-1): - author = authors[ind] - color = colors.get(author,DEFAULT_COLOR) - v_2 = versions[ind + 1] - res = text_diff_add(v_2, res, color) - return res,colors.items() - -from cm.utils.html import surrond_text_node - -def text_diff_add(a,b, color): - res = [] - a, b = createlist(a), createlist(b) - s = difflib.SequenceMatcher(isatag, a, b) - for e in s.get_opcodes(): - if e[0] == "replace": - html_chunk = ''.join(b[e[3]:e[4]]) - new_html_chunk = surrond_text_node(html_chunk,'' %color,'') - res.append(new_html_chunk) - #res.append('' %color+''.join(b[e[3]:e[4]])+"") - elif e[0] == "delete": - pass - elif e[0] == "insert": - html_chunk = ''.join(b[e[3]:e[4]]) - new_html_chunk = surrond_text_node(html_chunk,'' %color,'') - res.append(new_html_chunk) - #res.append('' %color+''.join(b[e[3]:e[4]]) + "") - elif e[0] == "equal": - res.append(''.join(b[e[3]:e[4]])) - else: - raise "error parsing %s" %(e[0]) - return ''.join(res) - -def createlist(x, b=0): - mode = 'char' - cur = '' - out = [] - for c in x: - if mode == 'tag': - if c == '>': - if b: cur += ']' - else: cur += c - out.append(cur); cur = ''; mode = 'char' - else: cur += c - elif mode == 'char': - if c == '<': - out.append(cur) - if b: cur = '[' - else: cur = c - mode = 'tag' - elif c in string.whitespace: out.append(cur+c); cur = '' - else: cur += c - out.append(cur) - return filter(lambda x: x is not '', out) diff -r 03106bfa4845 -r 0f2c5744b39b src/cm/ext/diff.py --- a/src/cm/ext/diff.py Fri Feb 05 15:15:46 2010 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,271 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2004-2009 Edgewall Software -# Copyright (C) 2004-2006 Christopher Lenz -# All rights reserved. -# -# This software is licensed as described in the file COPYING, which -# you should have received as part of this distribution. The terms -# are also available at http://trac.edgewall.org/wiki/TracLicense. -# -# This software consists of voluntary contributions made by many -# individuals. For the exact contribution history, see the revision -# history and logs, available at http://trac.edgewall.org/log/. -# -# Author: Christopher Lenz - -from trac.util.html import escape, Markup -from trac.util.text import expandtabs - -from difflib import SequenceMatcher -import re - -__all__ = ['get_diff_options', 'hdf_diff', 'diff_blocks', 'unified_diff'] - - -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 _get_opcodes(fromlines, tolines, ignore_blank_lines=False, - ignore_case=False, ignore_space_changes=False): - """ - Generator built on top of SequenceMatcher.get_opcodes(). - - This function detects line changes that should be ignored and emits them - as tagged as 'equal', possibly joined with the preceding and/or following - 'equal' block. - """ - - def is_ignorable(tag, fromlines, tolines): - if tag == 'delete' and ignore_blank_lines: - if ''.join(fromlines) == '': - return True - elif tag == 'insert' and ignore_blank_lines: - if ''.join(tolines) == '': - return True - elif tag == 'replace' and (ignore_case or ignore_space_changes): - if len(fromlines) != len(tolines): - return False - def f(str): - if ignore_case: - str = str.lower() - if ignore_space_changes: - str = ' '.join(str.split()) - return str - for i in range(len(fromlines)): - if f(fromlines[i]) != f(tolines[i]): - return False - return True - - matcher = SequenceMatcher(None, fromlines, tolines) - previous = None - for tag, i1, i2, j1, j2 in matcher.get_opcodes(): - if tag == 'equal': - if previous: - previous = (tag, previous[1], i2, previous[3], j2) - else: - previous = (tag, i1, i2, j1, j2) - else: - if is_ignorable(tag, fromlines[i1:i2], tolines[j1:j2]): - if previous: - previous = 'equal', previous[1], i2, previous[3], j2 - else: - previous = 'equal', i1, i2, j1, j2 - continue - if previous: - yield previous - yield tag, i1, i2, j1, j2 - previous = None - - if previous: - yield previous - -def _group_opcodes(opcodes, n=3): - """ - Python 2.2 doesn't have SequenceMatcher.get_grouped_opcodes(), so let's - provide equivalent here. The opcodes parameter can be any iterable or - sequence. - - This function can also be used to generate full-context diffs by passing - None for the parameter n. - """ - # Full context produces all the opcodes - if n is None: - yield list(opcodes) - return - - # Otherwise we leave at most n lines with the tag 'equal' before and after - # every change - nn = n + n - group = [] - for idx, (tag, i1, i2, j1, j2) in enumerate(opcodes): - if idx == 0 and tag == 'equal': # Fixup leading unchanged block - i1, j1 = max(i1, i2 - n), max(j1, j2 - n) - elif tag == 'equal' and i2 - i1 > nn: - group.append((tag, i1, min(i2, i1 + n), j1, min(j2, j1 + n))) - yield group - group = [] - i1, j1 = max(i1, i2 - n), max(j1, j2 - n) - group.append((tag, i1, i2, j1 ,j2)) - - if group and not (len(group) == 1 and group[0][0] == 'equal'): - if group[-1][0] == 'equal': # Fixup trailing unchanged block - tag, i1, i2, j1, j2 = group[-1] - group[-1] = tag, i1, min(i2, i1 + n), j1, min(j2, j1 + n) - yield group - -def hdf_diff(*args, **kwargs): - return diff_blocks(*args, **kwargs) - -def diff_blocks(fromlines, tolines, context=None, tabwidth=8, - ignore_blank_lines=0, ignore_case=0, ignore_space_changes=0): - """Return an array that is adequate for adding to the data dictionary - - See the diff_div.html template. - """ - - type_map = {'replace': 'mod', 'delete': 'rem', 'insert': 'add', - 'equal': 'unmod'} - - space_re = re.compile(' ( +)|^ ') - def htmlify(match): - div, mod = divmod(len(match.group(0)), 2) - return div * '  ' + mod * ' ' - - def markup_intraline_changes(opcodes): - for tag, i1, i2, j1, j2 in opcodes: - if tag == 'replace' and i2 - i1 == j2 - j1: - for i in range(i2 - i1): - fromline, toline = fromlines[i1 + i], tolines[j1 + i] - (start, end) = _get_change_extent(fromline, toline) - if start != 0 or end != 0: - last = end+len(fromline) - fromlines[i1+i] = fromline[:start] + '\0' + fromline[start:last] + \ - '\1' + fromline[last:] - last = end+len(toline) - tolines[j1+i] = toline[:start] + '\0' + toline[start:last] + \ - '\1' + toline[last:] - yield tag, i1, i2, j1, j2 - - changes = [] - opcodes = _get_opcodes(fromlines, tolines, ignore_blank_lines, ignore_case, - ignore_space_changes) - for group in _group_opcodes(opcodes, context): - blocks = [] - last_tag = None - for tag, i1, i2, j1, j2 in markup_intraline_changes(group): - if tag != last_tag: - blocks.append({'type': type_map[tag], - 'base': {'offset': i1, 'lines': []}, - 'changed': {'offset': j1, 'lines': []}}) - if tag == 'equal': - for line in fromlines[i1:i2]: - line = line.expandtabs(tabwidth) - line = space_re.sub(htmlify, escape(line, quotes=False)) - blocks[-1]['base']['lines'].append(Markup(unicode(line))) - for line in tolines[j1:j2]: - line = line.expandtabs(tabwidth) - line = space_re.sub(htmlify, escape(line, quotes=False)) - blocks[-1]['changed']['lines'].append(Markup(unicode(line))) - else: - if tag in ('replace', 'delete'): - for line in fromlines[i1:i2]: - line = expandtabs(line, tabwidth, '\0\1') - line = escape(line, quotes=False) - line = ''.join([space_re.sub(htmlify, seg) - for seg in line.split('\0')]) - line = line.replace('\1', '') - blocks[-1]['base']['lines'].append( - Markup(unicode(line))) - if tag in ('replace', 'insert'): - for line in tolines[j1:j2]: - line = expandtabs(line, tabwidth, '\0\1') - line = escape(line, quotes=False) - line = ''.join([space_re.sub(htmlify, seg) - for seg in line.split('\0')]) - line = line.replace('\1', '') - blocks[-1]['changed']['lines'].append( - Markup(unicode(line))) - changes.append(blocks) - return changes - -def unified_diff(fromlines, tolines, context=None, ignore_blank_lines=0, - ignore_case=0, ignore_space_changes=0): - opcodes = _get_opcodes(fromlines, tolines, ignore_blank_lines, ignore_case, - ignore_space_changes) - for group in _group_opcodes(opcodes, context): - i1, i2, j1, j2 = group[0][1], group[-1][2], group[0][3], group[-1][4] - if i1 == 0 and i2 == 0: - i1, i2 = -1, -1 # support for 'A'dd changes - yield '@@ -%d,%d +%d,%d @@' % (i1 + 1, i2 - i1, j1 + 1, j2 - j1) - for tag, i1, i2, j1, j2 in group: - if tag == 'equal': - for line in fromlines[i1:i2]: - yield ' ' + line - else: - if tag in ('replace', 'delete'): - for line in fromlines[i1:i2]: - yield '-' + line - if tag in ('replace', 'insert'): - for line in tolines[j1:j2]: - yield '+' + line - -def get_diff_options(req): - options_data = {} - data = {'options': options_data} - - def get_bool_option(name, default=0): - pref = int(req.session.get('diff_' + name, default)) - arg = int(req.args.has_key(name)) - if req.args.has_key('update') and arg != pref: - req.session['diff_' + name] = arg - else: - arg = pref - return arg - - pref = req.session.get('diff_style', 'inline') - style = req.args.get('style', pref) - if req.args.has_key('update') and style != pref: - req.session['diff_style'] = style - data['style'] = style - - pref = int(req.session.get('diff_contextlines', 2)) - try: - arg = int(req.args.get('contextlines', pref)) - except ValueError: - arg = -1 - if req.args.has_key('update') and arg != pref: - req.session['diff_contextlines'] = arg - options = ['-U%d' % arg] - options_data['contextlines'] = arg - - arg = get_bool_option('ignoreblanklines') - if arg: - options.append('-B') - options_data['ignoreblanklines'] = arg - - arg = get_bool_option('ignorecase') - if arg: - options.append('-i') - options_data['ignorecase'] = arg - - arg = get_bool_option('ignorewhitespace') - if arg: - options.append('-b') - options_data['ignorewhitespace'] = arg - - return (style, options, data) diff -r 03106bfa4845 -r 0f2c5744b39b src/cm/urls.py --- a/src/cm/urls.py Fri Feb 05 15:15:46 2010 +0100 +++ b/src/cm/urls.py Fri Feb 05 16:01:32 2010 +0100 @@ -64,14 +64,14 @@ url(r'^text/(?P\w*)/settings/$', text_settings, name="text-settings"), url(r'^text/(?P\w*)/history/$', text_history, name="text-history"), url(r'^text/(?P\w*)/history-version/(?P\w*)/$', text_history_version, name="text-history-version"), - url(r'^text/(?P\w*)/history-compare/(?P\w*)/(?P\w*)/$', text_history_compare, name="text-history-compare"), + url(r'^text/(?P\w*)/history-compare/(?P\w*)/(?P\w*)/(?P\d*)$', text_history_compare, name="text-history-compare"), url(r'^text/(?P\w*)/revert/(?P\w*)/$', text_revert, name="text-revert"), url(r'^text/(?P\w*)/attach/(?P\w*)/$', text_attach, name="text-attach"), url(r'^text/(?P\w*)/delete/$', text_delete, name="text-delete"), url(r'^text/(?P\w*)/(?P\w*)/delete/$', text_version_delete, name="text-version-delete"), url(r'^text/(?P\w*)/export/(?P\w*)/(?P\w*)/(?P\w*)/(?P\w*)/$', text_export, name="text-export"), url(r'^text/(?P\w*)/history/$', text_history, name="text-history"), - url(r'^text/(?P\w*)/diff/(?P\w*)/(?P\w*)/$', text_diff, name="text-diff"), + #url(r'^text/(?P\w*)/diff/(?P\w*)/(?P\w*)/$', text_diff, name="text-diff"), # url(r'^text/(?P\w*)/version/(?P\w*)/$', text_version, name="text-version"), # main client frame diff -r 03106bfa4845 -r 0f2c5744b39b src/cm/utils/diff.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/cm/utils/diff.py Fri Feb 05 16:01:32 2010 +0100 @@ -0,0 +1,115 @@ +import difflib, string +from django.utils.translation import ugettext as _ + +def isatag(x): return x.startswith("<") and x.endswith(">") + +def text_diff(a, b): + res = [] + a, b = createlist(a), createlist(b) + s = difflib.SequenceMatcher(isatag, a, b) + for e in s.get_opcodes(): + if e[0] == "replace": + res.append(''+''.join(a[e[1]:e[2]]) + ''+''.join(b[e[3]:e[4]])+"") + elif e[0] == "delete": + res.append(''+ ''.join(a[e[1]:e[2]]) + "") + elif e[0] == "insert": + res.append(''+''.join(b[e[3]:e[4]]) + "") + elif e[0] == "equal": + res.append(''.join(b[e[3]:e[4]])) + else: + raise "error parsing %s" %(e[0]) + return ''.join(res) + +COLORS = [ +'#FF0000', +'#EE0000', +'#DD0000', +'#CC0000', +'#BB0000', +'#AA0000', +'#990000', +'#880000', +'#770000', +] + +from colorsys import hls_to_rgb, hsv_to_rgb + +def generatePastelColors(n): + s = .4 + v = .99 + return ['#'+''.join((hex(int(r*255))[2:],hex(int(g*255))[2:],hex(int(b*255))[2:])) for r,g,b in [hsv_to_rgb(h/200.,s,v) for h in range(1,100,100/n)]] + + +DEFAULT_COLOR = '#D9D9D9' +def get_colors(authors, last_white = False): + """ + returns a dict for authors's colors (with last one white if 'last_white') + """ + authors = set(authors) + #new_colors = list(tuple(COLORS)) + new_colors = generatePastelColors(len(set(authors))) + res = [] + if None in authors or '' in authors: + res = [(_(u'unknown'),DEFAULT_COLOR) ] + res.extend([(author,new_colors.pop()) for author in authors if author]) + #if authors[-1]: + # res[authors[-1]] = '#FFFFFF' + return res + + +def text_history(versions, authors): + res = versions[0] + colors = get_colors(authors) + for ind in range(len(versions)-1): + author = authors[ind] + color = colors.get(author,DEFAULT_COLOR) + v_2 = versions[ind + 1] + res = text_diff_add(v_2, res, color) + return res,colors.items() + +from cm.utils.html import surrond_text_node + +def text_diff_add(a,b, color): + res = [] + a, b = createlist(a), createlist(b) + s = difflib.SequenceMatcher(isatag, a, b) + for e in s.get_opcodes(): + if e[0] == "replace": + html_chunk = ''.join(b[e[3]:e[4]]) + new_html_chunk = surrond_text_node(html_chunk,'' %color,'') + res.append(new_html_chunk) + #res.append('' %color+''.join(b[e[3]:e[4]])+"") + elif e[0] == "delete": + pass + elif e[0] == "insert": + html_chunk = ''.join(b[e[3]:e[4]]) + new_html_chunk = surrond_text_node(html_chunk,'' %color,'') + res.append(new_html_chunk) + #res.append('' %color+''.join(b[e[3]:e[4]]) + "") + elif e[0] == "equal": + res.append(''.join(b[e[3]:e[4]])) + else: + raise "error parsing %s" %(e[0]) + return ''.join(res) + +def createlist(x, b=0): + mode = 'char' + cur = '' + out = [] + for c in x: + if mode == 'tag': + if c == '>': + if b: cur += ']' + else: cur += c + out.append(cur); cur = ''; mode = 'char' + else: cur += c + elif mode == 'char': + if c == '<': + out.append(cur) + if b: cur = '[' + else: cur = c + mode = 'tag' + elif c in string.whitespace: out.append(cur+c); cur = '' + else: cur += c + out.append(cur) + return filter(lambda x: x is not '', out) diff -r 03106bfa4845 -r 0f2c5744b39b src/cm/utils/string.py --- a/src/cm/utils/string.py Fri Feb 05 15:15:46 2010 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,15 +0,0 @@ -import chardet - -def to_unicode(input): - if type(input) == str: - res = None - for encoding in [chardet.detect(input)['encoding'], 'utf8', 'latin1']: - try: - res = unicode(input, encoding) - break; - except UnicodeDecodeError: - pass - if not res: - raise Exception('UnicodeDecodeError: could not decode') - return res - return input \ No newline at end of file diff -r 03106bfa4845 -r 0f2c5744b39b src/cm/utils/string_utils.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/cm/utils/string_utils.py Fri Feb 05 16:01:32 2010 +0100 @@ -0,0 +1,15 @@ +import chardet + +def to_unicode(input): + if type(input) == str: + res = None + for encoding in [chardet.detect(input)['encoding'], 'utf8', 'latin1']: + try: + res = unicode(input, encoding) + break; + except UnicodeDecodeError: + pass + if not res: + raise Exception('UnicodeDecodeError: could not decode') + return res + return input \ No newline at end of file diff -r 03106bfa4845 -r 0f2c5744b39b src/cm/views/__init__.py --- a/src/cm/views/__init__.py Fri Feb 05 15:15:46 2010 +0100 +++ b/src/cm/views/__init__.py Fri Feb 05 16:01:32 2010 +0100 @@ -1,5 +1,3 @@ -from cm.diff import text_diff as _text_diff, \ - text_history as inner_text_history, get_colors from cm.message import display_message from cm.models import Text, TextVersion, Attachment, Comment, Configuration from cm.security import has_global_perm diff -r 03106bfa4845 -r 0f2c5744b39b src/cm/views/texts.py --- a/src/cm/views/texts.py Fri Feb 05 15:15:46 2010 +0100 +++ b/src/cm/views/texts.py Fri Feb 05 16:01:32 2010 +0100 @@ -3,8 +3,6 @@ from cm.client import jsonize, get_filter_datas, edit_comment, remove_comment, \ add_comment, RequestComplexEncoder, comments_thread, own_notify 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 * @@ -471,12 +469,16 @@ context_instance=RequestContext(request)) #@has_perm_on_text('can_view_text') -def text_history_compare(request, key, v1_version_key, v2_version_key): +def text_history_compare(request, key, v1_version_key, v2_version_key, mode=''): text = get_text_by_keys_or_404(key) v1 = get_textversion_by_keys_or_404(v1_version_key, key=key) v2 = get_textversion_by_keys_or_404(v2_version_key, key=key) content = get_uniffied_inner_diff_table(cleanup_textarea(v1.content), cleanup_textarea(v2.content)) + if mode=='1': + # alternate diff + from cm.utils.diff import text_diff + content = text_diff(v1.get_content(), v2.get_content()) template_dict = { 'text' : text, @@ -582,32 +584,11 @@ # 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)) - -# commented out, unused suspected -#@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"))