##// END OF EJS Templates
Minor fixes
neko259 -
r548:b40b7f87 1.7-dev
parent child Browse files
Show More
@@ -1,394 +1,395 b''
1 1 from datetime import datetime, timedelta
2 2 from datetime import time as dtime
3 3 import os
4 4 from random import random
5 5 import time
6 6 import math
7 7 import re
8 8 import hashlib
9 9
10 10 from django.core.cache import cache
11 11 from django.core.paginator import Paginator
12 12
13 13 from django.db import models
14 14 from django.http import Http404
15 15 from django.utils import timezone
16 16 from markupfield.fields import MarkupField
17 17
18 18 from neboard import settings
19 19 from boards import thumbs
20 20
21 21 MAX_TITLE_LENGTH = 50
22 22
23 23 APP_LABEL_BOARDS = 'boards'
24 24
25 25 CACHE_KEY_PPD = 'ppd'
26 26
27 27 POSTS_PER_DAY_RANGE = range(7)
28 28
29 29 BAN_REASON_AUTO = 'Auto'
30 30
31 31 IMAGE_THUMB_SIZE = (200, 150)
32 32
33 33 TITLE_MAX_LENGTH = 50
34 34
35 35 DEFAULT_MARKUP_TYPE = 'markdown'
36 36
37 37 NO_PARENT = -1
38 38 NO_IP = '0.0.0.0'
39 39 UNKNOWN_UA = ''
40 40 ALL_PAGES = -1
41 41 IMAGES_DIRECTORY = 'images/'
42 42 FILE_EXTENSION_DELIMITER = '.'
43 43
44 44 SETTING_MODERATE = "moderate"
45 45
46 46 REGEX_REPLY = re.compile('>>(\d+)')
47 47
48 48
49 49 class PostManager(models.Manager):
50 50
51 51 def create_post(self, title, text, image=None, thread=None,
52 52 ip=NO_IP, tags=None, user=None):
53 53 """
54 54 Create new post
55 55 """
56 56
57 57 posting_time = timezone.now()
58 58 if not thread:
59 59 thread = Thread.objects.create(bump_time=posting_time,
60 60 last_edit_time=posting_time)
61 61 else:
62 62 thread.bump()
63 63 thread.last_edit_time = posting_time
64 64 thread.save()
65 65
66 66 post = self.create(title=title,
67 67 text=text,
68 68 pub_time=posting_time,
69 69 thread_new=thread,
70 70 image=image,
71 71 poster_ip=ip,
72 72 poster_user_agent=UNKNOWN_UA, # TODO Get UA at
73 73 # last!
74 74 last_edit_time=posting_time,
75 75 user=user)
76 76
77 77 thread.replies.add(post)
78 78 if tags:
79 79 linked_tags = []
80 80 for tag in tags:
81 81 tag_linked_tags = tag.get_linked_tags()
82 82 if len(tag_linked_tags) > 0:
83 83 linked_tags.extend(tag_linked_tags)
84 84
85 85 tags.extend(linked_tags)
86 86 map(thread.add_tag, tags)
87 87
88 88 self._delete_old_threads()
89 89 self.connect_replies(post)
90 90
91 91 return post
92 92
93 93 def delete_post(self, post):
94 94 """
95 95 Delete post and update or delete its thread
96 96 """
97 97
98 98 thread = post.thread_new
99 99
100 100 if thread.get_opening_post() == self:
101 101 thread.replies.delete()
102 102
103 103 thread.delete()
104 104 else:
105 105 thread.last_edit_time = timezone.now()
106 106 thread.save()
107 107
108 108 post.delete()
109 109
110 110 def delete_posts_by_ip(self, ip):
111 111 """
112 112 Delete all posts of the author with same IP
113 113 """
114 114
115 115 posts = self.filter(poster_ip=ip)
116 116 map(self.delete_post, posts)
117 117
118 # TODO Move this method to thread manager
118 # TODO This method may not be needed any more, because django's paginator
119 # is used
119 120 def get_threads(self, tag=None, page=ALL_PAGES,
120 121 order_by='-bump_time', archived=False):
121 122 if tag:
122 123 threads = tag.threads
123 124
124 125 if not threads.exists():
125 126 raise Http404
126 127 else:
127 128 threads = Thread.objects.all()
128 129
129 130 threads = threads.filter(archived=archived).order_by(order_by)
130 131
131 132 if page != ALL_PAGES:
132 133 threads = Paginator(threads, settings.THREADS_PER_PAGE).page(
133 134 page).object_list
134 135
135 136 return threads
136 137
137 138 # TODO Move this method to thread manager
138 139 def _delete_old_threads(self):
139 140 """
140 141 Preserves maximum thread count. If there are too many threads,
141 142 archive the old ones.
142 143 """
143 144
144 145 threads = self.get_threads()
145 146 thread_count = threads.count()
146 147
147 148 if thread_count > settings.MAX_THREAD_COUNT:
148 149 num_threads_to_delete = thread_count - settings.MAX_THREAD_COUNT
149 150 old_threads = threads[thread_count - num_threads_to_delete:]
150 151
151 152 for thread in old_threads:
152 153 thread.archived = True
153 154 thread.last_edit_time = timezone.now()
154 155 thread.save()
155 156
156 157 def connect_replies(self, post):
157 158 """
158 159 Connect replies to a post to show them as a reflink map
159 160 """
160 161
161 162 for reply_number in re.finditer(REGEX_REPLY, post.text.raw):
162 163 post_id = reply_number.group(1)
163 164 ref_post = self.filter(id=post_id)
164 165 if ref_post.count() > 0:
165 166 referenced_post = ref_post[0]
166 167 referenced_post.referenced_posts.add(post)
167 168 referenced_post.last_edit_time = post.pub_time
168 169 referenced_post.save()
169 170
170 171 referenced_thread = referenced_post.thread_new
171 172 referenced_thread.last_edit_time = post.pub_time
172 173 referenced_thread.save()
173 174
174 175 def get_posts_per_day(self):
175 176 """
176 177 Get average count of posts per day for the last 7 days
177 178 """
178 179
179 180 today = datetime.now().date()
180 181 ppd = cache.get(CACHE_KEY_PPD + str(today))
181 182 if ppd:
182 183 return ppd
183 184
184 185 posts_per_days = []
185 186 for i in POSTS_PER_DAY_RANGE:
186 187 day_end = today - timedelta(i + 1)
187 188 day_start = today - timedelta(i + 2)
188 189
189 190 day_time_start = timezone.make_aware(datetime.combine(day_start,
190 191 dtime()), timezone.get_current_timezone())
191 192 day_time_end = timezone.make_aware(datetime.combine(day_end,
192 193 dtime()), timezone.get_current_timezone())
193 194
194 195 posts_per_days.append(float(self.filter(
195 196 pub_time__lte=day_time_end,
196 197 pub_time__gte=day_time_start).count()))
197 198
198 199 ppd = (sum(posts_per_day for posts_per_day in posts_per_days) /
199 200 len(posts_per_days))
200 201 cache.set(CACHE_KEY_PPD, ppd)
201 202 return ppd
202 203
203 204
204 205 class Post(models.Model):
205 206 """A post is a message."""
206 207
207 208 objects = PostManager()
208 209
209 210 class Meta:
210 211 app_label = APP_LABEL_BOARDS
211 212
212 213 # TODO Save original file name to some field
213 214 def _update_image_filename(self, filename):
214 215 """Get unique image filename"""
215 216
216 217 path = IMAGES_DIRECTORY
217 218 new_name = str(int(time.mktime(time.gmtime())))
218 219 new_name += str(int(random() * 1000))
219 220 new_name += FILE_EXTENSION_DELIMITER
220 221 new_name += filename.split(FILE_EXTENSION_DELIMITER)[-1:][0]
221 222
222 223 return os.path.join(path, new_name)
223 224
224 225 title = models.CharField(max_length=TITLE_MAX_LENGTH)
225 226 pub_time = models.DateTimeField()
226 227 text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE,
227 228 escape_html=False)
228 229
229 230 image_width = models.IntegerField(default=0)
230 231 image_height = models.IntegerField(default=0)
231 232
232 233 image_pre_width = models.IntegerField(default=0)
233 234 image_pre_height = models.IntegerField(default=0)
234 235
235 236 image = thumbs.ImageWithThumbsField(upload_to=_update_image_filename,
236 237 blank=True, sizes=(IMAGE_THUMB_SIZE,),
237 238 width_field='image_width',
238 239 height_field='image_height',
239 240 preview_width_field='image_pre_width',
240 241 preview_height_field='image_pre_height')
241 242 image_hash = models.CharField(max_length=36)
242 243
243 244 poster_ip = models.GenericIPAddressField()
244 245 poster_user_agent = models.TextField()
245 246
246 247 thread = models.ForeignKey('Post', null=True, default=None)
247 248 thread_new = models.ForeignKey('Thread', null=True, default=None)
248 249 last_edit_time = models.DateTimeField()
249 250 user = models.ForeignKey('User', null=True, default=None)
250 251
251 252 referenced_posts = models.ManyToManyField('Post', symmetrical=False,
252 253 null=True,
253 254 blank=True, related_name='rfp+')
254 255
255 256 def __unicode__(self):
256 257 return '#' + str(self.id) + ' ' + self.title + ' (' + \
257 258 self.text.raw[:50] + ')'
258 259
259 260 def get_title(self):
260 261 title = self.title
261 262 if len(title) == 0:
262 263 title = self.text.rendered
263 264
264 265 return title
265 266
266 267 def get_sorted_referenced_posts(self):
267 268 return self.referenced_posts.order_by('id')
268 269
269 270 def is_referenced(self):
270 271 return self.referenced_posts.all().exists()
271 272
272 273 def is_opening(self):
273 274 return self.thread_new.get_replies()[0] == self
274 275
275 276 def save(self, *args, **kwargs):
276 277 """
277 278 Save the model and compute the image hash
278 279 """
279 280
280 281 if not self.pk and self.image:
281 282 md5 = hashlib.md5()
282 283 for chunk in self.image.chunks():
283 284 md5.update(chunk)
284 285 self.image_hash = md5.hexdigest()
285 286 super(Post, self).save(*args, **kwargs)
286 287
287 288
288 289 class Thread(models.Model):
289 290
290 291 class Meta:
291 292 app_label = APP_LABEL_BOARDS
292 293
293 294 tags = models.ManyToManyField('Tag')
294 295 bump_time = models.DateTimeField()
295 296 last_edit_time = models.DateTimeField()
296 297 replies = models.ManyToManyField('Post', symmetrical=False, null=True,
297 298 blank=True, related_name='tre+')
298 299 archived = models.BooleanField(default=False)
299 300
300 301 def get_tags(self):
301 302 """
302 303 Get a sorted tag list
303 304 """
304 305
305 306 return self.tags.order_by('name')
306 307
307 308 def bump(self):
308 309 """
309 310 Bump (move to up) thread
310 311 """
311 312
312 313 if self.can_bump():
313 314 self.bump_time = timezone.now()
314 315
315 316 def get_reply_count(self):
316 317 return self.replies.count()
317 318
318 319 def get_images_count(self):
319 320 return self.replies.filter(image_width__gt=0).count()
320 321
321 322 def can_bump(self):
322 323 """
323 324 Check if the thread can be bumped by replying
324 325 """
325 326
326 327 if self.archived:
327 328 return False
328 329
329 330 post_count = self.get_reply_count()
330 331
331 332 return post_count < settings.MAX_POSTS_PER_THREAD
332 333
333 334 def delete_with_posts(self):
334 335 """
335 336 Completely delete thread and all its posts
336 337 """
337 338
338 339 if self.replies.count() > 0:
339 340 self.replies.all().delete()
340 341
341 342 self.delete()
342 343
343 344 def get_last_replies(self):
344 345 """
345 346 Get last replies, not including opening post
346 347 """
347 348
348 349 if settings.LAST_REPLIES_COUNT > 0:
349 350 reply_count = self.get_reply_count()
350 351
351 352 if reply_count > 0:
352 353 reply_count_to_show = min(settings.LAST_REPLIES_COUNT,
353 354 reply_count - 1)
354 355 last_replies = self.replies.all().order_by('pub_time')[
355 356 reply_count - reply_count_to_show:]
356 357
357 358 return last_replies
358 359
359 360 def get_skipped_replies_count(self):
360 361 last_replies = self.get_last_replies()
361 362 return self.get_reply_count() - len(last_replies) - 1
362 363
363 364 def get_replies(self):
364 365 """
365 366 Get sorted thread posts
366 367 """
367 368
368 369 return self.replies.all().order_by('pub_time')
369 370
370 371 def add_tag(self, tag):
371 372 """
372 373 Connect thread to a tag and tag to a thread
373 374 """
374 375
375 376 self.tags.add(tag)
376 377 tag.threads.add(self)
377 378
378 379 def get_opening_post(self):
379 380 """
380 381 Get first post of the thread
381 382 """
382 383
383 384 return self.get_replies()[0]
384 385
385 386 def __unicode__(self):
386 387 return str(self.get_replies()[0].id)
387 388
388 389 def get_pub_time(self):
389 390 """
390 391 Thread does not have its own pub time, so we need to get it from
391 392 the opening post
392 393 """
393 394
394 395 return self.get_opening_post().pub_time
@@ -1,241 +1,243 b''
1 1 __author__ = 'neko259'
2 2
3 3 import hashlib
4 4
5 5 from django.core import serializers
6 6 from django.core.urlresolvers import reverse
7 7 from django.http import HttpResponseRedirect
8 8 from django.http.response import HttpResponse
9 9 from django.template import RequestContext
10 10 from django.shortcuts import render, redirect, get_object_or_404
11 11 from django.utils import timezone
12 12 from django.db import transaction
13 13 from django.views.decorators.cache import cache_page
14 14 from django.views.i18n import javascript_catalog
15 15
16 16 import boards
17 17 from boards.forms import PlainErrorList
18 18 from boards.models import Post, Tag, Ban, User
19 19 from boards.models.post import SETTING_MODERATE
20 20 from boards.models.user import RANK_USER
21 21 from boards import authors
22 22 import neboard
23 23
24 24
25 25 BAN_REASON_SPAM = 'Autoban: spam bot'
26 26
27 27 DEFAULT_PAGE = 1
28 28
29 29
30 30 def all_tags(request):
31 31 """All tags list"""
32 32
33 33 context = _init_default_context(request)
34 34 context['all_tags'] = Tag.objects.get_not_empty_tags()
35 35
36 36 return render(request, 'boards/tags.html', context)
37 37
38 38
39 # TODO Maybe this jumper is not needed any more? Only as a hack to find some
40 # post without knowing its thread
39 41 def jump_to_post(request, post_id):
40 42 """Determine thread in which the requested post is and open it's page"""
41 43
42 44 post = get_object_or_404(Post, id=post_id)
43 45
44 46 if not post.thread:
45 47 return redirect('thread', post_id=post.id)
46 48 else:
47 49 return redirect(reverse('thread', kwargs={'post_id': post.thread.id})
48 50 + '#' + str(post.id))
49 51
50 52
51 53 def authors(request):
52 54 """Show authors list"""
53 55
54 56 context = _init_default_context(request)
55 57 context['authors'] = boards.authors.authors
56 58
57 59 return render(request, 'boards/authors.html', context)
58 60
59 61
60 62 @transaction.atomic
61 63 def delete(request, post_id):
62 64 """Delete post"""
63 65
64 66 user = _get_user(request)
65 67 post = get_object_or_404(Post, id=post_id)
66 68
67 69 if user.is_moderator():
68 70 # TODO Show confirmation page before deletion
69 71 Post.objects.delete_post(post)
70 72
71 73 if not post.thread:
72 74 return _redirect_to_next(request)
73 75 else:
74 76 return redirect('thread', post_id=post.thread.id)
75 77
76 78
77 79 @transaction.atomic
78 80 def ban(request, post_id):
79 81 """Ban user"""
80 82
81 83 user = _get_user(request)
82 84 post = get_object_or_404(Post, id=post_id)
83 85
84 86 if user.is_moderator():
85 87 # TODO Show confirmation page before ban
86 88 ban, created = Ban.objects.get_or_create(ip=post.poster_ip)
87 89 if created:
88 90 ban.reason = 'Banned for post ' + str(post_id)
89 91 ban.save()
90 92
91 93 return _redirect_to_next(request)
92 94
93 95
94 96 def page_404(request):
95 97 """Show page 404 (not found error)"""
96 98
97 99 context = _init_default_context(request)
98 100 return render(request, 'boards/404.html', context)
99 101
100 102
101 103 @transaction.atomic
102 104 def tag_subscribe(request, tag_name):
103 105 """Add tag to favorites"""
104 106
105 107 user = _get_user(request)
106 108 tag = get_object_or_404(Tag, name=tag_name)
107 109
108 110 if not tag in user.fav_tags.all():
109 111 user.add_tag(tag)
110 112
111 113 return _redirect_to_next(request)
112 114
113 115
114 116 @transaction.atomic
115 117 def tag_unsubscribe(request, tag_name):
116 118 """Remove tag from favorites"""
117 119
118 120 user = _get_user(request)
119 121 tag = get_object_or_404(Tag, name=tag_name)
120 122
121 123 if tag in user.fav_tags.all():
122 124 user.remove_tag(tag)
123 125
124 126 return _redirect_to_next(request)
125 127
126 128
127 129 def static_page(request, name):
128 130 """Show a static page that needs only tags list and a CSS"""
129 131
130 132 context = _init_default_context(request)
131 133 return render(request, 'boards/staticpages/' + name + '.html', context)
132 134
133 135
134 136 # TODO This has to be moved under the api module
135 137 def api_get_post(request, post_id):
136 138 """
137 139 Get the JSON of a post. This can be
138 140 used as and API for external clients.
139 141 """
140 142
141 143 post = get_object_or_404(Post, id=post_id)
142 144
143 145 json = serializers.serialize("json", [post], fields=(
144 146 "pub_time", "_text_rendered", "title", "text", "image",
145 147 "image_width", "image_height", "replies", "tags"
146 148 ))
147 149
148 150 return HttpResponse(content=json)
149 151
150 152
151 153 @cache_page(86400)
152 154 def cached_js_catalog(request, domain='djangojs', packages=None):
153 155 return javascript_catalog(request, domain, packages)
154 156
155 157
156 158 # TODO This method is deprecated and should be removed after switching to
157 159 # class-based view
158 160 def _get_theme(request, user=None):
159 161 """Get user's CSS theme"""
160 162
161 163 if not user:
162 164 user = _get_user(request)
163 165 theme = user.get_setting('theme')
164 166 if not theme:
165 167 theme = neboard.settings.DEFAULT_THEME
166 168
167 169 return theme
168 170
169 171
170 172 # TODO This method is deprecated and should be removed after switching to
171 173 # class-based view
172 174 def _init_default_context(request):
173 175 """Create context with default values that are used in most views"""
174 176
175 177 context = RequestContext(request)
176 178
177 179 user = _get_user(request)
178 180 context['user'] = user
179 181 context['tags'] = user.get_sorted_fav_tags()
180 182 context['posts_per_day'] = float(Post.objects.get_posts_per_day())
181 183
182 184 theme = _get_theme(request, user)
183 185 context['theme'] = theme
184 186 context['theme_css'] = 'css/' + theme + '/base_page.css'
185 187
186 188 # This shows the moderator panel
187 189 moderate = user.get_setting(SETTING_MODERATE)
188 190 if moderate == 'True':
189 191 context['moderator'] = user.is_moderator()
190 192 else:
191 193 context['moderator'] = False
192 194
193 195 return context
194 196
195 197
196 198 # TODO This method is deprecated and should be removed after switching to
197 199 # class-based view
198 200 def _get_user(request):
199 201 """
200 202 Get current user from the session. If the user does not exist, create
201 203 a new one.
202 204 """
203 205
204 206 session = request.session
205 207 if not 'user_id' in session:
206 208 request.session.save()
207 209
208 210 md5 = hashlib.md5()
209 211 md5.update(session.session_key)
210 212 new_id = md5.hexdigest()
211 213
212 214 while User.objects.filter(user_id=new_id).exists():
213 215 md5.update(str(timezone.now()))
214 216 new_id = md5.hexdigest()
215 217
216 218 time_now = timezone.now()
217 219 user = User.objects.create(user_id=new_id, rank=RANK_USER,
218 220 registration_time=time_now)
219 221
220 222 # TODO This is just a test. This method should be removed
221 223 # _delete_old_users()
222 224
223 225 session['user_id'] = user.id
224 226 else:
225 227 user = User.objects.get(id=session['user_id'])
226 228
227 229 return user
228 230
229 231
230 232 def _redirect_to_next(request):
231 233 """
232 234 If a 'next' parameter was specified, redirect to the next page. This is
233 235 used when the user is required to return to some page after the current
234 236 view has finished its work.
235 237 """
236 238
237 239 if 'next' in request.GET:
238 240 next_page = request.GET['next']
239 241 return HttpResponseRedirect(next_page)
240 242 else:
241 243 return redirect('index')
@@ -1,125 +1,125 b''
1 1 import string
2 2
3 3 from django.core.paginator import Paginator
4 4 from django.core.urlresolvers import reverse
5 5 from django.db import transaction
6 6 from django.shortcuts import render, redirect
7 7
8 8 from boards import utils
9 9 from boards.forms import ThreadForm, PlainErrorList
10 10 from boards.models import Post, Thread, Ban, Tag
11 11 from boards.views.banned import BannedView
12 12 from boards.views.base import BaseBoardView, PARAMETER_FORM
13 13 from boards.views.posting_mixin import PostMixin
14 14 import neboard
15 15
16 16 PARAMETER_CURRENT_PAGE = 'current_page'
17 17
18 18 PARAMETER_PAGINATOR = 'paginator'
19 19
20 20 PARAMETER_THREADS = 'threads'
21 21
22 22 TEMPLATE = 'boards/posting_general.html'
23 23 DEFAULT_PAGE = 1
24 24
25 25
26 26 class AllThreadsView(PostMixin, BaseBoardView):
27 27
28 28 def get(self, request, page=DEFAULT_PAGE, form=None):
29 29 context = self.get_context_data(request=request)
30 30
31 31 if not form:
32 32 form = ThreadForm(error_class=PlainErrorList)
33 33
34 34 paginator = Paginator(self.get_threads(),
35 35 neboard.settings.THREADS_PER_PAGE)
36 36
37 37 threads = paginator.page(page).object_list
38 38
39 39 context[PARAMETER_THREADS] = threads
40 40 context[PARAMETER_FORM] = form
41 41
42 42 self._get_page_context(paginator, context, page)
43 43
44 44 return render(request, TEMPLATE, context)
45 45
46 46 def post(self, request, page=DEFAULT_PAGE):
47 47 context = self.get_context_data(request=request)
48 48
49 49 form = ThreadForm(request.POST, request.FILES,
50 50 error_class=PlainErrorList)
51 51 form.session = request.session
52 52
53 53 if form.is_valid():
54 54 return self._new_post(request, form)
55 55 if form.need_to_ban:
56 56 # Ban user because he is suspected to be a bot
57 57 self._ban_current_user(request)
58 58
59 59 return self.get(request, page, form)
60 60
61 61 @staticmethod
62 62 def _get_page_context(paginator, context, page):
63 63 """
64 64 Get pagination context variables
65 65 """
66 66
67 67 context[PARAMETER_PAGINATOR] = paginator
68 68 context[PARAMETER_CURRENT_PAGE] = paginator.page(int(page))
69 69
70 70 # TODO This method should be refactored
71 71 @transaction.atomic
72 72 def _new_post(self, request, form, opening_post=None, html_response=True):
73 73 """
74 74 Add a new thread opening post.
75 75 """
76 76
77 77 ip = utils.get_client_ip(request)
78 78 is_banned = Ban.objects.filter(ip=ip).exists()
79 79
80 80 if is_banned:
81 81 if html_response:
82 82 return redirect(BannedView().as_view())
83 83 else:
84 84 return
85 85
86 86 data = form.cleaned_data
87 87
88 88 title = data['title']
89 89 text = data['text']
90 90
91 91 text = self._remove_invalid_links(text)
92 92
93 93 if 'image' in data.keys():
94 94 image = data['image']
95 95 else:
96 96 image = None
97 97
98 98 tags = []
99 99
100 100 tag_strings = data['tags']
101 101
102 102 if tag_strings:
103 103 tag_strings = tag_strings.split(' ')
104 104 for tag_name in tag_strings:
105 105 tag_name = string.lower(tag_name.strip())
106 106 if len(tag_name) > 0:
107 107 tag, created = Tag.objects.get_or_create(name=tag_name)
108 108 tags.append(tag)
109 109
110 110 post = Post.objects.create_post(title=title, text=text, ip=ip,
111 111 image=image, tags=tags,
112 112 user=self._get_user(request))
113 113
114 114 thread_to_show = (opening_post.id if opening_post else post.id)
115 115
116 116 if html_response:
117 117 if opening_post:
118 118 return redirect(
119 119 reverse('thread', kwargs={'post_id': thread_to_show}) +
120 120 '#' + str(post.id))
121 121 else:
122 122 return redirect('thread', post_id=thread_to_show)
123 123
124 124 def get_threads(self):
125 return Thread.objects.filter(archived=False)
125 return Thread.objects.filter(archived=False).order_by('-bump_time')
@@ -1,28 +1,28 b''
1 1 from django.shortcuts import get_object_or_404
2 2 from boards.models import Tag, Post
3 3 from boards.views.all_threads import AllThreadsView, DEFAULT_PAGE
4 4
5 5 __author__ = 'neko259'
6 6
7 7
8 8 class TagView(AllThreadsView):
9 9
10 10 tag_name = None
11 11
12 12 def get_threads(self):
13 13 tag = get_object_or_404(Tag, name=self.tag_name)
14 14
15 return tag.threads.filter(archived=False)
15 return tag.threads.filter(archived=False).order_by('-bump_time')
16 16
17 17 def get_context_data(self, **kwargs):
18 18 context = super(TagView, self).get_context_data(**kwargs)
19 19
20 20 tag = get_object_or_404(Tag, name=self.tag_name)
21 21 context['tag'] = tag
22 22
23 23 return context
24 24
25 25 def get(self, request, tag_name, page=DEFAULT_PAGE):
26 26 self.tag_name = tag_name
27 27
28 return super(TagView, self).get(request, page) No newline at end of file
28 return super(TagView, self).get(request, page)
General Comments 0
You need to be logged in to leave comments. Login now