##// END OF EJS Templates
Refactored post post-save triggers
neko259 -
r1440:2337e9d6 default
parent child Browse files
Show More
@@ -1,363 +1,372 b''
1 import logging
1 import logging
2 import re
2 import re
3 import uuid
3 import uuid
4
4
5 from django.core.exceptions import ObjectDoesNotExist
5 from django.core.exceptions import ObjectDoesNotExist
6 from django.core.urlresolvers import reverse
6 from django.core.urlresolvers import reverse
7 from django.db import models
7 from django.db import models
8 from django.db.models import TextField, QuerySet
8 from django.db.models import TextField, QuerySet
9 from django.template.defaultfilters import striptags, truncatewords
9 from django.template.defaultfilters import striptags, truncatewords
10 from django.template.loader import render_to_string
10 from django.template.loader import render_to_string
11 from django.utils import timezone
11 from django.utils import timezone
12
12
13 from boards import settings
13 from boards import settings
14 from boards.abstracts.tripcode import Tripcode
14 from boards.abstracts.tripcode import Tripcode
15 from boards.mdx_neboard import Parser
15 from boards.mdx_neboard import Parser
16 from boards.models import PostImage, Attachment
16 from boards.models import PostImage, Attachment
17 from boards.models.base import Viewable
17 from boards.models.base import Viewable
18 from boards.models.post.export import get_exporter, DIFF_TYPE_JSON
18 from boards.models.post.export import get_exporter, DIFF_TYPE_JSON
19 from boards.models.post.manager import PostManager
19 from boards.models.post.manager import PostManager
20 from boards.models.user import Notification
20 from boards.models.user import Notification
21
21
22 CSS_CLS_HIDDEN_POST = 'hidden_post'
22 CSS_CLS_HIDDEN_POST = 'hidden_post'
23 CSS_CLS_DEAD_POST = 'dead_post'
23 CSS_CLS_DEAD_POST = 'dead_post'
24 CSS_CLS_ARCHIVE_POST = 'archive_post'
24 CSS_CLS_ARCHIVE_POST = 'archive_post'
25 CSS_CLS_POST = 'post'
25 CSS_CLS_POST = 'post'
26 CSS_CLS_MONOCHROME = 'monochrome'
26 CSS_CLS_MONOCHROME = 'monochrome'
27
27
28 TITLE_MAX_WORDS = 10
28 TITLE_MAX_WORDS = 10
29
29
30 APP_LABEL_BOARDS = 'boards'
30 APP_LABEL_BOARDS = 'boards'
31
31
32 BAN_REASON_AUTO = 'Auto'
32 BAN_REASON_AUTO = 'Auto'
33
33
34 IMAGE_THUMB_SIZE = (200, 150)
34 IMAGE_THUMB_SIZE = (200, 150)
35
35
36 TITLE_MAX_LENGTH = 200
36 TITLE_MAX_LENGTH = 200
37
37
38 REGEX_REPLY = re.compile(r'\[post\](\d+)\[/post\]')
38 REGEX_REPLY = re.compile(r'\[post\](\d+)\[/post\]')
39 REGEX_NOTIFICATION = re.compile(r'\[user\](\w+)\[/user\]')
39 REGEX_NOTIFICATION = re.compile(r'\[user\](\w+)\[/user\]')
40
40
41 PARAMETER_TRUNCATED = 'truncated'
41 PARAMETER_TRUNCATED = 'truncated'
42 PARAMETER_TAG = 'tag'
42 PARAMETER_TAG = 'tag'
43 PARAMETER_OFFSET = 'offset'
43 PARAMETER_OFFSET = 'offset'
44 PARAMETER_DIFF_TYPE = 'type'
44 PARAMETER_DIFF_TYPE = 'type'
45 PARAMETER_CSS_CLASS = 'css_class'
45 PARAMETER_CSS_CLASS = 'css_class'
46 PARAMETER_THREAD = 'thread'
46 PARAMETER_THREAD = 'thread'
47 PARAMETER_IS_OPENING = 'is_opening'
47 PARAMETER_IS_OPENING = 'is_opening'
48 PARAMETER_POST = 'post'
48 PARAMETER_POST = 'post'
49 PARAMETER_OP_ID = 'opening_post_id'
49 PARAMETER_OP_ID = 'opening_post_id'
50 PARAMETER_NEED_OPEN_LINK = 'need_open_link'
50 PARAMETER_NEED_OPEN_LINK = 'need_open_link'
51 PARAMETER_REPLY_LINK = 'reply_link'
51 PARAMETER_REPLY_LINK = 'reply_link'
52 PARAMETER_NEED_OP_DATA = 'need_op_data'
52 PARAMETER_NEED_OP_DATA = 'need_op_data'
53
53
54 POST_VIEW_PARAMS = (
54 POST_VIEW_PARAMS = (
55 'need_op_data',
55 'need_op_data',
56 'reply_link',
56 'reply_link',
57 'need_open_link',
57 'need_open_link',
58 'truncated',
58 'truncated',
59 'mode_tree',
59 'mode_tree',
60 'perms',
60 'perms',
61 )
61 )
62
62
63
63
64 class Post(models.Model, Viewable):
64 class Post(models.Model, Viewable):
65 """A post is a message."""
65 """A post is a message."""
66
66
67 objects = PostManager()
67 objects = PostManager()
68
68
69 class Meta:
69 class Meta:
70 app_label = APP_LABEL_BOARDS
70 app_label = APP_LABEL_BOARDS
71 ordering = ('id',)
71 ordering = ('id',)
72
72
73 title = models.CharField(max_length=TITLE_MAX_LENGTH, null=True, blank=True)
73 title = models.CharField(max_length=TITLE_MAX_LENGTH, null=True, blank=True)
74 pub_time = models.DateTimeField()
74 pub_time = models.DateTimeField()
75 text = TextField(blank=True, null=True)
75 text = TextField(blank=True, null=True)
76 _text_rendered = TextField(blank=True, null=True, editable=False)
76 _text_rendered = TextField(blank=True, null=True, editable=False)
77
77
78 images = models.ManyToManyField(PostImage, null=True, blank=True,
78 images = models.ManyToManyField(PostImage, null=True, blank=True,
79 related_name='post_images', db_index=True)
79 related_name='post_images', db_index=True)
80 attachments = models.ManyToManyField(Attachment, null=True, blank=True,
80 attachments = models.ManyToManyField(Attachment, null=True, blank=True,
81 related_name='attachment_posts')
81 related_name='attachment_posts')
82
82
83 poster_ip = models.GenericIPAddressField()
83 poster_ip = models.GenericIPAddressField()
84
84
85 # TODO This field can be removed cause UID is used for update now
85 # TODO This field can be removed cause UID is used for update now
86 last_edit_time = models.DateTimeField()
86 last_edit_time = models.DateTimeField()
87
87
88 referenced_posts = models.ManyToManyField('Post', symmetrical=False,
88 referenced_posts = models.ManyToManyField('Post', symmetrical=False,
89 null=True,
89 null=True,
90 blank=True, related_name='refposts',
90 blank=True, related_name='refposts',
91 db_index=True)
91 db_index=True)
92 refmap = models.TextField(null=True, blank=True)
92 refmap = models.TextField(null=True, blank=True)
93 threads = models.ManyToManyField('Thread', db_index=True,
93 threads = models.ManyToManyField('Thread', db_index=True,
94 related_name='multi_replies')
94 related_name='multi_replies')
95 thread = models.ForeignKey('Thread', db_index=True, related_name='pt+')
95 thread = models.ForeignKey('Thread', db_index=True, related_name='pt+')
96
96
97 url = models.TextField()
97 url = models.TextField()
98 uid = models.TextField(db_index=True)
98 uid = models.TextField(db_index=True)
99
99
100 tripcode = models.CharField(max_length=50, blank=True, default='')
100 tripcode = models.CharField(max_length=50, blank=True, default='')
101 opening = models.BooleanField(db_index=True)
101 opening = models.BooleanField(db_index=True)
102 hidden = models.BooleanField(default=False)
102 hidden = models.BooleanField(default=False)
103
103
104 def __str__(self):
104 def __str__(self):
105 return 'P#{}/{}'.format(self.id, self.get_title())
105 return 'P#{}/{}'.format(self.id, self.get_title())
106
106
107 def get_referenced_posts(self):
107 def get_referenced_posts(self):
108 threads = self.get_threads().all()
108 threads = self.get_threads().all()
109 return self.referenced_posts.filter(threads__in=threads)\
109 return self.referenced_posts.filter(threads__in=threads)\
110 .order_by('pub_time').distinct().all()
110 .order_by('pub_time').distinct().all()
111
111
112 def get_title(self) -> str:
112 def get_title(self) -> str:
113 return self.title
113 return self.title
114
114
115 def get_title_or_text(self):
115 def get_title_or_text(self):
116 title = self.get_title()
116 title = self.get_title()
117 if not title:
117 if not title:
118 title = truncatewords(striptags(self.get_text()), TITLE_MAX_WORDS)
118 title = truncatewords(striptags(self.get_text()), TITLE_MAX_WORDS)
119
119
120 return title
120 return title
121
121
122 def build_refmap(self) -> None:
122 def build_refmap(self) -> None:
123 """
123 """
124 Builds a replies map string from replies list. This is a cache to stop
124 Builds a replies map string from replies list. This is a cache to stop
125 the server from recalculating the map on every post show.
125 the server from recalculating the map on every post show.
126 """
126 """
127
127
128 post_urls = [refpost.get_link_view()
128 post_urls = [refpost.get_link_view()
129 for refpost in self.referenced_posts.all()]
129 for refpost in self.referenced_posts.all()]
130
130
131 self.refmap = ', '.join(post_urls)
131 self.refmap = ', '.join(post_urls)
132
132
133 def is_referenced(self) -> bool:
133 def is_referenced(self) -> bool:
134 return self.refmap and len(self.refmap) > 0
134 return self.refmap and len(self.refmap) > 0
135
135
136 def is_opening(self) -> bool:
136 def is_opening(self) -> bool:
137 """
137 """
138 Checks if this is an opening post or just a reply.
138 Checks if this is an opening post or just a reply.
139 """
139 """
140
140
141 return self.opening
141 return self.opening
142
142
143 def get_absolute_url(self, thread=None):
143 def get_absolute_url(self, thread=None):
144 url = None
144 url = None
145
145
146 if thread is None:
146 if thread is None:
147 thread = self.get_thread()
147 thread = self.get_thread()
148
148
149 # Url is cached only for the "main" thread. When getting url
149 # Url is cached only for the "main" thread. When getting url
150 # for other threads, do it manually.
150 # for other threads, do it manually.
151 if self.url:
151 if self.url:
152 url = self.url
152 url = self.url
153
153
154 if url is None:
154 if url is None:
155 opening_id = thread.get_opening_post_id()
155 opening_id = thread.get_opening_post_id()
156 url = reverse('thread', kwargs={'post_id': opening_id})
156 url = reverse('thread', kwargs={'post_id': opening_id})
157 if self.id != opening_id:
157 if self.id != opening_id:
158 url += '#' + str(self.id)
158 url += '#' + str(self.id)
159
159
160 return url
160 return url
161
161
162 def get_thread(self):
162 def get_thread(self):
163 return self.thread
163 return self.thread
164
164
165 def get_threads(self) -> QuerySet:
165 def get_threads(self) -> QuerySet:
166 """
166 """
167 Gets post's thread.
167 Gets post's thread.
168 """
168 """
169
169
170 return self.threads
170 return self.threads
171
171
172 def get_view(self, *args, **kwargs) -> str:
172 def get_view(self, *args, **kwargs) -> str:
173 """
173 """
174 Renders post's HTML view. Some of the post params can be passed over
174 Renders post's HTML view. Some of the post params can be passed over
175 kwargs for the means of caching (if we view the thread, some params
175 kwargs for the means of caching (if we view the thread, some params
176 are same for every post and don't need to be computed over and over.
176 are same for every post and don't need to be computed over and over.
177 """
177 """
178
178
179 thread = self.get_thread()
179 thread = self.get_thread()
180
180
181 css_classes = [CSS_CLS_POST]
181 css_classes = [CSS_CLS_POST]
182 if thread.is_archived():
182 if thread.is_archived():
183 css_classes.append(CSS_CLS_ARCHIVE_POST)
183 css_classes.append(CSS_CLS_ARCHIVE_POST)
184 elif not thread.can_bump():
184 elif not thread.can_bump():
185 css_classes.append(CSS_CLS_DEAD_POST)
185 css_classes.append(CSS_CLS_DEAD_POST)
186 if self.is_hidden():
186 if self.is_hidden():
187 css_classes.append(CSS_CLS_HIDDEN_POST)
187 css_classes.append(CSS_CLS_HIDDEN_POST)
188 if thread.is_monochrome():
188 if thread.is_monochrome():
189 css_classes.append(CSS_CLS_MONOCHROME)
189 css_classes.append(CSS_CLS_MONOCHROME)
190
190
191 params = dict()
191 params = dict()
192 for param in POST_VIEW_PARAMS:
192 for param in POST_VIEW_PARAMS:
193 if param in kwargs:
193 if param in kwargs:
194 params[param] = kwargs[param]
194 params[param] = kwargs[param]
195
195
196 params.update({
196 params.update({
197 PARAMETER_POST: self,
197 PARAMETER_POST: self,
198 PARAMETER_IS_OPENING: self.is_opening(),
198 PARAMETER_IS_OPENING: self.is_opening(),
199 PARAMETER_THREAD: thread,
199 PARAMETER_THREAD: thread,
200 PARAMETER_CSS_CLASS: ' '.join(css_classes),
200 PARAMETER_CSS_CLASS: ' '.join(css_classes),
201 })
201 })
202
202
203 return render_to_string('boards/post.html', params)
203 return render_to_string('boards/post.html', params)
204
204
205 def get_search_view(self, *args, **kwargs):
205 def get_search_view(self, *args, **kwargs):
206 return self.get_view(need_op_data=True, *args, **kwargs)
206 return self.get_view(need_op_data=True, *args, **kwargs)
207
207
208 def get_first_image(self) -> PostImage:
208 def get_first_image(self) -> PostImage:
209 return self.images.earliest('id')
209 return self.images.earliest('id')
210
210
211 def delete(self, using=None):
211 def delete(self, using=None):
212 """
212 """
213 Deletes all post images and the post itself.
213 Deletes all post images and the post itself.
214 """
214 """
215
215
216 for image in self.images.all():
216 for image in self.images.all():
217 image_refs_count = image.post_images.count()
217 image_refs_count = image.post_images.count()
218 if image_refs_count == 1:
218 if image_refs_count == 1:
219 image.delete()
219 image.delete()
220
220
221 for attachment in self.attachments.all():
221 for attachment in self.attachments.all():
222 attachment_refs_count = attachment.attachment_posts.count()
222 attachment_refs_count = attachment.attachment_posts.count()
223 if attachment_refs_count == 1:
223 if attachment_refs_count == 1:
224 attachment.delete()
224 attachment.delete()
225
225
226 thread = self.get_thread()
226 thread = self.get_thread()
227 thread.last_edit_time = timezone.now()
227 thread.last_edit_time = timezone.now()
228 thread.save()
228 thread.save()
229
229
230 super(Post, self).delete(using)
230 super(Post, self).delete(using)
231
231
232 logging.getLogger('boards.post.delete').info(
232 logging.getLogger('boards.post.delete').info(
233 'Deleted post {}'.format(self))
233 'Deleted post {}'.format(self))
234
234
235 def get_post_data(self, format_type=DIFF_TYPE_JSON, request=None,
235 def get_post_data(self, format_type=DIFF_TYPE_JSON, request=None,
236 include_last_update=False) -> str:
236 include_last_update=False) -> str:
237 """
237 """
238 Gets post HTML or JSON data that can be rendered on a page or used by
238 Gets post HTML or JSON data that can be rendered on a page or used by
239 API.
239 API.
240 """
240 """
241
241
242 return get_exporter(format_type).export(self, request,
242 return get_exporter(format_type).export(self, request,
243 include_last_update)
243 include_last_update)
244
244
245 def notify_clients(self, recursive=True):
245 def notify_clients(self, recursive=True):
246 """
246 """
247 Sends post HTML data to the thread web socket.
247 Sends post HTML data to the thread web socket.
248 """
248 """
249
249
250 if not settings.get_bool('External', 'WebsocketsEnabled'):
250 if not settings.get_bool('External', 'WebsocketsEnabled'):
251 return
251 return
252
252
253 thread_ids = list()
253 thread_ids = list()
254 for thread in self.get_threads().all():
254 for thread in self.get_threads().all():
255 thread_ids.append(thread.id)
255 thread_ids.append(thread.id)
256
256
257 thread.notify_clients()
257 thread.notify_clients()
258
258
259 if recursive:
259 if recursive:
260 for reply_number in re.finditer(REGEX_REPLY, self.get_raw_text()):
260 for reply_number in re.finditer(REGEX_REPLY, self.get_raw_text()):
261 post_id = reply_number.group(1)
261 post_id = reply_number.group(1)
262
262
263 try:
263 try:
264 ref_post = Post.objects.get(id=post_id)
264 ref_post = Post.objects.get(id=post_id)
265
265
266 if ref_post.get_threads().exclude(id__in=thread_ids).exists():
266 if ref_post.get_threads().exclude(id__in=thread_ids).exists():
267 # If post is in this thread, its thread was already notified.
267 # If post is in this thread, its thread was already notified.
268 # Otherwise, notify its thread separately.
268 # Otherwise, notify its thread separately.
269 ref_post.notify_clients(recursive=False)
269 ref_post.notify_clients(recursive=False)
270 except ObjectDoesNotExist:
270 except ObjectDoesNotExist:
271 pass
271 pass
272
272
273 def build_url(self):
273 def build_url(self):
274 self.url = self.get_absolute_url()
274 self.url = self.get_absolute_url()
275 self.save(update_fields=['url'])
275 self.save(update_fields=['url'])
276
276
277 def save(self, force_insert=False, force_update=False, using=None,
277 def save(self, force_insert=False, force_update=False, using=None,
278 update_fields=None):
278 update_fields=None):
279 new_post = self.id is None
280
279 self._text_rendered = Parser().parse(self.get_raw_text())
281 self._text_rendered = Parser().parse(self.get_raw_text())
280
282
281 self.uid = str(uuid.uuid4())
283 self.uid = str(uuid.uuid4())
282 if update_fields is not None and 'uid' not in update_fields:
284 if update_fields is not None and 'uid' not in update_fields:
283 update_fields += ['uid']
285 update_fields += ['uid']
284
286
285 if self.id:
287 if not new_post:
286 for thread in self.get_threads().all():
288 for thread in self.get_threads().all():
287 thread.last_edit_time = self.last_edit_time
289 thread.last_edit_time = self.last_edit_time
288
290
289 thread.save(update_fields=['last_edit_time', 'status'])
291 thread.save(update_fields=['last_edit_time', 'status'])
290
292
291 super().save(force_insert, force_update, using, update_fields)
293 super().save(force_insert, force_update, using, update_fields)
292
294
295 # Post save triggers
296 if new_post:
297 self.build_url()
298
299 self._connect_replies()
300 self._connect_notifications()
301
293 def get_text(self) -> str:
302 def get_text(self) -> str:
294 return self._text_rendered
303 return self._text_rendered
295
304
296 def get_raw_text(self) -> str:
305 def get_raw_text(self) -> str:
297 return self.text
306 return self.text
298
307
299 def get_absolute_id(self) -> str:
308 def get_absolute_id(self) -> str:
300 """
309 """
301 If the post has many threads, shows its main thread OP id in the post
310 If the post has many threads, shows its main thread OP id in the post
302 ID.
311 ID.
303 """
312 """
304
313
305 if self.get_threads().count() > 1:
314 if self.get_threads().count() > 1:
306 return '{}/{}'.format(self.get_thread().get_opening_post_id(), self.id)
315 return '{}/{}'.format(self.get_thread().get_opening_post_id(), self.id)
307 else:
316 else:
308 return str(self.id)
317 return str(self.id)
309
318
310 def connect_notifications(self):
319 def _connect_notifications(self):
311 for reply_number in re.finditer(REGEX_NOTIFICATION, self.get_raw_text()):
320 for reply_number in re.finditer(REGEX_NOTIFICATION, self.get_raw_text()):
312 user_name = reply_number.group(1).lower()
321 user_name = reply_number.group(1).lower()
313 Notification.objects.get_or_create(name=user_name, post=self)
322 Notification.objects.get_or_create(name=user_name, post=self)
314
323
315 def connect_replies(self):
324 def _connect_replies(self):
316 """
325 """
317 Connects replies to a post to show them as a reflink map
326 Connects replies to a post to show them as a reflink map
318 """
327 """
319
328
320 for reply_number in re.finditer(REGEX_REPLY, self.get_raw_text()):
329 for reply_number in re.finditer(REGEX_REPLY, self.get_raw_text()):
321 post_id = reply_number.group(1)
330 post_id = reply_number.group(1)
322
331
323 try:
332 try:
324 referenced_post = Post.objects.get(id=post_id)
333 referenced_post = Post.objects.get(id=post_id)
325
334
326 referenced_post.referenced_posts.add(self)
335 referenced_post.referenced_posts.add(self)
327 referenced_post.last_edit_time = self.pub_time
336 referenced_post.last_edit_time = self.pub_time
328 referenced_post.build_refmap()
337 referenced_post.build_refmap()
329 referenced_post.save(update_fields=['refmap', 'last_edit_time'])
338 referenced_post.save(update_fields=['refmap', 'last_edit_time'])
330 except ObjectDoesNotExist:
339 except ObjectDoesNotExist:
331 pass
340 pass
332
341
333 def connect_threads(self, opening_posts):
342 def connect_threads(self, opening_posts):
334 for opening_post in opening_posts:
343 for opening_post in opening_posts:
335 threads = opening_post.get_threads().all()
344 threads = opening_post.get_threads().all()
336 for thread in threads:
345 for thread in threads:
337 if thread.can_bump():
346 if thread.can_bump():
338 thread.update_bump_status()
347 thread.update_bump_status()
339
348
340 thread.last_edit_time = self.last_edit_time
349 thread.last_edit_time = self.last_edit_time
341 thread.save(update_fields=['last_edit_time', 'status'])
350 thread.save(update_fields=['last_edit_time', 'status'])
342 self.threads.add(opening_post.get_thread())
351 self.threads.add(opening_post.get_thread())
343
352
344 def get_tripcode(self):
353 def get_tripcode(self):
345 if self.tripcode:
354 if self.tripcode:
346 return Tripcode(self.tripcode)
355 return Tripcode(self.tripcode)
347
356
348 def get_link_view(self):
357 def get_link_view(self):
349 """
358 """
350 Gets view of a reflink to the post.
359 Gets view of a reflink to the post.
351 """
360 """
352 result = '<a href="{}">&gt;&gt;{}</a>'.format(self.get_absolute_url(),
361 result = '<a href="{}">&gt;&gt;{}</a>'.format(self.get_absolute_url(),
353 self.id)
362 self.id)
354 if self.is_opening():
363 if self.is_opening():
355 result = '<b>{}</b>'.format(result)
364 result = '<b>{}</b>'.format(result)
356
365
357 return result
366 return result
358
367
359 def is_hidden(self) -> bool:
368 def is_hidden(self) -> bool:
360 return self.hidden
369 return self.hidden
361
370
362 def set_hidden(self, hidden):
371 def set_hidden(self, hidden):
363 self.hidden = hidden
372 self.hidden = hidden
@@ -1,131 +1,128 b''
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,
33 ip=NO_IP, tags: list=None, opening_posts: list=None,
34 tripcode='', monochrome=False):
34 tripcode='', monochrome=False):
35 """
35 """
36 Creates new post
36 Creates new post
37 """
37 """
38
38
39 if not utils.is_anonymous_mode():
39 if not utils.is_anonymous_mode():
40 is_banned = Ban.objects.filter(ip=ip).exists()
40 is_banned = Ban.objects.filter(ip=ip).exists()
41 else:
41 else:
42 is_banned = False
42 is_banned = False
43
43
44 # TODO Raise specific exception and catch it in the views
44 # TODO Raise specific exception and catch it in the views
45 if is_banned:
45 if is_banned:
46 raise Exception("This user is banned")
46 raise Exception("This user is banned")
47
47
48 if not tags:
48 if not tags:
49 tags = []
49 tags = []
50 if not opening_posts:
50 if not opening_posts:
51 opening_posts = []
51 opening_posts = []
52
52
53 posting_time = timezone.now()
53 posting_time = timezone.now()
54 new_thread = False
54 new_thread = False
55 if not thread:
55 if not thread:
56 thread = boards.models.thread.Thread.objects.create(
56 thread = boards.models.thread.Thread.objects.create(
57 bump_time=posting_time, last_edit_time=posting_time,
57 bump_time=posting_time, last_edit_time=posting_time,
58 monochrome=monochrome)
58 monochrome=monochrome)
59 list(map(thread.tags.add, tags))
59 list(map(thread.tags.add, tags))
60 boards.models.thread.Thread.objects.process_oldest_threads()
60 boards.models.thread.Thread.objects.process_oldest_threads()
61 new_thread = True
61 new_thread = True
62
62
63 pre_text = Parser().preparse(text)
63 pre_text = Parser().preparse(text)
64
64
65 post = self.create(title=title,
65 post = self.create(title=title,
66 text=pre_text,
66 text=pre_text,
67 pub_time=posting_time,
67 pub_time=posting_time,
68 poster_ip=ip,
68 poster_ip=ip,
69 thread=thread,
69 thread=thread,
70 last_edit_time=posting_time,
70 last_edit_time=posting_time,
71 tripcode=tripcode,
71 tripcode=tripcode,
72 opening=new_thread)
72 opening=new_thread)
73 post.threads.add(thread)
73 post.threads.add(thread)
74
74
75 logger = logging.getLogger('boards.post.create')
75 logger = logging.getLogger('boards.post.create')
76
76
77 logger.info('Created post [{}] with text [{}] by {}'.format(post,
77 logger.info('Created post [{}] with text [{}] by {}'.format(post,
78 post.get_text(),post.poster_ip))
78 post.get_text(),post.poster_ip))
79
79
80 # TODO Move this to other place
80 # TODO Move this to other place
81 if file:
81 if file:
82 file_type = file.name.split('.')[-1].lower()
82 file_type = file.name.split('.')[-1].lower()
83 if file_type in IMAGE_TYPES:
83 if file_type in IMAGE_TYPES:
84 post.images.add(PostImage.objects.create_with_hash(file))
84 post.images.add(PostImage.objects.create_with_hash(file))
85 else:
85 else:
86 post.attachments.add(Attachment.objects.create_with_hash(file))
86 post.attachments.add(Attachment.objects.create_with_hash(file))
87
87
88 post.build_url()
89 post.connect_replies()
90 post.connect_threads(opening_posts)
88 post.connect_threads(opening_posts)
91 post.connect_notifications()
92
89
93 # Thread needs to be bumped only when the post is already created
90 # Thread needs to be bumped only when the post is already created
94 if not new_thread:
91 if not new_thread:
95 thread.last_edit_time = posting_time
92 thread.last_edit_time = posting_time
96 thread.bump()
93 thread.bump()
97 thread.save()
94 thread.save()
98
95
99 return post
96 return post
100
97
101 def delete_posts_by_ip(self, ip):
98 def delete_posts_by_ip(self, ip):
102 """
99 """
103 Deletes all posts of the author with same IP
100 Deletes all posts of the author with same IP
104 """
101 """
105
102
106 posts = self.filter(poster_ip=ip)
103 posts = self.filter(poster_ip=ip)
107 for post in posts:
104 for post in posts:
108 post.delete()
105 post.delete()
109
106
110 @utils.cached_result()
107 @utils.cached_result()
111 def get_posts_per_day(self) -> float:
108 def get_posts_per_day(self) -> float:
112 """
109 """
113 Gets average count of posts per day for the last 7 days
110 Gets average count of posts per day for the last 7 days
114 """
111 """
115
112
116 day_end = date.today()
113 day_end = date.today()
117 day_start = day_end - timedelta(POSTS_PER_DAY_RANGE)
114 day_start = day_end - timedelta(POSTS_PER_DAY_RANGE)
118
115
119 day_time_start = timezone.make_aware(datetime.combine(
116 day_time_start = timezone.make_aware(datetime.combine(
120 day_start, dtime()), timezone.get_current_timezone())
117 day_start, dtime()), timezone.get_current_timezone())
121 day_time_end = timezone.make_aware(datetime.combine(
118 day_time_end = timezone.make_aware(datetime.combine(
122 day_end, dtime()), timezone.get_current_timezone())
119 day_end, dtime()), timezone.get_current_timezone())
123
120
124 posts_per_period = float(self.filter(
121 posts_per_period = float(self.filter(
125 pub_time__lte=day_time_end,
122 pub_time__lte=day_time_end,
126 pub_time__gte=day_time_start).count())
123 pub_time__gte=day_time_start).count())
127
124
128 ppd = posts_per_period / POSTS_PER_DAY_RANGE
125 ppd = posts_per_period / POSTS_PER_DAY_RANGE
129
126
130 return ppd
127 return ppd
131
128
General Comments 0
You need to be logged in to leave comments. Login now