annotate gpp/forums/views.py @ 115:0ce0104c7df3

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