annotate forums/views/main.py @ 861:e4f8d87c3d30

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