annotate forums/views/main.py @ 693:ad69236e8501

For issue #52, update many 3rd party Javascript libraries. Updated to jquery 1.10.2, jquery ui 1.10.3. This broke a lot of stuff. - Found a newer version of the jquery cycle all plugin (3.0.3). - Updated JPlayer to 2.4.0. - Updated to MarkItUp 1.1.14. This also required me to add multiline attributes set to true on various buttons in the markdown set. - As per a stackoverflow post, added some code to get multiline titles in a jQuery UI dialog. They removed that functionality but allow you to put it back. Tweaked the MarkItUp preview CSS to show blockquotes in italic. Did not update TinyMCE at this time. I'm not using the JQuery version and this version appears to work ok for now. What I should do is make a repo for MarkItUp and do a vendor branch thing so I don't have to futz around diffing directories to figure out if I'll lose changes when I update.
author Brian Neal <bgneal@gmail.com>
date Wed, 04 Sep 2013 19:55:20 -0500
parents 91de9b15b410
children 9e803323a0d0
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@459 26 from bio.models import UserProfile, 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@459 31 ForumLastVisit, Attachment)
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@680 173 posts = topic.posts.select_related('user')
bgneal@232 174
bgneal@232 175 paginator = create_post_paginator(posts)
bgneal@232 176 page_num = get_page_num(request)
bgneal@232 177 try:
bgneal@232 178 page = paginator.page(page_num)
bgneal@232 179 except InvalidPage:
bgneal@232 180 raise Http404
bgneal@232 181 get_post_unread_status(topic, page.object_list, request.user)
bgneal@232 182
bgneal@329 183 # Attach user profiles to each post's user to avoid using
bgneal@329 184 # get_user_profile() in the template.
bgneal@232 185 users = set(post.user.id for post in page.object_list)
bgneal@232 186
bgneal@232 187 profiles = UserProfile.objects.filter(user__id__in=users).select_related()
bgneal@349 188 profile_keys = [profile.id for profile in profiles]
bgneal@232 189 user_profiles = dict((profile.user.id, profile) for profile in profiles)
bgneal@232 190
bgneal@445 191 last_post_on_page = None
bgneal@232 192 for post in page.object_list:
bgneal@329 193 post.user.user_profile = user_profiles[post.user.id]
bgneal@285 194 post.attach_list = []
bgneal@445 195 last_post_on_page = post
bgneal@285 196
bgneal@329 197 # Attach badge ownership info to the user profiles to avoid lots
bgneal@329 198 # of database hits in the template:
bgneal@329 199 bos_qs = BadgeOwnership.objects.filter(
bgneal@349 200 profile__id__in=profile_keys).select_related()
bgneal@329 201 bos = collections.defaultdict(list)
bgneal@329 202 for bo in bos_qs:
bgneal@329 203 bos[bo.profile.id].append(bo)
bgneal@329 204
bgneal@349 205 for user_id, profile in user_profiles.iteritems():
bgneal@349 206 profile.badge_ownership = bos[profile.id]
bgneal@329 207
bgneal@285 208 # Attach any attachments
bgneal@285 209 post_ids = [post.pk for post in page.object_list]
bgneal@285 210 attachments = Attachment.objects.filter(post__in=post_ids).select_related(
bgneal@285 211 'embed').order_by('order')
bgneal@285 212
bgneal@285 213 post_dict = dict((post.pk, post) for post in page.object_list)
bgneal@285 214 for item in attachments:
bgneal@285 215 post_dict[item.post.id].attach_list.append(item.embed)
bgneal@232 216
bgneal@232 217 last_page = page_num == paginator.num_pages
bgneal@232 218
bgneal@445 219 if request.user.is_authenticated():
bgneal@445 220 if last_page or last_post_on_page is None:
bgneal@445 221 visit_time = datetime.datetime.now()
bgneal@445 222 else:
bgneal@445 223 visit_time = last_post_on_page.creation_date
bgneal@445 224 _update_last_visit(request.user, topic, visit_time)
bgneal@232 225
bgneal@232 226 # we do this for the template since it is rendered twice
bgneal@232 227 page_nav = render_to_string('forums/pagination.html', {'page': page})
bgneal@232 228
bgneal@459 229 can_moderate = perms.can_moderate(topic.forum, request.user)
bgneal@232 230
bgneal@232 231 can_reply = request.user.is_authenticated() and (
bgneal@232 232 not topic.locked or can_moderate)
bgneal@232 233
bgneal@232 234 is_favorite = request.user.is_authenticated() and (
bgneal@232 235 topic in request.user.favorite_topics.all())
bgneal@232 236
bgneal@232 237 is_subscribed = request.user.is_authenticated() and (
bgneal@232 238 topic in request.user.subscriptions.all())
bgneal@232 239
bgneal@232 240 return render_to_response('forums/topic.html', {
bgneal@232 241 'forum': topic.forum,
bgneal@232 242 'topic': topic,
bgneal@232 243 'page': page,
bgneal@232 244 'page_nav': page_nav,
bgneal@232 245 'last_page': last_page,
bgneal@232 246 'can_moderate': can_moderate,
bgneal@232 247 'can_reply': can_reply,
bgneal@232 248 'form': NewPostForm(initial={'topic_id': topic.id}),
bgneal@232 249 'is_favorite': is_favorite,
bgneal@232 250 'is_subscribed': is_subscribed,
bgneal@232 251 },
bgneal@232 252 context_instance=RequestContext(request))
bgneal@232 253
bgneal@232 254
bgneal@374 255 def topic_unread(request, id):
bgneal@374 256 """
bgneal@374 257 This view redirects to the first post the user hasn't read, if we can
bgneal@374 258 figure that out. Otherwise we redirect to the topic.
bgneal@374 259
bgneal@374 260 """
bgneal@374 261 topic_url = reverse('forums-topic_index', kwargs={'id': id})
bgneal@374 262
bgneal@374 263 if request.user.is_authenticated():
bgneal@680 264 topic = get_object_or_404(Topic.objects.select_related('forum', 'user'), pk=id)
bgneal@374 265 try:
bgneal@376 266 tlv = TopicLastVisit.objects.get(user=request.user, topic=topic)
bgneal@374 267 except TopicLastVisit.DoesNotExist:
bgneal@376 268 try:
bgneal@376 269 flv = ForumLastVisit.objects.get(user=request.user,
bgneal@376 270 forum=topic.forum)
bgneal@376 271 except ForumLastVisit.DoesNotExist:
bgneal@376 272 return HttpResponseRedirect(topic_url)
bgneal@376 273 else:
bgneal@376 274 last_visit = flv.begin_date
bgneal@376 275 else:
bgneal@376 276 last_visit = tlv.last_visit
bgneal@374 277
bgneal@376 278 posts = Post.objects.filter(topic=topic, creation_date__gt=last_visit)
bgneal@374 279 if posts:
bgneal@374 280 return _goto_post(posts[0])
bgneal@374 281 else:
bgneal@374 282 # just go to the last post in the topic
bgneal@374 283 return _goto_post(topic.last_post)
bgneal@374 284
bgneal@374 285 # user isn't authenticated, just go to the topic
bgneal@374 286 return HttpResponseRedirect(topic_url)
bgneal@374 287
bgneal@374 288
bgneal@529 289 def topic_latest(request, id):
bgneal@529 290 """
bgneal@529 291 This view shows the latest (last) post in a given topic.
bgneal@529 292
bgneal@529 293 """
bgneal@680 294 topic = get_object_or_404(Topic.objects.select_related('forum', 'user'), pk=id)
bgneal@529 295
bgneal@529 296 if topic.last_post:
bgneal@529 297 return _goto_post(topic.last_post)
bgneal@529 298
bgneal@529 299 raise Http404
bgneal@529 300
bgneal@529 301
bgneal@232 302 @login_required
bgneal@232 303 def new_topic(request, slug):
bgneal@232 304 """
bgneal@232 305 This view handles the creation of new topics.
bgneal@232 306 """
bgneal@232 307 forum = get_object_or_404(Forum.objects.select_related(), slug=slug)
bgneal@232 308
bgneal@459 309 if not perms.can_access(forum.category, request.user):
bgneal@232 310 return HttpResponseForbidden()
bgneal@232 311
bgneal@232 312 if request.method == 'POST':
bgneal@232 313 form = NewTopicForm(request.user, forum, request.POST)
bgneal@232 314 if form.is_valid():
bgneal@232 315 if antispam.utils.spam_check(request, form.cleaned_data['body']):
bgneal@232 316 return HttpResponseRedirect(reverse('antispam-suspended'))
bgneal@232 317
bgneal@232 318 topic = form.save(request.META.get("REMOTE_ADDR"))
bgneal@232 319 _bump_post_count(request.user)
bgneal@232 320 return HttpResponseRedirect(reverse('forums-new_topic_thanks',
bgneal@232 321 kwargs={'tid': topic.pk}))
bgneal@232 322 else:
bgneal@232 323 form = NewTopicForm(request.user, forum)
bgneal@286 324
bgneal@232 325 return render_to_response('forums/new_topic.html', {
bgneal@232 326 'forum': forum,
bgneal@232 327 'form': form,
bgneal@232 328 },
bgneal@232 329 context_instance=RequestContext(request))
bgneal@232 330
bgneal@232 331
bgneal@232 332 @login_required
bgneal@232 333 def new_topic_thanks(request, tid):
bgneal@232 334 """
bgneal@232 335 This view displays the success page for a newly created topic.
bgneal@232 336 """
bgneal@232 337 topic = get_object_or_404(Topic.objects.select_related(), pk=tid)
bgneal@232 338 return render_to_response('forums/new_topic_thanks.html', {
bgneal@232 339 'forum': topic.forum,
bgneal@232 340 'topic': topic,
bgneal@232 341 },
bgneal@232 342 context_instance=RequestContext(request))
bgneal@232 343
bgneal@232 344
bgneal@232 345 @require_POST
bgneal@232 346 def quick_reply_ajax(request):
bgneal@232 347 """
bgneal@232 348 This function handles the quick reply to a thread function. This
bgneal@232 349 function is meant to be the target of an AJAX post, and returns
bgneal@232 350 the HTML for the new post, which the client-side script appends
bgneal@232 351 to the document.
bgneal@232 352 """
bgneal@232 353 if not request.user.is_authenticated():
bgneal@232 354 return HttpResponseForbidden('Please login or register to post.')
bgneal@232 355
bgneal@232 356 form = NewPostForm(request.POST)
bgneal@232 357 if form.is_valid():
bgneal@459 358 if not perms.can_post(form.topic, request.user):
bgneal@232 359 return HttpResponseForbidden("You don't have permission to post in this topic.")
bgneal@232 360 if antispam.utils.spam_check(request, form.cleaned_data['body']):
bgneal@232 361 return HttpResponseForbidden(antispam.BUSTED_MESSAGE)
bgneal@232 362
bgneal@232 363 post = form.save(request.user, request.META.get("REMOTE_ADDR", ""))
bgneal@232 364 post.unread = True
bgneal@329 365 post.user.user_profile = request.user.get_profile()
bgneal@285 366 post.attach_list = post.attachments.all()
bgneal@232 367 _bump_post_count(request.user)
bgneal@446 368 _update_last_visit(request.user, form.topic, datetime.datetime.now())
bgneal@285 369
bgneal@232 370 return render_to_response('forums/display_post.html', {
bgneal@232 371 'post': post,
bgneal@459 372 'can_moderate': perms.can_moderate(form.topic.forum, request.user),
bgneal@232 373 'can_reply': True,
bgneal@232 374 },
bgneal@232 375 context_instance=RequestContext(request))
bgneal@232 376
bgneal@286 377 return HttpResponseBadRequest("Oops, did you forget some text?");
bgneal@232 378
bgneal@232 379
bgneal@374 380 def _goto_post(post):
bgneal@374 381 """
bgneal@374 382 Calculate what page the given post is on in its parent topic, then
bgneal@374 383 return a redirect to it.
bgneal@374 384
bgneal@374 385 """
bgneal@374 386 count = post.topic.posts.filter(creation_date__lt=post.creation_date).count()
bgneal@374 387 page = count / POSTS_PER_PAGE + 1
bgneal@374 388 url = (reverse('forums-topic_index', kwargs={'id': post.topic.id}) +
bgneal@374 389 '?page=%s#p%s' % (page, post.id))
bgneal@374 390 return HttpResponseRedirect(url)
bgneal@374 391
bgneal@374 392
bgneal@232 393 def goto_post(request, post_id):
bgneal@232 394 """
bgneal@232 395 This function calculates what page a given post is on, then redirects
bgneal@232 396 to that URL. This function is the target of get_absolute_url() for
bgneal@232 397 Post objects.
bgneal@232 398 """
bgneal@232 399 post = get_object_or_404(Post.objects.select_related(), pk=post_id)
bgneal@374 400 return _goto_post(post)
bgneal@232 401
bgneal@232 402
bgneal@232 403 @require_POST
bgneal@232 404 def flag_post(request):
bgneal@232 405 """
bgneal@232 406 This function handles the flagging of posts by users. This function should
bgneal@232 407 be the target of an AJAX post.
bgneal@232 408 """
bgneal@232 409 if not request.user.is_authenticated():
bgneal@232 410 return HttpResponseForbidden('Please login or register to flag a post.')
bgneal@232 411
bgneal@232 412 id = request.POST.get('id')
bgneal@232 413 if id is None:
bgneal@232 414 return HttpResponseBadRequest('No post id')
bgneal@232 415
bgneal@232 416 try:
bgneal@232 417 post = Post.objects.get(pk=id)
bgneal@232 418 except Post.DoesNotExist:
bgneal@232 419 return HttpResponseBadRequest('No post with id %s' % id)
bgneal@232 420
bgneal@232 421 flag = FlaggedPost(user=request.user, post=post)
bgneal@232 422 flag.save()
bgneal@232 423 email_admins('A Post Has Been Flagged', """Hello,
bgneal@232 424
bgneal@232 425 A user has flagged a forum post for review.
bgneal@232 426 """)
bgneal@232 427 return HttpResponse('The post was flagged. A moderator will review the post shortly. ' \
bgneal@232 428 'Thanks for helping to improve the discussions on this site.')
bgneal@232 429
bgneal@232 430
bgneal@232 431 @login_required
bgneal@232 432 def edit_post(request, id):
bgneal@232 433 """
bgneal@232 434 This view function allows authorized users to edit posts.
bgneal@232 435 The superuser, forum moderators, and original author can edit posts.
bgneal@232 436 """
bgneal@232 437 post = get_object_or_404(Post.objects.select_related(), pk=id)
bgneal@232 438
bgneal@459 439 can_moderate = perms.can_moderate(post.topic.forum, request.user)
bgneal@232 440 can_edit = can_moderate or request.user == post.user
bgneal@232 441
bgneal@232 442 if not can_edit:
bgneal@232 443 return HttpResponseForbidden("You don't have permission to edit that post.")
bgneal@232 444
bgneal@295 445 topic_name = None
bgneal@295 446 first_post = Post.objects.filter(topic=post.topic).order_by('creation_date')[0]
bgneal@295 447 if first_post.id == post.id:
bgneal@295 448 topic_name = post.topic.name
bgneal@295 449
bgneal@232 450 if request.method == "POST":
bgneal@295 451 form = PostForm(request.POST, instance=post, topic_name=topic_name)
bgneal@232 452 if form.is_valid():
bgneal@232 453 if antispam.utils.spam_check(request, form.cleaned_data['body']):
bgneal@232 454 return HttpResponseRedirect(reverse('antispam-suspended'))
bgneal@232 455 post = form.save(commit=False)
bgneal@232 456 post.touch()
bgneal@232 457 post.save()
bgneal@469 458 notify_updated_post(post)
bgneal@285 459
bgneal@295 460 # if we are editing a first post, save the parent topic as well
bgneal@295 461 if topic_name:
bgneal@295 462 post.topic.save()
bgneal@469 463 notify_updated_topic(post.topic)
bgneal@295 464
bgneal@285 465 # Save any attachments
bgneal@286 466 form.attach_proc.save_attachments(post)
bgneal@285 467
bgneal@232 468 return HttpResponseRedirect(post.get_absolute_url())
bgneal@232 469 else:
bgneal@295 470 form = PostForm(instance=post, topic_name=topic_name)
bgneal@232 471
bgneal@329 472 post.user.user_profile = post.user.get_profile()
bgneal@232 473
bgneal@232 474 return render_to_response('forums/edit_post.html', {
bgneal@232 475 'forum': post.topic.forum,
bgneal@232 476 'topic': post.topic,
bgneal@232 477 'post': post,
bgneal@232 478 'form': form,
bgneal@232 479 'can_moderate': can_moderate,
bgneal@232 480 },
bgneal@232 481 context_instance=RequestContext(request))
bgneal@232 482
bgneal@232 483
bgneal@232 484 @require_POST
bgneal@232 485 def delete_post(request):
bgneal@232 486 """
bgneal@232 487 This view function allows superusers and forum moderators to delete posts.
bgneal@232 488 This function is the target of AJAX calls from the client.
bgneal@232 489 """
bgneal@232 490 if not request.user.is_authenticated():
bgneal@232 491 return HttpResponseForbidden('Please login to delete a post.')
bgneal@232 492
bgneal@232 493 id = request.POST.get('id')
bgneal@232 494 if id is None:
bgneal@232 495 return HttpResponseBadRequest('No post id')
bgneal@232 496
bgneal@232 497 post = get_object_or_404(Post.objects.select_related(), pk=id)
bgneal@232 498
bgneal@460 499 if not perms.can_moderate(post.topic.forum, request.user):
bgneal@232 500 return HttpResponseForbidden("You don't have permission to delete that post.")
bgneal@232 501
bgneal@232 502 delete_single_post(post)
bgneal@232 503 return HttpResponse("The post has been deleted.")
bgneal@232 504
bgneal@232 505
bgneal@232 506 def delete_single_post(post):
bgneal@232 507 """
bgneal@232 508 This function deletes a single post. It handles the case of where
bgneal@232 509 a post is the sole post in a topic by deleting the topic also. It
bgneal@232 510 adjusts any foreign keys in Topic or Forum objects that might be pointing
bgneal@232 511 to this post before deleting the post to avoid a cascading delete.
bgneal@232 512 """
bgneal@232 513 if post.topic.post_count == 1 and post == post.topic.last_post:
bgneal@232 514 _delete_topic(post.topic)
bgneal@232 515 else:
bgneal@232 516 _delete_post(post)
bgneal@232 517
bgneal@232 518
bgneal@232 519 def _delete_post(post):
bgneal@232 520 """
bgneal@232 521 Internal function to delete a single post object.
bgneal@232 522 Decrements the post author's post count.
bgneal@232 523 Adjusts the parent topic and forum's last_post as needed.
bgneal@232 524 """
bgneal@232 525 # Adjust post creator's post count
bgneal@232 526 profile = post.user.get_profile()
bgneal@232 527 if profile.forum_post_count > 0:
bgneal@232 528 profile.forum_post_count -= 1
bgneal@562 529 profile.save(content_update=False)
bgneal@232 530
bgneal@232 531 # If this post is the last_post in a topic, we need to update
bgneal@232 532 # both the topic and parent forum's last post fields. If we don't
bgneal@232 533 # the cascading delete will delete them also!
bgneal@232 534
bgneal@232 535 topic = post.topic
bgneal@232 536 if topic.last_post == post:
bgneal@232 537 topic.last_post_pre_delete()
bgneal@232 538 topic.save()
bgneal@232 539
bgneal@232 540 forum = topic.forum
bgneal@232 541 if forum.last_post == post:
bgneal@232 542 forum.last_post_pre_delete()
bgneal@232 543 forum.save()
bgneal@232 544
bgneal@285 545 # delete any attachments
bgneal@285 546 post.attachments.clear()
bgneal@285 547
bgneal@232 548 # Should be safe to delete the post now:
bgneal@232 549 post.delete()
bgneal@232 550
bgneal@232 551
bgneal@232 552 def _delete_topic(topic):
bgneal@232 553 """
bgneal@232 554 Internal function to delete an entire topic.
bgneal@232 555 Deletes the topic and all posts contained within.
bgneal@232 556 Adjusts the parent forum's last_post as needed.
bgneal@232 557 Note that we don't bother adjusting all the users'
bgneal@232 558 post counts as that doesn't seem to be worth the effort.
bgneal@232 559 """
bgneal@318 560 parent_forum = topic.forum
bgneal@318 561 if parent_forum.last_post and parent_forum.last_post.topic == topic:
bgneal@318 562 parent_forum.last_post_pre_delete(deleting_topic=True)
bgneal@318 563 parent_forum.save()
bgneal@232 564
bgneal@232 565 # delete subscriptions to this topic
bgneal@232 566 topic.subscribers.clear()
bgneal@232 567 topic.bookmarkers.clear()
bgneal@232 568
bgneal@285 569 # delete all attachments
bgneal@285 570 posts = Post.objects.filter(topic=topic)
bgneal@285 571 for post in posts:
bgneal@285 572 post.attachments.clear()
bgneal@285 573
bgneal@318 574 # Null out the topic's last post so we don't have a foreign key pointing
bgneal@318 575 # to a post when we delete posts.
bgneal@318 576 topic.last_post = None
bgneal@318 577 topic.save()
bgneal@318 578
bgneal@318 579 # delete all posts in bulk
bgneal@318 580 posts.delete()
bgneal@318 581
bgneal@318 582 # It should be safe to just delete the topic now.
bgneal@232 583 topic.delete()
bgneal@232 584
bgneal@318 585 # Resync parent forum's post and topic counts
bgneal@318 586 parent_forum.sync()
bgneal@318 587 parent_forum.save()
bgneal@318 588
bgneal@232 589
bgneal@232 590 @login_required
bgneal@232 591 def new_post(request, topic_id):
bgneal@232 592 """
bgneal@232 593 This function is the view for creating a normal, non-quick reply
bgneal@232 594 to a topic.
bgneal@232 595 """
bgneal@232 596 topic = get_object_or_404(Topic.objects.select_related(), pk=topic_id)
bgneal@459 597 can_post = perms.can_post(topic, request.user)
bgneal@232 598
bgneal@232 599 if can_post:
bgneal@232 600 if request.method == 'POST':
bgneal@232 601 form = PostForm(request.POST)
bgneal@232 602 if form.is_valid():
bgneal@232 603 if antispam.utils.spam_check(request, form.cleaned_data['body']):
bgneal@232 604 return HttpResponseRedirect(reverse('antispam-suspended'))
bgneal@232 605 post = form.save(commit=False)
bgneal@232 606 post.topic = topic
bgneal@232 607 post.user = request.user
bgneal@232 608 post.user_ip = request.META.get("REMOTE_ADDR", "")
bgneal@232 609 post.save()
bgneal@469 610 notify_new_post(post)
bgneal@285 611
bgneal@285 612 # Save any attachments
bgneal@286 613 form.attach_proc.save_attachments(post)
bgneal@285 614
bgneal@232 615 _bump_post_count(request.user)
bgneal@446 616 _update_last_visit(request.user, topic, datetime.datetime.now())
bgneal@232 617 return HttpResponseRedirect(post.get_absolute_url())
bgneal@232 618 else:
bgneal@232 619 quote_id = request.GET.get('quote')
bgneal@232 620 if quote_id:
bgneal@232 621 quote_post = get_object_or_404(Post.objects.select_related(),
bgneal@232 622 pk=quote_id)
bgneal@566 623 form = PostForm(initial={'body': quote_message(quote_post.user.username,
bgneal@232 624 quote_post.body)})
bgneal@232 625 else:
bgneal@232 626 form = PostForm()
bgneal@232 627 else:
bgneal@232 628 form = None
bgneal@232 629
bgneal@232 630 return render_to_response('forums/new_post.html', {
bgneal@232 631 'forum': topic.forum,
bgneal@232 632 'topic': topic,
bgneal@232 633 'form': form,
bgneal@232 634 'can_post': can_post,
bgneal@232 635 },
bgneal@232 636 context_instance=RequestContext(request))
bgneal@232 637
bgneal@232 638
bgneal@232 639 @login_required
bgneal@232 640 def mod_topic_stick(request, id):
bgneal@232 641 """
bgneal@232 642 This view function is for moderators to toggle the sticky status of a topic.
bgneal@232 643 """
bgneal@232 644 topic = get_object_or_404(Topic.objects.select_related(), pk=id)
bgneal@459 645 if perms.can_moderate(topic.forum, request.user):
bgneal@232 646 topic.sticky = not topic.sticky
bgneal@232 647 topic.save()
bgneal@232 648 return HttpResponseRedirect(topic.get_absolute_url())
bgneal@232 649
bgneal@232 650 return HttpResponseForbidden()
bgneal@232 651
bgneal@232 652
bgneal@232 653 @login_required
bgneal@232 654 def mod_topic_lock(request, id):
bgneal@232 655 """
bgneal@232 656 This view function is for moderators to toggle the locked status of a topic.
bgneal@232 657 """
bgneal@232 658 topic = get_object_or_404(Topic.objects.select_related(), pk=id)
bgneal@459 659 if perms.can_moderate(topic.forum, request.user):
bgneal@232 660 topic.locked = not topic.locked
bgneal@232 661 topic.save()
bgneal@232 662 return HttpResponseRedirect(topic.get_absolute_url())
bgneal@232 663
bgneal@232 664 return HttpResponseForbidden()
bgneal@232 665
bgneal@232 666
bgneal@232 667 @login_required
bgneal@232 668 def mod_topic_delete(request, id):
bgneal@232 669 """
bgneal@232 670 This view function is for moderators to delete an entire topic.
bgneal@232 671 """
bgneal@232 672 topic = get_object_or_404(Topic.objects.select_related(), pk=id)
bgneal@459 673 if perms.can_moderate(topic.forum, request.user):
bgneal@232 674 forum_url = topic.forum.get_absolute_url()
bgneal@232 675 _delete_topic(topic)
bgneal@232 676 return HttpResponseRedirect(forum_url)
bgneal@232 677
bgneal@232 678 return HttpResponseForbidden()
bgneal@232 679
bgneal@232 680
bgneal@232 681 @login_required
bgneal@232 682 def mod_topic_move(request, id):
bgneal@232 683 """
bgneal@232 684 This view function is for moderators to move a topic to a different forum.
bgneal@232 685 """
bgneal@232 686 topic = get_object_or_404(Topic.objects.select_related(), pk=id)
bgneal@459 687 if not perms.can_moderate(topic.forum, request.user):
bgneal@232 688 return HttpResponseForbidden()
bgneal@232 689
bgneal@232 690 if request.method == 'POST':
bgneal@232 691 form = MoveTopicForm(request.user, request.POST)
bgneal@232 692 if form.is_valid():
bgneal@232 693 new_forum = form.cleaned_data['forums']
bgneal@232 694 old_forum = topic.forum
bgneal@232 695 _move_topic(topic, old_forum, new_forum)
bgneal@232 696 return HttpResponseRedirect(topic.get_absolute_url())
bgneal@232 697 else:
bgneal@232 698 form = MoveTopicForm(request.user)
bgneal@232 699
bgneal@232 700 return render_to_response('forums/move_topic.html', {
bgneal@232 701 'forum': topic.forum,
bgneal@232 702 'topic': topic,
bgneal@232 703 'form': form,
bgneal@232 704 },
bgneal@232 705 context_instance=RequestContext(request))
bgneal@232 706
bgneal@232 707
bgneal@232 708 @login_required
bgneal@232 709 def mod_forum(request, slug):
bgneal@232 710 """
bgneal@232 711 Displays a view to allow moderators to perform various operations
bgneal@232 712 on topics in a forum in bulk. We currently support mass locking/unlocking,
bgneal@232 713 stickying and unstickying, moving, and deleting topics.
bgneal@232 714 """
bgneal@232 715 forum = get_object_or_404(Forum.objects.select_related(), slug=slug)
bgneal@459 716 if not perms.can_moderate(forum, request.user):
bgneal@232 717 return HttpResponseForbidden()
bgneal@232 718
bgneal@232 719 topics = forum.topics.select_related('user', 'last_post', 'last_post__user')
bgneal@232 720 paginator = create_topic_paginator(topics)
bgneal@232 721 page_num = get_page_num(request)
bgneal@232 722 try:
bgneal@232 723 page = paginator.page(page_num)
bgneal@232 724 except InvalidPage:
bgneal@232 725 raise Http404
bgneal@232 726
bgneal@232 727 # we do this for the template since it is rendered twice
bgneal@232 728 page_nav = render_to_string('forums/pagination.html', {'page': page})
bgneal@232 729 form = None
bgneal@232 730
bgneal@232 731 if request.method == 'POST':
bgneal@232 732 topic_ids = request.POST.getlist('topic_ids')
bgneal@232 733 url = reverse('forums-mod_forum', kwargs={'slug':forum.slug})
bgneal@232 734 url += '?page=%s' % page_num
bgneal@232 735
bgneal@232 736 if len(topic_ids):
bgneal@232 737 if request.POST.get('sticky'):
bgneal@232 738 _bulk_sticky(forum, topic_ids)
bgneal@232 739 return HttpResponseRedirect(url)
bgneal@232 740 elif request.POST.get('lock'):
bgneal@232 741 _bulk_lock(forum, topic_ids)
bgneal@232 742 return HttpResponseRedirect(url)
bgneal@232 743 elif request.POST.get('delete'):
bgneal@232 744 _bulk_delete(forum, topic_ids)
bgneal@232 745 return HttpResponseRedirect(url)
bgneal@232 746 elif request.POST.get('move'):
bgneal@232 747 form = MoveTopicForm(request.user, request.POST, hide_label=True)
bgneal@232 748 if form.is_valid():
bgneal@232 749 _bulk_move(topic_ids, forum, form.cleaned_data['forums'])
bgneal@232 750 return HttpResponseRedirect(url)
bgneal@286 751
bgneal@232 752 if form is None:
bgneal@232 753 form = MoveTopicForm(request.user, hide_label=True)
bgneal@232 754
bgneal@232 755 return render_to_response('forums/mod_forum.html', {
bgneal@232 756 'forum': forum,
bgneal@232 757 'page': page,
bgneal@232 758 'page_nav': page_nav,
bgneal@232 759 'form': form,
bgneal@232 760 },
bgneal@232 761 context_instance=RequestContext(request))
bgneal@232 762
bgneal@232 763
bgneal@232 764 @login_required
bgneal@232 765 @require_POST
bgneal@382 766 def catchup_all(request):
bgneal@382 767 """
bgneal@382 768 This view marks all forums as being read.
bgneal@382 769 """
bgneal@384 770 forum_ids = Forum.objects.forum_ids_for_user(request.user)
bgneal@382 771
bgneal@382 772 tlvs = TopicLastVisit.objects.filter(user=request.user,
bgneal@382 773 topic__forum__id__in=forum_ids).delete()
bgneal@382 774
bgneal@382 775 now = datetime.datetime.now()
bgneal@384 776 ForumLastVisit.objects.filter(user=request.user,
bgneal@384 777 forum__in=forum_ids).update(begin_date=now, end_date=now)
bgneal@382 778
bgneal@382 779 return HttpResponseRedirect(reverse('forums-index'))
bgneal@382 780
bgneal@382 781
bgneal@382 782 @login_required
bgneal@382 783 @require_POST
bgneal@232 784 def forum_catchup(request, slug):
bgneal@232 785 """
bgneal@232 786 This view marks all the topics in the forum as being read.
bgneal@232 787 """
bgneal@232 788 forum = get_object_or_404(Forum.objects.select_related(), slug=slug)
bgneal@232 789
bgneal@459 790 if not perms.can_access(forum.category, request.user):
bgneal@232 791 return HttpResponseForbidden()
bgneal@232 792
bgneal@232 793 forum.catchup(request.user)
bgneal@232 794 return HttpResponseRedirect(forum.get_absolute_url())
bgneal@232 795
bgneal@232 796
bgneal@232 797 @login_required
bgneal@232 798 def mod_topic_split(request, id):
bgneal@232 799 """
bgneal@232 800 This view function allows moderators to split posts off to a new topic.
bgneal@232 801 """
bgneal@232 802 topic = get_object_or_404(Topic.objects.select_related(), pk=id)
bgneal@459 803 if not perms.can_moderate(topic.forum, request.user):
bgneal@232 804 return HttpResponseRedirect(topic.get_absolute_url())
bgneal@232 805
bgneal@232 806 if request.method == "POST":
bgneal@232 807 form = SplitTopicForm(request.user, request.POST)
bgneal@232 808 if form.is_valid():
bgneal@232 809 if form.split_at:
bgneal@232 810 _split_topic_at(topic, form.post_ids[0],
bgneal@232 811 form.cleaned_data['forums'],
bgneal@232 812 form.cleaned_data['name'])
bgneal@232 813 else:
bgneal@232 814 _split_topic(topic, form.post_ids,
bgneal@232 815 form.cleaned_data['forums'],
bgneal@232 816 form.cleaned_data['name'])
bgneal@232 817
bgneal@232 818 return HttpResponseRedirect(topic.get_absolute_url())
bgneal@232 819 else:
bgneal@232 820 form = SplitTopicForm(request.user)
bgneal@232 821
bgneal@232 822 posts = topic.posts.select_related()
bgneal@232 823
bgneal@232 824 return render_to_response('forums/mod_split_topic.html', {
bgneal@232 825 'forum': topic.forum,
bgneal@232 826 'topic': topic,
bgneal@232 827 'posts': posts,
bgneal@232 828 'form': form,
bgneal@232 829 },
bgneal@232 830 context_instance=RequestContext(request))
bgneal@232 831
bgneal@232 832
bgneal@232 833 @login_required
bgneal@232 834 def unread_topics(request):
bgneal@232 835 """Displays the topics with unread posts for a given user."""
bgneal@232 836
bgneal@232 837 topics = get_unread_topics(request.user)
bgneal@232 838
bgneal@232 839 paginator = create_topic_paginator(topics)
bgneal@232 840 page_num = get_page_num(request)
bgneal@232 841 try:
bgneal@232 842 page = paginator.page(page_num)
bgneal@232 843 except InvalidPage:
bgneal@232 844 raise Http404
bgneal@232 845
bgneal@232 846 attach_topic_page_ranges(page.object_list)
bgneal@232 847
bgneal@232 848 # we do this for the template since it is rendered twice
bgneal@232 849 page_nav = render_to_string('forums/pagination.html', {'page': page})
bgneal@232 850
bgneal@232 851 return render_to_response('forums/topic_list.html', {
bgneal@232 852 'title': 'Topics With Unread Posts',
bgneal@232 853 'page': page,
bgneal@232 854 'page_nav': page_nav,
bgneal@374 855 'unread': True,
bgneal@232 856 },
bgneal@232 857 context_instance=RequestContext(request))
bgneal@232 858
bgneal@232 859
bgneal@232 860 def unanswered_topics(request):
bgneal@232 861 """Displays the topics with no replies."""
bgneal@232 862
bgneal@232 863 forum_ids = Forum.objects.forum_ids_for_user(request.user)
bgneal@232 864 topics = Topic.objects.filter(forum__id__in=forum_ids,
bgneal@232 865 post_count=1).select_related(
bgneal@232 866 'forum', 'user', 'last_post', 'last_post__user')
bgneal@232 867
bgneal@232 868 paginator = create_topic_paginator(topics)
bgneal@232 869 page_num = get_page_num(request)
bgneal@232 870 try:
bgneal@232 871 page = paginator.page(page_num)
bgneal@232 872 except InvalidPage:
bgneal@232 873 raise Http404
bgneal@232 874
bgneal@232 875 attach_topic_page_ranges(page.object_list)
bgneal@232 876
bgneal@232 877 # we do this for the template since it is rendered twice
bgneal@232 878 page_nav = render_to_string('forums/pagination.html', {'page': page})
bgneal@232 879
bgneal@232 880 return render_to_response('forums/topic_list.html', {
bgneal@232 881 'title': 'Unanswered Topics',
bgneal@232 882 'page': page,
bgneal@232 883 'page_nav': page_nav,
bgneal@374 884 'unread': False,
bgneal@232 885 },
bgneal@232 886 context_instance=RequestContext(request))
bgneal@232 887
bgneal@232 888
bgneal@409 889 def active_topics(request, num):
bgneal@409 890 """Displays the last num topics that have been posted to."""
bgneal@409 891
bgneal@409 892 # sanity check num
bgneal@409 893 num = min(50, max(10, int(num)))
bgneal@409 894
bgneal@409 895 # MySQL didn't do this query very well unfortunately...
bgneal@522 896 #
bgneal@522 897 #public_forum_ids = Forum.objects.public_forum_ids()
bgneal@409 898 #topics = Topic.objects.filter(forum__in=public_forum_ids).select_related(
bgneal@409 899 # 'forum', 'user', 'last_post', 'last_post__user').order_by(
bgneal@409 900 # '-update_date')[:num]
bgneal@522 901
bgneal@522 902 # Save 1 query by using forums.latest to give us a list of the most recent
bgneal@522 903 # topics; forums.latest doesn't save enough info to give us everything we
bgneal@522 904 # need so we hit the database for the rest.
bgneal@522 905
bgneal@522 906 topic_ids = get_latest_topic_ids(num)
bgneal@409 907 topics = Topic.objects.filter(id__in=topic_ids).select_related(
bgneal@409 908 'forum', 'user', 'last_post', 'last_post__user').order_by(
bgneal@522 909 '-update_date')
bgneal@409 910
bgneal@409 911 paginator = create_topic_paginator(topics)
bgneal@409 912 page_num = get_page_num(request)
bgneal@409 913 try:
bgneal@409 914 page = paginator.page(page_num)
bgneal@409 915 except InvalidPage:
bgneal@409 916 raise Http404
bgneal@409 917
bgneal@409 918 attach_topic_page_ranges(page.object_list)
bgneal@409 919
bgneal@409 920 # we do this for the template since it is rendered twice
bgneal@409 921 page_nav = render_to_string('forums/pagination.html', {'page': page})
bgneal@409 922
bgneal@409 923 title = 'Last %d Active Topics' % num
bgneal@409 924
bgneal@409 925 return render_to_response('forums/topic_list.html', {
bgneal@409 926 'title': title,
bgneal@409 927 'page': page,
bgneal@409 928 'page_nav': page_nav,
bgneal@409 929 'unread': False,
bgneal@409 930 },
bgneal@409 931 context_instance=RequestContext(request))
bgneal@409 932
bgneal@409 933
bgneal@232 934 @login_required
bgneal@232 935 def my_posts(request):
bgneal@232 936 """Displays a list of posts the requesting user made."""
bgneal@232 937 return _user_posts(request, request.user, request.user, 'My Posts')
bgneal@232 938
bgneal@232 939
bgneal@232 940 @login_required
bgneal@232 941 def posts_for_user(request, username):
bgneal@232 942 """Displays a list of posts by the given user.
bgneal@232 943 Only the forums that the requesting user can see are examined.
bgneal@232 944 """
bgneal@232 945 target_user = get_object_or_404(User, username=username)
bgneal@232 946 return _user_posts(request, target_user, request.user, 'Posts by %s' % username)
bgneal@232 947
bgneal@232 948
bgneal@232 949 @login_required
bgneal@232 950 def post_ip_info(request, post_id):
bgneal@232 951 """Displays information about the IP address the post was made from."""
bgneal@232 952 post = get_object_or_404(Post.objects.select_related(), pk=post_id)
bgneal@232 953
bgneal@459 954 if not perms.can_moderate(post.topic.forum, request.user):
bgneal@232 955 return HttpResponseForbidden("You don't have permission for this post.")
bgneal@232 956
bgneal@232 957 ip_users = sorted(set(Post.objects.filter(
bgneal@232 958 user_ip=post.user_ip).values_list('user__username', flat=True)))
bgneal@232 959
bgneal@232 960 return render_to_response('forums/post_ip.html', {
bgneal@232 961 'post': post,
bgneal@232 962 'ip_users': ip_users,
bgneal@232 963 },
bgneal@232 964 context_instance=RequestContext(request))
bgneal@232 965
bgneal@232 966
bgneal@232 967 def _user_posts(request, target_user, req_user, page_title):
bgneal@286 968 """Displays a list of posts made by the target user.
bgneal@232 969 req_user is the user trying to view the posts. Only the forums
bgneal@232 970 req_user can see are searched.
bgneal@232 971 """
bgneal@232 972 forum_ids = Forum.objects.forum_ids_for_user(req_user)
bgneal@232 973 posts = Post.objects.filter(user=target_user,
bgneal@232 974 topic__forum__id__in=forum_ids).order_by(
bgneal@232 975 '-creation_date').select_related()
bgneal@232 976
bgneal@232 977 paginator = create_post_paginator(posts)
bgneal@232 978 page_num = get_page_num(request)
bgneal@232 979 try:
bgneal@232 980 page = paginator.page(page_num)
bgneal@232 981 except InvalidPage:
bgneal@232 982 raise Http404
bgneal@232 983
bgneal@232 984 # we do this for the template since it is rendered twice
bgneal@232 985 page_nav = render_to_string('forums/pagination.html', {'page': page})
bgneal@232 986
bgneal@232 987 return render_to_response('forums/post_list.html', {
bgneal@232 988 'title': page_title,
bgneal@232 989 'page': page,
bgneal@232 990 'page_nav': page_nav,
bgneal@232 991 },
bgneal@232 992 context_instance=RequestContext(request))
bgneal@232 993
bgneal@232 994
bgneal@232 995 def _bump_post_count(user):
bgneal@232 996 """
bgneal@232 997 Increments the forum_post_count for the given user.
bgneal@232 998 """
bgneal@232 999 profile = user.get_profile()
bgneal@232 1000 profile.forum_post_count += 1
bgneal@562 1001 profile.save(content_update=False)
bgneal@232 1002
bgneal@232 1003
bgneal@232 1004 def _move_topic(topic, old_forum, new_forum):
bgneal@232 1005 if new_forum != old_forum:
bgneal@232 1006 topic.forum = new_forum
bgneal@232 1007 topic.save()
bgneal@232 1008 # Have to adjust foreign keys to last_post, denormalized counts, etc.:
bgneal@232 1009 old_forum.sync()
bgneal@232 1010 old_forum.save()
bgneal@232 1011 new_forum.sync()
bgneal@232 1012 new_forum.save()
bgneal@232 1013
bgneal@232 1014
bgneal@232 1015 def _bulk_sticky(forum, topic_ids):
bgneal@232 1016 """
bgneal@232 1017 Performs a toggle on the sticky status for a given list of topic ids.
bgneal@232 1018 """
bgneal@232 1019 topics = Topic.objects.filter(pk__in=topic_ids)
bgneal@232 1020 for topic in topics:
bgneal@232 1021 if topic.forum == forum:
bgneal@232 1022 topic.sticky = not topic.sticky
bgneal@232 1023 topic.save()
bgneal@232 1024
bgneal@232 1025
bgneal@232 1026 def _bulk_lock(forum, topic_ids):
bgneal@232 1027 """
bgneal@232 1028 Performs a toggle on the locked status for a given list of topic ids.
bgneal@232 1029 """
bgneal@232 1030 topics = Topic.objects.filter(pk__in=topic_ids)
bgneal@232 1031 for topic in topics:
bgneal@232 1032 if topic.forum == forum:
bgneal@232 1033 topic.locked = not topic.locked
bgneal@232 1034 topic.save()
bgneal@232 1035
bgneal@232 1036
bgneal@232 1037 def _bulk_delete(forum, topic_ids):
bgneal@232 1038 """
bgneal@232 1039 Deletes the list of topics.
bgneal@232 1040 """
bgneal@235 1041 # Because we are deleting stuff, retrieve each topic one at a
bgneal@235 1042 # time since we are going to be adjusting de-normalized fields
bgneal@235 1043 # during deletes. In particular, we can't do this:
bgneal@235 1044 # topics = Topic.objects.filter(pk__in=topic_ids).select_related()
bgneal@235 1045 # for topic in topics:
bgneal@235 1046 # since topic.forum.last_post can go stale after a delete.
bgneal@235 1047
bgneal@235 1048 for id in topic_ids:
bgneal@235 1049 try:
bgneal@235 1050 topic = Topic.objects.select_related().get(pk=id)
bgneal@235 1051 except Topic.DoesNotExist:
bgneal@235 1052 continue
bgneal@235 1053 _delete_topic(topic)
bgneal@232 1054
bgneal@232 1055
bgneal@232 1056 def _bulk_move(topic_ids, old_forum, new_forum):
bgneal@232 1057 """
bgneal@232 1058 Moves the list of topics to a new forum.
bgneal@232 1059 """
bgneal@232 1060 topics = Topic.objects.filter(pk__in=topic_ids).select_related()
bgneal@232 1061 for topic in topics:
bgneal@232 1062 if topic.forum == old_forum:
bgneal@232 1063 _move_topic(topic, old_forum, new_forum)
bgneal@232 1064
bgneal@232 1065
bgneal@445 1066 def _update_last_visit(user, topic, visit_time):
bgneal@232 1067 """
bgneal@232 1068 Does the bookkeeping for the last visit status for the user to the
bgneal@232 1069 topic/forum.
bgneal@232 1070 """
bgneal@232 1071 now = datetime.datetime.now()
bgneal@232 1072 try:
bgneal@232 1073 flv = ForumLastVisit.objects.get(user=user, forum=topic.forum)
bgneal@232 1074 except ForumLastVisit.DoesNotExist:
bgneal@232 1075 flv = ForumLastVisit(user=user, forum=topic.forum)
bgneal@232 1076 flv.begin_date = now
bgneal@232 1077
bgneal@232 1078 flv.end_date = now
bgneal@232 1079 flv.save()
bgneal@232 1080
bgneal@232 1081 if topic.update_date > flv.begin_date:
bgneal@232 1082 try:
bgneal@232 1083 tlv = TopicLastVisit.objects.get(user=user, topic=topic)
bgneal@232 1084 except TopicLastVisit.DoesNotExist:
bgneal@445 1085 tlv = TopicLastVisit(user=user, topic=topic, last_visit=datetime.datetime.min)
bgneal@232 1086
bgneal@445 1087 if visit_time > tlv.last_visit:
bgneal@445 1088 tlv.last_visit = visit_time
bgneal@445 1089 tlv.save()
bgneal@232 1090
bgneal@232 1091
bgneal@232 1092 def _split_topic_at(topic, post_id, new_forum, new_name):
bgneal@232 1093 """
bgneal@232 1094 This function splits the post given by post_id and all posts that come
bgneal@232 1095 after it in the given topic to a new topic in a new forum.
bgneal@232 1096 It is assumed the caller has been checked for moderator rights.
bgneal@232 1097 """
bgneal@232 1098 post = get_object_or_404(Post, id=post_id)
bgneal@232 1099 if post.topic == topic:
bgneal@232 1100 post_ids = Post.objects.filter(topic=topic,
bgneal@232 1101 creation_date__gte=post.creation_date).values_list('id', flat=True)
bgneal@232 1102 _split_topic(topic, post_ids, new_forum, new_name)
bgneal@232 1103
bgneal@232 1104
bgneal@232 1105 def _split_topic(topic, post_ids, new_forum, new_name):
bgneal@232 1106 """
bgneal@232 1107 This function splits the posts given by the post_ids list in the
bgneal@232 1108 given topic to a new topic in a new forum.
bgneal@232 1109 It is assumed the caller has been checked for moderator rights.
bgneal@232 1110 """
bgneal@232 1111 posts = Post.objects.filter(topic=topic, id__in=post_ids)
bgneal@232 1112 if len(posts) > 0:
bgneal@232 1113 new_topic = Topic(forum=new_forum, name=new_name, user=posts[0].user)
bgneal@232 1114 new_topic.save()
bgneal@469 1115 notify_new_topic(new_topic)
bgneal@232 1116 for post in posts:
bgneal@232 1117 post.topic = new_topic
bgneal@232 1118 post.save()
bgneal@286 1119
bgneal@232 1120 topic.post_count_update()
bgneal@232 1121 topic.save()
bgneal@232 1122 new_topic.post_count_update()
bgneal@232 1123 new_topic.save()
bgneal@232 1124 topic.forum.sync()
bgneal@232 1125 topic.forum.save()
bgneal@232 1126 new_forum.sync()
bgneal@232 1127 new_forum.save()