~widelands-dev/widelands-website/trunk

« back to all changes in this revision

Viewing changes to wiki/models.py

  • Committer: Holger Rapp
  • Date: 2019-06-21 18:34:42 UTC
  • mfrom: (540.1.3 update_ops_script)
  • Revision ID: sirver@gmx.de-20190621183442-y2ulybzr0rdvfefd
Adapt the update script for the new server.

Show diffs side-by-side

added added

removed removed

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