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