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