1
# -*- coding: utf-8 -*-
3
from datetime import datetime
5
from django.conf import settings
6
from django.core.cache import cache
7
from django.template import RequestContext
8
from django.core.urlresolvers import reverse
9
from django.http import (Http404, HttpResponseRedirect,
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 _
14
from django.contrib.contenttypes.models import ContentType
15
from django.contrib.syndication.feeds import FeedDoesNotExist
17
from wiki.forms import ArticleForm, SearchForm
18
from wiki.models import Article, ChangeSet
19
from wiki.feeds import (RssArticleHistoryFeed, AtomArticleHistoryFeed,
20
RssHistoryFeed, AtomHistoryFeed)
21
from wiki.utils import get_ct
22
from django.contrib.auth.decorators import login_required
27
# lock duration in minutes
29
WIKI_LOCK_DURATION = settings.WIKI_LOCK_DURATION
30
except AttributeError:
31
WIKI_LOCK_DURATION = 15
34
from notification import models as notification
39
ALL_ARTICLES = Article.objects.all()
40
ALL_CHANGES = ChangeSet.objects.all()
43
def get_real_ip(request):
44
""" Returns the real user IP, even if behind a proxy.
45
Set BEHIND_PROXY to True in your settings if Django is
46
running behind a proxy.
48
if getattr(settings, 'BEHIND_PROXY', False):
49
return request.META['HTTP_X_FORWARDED_FOR']
50
return request.META['REMOTE_ADDR']
52
def get_articles_by_group(article_qs, group_slug=None,
53
group_slug_field=None, group_qs=None):
55
if group_slug is not None:
56
group = get_object_or_404(group_qs,
57
**{group_slug_field: group_slug})
58
article_qs = article_qs.filter(content_type=get_ct(group),
60
return article_qs, group
62
def get_articles_for_object(object, article_qs=None):
63
if article_qs is None:
64
article_qs = ALL_ARTICLES
65
return article_qs.filter( content_type=get_ct(object),
68
def get_url(urlname, group=None, args=None, kw=None):
70
return reverse(urlname, args=args)
72
app = group._meta.app_label
73
urlconf = '.'.join([app, 'urls'])
74
url = reverse(urlname, urlconf, kwargs=kw)
75
return ''.join(['/', app, url]) # @@@ harcoded: /app/.../
78
class ArticleEditLock(object):
79
""" A soft lock to edting an article.
82
def __init__(self, title, request, message_template=None):
84
self.user_ip = get_real_ip(request)
85
self.created_at = datetime.now()
87
if message_template is None:
88
message_template = ('Possible edit conflict:'
89
' another user started editing this article at %s')
91
self.message_template = message_template
93
cache.set(title, self, WIKI_LOCK_DURATION*60)
95
def create_message(self, request):
96
""" Send a message to the user if there is another user
99
if not self.is_mine(request):
101
user.message_set.create(
102
message=self.message_template%self.created_at)
104
def is_mine(self, request):
105
return self.user_ip == get_real_ip(request)
108
def has_read_perm(user, group, is_member, is_private):
109
""" Return True if the user has permission to *read*
110
Articles, False otherwise.
112
if (group is None) or (is_member is None) or is_member(user, group):
114
if (is_private is not None) and is_private(group):
118
def has_write_perm(user, group, is_member):
119
""" Return True if the user have permission to edit Articles,
122
if (group is None) or (is_member is None) or is_member(user, group):
127
def article_list(request,
128
group_slug=None, group_slug_field=None, group_qs=None,
129
article_qs=ALL_ARTICLES,
130
ArticleClass=Article,
131
SearchFormClass=SearchForm,
132
template_name='index.html',
138
if request.method == 'GET':
140
articles, group = get_articles_by_group(
141
article_qs, group_slug,
142
group_slug_field, group_qs)
144
allow_read = has_read_perm(request.user, group, is_member, is_private)
145
allow_write = has_write_perm(request.user, group, is_member)
148
return HttpResponseForbidden()
150
articles = articles.order_by('-created_at')
152
search_form = SearchFormClass()
154
template_params = {'articles': articles,
155
'search_form': search_form,
156
'allow_write': allow_write}
158
if group_slug is not None:
159
template_params['group'] = group
160
new_article = ArticleClass(title="NewArticle",
161
content_type=get_ct(group),
164
new_article = ArticleClass(title="NewArticle")
165
template_params['new_article'] = new_article
166
if extra_context is not None:
167
template_params.update(extra_context)
169
return render_to_response('/'.join([template_dir, template_name]),
171
context_instance=RequestContext(request))
172
return HttpResponseNotAllowed(['GET'])
175
def view_article(request, title,
176
ArticleClass=Article, # to create an unsaved instance
177
group_slug=None, group_slug_field=None, group_qs=None,
178
article_qs=ALL_ARTICLES,
179
template_name='view.html',
186
if request.method == 'GET':
187
article_args = {'title': title}
188
if group_slug is not None:
189
group = get_object_or_404(group_qs,**{group_slug_field: group_slug})
190
article_args.update({'content_type': get_ct(group),
191
'object_id': group.id})
192
allow_read = has_read_perm(request.user, group, is_member,
194
allow_write = has_write_perm(request.user, group, is_member)
196
allow_read = allow_write = True
199
return HttpResponseForbidden()
202
article = article_qs.get(**article_args)
203
if notification is not None:
204
is_observing = notification.is_observing(article, request.user)
207
except ArticleClass.DoesNotExist:
208
article = ArticleClass(**article_args)
211
template_params = {'article': article,
212
'allow_write': allow_write}
214
if notification is not None:
215
template_params.update({'is_observing': is_observing,
216
'can_observe': True})
218
if group_slug is not None:
219
template_params['group'] = group
220
if extra_context is not None:
221
template_params.update(extra_context)
223
return render_to_response('/'.join([template_dir, template_name]),
225
context_instance=RequestContext(request))
226
return HttpResponseNotAllowed(['GET'])
230
def edit_article(request, title,
231
group_slug=None, group_slug_field=None, group_qs=None,
232
article_qs=ALL_ARTICLES,
233
ArticleClass=Article, # to get the DoesNotExist exception
234
ArticleFormClass=ArticleForm,
235
template_name='edit.html',
238
check_membership=False,
243
print "is_member:", is_member(2,1)
246
article_args = {'title': title}
247
if group_slug is not None:
248
group = get_object_or_404(group_qs,**{group_slug_field: group_slug})
249
group_ct = get_ct(group)
250
article_args.update({'content_type': group_ct,
251
'object_id': group.id})
252
allow_read = has_read_perm(request.user, group, is_member,
254
allow_write = has_write_perm(request.user, group, is_member)
255
print "allow_write:", allow_write
257
allow_read = allow_write = True
260
return HttpResponseForbidden()
263
article = article_qs.get(**article_args)
264
except ArticleClass.DoesNotExist:
267
if request.method == 'POST':
269
form = ArticleFormClass(request.POST, instance=article)
273
if request.user.is_authenticated():
274
form.editor = request.user
276
user_message = u"Your article was created successfully."
278
user_message = u"Your article was edited successfully."
279
request.user.message_set.create(message=user_message)
281
if ((article is None) and (group_slug is not None)):
284
new_article, changeset = form.save()
286
url = get_url('wiki_article', group,
288
{'title': new_article.title,
289
'group_slug': group_slug})
291
return redirect_to(request, url)
293
elif request.method == 'GET':
294
user_ip = get_real_ip(request)
296
lock = cache.get(title, None)
298
lock = ArticleEditLock(title, request)
299
lock.create_message(request)
301
initial = {'user_ip': user_ip}
302
if group_slug is not None:
303
initial.update({'content_type': group_ct.id,
304
'object_id': group.id})
307
initial.update({'title': title,
309
form = ArticleFormClass(initial=initial)
311
initial['action'] = 'edit'
312
form = ArticleFormClass(instance=article,
315
template_params = {'form': form}
317
if group_slug is not None:
318
template_params['group'] = group
319
if extra_context is not None:
320
template_params.update(extra_context)
322
return render_to_response('/'.join([template_dir, template_name]),
324
context_instance=RequestContext(request))
327
def view_changeset(request, title, revision,
328
group_slug=None, group_slug_field=None, group_qs=None,
329
article_qs=ALL_ARTICLES,
330
changes_qs=ALL_CHANGES,
331
template_name='changeset.html',
338
if request.method == "GET":
339
article_args = {'article__title': title}
340
if group_slug is not None:
341
group = get_object_or_404(group_qs,**{group_slug_field: group_slug})
342
article_args.update({'article__content_type': get_ct(group),
343
'article__object_id': group.id})
344
changeset = get_object_or_404(
346
revision=int(revision),
349
article_args = {'title': title}
350
if group_slug is not None:
351
group = get_object_or_404(group_qs,**{group_slug_field: group_slug})
352
article_args.update({'content_type': get_ct(group),
353
'object_id': group.id})
354
allow_read = has_read_perm(request.user, group, is_member,
356
allow_write = has_write_perm(request.user, group, is_member)
358
allow_read = allow_write = True
361
return HttpResponseForbidden()
363
article = article_qs.get(**article_args)
365
template_params = {'article': article,
366
'article_title': article.title,
367
'changeset': changeset,
368
'allow_write': allow_write}
370
if group_slug is not None:
371
template_params['group'] = group
372
if extra_context is not None:
373
template_params.update(extra_context)
375
return render_to_response('/'.join([template_dir, template_name]),
377
context_instance=RequestContext(request))
378
return HttpResponseNotAllowed(['GET'])
381
def article_history(request, title,
382
group_slug=None, group_slug_field=None, group_qs=None,
383
article_qs=ALL_ARTICLES,
384
template_name='history.html',
391
if request.method == 'GET':
393
article_args = {'title': title}
394
if group_slug is not None:
395
group = get_object_or_404(group_qs,**{group_slug_field: group_slug})
396
article_args.update({'content_type': get_ct(group),
397
'object_id': group.id})
398
allow_read = has_read_perm(request.user, group, is_member,
400
allow_write = has_write_perm(request.user, group, is_member)
402
allow_read = allow_write = True
405
return HttpResponseForbidden()
407
article = get_object_or_404(article_qs, **article_args)
408
changes = article.changeset_set.filter(
409
reverted=False).order_by('-revision')
411
template_params = {'article': article,
413
'allow_write': allow_write}
414
if group_slug is not None:
415
template_params['group'] = group
416
if extra_context is not None:
417
template_params.update(extra_context)
419
return render_to_response('/'.join([template_dir, template_name]),
421
context_instance=RequestContext(request))
423
return HttpResponseNotAllowed(['GET'])
427
def revert_to_revision(request, title,
428
group_slug=None, group_slug_field=None, group_qs=None,
429
article_qs=ALL_ARTICLES,
435
if request.method == 'POST':
437
revision = int(request.POST['revision'])
439
article_args = {'title': title}
442
if group_slug is not None:
443
group = get_object_or_404(group_qs,**{group_slug_field: group_slug})
444
article_args.update({'content_type': get_ct(group),
445
'object_id': group.id})
446
allow_read = has_read_perm(request.user, group, is_member,
448
allow_write = has_write_perm(request.user, group, is_member)
450
allow_read = allow_write = True
452
if not (allow_read or allow_write):
453
return HttpResponseForbidden()
455
article = get_object_or_404(article_qs, **article_args)
457
if request.user.is_authenticated():
458
article.revert_to(revision, get_real_ip(request), request.user)
460
article.revert_to(revision, get_real_ip(request))
463
if request.user.is_authenticated():
464
request.user.message_set.create(
465
message=u"The article was reverted successfully.")
467
url = get_url('wiki_article_history', group,
468
[title], {'title': title,
469
'group_slug': group_slug})
471
return redirect_to(request, url)
473
return HttpResponseNotAllowed(['POST'])
476
def search_article(request,
477
group_slug=None, group_slug_field=None, group_qs=None,
478
article_qs=ALL_ARTICLES,
479
SearchFormClass=SearchForm,
484
if request.method == 'POST':
485
search_form = SearchFormClass(request.POST)
486
if search_form.is_valid():
487
search_term = search_form.cleaned_data['search_term']
490
if group_slug is not None:
491
group = get_object_or_404(group_qs,
492
**{group_slug_field: group_slug})
493
allow_read = has_read_perm(request.user, group, is_member,
501
# go to article by title
502
url = get_url('wiki_article', group,
503
[search_term], {'title': search_term,
504
'group_slug': group_slug})
506
return redirect_to(request, url)
508
return HttpResponseNotAllowed(['POST'])
512
group_slug=None, group_slug_field=None, group_qs=None,
513
article_qs=ALL_ARTICLES, changes_qs=ALL_CHANGES,
514
template_name='recentchanges.html',
519
if request.method == 'GET':
520
if group_slug is not None:
521
group = get_object_or_404(group_qs,
522
**{group_slug_field : group_slug})
523
changes_qs = changes_qs.filter(article__content_type=get_ct(group),
524
article__object_id=group.id)
525
allow_read = has_read_perm(request.user, group, is_member,
527
allow_write = has_write_perm(request.user, group, is_member)
529
allow_read = allow_write = True
532
return HttpResponseForbidden()
534
template_params = {'changes': changes_qs.order_by('-modified'),
535
'allow_write': allow_write}
536
if group_slug is not None:
537
template_params['group'] = group_slug
539
if extra_context is not None:
540
template_params.update(extra_context)
542
return render_to_response('/'.join([template_dir, template_name]),
544
context_instance=RequestContext(request))
545
return HttpResponseNotAllowed(['GET'])
549
def observe_article(request, title,
550
group_slug=None, group_slug_field=None, group_qs=None,
551
article_qs=ALL_ARTICLES,
552
template_name='recentchanges.html',
558
if request.method == 'POST':
560
article_args = {'title': title}
562
if group_slug is not None:
563
group = get_object_or_404(group_qs,**{group_slug_field: group_slug})
564
article_args.update({'content_type': get_ct(group),
565
'object_id': group.id})
566
allow_read = has_read_perm(request.user, group, is_member,
572
return HttpResponseForbidden()
574
article = get_object_or_404(article_qs, **article_args)
576
notification.observe(article, request.user,
577
'wiki_observed_article_changed')
579
url = get_url('wiki_article', group,
580
[article.title], {'title': article.title,
581
'group_slug': group_slug})
583
return redirect_to(request, url)
585
return HttpResponseNotAllowed(['POST'])
589
def stop_observing_article(request, title,
590
group_slug=None, group_slug_field=None, group_qs=None,
591
article_qs=ALL_ARTICLES,
592
template_name='recentchanges.html',
598
if request.method == 'POST':
600
article_args = {'title': title}
602
if group_slug is not None:
603
group = get_object_or_404(group_qs,**{group_slug_field: group_slug})
604
article_args.update({'content_type': get_ct(group),
605
'object_id': group.id})
606
allow_read = has_read_perm(request.user, group, is_member,
612
return HttpResponseForbidden()
614
article = get_object_or_404(article_qs, **article_args)
616
notification.stop_observing(article, request.user)
618
url = get_url('wiki_article', group,
619
[article.title], {'title': article.title,
620
'group_slug': group_slug})
622
return redirect_to(request, url)
623
return HttpResponseNotAllowed(['POST'])
626
def article_history_feed(request, feedtype, title,
627
group_slug=None, group_slug_field=None, group_qs=None,
628
article_qs=ALL_ARTICLES, changes_qs=ALL_CHANGES,
634
feeds = {'rss' : RssArticleHistoryFeed,
635
'atom' : AtomArticleHistoryFeed}
636
ArticleHistoryFeed = feeds.get(feedtype, RssArticleHistoryFeed)
639
feedgen = ArticleHistoryFeed(title, request,
640
group_slug, group_slug_field, group_qs,
641
article_qs, changes_qs,
643
*args, **kw).get_feed(title)
644
except FeedDoesNotExist:
647
response = HttpResponse(mimetype=feedgen.mime_type)
648
feedgen.write(response, 'utf-8')
652
def history_feed(request, feedtype,
653
group_slug=None, group_slug_field=None, group_qs=None,
654
article_qs=ALL_ARTICLES, changes_qs=ALL_CHANGES,
660
feeds = {'rss' : RssHistoryFeed,
661
'atom' : AtomHistoryFeed}
662
HistoryFeed = feeds.get(feedtype, RssHistoryFeed)
665
feedgen = HistoryFeed(request,
666
group_slug, group_slug_field, group_qs,
667
article_qs, changes_qs,
669
*args, **kw).get_feed()
670
except FeedDoesNotExist:
673
response = HttpResponse(mimetype=feedgen.mime_type)
674
feedgen.write(response, 'utf-8')