##// END OF EJS Templates
Added ban reasons. Added write-only bans.
neko259 -
r340:1472a0a6 default
parent child Browse files
Show More
@@ -1,17 +1,19 b''
1 from django.shortcuts import redirect
1 from django.shortcuts import redirect
2 from boards import views, utils
2 from boards import views, utils
3 from boards.models import Ban
3 from boards.models import Ban
4
4
5
5
6 class BanMiddleware:
6 class BanMiddleware:
7 """This is run before showing the thread. Banned users don't need to see
7 """This is run before showing the thread. Banned users don't need to see
8 anything"""
8 anything"""
9
9
10 def process_view(self, request, view_func, view_args, view_kwargs):
10 def process_view(self, request, view_func, view_args, view_kwargs):
11
11
12 if view_func != views.you_are_banned:
12 if view_func != views.you_are_banned:
13 ip = utils.get_client_ip(request)
13 ip = utils.get_client_ip(request)
14 is_banned = Ban.objects.filter(ip=ip).exists()
14 bans = Ban.objects.filter(ip=ip)
15
15
16 if is_banned:
16 if bans.exists():
17 return redirect(views.you_are_banned) No newline at end of file
17 ban = bans[0]
18 if not ban.can_read:
19 return redirect(views.you_are_banned) No newline at end of file
@@ -1,421 +1,428 b''
1 import os
1 import os
2 from random import random
2 from random import random
3 import time
3 import time
4 import math
4 import math
5 from django.core.cache import cache
5 from django.core.cache import cache
6
6
7 from django.db import models
7 from django.db import models
8 from django.db.models import Count
8 from django.db.models import Count
9 from django.http import Http404
9 from django.http import Http404
10 from django.utils import timezone
10 from django.utils import timezone
11 from markupfield.fields import MarkupField
11 from markupfield.fields import MarkupField
12 from boards import settings as board_settings
12 from boards import settings as board_settings
13
13
14 from neboard import settings
14 from neboard import settings
15 import thumbs
15 import thumbs
16
16
17 import re
17 import re
18
18
19 BAN_REASON_MAX_LENGTH = 200
20
21 BAN_REASON_AUTO = 'Auto'
22
19 IMAGE_THUMB_SIZE = (200, 150)
23 IMAGE_THUMB_SIZE = (200, 150)
20
24
21 TITLE_MAX_LENGTH = 50
25 TITLE_MAX_LENGTH = 50
22
26
23 DEFAULT_MARKUP_TYPE = 'markdown'
27 DEFAULT_MARKUP_TYPE = 'markdown'
24
28
25 NO_PARENT = -1
29 NO_PARENT = -1
26 NO_IP = '0.0.0.0'
30 NO_IP = '0.0.0.0'
27 UNKNOWN_UA = ''
31 UNKNOWN_UA = ''
28 ALL_PAGES = -1
32 ALL_PAGES = -1
29 OPENING_POST_POPULARITY_WEIGHT = 2
33 OPENING_POST_POPULARITY_WEIGHT = 2
30 IMAGES_DIRECTORY = 'images/'
34 IMAGES_DIRECTORY = 'images/'
31 FILE_EXTENSION_DELIMITER = '.'
35 FILE_EXTENSION_DELIMITER = '.'
32
36
33 RANK_ADMIN = 0
37 RANK_ADMIN = 0
34 RANK_MODERATOR = 10
38 RANK_MODERATOR = 10
35 RANK_USER = 100
39 RANK_USER = 100
36
40
37 SETTING_MODERATE = "moderate"
41 SETTING_MODERATE = "moderate"
38
42
39 REGEX_REPLY = re.compile('>>(\d+)')
43 REGEX_REPLY = re.compile('>>(\d+)')
40
44
41
45
42 class PostManager(models.Manager):
46 class PostManager(models.Manager):
43
47
44 def create_post(self, title, text, image=None, thread=None,
48 def create_post(self, title, text, image=None, thread=None,
45 ip=NO_IP, tags=None, user=None):
49 ip=NO_IP, tags=None, user=None):
46 post = self.create(title=title,
50 post = self.create(title=title,
47 text=text,
51 text=text,
48 pub_time=timezone.now(),
52 pub_time=timezone.now(),
49 thread=thread,
53 thread=thread,
50 image=image,
54 image=image,
51 poster_ip=ip,
55 poster_ip=ip,
52 poster_user_agent=UNKNOWN_UA,
56 poster_user_agent=UNKNOWN_UA,
53 last_edit_time=timezone.now(),
57 last_edit_time=timezone.now(),
54 bump_time=timezone.now(),
58 bump_time=timezone.now(),
55 user=user)
59 user=user)
56
60
57 if tags:
61 if tags:
58 map(post.tags.add, tags)
62 map(post.tags.add, tags)
59 for tag in tags:
63 for tag in tags:
60 tag.threads.add(post)
64 tag.threads.add(post)
61
65
62 if thread:
66 if thread:
63 thread.replies.add(post)
67 thread.replies.add(post)
64 thread.bump()
68 thread.bump()
65 thread.last_edit_time = timezone.now()
69 thread.last_edit_time = timezone.now()
66 thread.save()
70 thread.save()
67
71
68 #cache_key = thread.get_cache_key()
72 #cache_key = thread.get_cache_key()
69 #cache.delete(cache_key)
73 #cache.delete(cache_key)
70
74
71 else:
75 else:
72 self._delete_old_threads()
76 self._delete_old_threads()
73
77
74 self.connect_replies(post)
78 self.connect_replies(post)
75
79
76 return post
80 return post
77
81
78 def delete_post(self, post):
82 def delete_post(self, post):
79 if post.replies.count() > 0:
83 if post.replies.count() > 0:
80 map(self.delete_post, post.replies.all())
84 map(self.delete_post, post.replies.all())
81
85
82 # Update thread's last edit time (used as cache key)
86 # Update thread's last edit time (used as cache key)
83 thread = post.thread
87 thread = post.thread
84 if thread:
88 if thread:
85 thread.last_edit_time = timezone.now()
89 thread.last_edit_time = timezone.now()
86 thread.save()
90 thread.save()
87
91
88 #cache_key = thread.get_cache_key()
92 #cache_key = thread.get_cache_key()
89 #cache.delete(cache_key)
93 #cache.delete(cache_key)
90
94
91 post.delete()
95 post.delete()
92
96
93 def delete_posts_by_ip(self, ip):
97 def delete_posts_by_ip(self, ip):
94 posts = self.filter(poster_ip=ip)
98 posts = self.filter(poster_ip=ip)
95 map(self.delete_post, posts)
99 map(self.delete_post, posts)
96
100
97 def get_threads(self, tag=None, page=ALL_PAGES,
101 def get_threads(self, tag=None, page=ALL_PAGES,
98 order_by='-bump_time'):
102 order_by='-bump_time'):
99 if tag:
103 if tag:
100 threads = tag.threads
104 threads = tag.threads
101
105
102 if threads.count() == 0:
106 if threads.count() == 0:
103 raise Http404
107 raise Http404
104 else:
108 else:
105 threads = self.filter(thread=None)
109 threads = self.filter(thread=None)
106
110
107 threads = threads.order_by(order_by)
111 threads = threads.order_by(order_by)
108
112
109 if page != ALL_PAGES:
113 if page != ALL_PAGES:
110 thread_count = threads.count()
114 thread_count = threads.count()
111
115
112 if page < self._get_page_count(thread_count):
116 if page < self._get_page_count(thread_count):
113 start_thread = page * settings.THREADS_PER_PAGE
117 start_thread = page * settings.THREADS_PER_PAGE
114 end_thread = min(start_thread + settings.THREADS_PER_PAGE,
118 end_thread = min(start_thread + settings.THREADS_PER_PAGE,
115 thread_count)
119 thread_count)
116 threads = threads[start_thread:end_thread]
120 threads = threads[start_thread:end_thread]
117
121
118 return threads
122 return threads
119
123
120 def get_thread(self, opening_post_id):
124 def get_thread(self, opening_post_id):
121 try:
125 try:
122 opening_post = self.get(id=opening_post_id, thread=None)
126 opening_post = self.get(id=opening_post_id, thread=None)
123 except Post.DoesNotExist:
127 except Post.DoesNotExist:
124 raise Http404
128 raise Http404
125
129
126 #cache_key = opening_post.get_cache_key()
130 #cache_key = opening_post.get_cache_key()
127 #thread = cache.get(cache_key)
131 #thread = cache.get(cache_key)
128 #if thread:
132 #if thread:
129 # return thread
133 # return thread
130
134
131 if opening_post.replies:
135 if opening_post.replies:
132 thread = [opening_post]
136 thread = [opening_post]
133 thread.extend(opening_post.replies.all().order_by('pub_time'))
137 thread.extend(opening_post.replies.all().order_by('pub_time'))
134
138
135 #cache.set(cache_key, thread, board_settings.CACHE_TIMEOUT)
139 #cache.set(cache_key, thread, board_settings.CACHE_TIMEOUT)
136
140
137 return thread
141 return thread
138
142
139 def exists(self, post_id):
143 def exists(self, post_id):
140 posts = self.filter(id=post_id)
144 posts = self.filter(id=post_id)
141
145
142 return posts.count() > 0
146 return posts.count() > 0
143
147
144 def get_thread_page_count(self, tag=None):
148 def get_thread_page_count(self, tag=None):
145 if tag:
149 if tag:
146 threads = self.filter(thread=None, tags=tag)
150 threads = self.filter(thread=None, tags=tag)
147 else:
151 else:
148 threads = self.filter(thread=None)
152 threads = self.filter(thread=None)
149
153
150 return self._get_page_count(threads.count())
154 return self._get_page_count(threads.count())
151
155
152 def _delete_old_threads(self):
156 def _delete_old_threads(self):
153 """
157 """
154 Preserves maximum thread count. If there are too many threads,
158 Preserves maximum thread count. If there are too many threads,
155 delete the old ones.
159 delete the old ones.
156 """
160 """
157
161
158 # TODO Move old threads to the archive instead of deleting them.
162 # TODO Move old threads to the archive instead of deleting them.
159 # Maybe make some 'old' field in the model to indicate the thread
163 # Maybe make some 'old' field in the model to indicate the thread
160 # must not be shown and be able for replying.
164 # must not be shown and be able for replying.
161
165
162 threads = self.get_threads()
166 threads = self.get_threads()
163 thread_count = threads.count()
167 thread_count = threads.count()
164
168
165 if thread_count > settings.MAX_THREAD_COUNT:
169 if thread_count > settings.MAX_THREAD_COUNT:
166 num_threads_to_delete = thread_count - settings.MAX_THREAD_COUNT
170 num_threads_to_delete = thread_count - settings.MAX_THREAD_COUNT
167 old_threads = threads[thread_count - num_threads_to_delete:]
171 old_threads = threads[thread_count - num_threads_to_delete:]
168
172
169 map(self.delete_post, old_threads)
173 map(self.delete_post, old_threads)
170
174
171 def connect_replies(self, post):
175 def connect_replies(self, post):
172 """Connect replies to a post to show them as a refmap"""
176 """Connect replies to a post to show them as a refmap"""
173
177
174 for reply_number in re.finditer(REGEX_REPLY, post.text.raw):
178 for reply_number in re.finditer(REGEX_REPLY, post.text.raw):
175 id = reply_number.group(1)
179 id = reply_number.group(1)
176 ref_post = self.filter(id=id)
180 ref_post = self.filter(id=id)
177 if ref_post.count() > 0:
181 if ref_post.count() > 0:
178 ref_post[0].referenced_posts.add(post)
182 ref_post[0].referenced_posts.add(post)
179
183
180 def _get_page_count(self, thread_count):
184 def _get_page_count(self, thread_count):
181 return int(math.ceil(thread_count / float(settings.THREADS_PER_PAGE)))
185 return int(math.ceil(thread_count / float(settings.THREADS_PER_PAGE)))
182
186
183
187
184 class TagManager(models.Manager):
188 class TagManager(models.Manager):
185
189
186 def get_not_empty_tags(self):
190 def get_not_empty_tags(self):
187 tags = self.annotate(Count('threads')) \
191 tags = self.annotate(Count('threads')) \
188 .filter(threads__count__gt=0).order_by('name')
192 .filter(threads__count__gt=0).order_by('name')
189
193
190 return tags
194 return tags
191
195
192
196
193 class Tag(models.Model):
197 class Tag(models.Model):
194 """
198 """
195 A tag is a text node assigned to the post. The tag serves as a board
199 A tag is a text node assigned to the post. The tag serves as a board
196 section. There can be multiple tags for each message
200 section. There can be multiple tags for each message
197 """
201 """
198
202
199 objects = TagManager()
203 objects = TagManager()
200
204
201 name = models.CharField(max_length=100)
205 name = models.CharField(max_length=100)
202 threads = models.ManyToManyField('Post', null=True,
206 threads = models.ManyToManyField('Post', null=True,
203 blank=True, related_name='tag+')
207 blank=True, related_name='tag+')
204 linked = models.ForeignKey('Tag', null=True, blank=True)
208 linked = models.ForeignKey('Tag', null=True, blank=True)
205
209
206 def __unicode__(self):
210 def __unicode__(self):
207 return self.name
211 return self.name
208
212
209 def is_empty(self):
213 def is_empty(self):
210 return self.get_post_count() == 0
214 return self.get_post_count() == 0
211
215
212 def get_post_count(self):
216 def get_post_count(self):
213 return self.threads.count()
217 return self.threads.count()
214
218
215 def get_popularity(self):
219 def get_popularity(self):
216 posts_with_tag = Post.objects.get_threads(tag=self)
220 posts_with_tag = Post.objects.get_threads(tag=self)
217 reply_count = 0
221 reply_count = 0
218 for post in posts_with_tag:
222 for post in posts_with_tag:
219 reply_count += post.get_reply_count()
223 reply_count += post.get_reply_count()
220 reply_count += OPENING_POST_POPULARITY_WEIGHT
224 reply_count += OPENING_POST_POPULARITY_WEIGHT
221
225
222 return reply_count
226 return reply_count
223
227
224 def get_linked_tags(self):
228 def get_linked_tags(self):
225 tag_list = []
229 tag_list = []
226 self.get_linked_tags_list(tag_list)
230 self.get_linked_tags_list(tag_list)
227
231
228 return tag_list
232 return tag_list
229
233
230 def get_linked_tags_list(self, tag_list=[]):
234 def get_linked_tags_list(self, tag_list=[]):
231 """
235 """
232 Returns the list of tags linked to current. The list can be got
236 Returns the list of tags linked to current. The list can be got
233 through returned value or tag_list parameter
237 through returned value or tag_list parameter
234 """
238 """
235
239
236 linked_tag = self.linked
240 linked_tag = self.linked
237
241
238 if linked_tag and not (linked_tag in tag_list):
242 if linked_tag and not (linked_tag in tag_list):
239 tag_list.append(linked_tag)
243 tag_list.append(linked_tag)
240
244
241 linked_tag.get_linked_tags_list(tag_list)
245 linked_tag.get_linked_tags_list(tag_list)
242
246
243
247
244 class Post(models.Model):
248 class Post(models.Model):
245 """A post is a message."""
249 """A post is a message."""
246
250
247 objects = PostManager()
251 objects = PostManager()
248
252
249 def _update_image_filename(self, filename):
253 def _update_image_filename(self, filename):
250 """Get unique image filename"""
254 """Get unique image filename"""
251
255
252 path = IMAGES_DIRECTORY
256 path = IMAGES_DIRECTORY
253 new_name = str(int(time.mktime(time.gmtime())))
257 new_name = str(int(time.mktime(time.gmtime())))
254 new_name += str(int(random() * 1000))
258 new_name += str(int(random() * 1000))
255 new_name += FILE_EXTENSION_DELIMITER
259 new_name += FILE_EXTENSION_DELIMITER
256 new_name += filename.split(FILE_EXTENSION_DELIMITER)[-1:][0]
260 new_name += filename.split(FILE_EXTENSION_DELIMITER)[-1:][0]
257
261
258 return os.path.join(path, new_name)
262 return os.path.join(path, new_name)
259
263
260 title = models.CharField(max_length=TITLE_MAX_LENGTH)
264 title = models.CharField(max_length=TITLE_MAX_LENGTH)
261 pub_time = models.DateTimeField()
265 pub_time = models.DateTimeField()
262 text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE,
266 text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE,
263 escape_html=False)
267 escape_html=False)
264
268
265 image_width = models.IntegerField(default=0)
269 image_width = models.IntegerField(default=0)
266 image_height = models.IntegerField(default=0)
270 image_height = models.IntegerField(default=0)
267
271
268 image = thumbs.ImageWithThumbsField(upload_to=_update_image_filename,
272 image = thumbs.ImageWithThumbsField(upload_to=_update_image_filename,
269 blank=True, sizes=(IMAGE_THUMB_SIZE,),
273 blank=True, sizes=(IMAGE_THUMB_SIZE,),
270 width_field='image_width',
274 width_field='image_width',
271 height_field='image_height')
275 height_field='image_height')
272
276
273 poster_ip = models.GenericIPAddressField()
277 poster_ip = models.GenericIPAddressField()
274 poster_user_agent = models.TextField()
278 poster_user_agent = models.TextField()
275
279
276 thread = models.ForeignKey('Post', null=True, default=None)
280 thread = models.ForeignKey('Post', null=True, default=None)
277 tags = models.ManyToManyField(Tag)
281 tags = models.ManyToManyField(Tag)
278 last_edit_time = models.DateTimeField()
282 last_edit_time = models.DateTimeField()
279 bump_time = models.DateTimeField()
283 bump_time = models.DateTimeField()
280 user = models.ForeignKey('User', null=True, default=None)
284 user = models.ForeignKey('User', null=True, default=None)
281
285
282 replies = models.ManyToManyField('Post', symmetrical=False, null=True,
286 replies = models.ManyToManyField('Post', symmetrical=False, null=True,
283 blank=True, related_name='re+')
287 blank=True, related_name='re+')
284 referenced_posts = models.ManyToManyField('Post', symmetrical=False,
288 referenced_posts = models.ManyToManyField('Post', symmetrical=False,
285 null=True,
289 null=True,
286 blank=True, related_name='rfp+')
290 blank=True, related_name='rfp+')
287
291
288 def __unicode__(self):
292 def __unicode__(self):
289 return '#' + str(self.id) + ' ' + self.title + ' (' + \
293 return '#' + str(self.id) + ' ' + self.title + ' (' + \
290 self.text.raw[:50] + ')'
294 self.text.raw[:50] + ')'
291
295
292 def get_title(self):
296 def get_title(self):
293 title = self.title
297 title = self.title
294 if len(title) == 0:
298 if len(title) == 0:
295 title = self.text.raw[:20]
299 title = self.text.raw[:20]
296
300
297 return title
301 return title
298
302
299 def get_reply_count(self):
303 def get_reply_count(self):
300 return self.replies.count()
304 return self.replies.count()
301
305
302 def get_images_count(self):
306 def get_images_count(self):
303 images_count = 1 if self.image else 0
307 images_count = 1 if self.image else 0
304 images_count += self.replies.filter(image_width__gt=0).count()
308 images_count += self.replies.filter(image_width__gt=0).count()
305
309
306 return images_count
310 return images_count
307
311
308 def can_bump(self):
312 def can_bump(self):
309 """Check if the thread can be bumped by replying"""
313 """Check if the thread can be bumped by replying"""
310
314
311 post_count = self.get_reply_count() + 1
315 post_count = self.get_reply_count() + 1
312
316
313 return post_count <= settings.MAX_POSTS_PER_THREAD
317 return post_count <= settings.MAX_POSTS_PER_THREAD
314
318
315 def bump(self):
319 def bump(self):
316 """Bump (move to up) thread"""
320 """Bump (move to up) thread"""
317
321
318 if self.can_bump():
322 if self.can_bump():
319 self.bump_time = timezone.now()
323 self.bump_time = timezone.now()
320
324
321 def get_last_replies(self):
325 def get_last_replies(self):
322 if settings.LAST_REPLIES_COUNT > 0:
326 if settings.LAST_REPLIES_COUNT > 0:
323 reply_count = self.get_reply_count()
327 reply_count = self.get_reply_count()
324
328
325 if reply_count > 0:
329 if reply_count > 0:
326 reply_count_to_show = min(settings.LAST_REPLIES_COUNT,
330 reply_count_to_show = min(settings.LAST_REPLIES_COUNT,
327 reply_count)
331 reply_count)
328 last_replies = self.replies.all().order_by('pub_time')[
332 last_replies = self.replies.all().order_by('pub_time')[
329 reply_count - reply_count_to_show:]
333 reply_count - reply_count_to_show:]
330
334
331 return last_replies
335 return last_replies
332
336
333 def get_tags(self):
337 def get_tags(self):
334 """Get a sorted tag list"""
338 """Get a sorted tag list"""
335
339
336 return self.tags.order_by('name')
340 return self.tags.order_by('name')
337
341
338 def get_cache_key(self):
342 def get_cache_key(self):
339 return str(self.id) + str(self.last_edit_time.microsecond)
343 return str(self.id) + str(self.last_edit_time.microsecond)
340
344
341
345
342 class User(models.Model):
346 class User(models.Model):
343
347
344 user_id = models.CharField(max_length=50)
348 user_id = models.CharField(max_length=50)
345 rank = models.IntegerField()
349 rank = models.IntegerField()
346
350
347 registration_time = models.DateTimeField()
351 registration_time = models.DateTimeField()
348
352
349 fav_tags = models.ManyToManyField(Tag, null=True, blank=True)
353 fav_tags = models.ManyToManyField(Tag, null=True, blank=True)
350 fav_threads = models.ManyToManyField(Post, related_name='+', null=True,
354 fav_threads = models.ManyToManyField(Post, related_name='+', null=True,
351 blank=True)
355 blank=True)
352
356
353 def save_setting(self, name, value):
357 def save_setting(self, name, value):
354 setting, created = Setting.objects.get_or_create(name=name, user=self)
358 setting, created = Setting.objects.get_or_create(name=name, user=self)
355 setting.value = str(value)
359 setting.value = str(value)
356 setting.save()
360 setting.save()
357
361
358 return setting
362 return setting
359
363
360 def get_setting(self, name):
364 def get_setting(self, name):
361 if Setting.objects.filter(name=name, user=self).exists():
365 if Setting.objects.filter(name=name, user=self).exists():
362 setting = Setting.objects.get(name=name, user=self)
366 setting = Setting.objects.get(name=name, user=self)
363 setting_value = setting.value
367 setting_value = setting.value
364 else:
368 else:
365 setting_value = None
369 setting_value = None
366
370
367 return setting_value
371 return setting_value
368
372
369 def is_moderator(self):
373 def is_moderator(self):
370 return RANK_MODERATOR >= self.rank
374 return RANK_MODERATOR >= self.rank
371
375
372 def get_sorted_fav_tags(self):
376 def get_sorted_fav_tags(self):
373 cache_key = self._get_tag_cache_key()
377 cache_key = self._get_tag_cache_key()
374 fav_tags = cache.get(cache_key)
378 fav_tags = cache.get(cache_key)
375 if fav_tags:
379 if fav_tags:
376 return fav_tags
380 return fav_tags
377
381
378 tags = self.fav_tags.annotate(Count('threads'))\
382 tags = self.fav_tags.annotate(Count('threads'))\
379 .filter(threads__count__gt=0).order_by('name')
383 .filter(threads__count__gt=0).order_by('name')
380
384
381 if tags:
385 if tags:
382 cache.set(cache_key, tags, board_settings.CACHE_TIMEOUT)
386 cache.set(cache_key, tags, board_settings.CACHE_TIMEOUT)
383
387
384 return tags
388 return tags
385
389
386 def get_post_count(self):
390 def get_post_count(self):
387 return Post.objects.filter(user=self).count()
391 return Post.objects.filter(user=self).count()
388
392
389 def __unicode__(self):
393 def __unicode__(self):
390 return self.user_id + '(' + str(self.rank) + ')'
394 return self.user_id + '(' + str(self.rank) + ')'
391
395
392 def get_last_access_time(self):
396 def get_last_access_time(self):
393 posts = Post.objects.filter(user=self)
397 posts = Post.objects.filter(user=self)
394 if posts.count() > 0:
398 if posts.count() > 0:
395 return posts.latest('pub_time').pub_time
399 return posts.latest('pub_time').pub_time
396
400
397 def add_tag(self, tag):
401 def add_tag(self, tag):
398 self.fav_tags.add(tag)
402 self.fav_tags.add(tag)
399 cache.delete(self._get_tag_cache_key())
403 cache.delete(self._get_tag_cache_key())
400
404
401 def remove_tag(self, tag):
405 def remove_tag(self, tag):
402 self.fav_tags.remove(tag)
406 self.fav_tags.remove(tag)
403 cache.delete(self._get_tag_cache_key())
407 cache.delete(self._get_tag_cache_key())
404
408
405 def _get_tag_cache_key(self):
409 def _get_tag_cache_key(self):
406 return self.user_id + '_tags'
410 return self.user_id + '_tags'
407
411
408
412
409 class Setting(models.Model):
413 class Setting(models.Model):
410
414
411 name = models.CharField(max_length=50)
415 name = models.CharField(max_length=50)
412 value = models.CharField(max_length=50)
416 value = models.CharField(max_length=50)
413 user = models.ForeignKey(User)
417 user = models.ForeignKey(User)
414
418
415
419
416 class Ban(models.Model):
420 class Ban(models.Model):
417
421
418 ip = models.GenericIPAddressField()
422 ip = models.GenericIPAddressField()
423 reason = models.CharField(default=BAN_REASON_AUTO,
424 max_length=BAN_REASON_MAX_LENGTH)
425 can_read = models.BooleanField(default=True)
419
426
420 def __unicode__(self):
427 def __unicode__(self):
421 return self.ip
428 return self.ip
@@ -1,57 +1,57 b''
1 {% load staticfiles %}
1 {% load staticfiles %}
2 {% load i18n %}
2 {% load i18n %}
3 {% load static from staticfiles %}
3 {% load static from staticfiles %}
4
4
5 <!DOCTYPE html>
5 <!DOCTYPE html>
6 <html>
6 <html>
7 <head>
7 <head>
8 <link rel="stylesheet" type="text/css"
8 <link rel="stylesheet" type="text/css"
9 href="{% static 'css/base.css' %}" media="all"/>
9 href="{% static 'css/base.css' %}" media="all"/>
10 <link rel="stylesheet" type="text/css"
10 <link rel="stylesheet" type="text/css"
11 href="{% static theme_css %}" media="all"/>
11 href="{% static theme_css %}" media="all"/>
12 <link rel="alternate" type="application/rss+xml" href="rss/" title=
12 <link rel="alternate" type="application/rss+xml" href="rss/" title=
13 "{% trans 'Feed' %}"/>
13 "{% trans 'Feed' %}"/>
14
14
15 <link rel="icon" type="image/png"
15 <link rel="icon" type="image/png"
16 href="{% static 'favicon.png' %}">
16 href="{% static 'favicon.png' %}">
17
17
18 <meta name="viewport" content="width=device-width, initial-scale=1"/>
18 <meta name="viewport" content="width=device-width, initial-scale=1"/>
19 <meta charset="utf-8"/>
19 <meta charset="utf-8"/>
20
20
21 {% block head %}{% endblock %}
21 {% block head %}{% endblock %}
22 </head>
22 </head>
23 <body>
23 <body>
24 <script src="{% static 'js/jquery-2.0.1.min.js' %}"></script>
24 <script src="{% static 'js/jquery-2.0.1.min.js' %}"></script>
25 <script src="{% static 'js/jquery-ui-1.10.3.custom.min.js' %}"></script>
25 <script src="{% static 'js/jquery-ui-1.10.3.custom.min.js' %}"></script>
26 <script src="{% static 'js/jquery.mousewheel.js' %}"></script>
26 <script src="{% static 'js/jquery.mousewheel.js' %}"></script>
27 <script src="{% url 'django.views.i18n.javascript_catalog' %}"></script>
27 <script src="{% url 'django.views.i18n.javascript_catalog' %}"></script>
28 <script src="{% static 'js/panel.js' %}"></script>
28 <script src="{% static 'js/panel.js' %}"></script>
29 <script src="{% static 'js/popup.js' %}"></script>
29 <script src="{% static 'js/popup.js' %}"></script>
30 <script src="{% static 'js/image.js' %}"></script>
30 <script src="{% static 'js/image.js' %}"></script>
31 <script src="{% static 'js/main.js' %}"></script>
31 <script src="{% static 'js/main.js' %}"></script>
32
32
33 <div class="navigation_panel">
33 <div class="navigation_panel">
34 <a class="link" href="{% url 'index' %}">{% trans "All threads" %}</a>
34 <a class="link" href="{% url 'index' %}">{% trans "All threads" %}</a>
35 {% for tag in tags %}
35 {% for tag in tags %}
36 <a class="tag" href="{% url 'tag' tag_name=tag.name %}"
36 <a class="tag" href="{% url 'tag' tag_name=tag.name %}"
37 >#{{ tag.name }}</a>
37 >#{{ tag.name }}</a>,
38 {% endfor %}
38 {% endfor %}
39 <a class="tag" href="{% url 'tags' %}" title="{% trans 'Tag management' %}"
39 <a class="tag" href="{% url 'tags' %}" title="{% trans 'Tag management' %}"
40 >[...]</a>
40 >[...]</a>
41 <a class="link" href="{% url 'settings' %}">{% trans 'Settings' %}</a>
41 <a class="link" href="{% url 'settings' %}">{% trans 'Settings' %}</a>
42 </div>
42 </div>
43
43
44 {% block content %}{% endblock %}
44 {% block content %}{% endblock %}
45
45
46 <div class="navigation_panel">
46 <div class="navigation_panel">
47 {% block metapanel %}{% endblock %}
47 {% block metapanel %}{% endblock %}
48 [<a href="{% url "login" %}">{% trans 'Login' %}</a>]
48 [<a href="{% url "login" %}">{% trans 'Login' %}</a>]
49 <a class="link" href="#top">{% trans 'Up' %}</a>
49 <a class="link" href="#top">{% trans 'Up' %}</a>
50 </div>
50 </div>
51
51
52 <div class="footer">
52 <div class="footer">
53 <!-- Put your banners here -->
53 <!-- Put your banners here -->
54 </div>
54 </div>
55
55
56 </body>
56 </body>
57 </html>
57 </html>
@@ -1,228 +1,229 b''
1 {% extends "boards/base.html" %}
1 {% extends "boards/base.html" %}
2
2
3 {% load i18n %}
3 {% load i18n %}
4 {% load markup %}
4 {% load markup %}
5 {% load cache %}
5 {% load cache %}
6 {% load board %}
6 {% load board %}
7
7
8 {% block head %}
8 {% block head %}
9 {% if tag %}
9 {% if tag %}
10 <title>Neboard - {{ tag.name }}</title>
10 <title>Neboard - {{ tag.name }}</title>
11 {% else %}
11 {% else %}
12 <title>Neboard</title>
12 <title>Neboard</title>
13 {% endif %}
13 {% endif %}
14 {% endblock %}
14 {% endblock %}
15
15
16 {% block content %}
16 {% block content %}
17
17
18 {% get_current_language as LANGUAGE_CODE %}
18 {% get_current_language as LANGUAGE_CODE %}
19
19
20 {% if tag %}
20 {% if tag %}
21 <div class="tag_info">
21 <div class="tag_info">
22 <h2>
22 <h2>
23 {% if tag in user.fav_tags.all %}
23 {% if tag in user.fav_tags.all %}
24 <a href="{% url 'tag_unsubscribe' tag.name %}?next={{ request.path }}"
24 <a href="{% url 'tag_unsubscribe' tag.name %}?next={{ request.path }}"
25 class="fav">β˜…</a>
25 class="fav">β˜…</a>
26 {% else %}
26 {% else %}
27 <a href="{% url 'tag_subscribe' tag.name %}?next={{ request.path }}"
27 <a href="{% url 'tag_subscribe' tag.name %}?next={{ request.path }}"
28 class="not_fav">β˜…</a>
28 class="not_fav">β˜…</a>
29 {% endif %}
29 {% endif %}
30 #{{ tag.name }}
30 #{{ tag.name }}
31 </h2>
31 </h2>
32 </div>
32 </div>
33 {% endif %}
33 {% endif %}
34
34
35 {% if threads %}
35 {% if threads %}
36 {% for thread in threads %}
36 {% for thread in threads %}
37 {% cache 600 thread_short thread.thread.last_edit_time moderator LANGUAGE_CODE %}
37 {% cache 600 thread_short thread.thread.last_edit_time moderator LANGUAGE_CODE %}
38 <div class="thread">
38 <div class="thread">
39 {% if thread.bumpable %}
39 {% if thread.bumpable %}
40 <div class="post" id="{{ thread.thread.id }}">
40 <div class="post" id="{{ thread.thread.id }}">
41 {% else %}
41 {% else %}
42 <div class="post dead_post" id="{{ thread.thread.id }}">
42 <div class="post dead_post" id="{{ thread.thread.id }}">
43 {% endif %}
43 {% endif %}
44 {% if thread.thread.image %}
44 {% if thread.thread.image %}
45 <div class="image">
45 <div class="image">
46 <a class="thumb"
46 <a class="thumb"
47 href="{{ thread.thread.image.url }}"><img
47 href="{{ thread.thread.image.url }}"><img
48 src="{{ thread.thread.image.url_200x150 }}"
48 src="{{ thread.thread.image.url_200x150 }}"
49 alt="{{ thread.thread.id }}"
49 alt="{{ thread.thread.id }}"
50 data-width="{{ thread.thread.image_width }}"
50 data-width="{{ thread.thread.image_width }}"
51 data-height="{{ thread.thread.image_height }}" />
51 data-height="{{ thread.thread.image_height }}" />
52 </a>
52 </a>
53 </div>
53 </div>
54 {% endif %}
54 {% endif %}
55 <div class="message">
55 <div class="message">
56 <div class="post-info">
56 <div class="post-info">
57 <span class="title">{{ thread.thread.title }}</span>
57 <span class="title">{{ thread.thread.title }}</span>
58 <a class="post_id" href="{% url 'thread' thread.thread.id %}"
58 <a class="post_id" href="{% url 'thread' thread.thread.id %}"
59 >({{ thread.thread.id }})</a>
59 >({{ thread.thread.id }})</a>
60 [{{ thread.thread.pub_time }}]
60 [{{ thread.thread.pub_time }}]
61 [<a class="link" href="{% url 'thread' thread.thread.id %}#form"
61 [<a class="link" href="{% url 'thread' thread.thread.id %}#form"
62 >{% trans "Reply" %}</a>]
62 >{% trans "Reply" %}</a>]
63
63
64 {% if moderator %}
64 {% if moderator %}
65 <span class="moderator_info">
65 <span class="moderator_info">
66 [<a href="{% url 'delete' post_id=thread.thread.id %}?next={{ request.path }}"
66 [<a href="{% url 'delete' post_id=thread.thread.id %}?next={{ request.path }}"
67 >{% trans 'Delete' %}</a>]
67 >{% trans 'Delete' %}</a>]
68 ({{ thread.thread.poster_ip }})
68 ({{ thread.thread.poster_ip }})
69 [<a href="{% url 'ban' post_id=thread.thread.id %}?next={{ request.path }}"
69 [<a href="{% url 'ban' post_id=thread.thread.id %}?next={{ request.path }}"
70 >{% trans 'Ban IP' %}</a>]
70 >{% trans 'Ban IP' %}</a>]
71 </span>
71 </span>
72 {% endif %}
72 {% endif %}
73 </div>
73 </div>
74 {% autoescape off %}
74 {% autoescape off %}
75 {{ thread.thread.text.rendered|truncatewords_html:50 }}
75 {{ thread.thread.text.rendered|truncatewords_html:50 }}
76 {% endautoescape %}
76 {% endautoescape %}
77 {% if thread.thread.referenced_posts.all %}
77 {% if thread.thread.referenced_posts.all %}
78 <div class="refmap">
78 <div class="refmap">
79 {% trans "Replies" %}:
79 {% trans "Replies" %}:
80 {% for ref_post in thread.thread.referenced_posts.all %}
80 {% for ref_post in thread.thread.referenced_posts.all %}
81 <a href="{% post_url ref_post.id %}">&gt;&gt;{{ ref_post.id }}</a>
81 <a href="{% post_url ref_post.id %}">&gt;&gt;{{ ref_post.id }}</a>
82 {% endfor %}
82 {% endfor %}
83 </div>
83 </div>
84 {% endif %}
84 {% endif %}
85 </div>
85 </div>
86 <div class="metadata">
86 <div class="metadata">
87 {{ thread.thread.get_reply_count }} {% trans 'replies' %},
87 {{ thread.thread.get_reply_count }} {% trans 'replies' %},
88 {{ thread.thread.get_images_count }} {% trans 'images' %}.
88 {{ thread.thread.get_images_count }} {% trans 'images' %}.
89 {% if thread.thread.tags %}
89 {% if thread.thread.tags %}
90 <span class="tags">
90 <span class="tags">
91 {% for tag in thread.thread.get_tags %}
91 {% for tag in thread.thread.get_tags %}
92 <a class="tag" href="
92 <a class="tag" href="
93 {% url 'tag' tag_name=tag.name %}">
93 {% url 'tag' tag_name=tag.name %}">
94 #{{ tag.name }}</a>
94 #{{ tag.name }}</a
95 >{% if not forloop.last %},{% endif %}
95 {% endfor %}
96 {% endfor %}
96 </span>
97 </span>
97 {% endif %}
98 {% endif %}
98 </div>
99 </div>
99 </div>
100 </div>
100 {% if thread.last_replies.exists %}
101 {% if thread.last_replies.exists %}
101 <div class="last-replies">
102 <div class="last-replies">
102 {% for post in thread.last_replies %}
103 {% for post in thread.last_replies %}
103 {% if thread.bumpable %}
104 {% if thread.bumpable %}
104 <div class="post" id="{{ post.id }}">
105 <div class="post" id="{{ post.id }}">
105 {% else %}
106 {% else %}
106 <div class="post dead_post" id="{{ post.id }}">
107 <div class="post dead_post" id="{{ post.id }}">
107 {% endif %}
108 {% endif %}
108 {% if post.image %}
109 {% if post.image %}
109 <div class="image">
110 <div class="image">
110 <a class="thumb"
111 <a class="thumb"
111 href="{{ post.image.url }}"><img
112 href="{{ post.image.url }}"><img
112 src=" {{ post.image.url_200x150 }}"
113 src=" {{ post.image.url_200x150 }}"
113 alt="{{ post.id }}"
114 alt="{{ post.id }}"
114 data-width="{{ post.image_width }}"
115 data-width="{{ post.image_width }}"
115 data-height="{{ post.image_height }}"/>
116 data-height="{{ post.image_height }}"/>
116 </a>
117 </a>
117 </div>
118 </div>
118 {% endif %}
119 {% endif %}
119 <div class="message">
120 <div class="message">
120 <div class="post-info">
121 <div class="post-info">
121 <span class="title">{{ post.title }}</span>
122 <span class="title">{{ post.title }}</span>
122 <a class="post_id" href="
123 <a class="post_id" href="
123 {% url 'thread' thread.thread.id %}#{{ post.id }}">
124 {% url 'thread' thread.thread.id %}#{{ post.id }}">
124 ({{ post.id }})</a>
125 ({{ post.id }})</a>
125 [{{ post.pub_time }}]
126 [{{ post.pub_time }}]
126 </div>
127 </div>
127 {% autoescape off %}
128 {% autoescape off %}
128 {{ post.text.rendered|truncatewords_html:50 }}
129 {{ post.text.rendered|truncatewords_html:50 }}
129 {% endautoescape %}
130 {% endautoescape %}
130 </div>
131 </div>
131 {% if post.referenced_posts.all %}
132 {% if post.referenced_posts.all %}
132 <div class="refmap">
133 <div class="refmap">
133 {% trans "Replies" %}:
134 {% trans "Replies" %}:
134 {% for ref_post in post.referenced_posts.all %}
135 {% for ref_post in post.referenced_posts.all %}
135 <a href="{% post_url ref_post.id %}">&gt;&gt;{{ ref_post.id }}</a>
136 <a href="{% post_url ref_post.id %}">&gt;&gt;{{ ref_post.id }}</a>
136 {% endfor %}
137 {% endfor %}
137 </div>
138 </div>
138 {% endif %}
139 {% endif %}
139 </div>
140 </div>
140 {% endfor %}
141 {% endfor %}
141 </div>
142 </div>
142 {% endif %}
143 {% endif %}
143 </div>
144 </div>
144 {% endcache %}
145 {% endcache %}
145 {% endfor %}
146 {% endfor %}
146 {% else %}
147 {% else %}
147 <div class="post">
148 <div class="post">
148 {% trans 'No threads exist. Create the first one!' %}</div>
149 {% trans 'No threads exist. Create the first one!' %}</div>
149 {% endif %}
150 {% endif %}
150
151
151 <form enctype="multipart/form-data" method="post">{% csrf_token %}
152 <form enctype="multipart/form-data" method="post">{% csrf_token %}
152 <div class="post-form-w">
153 <div class="post-form-w">
153
154
154 <div class="form-title">{% trans "Create new thread" %}</div>
155 <div class="form-title">{% trans "Create new thread" %}</div>
155 <div class="post-form">
156 <div class="post-form">
156 <div class="form-row">
157 <div class="form-row">
157 <div class="form-label">{% trans 'Title' %}</div>
158 <div class="form-label">{% trans 'Title' %}</div>
158 <div class="form-input">{{ form.title }}</div>
159 <div class="form-input">{{ form.title }}</div>
159 <div class="form-errors">{{ form.title.errors }}</div>
160 <div class="form-errors">{{ form.title.errors }}</div>
160 </div>
161 </div>
161 <div class="form-row">
162 <div class="form-row">
162 <div class="form-label">{% trans 'Formatting' %}</div>
163 <div class="form-label">{% trans 'Formatting' %}</div>
163 <div class="form-input" id="mark_panel">
164 <div class="form-input" id="mark_panel">
164 <span class="mark_btn" id="quote"><span class="quote">&gt;{% trans 'quote' %}</span></span>
165 <span class="mark_btn" id="quote"><span class="quote">&gt;{% trans 'quote' %}</span></span>
165 <span class="mark_btn" id="italic"><i>{% trans 'italic' %}</i></span>
166 <span class="mark_btn" id="italic"><i>{% trans 'italic' %}</i></span>
166 <span class="mark_btn" id="bold"><b>{% trans 'bold' %}</b></span>
167 <span class="mark_btn" id="bold"><b>{% trans 'bold' %}</b></span>
167 <span class="mark_btn" id="spoiler"><span class="spoiler">{% trans 'spoiler' %}</span></span>
168 <span class="mark_btn" id="spoiler"><span class="spoiler">{% trans 'spoiler' %}</span></span>
168 <span class="mark_btn" id="comment"><span class="comment">// {% trans 'comment' %}</span></span>
169 <span class="mark_btn" id="comment"><span class="comment">// {% trans 'comment' %}</span></span>
169 </div>
170 </div>
170 </div>
171 </div>
171 <div class="form-row">
172 <div class="form-row">
172 <div class="form-label">{% trans 'Text' %}</div>
173 <div class="form-label">{% trans 'Text' %}</div>
173 <div class="form-input">{{ form.text }}</div>
174 <div class="form-input">{{ form.text }}</div>
174 <div class="form-errors">{{ form.text.errors }}</div>
175 <div class="form-errors">{{ form.text.errors }}</div>
175 </div>
176 </div>
176 <div class="form-row">
177 <div class="form-row">
177 <div class="form-label">{% trans 'Image' %}</div>
178 <div class="form-label">{% trans 'Image' %}</div>
178 <div class="form-input">{{ form.image }}</div>
179 <div class="form-input">{{ form.image }}</div>
179 <div class="form-errors">{{ form.image.errors }}</div>
180 <div class="form-errors">{{ form.image.errors }}</div>
180 </div>
181 </div>
181 <div class="form-row">
182 <div class="form-row">
182 <div class="form-label">{% trans 'Tags' %}</div>
183 <div class="form-label">{% trans 'Tags' %}</div>
183 <div class="form-input">{{ form.tags }}</div>
184 <div class="form-input">{{ form.tags }}</div>
184 <div class="form-errors">{{ form.tags.errors }}</div>
185 <div class="form-errors">{{ form.tags.errors }}</div>
185 </div>
186 </div>
186 <div class="form-row form-email">
187 <div class="form-row form-email">
187 <div class="form-label">{% trans 'e-mail' %}</div>
188 <div class="form-label">{% trans 'e-mail' %}</div>
188 <div class="form-input">{{ form.email }}</div>
189 <div class="form-input">{{ form.email }}</div>
189 <div class="form-errors">{{ form.email.errors }}</div>
190 <div class="form-errors">{{ form.email.errors }}</div>
190 </div>
191 </div>
191 <div class="form-row">
192 <div class="form-row">
192 {{ form.captcha }}
193 {{ form.captcha }}
193 <div class="form-errors">{{ form.captcha.errors }}</div>
194 <div class="form-errors">{{ form.captcha.errors }}</div>
194 </div>
195 </div>
195 <div class="form-row">
196 <div class="form-row">
196 <div class="form-errors">{{ form.other.errors }}</div>
197 <div class="form-errors">{{ form.other.errors }}</div>
197 </div>
198 </div>
198 </div>
199 </div>
199 <div class="form-submit">
200 <div class="form-submit">
200 <input type="submit" value="{% trans "Post" %}"/></div>
201 <input type="submit" value="{% trans "Post" %}"/></div>
201 <div>
202 <div>
202 {% trans 'Tags must be delimited by spaces. Text or image is required.' %}
203 {% trans 'Tags must be delimited by spaces. Text or image is required.' %}
203 </div>
204 </div>
204 <div><a href="{% url "staticpage" name="help" %}">
205 <div><a href="{% url "staticpage" name="help" %}">
205 {% trans 'Text syntax' %}</a></div>
206 {% trans 'Text syntax' %}</a></div>
206 </div>
207 </div>
207 </form>
208 </form>
208
209
209 {% endblock %}
210 {% endblock %}
210
211
211 {% block metapanel %}
212 {% block metapanel %}
212
213
213 <span class="metapanel">
214 <span class="metapanel">
214 <b><a href="{% url "authors" %}">Neboard</a> 1.3</b>
215 <b><a href="{% url "authors" %}">Neboard</a> 1.3</b>
215 {% trans "Pages:" %}
216 {% trans "Pages:" %}
216 {% for page in pages %}
217 {% for page in pages %}
217 [<a href="
218 [<a href="
218 {% if tag %}
219 {% if tag %}
219 {% url "tag" tag_name=tag page=page %}
220 {% url "tag" tag_name=tag page=page %}
220 {% else %}
221 {% else %}
221 {% url "index" page=page %}
222 {% url "index" page=page %}
222 {% endif %}
223 {% endif %}
223 ">{{ page }}</a>]
224 ">{{ page }}</a>]
224 {% endfor %}
225 {% endfor %}
225 [<a href="rss/">RSS</a>]
226 [<a href="rss/">RSS</a>]
226 </span>
227 </span>
227
228
228 {% endblock %}
229 {% endblock %}
@@ -1,12 +1,13 b''
1 {% extends "boards/static_base.html" %}
1 {% extends "boards/static_base.html" %}
2
2
3 {% load i18n %}
3 {% load i18n %}
4
4
5 {% block head %}
5 {% block head %}
6 <title>{% trans "Banned" %}</title>
6 <title>{% trans "Banned" %}</title>
7 {% endblock %}
7 {% endblock %}
8
8
9 {% block staticcontent %}
9 {% block staticcontent %}
10 <p><img src="{{ STATIC_URL }}images/banned.png" width="200" /></p>
10 <p><img src="{{ STATIC_URL }}images/banned.png" width="200" /></p>
11 <p>{% trans 'Your IP address has been banned. Contact the administrator' %}</p>
11 <p>{% trans 'Your IP address has been banned. Contact the administrator' %}</p>
12 <p>{{ ban_reason }}</p>
12 {% endblock %} No newline at end of file
13 {% endblock %}
@@ -1,161 +1,162 b''
1 {% extends "boards/base.html" %}
1 {% extends "boards/base.html" %}
2
2
3 {% load i18n %}
3 {% load i18n %}
4 {% load markup %}
4 {% load markup %}
5 {% load cache %}
5 {% load cache %}
6 {% load static from staticfiles %}
6 {% load static from staticfiles %}
7 {% load board %}
7 {% load board %}
8
8
9 {% block head %}
9 {% block head %}
10 <title>Neboard - {{ posts.0.get_title }}</title>
10 <title>Neboard - {{ posts.0.get_title }}</title>
11 {% endblock %}
11 {% endblock %}
12
12
13 {% block content %}
13 {% block content %}
14 {% get_current_language as LANGUAGE_CODE %}
14 {% get_current_language as LANGUAGE_CODE %}
15
15
16 <script src="{% static 'js/thread.js' %}"></script>
16 <script src="{% static 'js/thread.js' %}"></script>
17
17
18 {% if posts %}
18 {% if posts %}
19 {% cache 600 thread_view posts.0.last_edit_time moderator LANGUAGE_CODE %}
19 {% cache 600 thread_view posts.0.last_edit_time moderator LANGUAGE_CODE %}
20 {% if bumpable %}
20 {% if bumpable %}
21 <div class="bar-bg">
21 <div class="bar-bg">
22 <div class="bar-value" style="width:{{ bumplimit_progress }}%">
22 <div class="bar-value" style="width:{{ bumplimit_progress }}%">
23 </div>
23 </div>
24 <div class="bar-text">
24 <div class="bar-text">
25 {{ posts_left }} {% trans 'posts to bumplimit' %}
25 {{ posts_left }} {% trans 'posts to bumplimit' %}
26 </div>
26 </div>
27 </div>
27 </div>
28 {% endif %}
28 {% endif %}
29 <div class="thread">
29 <div class="thread">
30 {% for post in posts %}
30 {% for post in posts %}
31 {% if bumpable %}
31 {% if bumpable %}
32 <div class="post" id="{{ post.id }}">
32 <div class="post" id="{{ post.id }}">
33 {% else %}
33 {% else %}
34 <div class="post dead_post" id="{{ post.id }}">
34 <div class="post dead_post" id="{{ post.id }}">
35 {% endif %}
35 {% endif %}
36 {% if post.image %}
36 {% if post.image %}
37 <div class="image">
37 <div class="image">
38 <a
38 <a
39 class="thumb"
39 class="thumb"
40 href="{{ post.image.url }}"><img
40 href="{{ post.image.url }}"><img
41 src="{{ post.image.url_200x150 }}"
41 src="{{ post.image.url_200x150 }}"
42 alt="{{ post.id }}"
42 alt="{{ post.id }}"
43 data-width="{{ post.image_width }}"
43 data-width="{{ post.image_width }}"
44 data-height="{{ post.image_height }}"/>
44 data-height="{{ post.image_height }}"/>
45 </a>
45 </a>
46 </div>
46 </div>
47 {% endif %}
47 {% endif %}
48 <div class="message">
48 <div class="message">
49 <div class="post-info">
49 <div class="post-info">
50 <span class="title">{{ post.title }}</span>
50 <span class="title">{{ post.title }}</span>
51 <a class="post_id" href="#{{ post.id }}">
51 <a class="post_id" href="#{{ post.id }}">
52 ({{ post.id }})</a>
52 ({{ post.id }})</a>
53 [{{ post.pub_time }}]
53 [{{ post.pub_time }}]
54 [<a href="#" onclick="javascript:addQuickReply('{{ post.id }}')
54 [<a href="#" onclick="javascript:addQuickReply('{{ post.id }}')
55 ; return false;">&gt;&gt;</a>]
55 ; return false;">&gt;&gt;</a>]
56
56
57 {% if moderator %}
57 {% if moderator %}
58 <span class="moderator_info">
58 <span class="moderator_info">
59 [<a href="{% url 'delete' post_id=post.id %}"
59 [<a href="{% url 'delete' post_id=post.id %}"
60 >{% trans 'Delete' %}</a>]
60 >{% trans 'Delete' %}</a>]
61 ({{ post.poster_ip }})
61 ({{ post.poster_ip }})
62 [<a href="{% url 'ban' post_id=post.id %}?next={{ request.path }}"
62 [<a href="{% url 'ban' post_id=post.id %}?next={{ request.path }}"
63 >{% trans 'Ban IP' %}</a>]
63 >{% trans 'Ban IP' %}</a>]
64 </span>
64 </span>
65 {% endif %}
65 {% endif %}
66 </div>
66 </div>
67 {% autoescape off %}
67 {% autoescape off %}
68 {{ post.text.rendered }}
68 {{ post.text.rendered }}
69 {% endautoescape %}
69 {% endautoescape %}
70 {% if post.referenced_posts.all %}
70 {% if post.referenced_posts.all %}
71 <div class="refmap">
71 <div class="refmap">
72 {% trans "Replies" %}:
72 {% trans "Replies" %}:
73 {% for ref_post in post.referenced_posts.all %}
73 {% for ref_post in post.referenced_posts.all %}
74 <a href="{% post_url ref_post.id %}">&gt;&gt;{{ ref_post.id }}</a>
74 <a href="{% post_url ref_post.id %}">&gt;&gt;{{ ref_post.id }}</a>
75 {% endfor %}
75 {% endfor %}
76 </div>
76 </div>
77 {% endif %}
77 {% endif %}
78 </div>
78 </div>
79 {% if post.id == posts.0.id %}
79 {% if forloop.first %}
80 <div class="metadata">
80 <div class="metadata">
81 <span class="tags">
81 <span class="tags">
82 {% for tag in post.get_tags %}
82 {% for tag in post.get_tags %}
83 <a class="tag" href="{% url 'tag' tag.name %}">
83 <a class="tag" href="{% url 'tag' tag.name %}">
84 #{{ tag.name }}</a>
84 #{{ tag.name }}</a
85 >{% if not forloop.last %},{% endif %}
85 {% endfor %}
86 {% endfor %}
86 </span>
87 </span>
87 </div>
88 </div>
88 {% endif %}
89 {% endif %}
89 </div>
90 </div>
90 {% endfor %}
91 {% endfor %}
91 </div>
92 </div>
92 {% endcache %}
93 {% endcache %}
93 {% endif %}
94 {% endif %}
94
95
95 <form id="form" enctype="multipart/form-data" method="post"
96 <form id="form" enctype="multipart/form-data" method="post"
96 >{% csrf_token %}
97 >{% csrf_token %}
97 <div class="post-form-w">
98 <div class="post-form-w">
98 <div class="form-title">{% trans "Reply to thread" %} #{{ posts.0.id }}</div>
99 <div class="form-title">{% trans "Reply to thread" %} #{{ posts.0.id }}</div>
99 <div class="post-form">
100 <div class="post-form">
100 <div class="form-row">
101 <div class="form-row">
101 <div class="form-label">{% trans 'Title' %}</div>
102 <div class="form-label">{% trans 'Title' %}</div>
102 <div class="form-input">{{ form.title }}</div>
103 <div class="form-input">{{ form.title }}</div>
103 <div class="form-errors">{{ form.title.errors }}</div>
104 <div class="form-errors">{{ form.title.errors }}</div>
104 </div>
105 </div>
105 <div class="form-row">
106 <div class="form-row">
106 <div class="form-label">{% trans 'Formatting' %}</div>
107 <div class="form-label">{% trans 'Formatting' %}</div>
107 <div class="form-input" id="mark_panel">
108 <div class="form-input" id="mark_panel">
108 <span class="mark_btn" id="quote"><span class="quote">&gt;{% trans 'quote' %}</span></span>
109 <span class="mark_btn" id="quote"><span class="quote">&gt;{% trans 'quote' %}</span></span>
109 <span class="mark_btn" id="italic"><i>{% trans 'italic' %}</i></span>
110 <span class="mark_btn" id="italic"><i>{% trans 'italic' %}</i></span>
110 <span class="mark_btn" id="bold"><b>{% trans 'bold' %}</b></span>
111 <span class="mark_btn" id="bold"><b>{% trans 'bold' %}</b></span>
111 <span class="mark_btn" id="spoiler"><span class="spoiler">{% trans 'spoiler' %}</span></span>
112 <span class="mark_btn" id="spoiler"><span class="spoiler">{% trans 'spoiler' %}</span></span>
112 <span class="mark_btn" id="comment"><span class="comment">// {% trans 'comment' %}</span></span>
113 <span class="mark_btn" id="comment"><span class="comment">// {% trans 'comment' %}</span></span>
113 </div>
114 </div>
114 </div>
115 </div>
115 <div class="form-row">
116 <div class="form-row">
116 <div class="form-label">{% trans 'Text' %}</div>
117 <div class="form-label">{% trans 'Text' %}</div>
117 <div class="form-input">{{ form.text }}</div>
118 <div class="form-input">{{ form.text }}</div>
118 <div class="form-errors">{{ form.text.errors }}</div>
119 <div class="form-errors">{{ form.text.errors }}</div>
119 </div>
120 </div>
120 <div class="form-row">
121 <div class="form-row">
121 <div class="form-label">{% trans 'Image' %}</div>
122 <div class="form-label">{% trans 'Image' %}</div>
122 <div class="form-input">{{ form.image }}</div>
123 <div class="form-input">{{ form.image }}</div>
123 <div class="form-errors">{{ form.image.errors }}</div>
124 <div class="form-errors">{{ form.image.errors }}</div>
124 </div>
125 </div>
125 <div class="form-row form-email">
126 <div class="form-row form-email">
126 <div class="form-label">{% trans 'e-mail' %}</div>
127 <div class="form-label">{% trans 'e-mail' %}</div>
127 <div class="form-input">{{ form.email }}</div>
128 <div class="form-input">{{ form.email }}</div>
128 <div class="form-errors">{{ form.email.errors }}</div>
129 <div class="form-errors">{{ form.email.errors }}</div>
129 </div>
130 </div>
130 <div class="form-row">
131 <div class="form-row">
131 {{ form.captcha }}
132 {{ form.captcha }}
132 <div class="form-errors">{{ form.captcha.errors }}</div>
133 <div class="form-errors">{{ form.captcha.errors }}</div>
133 </div>
134 </div>
134 <div class="form-row">
135 <div class="form-row">
135 <div class="form-errors">{{ form.other.errors }}</div>
136 <div class="form-errors">{{ form.other.errors }}</div>
136 </div>
137 </div>
137 </div>
138 </div>
138
139
139 <div class="form-submit"><input type="submit"
140 <div class="form-submit"><input type="submit"
140 value="{% trans "Post" %}"/></div>
141 value="{% trans "Post" %}"/></div>
141 <div><a href="{% url "staticpage" name="help" %}">
142 <div><a href="{% url "staticpage" name="help" %}">
142 {% trans 'Text syntax' %}</a></div>
143 {% trans 'Text syntax' %}</a></div>
143 </div>
144 </div>
144 </form>
145 </form>
145
146
146 {% endblock %}
147 {% endblock %}
147
148
148 {% block metapanel %}
149 {% block metapanel %}
149
150
150 {% get_current_language as LANGUAGE_CODE %}
151 {% get_current_language as LANGUAGE_CODE %}
151
152
152 <span class="metapanel">
153 <span class="metapanel">
153 {% cache 600 thread_meta posts.0.last_edit_time moderator LANGUAGE_CODE %}
154 {% cache 600 thread_meta posts.0.last_edit_time moderator LANGUAGE_CODE %}
154 {{ posts.0.get_reply_count }} {% trans 'replies' %},
155 {{ posts.0.get_reply_count }} {% trans 'replies' %},
155 {{ posts.0.get_images_count }} {% trans 'images' %}.
156 {{ posts.0.get_images_count }} {% trans 'images' %}.
156 {% trans 'Last update: ' %}{{ posts.0.last_edit_time }}
157 {% trans 'Last update: ' %}{{ posts.0.last_edit_time }}
157 [<a href="rss/">RSS</a>]
158 [<a href="rss/">RSS</a>]
158 {% endcache %}
159 {% endcache %}
159 </span>
160 </span>
160
161
161 {% endblock %}
162 {% endblock %}
@@ -1,492 +1,505 b''
1 import hashlib
1 import hashlib
2 import string
2 import string
3 from django.core import serializers
3 from django.core import serializers
4 from django.core.urlresolvers import reverse
4 from django.core.urlresolvers import reverse
5 from django.http import HttpResponseRedirect
5 from django.http import HttpResponseRedirect
6 from django.http.response import HttpResponse
6 from django.http.response import HttpResponse
7 from django.template import RequestContext
7 from django.template import RequestContext
8 from django.shortcuts import render, redirect, get_object_or_404
8 from django.shortcuts import render, redirect, get_object_or_404
9 from django.utils import timezone
9 from django.utils import timezone
10 from django.db import transaction
10 from django.db import transaction
11
11
12 from boards import forms
12 from boards import forms
13 import boards
13 import boards
14 from boards import utils
14 from boards import utils
15 from boards.forms import ThreadForm, PostForm, SettingsForm, PlainErrorList, \
15 from boards.forms import ThreadForm, PostForm, SettingsForm, PlainErrorList, \
16 ThreadCaptchaForm, PostCaptchaForm, LoginForm, ModeratorSettingsForm
16 ThreadCaptchaForm, PostCaptchaForm, LoginForm, ModeratorSettingsForm
17
17
18 from boards.models import Post, Tag, Ban, User, RANK_USER, SETTING_MODERATE, \
18 from boards.models import Post, Tag, Ban, User, RANK_USER, SETTING_MODERATE, \
19 REGEX_REPLY
19 REGEX_REPLY
20 from boards import authors
20 from boards import authors
21 from boards.utils import get_client_ip
21 from boards.utils import get_client_ip
22 import neboard
22 import neboard
23 import re
23 import re
24
24
25 BAN_REASON_SPAM = 'Autoban: spam bot'
26
25
27
26 def index(request, page=0):
28 def index(request, page=0):
27 context = _init_default_context(request)
29 context = _init_default_context(request)
28
30
29 if utils.need_include_captcha(request):
31 if utils.need_include_captcha(request):
30 threadFormClass = ThreadCaptchaForm
32 threadFormClass = ThreadCaptchaForm
31 kwargs = {'request': request}
33 kwargs = {'request': request}
32 else:
34 else:
33 threadFormClass = ThreadForm
35 threadFormClass = ThreadForm
34 kwargs = {}
36 kwargs = {}
35
37
36 if request.method == 'POST':
38 if request.method == 'POST':
37 form = threadFormClass(request.POST, request.FILES,
39 form = threadFormClass(request.POST, request.FILES,
38 error_class=PlainErrorList, **kwargs)
40 error_class=PlainErrorList, **kwargs)
39 form.session = request.session
41 form.session = request.session
40
42
41 if form.is_valid():
43 if form.is_valid():
42 return _new_post(request, form)
44 return _new_post(request, form)
43 if form.need_to_ban:
45 if form.need_to_ban:
44 # Ban user because he is suspected to be a bot
46 # Ban user because he is suspected to be a bot
45 _ban_current_user(request)
47 _ban_current_user(request)
46 else:
48 else:
47 form = threadFormClass(error_class=PlainErrorList, **kwargs)
49 form = threadFormClass(error_class=PlainErrorList, **kwargs)
48
50
49 threads = []
51 threads = []
50 for thread in Post.objects.get_threads(page=int(page)):
52 for thread in Post.objects.get_threads(page=int(page)):
51 threads.append({
53 threads.append({
52 'thread': thread,
54 'thread': thread,
53 'bumpable': thread.can_bump(),
55 'bumpable': thread.can_bump(),
54 'last_replies': thread.get_last_replies(),
56 'last_replies': thread.get_last_replies(),
55 })
57 })
56
58
57 context['threads'] = None if len(threads) == 0 else threads
59 context['threads'] = None if len(threads) == 0 else threads
58 context['form'] = form
60 context['form'] = form
59 context['pages'] = range(Post.objects.get_thread_page_count())
61 context['pages'] = range(Post.objects.get_thread_page_count())
60
62
61 return render(request, 'boards/posting_general.html',
63 return render(request, 'boards/posting_general.html',
62 context)
64 context)
63
65
64
66
65 @transaction.commit_on_success
67 @transaction.commit_on_success
66 def _new_post(request, form, thread_id=boards.models.NO_PARENT):
68 def _new_post(request, form, thread_id=boards.models.NO_PARENT):
67 """Add a new post (in thread or as a reply)."""
69 """Add a new post (in thread or as a reply)."""
68
70
69 ip = get_client_ip(request)
71 ip = get_client_ip(request)
70 is_banned = Ban.objects.filter(ip=ip).exists()
72 is_banned = Ban.objects.filter(ip=ip).exists()
71
73
72 if is_banned:
74 if is_banned:
73 return redirect(you_are_banned)
75 return redirect(you_are_banned)
74
76
75 data = form.cleaned_data
77 data = form.cleaned_data
76
78
77 title = data['title']
79 title = data['title']
78 text = data['text']
80 text = data['text']
79
81
80 text = _remove_invalid_links(text)
82 text = _remove_invalid_links(text)
81
83
82 if 'image' in data.keys():
84 if 'image' in data.keys():
83 image = data['image']
85 image = data['image']
84 else:
86 else:
85 image = None
87 image = None
86
88
87 tags = []
89 tags = []
88
90
89 new_thread = thread_id == boards.models.NO_PARENT
91 new_thread = thread_id == boards.models.NO_PARENT
90 if new_thread:
92 if new_thread:
91 tag_strings = data['tags']
93 tag_strings = data['tags']
92
94
93 if tag_strings:
95 if tag_strings:
94 tag_strings = tag_strings.split(' ')
96 tag_strings = tag_strings.split(' ')
95 for tag_name in tag_strings:
97 for tag_name in tag_strings:
96 tag_name = string.lower(tag_name.strip())
98 tag_name = string.lower(tag_name.strip())
97 if len(tag_name) > 0:
99 if len(tag_name) > 0:
98 tag, created = Tag.objects.get_or_create(name=tag_name)
100 tag, created = Tag.objects.get_or_create(name=tag_name)
99 tags.append(tag)
101 tags.append(tag)
100
102
101 linked_tags = tag.get_linked_tags()
103 linked_tags = tag.get_linked_tags()
102 if len(linked_tags) > 0:
104 if len(linked_tags) > 0:
103 tags.extend(linked_tags)
105 tags.extend(linked_tags)
104
106
105 op = None if thread_id == boards.models.NO_PARENT else \
107 op = None if thread_id == boards.models.NO_PARENT else \
106 get_object_or_404(Post, id=thread_id)
108 get_object_or_404(Post, id=thread_id)
107 post = Post.objects.create_post(title=title, text=text, ip=ip,
109 post = Post.objects.create_post(title=title, text=text, ip=ip,
108 thread=op, image=image,
110 thread=op, image=image,
109 tags=tags, user=_get_user(request))
111 tags=tags, user=_get_user(request))
110
112
111 thread_to_show = (post.id if new_thread else thread_id)
113 thread_to_show = (post.id if new_thread else thread_id)
112
114
113 if new_thread:
115 if new_thread:
114 return redirect(thread, post_id=thread_to_show)
116 return redirect(thread, post_id=thread_to_show)
115 else:
117 else:
116 return redirect(reverse(thread, kwargs={'post_id': thread_to_show}) +
118 return redirect(reverse(thread, kwargs={'post_id': thread_to_show}) +
117 '#' + str(post.id))
119 '#' + str(post.id))
118
120
119
121
120 def tag(request, tag_name, page=0):
122 def tag(request, tag_name, page=0):
121 """
123 """
122 Get all tag threads. Threads are split in pages, so some page is
124 Get all tag threads. Threads are split in pages, so some page is
123 requested. Default page is 0.
125 requested. Default page is 0.
124 """
126 """
125
127
126 tag = get_object_or_404(Tag, name=tag_name)
128 tag = get_object_or_404(Tag, name=tag_name)
127 threads = []
129 threads = []
128 for thread in Post.objects.get_threads(tag=tag, page=int(page)):
130 for thread in Post.objects.get_threads(tag=tag, page=int(page)):
129 threads.append({
131 threads.append({
130 'thread': thread,
132 'thread': thread,
131 'bumpable': thread.can_bump(),
133 'bumpable': thread.can_bump(),
132 'last_replies': thread.get_last_replies(),
134 'last_replies': thread.get_last_replies(),
133 })
135 })
134
136
135 if request.method == 'POST':
137 if request.method == 'POST':
136 form = ThreadForm(request.POST, request.FILES,
138 form = ThreadForm(request.POST, request.FILES,
137 error_class=PlainErrorList)
139 error_class=PlainErrorList)
138 form.session = request.session
140 form.session = request.session
139
141
140 if form.is_valid():
142 if form.is_valid():
141 return _new_post(request, form)
143 return _new_post(request, form)
142 if form.need_to_ban:
144 if form.need_to_ban:
143 # Ban user because he is suspected to be a bot
145 # Ban user because he is suspected to be a bot
144 _ban_current_user(request)
146 _ban_current_user(request)
145 else:
147 else:
146 form = forms.ThreadForm(initial={'tags': tag_name},
148 form = forms.ThreadForm(initial={'tags': tag_name},
147 error_class=PlainErrorList)
149 error_class=PlainErrorList)
148
150
149 context = _init_default_context(request)
151 context = _init_default_context(request)
150 context['threads'] = None if len(threads) == 0 else threads
152 context['threads'] = None if len(threads) == 0 else threads
151 context['tag'] = tag
153 context['tag'] = tag
152 context['pages'] = range(Post.objects.get_thread_page_count(tag=tag))
154 context['pages'] = range(Post.objects.get_thread_page_count(tag=tag))
153
155
154 context['form'] = form
156 context['form'] = form
155
157
156 return render(request, 'boards/posting_general.html',
158 return render(request, 'boards/posting_general.html',
157 context)
159 context)
158
160
159
161
160 def thread(request, post_id):
162 def thread(request, post_id):
161 """Get all thread posts"""
163 """Get all thread posts"""
162
164
163 if utils.need_include_captcha(request):
165 if utils.need_include_captcha(request):
164 postFormClass = PostCaptchaForm
166 postFormClass = PostCaptchaForm
165 kwargs = {'request': request}
167 kwargs = {'request': request}
166 else:
168 else:
167 postFormClass = PostForm
169 postFormClass = PostForm
168 kwargs = {}
170 kwargs = {}
169
171
170 if request.method == 'POST':
172 if request.method == 'POST':
171 form = postFormClass(request.POST, request.FILES,
173 form = postFormClass(request.POST, request.FILES,
172 error_class=PlainErrorList, **kwargs)
174 error_class=PlainErrorList, **kwargs)
173 form.session = request.session
175 form.session = request.session
174
176
175 if form.is_valid():
177 if form.is_valid():
176 return _new_post(request, form, post_id)
178 return _new_post(request, form, post_id)
177 if form.need_to_ban:
179 if form.need_to_ban:
178 # Ban user because he is suspected to be a bot
180 # Ban user because he is suspected to be a bot
179 _ban_current_user(request)
181 _ban_current_user(request)
180 else:
182 else:
181 form = postFormClass(error_class=PlainErrorList, **kwargs)
183 form = postFormClass(error_class=PlainErrorList, **kwargs)
182
184
183 posts = Post.objects.get_thread(post_id)
185 posts = Post.objects.get_thread(post_id)
184
186
185 context = _init_default_context(request)
187 context = _init_default_context(request)
186
188
187 context['posts'] = posts
189 context['posts'] = posts
188 context['form'] = form
190 context['form'] = form
189 context['bumpable'] = posts[0].can_bump()
191 context['bumpable'] = posts[0].can_bump()
190 if context['bumpable']:
192 if context['bumpable']:
191 context['posts_left'] = neboard.settings.MAX_POSTS_PER_THREAD - len(
193 context['posts_left'] = neboard.settings.MAX_POSTS_PER_THREAD - len(
192 posts)
194 posts)
193 context['bumplimit_progress'] = str(
195 context['bumplimit_progress'] = str(
194 float(context['posts_left']) /
196 float(context['posts_left']) /
195 neboard.settings.MAX_POSTS_PER_THREAD * 100)
197 neboard.settings.MAX_POSTS_PER_THREAD * 100)
196
198
197 return render(request, 'boards/thread.html', context)
199 return render(request, 'boards/thread.html', context)
198
200
199
201
200 def login(request):
202 def login(request):
201 """Log in with user id"""
203 """Log in with user id"""
202
204
203 context = _init_default_context(request)
205 context = _init_default_context(request)
204
206
205 if request.method == 'POST':
207 if request.method == 'POST':
206 form = LoginForm(request.POST, request.FILES,
208 form = LoginForm(request.POST, request.FILES,
207 error_class=PlainErrorList)
209 error_class=PlainErrorList)
208 form.session = request.session
210 form.session = request.session
209
211
210 if form.is_valid():
212 if form.is_valid():
211 user = User.objects.get(user_id=form.cleaned_data['user_id'])
213 user = User.objects.get(user_id=form.cleaned_data['user_id'])
212 request.session['user_id'] = user.id
214 request.session['user_id'] = user.id
213 return redirect(index)
215 return redirect(index)
214
216
215 else:
217 else:
216 form = LoginForm()
218 form = LoginForm()
217
219
218 context['form'] = form
220 context['form'] = form
219
221
220 return render(request, 'boards/login.html', context)
222 return render(request, 'boards/login.html', context)
221
223
222
224
223 def settings(request):
225 def settings(request):
224 """User's settings"""
226 """User's settings"""
225
227
226 context = _init_default_context(request)
228 context = _init_default_context(request)
227 user = _get_user(request)
229 user = _get_user(request)
228 is_moderator = user.is_moderator()
230 is_moderator = user.is_moderator()
229
231
230 if request.method == 'POST':
232 if request.method == 'POST':
231 with transaction.commit_on_success():
233 with transaction.commit_on_success():
232 if is_moderator:
234 if is_moderator:
233 form = ModeratorSettingsForm(request.POST,
235 form = ModeratorSettingsForm(request.POST,
234 error_class=PlainErrorList)
236 error_class=PlainErrorList)
235 else:
237 else:
236 form = SettingsForm(request.POST, error_class=PlainErrorList)
238 form = SettingsForm(request.POST, error_class=PlainErrorList)
237
239
238 if form.is_valid():
240 if form.is_valid():
239 selected_theme = form.cleaned_data['theme']
241 selected_theme = form.cleaned_data['theme']
240
242
241 user.save_setting('theme', selected_theme)
243 user.save_setting('theme', selected_theme)
242
244
243 if is_moderator:
245 if is_moderator:
244 moderate = form.cleaned_data['moderate']
246 moderate = form.cleaned_data['moderate']
245 user.save_setting(SETTING_MODERATE, moderate)
247 user.save_setting(SETTING_MODERATE, moderate)
246
248
247 return redirect(settings)
249 return redirect(settings)
248 else:
250 else:
249 selected_theme = _get_theme(request)
251 selected_theme = _get_theme(request)
250
252
251 if is_moderator:
253 if is_moderator:
252 form = ModeratorSettingsForm(initial={'theme': selected_theme,
254 form = ModeratorSettingsForm(initial={'theme': selected_theme,
253 'moderate': context['moderator']},
255 'moderate': context['moderator']},
254 error_class=PlainErrorList)
256 error_class=PlainErrorList)
255 else:
257 else:
256 form = SettingsForm(initial={'theme': selected_theme},
258 form = SettingsForm(initial={'theme': selected_theme},
257 error_class=PlainErrorList)
259 error_class=PlainErrorList)
258
260
259 context['form'] = form
261 context['form'] = form
260
262
261 return render(request, 'boards/settings.html', context)
263 return render(request, 'boards/settings.html', context)
262
264
263
265
264 def all_tags(request):
266 def all_tags(request):
265 """All tags list"""
267 """All tags list"""
266
268
267 context = _init_default_context(request)
269 context = _init_default_context(request)
268 context['all_tags'] = Tag.objects.get_not_empty_tags()
270 context['all_tags'] = Tag.objects.get_not_empty_tags()
269
271
270 return render(request, 'boards/tags.html', context)
272 return render(request, 'boards/tags.html', context)
271
273
272
274
273 def jump_to_post(request, post_id):
275 def jump_to_post(request, post_id):
274 """Determine thread in which the requested post is and open it's page"""
276 """Determine thread in which the requested post is and open it's page"""
275
277
276 post = get_object_or_404(Post, id=post_id)
278 post = get_object_or_404(Post, id=post_id)
277
279
278 if not post.thread:
280 if not post.thread:
279 return redirect(thread, post_id=post.id)
281 return redirect(thread, post_id=post.id)
280 else:
282 else:
281 return redirect(reverse(thread, kwargs={'post_id': post.thread.id})
283 return redirect(reverse(thread, kwargs={'post_id': post.thread.id})
282 + '#' + str(post.id))
284 + '#' + str(post.id))
283
285
284
286
285 def authors(request):
287 def authors(request):
286 """Show authors list"""
288 """Show authors list"""
287
289
288 context = _init_default_context(request)
290 context = _init_default_context(request)
289 context['authors'] = boards.authors.authors
291 context['authors'] = boards.authors.authors
290
292
291 return render(request, 'boards/authors.html', context)
293 return render(request, 'boards/authors.html', context)
292
294
293
295
294 @transaction.commit_on_success
296 @transaction.commit_on_success
295 def delete(request, post_id):
297 def delete(request, post_id):
296 """Delete post"""
298 """Delete post"""
297
299
298 user = _get_user(request)
300 user = _get_user(request)
299 post = get_object_or_404(Post, id=post_id)
301 post = get_object_or_404(Post, id=post_id)
300
302
301 if user.is_moderator():
303 if user.is_moderator():
302 # TODO Show confirmation page before deletion
304 # TODO Show confirmation page before deletion
303 Post.objects.delete_post(post)
305 Post.objects.delete_post(post)
304
306
305 if not post.thread:
307 if not post.thread:
306 return _redirect_to_next(request)
308 return _redirect_to_next(request)
307 else:
309 else:
308 return redirect(thread, post_id=post.thread.id)
310 return redirect(thread, post_id=post.thread.id)
309
311
310
312
311 @transaction.commit_on_success
313 @transaction.commit_on_success
312 def ban(request, post_id):
314 def ban(request, post_id):
313 """Ban user"""
315 """Ban user"""
314
316
315 user = _get_user(request)
317 user = _get_user(request)
316 post = get_object_or_404(Post, id=post_id)
318 post = get_object_or_404(Post, id=post_id)
317
319
318 if user.is_moderator():
320 if user.is_moderator():
319 # TODO Show confirmation page before ban
321 # TODO Show confirmation page before ban
320 Ban.objects.get_or_create(ip=post.poster_ip)
322 ban, created = Ban.objects.get_or_create(ip=post.poster_ip)
323 if created:
324 ban.reason = 'Banned for post ' + str(post_id)
325 ban.save()
321
326
322 return _redirect_to_next(request)
327 return _redirect_to_next(request)
323
328
324
329
325 def you_are_banned(request):
330 def you_are_banned(request):
326 """Show the page that notifies that user is banned"""
331 """Show the page that notifies that user is banned"""
327
332
328 context = _init_default_context(request)
333 context = _init_default_context(request)
334
335 ban = get_object_or_404(Ban, ip=utils.get_client_ip(request))
336 context['ban_reason'] = ban.reason
329 return render(request, 'boards/staticpages/banned.html', context)
337 return render(request, 'boards/staticpages/banned.html', context)
330
338
331
339
332 def page_404(request):
340 def page_404(request):
333 """Show page 404 (not found error)"""
341 """Show page 404 (not found error)"""
334
342
335 context = _init_default_context(request)
343 context = _init_default_context(request)
336 return render(request, 'boards/404.html', context)
344 return render(request, 'boards/404.html', context)
337
345
338
346
339 @transaction.commit_on_success
347 @transaction.commit_on_success
340 def tag_subscribe(request, tag_name):
348 def tag_subscribe(request, tag_name):
341 """Add tag to favorites"""
349 """Add tag to favorites"""
342
350
343 user = _get_user(request)
351 user = _get_user(request)
344 tag = get_object_or_404(Tag, name=tag_name)
352 tag = get_object_or_404(Tag, name=tag_name)
345
353
346 if not tag in user.fav_tags.all():
354 if not tag in user.fav_tags.all():
347 user.add_tag(tag)
355 user.add_tag(tag)
348
356
349 return _redirect_to_next(request)
357 return _redirect_to_next(request)
350
358
351
359
352 @transaction.commit_on_success
360 @transaction.commit_on_success
353 def tag_unsubscribe(request, tag_name):
361 def tag_unsubscribe(request, tag_name):
354 """Remove tag from favorites"""
362 """Remove tag from favorites"""
355
363
356 user = _get_user(request)
364 user = _get_user(request)
357 tag = get_object_or_404(Tag, name=tag_name)
365 tag = get_object_or_404(Tag, name=tag_name)
358
366
359 if tag in user.fav_tags.all():
367 if tag in user.fav_tags.all():
360 user.remove_tag(tag)
368 user.remove_tag(tag)
361
369
362 return _redirect_to_next(request)
370 return _redirect_to_next(request)
363
371
364
372
365 def static_page(request, name):
373 def static_page(request, name):
366 """Show a static page that needs only tags list and a CSS"""
374 """Show a static page that needs only tags list and a CSS"""
367
375
368 context = _init_default_context(request)
376 context = _init_default_context(request)
369 return render(request, 'boards/staticpages/' + name + '.html', context)
377 return render(request, 'boards/staticpages/' + name + '.html', context)
370
378
371
379
372 def api_get_post(request, post_id):
380 def api_get_post(request, post_id):
373 """
381 """
374 Get the JSON of a post. This can be
382 Get the JSON of a post. This can be
375 used as and API for external clients.
383 used as and API for external clients.
376 """
384 """
377
385
378 post = get_object_or_404(Post, id=post_id)
386 post = get_object_or_404(Post, id=post_id)
379
387
380 json = serializers.serialize("json", [post], fields=(
388 json = serializers.serialize("json", [post], fields=(
381 "pub_time", "_text_rendered", "title", "text", "image",
389 "pub_time", "_text_rendered", "title", "text", "image",
382 "image_width", "image_height", "replies", "tags"
390 "image_width", "image_height", "replies", "tags"
383 ))
391 ))
384
392
385 return HttpResponse(content=json)
393 return HttpResponse(content=json)
386
394
387
395
388 def get_post(request, post_id):
396 def get_post(request, post_id):
389 """Get the html of a post. Used for popups."""
397 """Get the html of a post. Used for popups."""
390
398
391 post = get_object_or_404(Post, id=post_id)
399 post = get_object_or_404(Post, id=post_id)
392
400
393 context = RequestContext(request)
401 context = RequestContext(request)
394 context["post"] = post
402 context["post"] = post
395
403
396 return render(request, 'boards/post.html', context)
404 return render(request, 'boards/post.html', context)
397
405
398
406
399 def _get_theme(request, user=None):
407 def _get_theme(request, user=None):
400 """Get user's CSS theme"""
408 """Get user's CSS theme"""
401
409
402 if not user:
410 if not user:
403 user = _get_user(request)
411 user = _get_user(request)
404 theme = user.get_setting('theme')
412 theme = user.get_setting('theme')
405 if not theme:
413 if not theme:
406 theme = neboard.settings.DEFAULT_THEME
414 theme = neboard.settings.DEFAULT_THEME
407
415
408 return theme
416 return theme
409
417
410
418
411 def _init_default_context(request):
419 def _init_default_context(request):
412 """Create context with default values that are used in most views"""
420 """Create context with default values that are used in most views"""
413
421
414 context = RequestContext(request)
422 context = RequestContext(request)
415
423
416 user = _get_user(request)
424 user = _get_user(request)
417 context['user'] = user
425 context['user'] = user
418 context['tags'] = user.get_sorted_fav_tags()
426 context['tags'] = user.get_sorted_fav_tags()
419
427
420 theme = _get_theme(request, user)
428 theme = _get_theme(request, user)
421 context['theme'] = theme
429 context['theme'] = theme
422 context['theme_css'] = 'css/' + theme + '/base_page.css'
430 context['theme_css'] = 'css/' + theme + '/base_page.css'
423
431
424 # This shows the moderator panel
432 # This shows the moderator panel
425 moderate = user.get_setting(SETTING_MODERATE)
433 moderate = user.get_setting(SETTING_MODERATE)
426 if moderate == 'True':
434 if moderate == 'True':
427 context['moderator'] = user.is_moderator()
435 context['moderator'] = user.is_moderator()
428 else:
436 else:
429 context['moderator'] = False
437 context['moderator'] = False
430
438
431 return context
439 return context
432
440
433
441
434 def _get_user(request):
442 def _get_user(request):
435 """
443 """
436 Get current user from the session. If the user does not exist, create
444 Get current user from the session. If the user does not exist, create
437 a new one.
445 a new one.
438 """
446 """
439
447
440 session = request.session
448 session = request.session
441 if not 'user_id' in session:
449 if not 'user_id' in session:
442 request.session.save()
450 request.session.save()
443
451
444 md5 = hashlib.md5()
452 md5 = hashlib.md5()
445 md5.update(session.session_key)
453 md5.update(session.session_key)
446 new_id = md5.hexdigest()
454 new_id = md5.hexdigest()
447
455
448 time_now = timezone.now()
456 time_now = timezone.now()
449 user = User.objects.create(user_id=new_id, rank=RANK_USER,
457 user = User.objects.create(user_id=new_id, rank=RANK_USER,
450 registration_time=time_now)
458 registration_time=time_now)
451
459
452 session['user_id'] = user.id
460 session['user_id'] = user.id
453 else:
461 else:
454 user = User.objects.get(id=session['user_id'])
462 user = User.objects.get(id=session['user_id'])
455
463
456 return user
464 return user
457
465
458
466
459 def _redirect_to_next(request):
467 def _redirect_to_next(request):
460 """
468 """
461 If a 'next' parameter was specified, redirect to the next page. This is
469 If a 'next' parameter was specified, redirect to the next page. This is
462 used when the user is required to return to some page after the current
470 used when the user is required to return to some page after the current
463 view has finished its work.
471 view has finished its work.
464 """
472 """
465
473
466 if 'next' in request.GET:
474 if 'next' in request.GET:
467 next_page = request.GET['next']
475 next_page = request.GET['next']
468 return HttpResponseRedirect(next_page)
476 return HttpResponseRedirect(next_page)
469 else:
477 else:
470 return redirect(index)
478 return redirect(index)
471
479
472
480
481 @transaction.commit_on_success
473 def _ban_current_user(request):
482 def _ban_current_user(request):
474 """Add current user to the IP ban list"""
483 """Add current user to the IP ban list"""
475
484
476 ip = utils.get_client_ip(request)
485 ip = utils.get_client_ip(request)
477 Ban.objects.get_or_create(ip=ip)
486 ban, created = Ban.objects.get_or_create(ip=ip)
487 if created:
488 ban.can_read = False
489 ban.reason = BAN_REASON_SPAM
490 ban.save()
478
491
479
492
480 def _remove_invalid_links(text):
493 def _remove_invalid_links(text):
481 """
494 """
482 Replace invalid links in posts so that they won't be parsed.
495 Replace invalid links in posts so that they won't be parsed.
483 Invalid links are links to non-existent posts
496 Invalid links are links to non-existent posts
484 """
497 """
485
498
486 for reply_number in re.finditer(REGEX_REPLY, text):
499 for reply_number in re.finditer(REGEX_REPLY, text):
487 post_id = reply_number.group(1)
500 post_id = reply_number.group(1)
488 post = Post.objects.filter(id=post_id)
501 post = Post.objects.filter(id=post_id)
489 if not post.exists():
502 if not post.exists():
490 text = string.replace(text, '>>' + id, id)
503 text = string.replace(text, '>>' + id, id)
491
504
492 return text
505 return text
@@ -1,28 +1,28 b''
1 = Features =
1 = Features =
2 [DONE] Connecting tags to each other
2 [DONE] Connecting tags to each other
3 [DONE] Connect posts to the replies (in messages), get rid of the JS reply map
3 [DONE] Connect posts to the replies (in messages), get rid of the JS reply map
4 [DONE] Better django admin pages to simplify admin operations
4 [DONE] Better django admin pages to simplify admin operations
5 [DONE] Regen script to update all posts
5 [DONE] Regen script to update all posts
6 [DONE] Remove jump links from refmaps
6 [DONE] Remove jump links from refmaps
7 [DONE] Ban reasons. Split bans into 2 types "read-only" and "read
8 denied". Use second only for autoban for spam
7
9
8 [NOT STARTED] Tree view (JS)
10 [NOT STARTED] Tree view (JS)
9 [NOT STARTED] Adding tags to images filename
11 [NOT STARTED] Adding tags to images filename
10 [NOT STARTED] Federative network for s2s communication
12 [NOT STARTED] Federative network for s2s communication
11 [NOT STARTED] XMPP gate
13 [NOT STARTED] XMPP gate
12 [NOT STARTED] Bitmessage gate
14 [NOT STARTED] Bitmessage gate
13 [NOT STARTED] Notification engine
15 [NOT STARTED] Notification engine
14 [NOT STARTED] Javascript disabling engine
16 [NOT STARTED] Javascript disabling engine
15 [NOT STARTED] Thread autoupdate (JS + API)
17 [NOT STARTED] Thread autoupdate (JS + API)
16 [NOT STARTED] Group tags by first letter in all tags list
18 [NOT STARTED] Group tags by first letter in all tags list
17 [NOT STARTED] Show board speed in the lower panel (posts per day)
19 [NOT STARTED] Show board speed in the lower panel (posts per day)
18 [NOT STARTED] Character counter in the post field
20 [NOT STARTED] Character counter in the post field
19 [NOT STARTED] Use transactions in tests
21 [NOT STARTED] Use transactions in tests
20 [NOT STARTED] Clean up tests and make them run ALWAYS
22 [NOT STARTED] Clean up tests and make them run ALWAYS
21 [NOT STARTED] Save image thumbnails size to the separate field
23 [NOT STARTED] Save image thumbnails size to the separate field
22 [NOT STARTED] Whitelist functionality. Permin autoban of an address
24 [NOT STARTED] Whitelist functionality. Permin autoban of an address
23 [NOT STARTED] Ban reasons. Split bans into 2 types "read-only" and "read
24 denied". Use second only for autoban for spam
25
25
26 = Bugs =
26 = Bugs =
27 [DONE] Fix bug with creating threads from tag view
27 [DONE] Fix bug with creating threads from tag view
28 [DONE] Quote characters within quote causes quote parsing to fail
28 [DONE] Quote characters within quote causes quote parsing to fail
General Comments 0
You need to be logged in to leave comments. Login now