annotate gpp/forums/views.py @ 114:535d02d1c017

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