##// END OF EJS Templates
Added autoban by the hidden field (to ban spammers and prevent them from trying to post anything).
neko259 -
r271:ac659dae default
parent child Browse files
Show More
@@ -1,252 +1,254 b''
1 1 import re
2 2 from captcha.fields import CaptchaField
3 3 from django import forms
4 4 from django.forms.util import ErrorList
5 5 from django.utils.translation import ugettext_lazy as _
6 6 import time
7 7 from boards.models import TITLE_MAX_LENGTH, User
8 8 from neboard import settings
9 9 from boards import utils
10 10
11 11 LAST_POST_TIME = "last_post_time"
12 12 LAST_LOGIN_TIME = "last_login_time"
13 13
14 14 LOGIN_DELAY = 60 * 60
15 15
16 16 MAX_TEXT_LENGTH = 30000
17 17 MAX_IMAGE_SIZE = 8 * 1024 * 1024
18 18
19 19
20 20 class PlainErrorList(ErrorList):
21 21 def __unicode__(self):
22 22 return self.as_text()
23 23
24 24 def as_text(self):
25 25 return ''.join([u'(!) %s ' % e for e in self])
26 26
27 27
28 28 class NeboardForm(forms.Form):
29 29
30 30 def as_p(self):
31 31 "Returns this form rendered as HTML <p>s."
32 32 return self._html_output(
33 33 normal_row='<div class="form-row">'
34 34 '<div class="form-label">'
35 35 '%(label)s'
36 36 '</div>'
37 37 '<div class="form-input">'
38 38 '%(field)s'
39 39 '</div>'
40 40 '%(help_text)s'
41 41 '</div>',
42 42 error_row='<div class="form-errors">%s</div>',
43 43 row_ender='</p>',
44 44 help_text_html=' <span class="helptext">%s</span>',
45 45 errors_on_separate_row=True)
46 46
47 47
48 48 class PostForm(NeboardForm):
49 49
50 50 title = forms.CharField(max_length=TITLE_MAX_LENGTH, required=False,
51 51 label=_('Title'))
52 52 text = forms.CharField(widget=forms.Textarea, required=False,
53 53 label=_('Text'))
54 54 image = forms.ImageField(required=False, label=_('Image'))
55 55
56 56 # This field is for spam prevention only
57 57 email = forms.CharField(max_length=100, required=False, label=_('e-mail'),
58 58 widget=forms.TextInput(attrs={
59 59 'class': 'form-email'}))
60 60
61 61 session = None
62 need_to_ban = False
62 63
63 64 def clean_title(self):
64 65 title = self.cleaned_data['title']
65 66 if title:
66 67 if len(title) > TITLE_MAX_LENGTH:
67 68 raise forms.ValidationError(_('Title must have less than %s '
68 69 'characters') %
69 70 str(TITLE_MAX_LENGTH))
70 71 return title
71 72
72 73 def clean_text(self):
73 74 text = self.cleaned_data['text']
74 75 if text:
75 76 if len(text) > MAX_TEXT_LENGTH:
76 77 raise forms.ValidationError(_('Text must have less than %s '
77 78 'characters') %
78 79 str(MAX_TEXT_LENGTH))
79 80 return text
80 81
81 82 def clean_image(self):
82 83 image = self.cleaned_data['image']
83 84 if image:
84 85 if image._size > MAX_IMAGE_SIZE:
85 86 raise forms.ValidationError(_('Image must be less than %s '
86 87 'bytes') % str(MAX_IMAGE_SIZE))
87 88 return image
88 89
89 90 def clean(self):
90 91 cleaned_data = super(PostForm, self).clean()
91 92
92 93 if not self.session:
93 94 raise forms.ValidationError('Humans have sessions')
94 95
95 96 if cleaned_data['email']:
97 self.need_to_ban = True
96 98 raise forms.ValidationError('A human cannot enter a hidden field')
97 99
98 100 if not self.errors:
99 101 self._clean_text_image()
100 102
101 103 if not self.errors and self.session:
102 104 self._validate_posting_speed()
103 105
104 106 return cleaned_data
105 107
106 108 def _clean_text_image(self):
107 109 text = self.cleaned_data.get('text')
108 110 image = self.cleaned_data.get('image')
109 111
110 112 if (not text) and (not image):
111 113 error_message = _('Either text or image must be entered.')
112 114 self._errors['text'] = self.error_class([error_message])
113 115
114 116 def _validate_posting_speed(self):
115 117 can_post = True
116 118
117 119 if LAST_POST_TIME in self.session:
118 120 now = time.time()
119 121 last_post_time = self.session[LAST_POST_TIME]
120 122
121 123 current_delay = int(now - last_post_time)
122 124
123 125 if current_delay < settings.POSTING_DELAY:
124 126 error_message = _('Wait %s seconds after last posting') % str(
125 127 settings.POSTING_DELAY - current_delay)
126 128 self._errors['text'] = self.error_class([error_message])
127 129
128 130 can_post = False
129 131
130 132 if can_post:
131 133 self.session[LAST_POST_TIME] = time.time()
132 134
133 135
134 136 class ThreadForm(PostForm):
135 137
136 138 regex_tags = re.compile(ur'^[\w\s\d]+$', re.UNICODE)
137 139
138 140 tags = forms.CharField(max_length=100, label=_('Tags'))
139 141
140 142 def clean_tags(self):
141 143 tags = self.cleaned_data['tags']
142 144
143 145 if tags:
144 146 if not self.regex_tags.match(tags):
145 147 raise forms.ValidationError(
146 148 _('Inappropriate characters in tags.'))
147 149
148 150 return tags
149 151
150 152 def clean(self):
151 153 cleaned_data = super(ThreadForm, self).clean()
152 154
153 155 return cleaned_data
154 156
155 157
156 158 class PostCaptchaForm(PostForm):
157 159 captcha = CaptchaField()
158 160
159 161 def __init__(self, *args, **kwargs):
160 162 self.request = kwargs['request']
161 163 del kwargs['request']
162 164
163 165 super(PostCaptchaForm, self).__init__(*args, **kwargs)
164 166
165 167 def clean(self):
166 168 cleaned_data = super(PostCaptchaForm, self).clean()
167 169
168 170 success = self.is_valid()
169 171 utils.update_captcha_access(self.request, success)
170 172
171 173 if success:
172 174 return cleaned_data
173 175 else:
174 176 raise forms.ValidationError(_("Captcha validation failed"))
175 177
176 178
177 179 class ThreadCaptchaForm(ThreadForm):
178 180 captcha = CaptchaField()
179 181
180 182 def __init__(self, *args, **kwargs):
181 183 self.request = kwargs['request']
182 184 del kwargs['request']
183 185
184 186 super(ThreadCaptchaForm, self).__init__(*args, **kwargs)
185 187
186 188 def clean(self):
187 189 cleaned_data = super(ThreadCaptchaForm, self).clean()
188 190
189 191 success = self.is_valid()
190 192 utils.update_captcha_access(self.request, success)
191 193
192 194 if success:
193 195 return cleaned_data
194 196 else:
195 197 raise forms.ValidationError(_("Captcha validation failed"))
196 198
197 199
198 200 class SettingsForm(NeboardForm):
199 201
200 202 theme = forms.ChoiceField(choices=settings.THEMES,
201 203 label=_('Theme'))
202 204
203 205
204 206 class ModeratorSettingsForm(SettingsForm):
205 207
206 208 moderate = forms.BooleanField(required=False, label=_('Enable moderation '
207 209 'panel'))
208 210
209 211
210 212 class LoginForm(NeboardForm):
211 213
212 214 user_id = forms.CharField()
213 215
214 216 session = None
215 217
216 218 def clean_user_id(self):
217 219 user_id = self.cleaned_data['user_id']
218 220 if user_id:
219 221 users = User.objects.filter(user_id=user_id)
220 222 if len(users) == 0:
221 223 raise forms.ValidationError(_('No such user found'))
222 224
223 225 return user_id
224 226
225 227 def _validate_login_speed(self):
226 228 can_post = True
227 229
228 230 if LAST_LOGIN_TIME in self.session:
229 231 now = time.time()
230 232 last_login_time = self.session[LAST_LOGIN_TIME]
231 233
232 234 current_delay = int(now - last_login_time)
233 235
234 236 if current_delay < LOGIN_DELAY:
235 237 error_message = _('Wait %s minutes after last login') % str(
236 238 (LOGIN_DELAY - current_delay) / 60)
237 239 self._errors['user_id'] = self.error_class([error_message])
238 240
239 241 can_post = False
240 242
241 243 if can_post:
242 244 self.session[LAST_LOGIN_TIME] = time.time()
243 245
244 246 def clean(self):
245 247 if not self.session:
246 248 raise forms.ValidationError('Humans have sessions')
247 249
248 250 self._validate_login_speed()
249 251
250 252 cleaned_data = super(LoginForm, self).clean()
251 253
252 return cleaned_data No newline at end of file
254 return cleaned_data
@@ -1,419 +1,448 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
11 11 from boards import forms
12 12 import boards
13 13 from boards import utils
14 14 from boards.forms import ThreadForm, PostForm, SettingsForm, PlainErrorList, \
15 15 ThreadCaptchaForm, PostCaptchaForm, LoginForm, ModeratorSettingsForm
16 16
17 17 from boards.models import Post, Tag, Ban, User, RANK_USER, SETTING_MODERATE
18 18 from boards import authors
19 19 from boards.utils import get_client_ip
20 20 import neboard
21 21
22 22
23 23 def index(request, page=0):
24 24 context = _init_default_context(request)
25 25
26 26 if utils.need_include_captcha(request):
27 27 threadFormClass = ThreadCaptchaForm
28 28 kwargs = {'request': request}
29 29 else:
30 30 threadFormClass = ThreadForm
31 31 kwargs = {}
32 32
33 33 if request.method == 'POST':
34 34 form = threadFormClass(request.POST, request.FILES,
35 35 error_class=PlainErrorList, **kwargs)
36 36 form.session = request.session
37 37
38 38 if form.is_valid():
39 39 return _new_post(request, form)
40 if form.need_to_ban:
41 # Ban user because he is suspected to be a bot
42 _ban_current_user(request)
40 43 else:
41 44 form = threadFormClass(error_class=PlainErrorList, **kwargs)
42 45
43 46 threads = []
44 47 for thread in Post.objects.get_threads(page=int(page)):
45 48 threads.append({'thread': thread,
46 49 'bumpable': thread.can_bump()})
47 50
48 51 context['threads'] = None if len(threads) == 0 else threads
49 52 context['form'] = form
50 53 context['pages'] = range(Post.objects.get_thread_page_count())
51 54
52 55 return render(request, 'boards/posting_general.html',
53 56 context)
54 57
55 58
56 59 def _new_post(request, form, thread_id=boards.models.NO_PARENT):
57 60 """Add a new post (in thread or as a reply)."""
58 61
59 62 ip = get_client_ip(request)
60 63 is_banned = Ban.objects.filter(ip=ip).exists()
61 64
62 65 if is_banned:
63 66 return redirect(you_are_banned)
64 67
65 68 data = form.cleaned_data
66 69
67 70 title = data['title']
68 71 text = data['text']
69 72
70 73 if 'image' in data.keys():
71 74 image = data['image']
72 75 else:
73 76 image = None
74 77
75 78 tags = []
76 79
77 80 new_thread = thread_id == boards.models.NO_PARENT
78 81 if new_thread:
79 82 tag_strings = data['tags']
80 83
81 84 if tag_strings:
82 85 tag_strings = tag_strings.split(' ')
83 86 for tag_name in tag_strings:
84 87 tag_name = string.lower(tag_name.strip())
85 88 if len(tag_name) > 0:
86 89 tag, created = Tag.objects.get_or_create(name=tag_name)
87 90 tags.append(tag)
88 91
89 92 op = None if thread_id == boards.models.NO_PARENT else \
90 93 get_object_or_404(Post, id=thread_id)
91 94 post = Post.objects.create_post(title=title, text=text, ip=ip,
92 95 thread=op, image=image,
93 96 tags=tags, user=_get_user(request))
94 97
95 98 thread_to_show = (post.id if new_thread else thread_id)
96 99
97 100 if new_thread:
98 101 return redirect(thread, post_id=thread_to_show)
99 102 else:
100 103 return redirect(reverse(thread, kwargs={'post_id': thread_to_show}) +
101 104 '#' + str(post.id))
102 105
103 106
104 107 def tag(request, tag_name, page=0):
105 """Get all tag threads (posts without a parent)."""
108 """
109 Get all tag threads. Threads are split in pages, so some page is
110 requested. Default page is 0.
111 """
106 112
107 113 tag = get_object_or_404(Tag, name=tag_name)
108 114 threads = []
109 115 for thread in Post.objects.get_threads(tag=tag, page=int(page)):
110 116 threads.append({'thread': thread,
111 117 'bumpable': thread.can_bump()})
112 118
113 119 if request.method == 'POST':
114 120 form = ThreadForm(request.POST, request.FILES,
115 121 error_class=PlainErrorList)
116 122 if form.is_valid():
117 123 return _new_post(request, form)
124 if form.need_to_ban:
125 # Ban user because he is suspected to be a bot
126 _ban_current_user(request)
118 127 else:
119 128 form = forms.ThreadForm(initial={'tags': tag_name},
120 129 error_class=PlainErrorList)
121 130
122 131 context = _init_default_context(request)
123 132 context['threads'] = None if len(threads) == 0 else threads
124 133 context['tag'] = tag
125 134 context['pages'] = range(Post.objects.get_thread_page_count(tag=tag))
126 135
127 136 context['form'] = form
128 137
129 138 return render(request, 'boards/posting_general.html',
130 139 context)
131 140
132 141
133 142 def thread(request, post_id):
134 143 """Get all thread posts"""
135 144
136 145 if utils.need_include_captcha(request):
137 146 postFormClass = PostCaptchaForm
138 147 kwargs = {'request': request}
139 148 else:
140 149 postFormClass = PostForm
141 150 kwargs = {}
142 151
143 152 if request.method == 'POST':
144 153 form = postFormClass(request.POST, request.FILES,
145 154 error_class=PlainErrorList, **kwargs)
146 155 form.session = request.session
147 156
148 157 if form.is_valid():
149 158 return _new_post(request, form, post_id)
159 if form.need_to_ban:
160 # Ban user because he is suspected to be a bot
161 _ban_current_user(request)
150 162 else:
151 163 form = postFormClass(error_class=PlainErrorList, **kwargs)
152 164
153 165 posts = Post.objects.get_thread(post_id)
154 166
155 167 context = _init_default_context(request)
156 168
157 169 context['posts'] = posts
158 170 context['form'] = form
159 171 context['bumpable'] = posts[0].can_bump()
160 172
161 173 return render(request, 'boards/thread.html', context)
162 174
163 175
164 176 def login(request):
165 177 """Log in with user id"""
166 178
167 179 context = _init_default_context(request)
168 180
169 181 if request.method == 'POST':
170 182 form = LoginForm(request.POST, request.FILES,
171 183 error_class=PlainErrorList)
172 184 form.session = request.session
173 185
174 186 if form.is_valid():
175 187 user = User.objects.get(user_id=form.cleaned_data['user_id'])
176 188 request.session['user_id'] = user.id
177 189 return redirect(index)
178 190
179 191 else:
180 192 form = LoginForm()
181 193
182 194 context['form'] = form
183 195
184 196 return render(request, 'boards/login.html', context)
185 197
186 198
187 199 def settings(request):
188 200 """User's settings"""
189 201
190 202 context = _init_default_context(request)
191 203 user = _get_user(request)
192 204 is_moderator = user.is_moderator()
193 205
194 206 if request.method == 'POST':
195 207 if is_moderator:
196 208 form = ModeratorSettingsForm(request.POST,
197 209 error_class=PlainErrorList)
198 210 else:
199 211 form = SettingsForm(request.POST, error_class=PlainErrorList)
200 212
201 213 if form.is_valid():
202 214 selected_theme = form.cleaned_data['theme']
203 215
204 216 user.save_setting('theme', selected_theme)
205 217
206 218 if is_moderator:
207 219 moderate = form.cleaned_data['moderate']
208 220 user.save_setting(SETTING_MODERATE, moderate)
209 221
210 222 return redirect(settings)
211 223 else:
212 224 selected_theme = _get_theme(request)
213 225
214 226 if is_moderator:
215 227 form = ModeratorSettingsForm(initial={'theme': selected_theme,
216 228 'moderate': context['moderator']},
217 229 error_class=PlainErrorList)
218 230 else:
219 231 form = SettingsForm(initial={'theme': selected_theme},
220 232 error_class=PlainErrorList)
221 233
222 234 context['form'] = form
223 235
224 236 return render(request, 'boards/settings.html', context)
225 237
226 238
227 239 def all_tags(request):
228 240 """All tags list"""
229 241
230 242 context = _init_default_context(request)
231 243 context['all_tags'] = Tag.objects.get_not_empty_tags()
232 244
233 245 return render(request, 'boards/tags.html', context)
234 246
235 247
236 248 def jump_to_post(request, post_id):
237 249 """Determine thread in which the requested post is and open it's page"""
238 250
239 251 post = get_object_or_404(Post, id=post_id)
240 252
241 253 if not post.thread:
242 254 return redirect(thread, post_id=post.id)
243 255 else:
244 256 return redirect(reverse(thread, kwargs={'post_id': post.thread.id})
245 257 + '#' + str(post.id))
246 258
247 259
248 260 def authors(request):
249 261 """Show authors list"""
250 262
251 263 context = _init_default_context(request)
252 264 context['authors'] = boards.authors.authors
253 265
254 266 return render(request, 'boards/authors.html', context)
255 267
256 268
257 269 def delete(request, post_id):
258 270 """Delete post"""
259 271
260 272 user = _get_user(request)
261 273 post = get_object_or_404(Post, id=post_id)
262 274
263 275 if user.is_moderator():
264 276 # TODO Show confirmation page before deletion
265 277 Post.objects.delete_post(post)
266 278
267 279 if not post.thread:
268 280 return _redirect_to_next(request)
269 281 else:
270 282 return redirect(thread, post_id=post.thread.id)
271 283
272 284
273 285 def ban(request, post_id):
274 286 """Ban user"""
275 287
276 288 user = _get_user(request)
277 289 post = get_object_or_404(Post, id=post_id)
278 290
279 291 if user.is_moderator():
280 292 # TODO Show confirmation page before ban
281 293 Ban.objects.get_or_create(ip=post.poster_ip)
282 294
283 295 return _redirect_to_next(request)
284 296
285 297
286 298 def you_are_banned(request):
287 299 """Show the page that notifies that user is banned"""
288 300
289 301 context = _init_default_context(request)
290 302 return render(request, 'boards/staticpages/banned.html', context)
291 303
292 304
293 305 def page_404(request):
294 306 """Show page 404 (not found error)"""
295 307
296 308 context = _init_default_context(request)
297 309 return render(request, 'boards/404.html', context)
298 310
299 311
300 312 def tag_subscribe(request, tag_name):
301 313 """Add tag to favorites"""
302 314
303 315 user = _get_user(request)
304 316 tag = get_object_or_404(Tag, name=tag_name)
305 317
306 318 if not tag in user.fav_tags.all():
307 319 user.fav_tags.add(tag)
308 320
309 321 return _redirect_to_next(request)
310 322
311 323
312 324 def tag_unsubscribe(request, tag_name):
313 325 """Remove tag from favorites"""
314 326
315 327 user = _get_user(request)
316 328 tag = get_object_or_404(Tag, name=tag_name)
317 329
318 330 if tag in user.fav_tags.all():
319 331 user.fav_tags.remove(tag)
320 332
321 333 return _redirect_to_next(request)
322 334
323 335
324 336 def static_page(request, name):
325 337 """Show a static page that needs only tags list and a CSS"""
326 338
327 339 context = _init_default_context(request)
328 340 return render(request, 'boards/staticpages/' + name + '.html', context)
329 341
330 342
331 343 def api_get_post(request, post_id):
332 344 """
333 345 Get the JSON of a post. This can be
334 346 used as and API for external clients.
335 347 """
336 348
337 349 post = get_object_or_404(Post, id=post_id)
338 350
339 351 json = serializers.serialize("json", [post], fields=(
340 352 "pub_time", "_text_rendered", "title", "text", "image",
341 353 "image_width", "image_height", "replies", "tags"
342 354 ))
343 355
344 356 return HttpResponse(content=json)
345 357
346 358
347 359 def get_post(request, post_id):
348 """ Get the html of a post. Used for popups. """
360 """Get the html of a post. Used for popups."""
349 361
350 362 post = get_object_or_404(Post, id=post_id)
351 363
352 364 context = RequestContext(request)
353 365 context["post"] = post
354 366
355 367 return render(request, 'boards/post.html', context)
356 368
357 369
358 370 def _get_theme(request, user=None):
359 371 """Get user's CSS theme"""
360 372
361 373 if not user:
362 374 user = _get_user(request)
363 375 theme = user.get_setting('theme')
364 376 if not theme:
365 377 theme = neboard.settings.DEFAULT_THEME
366 378
367 379 return theme
368 380
369 381
370 382 def _init_default_context(request):
371 383 """Create context with default values that are used in most views"""
372 384
373 385 context = RequestContext(request)
374 386
375 387 user = _get_user(request)
376 388 context['user'] = user
377 389 context['tags'] = user.get_sorted_fav_tags()
378 390
379 391 theme = _get_theme(request, user)
380 392 context['theme'] = theme
381 393 context['theme_css'] = 'css/' + theme + '/base_page.css'
382 394
395 # This shows the moderator panel
383 396 moderate = user.get_setting(SETTING_MODERATE)
384 397 if moderate == 'True':
385 398 context['moderator'] = user.is_moderator()
386 399 else:
387 400 context['moderator'] = False
388 401
389 402 return context
390 403
391 404
392 405 def _get_user(request):
393 """Get current user from the session"""
406 """
407 Get current user from the session. If the user does not exist, create
408 a new one.
409 """
394 410
395 411 session = request.session
396 412 if not 'user_id' in session:
397 413 request.session.save()
398 414
399 415 md5 = hashlib.md5()
400 416 md5.update(session.session_key)
401 417 new_id = md5.hexdigest()
402 418
403 419 time_now = timezone.now()
404 420 user = User.objects.create(user_id=new_id, rank=RANK_USER,
405 421 registration_time=time_now)
406 422
407 423 session['user_id'] = user.id
408 424 else:
409 425 user = User.objects.get(id=session['user_id'])
410 426
411 427 return user
412 428
413 429
414 430 def _redirect_to_next(request):
431 """
432 If a 'next' parameter was specified, redirect to the next page. This is
433 used when the user is required to return to some page after the current
434 view has finished its work.
435 """
436
415 437 if 'next' in request.GET:
416 438 next_page = request.GET['next']
417 439 return HttpResponseRedirect(next_page)
418 440 else:
419 441 return redirect(index)
442
443
444 def _ban_current_user(request):
445 """Add current user to the IP ban list"""
446
447 ip = utils.get_client_ip(request)
448 Ban.objects.get_or_create(ip=ip)
General Comments 0
You need to be logged in to leave comments. Login now