5
5
from django.conf import settings
6
6
from django.core.cache import cache
7
7
from django.template import RequestContext
8
from django.core.urlresolvers import reverse
8
from django.urls import reverse
9
9
from django.http import (Http404, HttpResponseRedirect,
10
10
HttpResponseNotAllowed, HttpResponse, HttpResponseForbidden)
11
from django.shortcuts import get_object_or_404, render_to_response
12
from django.views.generic.simple import redirect_to
13
from django.utils.translation import ugettext_lazy as _
11
from django.shortcuts import get_object_or_404, render, redirect
14
12
from django.contrib.contenttypes.models import ContentType
15
from django.contrib.syndication.feeds import FeedDoesNotExist
13
from django.contrib import messages
17
15
from wiki.forms import ArticleForm
18
16
from wiki.models import Article, ChangeSet, dmp
19
from wiki.feeds import (RssArticleHistoryFeed, AtomArticleHistoryFeed,
20
RssHistoryFeed, AtomHistoryFeed)
21
18
from wiki.utils import get_ct
22
19
from django.contrib.auth.decorators import login_required
24
20
from mainpage.templatetags.wl_markdown import do_wl_markdown
21
from markdownextensions.semanticwikilinks.mdx_semanticwikilinks import WIKILINK_RE
23
from mainpage.wl_utils import get_real_ip
24
from mainpage.wl_utils import get_valid_cache_key
27
import urllib.request, urllib.parse, urllib.error
27
30
# lock duration in minutes
72
68
app = group._meta.app_label
73
69
urlconf = '.'.join([app, 'urls'])
74
70
url = reverse(urlname, urlconf, kwargs=kw)
75
return ''.join(['/', app, url]) # @@@ harcoded: /app/.../
71
return ''.join(['/', app, url]) # @@@ harcoded: /app/.../
78
74
class ArticleEditLock(object):
79
""" A soft lock to edting an article.
75
"""A soft lock to edting an article."""
82
76
def __init__(self, title, request, message_template=None):
84
self.user_ip = get_real_ip(request)
85
self.created_at = datetime.now()
78
self.user = request.user
79
self.created_at = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
87
81
if message_template is None:
88
82
message_template = ('Possible edit conflict:'
89
' another user started editing this article at %s')
83
' Another user started editing this article at %s')
91
85
self.message_template = message_template
93
cache.set(title, self, WIKI_LOCK_DURATION*60)
86
cache.set(title, self, WIKI_LOCK_DURATION * 60)
95
88
def create_message(self, request):
96
""" Send a message to the user if there is another user
89
"""Show a message to the user if there is another user editing this
99
91
if not self.is_mine(request):
100
92
user = request.user
101
user.message_set.create(
102
message=self.message_template%self.created_at)
93
messages.add_message(request,
95
self.message_template % self.created_at)
104
97
def is_mine(self, request):
105
return self.user_ip == get_real_ip(request)
98
return self.user == request.user
108
101
def has_read_perm(user, group, is_member, is_private):
153
146
if group_slug is not None:
154
147
template_params['group'] = group
155
new_article = ArticleClass(title="NewArticle",
148
new_article = ArticleClass(title='NewArticle',
156
149
content_type=get_ct(group),
157
150
object_id=group.id)
159
new_article = ArticleClass(title="NewArticle")
152
new_article = ArticleClass(title='NewArticle')
160
153
template_params['new_article'] = new_article
161
154
if extra_context is not None:
162
155
template_params.update(extra_context)
164
return render_to_response('/'.join([template_dir, template_name]),
166
context_instance=RequestContext(request))
157
return render(request, '/'.join([template_dir, template_name]),
167
159
return HttpResponseNotAllowed(['GET'])
170
def view_article(request, title,
171
ArticleClass=Article, # to create an unsaved instance
162
def view_article(request, title, revision=None,
163
ArticleClass=Article, # to create an unsaved instance
172
164
group_slug=None, group_slug_field=None, group_qs=None,
173
165
article_qs=ALL_ARTICLES,
174
166
template_name='view.html',
193
186
if not allow_read:
194
187
return HttpResponseForbidden()
190
redirected_from = None
197
192
article = article_qs.get(**article_args)
198
193
if notification is not None:
199
194
is_observing = notification.is_observing(article, request.user)
202
195
except ArticleClass.DoesNotExist:
203
article = ArticleClass(**article_args)
197
# try to find an article that once had this title
198
article = ChangeSet.objects.filter(
199
old_title=title).order_by('-revision')[0].article
200
redirected_from = title
201
# if article is not None:
202
# return redirect(article, permanent=True)
204
article = ArticleClass(**article_args)
206
if revision is not None:
207
changeset = get_object_or_404(
208
article.changeset_set, revision=revision)
209
article.content = changeset.get_content()
206
211
template_params = {'article': article,
212
'revision': revision,
213
'redirected_from': redirected_from,
207
214
'allow_write': allow_write}
209
216
if notification is not None:
252
258
return HttpResponseForbidden()
261
# Try to fetch an existing article
255
262
article = article_qs.get(**article_args)
256
263
except ArticleClass.DoesNotExist:
264
# No article found, maybe we have a redirect
266
cs = ChangeSet.objects.filter(old_title=title)[0]
267
article = article_qs.get(title=cs.article)
269
# No Article found and no redirect found
259
272
if request.method == 'POST':
261
274
form = ArticleFormClass(request.POST, instance=article)
263
276
form.cache_old_content()
264
277
if form.is_valid():
265
if request.user.is_authenticated():
279
if request.user.is_authenticated:
266
280
form.editor = request.user
268
user_message = u"Your article was created successfully."
270
user_message = u"Your article was edited successfully."
271
request.user.message_set.create(message=user_message)
273
282
if ((article is None) and (group_slug is not None)):
274
283
form.group = group
276
285
new_article, changeset = form.save()
278
url = get_url('wiki_article', group,
280
{'title': new_article.title,
281
'group_slug': group_slug})
283
return redirect_to(request, url)
287
lock = cache.get(get_valid_cache_key(title))
290
cache.delete(get_valid_cache_key(title))
292
if notification and not changeset.reverted:
293
# Get observers for this article and exclude current editor
294
items = notification.ObservedItem.objects.all_for(
295
new_article, 'post_save').exclude(user=request.user).iterator()
296
users = [o.user for o in items]
297
notification.send(users, 'wiki_observed_article_changed',
298
{'editor': request.user,
299
'rev': changeset.revision,
300
'rev_comment': changeset.comment,
301
'article': new_article})
304
return redirect(new_article)
285
306
elif request.method == 'GET':
286
user_ip = get_real_ip(request)
288
lock = cache.get(title, None)
307
lock = cache.get(get_valid_cache_key(title))
290
lock = ArticleEditLock(title, request)
309
lock = ArticleEditLock(get_valid_cache_key(title), request)
291
310
lock.create_message(request)
293
initial = {'user_ip': user_ip}
294
312
if group_slug is not None:
295
313
initial.update({'content_type': group_ct.id,
296
314
'object_id': group.id})
303
321
initial['action'] = 'edit'
304
322
form = ArticleFormClass(instance=article,
308
template_params = {'form': form, "new_article": True }
325
template_params = {'form': form, 'new_article': True}
310
template_params = {'form': form, "new_article": False,
311
"content_type": ContentType.objects.get_for_model(Article).pk, "object_id": article.pk,
312
"images": article.all_images(),
327
template_params = {'form': form, 'new_article': False,
328
'content_type': ContentType.objects.get_for_model(Article).pk, 'object_id': article.pk,
329
'images': article.all_images(),
316
333
if group_slug is not None:
317
334
template_params['group'] = group
318
335
if extra_context is not None:
319
336
template_params.update(extra_context)
321
return render_to_response('/'.join([template_dir, template_name]),
323
context_instance=RequestContext(request))
338
return render(request, '/'.join([template_dir, template_name]),
326
342
def view_changeset(request, title, revision,
327
344
group_slug=None, group_slug_field=None, group_qs=None,
328
345
article_qs=ALL_ARTICLES,
329
346
changes_qs=ALL_CHANGES,
454
484
article = get_object_or_404(article_qs, **article_args)
456
if request.user.is_authenticated():
457
article.revert_to(revision, get_real_ip(request), request.user)
459
article.revert_to(revision, get_real_ip(request))
462
if request.user.is_authenticated():
463
request.user.message_set.create(
464
message=u"The article was reverted successfully.")
466
url = get_url('wiki_article_history', group,
467
[title], {'title': title,
468
'group_slug': group_slug})
470
return redirect_to(request, url)
487
# Check whether there is another Article with the same name to which this article
488
# wants to be reverted to. If so: prevent it and show a message.
489
old_title = article.changeset_set.filter(
490
revision=revision+1).get().old_title
492
art = Article.objects.exclude(pk=article.pk).get(title=old_title)
493
except Article.DoesNotExist:
494
# No existing article found -> reverting possible
495
if request.user.is_authenticated:
496
article.revert_to(revision, request.user)
498
article.revert_to(revision)
499
return redirect(article)
500
# An article with this name exists
502
request, 'Reverting not possible because an article with name \'%s\' already exists' % old_title)
503
return redirect(article)
472
505
return HttpResponseNotAllowed(['POST'])
577
605
if notification.is_observing(article, request.user):
578
606
notification.stop_observing(article, request.user)
580
url = get_url('wiki_article', group,
581
[article.title], {'title': article.title,
582
'group_slug': group_slug})
584
return redirect_to(request, url)
586
def article_preview( request ):
588
This is a AJAX function that previews the body of the
589
article as it is currently displayed.
591
This function is actually pretty simple, it just
592
runs the function through the view template and returns
595
rv = do_wl_markdown( request.POST["body"], safe_mode="escape" )
596
return HttpResponse(rv, mimetype="text/html")
598
def article_diff( request ):
600
This is a AJAX function that diffs the body of the
601
article as it is currently displayed with the current version
604
current_article = get_object_or_404(Article, pk=int(request.POST["article"]))
605
content = request.POST["body"]
608
return redirect(article)
611
def article_preview(request):
612
"""This is a AJAX function that previews the body of the article as it is
615
This function is actually pretty simple, it just runs the function
616
through the view template and returns it to the caller
619
rv = do_wl_markdown(request.POST['body'], 'bleachit')
620
return HttpResponse(rv, content_type='text/html')
623
def article_diff(request):
624
"""This is a AJAX function that diffs the body of the article as it is
625
currently displayed with the current version of the article."""
626
current_article = get_object_or_404(
627
Article, pk=int(request.POST['article']))
628
content = request.POST['body']
607
630
diffs = dmp.diff_main(current_article.content, content)
609
return HttpResponse(dmp.diff_prettyHtml(diffs), mimetype="text/html")
611
def article_history_feed(request, feedtype, title,
612
group_slug=None, group_slug_field=None, group_qs=None,
613
article_qs=ALL_ARTICLES, changes_qs=ALL_CHANGES,
619
feeds = {'rss' : RssArticleHistoryFeed,
620
'atom' : AtomArticleHistoryFeed}
621
ArticleHistoryFeed = feeds.get(feedtype, RssArticleHistoryFeed)
624
feedgen = ArticleHistoryFeed(title, request,
625
group_slug, group_slug_field, group_qs,
626
article_qs, changes_qs,
628
*args, **kw).get_feed(title)
629
except FeedDoesNotExist:
632
response = HttpResponse(mimetype=feedgen.mime_type)
633
feedgen.write(response, 'utf-8')
637
def history_feed(request, feedtype,
638
group_slug=None, group_slug_field=None, group_qs=None,
639
article_qs=ALL_ARTICLES, changes_qs=ALL_CHANGES,
645
feeds = {'rss' : RssHistoryFeed,
646
'atom' : AtomHistoryFeed}
647
HistoryFeed = feeds.get(feedtype, RssHistoryFeed)
650
feedgen = HistoryFeed(request,
651
group_slug, group_slug_field, group_qs,
652
article_qs, changes_qs,
654
*args, **kw).get_feed()
655
except FeedDoesNotExist:
658
response = HttpResponse(mimetype=feedgen.mime_type)
659
feedgen.write(response, 'utf-8')
631
dmp.diff_cleanupSemantic(diffs)
633
return HttpResponse(dmp.diff_prettyHtml(diffs), content_type='text/html')
636
def backlinks(request, title):
637
"""Search for links in other wiki articles pointing to the
641
# Find old title(s) of this article
642
this_article = get_object_or_404(Article, title=title)
643
changesets = this_article.changeset_set.all()
645
for cs in changesets:
646
if cs.old_title and cs.old_title != title and cs.old_title not in old_titles:
647
old_titles.append(cs.old_title)
649
# Search for semantic wiki links. The regexpr was copied from there
650
# and slightly modified
651
search_title = [re.compile(r"\[\[\s*(%s)/?\s*(\|\s*.+?\s*)?\]\]" % title)]
653
# Search for links in MarkDown syntax, like [Foo](wiki/FooBar)
654
# The regexpr matches the title between '/' and ')'
655
search_title.append(re.compile(r"\/%s\)" % title))
657
# Search for current and previous titles
660
articles_all = Article.objects.all().exclude(title=title)
661
for article in articles_all:
662
for regexp in search_title:
663
# Need to unqoute the content to match
664
# e.g. [[ Back | Title%20of%20Page ]]
665
match = regexp.search(urllib.parse.unquote(article.content))
667
found_links.append({'title': article.title})
669
for old_title in old_titles:
670
if old_title in article.content:
671
found_old_links.append(
672
{'old_title': old_title, 'title': article.title })
674
context = {'found_links': found_links,
675
'found_old_links': found_old_links,
677
'article': this_article,
679
return render(request, 'wiki/backlinks.html',