##// END OF EJS Templates
Fixed issues with new images storage
neko259 -
r1591:af1d8bd1 default
parent child Browse files
Show More
@@ -1,150 +1,150 b''
1 import hashlib
1 import hashlib
2 from boards.models.attachment import FILE_TYPES_IMAGE
2 from boards.models.attachment import FILE_TYPES_IMAGE
3 from django.template.loader import render_to_string
3 from django.template.loader import render_to_string
4 from django.db import models
4 from django.db import models
5 from django.db.models import Count
5 from django.db.models import Count
6 from django.core.urlresolvers import reverse
6 from django.core.urlresolvers import reverse
7
7
8 from boards.models import Attachment
8 from boards.models import Attachment
9 from boards.models.base import Viewable
9 from boards.models.base import Viewable
10 from boards.models.thread import STATUS_ACTIVE, STATUS_BUMPLIMIT, STATUS_ARCHIVE
10 from boards.models.thread import STATUS_ACTIVE, STATUS_BUMPLIMIT, STATUS_ARCHIVE
11 from boards.utils import cached_result
11 from boards.utils import cached_result
12 import boards
12 import boards
13
13
14 __author__ = 'neko259'
14 __author__ = 'neko259'
15
15
16
16
17 RELATED_TAGS_COUNT = 5
17 RELATED_TAGS_COUNT = 5
18
18
19
19
20 class TagManager(models.Manager):
20 class TagManager(models.Manager):
21
21
22 def get_not_empty_tags(self):
22 def get_not_empty_tags(self):
23 """
23 """
24 Gets tags that have non-archived threads.
24 Gets tags that have non-archived threads.
25 """
25 """
26
26
27 return self.annotate(num_threads=Count('thread_tags')).filter(num_threads__gt=0)\
27 return self.annotate(num_threads=Count('thread_tags')).filter(num_threads__gt=0)\
28 .order_by('-required', 'name')
28 .order_by('-required', 'name')
29
29
30 def get_tag_url_list(self, tags: list) -> str:
30 def get_tag_url_list(self, tags: list) -> str:
31 """
31 """
32 Gets a comma-separated list of tag links.
32 Gets a comma-separated list of tag links.
33 """
33 """
34
34
35 return ', '.join([tag.get_view() for tag in tags])
35 return ', '.join([tag.get_view() for tag in tags])
36
36
37
37
38 class Tag(models.Model, Viewable):
38 class Tag(models.Model, Viewable):
39 """
39 """
40 A tag is a text node assigned to the thread. The tag serves as a board
40 A tag is a text node assigned to the thread. The tag serves as a board
41 section. There can be multiple tags for each thread
41 section. There can be multiple tags for each thread
42 """
42 """
43
43
44 objects = TagManager()
44 objects = TagManager()
45
45
46 class Meta:
46 class Meta:
47 app_label = 'boards'
47 app_label = 'boards'
48 ordering = ('name',)
48 ordering = ('name',)
49
49
50 name = models.CharField(max_length=100, db_index=True, unique=True)
50 name = models.CharField(max_length=100, db_index=True, unique=True)
51 required = models.BooleanField(default=False, db_index=True)
51 required = models.BooleanField(default=False, db_index=True)
52 description = models.TextField(blank=True)
52 description = models.TextField(blank=True)
53
53
54 parent = models.ForeignKey('Tag', null=True, blank=True,
54 parent = models.ForeignKey('Tag', null=True, blank=True,
55 related_name='children')
55 related_name='children')
56
56
57 def __str__(self):
57 def __str__(self):
58 return self.name
58 return self.name
59
59
60 def is_empty(self) -> bool:
60 def is_empty(self) -> bool:
61 """
61 """
62 Checks if the tag has some threads.
62 Checks if the tag has some threads.
63 """
63 """
64
64
65 return self.get_thread_count() == 0
65 return self.get_thread_count() == 0
66
66
67 def get_thread_count(self, status=None) -> int:
67 def get_thread_count(self, status=None) -> int:
68 threads = self.get_threads()
68 threads = self.get_threads()
69 if status is not None:
69 if status is not None:
70 threads = threads.filter(status=status)
70 threads = threads.filter(status=status)
71 return threads.count()
71 return threads.count()
72
72
73 def get_active_thread_count(self) -> int:
73 def get_active_thread_count(self) -> int:
74 return self.get_thread_count(status=STATUS_ACTIVE)
74 return self.get_thread_count(status=STATUS_ACTIVE)
75
75
76 def get_bumplimit_thread_count(self) -> int:
76 def get_bumplimit_thread_count(self) -> int:
77 return self.get_thread_count(status=STATUS_BUMPLIMIT)
77 return self.get_thread_count(status=STATUS_BUMPLIMIT)
78
78
79 def get_archived_thread_count(self) -> int:
79 def get_archived_thread_count(self) -> int:
80 return self.get_thread_count(status=STATUS_ARCHIVE)
80 return self.get_thread_count(status=STATUS_ARCHIVE)
81
81
82 def get_absolute_url(self):
82 def get_absolute_url(self):
83 return reverse('tag', kwargs={'tag_name': self.name})
83 return reverse('tag', kwargs={'tag_name': self.name})
84
84
85 def get_threads(self):
85 def get_threads(self):
86 return self.thread_tags.order_by('-bump_time')
86 return self.thread_tags.order_by('-bump_time')
87
87
88 def is_required(self):
88 def is_required(self):
89 return self.required
89 return self.required
90
90
91 def get_view(self):
91 def get_view(self):
92 link = '<a class="tag" href="{}">{}</a>'.format(
92 link = '<a class="tag" href="{}">{}</a>'.format(
93 self.get_absolute_url(), self.name)
93 self.get_absolute_url(), self.name)
94 if self.is_required():
94 if self.is_required():
95 link = '<b>{}</b>'.format(link)
95 link = '<b>{}</b>'.format(link)
96 return link
96 return link
97
97
98 def get_search_view(self, *args, **kwargs):
98 def get_search_view(self, *args, **kwargs):
99 return render_to_string('boards/tag.html', {
99 return render_to_string('boards/tag.html', {
100 'tag': self,
100 'tag': self,
101 })
101 })
102
102
103 @cached_result()
103 @cached_result()
104 def get_post_count(self):
104 def get_post_count(self):
105 return self.get_threads().aggregate(num_posts=Count('multi_replies'))['num_posts']
105 return self.get_threads().aggregate(num_posts=Count('multi_replies'))['num_posts']
106
106
107 def get_description(self):
107 def get_description(self):
108 return self.description
108 return self.description
109
109
110 def get_random_image_post(self, status=[STATUS_ACTIVE, STATUS_BUMPLIMIT]):
110 def get_random_image_post(self, status=[STATUS_ACTIVE, STATUS_BUMPLIMIT]):
111 posts = boards.models.Post.objects.filter(attachments__mimetype__in=FILE_TYPES_IMAGE)\
111 posts = boards.models.Post.objects.filter(attachments__mimetype__in=FILE_TYPES_IMAGE)\
112 .annotate(images_count=Count(
112 .annotate(images_count=Count(
113 'attachments')).filter(images_count__gt=0, threads__tags__in=[self])
113 'attachments')).filter(images_count__gt=0, threads__tags__in=[self])
114 if status is not None:
114 if status is not None:
115 posts = posts.filter(thread__status__in=status)
115 posts = posts.filter(thread__status__in=status)
116 return posts.order_by('?').first()
116 return posts.order_by('?').first()
117
117
118 def get_first_letter(self):
118 def get_first_letter(self):
119 return self.name and self.name[0] or ''
119 return self.name and self.name[0] or ''
120
120
121 def get_related_tags(self):
121 def get_related_tags(self):
122 return set(Tag.objects.filter(thread_tags__in=self.get_threads()).exclude(
122 return set(Tag.objects.filter(thread_tags__in=self.get_threads()).exclude(
123 id=self.id).order_by('?')[:RELATED_TAGS_COUNT])
123 id=self.id).order_by('?')[:RELATED_TAGS_COUNT])
124
124
125 @cached_result()
125 @cached_result()
126 def get_color(self):
126 def get_color(self):
127 """
127 """
128 Gets color hashed from the tag name.
128 Gets color hashed from the tag name.
129 """
129 """
130 return hashlib.md5(self.name.encode()).hexdigest()[:6]
130 return hashlib.md5(self.name.encode()).hexdigest()[:6]
131
131
132 def get_parent(self):
132 def get_parent(self):
133 return self.parent
133 return self.parent
134
134
135 def get_all_parents(self):
135 def get_all_parents(self):
136 parents = list()
136 parents = list()
137 parent = self.get_parent()
137 parent = self.get_parent()
138 if parent and parent not in parents:
138 if parent and parent not in parents:
139 parents.insert(0, parent)
139 parents.insert(0, parent)
140 parents = parent.get_all_parents() + parents
140 parents = parent.get_all_parents() + parents
141
141
142 return parents
142 return parents
143
143
144 def get_children(self):
144 def get_children(self):
145 return self.children
145 return self.children
146
146
147 def get_images(self):
147 def get_images(self):
148 return Attachment.objects.filter(
148 return Attachment.objects.filter(
149 post_attachments__thread__tags__in=[self]).filter(
149 attachment_posts__thread__tags__in=[self]).filter(
150 mimetype__in=FILE_TYPES_IMAGE).order_by('-post_images__pub_time') No newline at end of file
150 mimetype__in=FILE_TYPES_IMAGE).order_by('-attachment_posts__pub_time') No newline at end of file
@@ -1,325 +1,325 b''
1 import logging
1 import logging
2 from adjacent import Client
2 from adjacent import Client
3 from boards.models.attachment import FILE_TYPES_IMAGE
3 from boards.models.attachment import FILE_TYPES_IMAGE
4
4
5 from django.db.models import Count, Sum, QuerySet, Q
5 from django.db.models import Count, Sum, QuerySet, Q
6 from django.utils import timezone
6 from django.utils import timezone
7 from django.db import models, transaction
7 from django.db import models, transaction
8
8
9 from boards.models import STATUS_BUMPLIMIT, STATUS_ACTIVE, STATUS_ARCHIVE
9 from boards.models import STATUS_BUMPLIMIT, STATUS_ACTIVE, STATUS_ARCHIVE
10
10
11 from boards import settings
11 from boards import settings
12 import boards
12 import boards
13 from boards.utils import cached_result, datetime_to_epoch
13 from boards.utils import cached_result, datetime_to_epoch
14 from boards.models.post import Post
14 from boards.models.post import Post
15 from boards.models.tag import Tag
15 from boards.models.tag import Tag
16
16
17 FAV_THREAD_NO_UPDATES = -1
17 FAV_THREAD_NO_UPDATES = -1
18
18
19
19
20 __author__ = 'neko259'
20 __author__ = 'neko259'
21
21
22
22
23 logger = logging.getLogger(__name__)
23 logger = logging.getLogger(__name__)
24
24
25
25
26 WS_NOTIFICATION_TYPE_NEW_POST = 'new_post'
26 WS_NOTIFICATION_TYPE_NEW_POST = 'new_post'
27 WS_NOTIFICATION_TYPE = 'notification_type'
27 WS_NOTIFICATION_TYPE = 'notification_type'
28
28
29 WS_CHANNEL_THREAD = "thread:"
29 WS_CHANNEL_THREAD = "thread:"
30
30
31 STATUS_CHOICES = (
31 STATUS_CHOICES = (
32 (STATUS_ACTIVE, STATUS_ACTIVE),
32 (STATUS_ACTIVE, STATUS_ACTIVE),
33 (STATUS_BUMPLIMIT, STATUS_BUMPLIMIT),
33 (STATUS_BUMPLIMIT, STATUS_BUMPLIMIT),
34 (STATUS_ARCHIVE, STATUS_ARCHIVE),
34 (STATUS_ARCHIVE, STATUS_ARCHIVE),
35 )
35 )
36
36
37
37
38 class ThreadManager(models.Manager):
38 class ThreadManager(models.Manager):
39 def process_oldest_threads(self):
39 def process_oldest_threads(self):
40 """
40 """
41 Preserves maximum thread count. If there are too many threads,
41 Preserves maximum thread count. If there are too many threads,
42 archive or delete the old ones.
42 archive or delete the old ones.
43 """
43 """
44
44
45 threads = Thread.objects.exclude(status=STATUS_ARCHIVE).order_by('-bump_time')
45 threads = Thread.objects.exclude(status=STATUS_ARCHIVE).order_by('-bump_time')
46 thread_count = threads.count()
46 thread_count = threads.count()
47
47
48 max_thread_count = settings.get_int('Messages', 'MaxThreadCount')
48 max_thread_count = settings.get_int('Messages', 'MaxThreadCount')
49 if thread_count > max_thread_count:
49 if thread_count > max_thread_count:
50 num_threads_to_delete = thread_count - max_thread_count
50 num_threads_to_delete = thread_count - max_thread_count
51 old_threads = threads[thread_count - num_threads_to_delete:]
51 old_threads = threads[thread_count - num_threads_to_delete:]
52
52
53 for thread in old_threads:
53 for thread in old_threads:
54 if settings.get_bool('Storage', 'ArchiveThreads'):
54 if settings.get_bool('Storage', 'ArchiveThreads'):
55 self._archive_thread(thread)
55 self._archive_thread(thread)
56 else:
56 else:
57 thread.delete()
57 thread.delete()
58
58
59 logger.info('Processed %d old threads' % num_threads_to_delete)
59 logger.info('Processed %d old threads' % num_threads_to_delete)
60
60
61 def _archive_thread(self, thread):
61 def _archive_thread(self, thread):
62 thread.status = STATUS_ARCHIVE
62 thread.status = STATUS_ARCHIVE
63 thread.last_edit_time = timezone.now()
63 thread.last_edit_time = timezone.now()
64 thread.update_posts_time()
64 thread.update_posts_time()
65 thread.save(update_fields=['last_edit_time', 'status'])
65 thread.save(update_fields=['last_edit_time', 'status'])
66
66
67 def get_new_posts(self, datas):
67 def get_new_posts(self, datas):
68 query = None
68 query = None
69 # TODO Use classes instead of dicts
69 # TODO Use classes instead of dicts
70 for data in datas:
70 for data in datas:
71 if data['last_id'] != FAV_THREAD_NO_UPDATES:
71 if data['last_id'] != FAV_THREAD_NO_UPDATES:
72 q = (Q(id=data['op'].get_thread_id())
72 q = (Q(id=data['op'].get_thread_id())
73 & Q(multi_replies__id__gt=data['last_id']))
73 & Q(multi_replies__id__gt=data['last_id']))
74 if query is None:
74 if query is None:
75 query = q
75 query = q
76 else:
76 else:
77 query = query | q
77 query = query | q
78 if query is not None:
78 if query is not None:
79 return self.filter(query).annotate(
79 return self.filter(query).annotate(
80 new_post_count=Count('multi_replies'))
80 new_post_count=Count('multi_replies'))
81
81
82 def get_new_post_count(self, datas):
82 def get_new_post_count(self, datas):
83 new_posts = self.get_new_posts(datas)
83 new_posts = self.get_new_posts(datas)
84 return new_posts.aggregate(total_count=Count('multi_replies'))\
84 return new_posts.aggregate(total_count=Count('multi_replies'))\
85 ['total_count'] if new_posts else 0
85 ['total_count'] if new_posts else 0
86
86
87
87
88 def get_thread_max_posts():
88 def get_thread_max_posts():
89 return settings.get_int('Messages', 'MaxPostsPerThread')
89 return settings.get_int('Messages', 'MaxPostsPerThread')
90
90
91
91
92 class Thread(models.Model):
92 class Thread(models.Model):
93 objects = ThreadManager()
93 objects = ThreadManager()
94
94
95 class Meta:
95 class Meta:
96 app_label = 'boards'
96 app_label = 'boards'
97
97
98 tags = models.ManyToManyField('Tag', related_name='thread_tags')
98 tags = models.ManyToManyField('Tag', related_name='thread_tags')
99 bump_time = models.DateTimeField(db_index=True)
99 bump_time = models.DateTimeField(db_index=True)
100 last_edit_time = models.DateTimeField()
100 last_edit_time = models.DateTimeField()
101 max_posts = models.IntegerField(default=get_thread_max_posts)
101 max_posts = models.IntegerField(default=get_thread_max_posts)
102 status = models.CharField(max_length=50, default=STATUS_ACTIVE,
102 status = models.CharField(max_length=50, default=STATUS_ACTIVE,
103 choices=STATUS_CHOICES)
103 choices=STATUS_CHOICES)
104 monochrome = models.BooleanField(default=False)
104 monochrome = models.BooleanField(default=False)
105
105
106 def get_tags(self) -> QuerySet:
106 def get_tags(self) -> QuerySet:
107 """
107 """
108 Gets a sorted tag list.
108 Gets a sorted tag list.
109 """
109 """
110
110
111 return self.tags.order_by('name')
111 return self.tags.order_by('name')
112
112
113 def bump(self):
113 def bump(self):
114 """
114 """
115 Bumps (moves to up) thread if possible.
115 Bumps (moves to up) thread if possible.
116 """
116 """
117
117
118 if self.can_bump():
118 if self.can_bump():
119 self.bump_time = self.last_edit_time
119 self.bump_time = self.last_edit_time
120
120
121 self.update_bump_status()
121 self.update_bump_status()
122
122
123 logger.info('Bumped thread %d' % self.id)
123 logger.info('Bumped thread %d' % self.id)
124
124
125 def has_post_limit(self) -> bool:
125 def has_post_limit(self) -> bool:
126 return self.max_posts > 0
126 return self.max_posts > 0
127
127
128 def update_bump_status(self, exclude_posts=None):
128 def update_bump_status(self, exclude_posts=None):
129 if self.has_post_limit() and self.get_reply_count() >= self.max_posts:
129 if self.has_post_limit() and self.get_reply_count() >= self.max_posts:
130 self.status = STATUS_BUMPLIMIT
130 self.status = STATUS_BUMPLIMIT
131 self.update_posts_time(exclude_posts=exclude_posts)
131 self.update_posts_time(exclude_posts=exclude_posts)
132
132
133 def _get_cache_key(self):
133 def _get_cache_key(self):
134 return [datetime_to_epoch(self.last_edit_time)]
134 return [datetime_to_epoch(self.last_edit_time)]
135
135
136 @cached_result(key_method=_get_cache_key)
136 @cached_result(key_method=_get_cache_key)
137 def get_reply_count(self) -> int:
137 def get_reply_count(self) -> int:
138 return self.get_replies().count()
138 return self.get_replies().count()
139
139
140 @cached_result(key_method=_get_cache_key)
140 @cached_result(key_method=_get_cache_key)
141 def get_images_count(self) -> int:
141 def get_images_count(self) -> int:
142 return self.get_replies().filter(
142 return self.get_replies().filter(
143 attachments__mimetype__in=FILE_TYPES_IMAGE)\
143 attachments__mimetype__in=FILE_TYPES_IMAGE)\
144 .annotate(images_count=Count(
144 .annotate(images_count=Count(
145 'attachments')).aggregate(Sum('images_count'))['images_count__sum']
145 'attachments')).aggregate(Sum('images_count'))['images_count__sum'] or 0
146
146
147 def can_bump(self) -> bool:
147 def can_bump(self) -> bool:
148 """
148 """
149 Checks if the thread can be bumped by replying to it.
149 Checks if the thread can be bumped by replying to it.
150 """
150 """
151
151
152 return self.get_status() == STATUS_ACTIVE
152 return self.get_status() == STATUS_ACTIVE
153
153
154 def get_last_replies(self) -> QuerySet:
154 def get_last_replies(self) -> QuerySet:
155 """
155 """
156 Gets several last replies, not including opening post
156 Gets several last replies, not including opening post
157 """
157 """
158
158
159 last_replies_count = settings.get_int('View', 'LastRepliesCount')
159 last_replies_count = settings.get_int('View', 'LastRepliesCount')
160
160
161 if last_replies_count > 0:
161 if last_replies_count > 0:
162 reply_count = self.get_reply_count()
162 reply_count = self.get_reply_count()
163
163
164 if reply_count > 0:
164 if reply_count > 0:
165 reply_count_to_show = min(last_replies_count,
165 reply_count_to_show = min(last_replies_count,
166 reply_count - 1)
166 reply_count - 1)
167 replies = self.get_replies()
167 replies = self.get_replies()
168 last_replies = replies[reply_count - reply_count_to_show:]
168 last_replies = replies[reply_count - reply_count_to_show:]
169
169
170 return last_replies
170 return last_replies
171
171
172 def get_skipped_replies_count(self) -> int:
172 def get_skipped_replies_count(self) -> int:
173 """
173 """
174 Gets number of posts between opening post and last replies.
174 Gets number of posts between opening post and last replies.
175 """
175 """
176 reply_count = self.get_reply_count()
176 reply_count = self.get_reply_count()
177 last_replies_count = min(settings.get_int('View', 'LastRepliesCount'),
177 last_replies_count = min(settings.get_int('View', 'LastRepliesCount'),
178 reply_count - 1)
178 reply_count - 1)
179 return reply_count - last_replies_count - 1
179 return reply_count - last_replies_count - 1
180
180
181 def get_replies(self, view_fields_only=False) -> QuerySet:
181 def get_replies(self, view_fields_only=False) -> QuerySet:
182 """
182 """
183 Gets sorted thread posts
183 Gets sorted thread posts
184 """
184 """
185
185
186 query = self.multi_replies.order_by('pub_time').prefetch_related(
186 query = self.multi_replies.order_by('pub_time').prefetch_related(
187 'thread', 'attachments')
187 'thread', 'attachments')
188 if view_fields_only:
188 if view_fields_only:
189 query = query.defer('poster_ip')
189 query = query.defer('poster_ip')
190 return query
190 return query
191
191
192 def get_top_level_replies(self) -> QuerySet:
192 def get_top_level_replies(self) -> QuerySet:
193 return self.get_replies().exclude(refposts__threads__in=[self])
193 return self.get_replies().exclude(refposts__threads__in=[self])
194
194
195 def get_replies_with_images(self, view_fields_only=False) -> QuerySet:
195 def get_replies_with_images(self, view_fields_only=False) -> QuerySet:
196 """
196 """
197 Gets replies that have at least one image attached
197 Gets replies that have at least one image attached
198 """
198 """
199 return self.get_replies(view_fields_only).filter(
199 return self.get_replies(view_fields_only).filter(
200 attachments__mimetype__in=FILE_TYPES_IMAGE).annotate(images_count=Count(
200 attachments__mimetype__in=FILE_TYPES_IMAGE).annotate(images_count=Count(
201 'attachments')).filter(images_count__gt=0)
201 'attachments')).filter(images_count__gt=0)
202
202
203 def get_opening_post(self, only_id=False) -> Post:
203 def get_opening_post(self, only_id=False) -> Post:
204 """
204 """
205 Gets the first post of the thread
205 Gets the first post of the thread
206 """
206 """
207
207
208 query = self.get_replies().filter(opening=True)
208 query = self.get_replies().filter(opening=True)
209 if only_id:
209 if only_id:
210 query = query.only('id')
210 query = query.only('id')
211 opening_post = query.first()
211 opening_post = query.first()
212
212
213 return opening_post
213 return opening_post
214
214
215 @cached_result()
215 @cached_result()
216 def get_opening_post_id(self) -> int:
216 def get_opening_post_id(self) -> int:
217 """
217 """
218 Gets ID of the first thread post.
218 Gets ID of the first thread post.
219 """
219 """
220
220
221 return self.get_opening_post(only_id=True).id
221 return self.get_opening_post(only_id=True).id
222
222
223 def get_pub_time(self):
223 def get_pub_time(self):
224 """
224 """
225 Gets opening post's pub time because thread does not have its own one.
225 Gets opening post's pub time because thread does not have its own one.
226 """
226 """
227
227
228 return self.get_opening_post().pub_time
228 return self.get_opening_post().pub_time
229
229
230 def __str__(self):
230 def __str__(self):
231 return 'T#{}/{}'.format(self.id, self.get_opening_post_id())
231 return 'T#{}/{}'.format(self.id, self.get_opening_post_id())
232
232
233 def get_tag_url_list(self) -> list:
233 def get_tag_url_list(self) -> list:
234 return boards.models.Tag.objects.get_tag_url_list(self.get_tags())
234 return boards.models.Tag.objects.get_tag_url_list(self.get_tags())
235
235
236 def update_posts_time(self, exclude_posts=None):
236 def update_posts_time(self, exclude_posts=None):
237 last_edit_time = self.last_edit_time
237 last_edit_time = self.last_edit_time
238
238
239 for post in self.multi_replies.all():
239 for post in self.multi_replies.all():
240 if exclude_posts is None or post not in exclude_posts:
240 if exclude_posts is None or post not in exclude_posts:
241 # Manual update is required because uids are generated on save
241 # Manual update is required because uids are generated on save
242 post.last_edit_time = last_edit_time
242 post.last_edit_time = last_edit_time
243 post.save(update_fields=['last_edit_time'])
243 post.save(update_fields=['last_edit_time'])
244
244
245 post.get_threads().update(last_edit_time=last_edit_time)
245 post.get_threads().update(last_edit_time=last_edit_time)
246
246
247 def notify_clients(self):
247 def notify_clients(self):
248 if not settings.get_bool('External', 'WebsocketsEnabled'):
248 if not settings.get_bool('External', 'WebsocketsEnabled'):
249 return
249 return
250
250
251 client = Client()
251 client = Client()
252
252
253 channel_name = WS_CHANNEL_THREAD + str(self.get_opening_post_id())
253 channel_name = WS_CHANNEL_THREAD + str(self.get_opening_post_id())
254 client.publish(channel_name, {
254 client.publish(channel_name, {
255 WS_NOTIFICATION_TYPE: WS_NOTIFICATION_TYPE_NEW_POST,
255 WS_NOTIFICATION_TYPE: WS_NOTIFICATION_TYPE_NEW_POST,
256 })
256 })
257 client.send()
257 client.send()
258
258
259 def get_absolute_url(self):
259 def get_absolute_url(self):
260 return self.get_opening_post().get_absolute_url()
260 return self.get_opening_post().get_absolute_url()
261
261
262 def get_required_tags(self):
262 def get_required_tags(self):
263 return self.get_tags().filter(required=True)
263 return self.get_tags().filter(required=True)
264
264
265 def get_replies_newer(self, post_id):
265 def get_replies_newer(self, post_id):
266 return self.get_replies().filter(id__gt=post_id)
266 return self.get_replies().filter(id__gt=post_id)
267
267
268 def is_archived(self):
268 def is_archived(self):
269 return self.get_status() == STATUS_ARCHIVE
269 return self.get_status() == STATUS_ARCHIVE
270
270
271 def get_status(self):
271 def get_status(self):
272 return self.status
272 return self.status
273
273
274 def is_monochrome(self):
274 def is_monochrome(self):
275 return self.monochrome
275 return self.monochrome
276
276
277 # If tags have parent, add them to the tag list
277 # If tags have parent, add them to the tag list
278 @transaction.atomic
278 @transaction.atomic
279 def refresh_tags(self):
279 def refresh_tags(self):
280 for tag in self.get_tags().all():
280 for tag in self.get_tags().all():
281 parents = tag.get_all_parents()
281 parents = tag.get_all_parents()
282 if len(parents) > 0:
282 if len(parents) > 0:
283 self.tags.add(*parents)
283 self.tags.add(*parents)
284
284
285 def get_reply_tree(self):
285 def get_reply_tree(self):
286 replies = self.get_replies().prefetch_related('refposts')
286 replies = self.get_replies().prefetch_related('refposts')
287 tree = []
287 tree = []
288 for reply in replies:
288 for reply in replies:
289 parents = reply.refposts.all()
289 parents = reply.refposts.all()
290
290
291 found_parent = False
291 found_parent = False
292 searching_for_index = False
292 searching_for_index = False
293
293
294 if len(parents) > 0:
294 if len(parents) > 0:
295 index = 0
295 index = 0
296 parent_depth = 0
296 parent_depth = 0
297
297
298 indexes_to_insert = []
298 indexes_to_insert = []
299
299
300 for depth, element in tree:
300 for depth, element in tree:
301 index += 1
301 index += 1
302
302
303 # If this element is next after parent on the same level,
303 # If this element is next after parent on the same level,
304 # insert child before it
304 # insert child before it
305 if searching_for_index and depth <= parent_depth:
305 if searching_for_index and depth <= parent_depth:
306 indexes_to_insert.append((index - 1, parent_depth))
306 indexes_to_insert.append((index - 1, parent_depth))
307 searching_for_index = False
307 searching_for_index = False
308
308
309 if element in parents:
309 if element in parents:
310 found_parent = True
310 found_parent = True
311 searching_for_index = True
311 searching_for_index = True
312 parent_depth = depth
312 parent_depth = depth
313
313
314 if not found_parent:
314 if not found_parent:
315 tree.append((0, reply))
315 tree.append((0, reply))
316 else:
316 else:
317 if searching_for_index:
317 if searching_for_index:
318 tree.append((parent_depth + 1, reply))
318 tree.append((parent_depth + 1, reply))
319
319
320 offset = 0
320 offset = 0
321 for last_index, parent_depth in indexes_to_insert:
321 for last_index, parent_depth in indexes_to_insert:
322 tree.insert(last_index + offset, (parent_depth + 1, reply))
322 tree.insert(last_index + offset, (parent_depth + 1, reply))
323 offset += 1
323 offset += 1
324
324
325 return tree
325 return tree
General Comments 0
You need to be logged in to leave comments. Login now