##// END OF EJS Templates
Added anonymous mode in which the board does not save poster IP addresses
neko259 -
r1362:9b48eb30 default
parent child Browse files
Show More
@@ -1,33 +1,34
1 [Version]
1 [Version]
2 Version = 2.10.0 BT
2 Version = 2.10.0 BT
3 SiteName = Neboard DEV
3 SiteName = Neboard DEV
4
4
5 [Cache]
5 [Cache]
6 # Timeout for caching, if cache is used
6 # Timeout for caching, if cache is used
7 CacheTimeout = 600
7 CacheTimeout = 600
8
8
9 [Forms]
9 [Forms]
10 # Max post length in characters
10 # Max post length in characters
11 MaxTextLength = 30000
11 MaxTextLength = 30000
12 MaxFileSize = 8000000
12 MaxFileSize = 8000000
13 LimitPostingSpeed = false
13 LimitPostingSpeed = false
14
14
15 [Messages]
15 [Messages]
16 # Thread bumplimit
16 # Thread bumplimit
17 MaxPostsPerThread = 10
17 MaxPostsPerThread = 10
18 # Old posts will be archived or deleted if this value is reached
18 # Old posts will be archived or deleted if this value is reached
19 MaxThreadCount = 5
19 MaxThreadCount = 5
20 AnonymousMode = false
20
21
21 [View]
22 [View]
22 DefaultTheme = md
23 DefaultTheme = md
23 DefaultImageViewer = simple
24 DefaultImageViewer = simple
24 LastRepliesCount = 3
25 LastRepliesCount = 3
25 ThreadsPerPage = 3
26 ThreadsPerPage = 3
26
27
27 [Storage]
28 [Storage]
28 # Enable archiving threads instead of deletion when the thread limit is reached
29 # Enable archiving threads instead of deletion when the thread limit is reached
29 ArchiveThreads = true
30 ArchiveThreads = true
30
31
31 [External]
32 [External]
32 # Thread update
33 # Thread update
33 WebsocketsEnabled = false
34 WebsocketsEnabled = false
@@ -1,125 +1,126
1 import logging
1 import logging
2
2
3 from datetime import datetime, timedelta, date
3 from datetime import datetime, timedelta, date
4 from datetime import time as dtime
4 from datetime import time as dtime
5
5
6 from django.db import models, transaction
6 from django.db import models, transaction
7 from django.utils import timezone
7 from django.utils import timezone
8
8
9 import boards
9 import boards
10
10
11 from boards.models.user import Ban
11 from boards.models.user import Ban
12 from boards.mdx_neboard import Parser
12 from boards.mdx_neboard import Parser
13 from boards.models import PostImage, Attachment
13 from boards.models import PostImage, Attachment
14 from boards import utils
14 from boards import utils
15
15
16 __author__ = 'neko259'
16 __author__ = 'neko259'
17
17
18 IMAGE_TYPES = (
18 IMAGE_TYPES = (
19 'jpeg',
19 'jpeg',
20 'jpg',
20 'jpg',
21 'png',
21 'png',
22 'bmp',
22 'bmp',
23 'gif',
23 'gif',
24 )
24 )
25
25
26 POSTS_PER_DAY_RANGE = 7
26 POSTS_PER_DAY_RANGE = 7
27 NO_IP = '0.0.0.0'
27 NO_IP = '0.0.0.0'
28
28
29
29
30 class PostManager(models.Manager):
30 class PostManager(models.Manager):
31 @transaction.atomic
31 @transaction.atomic
32 def create_post(self, title: str, text: str, file=None, thread=None,
32 def create_post(self, title: str, text: str, file=None, thread=None,
33 ip=NO_IP, tags: list=None, opening_posts: list=None, tripcode=None):
33 ip=NO_IP, tags: list=None, opening_posts: list=None, tripcode=None):
34 """
34 """
35 Creates new post
35 Creates new post
36 """
36 """
37
37
38 if not utils.is_anonymous_mode():
38 is_banned = Ban.objects.filter(ip=ip).exists()
39 is_banned = Ban.objects.filter(ip=ip).exists()
39
40
40 # TODO Raise specific exception and catch it in the views
41 # TODO Raise specific exception and catch it in the views
41 if is_banned:
42 if is_banned:
42 raise Exception("This user is banned")
43 raise Exception("This user is banned")
43
44
44 if not tags:
45 if not tags:
45 tags = []
46 tags = []
46 if not opening_posts:
47 if not opening_posts:
47 opening_posts = []
48 opening_posts = []
48
49
49 posting_time = timezone.now()
50 posting_time = timezone.now()
50 new_thread = False
51 new_thread = False
51 if not thread:
52 if not thread:
52 thread = boards.models.thread.Thread.objects.create(
53 thread = boards.models.thread.Thread.objects.create(
53 bump_time=posting_time, last_edit_time=posting_time)
54 bump_time=posting_time, last_edit_time=posting_time)
54 list(map(thread.tags.add, tags))
55 list(map(thread.tags.add, tags))
55 boards.models.thread.Thread.objects.process_oldest_threads()
56 boards.models.thread.Thread.objects.process_oldest_threads()
56 new_thread = True
57 new_thread = True
57
58
58 pre_text = Parser().preparse(text)
59 pre_text = Parser().preparse(text)
59
60
60 post = self.create(title=title,
61 post = self.create(title=title,
61 text=pre_text,
62 text=pre_text,
62 pub_time=posting_time,
63 pub_time=posting_time,
63 poster_ip=ip,
64 poster_ip=ip,
64 thread=thread,
65 thread=thread,
65 last_edit_time=posting_time,
66 last_edit_time=posting_time,
66 tripcode=tripcode,
67 tripcode=tripcode,
67 opening=new_thread)
68 opening=new_thread)
68 post.threads.add(thread)
69 post.threads.add(thread)
69
70
70 logger = logging.getLogger('boards.post.create')
71 logger = logging.getLogger('boards.post.create')
71
72
72 logger.info('Created post {} by {}'.format(post, post.poster_ip))
73 logger.info('Created post {} by {}'.format(post, post.poster_ip))
73
74
74 # TODO Move this to other place
75 # TODO Move this to other place
75 if file:
76 if file:
76 file_type = file.name.split('.')[-1].lower()
77 file_type = file.name.split('.')[-1].lower()
77 if file_type in IMAGE_TYPES:
78 if file_type in IMAGE_TYPES:
78 post.images.add(PostImage.objects.create_with_hash(file))
79 post.images.add(PostImage.objects.create_with_hash(file))
79 else:
80 else:
80 post.attachments.add(Attachment.objects.create_with_hash(file))
81 post.attachments.add(Attachment.objects.create_with_hash(file))
81
82
82 post.build_url()
83 post.build_url()
83 post.connect_replies()
84 post.connect_replies()
84 post.connect_threads(opening_posts)
85 post.connect_threads(opening_posts)
85 post.connect_notifications()
86 post.connect_notifications()
86
87
87 # Thread needs to be bumped only when the post is already created
88 # Thread needs to be bumped only when the post is already created
88 if not new_thread:
89 if not new_thread:
89 thread.last_edit_time = posting_time
90 thread.last_edit_time = posting_time
90 thread.bump()
91 thread.bump()
91 thread.save()
92 thread.save()
92
93
93 return post
94 return post
94
95
95 def delete_posts_by_ip(self, ip):
96 def delete_posts_by_ip(self, ip):
96 """
97 """
97 Deletes all posts of the author with same IP
98 Deletes all posts of the author with same IP
98 """
99 """
99
100
100 posts = self.filter(poster_ip=ip)
101 posts = self.filter(poster_ip=ip)
101 for post in posts:
102 for post in posts:
102 post.delete()
103 post.delete()
103
104
104 @utils.cached_result()
105 @utils.cached_result()
105 def get_posts_per_day(self) -> float:
106 def get_posts_per_day(self) -> float:
106 """
107 """
107 Gets average count of posts per day for the last 7 days
108 Gets average count of posts per day for the last 7 days
108 """
109 """
109
110
110 day_end = date.today()
111 day_end = date.today()
111 day_start = day_end - timedelta(POSTS_PER_DAY_RANGE)
112 day_start = day_end - timedelta(POSTS_PER_DAY_RANGE)
112
113
113 day_time_start = timezone.make_aware(datetime.combine(
114 day_time_start = timezone.make_aware(datetime.combine(
114 day_start, dtime()), timezone.get_current_timezone())
115 day_start, dtime()), timezone.get_current_timezone())
115 day_time_end = timezone.make_aware(datetime.combine(
116 day_time_end = timezone.make_aware(datetime.combine(
116 day_end, dtime()), timezone.get_current_timezone())
117 day_end, dtime()), timezone.get_current_timezone())
117
118
118 posts_per_period = float(self.filter(
119 posts_per_period = float(self.filter(
119 pub_time__lte=day_time_end,
120 pub_time__lte=day_time_end,
120 pub_time__gte=day_time_start).count())
121 pub_time__gte=day_time_start).count())
121
122
122 ppd = posts_per_period / POSTS_PER_DAY_RANGE
123 ppd = posts_per_period / POSTS_PER_DAY_RANGE
123
124
124 return ppd
125 return ppd
125
126
@@ -1,103 +1,121
1 """
1 """
2 This module contains helper functions and helper classes.
2 This module contains helper functions and helper classes.
3 """
3 """
4 import hashlib
4 import hashlib
5 import time
5 import time
6 import hmac
6 import hmac
7
7
8 from django.core.cache import cache
8 from django.core.cache import cache
9 from django.db.models import Model
9 from django.db.models import Model
10 from django import forms
10 from django import forms
11
11
12 from django.utils import timezone
12 from django.utils import timezone
13 from django.utils.translation import ugettext_lazy as _
13 from django.utils.translation import ugettext_lazy as _
14
14 import boards
15 import boards
16 from boards.settings import get_bool
15
17
16 from neboard import settings
18 from neboard import settings
17
19
18
20
19 CACHE_KEY_DELIMITER = '_'
21 CACHE_KEY_DELIMITER = '_'
20 PERMISSION_MODERATE = 'moderation'
22 PERMISSION_MODERATE = 'moderation'
21
23
24 HTTP_FORWARDED = 'HTTP_X_FORWARDED_FOR'
25 META_REMOTE_ADDR = 'REMOTE_ADDR'
26
27 SETTING_MESSAGES = 'Messages'
28 SETTING_ANON_MODE = 'AnonymousMode'
29
30 ANON_IP = '127.0.0.1'
31
32
33 def is_anonymous_mode():
34 return get_bool(SETTING_MESSAGES, SETTING_ANON_MODE)
35
36
22 def get_client_ip(request):
37 def get_client_ip(request):
23 x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
38 if is_anonymous_mode():
39 ip = ANON_IP
40 else:
41 x_forwarded_for = request.META.get(HTTP_FORWARDED)
24 if x_forwarded_for:
42 if x_forwarded_for:
25 ip = x_forwarded_for.split(',')[-1].strip()
43 ip = x_forwarded_for.split(',')[-1].strip()
26 else:
44 else:
27 ip = request.META.get('REMOTE_ADDR')
45 ip = request.META.get(META_REMOTE_ADDR)
28 return ip
46 return ip
29
47
30
48
31 # TODO The output format is not epoch because it includes microseconds
49 # TODO The output format is not epoch because it includes microseconds
32 def datetime_to_epoch(datetime):
50 def datetime_to_epoch(datetime):
33 return int(time.mktime(timezone.localtime(
51 return int(time.mktime(timezone.localtime(
34 datetime,timezone.get_current_timezone()).timetuple())
52 datetime,timezone.get_current_timezone()).timetuple())
35 * 1000000 + datetime.microsecond)
53 * 1000000 + datetime.microsecond)
36
54
37
55
38 def get_websocket_token(user_id='', timestamp=''):
56 def get_websocket_token(user_id='', timestamp=''):
39 """
57 """
40 Create token to validate information provided by new connection.
58 Create token to validate information provided by new connection.
41 """
59 """
42
60
43 sign = hmac.new(settings.CENTRIFUGE_PROJECT_SECRET.encode())
61 sign = hmac.new(settings.CENTRIFUGE_PROJECT_SECRET.encode())
44 sign.update(settings.CENTRIFUGE_PROJECT_ID.encode())
62 sign.update(settings.CENTRIFUGE_PROJECT_ID.encode())
45 sign.update(user_id.encode())
63 sign.update(user_id.encode())
46 sign.update(timestamp.encode())
64 sign.update(timestamp.encode())
47 token = sign.hexdigest()
65 token = sign.hexdigest()
48
66
49 return token
67 return token
50
68
51
69
52 def cached_result(key_method=None):
70 def cached_result(key_method=None):
53 """
71 """
54 Caches method result in the Django's cache system, persisted by object name,
72 Caches method result in the Django's cache system, persisted by object name,
55 object name and model id if object is a Django model.
73 object name and model id if object is a Django model.
56 """
74 """
57 def _cached_result(function):
75 def _cached_result(function):
58 def inner_func(obj, *args, **kwargs):
76 def inner_func(obj, *args, **kwargs):
59 # TODO Include method arguments to the cache key
77 # TODO Include method arguments to the cache key
60 cache_key_params = [obj.__class__.__name__, function.__name__]
78 cache_key_params = [obj.__class__.__name__, function.__name__]
61 if isinstance(obj, Model):
79 if isinstance(obj, Model):
62 cache_key_params.append(str(obj.id))
80 cache_key_params.append(str(obj.id))
63
81
64 if key_method is not None:
82 if key_method is not None:
65 cache_key_params += [str(arg) for arg in key_method(obj)]
83 cache_key_params += [str(arg) for arg in key_method(obj)]
66
84
67 cache_key = CACHE_KEY_DELIMITER.join(cache_key_params)
85 cache_key = CACHE_KEY_DELIMITER.join(cache_key_params)
68
86
69 persisted_result = cache.get(cache_key)
87 persisted_result = cache.get(cache_key)
70 if persisted_result is not None:
88 if persisted_result is not None:
71 result = persisted_result
89 result = persisted_result
72 else:
90 else:
73 result = function(obj, *args, **kwargs)
91 result = function(obj, *args, **kwargs)
74 cache.set(cache_key, result)
92 cache.set(cache_key, result)
75
93
76 return result
94 return result
77
95
78 return inner_func
96 return inner_func
79 return _cached_result
97 return _cached_result
80
98
81
99
82 def is_moderator(request):
100 def is_moderator(request):
83 try:
101 try:
84 moderate = request.user.has_perm(PERMISSION_MODERATE)
102 moderate = request.user.has_perm(PERMISSION_MODERATE)
85 except AttributeError:
103 except AttributeError:
86 moderate = False
104 moderate = False
87
105
88 return moderate
106 return moderate
89
107
90
108
91 def get_file_hash(file) -> str:
109 def get_file_hash(file) -> str:
92 md5 = hashlib.md5()
110 md5 = hashlib.md5()
93 for chunk in file.chunks():
111 for chunk in file.chunks():
94 md5.update(chunk)
112 md5.update(chunk)
95 return md5.hexdigest()
113 return md5.hexdigest()
96
114
97
115
98 def validate_file_size(size: int):
116 def validate_file_size(size: int):
99 max_size = boards.settings.get_int('Forms', 'MaxFileSize')
117 max_size = boards.settings.get_int('Forms', 'MaxFileSize')
100 if size > max_size:
118 if size > max_size:
101 raise forms.ValidationError(
119 raise forms.ValidationError(
102 _('File must be less than %s bytes')
120 _('File must be less than %s bytes')
103 % str(max_size))
121 % str(max_size))
General Comments 0
You need to be logged in to leave comments. Login now