~widelands-dev/widelands-website/django_staticfiles

« back to all changes in this revision

Viewing changes to wiki/models.py

  • Committer: Holger Rapp
  • Date: 2009-02-20 12:25:18 UTC
  • Revision ID: holgerrapp@gmx.net-20090220122518-feaq34ta973snnct
Imported wikiapp into our repository, because we did some local changes (users must be logged in to edit wiki pages)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
from datetime import datetime
2
 
from django.urls import reverse
 
2
from django.core.urlresolvers import reverse
3
3
 
4
4
# Google Diff Match Patch library
5
5
# http://code.google.com/p/google-diff-match-patch
10
10
from django.contrib.auth.models import User
11
11
from django.utils.translation import ugettext_lazy as _
12
12
from django.contrib.contenttypes.models import ContentType
13
 
from django.contrib.contenttypes.fields import GenericRelation, GenericForeignKey
 
13
from django.contrib.contenttypes import generic
14
14
 
15
15
from tagging.fields import TagField
16
16
from tagging.models import Tag
17
 
from wlimages.models import Image
18
17
 
19
18
try:
20
19
    from notification import models as notification
25
24
# We dont need to create a new one everytime
26
25
dmp = diff_match_patch()
27
26
 
28
 
 
29
27
def diff(txt1, txt2):
30
28
    """Create a 'diff' from txt1 to txt2."""
31
29
    patch = dmp.patch_make(txt1, txt2)
43
41
 
44
42
 
45
43
class Article(models.Model):
46
 
    """A wiki page reflecting the actual revision."""
47
 
    title = models.CharField(_(u"Title"), max_length=50, unique=True)
 
44
    """ A wiki page.
 
45
    """
 
46
    title = models.CharField(_(u"Title"), max_length=50)
48
47
    content = models.TextField(_(u"Content"))
49
48
    summary = models.CharField(_(u"Summary"), max_length=150,
50
49
                               null=True, blank=True)
53
52
                              null=True, blank=True)
54
53
    creator = models.ForeignKey(User, verbose_name=_('Article Creator'),
55
54
                                null=True)
 
55
    creator_ip = models.IPAddressField(_("IP Address of the Article Creator"),
 
56
                                       blank=True, null=True)
56
57
    created_at = models.DateTimeField(default=datetime.now)
57
58
    last_update = models.DateTimeField(blank=True, null=True)
58
59
 
59
60
    content_type = models.ForeignKey(ContentType, null=True)
60
61
    object_id = models.PositiveIntegerField(null=True)
61
 
    group = GenericForeignKey('content_type', 'object_id')
62
 
 
63
 
    images = GenericRelation(Image)
 
62
    group = generic.GenericForeignKey('content_type', 'object_id')
64
63
 
65
64
    tags = TagField()
66
65
 
67
66
    class Meta:
68
67
        verbose_name = _(u'Article')
69
68
        verbose_name_plural = _(u'Articles')
70
 
        app_label = 'wiki'
71
 
        default_permissions = ('change', 'add',)
72
 
        ordering = ['title']
73
69
 
74
70
    def get_absolute_url(self):
75
71
        if self.group is None:
76
72
            return reverse('wiki_article', args=(self.title,))
77
73
        return self.group.get_absolute_url() + 'wiki/' + self.title
78
74
 
79
 
    def save(self, *args, **kwargs):
 
75
    def save(self, force_insert=False, force_update=False):
80
76
        self.last_update = datetime.now()
81
 
        super(Article, self).save(*args, **kwargs)
 
77
        super(Article, self).save(force_insert, force_update)
82
78
 
83
79
    def latest_changeset(self):
84
80
        try:
87
83
        except IndexError:
88
84
            return ChangeSet.objects.none()
89
85
 
90
 
    def all_images(self):
91
 
        return self.images.all()
92
 
 
93
86
    def new_revision(self, old_content, old_title, old_markup,
94
 
                     comment, editor):
95
 
        """Create a new ChangeSet with the old content."""
 
87
                     comment, editor_ip, editor):
 
88
        '''Create a new ChangeSet with the old content.'''
96
89
 
97
90
        content_diff = diff(self.content, old_content)
98
91
 
99
92
        cs = ChangeSet.objects.create(
100
93
            article=self,
101
94
            comment=comment,
 
95
            editor_ip=editor_ip,
102
96
            editor=editor,
103
97
            old_title=old_title,
104
98
            old_markup=old_markup,
105
99
            content_diff=content_diff)
106
100
 
 
101
        if None not in (notification, self.creator):
 
102
            if editor is None:
 
103
                editor = editor_ip
 
104
            notification.send([self.creator], "wiki_article_edited",
 
105
                              {'article': self, 'user': editor})
 
106
 
107
107
        return cs
108
108
 
109
 
    def revert_to(self, revision, editor=None):
110
 
        """Revert the article to a previuos state, by revision number."""
 
109
    def revert_to(self, revision, editor_ip, editor=None):
 
110
        """ Revert the article to a previuos state, by revision number.
 
111
        """
111
112
        changeset = self.changeset_set.get(revision=revision)
112
 
        changeset.reapply(editor)
 
113
        changeset.reapply(editor_ip, editor)
113
114
 
114
 
    def compare(self, from_revision, to_revision):
115
 
        """Compares to revisions of this article."""
116
 
        changeset = self.changeset_set.get(revision=to_revision)
117
 
        return changeset.compare_to(from_revision)
118
115
 
119
116
    def __unicode__(self):
120
117
        return self.title
121
118
 
122
119
 
 
120
 
123
121
class ChangeSetManager(models.Manager):
124
122
 
125
123
    def all_later(self, revision):
126
 
        """Return all changes later to the given revision.
127
 
 
 
124
        """ Return all changes later to the given revision.
128
125
        Util when we want to revert to the given revision.
129
 
 
130
126
        """
131
127
        return self.filter(revision__gt=int(revision))
132
128
 
133
129
 
 
130
class NonRevertedChangeSetManager(ChangeSetManager):
 
131
 
 
132
    def get_default_queryset(self):
 
133
        super(PublishedBookManager, self).get_query_set().filter(
 
134
            reverted=False)
 
135
 
 
136
 
134
137
class ChangeSet(models.Model):
135
138
    """A report of an older version of some Article."""
136
139
 
137
140
    article = models.ForeignKey(Article, verbose_name=_(u"Article"))
138
141
 
139
 
    # Editor identification -- logged
 
142
    # Editor identification -- logged or anonymous
140
143
    editor = models.ForeignKey(User, verbose_name=_(u'Editor'),
141
144
                               null=True)
 
145
    editor_ip = models.IPAddressField(_(u"IP Address of the Editor"))
142
146
 
143
147
    # Revision number, starting from 1
144
148
    revision = models.IntegerField(_(u"Revision Number"))
150
154
                                  null=True, blank=True)
151
155
    content_diff = models.TextField(_(u"Content Patch"), blank=True)
152
156
 
153
 
    comment = models.TextField(_(u"Editor comment"), blank=True)
 
157
    comment = models.CharField(_(u"Editor comment"), max_length=50, blank=True)
154
158
    modified = models.DateTimeField(_(u"Modified at"), default=datetime.now)
155
159
    reverted = models.BooleanField(_(u"Reverted Revision"), default=False)
156
160
 
157
161
    objects = ChangeSetManager()
 
162
    non_reverted_objects = NonRevertedChangeSetManager()
158
163
 
159
164
    class Meta:
160
165
        verbose_name = _(u'Change set')
161
166
        verbose_name_plural = _(u'Change sets')
162
 
        get_latest_by = 'modified'
 
167
        get_latest_by  = 'modified'
163
168
        ordering = ('-revision',)
164
 
        app_label = 'wiki'
165
169
 
166
170
    def __unicode__(self):
167
171
        return u'#%s' % self.revision
168
172
 
 
173
    @models.permalink
169
174
    def get_absolute_url(self):
170
175
        if self.article.group is None:
171
 
            return reverse('wiki_changeset', kwargs={
172
 
                'title': self.article.title,
173
 
                'revision': self.revision
174
 
            })
175
 
        return reverse('wiki_changeset', kwargs={
176
 
            'group_slug': self.article.group.slug,
177
 
            'title': self.article.title,
178
 
            'revision': self.revision,
179
 
        })
 
176
            return ('wiki_changeset', (),
 
177
                    {'title': self.article.title,
 
178
                     'revision': self.revision})
 
179
        return ('wiki_changeset', (),
 
180
                {'group_slug': self.article.group.slug,
 
181
                 'title': self.article.title,
 
182
                 'revision': self.revision})
 
183
 
180
184
 
181
185
    def is_anonymous_change(self):
182
186
        return self.editor is None
183
187
 
184
 
    def reapply(self, editor):
185
 
        """Return the Article to this revision."""
 
188
    def reapply(self, editor_ip, editor):
 
189
        """ Return the Article to this revision.
 
190
        """
186
191
 
187
192
        # XXX Would be better to exclude reverted revisions
188
193
        #     and revisions previous/next to reverted ones
213
218
        article.new_revision(
214
219
            old_content=old_content, old_title=old_title,
215
220
            old_markup=old_markup,
216
 
            comment='Reverted to revision #%s' % self.revision,
217
 
            editor=editor
218
 
            )
 
221
            comment="Reverted to revision #%s" % self.revision,
 
222
            editor_ip=editor_ip, editor=editor)
219
223
 
220
224
        self.save()
221
225
 
222
226
        if None not in (notification, self.editor):
223
 
            notification.send([self.editor], 'wiki_revision_reverted',
 
227
            notification.send([self.editor], "wiki_revision_reverted",
224
228
                              {'revision': self, 'article': self.article})
225
229
 
226
 
    def save(self, *args, **kwargs):
227
 
        """Saves the article with a new revision."""
 
230
    def save(self, force_insert=False, force_update=False):
 
231
        """ Saves the article with a new revision.
 
232
        """
228
233
        if self.id is None:
229
234
            try:
230
235
                self.revision = ChangeSet.objects.filter(
231
236
                    article=self.article).latest().revision + 1
232
237
            except self.DoesNotExist:
233
238
                self.revision = 1
234
 
 
235
 
        super(ChangeSet, self).save(*args, **kwargs)
236
 
 
237
 
    def get_content(self):
238
 
        """Returns the content of this revision."""
239
 
        content = self.article.content
240
 
        newer_changesets = ChangeSet.objects.filter(
241
 
            article=self.article, revision__gt=self.revision).order_by('-revision')
242
 
        for changeset in newer_changesets:
 
239
        super(ChangeSet, self).save(force_insert, force_update)
 
240
 
 
241
    def display_diff(self):
 
242
        ''' Returns a HTML representation of the diff.
 
243
        '''
 
244
 
 
245
        # well, it *will* be the old content
 
246
        old_content = self.article.content
 
247
 
 
248
        # newer non-reverted revisions of this article, starting from this
 
249
        newer_changesets = ChangeSet.non_reverted_objects.filter(
 
250
            article=self.article,
 
251
            revision__gte=self.revision)
 
252
 
 
253
        # apply all patches to get the content of this revision
 
254
        for i, changeset in enumerate(newer_changesets):
243
255
            patches = dmp.patch_fromText(changeset.content_diff)
244
 
            content = dmp.patch_apply(patches, content)[0]
245
 
        return content
 
256
            if len(newer_changesets) == i+1:
 
257
                # we need to compare with the next revision after the change
 
258
                next_rev_content = old_content
 
259
            old_content = dmp.patch_apply(patches, old_content)[0]
246
260
 
247
 
    def compare_to(self, revision_from):
248
 
        other_content = u""
249
 
        if revision_from > 0:
250
 
            other_content = ChangeSet.objects.filter(
251
 
                article=self.article, revision__lte=revision_from).order_by('-revision')[0].get_content()
252
 
        diffs = dmp.diff_main(other_content, self.get_content())
253
 
        dmp.diff_cleanupSemantic(diffs)
 
261
        diffs = dmp.diff_main(old_content, next_rev_content)
254
262
        return dmp.diff_prettyHtml(diffs)
 
263
 
 
264
if notification is not None:
 
265
    signals.post_save.connect(notification.handle_observations, sender=Article)