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