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