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