86
101
reverted=False).order_by('-revision')[0]
87
102
except IndexError:
88
103
return ChangeSet.objects.none()
90
105
def all_images(self):
91
106
return self.images.all()
93
108
def new_revision(self, old_content, old_title, old_markup,
95
"""Create a new ChangeSet with the old content."""
109
comment, editor_ip, editor):
110
'''Create a new ChangeSet with the old content.'''
97
112
content_diff = diff(self.content, old_content)
99
114
cs = ChangeSet.objects.create(
103
119
old_title=old_title,
104
120
old_markup=old_markup,
109
def revert_to(self, revision, editor=None):
110
"""Revert the article to a previuos state, by revision number."""
125
def revert_to(self, revision, editor_ip, editor=None):
126
""" Revert the article to a previuos state, by revision number.
111
128
changeset = self.changeset_set.get(revision=revision)
112
changeset.reapply(editor)
129
changeset.reapply(editor_ip, editor)
114
131
def compare(self, from_revision, to_revision):
115
"""Compares to revisions of this article."""
132
""" Compares to revisions of this article.
116
134
changeset = self.changeset_set.get(revision=to_revision)
117
135
return changeset.compare_to(from_revision)
119
138
def __unicode__(self):
120
139
return self.title
123
143
class ChangeSetManager(models.Manager):
125
145
def all_later(self, revision):
126
"""Return all changes later to the given revision.
146
""" Return all changes later to the given revision.
128
147
Util when we want to revert to the given revision.
131
149
return self.filter(revision__gt=int(revision))
152
class NonRevertedChangeSetManager(ChangeSetManager):
154
def get_default_queryset(self):
155
super(PublishedBookManager, self).get_queryset().filter(
134
159
class ChangeSet(models.Model):
135
160
"""A report of an older version of some Article."""
137
162
article = models.ForeignKey(Article, verbose_name=_(u"Article"))
139
# Editor identification -- logged
164
# Editor identification -- logged or anonymous
140
165
editor = models.ForeignKey(User, verbose_name=_(u'Editor'),
167
editor_ip = models.GenericIPAddressField(_(u"IP Address of the Editor"))
143
169
# Revision number, starting from 1
144
170
revision = models.IntegerField(_(u"Revision Number"))
155
181
reverted = models.BooleanField(_(u"Reverted Revision"), default=False)
157
183
objects = ChangeSetManager()
184
non_reverted_objects = NonRevertedChangeSetManager()
160
187
verbose_name = _(u'Change set')
161
188
verbose_name_plural = _(u'Change sets')
162
get_latest_by = 'modified'
189
get_latest_by = 'modified'
163
190
ordering = ('-revision',)
166
193
def __unicode__(self):
167
194
return u'#%s' % self.revision
169
197
def get_absolute_url(self):
170
198
if self.article.group is None:
171
return reverse('wiki_changeset', kwargs={
172
'title': self.article.title,
173
'revision': self.revision
175
return reverse('wiki_changeset', kwargs={
176
'group_slug': self.article.group.slug,
177
'title': self.article.title,
178
'revision': self.revision,
199
return ('wiki_changeset', (),
200
{'title': self.article.title,
201
'revision': self.revision})
202
return ('wiki_changeset', (),
203
{'group_slug': self.article.group.slug,
204
'title': self.article.title,
205
'revision': self.revision})
181
208
def is_anonymous_change(self):
182
209
return self.editor is None
184
def reapply(self, editor):
185
"""Return the Article to this revision."""
211
def reapply(self, editor_ip, editor):
212
""" Return the Article to this revision.
187
215
# XXX Would be better to exclude reverted revisions
188
216
# and revisions previous/next to reverted ones
213
241
article.new_revision(
214
242
old_content=old_content, old_title=old_title,
215
243
old_markup=old_markup,
216
comment='Reverted to revision #%s' % self.revision,
244
comment="Reverted to revision #%s" % self.revision,
245
editor_ip=editor_ip, editor=editor)
222
249
if None not in (notification, self.editor):
223
notification.send([self.editor], 'wiki_revision_reverted',
250
notification.send([self.editor], "wiki_revision_reverted",
224
251
{'revision': self, 'article': self.article})
226
253
def save(self, *args, **kwargs):
227
"""Saves the article with a new revision."""
254
""" Saves the article with a new revision.
228
256
if self.id is None:
230
258
self.revision = ChangeSet.objects.filter(
231
259
article=self.article).latest().revision + 1
232
260
except self.DoesNotExist:
233
261
self.revision = 1
235
262
super(ChangeSet, self).save(*args, **kwargs)
264
def display_diff(self):
265
''' Returns a HTML representation of the diff.
268
# well, it *will* be the old content
269
old_content = self.article.content
271
# newer non-reverted revisions of this article, starting from this
272
newer_changesets = ChangeSet.non_reverted_objects.filter(
273
article=self.article,
274
revision__gte=self.revision)
276
# apply all patches to get the content of this revision
277
for i, changeset in enumerate(newer_changesets):
278
patches = dmp.patch_fromText(changeset.content_diff)
279
if len(newer_changesets) == i+1:
280
# we need to compare with the next revision after the change
281
next_rev_content = old_content
282
old_content = dmp.patch_apply(patches, old_content)[0]
284
diffs = dmp.diff_main(old_content, next_rev_content)
285
dmp.diff_cleanupSemantic(diffs)
286
return dmp.diff_prettyHtml(diffs)
237
288
def get_content(self):
238
"""Returns the content of this revision."""
289
""" Returns the content of this revision.
239
291
content = self.article.content
240
newer_changesets = ChangeSet.objects.filter(
241
article=self.article, revision__gt=self.revision).order_by('-revision')
292
newer_changesets = ChangeSet.objects.filter(article=self.article, revision__gt=self.revision).order_by('-revision')
242
293
for changeset in newer_changesets:
243
294
patches = dmp.patch_fromText(changeset.content_diff)
244
295
content = dmp.patch_apply(patches, content)[0]
247
298
def compare_to(self, revision_from):
248
299
other_content = u""
249
300
if revision_from > 0:
250
other_content = ChangeSet.objects.filter(
251
article=self.article, revision__lte=revision_from).order_by('-revision')[0].get_content()
301
other_content = ChangeSet.objects.filter(article=self.article, revision__lte=revision_from).order_by("-revision")[0].get_content()
252
302
diffs = dmp.diff_main(other_content, self.get_content())
253
303
dmp.diff_cleanupSemantic(diffs)
254
304
return dmp.diff_prettyHtml(diffs)
306
if notification is not None:
307
signals.post_save.connect(notification.handle_observations, sender=Article)