##// END OF EJS Templates
Refactored type hints for thread model
neko259 -
r1186:6e932174 default
parent child Browse files
Show More
@@ -1,243 +1,235 b''
1 1 import logging
2 2 from adjacent import Client
3 3
4 from django.db.models import Count, Sum
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 def get_tags(self) -> list:
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 def get_last_replies(self) -> list:
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 def get_replies(self, view_fields_only=False) -> list:
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 def get_top_level_replies(self):
159 def get_top_level_replies(self) -> QuerySet:
160 160 return self.get_replies().exclude(refposts__threads__in=[self])
161 161
162 def get_replies_with_images(self, view_fields_only=False) -> list:
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 # TODO Do we still need this?
171 def add_tag(self, tag: Tag):
172 """
173 Connects thread to a tag and tag to a thread
174 """
175
176 self.tags.add(tag)
177
178 170 def get_opening_post(self, only_id=False) -> Post:
179 171 """
180 172 Gets the first post of the thread
181 173 """
182 174
183 175 query = self.get_replies().order_by('pub_time')
184 176 if only_id:
185 177 query = query.only('id')
186 178 opening_post = query.first()
187 179
188 180 return opening_post
189 181
190 182 @cached_result()
191 183 def get_opening_post_id(self) -> int:
192 184 """
193 185 Gets ID of the first thread post.
194 186 """
195 187
196 188 return self.get_opening_post(only_id=True).id
197 189
198 190 def get_pub_time(self):
199 191 """
200 192 Gets opening post's pub time because thread does not have its own one.
201 193 """
202 194
203 195 return self.get_opening_post().pub_time
204 196
205 197 def delete(self, using=None):
206 198 """
207 199 Deletes thread with all replies.
208 200 """
209 201
210 202 for reply in self.get_replies().all():
211 203 reply.delete()
212 204
213 205 super(Thread, self).delete(using)
214 206
215 207 def __str__(self):
216 208 return 'T#{}/{}'.format(self.id, self.get_opening_post_id())
217 209
218 210 def get_tag_url_list(self) -> list:
219 211 return boards.models.Tag.objects.get_tag_url_list(self.get_tags())
220 212
221 213 def update_posts_time(self, exclude_posts=None):
222 214 for post in self.post_set.all():
223 215 if exclude_posts is not None and post not in exclude_posts:
224 216 # Manual update is required because uids are generated on save
225 217 post.last_edit_time = self.last_edit_time
226 218 post.save(update_fields=['last_edit_time'])
227 219
228 220 post.threads.update(last_edit_time=self.last_edit_time)
229 221
230 222 def notify_clients(self):
231 223 if not settings.get_bool('External', 'WebsocketsEnabled'):
232 224 return
233 225
234 226 client = Client()
235 227
236 228 channel_name = WS_CHANNEL_THREAD + str(self.get_opening_post_id())
237 229 client.publish(channel_name, {
238 230 WS_NOTIFICATION_TYPE: WS_NOTIFICATION_TYPE_NEW_POST,
239 231 })
240 232 client.send()
241 233
242 234 def get_absolute_url(self):
243 235 return self.get_opening_post().get_absolute_url()
General Comments 0
You need to be logged in to leave comments. Login now