##// END OF EJS Templates
Post deletion when the thread is deleted is already handled by django. No need to implement it manually
neko259 -
r1226:e8ff96e4 default
parent child Browse files
Show More
@@ -1,237 +1,227 b''
1 import logging
1 import logging
2 from adjacent import Client
2 from adjacent import Client
3
3
4 from django.db.models import Count, Sum, QuerySet
4 from django.db.models import Count, Sum, QuerySet
5 from django.utils import timezone
5 from django.utils import timezone
6 from django.db import models
6 from django.db import models
7
7
8 from boards import settings
8 from boards import settings
9 import boards
9 import boards
10 from boards.utils import cached_result, datetime_to_epoch
10 from boards.utils import cached_result, datetime_to_epoch
11 from boards.models.post import Post
11 from boards.models.post import Post
12 from boards.models.tag import Tag
12 from boards.models.tag import Tag
13
13
14
14
15 __author__ = 'neko259'
15 __author__ = 'neko259'
16
16
17
17
18 logger = logging.getLogger(__name__)
18 logger = logging.getLogger(__name__)
19
19
20
20
21 WS_NOTIFICATION_TYPE_NEW_POST = 'new_post'
21 WS_NOTIFICATION_TYPE_NEW_POST = 'new_post'
22 WS_NOTIFICATION_TYPE = 'notification_type'
22 WS_NOTIFICATION_TYPE = 'notification_type'
23
23
24 WS_CHANNEL_THREAD = "thread:"
24 WS_CHANNEL_THREAD = "thread:"
25
25
26
26
27 class ThreadManager(models.Manager):
27 class ThreadManager(models.Manager):
28 def process_oldest_threads(self):
28 def process_oldest_threads(self):
29 """
29 """
30 Preserves maximum thread count. If there are too many threads,
30 Preserves maximum thread count. If there are too many threads,
31 archive or delete the old ones.
31 archive or delete the old ones.
32 """
32 """
33
33
34 threads = Thread.objects.filter(archived=False).order_by('-bump_time')
34 threads = Thread.objects.filter(archived=False).order_by('-bump_time')
35 thread_count = threads.count()
35 thread_count = threads.count()
36
36
37 max_thread_count = settings.get_int('Messages', 'MaxThreadCount')
37 max_thread_count = settings.get_int('Messages', 'MaxThreadCount')
38 if thread_count > max_thread_count:
38 if thread_count > max_thread_count:
39 num_threads_to_delete = thread_count - max_thread_count
39 num_threads_to_delete = thread_count - max_thread_count
40 old_threads = threads[thread_count - num_threads_to_delete:]
40 old_threads = threads[thread_count - num_threads_to_delete:]
41
41
42 for thread in old_threads:
42 for thread in old_threads:
43 if settings.get_bool('Storage', 'ArchiveThreads'):
43 if settings.get_bool('Storage', 'ArchiveThreads'):
44 self._archive_thread(thread)
44 self._archive_thread(thread)
45 else:
45 else:
46 thread.delete()
46 thread.delete()
47
47
48 logger.info('Processed %d old threads' % num_threads_to_delete)
48 logger.info('Processed %d old threads' % num_threads_to_delete)
49
49
50 def _archive_thread(self, thread):
50 def _archive_thread(self, thread):
51 thread.archived = True
51 thread.archived = True
52 thread.bumpable = False
52 thread.bumpable = False
53 thread.last_edit_time = timezone.now()
53 thread.last_edit_time = timezone.now()
54 thread.update_posts_time()
54 thread.update_posts_time()
55 thread.save(update_fields=['archived', 'last_edit_time', 'bumpable'])
55 thread.save(update_fields=['archived', 'last_edit_time', 'bumpable'])
56
56
57
57
58 def get_thread_max_posts():
58 def get_thread_max_posts():
59 return settings.get_int('Messages', 'MaxPostsPerThread')
59 return settings.get_int('Messages', 'MaxPostsPerThread')
60
60
61
61
62 class Thread(models.Model):
62 class Thread(models.Model):
63 objects = ThreadManager()
63 objects = ThreadManager()
64
64
65 class Meta:
65 class Meta:
66 app_label = 'boards'
66 app_label = 'boards'
67
67
68 tags = models.ManyToManyField('Tag')
68 tags = models.ManyToManyField('Tag')
69 bump_time = models.DateTimeField(db_index=True)
69 bump_time = models.DateTimeField(db_index=True)
70 last_edit_time = models.DateTimeField()
70 last_edit_time = models.DateTimeField()
71 archived = models.BooleanField(default=False)
71 archived = models.BooleanField(default=False)
72 bumpable = models.BooleanField(default=True)
72 bumpable = models.BooleanField(default=True)
73 max_posts = models.IntegerField(default=get_thread_max_posts)
73 max_posts = models.IntegerField(default=get_thread_max_posts)
74
74
75 def get_tags(self) -> QuerySet:
75 def get_tags(self) -> QuerySet:
76 """
76 """
77 Gets a sorted tag list.
77 Gets a sorted tag list.
78 """
78 """
79
79
80 return self.tags.order_by('name')
80 return self.tags.order_by('name')
81
81
82 def bump(self):
82 def bump(self):
83 """
83 """
84 Bumps (moves to up) thread if possible.
84 Bumps (moves to up) thread if possible.
85 """
85 """
86
86
87 if self.can_bump():
87 if self.can_bump():
88 self.bump_time = self.last_edit_time
88 self.bump_time = self.last_edit_time
89
89
90 self.update_bump_status()
90 self.update_bump_status()
91
91
92 logger.info('Bumped thread %d' % self.id)
92 logger.info('Bumped thread %d' % self.id)
93
93
94 def has_post_limit(self) -> bool:
94 def has_post_limit(self) -> bool:
95 return self.max_posts > 0
95 return self.max_posts > 0
96
96
97 def update_bump_status(self, exclude_posts=None):
97 def update_bump_status(self, exclude_posts=None):
98 if self.has_post_limit() and self.get_reply_count() >= self.max_posts:
98 if self.has_post_limit() and self.get_reply_count() >= self.max_posts:
99 self.bumpable = False
99 self.bumpable = False
100 self.update_posts_time(exclude_posts=exclude_posts)
100 self.update_posts_time(exclude_posts=exclude_posts)
101
101
102 def _get_cache_key(self):
102 def _get_cache_key(self):
103 return [datetime_to_epoch(self.last_edit_time)]
103 return [datetime_to_epoch(self.last_edit_time)]
104
104
105 @cached_result(key_method=_get_cache_key)
105 @cached_result(key_method=_get_cache_key)
106 def get_reply_count(self) -> int:
106 def get_reply_count(self) -> int:
107 return self.get_replies().count()
107 return self.get_replies().count()
108
108
109 @cached_result(key_method=_get_cache_key)
109 @cached_result(key_method=_get_cache_key)
110 def get_images_count(self) -> int:
110 def get_images_count(self) -> int:
111 return self.get_replies().annotate(images_count=Count(
111 return self.get_replies().annotate(images_count=Count(
112 'images')).aggregate(Sum('images_count'))['images_count__sum']
112 'images')).aggregate(Sum('images_count'))['images_count__sum']
113
113
114 def can_bump(self) -> bool:
114 def can_bump(self) -> bool:
115 """
115 """
116 Checks if the thread can be bumped by replying to it.
116 Checks if the thread can be bumped by replying to it.
117 """
117 """
118
118
119 return self.bumpable and not self.archived
119 return self.bumpable and not self.archived
120
120
121 def get_last_replies(self) -> QuerySet:
121 def get_last_replies(self) -> QuerySet:
122 """
122 """
123 Gets several last replies, not including opening post
123 Gets several last replies, not including opening post
124 """
124 """
125
125
126 last_replies_count = settings.get_int('View', 'LastRepliesCount')
126 last_replies_count = settings.get_int('View', 'LastRepliesCount')
127
127
128 if last_replies_count > 0:
128 if last_replies_count > 0:
129 reply_count = self.get_reply_count()
129 reply_count = self.get_reply_count()
130
130
131 if reply_count > 0:
131 if reply_count > 0:
132 reply_count_to_show = min(last_replies_count,
132 reply_count_to_show = min(last_replies_count,
133 reply_count - 1)
133 reply_count - 1)
134 replies = self.get_replies()
134 replies = self.get_replies()
135 last_replies = replies[reply_count - reply_count_to_show:]
135 last_replies = replies[reply_count - reply_count_to_show:]
136
136
137 return last_replies
137 return last_replies
138
138
139 def get_skipped_replies_count(self) -> int:
139 def get_skipped_replies_count(self) -> int:
140 """
140 """
141 Gets number of posts between opening post and last replies.
141 Gets number of posts between opening post and last replies.
142 """
142 """
143 reply_count = self.get_reply_count()
143 reply_count = self.get_reply_count()
144 last_replies_count = min(settings.get_int('View', 'LastRepliesCount'),
144 last_replies_count = min(settings.get_int('View', 'LastRepliesCount'),
145 reply_count - 1)
145 reply_count - 1)
146 return reply_count - last_replies_count - 1
146 return reply_count - last_replies_count - 1
147
147
148 def get_replies(self, view_fields_only=False) -> QuerySet:
148 def get_replies(self, view_fields_only=False) -> QuerySet:
149 """
149 """
150 Gets sorted thread posts
150 Gets sorted thread posts
151 """
151 """
152
152
153 query = Post.objects.filter(threads__in=[self])
153 query = Post.objects.filter(threads__in=[self])
154 query = query.order_by('pub_time').prefetch_related('images', 'thread', 'threads')
154 query = query.order_by('pub_time').prefetch_related('images', 'thread', 'threads')
155 if view_fields_only:
155 if view_fields_only:
156 query = query.defer('poster_ip')
156 query = query.defer('poster_ip')
157 return query.all()
157 return query.all()
158
158
159 def get_top_level_replies(self) -> QuerySet:
159 def get_top_level_replies(self) -> QuerySet:
160 return self.get_replies().exclude(refposts__threads__in=[self])
160 return self.get_replies().exclude(refposts__threads__in=[self])
161
161
162 def get_replies_with_images(self, view_fields_only=False) -> QuerySet:
162 def get_replies_with_images(self, view_fields_only=False) -> QuerySet:
163 """
163 """
164 Gets replies that have at least one image attached
164 Gets replies that have at least one image attached
165 """
165 """
166
166
167 return self.get_replies(view_fields_only).annotate(images_count=Count(
167 return self.get_replies(view_fields_only).annotate(images_count=Count(
168 'images')).filter(images_count__gt=0)
168 'images')).filter(images_count__gt=0)
169
169
170 def get_opening_post(self, only_id=False) -> Post:
170 def get_opening_post(self, only_id=False) -> Post:
171 """
171 """
172 Gets the first post of the thread
172 Gets the first post of the thread
173 """
173 """
174
174
175 query = self.get_replies().order_by('pub_time')
175 query = self.get_replies().order_by('pub_time')
176 if only_id:
176 if only_id:
177 query = query.only('id')
177 query = query.only('id')
178 opening_post = query.first()
178 opening_post = query.first()
179
179
180 return opening_post
180 return opening_post
181
181
182 @cached_result()
182 @cached_result()
183 def get_opening_post_id(self) -> int:
183 def get_opening_post_id(self) -> int:
184 """
184 """
185 Gets ID of the first thread post.
185 Gets ID of the first thread post.
186 """
186 """
187
187
188 return self.get_opening_post(only_id=True).id
188 return self.get_opening_post(only_id=True).id
189
189
190 def get_pub_time(self):
190 def get_pub_time(self):
191 """
191 """
192 Gets opening post's pub time because thread does not have its own one.
192 Gets opening post's pub time because thread does not have its own one.
193 """
193 """
194
194
195 return self.get_opening_post().pub_time
195 return self.get_opening_post().pub_time
196
196
197 def delete(self, using=None):
198 """
199 Deletes thread with all replies.
200 """
201
202 for reply in self.get_replies().all():
203 reply.delete()
204
205 super(Thread, self).delete(using)
206
207 def __str__(self):
197 def __str__(self):
208 return 'T#{}/{}'.format(self.id, self.get_opening_post_id())
198 return 'T#{}/{}'.format(self.id, self.get_opening_post_id())
209
199
210 def get_tag_url_list(self) -> list:
200 def get_tag_url_list(self) -> list:
211 return boards.models.Tag.objects.get_tag_url_list(self.get_tags())
201 return boards.models.Tag.objects.get_tag_url_list(self.get_tags())
212
202
213 def update_posts_time(self, exclude_posts=None):
203 def update_posts_time(self, exclude_posts=None):
214 last_edit_time = self.last_edit_time
204 last_edit_time = self.last_edit_time
215
205
216 for post in self.post_set.all():
206 for post in self.post_set.all():
217 if exclude_posts is None or post not in exclude_posts:
207 if exclude_posts is None or post not in exclude_posts:
218 # Manual update is required because uids are generated on save
208 # Manual update is required because uids are generated on save
219 post.last_edit_time = last_edit_time
209 post.last_edit_time = last_edit_time
220 post.save(update_fields=['last_edit_time'])
210 post.save(update_fields=['last_edit_time'])
221
211
222 post.get_threads().update(last_edit_time=last_edit_time)
212 post.get_threads().update(last_edit_time=last_edit_time)
223
213
224 def notify_clients(self):
214 def notify_clients(self):
225 if not settings.get_bool('External', 'WebsocketsEnabled'):
215 if not settings.get_bool('External', 'WebsocketsEnabled'):
226 return
216 return
227
217
228 client = Client()
218 client = Client()
229
219
230 channel_name = WS_CHANNEL_THREAD + str(self.get_opening_post_id())
220 channel_name = WS_CHANNEL_THREAD + str(self.get_opening_post_id())
231 client.publish(channel_name, {
221 client.publish(channel_name, {
232 WS_NOTIFICATION_TYPE: WS_NOTIFICATION_TYPE_NEW_POST,
222 WS_NOTIFICATION_TYPE: WS_NOTIFICATION_TYPE_NEW_POST,
233 })
223 })
234 client.send()
224 client.send()
235
225
236 def get_absolute_url(self):
226 def get_absolute_url(self):
237 return self.get_opening_post().get_absolute_url()
227 return self.get_opening_post().get_absolute_url()
General Comments 0
You need to be logged in to leave comments. Login now