##// END OF EJS Templates
Added docstring
neko259 -
r1651:507a67ac 3.2.2 default
parent child Browse files
Show More
@@ -1,410 +1,414 b''
1 import uuid
1 import uuid
2 import hashlib
2 import hashlib
3 import re
3 import re
4
4
5 from boards import settings
5 from boards import settings
6 from boards.abstracts.tripcode import Tripcode
6 from boards.abstracts.tripcode import Tripcode
7 from boards.models import Attachment, KeyPair, GlobalId
7 from boards.models import Attachment, KeyPair, GlobalId
8 from boards.models.attachment import FILE_TYPES_IMAGE
8 from boards.models.attachment import FILE_TYPES_IMAGE
9 from boards.models.base import Viewable
9 from boards.models.base import Viewable
10 from boards.models.post.export import get_exporter, DIFF_TYPE_JSON
10 from boards.models.post.export import get_exporter, DIFF_TYPE_JSON
11 from boards.models.post.manager import PostManager
11 from boards.models.post.manager import PostManager
12 from boards.utils import datetime_to_epoch
12 from boards.utils import datetime_to_epoch
13 from django.core.exceptions import ObjectDoesNotExist
13 from django.core.exceptions import ObjectDoesNotExist
14 from django.core.urlresolvers import reverse
14 from django.core.urlresolvers import reverse
15 from django.db import models
15 from django.db import models
16 from django.db.models import TextField, QuerySet, F
16 from django.db.models import TextField, QuerySet, F
17 from django.template.defaultfilters import truncatewords, striptags
17 from django.template.defaultfilters import truncatewords, striptags
18 from django.template.loader import render_to_string
18 from django.template.loader import render_to_string
19
19
20 CSS_CLS_HIDDEN_POST = 'hidden_post'
20 CSS_CLS_HIDDEN_POST = 'hidden_post'
21 CSS_CLS_DEAD_POST = 'dead_post'
21 CSS_CLS_DEAD_POST = 'dead_post'
22 CSS_CLS_ARCHIVE_POST = 'archive_post'
22 CSS_CLS_ARCHIVE_POST = 'archive_post'
23 CSS_CLS_POST = 'post'
23 CSS_CLS_POST = 'post'
24 CSS_CLS_MONOCHROME = 'monochrome'
24 CSS_CLS_MONOCHROME = 'monochrome'
25
25
26 TITLE_MAX_WORDS = 10
26 TITLE_MAX_WORDS = 10
27
27
28 APP_LABEL_BOARDS = 'boards'
28 APP_LABEL_BOARDS = 'boards'
29
29
30 BAN_REASON_AUTO = 'Auto'
30 BAN_REASON_AUTO = 'Auto'
31
31
32 TITLE_MAX_LENGTH = 200
32 TITLE_MAX_LENGTH = 200
33
33
34 REGEX_REPLY = re.compile(r'\[post\](\d+)\[/post\]')
34 REGEX_REPLY = re.compile(r'\[post\](\d+)\[/post\]')
35 REGEX_GLOBAL_REPLY = re.compile(r'\[post\](\w+)::([^:]+)::(\d+)\[/post\]')
35 REGEX_GLOBAL_REPLY = re.compile(r'\[post\](\w+)::([^:]+)::(\d+)\[/post\]')
36 REGEX_URL = re.compile(r'https?\://[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,3}(/\S*)?')
36 REGEX_URL = re.compile(r'https?\://[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,3}(/\S*)?')
37 REGEX_NOTIFICATION = re.compile(r'\[user\](\w+)\[/user\]')
37 REGEX_NOTIFICATION = re.compile(r'\[user\](\w+)\[/user\]')
38
38
39 PARAMETER_TRUNCATED = 'truncated'
39 PARAMETER_TRUNCATED = 'truncated'
40 PARAMETER_TAG = 'tag'
40 PARAMETER_TAG = 'tag'
41 PARAMETER_OFFSET = 'offset'
41 PARAMETER_OFFSET = 'offset'
42 PARAMETER_DIFF_TYPE = 'type'
42 PARAMETER_DIFF_TYPE = 'type'
43 PARAMETER_CSS_CLASS = 'css_class'
43 PARAMETER_CSS_CLASS = 'css_class'
44 PARAMETER_THREAD = 'thread'
44 PARAMETER_THREAD = 'thread'
45 PARAMETER_IS_OPENING = 'is_opening'
45 PARAMETER_IS_OPENING = 'is_opening'
46 PARAMETER_POST = 'post'
46 PARAMETER_POST = 'post'
47 PARAMETER_OP_ID = 'opening_post_id'
47 PARAMETER_OP_ID = 'opening_post_id'
48 PARAMETER_NEED_OPEN_LINK = 'need_open_link'
48 PARAMETER_NEED_OPEN_LINK = 'need_open_link'
49 PARAMETER_REPLY_LINK = 'reply_link'
49 PARAMETER_REPLY_LINK = 'reply_link'
50 PARAMETER_NEED_OP_DATA = 'need_op_data'
50 PARAMETER_NEED_OP_DATA = 'need_op_data'
51
51
52 POST_VIEW_PARAMS = (
52 POST_VIEW_PARAMS = (
53 'need_op_data',
53 'need_op_data',
54 'reply_link',
54 'reply_link',
55 'need_open_link',
55 'need_open_link',
56 'truncated',
56 'truncated',
57 'mode_tree',
57 'mode_tree',
58 'perms',
58 'perms',
59 'tree_depth',
59 'tree_depth',
60 )
60 )
61
61
62
62
63 class Post(models.Model, Viewable):
63 class Post(models.Model, Viewable):
64 """A post is a message."""
64 """A post is a message."""
65
65
66 objects = PostManager()
66 objects = PostManager()
67
67
68 class Meta:
68 class Meta:
69 app_label = APP_LABEL_BOARDS
69 app_label = APP_LABEL_BOARDS
70 ordering = ('id',)
70 ordering = ('id',)
71
71
72 title = models.CharField(max_length=TITLE_MAX_LENGTH, null=True, blank=True)
72 title = models.CharField(max_length=TITLE_MAX_LENGTH, null=True, blank=True)
73 pub_time = models.DateTimeField()
73 pub_time = models.DateTimeField()
74 text = TextField(blank=True, null=True)
74 text = TextField(blank=True, null=True)
75 _text_rendered = TextField(blank=True, null=True, editable=False)
75 _text_rendered = TextField(blank=True, null=True, editable=False)
76
76
77 attachments = models.ManyToManyField(Attachment, null=True, blank=True,
77 attachments = models.ManyToManyField(Attachment, null=True, blank=True,
78 related_name='attachment_posts')
78 related_name='attachment_posts')
79
79
80 poster_ip = models.GenericIPAddressField()
80 poster_ip = models.GenericIPAddressField()
81
81
82 # Used for cache and threads updating
82 # Used for cache and threads updating
83 last_edit_time = models.DateTimeField()
83 last_edit_time = models.DateTimeField()
84
84
85 referenced_posts = models.ManyToManyField('Post', symmetrical=False,
85 referenced_posts = models.ManyToManyField('Post', symmetrical=False,
86 null=True,
86 null=True,
87 blank=True, related_name='refposts',
87 blank=True, related_name='refposts',
88 db_index=True)
88 db_index=True)
89 refmap = models.TextField(null=True, blank=True)
89 refmap = models.TextField(null=True, blank=True)
90 threads = models.ManyToManyField('Thread', db_index=True,
90 threads = models.ManyToManyField('Thread', db_index=True,
91 related_name='multi_replies')
91 related_name='multi_replies')
92 thread = models.ForeignKey('Thread', db_index=True, related_name='pt+')
92 thread = models.ForeignKey('Thread', db_index=True, related_name='pt+')
93
93
94 url = models.TextField()
94 url = models.TextField()
95 uid = models.TextField(db_index=True)
95 uid = models.TextField(db_index=True)
96
96
97 # Global ID with author key. If the message was downloaded from another
97 # Global ID with author key. If the message was downloaded from another
98 # server, this indicates the server.
98 # server, this indicates the server.
99 global_id = models.OneToOneField(GlobalId, null=True, blank=True,
99 global_id = models.OneToOneField(GlobalId, null=True, blank=True,
100 on_delete=models.CASCADE)
100 on_delete=models.CASCADE)
101
101
102 tripcode = models.CharField(max_length=50, blank=True, default='')
102 tripcode = models.CharField(max_length=50, blank=True, default='')
103 opening = models.BooleanField(db_index=True)
103 opening = models.BooleanField(db_index=True)
104 hidden = models.BooleanField(default=False)
104 hidden = models.BooleanField(default=False)
105 version = models.IntegerField(default=1)
105 version = models.IntegerField(default=1)
106
106
107 def __str__(self):
107 def __str__(self):
108 return 'P#{}/{}'.format(self.id, self.get_title())
108 return 'P#{}/{}'.format(self.id, self.get_title())
109
109
110 def get_title(self) -> str:
110 def get_title(self) -> str:
111 return self.title
111 return self.title
112
112
113 def get_title_or_text(self):
113 def get_title_or_text(self):
114 title = self.get_title()
114 title = self.get_title()
115 if not title:
115 if not title:
116 title = truncatewords(striptags(self.get_text()), TITLE_MAX_WORDS)
116 title = truncatewords(striptags(self.get_text()), TITLE_MAX_WORDS)
117
117
118 return title
118 return title
119
119
120 def build_refmap(self, excluded_ids=None) -> None:
120 def build_refmap(self, excluded_ids=None) -> None:
121 """
121 """
122 Builds a replies map string from replies list. This is a cache to stop
122 Builds a replies map string from replies list. This is a cache to stop
123 the server from recalculating the map on every post show.
123 the server from recalculating the map on every post show.
124 """
124 """
125
125
126 replies = self.referenced_posts
126 replies = self.referenced_posts
127 if excluded_ids is not None:
127 if excluded_ids is not None:
128 replies = replies.exclude(id__in=excluded_ids)
128 replies = replies.exclude(id__in=excluded_ids)
129 else:
129 else:
130 replies = replies.all()
130 replies = replies.all()
131
131
132 post_urls = [refpost.get_link_view() for refpost in replies]
132 post_urls = [refpost.get_link_view() for refpost in replies]
133
133
134 self.refmap = ', '.join(post_urls)
134 self.refmap = ', '.join(post_urls)
135
135
136 def is_referenced(self) -> bool:
136 def is_referenced(self) -> bool:
137 return self.refmap and len(self.refmap) > 0
137 return self.refmap and len(self.refmap) > 0
138
138
139 def is_opening(self) -> bool:
139 def is_opening(self) -> bool:
140 """
140 """
141 Checks if this is an opening post or just a reply.
141 Checks if this is an opening post or just a reply.
142 """
142 """
143
143
144 return self.opening
144 return self.opening
145
145
146 def get_absolute_url(self, thread=None):
146 def get_absolute_url(self, thread=None):
147 url = None
147 url = None
148
148
149 if thread is None:
149 if thread is None:
150 thread = self.get_thread()
150 thread = self.get_thread()
151
151
152 # Url is cached only for the "main" thread. When getting url
152 # Url is cached only for the "main" thread. When getting url
153 # for other threads, do it manually.
153 # for other threads, do it manually.
154 if self.url:
154 if self.url:
155 url = self.url
155 url = self.url
156
156
157 if url is None:
157 if url is None:
158 opening = self.is_opening()
158 opening = self.is_opening()
159 opening_id = self.id if opening else thread.get_opening_post_id()
159 opening_id = self.id if opening else thread.get_opening_post_id()
160 url = reverse('thread', kwargs={'post_id': opening_id})
160 url = reverse('thread', kwargs={'post_id': opening_id})
161 if not opening:
161 if not opening:
162 url += '#' + str(self.id)
162 url += '#' + str(self.id)
163
163
164 return url
164 return url
165
165
166 def get_thread(self):
166 def get_thread(self):
167 return self.thread
167 return self.thread
168
168
169 def get_thread_id(self):
169 def get_thread_id(self):
170 return self.thread_id
170 return self.thread_id
171
171
172 def get_threads(self) -> QuerySet:
172 def get_threads(self) -> QuerySet:
173 """
173 """
174 Gets post's thread.
174 Gets post's thread.
175 """
175 """
176
176
177 return self.threads
177 return self.threads
178
178
179 def _get_cache_key(self):
179 def _get_cache_key(self):
180 return [datetime_to_epoch(self.last_edit_time)]
180 return [datetime_to_epoch(self.last_edit_time)]
181
181
182 def get_view_params(self, *args, **kwargs):
182 def get_view_params(self, *args, **kwargs):
183 """
184 Gets the parameters required for viewing the post based on the arguments
185 given and the post itself.
186 """
183 thread = self.get_thread()
187 thread = self.get_thread()
184
188
185 css_classes = [CSS_CLS_POST]
189 css_classes = [CSS_CLS_POST]
186 if thread.is_archived():
190 if thread.is_archived():
187 css_classes.append(CSS_CLS_ARCHIVE_POST)
191 css_classes.append(CSS_CLS_ARCHIVE_POST)
188 elif not thread.can_bump():
192 elif not thread.can_bump():
189 css_classes.append(CSS_CLS_DEAD_POST)
193 css_classes.append(CSS_CLS_DEAD_POST)
190 if self.is_hidden():
194 if self.is_hidden():
191 css_classes.append(CSS_CLS_HIDDEN_POST)
195 css_classes.append(CSS_CLS_HIDDEN_POST)
192 if thread.is_monochrome():
196 if thread.is_monochrome():
193 css_classes.append(CSS_CLS_MONOCHROME)
197 css_classes.append(CSS_CLS_MONOCHROME)
194
198
195 params = dict()
199 params = dict()
196 for param in POST_VIEW_PARAMS:
200 for param in POST_VIEW_PARAMS:
197 if param in kwargs:
201 if param in kwargs:
198 params[param] = kwargs[param]
202 params[param] = kwargs[param]
199
203
200 params.update({
204 params.update({
201 PARAMETER_POST: self,
205 PARAMETER_POST: self,
202 PARAMETER_IS_OPENING: self.is_opening(),
206 PARAMETER_IS_OPENING: self.is_opening(),
203 PARAMETER_THREAD: thread,
207 PARAMETER_THREAD: thread,
204 PARAMETER_CSS_CLASS: ' '.join(css_classes),
208 PARAMETER_CSS_CLASS: ' '.join(css_classes),
205 })
209 })
206
210
207 return params
211 return params
208
212
209 def get_view(self, *args, **kwargs) -> str:
213 def get_view(self, *args, **kwargs) -> str:
210 """
214 """
211 Renders post's HTML view. Some of the post params can be passed over
215 Renders post's HTML view. Some of the post params can be passed over
212 kwargs for the means of caching (if we view the thread, some params
216 kwargs for the means of caching (if we view the thread, some params
213 are same for every post and don't need to be computed over and over.
217 are same for every post and don't need to be computed over and over.
214 """
218 """
215 params = self.get_view_params(*args, **kwargs)
219 params = self.get_view_params(*args, **kwargs)
216
220
217 return render_to_string('boards/post.html', params)
221 return render_to_string('boards/post.html', params)
218
222
219 def get_search_view(self, *args, **kwargs):
223 def get_search_view(self, *args, **kwargs):
220 return self.get_view(need_op_data=True, *args, **kwargs)
224 return self.get_view(need_op_data=True, *args, **kwargs)
221
225
222 def get_first_image(self) -> Attachment:
226 def get_first_image(self) -> Attachment:
223 return self.attachments.filter(mimetype__in=FILE_TYPES_IMAGE).earliest('id')
227 return self.attachments.filter(mimetype__in=FILE_TYPES_IMAGE).earliest('id')
224
228
225 def set_global_id(self, key_pair=None):
229 def set_global_id(self, key_pair=None):
226 """
230 """
227 Sets global id based on the given key pair. If no key pair is given,
231 Sets global id based on the given key pair. If no key pair is given,
228 default one is used.
232 default one is used.
229 """
233 """
230
234
231 if key_pair:
235 if key_pair:
232 key = key_pair
236 key = key_pair
233 else:
237 else:
234 try:
238 try:
235 key = KeyPair.objects.get(primary=True)
239 key = KeyPair.objects.get(primary=True)
236 except KeyPair.DoesNotExist:
240 except KeyPair.DoesNotExist:
237 # Do not update the global id because there is no key defined
241 # Do not update the global id because there is no key defined
238 return
242 return
239 global_id = GlobalId(key_type=key.key_type,
243 global_id = GlobalId(key_type=key.key_type,
240 key=key.public_key,
244 key=key.public_key,
241 local_id=self.id)
245 local_id=self.id)
242 global_id.save()
246 global_id.save()
243
247
244 self.global_id = global_id
248 self.global_id = global_id
245
249
246 self.save(update_fields=['global_id'])
250 self.save(update_fields=['global_id'])
247
251
248 def get_pub_time_str(self):
252 def get_pub_time_str(self):
249 return str(self.pub_time)
253 return str(self.pub_time)
250
254
251 def get_replied_ids(self):
255 def get_replied_ids(self):
252 """
256 """
253 Gets ID list of the posts that this post replies.
257 Gets ID list of the posts that this post replies.
254 """
258 """
255
259
256 raw_text = self.get_raw_text()
260 raw_text = self.get_raw_text()
257
261
258 local_replied = REGEX_REPLY.findall(raw_text)
262 local_replied = REGEX_REPLY.findall(raw_text)
259 global_replied = []
263 global_replied = []
260 for match in REGEX_GLOBAL_REPLY.findall(raw_text):
264 for match in REGEX_GLOBAL_REPLY.findall(raw_text):
261 key_type = match[0]
265 key_type = match[0]
262 key = match[1]
266 key = match[1]
263 local_id = match[2]
267 local_id = match[2]
264
268
265 try:
269 try:
266 global_id = GlobalId.objects.get(key_type=key_type,
270 global_id = GlobalId.objects.get(key_type=key_type,
267 key=key, local_id=local_id)
271 key=key, local_id=local_id)
268 for post in Post.objects.filter(global_id=global_id).only('id'):
272 for post in Post.objects.filter(global_id=global_id).only('id'):
269 global_replied.append(post.id)
273 global_replied.append(post.id)
270 except GlobalId.DoesNotExist:
274 except GlobalId.DoesNotExist:
271 pass
275 pass
272 return local_replied + global_replied
276 return local_replied + global_replied
273
277
274 def get_post_data(self, format_type=DIFF_TYPE_JSON, request=None,
278 def get_post_data(self, format_type=DIFF_TYPE_JSON, request=None,
275 include_last_update=False) -> str:
279 include_last_update=False) -> str:
276 """
280 """
277 Gets post HTML or JSON data that can be rendered on a page or used by
281 Gets post HTML or JSON data that can be rendered on a page or used by
278 API.
282 API.
279 """
283 """
280
284
281 return get_exporter(format_type).export(self, request,
285 return get_exporter(format_type).export(self, request,
282 include_last_update)
286 include_last_update)
283
287
284 def notify_clients(self, recursive=True):
288 def notify_clients(self, recursive=True):
285 """
289 """
286 Sends post HTML data to the thread web socket.
290 Sends post HTML data to the thread web socket.
287 """
291 """
288
292
289 if not settings.get_bool('External', 'WebsocketsEnabled'):
293 if not settings.get_bool('External', 'WebsocketsEnabled'):
290 return
294 return
291
295
292 thread_ids = list()
296 thread_ids = list()
293 for thread in self.get_threads().all():
297 for thread in self.get_threads().all():
294 thread_ids.append(thread.id)
298 thread_ids.append(thread.id)
295
299
296 thread.notify_clients()
300 thread.notify_clients()
297
301
298 if recursive:
302 if recursive:
299 for reply_number in re.finditer(REGEX_REPLY, self.get_raw_text()):
303 for reply_number in re.finditer(REGEX_REPLY, self.get_raw_text()):
300 post_id = reply_number.group(1)
304 post_id = reply_number.group(1)
301
305
302 try:
306 try:
303 ref_post = Post.objects.get(id=post_id)
307 ref_post = Post.objects.get(id=post_id)
304
308
305 if ref_post.get_threads().exclude(id__in=thread_ids).exists():
309 if ref_post.get_threads().exclude(id__in=thread_ids).exists():
306 # If post is in this thread, its thread was already notified.
310 # If post is in this thread, its thread was already notified.
307 # Otherwise, notify its thread separately.
311 # Otherwise, notify its thread separately.
308 ref_post.notify_clients(recursive=False)
312 ref_post.notify_clients(recursive=False)
309 except ObjectDoesNotExist:
313 except ObjectDoesNotExist:
310 pass
314 pass
311
315
312 def build_url(self):
316 def build_url(self):
313 self.url = self.get_absolute_url()
317 self.url = self.get_absolute_url()
314 self.save(update_fields=['url'])
318 self.save(update_fields=['url'])
315
319
316 def save(self, force_insert=False, force_update=False, using=None,
320 def save(self, force_insert=False, force_update=False, using=None,
317 update_fields=None):
321 update_fields=None):
318 new_post = self.id is None
322 new_post = self.id is None
319
323
320 self.uid = str(uuid.uuid4())
324 self.uid = str(uuid.uuid4())
321 if update_fields is not None and 'uid' not in update_fields:
325 if update_fields is not None and 'uid' not in update_fields:
322 update_fields += ['uid']
326 update_fields += ['uid']
323
327
324 if not new_post:
328 if not new_post:
325 for thread in self.get_threads().all():
329 for thread in self.get_threads().all():
326 thread.last_edit_time = self.last_edit_time
330 thread.last_edit_time = self.last_edit_time
327
331
328 thread.save(update_fields=['last_edit_time', 'status'])
332 thread.save(update_fields=['last_edit_time', 'status'])
329
333
330 super().save(force_insert, force_update, using, update_fields)
334 super().save(force_insert, force_update, using, update_fields)
331
335
332 if self.url is None:
336 if self.url is None:
333 self.build_url()
337 self.build_url()
334
338
335 def get_text(self) -> str:
339 def get_text(self) -> str:
336 return self._text_rendered
340 return self._text_rendered
337
341
338 def get_raw_text(self) -> str:
342 def get_raw_text(self) -> str:
339 return self.text
343 return self.text
340
344
341 def get_sync_text(self) -> str:
345 def get_sync_text(self) -> str:
342 """
346 """
343 Returns text applicable for sync. It has absolute post reflinks.
347 Returns text applicable for sync. It has absolute post reflinks.
344 """
348 """
345
349
346 replacements = dict()
350 replacements = dict()
347 for post_id in REGEX_REPLY.findall(self.get_raw_text()):
351 for post_id in REGEX_REPLY.findall(self.get_raw_text()):
348 try:
352 try:
349 absolute_post_id = str(Post.objects.get(id=post_id).global_id)
353 absolute_post_id = str(Post.objects.get(id=post_id).global_id)
350 replacements[post_id] = absolute_post_id
354 replacements[post_id] = absolute_post_id
351 except Post.DoesNotExist:
355 except Post.DoesNotExist:
352 pass
356 pass
353
357
354 text = self.get_raw_text() or ''
358 text = self.get_raw_text() or ''
355 for key in replacements:
359 for key in replacements:
356 text = text.replace('[post]{}[/post]'.format(key),
360 text = text.replace('[post]{}[/post]'.format(key),
357 '[post]{}[/post]'.format(replacements[key]))
361 '[post]{}[/post]'.format(replacements[key]))
358 text = text.replace('\r\n', '\n').replace('\r', '\n')
362 text = text.replace('\r\n', '\n').replace('\r', '\n')
359
363
360 return text
364 return text
361
365
362 def connect_threads(self, opening_posts):
366 def connect_threads(self, opening_posts):
363 for opening_post in opening_posts:
367 for opening_post in opening_posts:
364 threads = opening_post.get_threads().all()
368 threads = opening_post.get_threads().all()
365 for thread in threads:
369 for thread in threads:
366 if thread.can_bump():
370 if thread.can_bump():
367 thread.update_bump_status()
371 thread.update_bump_status()
368
372
369 thread.last_edit_time = self.last_edit_time
373 thread.last_edit_time = self.last_edit_time
370 thread.save(update_fields=['last_edit_time', 'status'])
374 thread.save(update_fields=['last_edit_time', 'status'])
371 self.threads.add(opening_post.get_thread())
375 self.threads.add(opening_post.get_thread())
372
376
373 def get_tripcode(self):
377 def get_tripcode(self):
374 if self.tripcode:
378 if self.tripcode:
375 return Tripcode(self.tripcode)
379 return Tripcode(self.tripcode)
376
380
377 def get_link_view(self):
381 def get_link_view(self):
378 """
382 """
379 Gets view of a reflink to the post.
383 Gets view of a reflink to the post.
380 """
384 """
381 result = '<a href="{}">&gt;&gt;{}</a>'.format(self.get_absolute_url(),
385 result = '<a href="{}">&gt;&gt;{}</a>'.format(self.get_absolute_url(),
382 self.id)
386 self.id)
383 if self.is_opening():
387 if self.is_opening():
384 result = '<b>{}</b>'.format(result)
388 result = '<b>{}</b>'.format(result)
385
389
386 return result
390 return result
387
391
388 def is_hidden(self) -> bool:
392 def is_hidden(self) -> bool:
389 return self.hidden
393 return self.hidden
390
394
391 def set_hidden(self, hidden):
395 def set_hidden(self, hidden):
392 self.hidden = hidden
396 self.hidden = hidden
393
397
394 def increment_version(self):
398 def increment_version(self):
395 self.version = F('version') + 1
399 self.version = F('version') + 1
396
400
397 def clear_cache(self):
401 def clear_cache(self):
398 """
402 """
399 Clears sync data (content cache, signatures etc).
403 Clears sync data (content cache, signatures etc).
400 """
404 """
401 global_id = self.global_id
405 global_id = self.global_id
402 if global_id is not None and global_id.is_local()\
406 if global_id is not None and global_id.is_local()\
403 and global_id.content is not None:
407 and global_id.content is not None:
404 global_id.clear_cache()
408 global_id.clear_cache()
405
409
406 def get_tags(self):
410 def get_tags(self):
407 return self.get_thread().get_tags()
411 return self.get_thread().get_tags()
408
412
409 def get_ip_color(self):
413 def get_ip_color(self):
410 return hashlib.md5(self.poster_ip.encode()).hexdigest()[:6]
414 return hashlib.md5(self.poster_ip.encode()).hexdigest()[:6]
General Comments 0
You need to be logged in to leave comments. Login now