comparison gpp/forums/views/main.py @ 232:a46788862737

Implement a forum favorites feature for #82
author Brian Neal <bgneal@gmail.com>
date Sun, 01 Aug 2010 21:26:12 +0000
parents
children d302c498560e
comparison
equal deleted inserted replaced
231:a2d388ed106e 232:a46788862737
1 """
2 Views for the forums application.
3 """
4 import datetime
5
6 from django.contrib.auth.decorators import login_required
7 from django.contrib.auth.models import User
8 from django.http import Http404
9 from django.http import HttpResponse
10 from django.http import HttpResponseBadRequest
11 from django.http import HttpResponseForbidden
12 from django.http import HttpResponseRedirect
13 from django.core.urlresolvers import reverse
14 from django.core.paginator import InvalidPage
15 from django.shortcuts import get_object_or_404
16 from django.shortcuts import render_to_response
17 from django.shortcuts import redirect
18 from django.template.loader import render_to_string
19 from django.template import RequestContext
20 from django.views.decorators.http import require_POST
21 from django.utils.text import wrap
22 from django.db.models import F
23
24 from core.paginator import DiggPaginator
25 from core.functions import email_admins
26 from forums.models import Forum, Topic, Post, FlaggedPost, TopicLastVisit, \
27 ForumLastVisit
28 from forums.forms import NewTopicForm, NewPostForm, PostForm, MoveTopicForm, \
29 SplitTopicForm
30 from forums.unread import get_forum_unread_status, get_topic_unread_status, \
31 get_post_unread_status, get_unread_topics
32
33 from bio.models import UserProfile
34 import antispam
35 import antispam.utils
36
37 #######################################################################
38
39 TOPICS_PER_PAGE = 50
40 POSTS_PER_PAGE = 20
41
42
43 def get_page_num(request):
44 """Returns the value of the 'page' variable in GET if it exists, or 1
45 if it does not."""
46
47 try:
48 page_num = int(request.GET.get('page', 1))
49 except ValueError:
50 page_num = 1
51
52 return page_num
53
54
55 def create_topic_paginator(topics):
56 return DiggPaginator(topics, TOPICS_PER_PAGE, body=5, tail=2, margin=3, padding=2)
57
58 def create_post_paginator(posts):
59 return DiggPaginator(posts, POSTS_PER_PAGE, body=5, tail=2, margin=3, padding=2)
60
61
62 def attach_topic_page_ranges(topics):
63 """Attaches a page_range attribute to each topic in the supplied list.
64 This attribute will be None if it is a single page topic. This is used
65 by the templates to generate "goto page x" links.
66 """
67 for topic in topics:
68 if topic.post_count > POSTS_PER_PAGE:
69 pp = DiggPaginator(range(topic.post_count), POSTS_PER_PAGE,
70 body=2, tail=3, margin=1)
71 topic.page_range = pp.page(1).page_range
72 else:
73 topic.page_range = None
74
75 #######################################################################
76
77 SPECIAL_QUERIES = {
78 'unread': 'forums-unread_topics',
79 'unanswered': 'forums-unanswered_topics',
80 'mine': 'forums-my_posts',
81 'favorites': 'forums-manage_favorites',
82 'subscriptions': 'forums-manage_subscriptions',
83 }
84
85 def index(request):
86 """
87 This view displays all the forums available, ordered in each category.
88 """
89 # check for special forum queries
90 query = request.GET.get("query")
91 if query in SPECIAL_QUERIES:
92 return redirect(SPECIAL_QUERIES[query])
93
94 public_forums = Forum.objects.public_forums()
95 feeds = [{'name': 'All Forums', 'feed': '/feeds/forums/'}]
96
97 forums = Forum.objects.forums_for_user(request.user)
98 get_forum_unread_status(forums, request.user)
99 cats = {}
100 for forum in forums:
101 forum.has_feed = forum in public_forums
102 if forum.has_feed:
103 feeds.append({
104 'name': '%s Forum' % forum.name,
105 'feed': '/feeds/forums/%s/' % forum.slug,
106 })
107
108 cat = cats.setdefault(forum.category.id, {
109 'cat': forum.category,
110 'forums': [],
111 })
112 cat['forums'].append(forum)
113
114 cmpdef = lambda a, b: cmp(a['cat'].position, b['cat'].position)
115 cats = sorted(cats.values(), cmpdef)
116
117 return render_to_response('forums/index.html', {
118 'cats': cats,
119 'feeds': feeds,
120 },
121 context_instance=RequestContext(request))
122
123
124 def forum_index(request, slug):
125 """
126 Displays all the topics in a forum.
127 """
128 forum = get_object_or_404(Forum.objects.select_related(), slug=slug)
129
130 if not forum.category.can_access(request.user):
131 return HttpResponseForbidden()
132
133 topics = forum.topics.select_related('user', 'last_post', 'last_post__user')
134 get_topic_unread_status(forum, topics, request.user)
135
136 paginator = create_topic_paginator(topics)
137 page_num = get_page_num(request)
138 try:
139 page = paginator.page(page_num)
140 except InvalidPage:
141 raise Http404
142
143 attach_topic_page_ranges(page.object_list)
144
145 # we do this for the template since it is rendered twice
146 page_nav = render_to_string('forums/pagination.html', {'page': page})
147
148 can_moderate = _can_moderate(forum, request.user)
149
150 return render_to_response('forums/forum_index.html', {
151 'forum': forum,
152 'page': page,
153 'page_nav': page_nav,
154 'can_moderate': can_moderate,
155 },
156 context_instance=RequestContext(request))
157
158
159 def topic_index(request, id):
160 """
161 Displays all the posts in a topic.
162 """
163 topic = get_object_or_404(Topic.objects.select_related(
164 'forum', 'forum__category', 'last_post'), pk=id)
165
166 if not topic.forum.category.can_access(request.user):
167 return HttpResponseForbidden()
168
169 topic.view_count = F('view_count') + 1
170 topic.save(force_update=True)
171
172 posts = topic.posts.select_related()
173
174 paginator = create_post_paginator(posts)
175 page_num = get_page_num(request)
176 try:
177 page = paginator.page(page_num)
178 except InvalidPage:
179 raise Http404
180 get_post_unread_status(topic, page.object_list, request.user)
181
182 # Attach user profiles to each post to avoid using get_user_profile() in
183 # the template.
184 users = set(post.user.id for post in page.object_list)
185
186 profiles = UserProfile.objects.filter(user__id__in=users).select_related()
187 user_profiles = dict((profile.user.id, profile) for profile in profiles)
188
189 for post in page.object_list:
190 post.user_profile = user_profiles[post.user.id]
191
192 last_page = page_num == paginator.num_pages
193
194 if request.user.is_authenticated() and last_page:
195 _update_last_visit(request.user, topic)
196
197 # we do this for the template since it is rendered twice
198 page_nav = render_to_string('forums/pagination.html', {'page': page})
199
200 can_moderate = _can_moderate(topic.forum, request.user)
201
202 can_reply = request.user.is_authenticated() and (
203 not topic.locked or can_moderate)
204
205 is_favorite = request.user.is_authenticated() and (
206 topic in request.user.favorite_topics.all())
207
208 is_subscribed = request.user.is_authenticated() and (
209 topic in request.user.subscriptions.all())
210
211 return render_to_response('forums/topic.html', {
212 'forum': topic.forum,
213 'topic': topic,
214 'page': page,
215 'page_nav': page_nav,
216 'last_page': last_page,
217 'can_moderate': can_moderate,
218 'can_reply': can_reply,
219 'form': NewPostForm(initial={'topic_id': topic.id}),
220 'is_favorite': is_favorite,
221 'is_subscribed': is_subscribed,
222 },
223 context_instance=RequestContext(request))
224
225
226 @login_required
227 def new_topic(request, slug):
228 """
229 This view handles the creation of new topics.
230 """
231 forum = get_object_or_404(Forum.objects.select_related(), slug=slug)
232
233 if not forum.category.can_access(request.user):
234 return HttpResponseForbidden()
235
236 if request.method == 'POST':
237 form = NewTopicForm(request.user, forum, request.POST)
238 if form.is_valid():
239 if antispam.utils.spam_check(request, form.cleaned_data['body']):
240 return HttpResponseRedirect(reverse('antispam-suspended'))
241
242 topic = form.save(request.META.get("REMOTE_ADDR"))
243 _bump_post_count(request.user)
244 return HttpResponseRedirect(reverse('forums-new_topic_thanks',
245 kwargs={'tid': topic.pk}))
246 else:
247 form = NewTopicForm(request.user, forum)
248
249 return render_to_response('forums/new_topic.html', {
250 'forum': forum,
251 'form': form,
252 },
253 context_instance=RequestContext(request))
254
255
256 @login_required
257 def new_topic_thanks(request, tid):
258 """
259 This view displays the success page for a newly created topic.
260 """
261 topic = get_object_or_404(Topic.objects.select_related(), pk=tid)
262 return render_to_response('forums/new_topic_thanks.html', {
263 'forum': topic.forum,
264 'topic': topic,
265 },
266 context_instance=RequestContext(request))
267
268
269 @require_POST
270 def quick_reply_ajax(request):
271 """
272 This function handles the quick reply to a thread function. This
273 function is meant to be the target of an AJAX post, and returns
274 the HTML for the new post, which the client-side script appends
275 to the document.
276 """
277 if not request.user.is_authenticated():
278 return HttpResponseForbidden('Please login or register to post.')
279
280 form = NewPostForm(request.POST)
281 if form.is_valid():
282 if not _can_post_in_topic(form.topic, request.user):
283 return HttpResponseForbidden("You don't have permission to post in this topic.")
284 if antispam.utils.spam_check(request, form.cleaned_data['body']):
285 return HttpResponseForbidden(antispam.BUSTED_MESSAGE)
286
287 post = form.save(request.user, request.META.get("REMOTE_ADDR", ""))
288 post.unread = True
289 post.user_profile = request.user.get_profile()
290 _bump_post_count(request.user)
291 _update_last_visit(request.user, form.topic)
292 return render_to_response('forums/display_post.html', {
293 'post': post,
294 'can_moderate': _can_moderate(form.topic.forum, request.user),
295 'can_reply': True,
296 },
297 context_instance=RequestContext(request))
298
299 return HttpResponseBadRequest("Invalid post.");
300
301
302 def goto_post(request, post_id):
303 """
304 This function calculates what page a given post is on, then redirects
305 to that URL. This function is the target of get_absolute_url() for
306 Post objects.
307 """
308 post = get_object_or_404(Post.objects.select_related(), pk=post_id)
309 count = post.topic.posts.filter(creation_date__lt=post.creation_date).count()
310 page = count / POSTS_PER_PAGE + 1
311 url = reverse('forums-topic_index', kwargs={'id': post.topic.id}) + \
312 '?page=%s#p%s' % (page, post.id)
313 return HttpResponseRedirect(url)
314
315
316 @require_POST
317 def flag_post(request):
318 """
319 This function handles the flagging of posts by users. This function should
320 be the target of an AJAX post.
321 """
322 if not request.user.is_authenticated():
323 return HttpResponseForbidden('Please login or register to flag a post.')
324
325 id = request.POST.get('id')
326 if id is None:
327 return HttpResponseBadRequest('No post id')
328
329 try:
330 post = Post.objects.get(pk=id)
331 except Post.DoesNotExist:
332 return HttpResponseBadRequest('No post with id %s' % id)
333
334 flag = FlaggedPost(user=request.user, post=post)
335 flag.save()
336 email_admins('A Post Has Been Flagged', """Hello,
337
338 A user has flagged a forum post for review.
339 """)
340 return HttpResponse('The post was flagged. A moderator will review the post shortly. ' \
341 'Thanks for helping to improve the discussions on this site.')
342
343
344 @login_required
345 def edit_post(request, id):
346 """
347 This view function allows authorized users to edit posts.
348 The superuser, forum moderators, and original author can edit posts.
349 """
350 post = get_object_or_404(Post.objects.select_related(), pk=id)
351
352 can_moderate = _can_moderate(post.topic.forum, request.user)
353 can_edit = can_moderate or request.user == post.user
354
355 if not can_edit:
356 return HttpResponseForbidden("You don't have permission to edit that post.")
357
358 if request.method == "POST":
359 form = PostForm(request.POST, instance=post)
360 if form.is_valid():
361 if antispam.utils.spam_check(request, form.cleaned_data['body']):
362 return HttpResponseRedirect(reverse('antispam-suspended'))
363 post = form.save(commit=False)
364 post.touch()
365 post.save()
366 return HttpResponseRedirect(post.get_absolute_url())
367 else:
368 form = PostForm(instance=post)
369
370 post.user_profile = request.user.get_profile()
371
372 return render_to_response('forums/edit_post.html', {
373 'forum': post.topic.forum,
374 'topic': post.topic,
375 'post': post,
376 'form': form,
377 'can_moderate': can_moderate,
378 },
379 context_instance=RequestContext(request))
380
381
382 @require_POST
383 def delete_post(request):
384 """
385 This view function allows superusers and forum moderators to delete posts.
386 This function is the target of AJAX calls from the client.
387 """
388 if not request.user.is_authenticated():
389 return HttpResponseForbidden('Please login to delete a post.')
390
391 id = request.POST.get('id')
392 if id is None:
393 return HttpResponseBadRequest('No post id')
394
395 post = get_object_or_404(Post.objects.select_related(), pk=id)
396
397 can_delete = request.user.is_superuser or \
398 request.user in post.topic.forum.moderators.all()
399
400 if not can_delete:
401 return HttpResponseForbidden("You don't have permission to delete that post.")
402
403 delete_single_post(post)
404 return HttpResponse("The post has been deleted.")
405
406
407 def delete_single_post(post):
408 """
409 This function deletes a single post. It handles the case of where
410 a post is the sole post in a topic by deleting the topic also. It
411 adjusts any foreign keys in Topic or Forum objects that might be pointing
412 to this post before deleting the post to avoid a cascading delete.
413 """
414 if post.topic.post_count == 1 and post == post.topic.last_post:
415 _delete_topic(post.topic)
416 else:
417 _delete_post(post)
418
419
420 def _delete_post(post):
421 """
422 Internal function to delete a single post object.
423 Decrements the post author's post count.
424 Adjusts the parent topic and forum's last_post as needed.
425 """
426 # Adjust post creator's post count
427 profile = post.user.get_profile()
428 if profile.forum_post_count > 0:
429 profile.forum_post_count -= 1
430 profile.save()
431
432 # If this post is the last_post in a topic, we need to update
433 # both the topic and parent forum's last post fields. If we don't
434 # the cascading delete will delete them also!
435
436 topic = post.topic
437 if topic.last_post == post:
438 topic.last_post_pre_delete()
439 topic.save()
440
441 forum = topic.forum
442 if forum.last_post == post:
443 forum.last_post_pre_delete()
444 forum.save()
445
446 # Should be safe to delete the post now:
447 post.delete()
448
449
450 def _delete_topic(topic):
451 """
452 Internal function to delete an entire topic.
453 Deletes the topic and all posts contained within.
454 Adjusts the parent forum's last_post as needed.
455 Note that we don't bother adjusting all the users'
456 post counts as that doesn't seem to be worth the effort.
457 """
458 if topic.forum.last_post and topic.forum.last_post.topic == topic:
459 topic.forum.last_post_pre_delete()
460 topic.forum.save()
461
462 # delete subscriptions to this topic
463 topic.subscribers.clear()
464 topic.bookmarkers.clear()
465
466 # It should be safe to just delete the topic now. This will
467 # automatically delete all posts in the topic.
468 topic.delete()
469
470
471 @login_required
472 def new_post(request, topic_id):
473 """
474 This function is the view for creating a normal, non-quick reply
475 to a topic.
476 """
477 topic = get_object_or_404(Topic.objects.select_related(), pk=topic_id)
478 can_post = _can_post_in_topic(topic, request.user)
479
480 if can_post:
481 if request.method == 'POST':
482 form = PostForm(request.POST)
483 if form.is_valid():
484 if antispam.utils.spam_check(request, form.cleaned_data['body']):
485 return HttpResponseRedirect(reverse('antispam-suspended'))
486 post = form.save(commit=False)
487 post.topic = topic
488 post.user = request.user
489 post.user_ip = request.META.get("REMOTE_ADDR", "")
490 post.save()
491 _bump_post_count(request.user)
492 _update_last_visit(request.user, topic)
493 return HttpResponseRedirect(post.get_absolute_url())
494 else:
495 quote_id = request.GET.get('quote')
496 if quote_id:
497 quote_post = get_object_or_404(Post.objects.select_related(),
498 pk=quote_id)
499 form = PostForm(initial={'body': _quote_message(quote_post.user.username,
500 quote_post.body)})
501 else:
502 form = PostForm()
503 else:
504 form = None
505
506 return render_to_response('forums/new_post.html', {
507 'forum': topic.forum,
508 'topic': topic,
509 'form': form,
510 'can_post': can_post,
511 },
512 context_instance=RequestContext(request))
513
514
515 @login_required
516 def mod_topic_stick(request, id):
517 """
518 This view function is for moderators to toggle the sticky status of a topic.
519 """
520 topic = get_object_or_404(Topic.objects.select_related(), pk=id)
521 if _can_moderate(topic.forum, request.user):
522 topic.sticky = not topic.sticky
523 topic.save()
524 return HttpResponseRedirect(topic.get_absolute_url())
525
526 return HttpResponseForbidden()
527
528
529 @login_required
530 def mod_topic_lock(request, id):
531 """
532 This view function is for moderators to toggle the locked status of a topic.
533 """
534 topic = get_object_or_404(Topic.objects.select_related(), pk=id)
535 if _can_moderate(topic.forum, request.user):
536 topic.locked = not topic.locked
537 topic.save()
538 return HttpResponseRedirect(topic.get_absolute_url())
539
540 return HttpResponseForbidden()
541
542
543 @login_required
544 def mod_topic_delete(request, id):
545 """
546 This view function is for moderators to delete an entire topic.
547 """
548 topic = get_object_or_404(Topic.objects.select_related(), pk=id)
549 if _can_moderate(topic.forum, request.user):
550 forum_url = topic.forum.get_absolute_url()
551 _delete_topic(topic)
552 return HttpResponseRedirect(forum_url)
553
554 return HttpResponseForbidden()
555
556
557 @login_required
558 def mod_topic_move(request, id):
559 """
560 This view function is for moderators to move a topic to a different forum.
561 """
562 topic = get_object_or_404(Topic.objects.select_related(), pk=id)
563 if not _can_moderate(topic.forum, request.user):
564 return HttpResponseForbidden()
565
566 if request.method == 'POST':
567 form = MoveTopicForm(request.user, request.POST)
568 if form.is_valid():
569 new_forum = form.cleaned_data['forums']
570 old_forum = topic.forum
571 _move_topic(topic, old_forum, new_forum)
572 return HttpResponseRedirect(topic.get_absolute_url())
573 else:
574 form = MoveTopicForm(request.user)
575
576 return render_to_response('forums/move_topic.html', {
577 'forum': topic.forum,
578 'topic': topic,
579 'form': form,
580 },
581 context_instance=RequestContext(request))
582
583
584 @login_required
585 def mod_forum(request, slug):
586 """
587 Displays a view to allow moderators to perform various operations
588 on topics in a forum in bulk. We currently support mass locking/unlocking,
589 stickying and unstickying, moving, and deleting topics.
590 """
591 forum = get_object_or_404(Forum.objects.select_related(), slug=slug)
592 if not _can_moderate(forum, request.user):
593 return HttpResponseForbidden()
594
595 topics = forum.topics.select_related('user', 'last_post', 'last_post__user')
596 paginator = create_topic_paginator(topics)
597 page_num = get_page_num(request)
598 try:
599 page = paginator.page(page_num)
600 except InvalidPage:
601 raise Http404
602
603 # we do this for the template since it is rendered twice
604 page_nav = render_to_string('forums/pagination.html', {'page': page})
605 form = None
606
607 if request.method == 'POST':
608 topic_ids = request.POST.getlist('topic_ids')
609 url = reverse('forums-mod_forum', kwargs={'slug':forum.slug})
610 url += '?page=%s' % page_num
611
612 if len(topic_ids):
613 if request.POST.get('sticky'):
614 _bulk_sticky(forum, topic_ids)
615 return HttpResponseRedirect(url)
616 elif request.POST.get('lock'):
617 _bulk_lock(forum, topic_ids)
618 return HttpResponseRedirect(url)
619 elif request.POST.get('delete'):
620 _bulk_delete(forum, topic_ids)
621 return HttpResponseRedirect(url)
622 elif request.POST.get('move'):
623 form = MoveTopicForm(request.user, request.POST, hide_label=True)
624 if form.is_valid():
625 _bulk_move(topic_ids, forum, form.cleaned_data['forums'])
626 return HttpResponseRedirect(url)
627
628 if form is None:
629 form = MoveTopicForm(request.user, hide_label=True)
630
631 return render_to_response('forums/mod_forum.html', {
632 'forum': forum,
633 'page': page,
634 'page_nav': page_nav,
635 'form': form,
636 },
637 context_instance=RequestContext(request))
638
639
640 @login_required
641 @require_POST
642 def forum_catchup(request, slug):
643 """
644 This view marks all the topics in the forum as being read.
645 """
646 forum = get_object_or_404(Forum.objects.select_related(), slug=slug)
647
648 if not forum.category.can_access(request.user):
649 return HttpResponseForbidden()
650
651 forum.catchup(request.user)
652 return HttpResponseRedirect(forum.get_absolute_url())
653
654
655 @login_required
656 def mod_topic_split(request, id):
657 """
658 This view function allows moderators to split posts off to a new topic.
659 """
660 topic = get_object_or_404(Topic.objects.select_related(), pk=id)
661 if not _can_moderate(topic.forum, request.user):
662 return HttpResponseRedirect(topic.get_absolute_url())
663
664 if request.method == "POST":
665 form = SplitTopicForm(request.user, request.POST)
666 if form.is_valid():
667 if form.split_at:
668 _split_topic_at(topic, form.post_ids[0],
669 form.cleaned_data['forums'],
670 form.cleaned_data['name'])
671 else:
672 _split_topic(topic, form.post_ids,
673 form.cleaned_data['forums'],
674 form.cleaned_data['name'])
675
676 return HttpResponseRedirect(topic.get_absolute_url())
677 else:
678 form = SplitTopicForm(request.user)
679
680 posts = topic.posts.select_related()
681
682 return render_to_response('forums/mod_split_topic.html', {
683 'forum': topic.forum,
684 'topic': topic,
685 'posts': posts,
686 'form': form,
687 },
688 context_instance=RequestContext(request))
689
690
691 @login_required
692 def unread_topics(request):
693 """Displays the topics with unread posts for a given user."""
694
695 topics = get_unread_topics(request.user)
696
697 paginator = create_topic_paginator(topics)
698 page_num = get_page_num(request)
699 try:
700 page = paginator.page(page_num)
701 except InvalidPage:
702 raise Http404
703
704 attach_topic_page_ranges(page.object_list)
705
706 # we do this for the template since it is rendered twice
707 page_nav = render_to_string('forums/pagination.html', {'page': page})
708
709 return render_to_response('forums/topic_list.html', {
710 'title': 'Topics With Unread Posts',
711 'page': page,
712 'page_nav': page_nav,
713 },
714 context_instance=RequestContext(request))
715
716
717 def unanswered_topics(request):
718 """Displays the topics with no replies."""
719
720 forum_ids = Forum.objects.forum_ids_for_user(request.user)
721 topics = Topic.objects.filter(forum__id__in=forum_ids,
722 post_count=1).select_related(
723 'forum', 'user', 'last_post', 'last_post__user')
724
725 paginator = create_topic_paginator(topics)
726 page_num = get_page_num(request)
727 try:
728 page = paginator.page(page_num)
729 except InvalidPage:
730 raise Http404
731
732 attach_topic_page_ranges(page.object_list)
733
734 # we do this for the template since it is rendered twice
735 page_nav = render_to_string('forums/pagination.html', {'page': page})
736
737 return render_to_response('forums/topic_list.html', {
738 'title': 'Unanswered Topics',
739 'page': page,
740 'page_nav': page_nav,
741 },
742 context_instance=RequestContext(request))
743
744
745 @login_required
746 def my_posts(request):
747 """Displays a list of posts the requesting user made."""
748 return _user_posts(request, request.user, request.user, 'My Posts')
749
750
751 @login_required
752 def posts_for_user(request, username):
753 """Displays a list of posts by the given user.
754 Only the forums that the requesting user can see are examined.
755 """
756 target_user = get_object_or_404(User, username=username)
757 return _user_posts(request, target_user, request.user, 'Posts by %s' % username)
758
759
760 @login_required
761 def post_ip_info(request, post_id):
762 """Displays information about the IP address the post was made from."""
763 post = get_object_or_404(Post.objects.select_related(), pk=post_id)
764
765 if not _can_moderate(post.topic.forum, request.user):
766 return HttpResponseForbidden("You don't have permission for this post.")
767
768 ip_users = sorted(set(Post.objects.filter(
769 user_ip=post.user_ip).values_list('user__username', flat=True)))
770
771 return render_to_response('forums/post_ip.html', {
772 'post': post,
773 'ip_users': ip_users,
774 },
775 context_instance=RequestContext(request))
776
777
778 def _user_posts(request, target_user, req_user, page_title):
779 """Displays a list of posts made by the target user.
780 req_user is the user trying to view the posts. Only the forums
781 req_user can see are searched.
782 """
783 forum_ids = Forum.objects.forum_ids_for_user(req_user)
784 posts = Post.objects.filter(user=target_user,
785 topic__forum__id__in=forum_ids).order_by(
786 '-creation_date').select_related()
787
788 paginator = create_post_paginator(posts)
789 page_num = get_page_num(request)
790 try:
791 page = paginator.page(page_num)
792 except InvalidPage:
793 raise Http404
794
795 # we do this for the template since it is rendered twice
796 page_nav = render_to_string('forums/pagination.html', {'page': page})
797
798 return render_to_response('forums/post_list.html', {
799 'title': page_title,
800 'page': page,
801 'page_nav': page_nav,
802 },
803 context_instance=RequestContext(request))
804
805
806 def _can_moderate(forum, user):
807 """
808 Determines if a user has permission to moderate a given forum.
809 """
810 return user.is_authenticated() and (
811 user.is_superuser or user in forum.moderators.all())
812
813
814 def _can_post_in_topic(topic, user):
815 """
816 This function returns true if the given user can post in the given topic
817 and false otherwise.
818 """
819 return (not topic.locked and topic.forum.category.can_access(user)) or \
820 (user.is_superuser or user in topic.forum.moderators.all())
821
822
823 def _bump_post_count(user):
824 """
825 Increments the forum_post_count for the given user.
826 """
827 profile = user.get_profile()
828 profile.forum_post_count += 1
829 profile.save()
830
831
832 def _quote_message(who, message):
833 """
834 Builds a message reply by quoting the existing message in a
835 typical email-like fashion. The quoting is compatible with Markdown.
836 """
837 header = '*%s wrote:*\n\n' % (who, )
838 lines = wrap(message, 55).split('\n')
839 for i, line in enumerate(lines):
840 lines[i] = '> ' + line
841 return header + '\n'.join(lines)
842
843
844 def _move_topic(topic, old_forum, new_forum):
845 if new_forum != old_forum:
846 topic.forum = new_forum
847 topic.save()
848 # Have to adjust foreign keys to last_post, denormalized counts, etc.:
849 old_forum.sync()
850 old_forum.save()
851 new_forum.sync()
852 new_forum.save()
853
854
855 def _bulk_sticky(forum, topic_ids):
856 """
857 Performs a toggle on the sticky status for a given list of topic ids.
858 """
859 topics = Topic.objects.filter(pk__in=topic_ids)
860 for topic in topics:
861 if topic.forum == forum:
862 topic.sticky = not topic.sticky
863 topic.save()
864
865
866 def _bulk_lock(forum, topic_ids):
867 """
868 Performs a toggle on the locked status for a given list of topic ids.
869 """
870 topics = Topic.objects.filter(pk__in=topic_ids)
871 for topic in topics:
872 if topic.forum == forum:
873 topic.locked = not topic.locked
874 topic.save()
875
876
877 def _bulk_delete(forum, topic_ids):
878 """
879 Deletes the list of topics.
880 """
881 topics = Topic.objects.filter(pk__in=topic_ids).select_related()
882 for topic in topics:
883 if topic.forum == forum:
884 _delete_topic(topic)
885
886
887 def _bulk_move(topic_ids, old_forum, new_forum):
888 """
889 Moves the list of topics to a new forum.
890 """
891 topics = Topic.objects.filter(pk__in=topic_ids).select_related()
892 for topic in topics:
893 if topic.forum == old_forum:
894 _move_topic(topic, old_forum, new_forum)
895
896
897 def _update_last_visit(user, topic):
898 """
899 Does the bookkeeping for the last visit status for the user to the
900 topic/forum.
901 """
902 now = datetime.datetime.now()
903 try:
904 flv = ForumLastVisit.objects.get(user=user, forum=topic.forum)
905 except ForumLastVisit.DoesNotExist:
906 flv = ForumLastVisit(user=user, forum=topic.forum)
907 flv.begin_date = now
908
909 flv.end_date = now
910 flv.save()
911
912 if topic.update_date > flv.begin_date:
913 try:
914 tlv = TopicLastVisit.objects.get(user=user, topic=topic)
915 except TopicLastVisit.DoesNotExist:
916 tlv = TopicLastVisit(user=user, topic=topic)
917
918 tlv.touch()
919 tlv.save()
920
921
922 def _split_topic_at(topic, post_id, new_forum, new_name):
923 """
924 This function splits the post given by post_id and all posts that come
925 after it in the given topic to a new topic in a new forum.
926 It is assumed the caller has been checked for moderator rights.
927 """
928 post = get_object_or_404(Post, id=post_id)
929 if post.topic == topic:
930 post_ids = Post.objects.filter(topic=topic,
931 creation_date__gte=post.creation_date).values_list('id', flat=True)
932 _split_topic(topic, post_ids, new_forum, new_name)
933
934
935 def _split_topic(topic, post_ids, new_forum, new_name):
936 """
937 This function splits the posts given by the post_ids list in the
938 given topic to a new topic in a new forum.
939 It is assumed the caller has been checked for moderator rights.
940 """
941 posts = Post.objects.filter(topic=topic, id__in=post_ids)
942 if len(posts) > 0:
943 new_topic = Topic(forum=new_forum, name=new_name, user=posts[0].user)
944 new_topic.save()
945 for post in posts:
946 post.topic = new_topic
947 post.save()
948
949 topic.post_count_update()
950 topic.save()
951 new_topic.post_count_update()
952 new_topic.save()
953 topic.forum.sync()
954 topic.forum.save()
955 new_forum.sync()
956 new_forum.save()