##// END OF EJS Templates
Show only sections in the image tags list in random images
neko259 -
r1257:ff69f54a default
parent child Browse files
Show More
@@ -1,227 +1,230 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 __str__(self):
197 def __str__(self):
198 return 'T#{}/{}'.format(self.id, self.get_opening_post_id())
198 return 'T#{}/{}'.format(self.id, self.get_opening_post_id())
199
199
200 def get_tag_url_list(self) -> list:
200 def get_tag_url_list(self) -> list:
201 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())
202
202
203 def update_posts_time(self, exclude_posts=None):
203 def update_posts_time(self, exclude_posts=None):
204 last_edit_time = self.last_edit_time
204 last_edit_time = self.last_edit_time
205
205
206 for post in self.post_set.all():
206 for post in self.post_set.all():
207 if exclude_posts is None or post not in exclude_posts:
207 if exclude_posts is None or post not in exclude_posts:
208 # Manual update is required because uids are generated on save
208 # Manual update is required because uids are generated on save
209 post.last_edit_time = last_edit_time
209 post.last_edit_time = last_edit_time
210 post.save(update_fields=['last_edit_time'])
210 post.save(update_fields=['last_edit_time'])
211
211
212 post.get_threads().update(last_edit_time=last_edit_time)
212 post.get_threads().update(last_edit_time=last_edit_time)
213
213
214 def notify_clients(self):
214 def notify_clients(self):
215 if not settings.get_bool('External', 'WebsocketsEnabled'):
215 if not settings.get_bool('External', 'WebsocketsEnabled'):
216 return
216 return
217
217
218 client = Client()
218 client = Client()
219
219
220 channel_name = WS_CHANNEL_THREAD + str(self.get_opening_post_id())
220 channel_name = WS_CHANNEL_THREAD + str(self.get_opening_post_id())
221 client.publish(channel_name, {
221 client.publish(channel_name, {
222 WS_NOTIFICATION_TYPE: WS_NOTIFICATION_TYPE_NEW_POST,
222 WS_NOTIFICATION_TYPE: WS_NOTIFICATION_TYPE_NEW_POST,
223 })
223 })
224 client.send()
224 client.send()
225
225
226 def get_absolute_url(self):
226 def get_absolute_url(self):
227 return self.get_opening_post().get_absolute_url()
227 return self.get_opening_post().get_absolute_url()
228
229 def get_required_tags(self):
230 return self.get_tags().filter(required=True)
@@ -1,39 +1,39 b''
1 {% extends "boards/base.html" %}
1 {% extends "boards/base.html" %}
2
2
3 {% load i18n %}
3 {% load i18n %}
4
4
5 {% block head %}
5 {% block head %}
6 <title>{% trans 'Random images' %} - {{ site_name }}</title>
6 <title>{% trans 'Random images' %} - {{ site_name }}</title>
7 {% endblock %}
7 {% endblock %}
8
8
9 {% block content %}
9 {% block content %}
10
10
11 {% if images %}
11 {% if images %}
12 <div class="random-images-table">
12 <div class="random-images-table">
13 <div>
13 <div>
14 {% for image in images %}
14 {% for image in images %}
15 <div class="gallery_image">
15 <div class="gallery_image">
16 {% autoescape off %}
16 {% autoescape off %}
17 {{ image.get_view }}
17 {{ image.get_view }}
18 {% endautoescape %}
18 {% endautoescape %}
19 {% with image.get_random_associated_post as post %}
19 {% with image.get_random_associated_post as post %}
20 <a href="{{ post.get_absolute_url }}">>>{{ post.id }}</a>
20 <a href="{{ post.get_absolute_url }}">>>{{ post.id }}</a>
21 <div>
21 <div>
22 {% for tag in post.get_thread.tags.all %}
22 {% for tag in post.get_thread.get_required_tags.all %}
23 {% autoescape off %}
23 {% autoescape off %}
24 {{ tag.get_view }}{% if not forloop.last %},{% endif %}
24 {{ tag.get_view }}{% if not forloop.last %},{% endif %}
25 {% endautoescape %}
25 {% endautoescape %}
26 {% endfor %}
26 {% endfor %}
27 </div>
27 </div>
28 {% endwith %}
28 {% endwith %}
29 </div>
29 </div>
30 {% if forloop.counter|divisibleby:"3" %}
30 {% if forloop.counter|divisibleby:"3" %}
31 </div>
31 </div>
32 <div>
32 <div>
33 {% endif %}
33 {% endif %}
34 {% endfor %}
34 {% endfor %}
35 </div>
35 </div>
36 </div>
36 </div>
37 {% endif %}
37 {% endif %}
38
38
39 {% endblock %}
39 {% endblock %}
General Comments 0
You need to be logged in to leave comments. Login now