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