Skip to content
This repository has been archived by the owner on Aug 26, 2022. It is now read-only.

Commit

Permalink
Merge pull request #342 from groovecoder/revision-feed-diff-767537
Browse files Browse the repository at this point in the history
fix bug 767537 - inline diff in rss feed
  • Loading branch information
darkwing committed Jul 5, 2012
2 parents 93a1e9c + f8fed8c commit 71f06fa
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 28 deletions.
56 changes: 36 additions & 20 deletions apps/wiki/feeds.py
Expand Up @@ -6,22 +6,21 @@

import jingo

from django.conf import settings
from django.contrib.auth.models import User
from django.contrib.syndication.views import Feed, FeedDoesNotExist
from django.shortcuts import get_object_or_404
from django.utils.feedgenerator import (SyndicationFeed, Rss201rev2Feed,
Atom1Feed, get_tag_uri)
Atom1Feed)
from django.utils.html import escape
import django.utils.simplejson as json
from django.shortcuts import get_object_or_404

from django.utils.translation import ugettext as _

from django.contrib.auth.models import User
from django.conf import settings

from sumo.urlresolvers import reverse
from devmo.models import UserProfile

from wiki.models import (Document, Revision, HelpfulVote, EditorToolbar,
ReviewTag,)
from wiki.helpers import diff_table, diff_inline
from wiki.models import Document, Revision


MAX_FEED_ITEMS = getattr(settings, 'MAX_FEED_ITEMS', 15)
Expand Down Expand Up @@ -196,24 +195,41 @@ def items(self):
return Revision.objects.order_by('-created')[:50]

def item_title(self, item):
return "%s edited %s" % (item.creator.username, item.title)
return "%s/%s" % (item.document.locale, item.document.full_path)

def item_description(self, item):
previous = item.get_previous()
if previous is None:
return 'Document created'
return item.comment
return '<p>Created by: %s</p>' % item.creator.username
# TODO: put this in a jinja template if django syndication will let us
by = '<p>Edited by: %s</p>' % item.creator.username
comment = '<p>Comment: %s</p>' % item.comment
diff = ("Diff:<blockquote>%s</blockquote>" % (
diff_inline(previous.content, item.content)))
link_cell = '<td><a href="%s">%s</a></td>'
view_cell = link_cell % (reverse('wiki.document',
args=[item.document.full_path]),
_('View Page'))
edit_cell = link_cell % (reverse('wiki.edit_document',
args=[item.document.full_path]),
_('Edit Page'))
compare_cell = link_cell % (reverse('wiki.compare_revisions',
args=[item.document.full_path])
+ '?' +
urllib.urlencode({'from': previous.id,
'to': item.id}),
_('Show comparison'))
history_cell = link_cell % (reverse('wiki.document_revisions',
args=[item.document.full_path]),
_('History'))
links_table = '<table border="0" width="80%">'
links_table = links_table + '<tr>%s%s%s%s</tr>' % (view_cell, edit_cell, compare_cell, history_cell)
links_table = links_table + '</table>'
description = "%s%s%s%s" % (by, comment, diff, links_table)
return description

def item_link(self, item):
previous = item.get_previous()
if previous is None:
return item.document.get_absolute_url()
compare_url = reverse('wiki.compare_revisions',
args=[item.document.slug])
qs = {'from': previous.id,
'to': item.id}
return "%s?%s" % (self.request.build_absolute_uri(compare_url),
urllib.urlencode(qs))
return reverse('wiki.document', args=[item.document.full_path])

def item_pubdate(self, item):
return item.created
Expand Down
61 changes: 59 additions & 2 deletions apps/wiki/helpers.py
@@ -1,22 +1,79 @@
import difflib
import re

import constance.config
from difflib import HtmlDiff
from jingo import register
import jinja2

from wiki import DIFF_WRAP_COLUMN
from wiki import parser


# http://stackoverflow.com/q/774316/571420
def show_diff(seqm):
"""Unify operations between two compared strings
seqm is a difflib.SequenceMatcher instance whose a & b are strings"""
lines = constance.config.FEED_DIFF_CONTEXT_LINES
full_output= []
for opcode, a0, a1, b0, b1 in seqm.get_opcodes():
if opcode == 'equal':
full_output.append(seqm.a[a0:a1])
elif opcode == 'insert':
full_output.append("<ins>" + seqm.b[b0:b1] + "</ins>")
elif opcode == 'delete':
full_output.append("<del>" + seqm.a[a0:a1] + "</del>")
elif opcode == 'replace':
full_output.append("&nbsp;<del>" + seqm.a[a0:a1] + "</del>&nbsp;")
full_output.append("&nbsp;<ins>" + seqm.b[b0:b1] + "</ins>&nbsp;")
else:
raise RuntimeError, "unexpected opcode"
output = []
whitespace_change = False
for piece in full_output:
if '<ins>' in piece or '<del>' in piece:
# a change
if re.match('<(ins|del)>\W+</(ins|del)>', piece):
# the change is whitespace,
# ignore it and remove preceding context
output = output[:-lines]
whitespace_change = True
continue
else:
output.append(piece)
else:
context_lines = piece.splitlines()
if output == []:
# first context only shows preceding lines for next change
context = ['<p>...</p>'] + context_lines[-lines:]
elif whitespace_change:
# context shows preceding lines for next change
context = ['<p>...</p>'] + context_lines[-lines:]
whitespace_change = False
else:
# context shows subsequent lines
# and preceding lines for next change
context = context_lines[:lines] + ['<p>...</p>'] + context_lines[-lines:]
output = output + context
# remove extra context from the very end
output = output[:-lines]
return ''.join(output)

@register.function
def diff_table(content_from, content_to):
"""Creates an HTML diff of the passed in content_from and content_to."""
html_diff = HtmlDiff(wrapcolumn=DIFF_WRAP_COLUMN)
html_diff = difflib.HtmlDiff(wrapcolumn=DIFF_WRAP_COLUMN)
from_lines = content_from.splitlines()
to_lines = content_to.splitlines()
diff = html_diff.make_table(from_lines, to_lines, context=True,
numlines=constance.config.DIFF_CONTEXT_LINES)
return jinja2.Markup(diff)

@register.function
def diff_inline(content_from, content_to):
sm = difflib.SequenceMatcher(None, content_from, content_to)
diff = show_diff(sm)
return jinja2.Markup(diff)


@register.function
def generate_video(v):
Expand Down
18 changes: 13 additions & 5 deletions apps/wiki/tests/test_views.py
Expand Up @@ -1186,18 +1186,26 @@ def test_revisions_feed(self):
for i in xrange(1, 6):
r = revision(save=True, document=d,
title='HTML9', comment='Revision %s' % i,
content = "Some Content %s" % i,
is_approved=True,
created=datetime.datetime.now()\
+ datetime.timedelta(seconds=5*i))

resp = self.client.get(reverse('wiki.feeds.recent_revisions',
args=(), kwargs={'format': 'rss'}))
eq_(200, resp.status_code)
feed = pq(resp.content)
eq_(5, len(feed.find('item')))
for i, item in enumerate(feed.find('item')):
desc_text = pq(item).find('description').text()
ok_('by: testuser' in desc_text)
if "Edited" in desc_text:
ok_('Comment: Revision' in desc_text)
ok_('<ins>' in desc_text)
ok_('$compare?to' in desc_text)
ok_('$edit' in desc_text)
ok_('$history' in desc_text)

ok_('Document created' in resp.content)
ok_('Revision 3' in resp.content)
ok_('$compare?to' in resp.content)


class SectionEditingResourceTests(TestCaseBase):
fixtures = ['test_users.json']
Expand Down Expand Up @@ -1838,4 +1846,4 @@ def test_legacy_redirect(self):
'filename': f['filename']})
resp = self.client.get(mindtouch_url)
eq_(301, resp.status_code)
ok_(a.get_absolute_url() in resp['Location'])
ok_(a.get_absolute_url() in resp['Location'])
7 changes: 6 additions & 1 deletion settings.py
Expand Up @@ -998,7 +998,12 @@ def read_only_mode(env):

DIFF_CONTEXT_LINES = (
0,
'Number of lines of context to show in diff displays.',
'Number of lines of context to show in diff display.',
),

FEED_DIFF_CONTEXT_LINES = (
3,
'Number of lines of context to show in feed diff display.',
),
)

Expand Down

0 comments on commit 71f06fa

Please sign in to comment.