annotate gpp/forums/views.py @ 133:c515b7401078

Use the new common way to apply markItUp to textareas and to get the smiley and markdown help dialogs for all the remaining apps except for forums and comments.
author Brian Neal <bgneal@gmail.com>
date Fri, 27 Nov 2009 00:21:47 +0000
parents 3ae999b0c53b
children 152d77265da6
rev   line source
bgneal@81 1 """
bgneal@81 2 Views for the forums application.
bgneal@81 3 """
bgneal@113 4 import datetime
bgneal@113 5
bgneal@83 6 from django.contrib.auth.decorators import login_required
bgneal@82 7 from django.http import Http404
bgneal@98 8 from django.http import HttpResponse
bgneal@89 9 from django.http import HttpResponseBadRequest
bgneal@90 10 from django.http import HttpResponseForbidden
bgneal@83 11 from django.http import HttpResponseRedirect
bgneal@83 12 from django.core.urlresolvers import reverse
bgneal@91 13 from django.core.paginator import InvalidPage
bgneal@82 14 from django.shortcuts import get_object_or_404
bgneal@81 15 from django.shortcuts import render_to_response
bgneal@97 16 from django.template.loader import render_to_string
bgneal@81 17 from django.template import RequestContext
bgneal@89 18 from django.views.decorators.http import require_POST
bgneal@108 19 from django.utils.text import wrap
bgneal@81 20
bgneal@90 21 from core.paginator import DiggPaginator
bgneal@98 22 from core.functions import email_admins
bgneal@113 23 from forums.models import Forum, Topic, Post, FlaggedPost, TopicLastVisit, \
bgneal@113 24 ForumLastVisit
bgneal@115 25 from forums.forms import NewTopicForm, NewPostForm, PostForm, MoveTopicForm, \
bgneal@115 26 SplitTopicForm
bgneal@114 27 from forums.unread import get_forum_unread_status, get_topic_unread_status, \
bgneal@114 28 get_post_unread_status
bgneal@81 29
bgneal@117 30 from bio.models import UserProfile
bgneal@90 31 #######################################################################
bgneal@90 32
bgneal@93 33 TOPICS_PER_PAGE = 50
bgneal@113 34 POSTS_PER_PAGE = 20
bgneal@90 35
bgneal@93 36 def create_topic_paginator(topics):
bgneal@93 37 return DiggPaginator(topics, TOPICS_PER_PAGE, body=5, tail=2, margin=3, padding=2)
bgneal@93 38
bgneal@93 39 def create_post_paginator(posts):
bgneal@93 40 return DiggPaginator(posts, POSTS_PER_PAGE, body=5, tail=2, margin=3, padding=2)
bgneal@90 41
bgneal@90 42 #######################################################################
bgneal@81 43
bgneal@81 44 def index(request):
bgneal@82 45 """
bgneal@82 46 This view displays all the forums available, ordered in each category.
bgneal@82 47 """
bgneal@100 48 forums = Forum.objects.forums_for_user(request.user)
bgneal@113 49 get_forum_unread_status(forums, request.user)
bgneal@81 50 cats = {}
bgneal@81 51 for forum in forums:
bgneal@81 52 cat = cats.setdefault(forum.category.id, {
bgneal@81 53 'cat': forum.category,
bgneal@81 54 'forums': [],
bgneal@81 55 })
bgneal@81 56 cat['forums'].append(forum)
bgneal@81 57
bgneal@81 58 cmpdef = lambda a, b: cmp(a['cat'].position, b['cat'].position)
bgneal@81 59 cats = sorted(cats.values(), cmpdef)
bgneal@81 60
bgneal@81 61 return render_to_response('forums/index.html', {
bgneal@81 62 'cats': cats,
bgneal@81 63 },
bgneal@81 64 context_instance=RequestContext(request))
bgneal@81 65
bgneal@82 66
bgneal@81 67 def forum_index(request, slug):
bgneal@82 68 """
bgneal@82 69 Displays all the topics in a forum.
bgneal@82 70 """
bgneal@101 71 forum = get_object_or_404(Forum.objects.select_related(), slug=slug)
bgneal@100 72
bgneal@100 73 if not forum.category.can_access(request.user):
bgneal@100 74 return HttpResponseForbidden()
bgneal@100 75
bgneal@107 76 topics = forum.topics.select_related('user', 'last_post', 'last_post__user')
bgneal@114 77 get_topic_unread_status(forum, topics, request.user)
bgneal@114 78
bgneal@93 79 paginator = create_topic_paginator(topics)
bgneal@93 80 page_num = int(request.GET.get('page', 1))
bgneal@93 81 try:
bgneal@93 82 page = paginator.page(page_num)
bgneal@93 83 except InvalidPage:
bgneal@93 84 raise Http404
bgneal@97 85
bgneal@97 86 # we do this for the template since it is rendered twice
bgneal@97 87 page_nav = render_to_string('forums/pagination.html', {'page': page})
bgneal@111 88
bgneal@111 89 can_moderate = _can_moderate(forum, request.user)
bgneal@82 90
bgneal@82 91 return render_to_response('forums/forum_index.html', {
bgneal@82 92 'forum': forum,
bgneal@93 93 'page': page,
bgneal@97 94 'page_nav': page_nav,
bgneal@111 95 'can_moderate': can_moderate,
bgneal@82 96 },
bgneal@82 97 context_instance=RequestContext(request))
bgneal@82 98
bgneal@82 99
bgneal@82 100 def topic_index(request, id):
bgneal@82 101 """
bgneal@82 102 Displays all the posts in a topic.
bgneal@82 103 """
bgneal@101 104 topic = get_object_or_404(Topic.objects.select_related(), pk=id)
bgneal@100 105
bgneal@100 106 if not topic.forum.category.can_access(request.user):
bgneal@100 107 return HttpResponseForbidden()
bgneal@100 108
bgneal@86 109 topic.view_count += 1
bgneal@86 110 topic.save()
bgneal@86 111
bgneal@86 112 posts = topic.posts.select_related()
bgneal@114 113
bgneal@93 114 paginator = create_post_paginator(posts)
bgneal@93 115 page_num = int(request.GET.get('page', 1))
bgneal@90 116 try:
bgneal@90 117 page = paginator.page(page_num)
bgneal@90 118 except InvalidPage:
bgneal@90 119 raise Http404
bgneal@117 120 get_post_unread_status(topic, page.object_list, request.user)
bgneal@117 121
bgneal@117 122 # Attach user profiles to each post to avoid using get_user_profile() in
bgneal@117 123 # the template.
bgneal@117 124 users = set(post.user.id for post in page.object_list)
bgneal@117 125
bgneal@117 126 profiles = UserProfile.objects.filter(user__id__in=users).select_related()
bgneal@117 127 user_profiles = dict((profile.user.id, profile) for profile in profiles)
bgneal@117 128
bgneal@117 129 for post in page.object_list:
bgneal@117 130 post.user_profile = user_profiles[post.user.id]
bgneal@90 131
bgneal@90 132 last_page = page_num == paginator.num_pages
bgneal@86 133
bgneal@113 134 if request.user.is_authenticated() and last_page:
bgneal@113 135 _update_last_visit(request.user, topic)
bgneal@113 136
bgneal@97 137 # we do this for the template since it is rendered twice
bgneal@97 138 page_nav = render_to_string('forums/pagination.html', {'page': page})
bgneal@97 139
bgneal@109 140 can_moderate = _can_moderate(topic.forum, request.user)
bgneal@104 141
bgneal@104 142 can_reply = request.user.is_authenticated() and (
bgneal@104 143 not topic.locked or can_moderate)
bgneal@104 144
bgneal@86 145 return render_to_response('forums/topic.html', {
bgneal@86 146 'forum': topic.forum,
bgneal@86 147 'topic': topic,
bgneal@90 148 'page': page,
bgneal@97 149 'page_nav': page_nav,
bgneal@87 150 'last_page': last_page,
bgneal@104 151 'can_moderate': can_moderate,
bgneal@104 152 'can_reply': can_reply,
bgneal@106 153 'form': NewPostForm(initial={'topic_id': topic.id}),
bgneal@86 154 },
bgneal@86 155 context_instance=RequestContext(request))
bgneal@83 156
bgneal@83 157
bgneal@83 158 @login_required
bgneal@83 159 def new_topic(request, slug):
bgneal@83 160 """
bgneal@83 161 This view handles the creation of new topics.
bgneal@83 162 """
bgneal@101 163 forum = get_object_or_404(Forum.objects.select_related(), slug=slug)
bgneal@100 164
bgneal@100 165 if not forum.category.can_access(request.user):
bgneal@100 166 return HttpResponseForbidden()
bgneal@100 167
bgneal@83 168 if request.method == 'POST':
bgneal@102 169 form = NewTopicForm(request.user, forum, request.POST)
bgneal@83 170 if form.is_valid():
bgneal@102 171 topic = form.save(request.META.get("REMOTE_ADDR"))
bgneal@108 172 _bump_post_count(request.user)
bgneal@83 173 return HttpResponseRedirect(reverse('forums-new_topic_thanks',
bgneal@83 174 kwargs={'tid': topic.pk}))
bgneal@83 175 else:
bgneal@102 176 form = NewTopicForm(request.user, forum)
bgneal@83 177
bgneal@83 178 return render_to_response('forums/new_topic.html', {
bgneal@83 179 'forum': forum,
bgneal@83 180 'form': form,
bgneal@83 181 },
bgneal@83 182 context_instance=RequestContext(request))
bgneal@83 183
bgneal@83 184
bgneal@83 185 @login_required
bgneal@83 186 def new_topic_thanks(request, tid):
bgneal@83 187 """
bgneal@83 188 This view displays the success page for a newly created topic.
bgneal@83 189 """
bgneal@101 190 topic = get_object_or_404(Topic.objects.select_related(), pk=tid)
bgneal@83 191 return render_to_response('forums/new_topic_thanks.html', {
bgneal@83 192 'forum': topic.forum,
bgneal@83 193 'topic': topic,
bgneal@83 194 },
bgneal@83 195 context_instance=RequestContext(request))
bgneal@89 196
bgneal@89 197
bgneal@89 198 @require_POST
bgneal@89 199 def quick_reply_ajax(request):
bgneal@89 200 """
bgneal@89 201 This function handles the quick reply to a thread function. This
bgneal@89 202 function is meant to be the target of an AJAX post, and returns
bgneal@89 203 the HTML for the new post, which the client-side script appends
bgneal@89 204 to the document.
bgneal@89 205 """
bgneal@90 206 if not request.user.is_authenticated():
bgneal@108 207 return HttpResponseForbidden('Please login or register to post.')
bgneal@90 208
bgneal@106 209 form = NewPostForm(request.POST)
bgneal@89 210 if form.is_valid():
bgneal@108 211 if not _can_post_in_topic(form.topic, request.user):
bgneal@108 212 return HttpResponseForbidden("You don't have permission to post in this topic.")
bgneal@100 213
bgneal@108 214 post = form.save(request.user, request.META.get("REMOTE_ADDR", ""))
bgneal@114 215 post.unread = True
bgneal@122 216 post.user_profile = request.user.get_profile()
bgneal@108 217 _bump_post_count(request.user)
bgneal@113 218 _update_last_visit(request.user, form.topic)
bgneal@89 219 return render_to_response('forums/display_post.html', {
bgneal@89 220 'post': post,
bgneal@113 221 'can_moderate': _can_moderate(form.topic.forum, request.user),
bgneal@120 222 'can_reply': True,
bgneal@89 223 },
bgneal@89 224 context_instance=RequestContext(request))
bgneal@89 225
bgneal@108 226 return HttpResponseBadRequest("Invalid post.");
bgneal@89 227
bgneal@91 228
bgneal@91 229 def goto_post(request, post_id):
bgneal@91 230 """
bgneal@91 231 This function calculates what page a given post is on, then redirects
bgneal@91 232 to that URL. This function is the target of get_absolute_url() for
bgneal@91 233 Post objects.
bgneal@91 234 """
bgneal@101 235 post = get_object_or_404(Post.objects.select_related(), pk=post_id)
bgneal@91 236 count = post.topic.posts.filter(creation_date__lt=post.creation_date).count()
bgneal@91 237 page = count / POSTS_PER_PAGE + 1
bgneal@91 238 url = reverse('forums-topic_index', kwargs={'id': post.topic.id}) + \
bgneal@91 239 '?page=%s#p%s' % (page, post.id)
bgneal@91 240 return HttpResponseRedirect(url)
bgneal@91 241
bgneal@98 242
bgneal@98 243 @require_POST
bgneal@98 244 def flag_post(request):
bgneal@98 245 """
bgneal@98 246 This function handles the flagging of posts by users. This function should
bgneal@98 247 be the target of an AJAX post.
bgneal@98 248 """
bgneal@98 249 if not request.user.is_authenticated():
bgneal@99 250 return HttpResponseForbidden('Please login or register to flag a post.')
bgneal@98 251
bgneal@98 252 id = request.POST.get('id')
bgneal@98 253 if id is None:
bgneal@98 254 return HttpResponseBadRequest('No post id')
bgneal@98 255
bgneal@98 256 try:
bgneal@98 257 post = Post.objects.get(pk=id)
bgneal@98 258 except Post.DoesNotExist:
bgneal@98 259 return HttpResponseBadRequest('No post with id %s' % id)
bgneal@98 260
bgneal@98 261 flag = FlaggedPost(user=request.user, post=post)
bgneal@98 262 flag.save()
bgneal@98 263 email_admins('A Post Has Been Flagged', """Hello,
bgneal@98 264
bgneal@98 265 A user has flagged a forum post for review.
bgneal@98 266 """)
bgneal@98 267 return HttpResponse('The post was flagged. A moderator will review the post shortly. ' \
bgneal@98 268 'Thanks for helping to improve the discussions on this site.')
bgneal@106 269
bgneal@106 270
bgneal@106 271 @login_required
bgneal@106 272 def edit_post(request, id):
bgneal@106 273 """
bgneal@106 274 This view function allows authorized users to edit posts.
bgneal@106 275 The superuser, forum moderators, and original author can edit posts.
bgneal@106 276 """
bgneal@106 277 post = get_object_or_404(Post.objects.select_related(), pk=id)
bgneal@108 278
bgneal@109 279 can_moderate = _can_moderate(post.topic.forum, request.user)
bgneal@108 280 can_edit = can_moderate or request.user == post.user
bgneal@106 281
bgneal@106 282 if not can_edit:
bgneal@106 283 return HttpResponseForbidden("You don't have permission to edit that post.")
bgneal@106 284
bgneal@106 285 if request.method == "POST":
bgneal@106 286 form = PostForm(request.POST, instance=post)
bgneal@106 287 if form.is_valid():
bgneal@115 288 post = form.save(commit=False)
bgneal@115 289 post.touch()
bgneal@115 290 post.save()
bgneal@106 291 return HttpResponseRedirect(post.get_absolute_url())
bgneal@106 292 else:
bgneal@106 293 form = PostForm(instance=post)
bgneal@106 294
bgneal@123 295 post.user_profile = request.user.get_profile()
bgneal@123 296
bgneal@106 297 return render_to_response('forums/edit_post.html', {
bgneal@106 298 'forum': post.topic.forum,
bgneal@106 299 'topic': post.topic,
bgneal@106 300 'post': post,
bgneal@106 301 'form': form,
bgneal@108 302 'can_moderate': can_moderate,
bgneal@106 303 },
bgneal@106 304 context_instance=RequestContext(request))
bgneal@107 305
bgneal@107 306
bgneal@107 307 @require_POST
bgneal@107 308 def delete_post(request):
bgneal@107 309 """
bgneal@107 310 This view function allows superusers and forum moderators to delete posts.
bgneal@107 311 This function is the target of AJAX calls from the client.
bgneal@107 312 """
bgneal@107 313 if not request.user.is_authenticated():
bgneal@107 314 return HttpResponseForbidden('Please login to delete a post.')
bgneal@107 315
bgneal@107 316 id = request.POST.get('id')
bgneal@107 317 if id is None:
bgneal@107 318 return HttpResponseBadRequest('No post id')
bgneal@107 319
bgneal@107 320 post = get_object_or_404(Post.objects.select_related(), pk=id)
bgneal@107 321
bgneal@107 322 can_delete = request.user.is_superuser or \
bgneal@107 323 request.user in post.topic.forum.moderators.all()
bgneal@107 324
bgneal@107 325 if not can_delete:
bgneal@107 326 return HttpResponseForbidden("You don't have permission to delete that post.")
bgneal@107 327
bgneal@107 328 if post.topic.post_count == 1 and post == post.topic.last_post:
bgneal@107 329 _delete_topic(post.topic)
bgneal@107 330 else:
bgneal@107 331 _delete_post(post)
bgneal@107 332
bgneal@107 333 return HttpResponse("The post has been deleted.")
bgneal@107 334
bgneal@107 335
bgneal@107 336 def _delete_post(post):
bgneal@107 337 """
bgneal@107 338 Internal function to delete a single post object.
bgneal@107 339 Decrements the post author's post count.
bgneal@107 340 Adjusts the parent topic and forum's last_post as needed.
bgneal@107 341 """
bgneal@107 342 # Adjust post creator's post count
bgneal@107 343 profile = post.user.get_profile()
bgneal@107 344 if profile.forum_post_count > 0:
bgneal@107 345 profile.forum_post_count -= 1
bgneal@107 346 profile.save()
bgneal@107 347
bgneal@107 348 # If this post is the last_post in a topic, we need to update
bgneal@107 349 # both the topic and parent forum's last post fields. If we don't
bgneal@107 350 # the cascading delete will delete them also!
bgneal@107 351
bgneal@107 352 topic = post.topic
bgneal@107 353 if topic.last_post == post:
bgneal@107 354 topic.last_post_pre_delete()
bgneal@107 355 topic.save()
bgneal@107 356
bgneal@107 357 forum = topic.forum
bgneal@107 358 if forum.last_post == post:
bgneal@107 359 forum.last_post_pre_delete()
bgneal@107 360 forum.save()
bgneal@107 361
bgneal@107 362 # Should be safe to delete the post now:
bgneal@107 363 post.delete()
bgneal@107 364
bgneal@107 365
bgneal@107 366 def _delete_topic(topic):
bgneal@107 367 """
bgneal@107 368 Internal function to delete an entire topic.
bgneal@107 369 Deletes the topic and all posts contained within.
bgneal@107 370 Adjusts the parent forum's last_post as needed.
bgneal@107 371 Note that we don't bother adjusting all the users'
bgneal@107 372 post counts as that doesn't seem to be worth the effort.
bgneal@107 373 """
bgneal@107 374 if topic.forum.last_post.topic == topic:
bgneal@107 375 topic.forum.last_post_pre_delete()
bgneal@107 376 topic.forum.save()
bgneal@107 377
bgneal@107 378 # It should be safe to just delete the topic now. This will
bgneal@107 379 # automatically delete all posts in the topic.
bgneal@107 380 topic.delete()
bgneal@108 381
bgneal@108 382
bgneal@108 383 @login_required
bgneal@108 384 def new_post(request, topic_id):
bgneal@108 385 """
bgneal@108 386 This function is the view for creating a normal, non-quick reply
bgneal@108 387 to a topic.
bgneal@108 388 """
bgneal@108 389 topic = get_object_or_404(Topic.objects.select_related(), pk=topic_id)
bgneal@108 390 can_post = _can_post_in_topic(topic, request.user)
bgneal@108 391
bgneal@108 392 if can_post:
bgneal@108 393 if request.method == 'POST':
bgneal@108 394 form = PostForm(request.POST)
bgneal@108 395 if form.is_valid():
bgneal@108 396 post = form.save(commit=False)
bgneal@108 397 post.topic = topic
bgneal@108 398 post.user = request.user
bgneal@108 399 post.user_ip = request.META.get("REMOTE_ADDR", "")
bgneal@108 400 post.save()
bgneal@108 401 _bump_post_count(request.user)
bgneal@113 402 _update_last_visit(request.user, topic)
bgneal@108 403 return HttpResponseRedirect(post.get_absolute_url())
bgneal@108 404 else:
bgneal@108 405 quote_id = request.GET.get('quote')
bgneal@108 406 if quote_id:
bgneal@108 407 quote_post = get_object_or_404(Post.objects.select_related(),
bgneal@108 408 pk=quote_id)
bgneal@108 409 form = PostForm(initial={'body': _quote_message(quote_post.user.username,
bgneal@108 410 quote_post.body)})
bgneal@108 411 else:
bgneal@108 412 form = PostForm()
bgneal@108 413 else:
bgneal@108 414 form = None
bgneal@108 415
bgneal@108 416 return render_to_response('forums/new_post.html', {
bgneal@108 417 'forum': topic.forum,
bgneal@108 418 'topic': topic,
bgneal@108 419 'form': form,
bgneal@108 420 'can_post': can_post,
bgneal@108 421 },
bgneal@108 422 context_instance=RequestContext(request))
bgneal@108 423
bgneal@108 424
bgneal@109 425 @login_required
bgneal@109 426 def mod_topic_stick(request, id):
bgneal@109 427 """
bgneal@109 428 This view function is for moderators to toggle the sticky status of a topic.
bgneal@109 429 """
bgneal@109 430 topic = get_object_or_404(Topic.objects.select_related(), pk=id)
bgneal@109 431 if _can_moderate(topic.forum, request.user):
bgneal@109 432 topic.sticky = not topic.sticky
bgneal@109 433 topic.save()
bgneal@109 434 return HttpResponseRedirect(topic.get_absolute_url())
bgneal@109 435
bgneal@110 436 return HttpResponseForbidden()
bgneal@109 437
bgneal@109 438
bgneal@109 439 @login_required
bgneal@109 440 def mod_topic_lock(request, id):
bgneal@109 441 """
bgneal@109 442 This view function is for moderators to toggle the locked status of a topic.
bgneal@109 443 """
bgneal@109 444 topic = get_object_or_404(Topic.objects.select_related(), pk=id)
bgneal@109 445 if _can_moderate(topic.forum, request.user):
bgneal@109 446 topic.locked = not topic.locked
bgneal@109 447 topic.save()
bgneal@109 448 return HttpResponseRedirect(topic.get_absolute_url())
bgneal@109 449
bgneal@110 450 return HttpResponseForbidden()
bgneal@109 451
bgneal@109 452
bgneal@109 453 @login_required
bgneal@109 454 def mod_topic_delete(request, id):
bgneal@109 455 """
bgneal@109 456 This view function is for moderators to delete an entire topic.
bgneal@109 457 """
bgneal@109 458 topic = get_object_or_404(Topic.objects.select_related(), pk=id)
bgneal@109 459 if _can_moderate(topic.forum, request.user):
bgneal@109 460 forum_url = topic.forum.get_absolute_url()
bgneal@109 461 _delete_topic(topic)
bgneal@109 462 return HttpResponseRedirect(forum_url)
bgneal@109 463
bgneal@110 464 return HttpResponseForbidden()
bgneal@110 465
bgneal@110 466
bgneal@110 467 @login_required
bgneal@110 468 def mod_topic_move(request, id):
bgneal@110 469 """
bgneal@110 470 This view function is for moderators to move a topic to a different forum.
bgneal@110 471 """
bgneal@110 472 topic = get_object_or_404(Topic.objects.select_related(), pk=id)
bgneal@110 473 if not _can_moderate(topic.forum, request.user):
bgneal@110 474 return HttpResponseForbidden()
bgneal@110 475
bgneal@110 476 if request.method == 'POST':
bgneal@110 477 form = MoveTopicForm(request.user, request.POST)
bgneal@110 478 if form.is_valid():
bgneal@110 479 new_forum = form.cleaned_data['forums']
bgneal@110 480 old_forum = topic.forum
bgneal@111 481 _move_topic(topic, old_forum, new_forum)
bgneal@110 482 return HttpResponseRedirect(topic.get_absolute_url())
bgneal@110 483 else:
bgneal@110 484 form = MoveTopicForm(request.user)
bgneal@110 485
bgneal@110 486 return render_to_response('forums/move_topic.html', {
bgneal@110 487 'forum': topic.forum,
bgneal@110 488 'topic': topic,
bgneal@110 489 'form': form,
bgneal@110 490 },
bgneal@110 491 context_instance=RequestContext(request))
bgneal@109 492
bgneal@109 493
bgneal@111 494 @login_required
bgneal@111 495 def mod_forum(request, slug):
bgneal@111 496 """
bgneal@111 497 Displays a view to allow moderators to perform various operations
bgneal@111 498 on topics in a forum in bulk. We currently support mass locking/unlocking,
bgneal@111 499 stickying and unstickying, moving, and deleting topics.
bgneal@111 500 """
bgneal@111 501 forum = get_object_or_404(Forum.objects.select_related(), slug=slug)
bgneal@111 502 if not _can_moderate(forum, request.user):
bgneal@111 503 return HttpResponseForbidden()
bgneal@111 504
bgneal@111 505 topics = forum.topics.select_related('user', 'last_post', 'last_post__user')
bgneal@111 506 paginator = create_topic_paginator(topics)
bgneal@111 507 page_num = int(request.REQUEST.get('page', 1))
bgneal@111 508 try:
bgneal@111 509 page = paginator.page(page_num)
bgneal@111 510 except InvalidPage:
bgneal@111 511 raise Http404
bgneal@111 512
bgneal@111 513 # we do this for the template since it is rendered twice
bgneal@111 514 page_nav = render_to_string('forums/pagination.html', {'page': page})
bgneal@111 515 form = None
bgneal@111 516
bgneal@111 517 if request.method == 'POST':
bgneal@111 518 topic_ids = request.POST.getlist('topic_ids')
bgneal@111 519 url = reverse('forums-mod_forum', kwargs={'slug':forum.slug})
bgneal@111 520 url += '?page=%s' % page_num
bgneal@111 521
bgneal@111 522 if len(topic_ids):
bgneal@111 523 if request.POST.get('sticky'):
bgneal@111 524 _bulk_sticky(forum, topic_ids)
bgneal@111 525 return HttpResponseRedirect(url)
bgneal@111 526 elif request.POST.get('lock'):
bgneal@111 527 _bulk_lock(forum, topic_ids)
bgneal@111 528 return HttpResponseRedirect(url)
bgneal@111 529 elif request.POST.get('delete'):
bgneal@111 530 _bulk_delete(forum, topic_ids)
bgneal@111 531 return HttpResponseRedirect(url)
bgneal@111 532 elif request.POST.get('move'):
bgneal@111 533 form = MoveTopicForm(request.user, request.POST, hide_label=True)
bgneal@111 534 if form.is_valid():
bgneal@111 535 _bulk_move(topic_ids, forum, form.cleaned_data['forums'])
bgneal@111 536 return HttpResponseRedirect(url)
bgneal@111 537
bgneal@111 538 if form is None:
bgneal@111 539 form = MoveTopicForm(request.user, hide_label=True)
bgneal@111 540
bgneal@111 541 return render_to_response('forums/mod_forum.html', {
bgneal@111 542 'forum': forum,
bgneal@111 543 'page': page,
bgneal@111 544 'page_nav': page_nav,
bgneal@111 545 'form': form,
bgneal@111 546 },
bgneal@111 547 context_instance=RequestContext(request))
bgneal@111 548
bgneal@111 549
bgneal@113 550 @login_required
bgneal@113 551 @require_POST
bgneal@113 552 def forum_catchup(request, slug):
bgneal@113 553 """
bgneal@113 554 This view marks all the topics in the forum as being read.
bgneal@113 555 """
bgneal@113 556 forum = get_object_or_404(Forum.objects.select_related(), slug=slug)
bgneal@113 557
bgneal@113 558 if not forum.category.can_access(request.user):
bgneal@113 559 return HttpResponseForbidden()
bgneal@113 560
bgneal@113 561 forum.catchup(request.user)
bgneal@113 562 return HttpResponseRedirect(forum.get_absolute_url())
bgneal@113 563
bgneal@113 564
bgneal@115 565 @login_required
bgneal@115 566 def mod_topic_split(request, id):
bgneal@115 567 """
bgneal@115 568 This view function allows moderators to split posts off to a new topic.
bgneal@115 569 """
bgneal@115 570 topic = get_object_or_404(Topic.objects.select_related(), pk=id)
bgneal@115 571 if not _can_moderate(topic.forum, request.user):
bgneal@115 572 return HttpResponseRedirect(topic.get_absolute_url())
bgneal@115 573
bgneal@115 574 if request.method == "POST":
bgneal@115 575 form = SplitTopicForm(request.user, request.POST)
bgneal@115 576 if form.is_valid():
bgneal@115 577 if form.split_at:
bgneal@115 578 _split_topic_at(topic, form.post_ids[0],
bgneal@115 579 form.cleaned_data['forums'],
bgneal@115 580 form.cleaned_data['name'])
bgneal@115 581 else:
bgneal@115 582 _split_topic(topic, form.post_ids,
bgneal@115 583 form.cleaned_data['forums'],
bgneal@115 584 form.cleaned_data['name'])
bgneal@115 585
bgneal@115 586 return HttpResponseRedirect(topic.get_absolute_url())
bgneal@115 587 else:
bgneal@115 588 form = SplitTopicForm(request.user)
bgneal@115 589
bgneal@115 590 posts = topic.posts.select_related()
bgneal@115 591
bgneal@115 592 return render_to_response('forums/mod_split_topic.html', {
bgneal@115 593 'forum': topic.forum,
bgneal@115 594 'topic': topic,
bgneal@115 595 'posts': posts,
bgneal@115 596 'form': form,
bgneal@115 597 },
bgneal@115 598 context_instance=RequestContext(request))
bgneal@115 599
bgneal@115 600
bgneal@109 601 def _can_moderate(forum, user):
bgneal@109 602 """
bgneal@109 603 Determines if a user has permission to moderate a given forum.
bgneal@109 604 """
bgneal@109 605 return user.is_authenticated() and (
bgneal@109 606 user.is_superuser or user in forum.moderators.all())
bgneal@109 607
bgneal@109 608
bgneal@108 609 def _can_post_in_topic(topic, user):
bgneal@108 610 """
bgneal@108 611 This function returns true if the given user can post in the given topic
bgneal@108 612 and false otherwise.
bgneal@108 613 """
bgneal@108 614 return (not topic.locked and topic.forum.category.can_access(user)) or \
bgneal@108 615 (user.is_superuser or user in topic.forum.moderators.all())
bgneal@108 616
bgneal@108 617
bgneal@108 618 def _bump_post_count(user):
bgneal@108 619 """
bgneal@108 620 Increments the forum_post_count for the given user.
bgneal@108 621 """
bgneal@108 622 profile = user.get_profile()
bgneal@108 623 profile.forum_post_count += 1
bgneal@108 624 profile.save()
bgneal@108 625
bgneal@108 626
bgneal@108 627 def _quote_message(who, message):
bgneal@111 628 """
bgneal@111 629 Builds a message reply by quoting the existing message in a
bgneal@111 630 typical email-like fashion. The quoting is compatible with Markdown.
bgneal@111 631 """
bgneal@111 632 header = '*%s wrote:*\n\n' % (who, )
bgneal@111 633 lines = wrap(message, 55).split('\n')
bgneal@111 634 for i, line in enumerate(lines):
bgneal@111 635 lines[i] = '> ' + line
bgneal@111 636 return header + '\n'.join(lines)
bgneal@111 637
bgneal@111 638
bgneal@111 639 def _move_topic(topic, old_forum, new_forum):
bgneal@111 640 if new_forum != old_forum:
bgneal@111 641 topic.forum = new_forum
bgneal@111 642 topic.save()
bgneal@111 643 # Have to adjust foreign keys to last_post, denormalized counts, etc.:
bgneal@112 644 old_forum.sync()
bgneal@111 645 old_forum.save()
bgneal@112 646 new_forum.sync()
bgneal@111 647 new_forum.save()
bgneal@111 648
bgneal@111 649
bgneal@111 650 def _bulk_sticky(forum, topic_ids):
bgneal@111 651 """
bgneal@111 652 Performs a toggle on the sticky status for a given list of topic ids.
bgneal@111 653 """
bgneal@111 654 topics = Topic.objects.filter(pk__in=topic_ids)
bgneal@111 655 for topic in topics:
bgneal@111 656 if topic.forum == forum:
bgneal@111 657 topic.sticky = not topic.sticky
bgneal@111 658 topic.save()
bgneal@111 659
bgneal@111 660
bgneal@111 661 def _bulk_lock(forum, topic_ids):
bgneal@111 662 """
bgneal@111 663 Performs a toggle on the locked status for a given list of topic ids.
bgneal@111 664 """
bgneal@111 665 topics = Topic.objects.filter(pk__in=topic_ids)
bgneal@111 666 for topic in topics:
bgneal@111 667 if topic.forum == forum:
bgneal@111 668 topic.locked = not topic.locked
bgneal@111 669 topic.save()
bgneal@111 670
bgneal@111 671
bgneal@111 672 def _bulk_delete(forum, topic_ids):
bgneal@111 673 """
bgneal@111 674 Deletes the list of topics.
bgneal@111 675 """
bgneal@111 676 topics = Topic.objects.filter(pk__in=topic_ids).select_related()
bgneal@111 677 for topic in topics:
bgneal@111 678 if topic.forum == forum:
bgneal@111 679 _delete_topic(topic)
bgneal@111 680
bgneal@111 681
bgneal@111 682 def _bulk_move(topic_ids, old_forum, new_forum):
bgneal@111 683 """
bgneal@111 684 Moves the list of topics to a new forum.
bgneal@111 685 """
bgneal@111 686 topics = Topic.objects.filter(pk__in=topic_ids).select_related()
bgneal@111 687 for topic in topics:
bgneal@111 688 if topic.forum == old_forum:
bgneal@111 689 _move_topic(topic, old_forum, new_forum)
bgneal@111 690
bgneal@113 691
bgneal@113 692 def _update_last_visit(user, topic):
bgneal@113 693 """
bgneal@113 694 Does the bookkeeping for the last visit status for the user to the
bgneal@113 695 topic/forum.
bgneal@113 696 """
bgneal@113 697 now = datetime.datetime.now()
bgneal@113 698 try:
bgneal@113 699 flv = ForumLastVisit.objects.get(user=user, forum=topic.forum)
bgneal@113 700 except ForumLastVisit.DoesNotExist:
bgneal@113 701 flv = ForumLastVisit(user=user, forum=topic.forum)
bgneal@113 702 flv.begin_date = now
bgneal@113 703
bgneal@113 704 flv.end_date = now
bgneal@113 705 flv.save()
bgneal@113 706
bgneal@113 707 if topic.update_date > flv.begin_date:
bgneal@113 708 try:
bgneal@113 709 tlv = TopicLastVisit.objects.get(user=user, topic=topic)
bgneal@113 710 except TopicLastVisit.DoesNotExist:
bgneal@113 711 tlv = TopicLastVisit(user=user, topic=topic)
bgneal@113 712
bgneal@113 713 tlv.touch()
bgneal@113 714 tlv.save()
bgneal@113 715
bgneal@115 716
bgneal@115 717 def _split_topic_at(topic, post_id, new_forum, new_name):
bgneal@115 718 """
bgneal@115 719 This function splits the post given by post_id and all posts that come
bgneal@115 720 after it in the given topic to a new topic in a new forum.
bgneal@115 721 It is assumed the caller has been checked for moderator rights.
bgneal@115 722 """
bgneal@115 723 post = get_object_or_404(Post, id=post_id)
bgneal@115 724 if post.topic == topic:
bgneal@115 725 post_ids = Post.objects.filter(topic=topic,
bgneal@115 726 creation_date__gte=post.creation_date).values_list('id', flat=True)
bgneal@115 727 _split_topic(topic, post_ids, new_forum, new_name)
bgneal@115 728
bgneal@115 729
bgneal@115 730 def _split_topic(topic, post_ids, new_forum, new_name):
bgneal@115 731 """
bgneal@115 732 This function splits the posts given by the post_ids list in the
bgneal@115 733 given topic to a new topic in a new forum.
bgneal@115 734 It is assumed the caller has been checked for moderator rights.
bgneal@115 735 """
bgneal@115 736 posts = Post.objects.filter(topic=topic, id__in=post_ids)
bgneal@115 737 if len(posts) > 0:
bgneal@115 738 new_topic = Topic(forum=new_forum, name=new_name, user=posts[0].user)
bgneal@115 739 new_topic.save()
bgneal@115 740 for post in posts:
bgneal@115 741 post.topic = new_topic
bgneal@115 742 post.save()
bgneal@115 743
bgneal@115 744 topic.post_count_update()
bgneal@115 745 topic.save()
bgneal@115 746 new_topic.post_count_update()
bgneal@115 747 new_topic.save()
bgneal@115 748 topic.forum.sync()
bgneal@115 749 topic.forum.save()
bgneal@115 750 new_forum.sync()
bgneal@115 751 new_forum.save()