##// END OF EJS Templates
Continue to extract section strings
neko259 -
r2004:d0cc4756 default
parent child Browse files
Show More
@@ -1,85 +1,86 b''
1 1 from boards.abstracts.settingsmanager import get_settings_manager, \
2 2 SETTING_LAST_NOTIFICATION_ID, SETTING_IMAGE_VIEWER, SETTING_ONLY_FAVORITES
3 3 from boards.models import Banner
4 4 from boards.models.user import Notification
5 5 from boards import settings
6 6 from boards.models import Post, Tag, Thread
7 from boards.settings import SECTION_FORMS
7 8
8 9 CONTEXT_SITE_NAME = 'site_name'
9 10 CONTEXT_VERSION = 'version'
10 11 CONTEXT_THEME_CSS = 'theme_css'
11 12 CONTEXT_THEME = 'theme'
12 13 CONTEXT_PPD = 'posts_per_day'
13 14 CONTEXT_USER = 'user'
14 15 CONTEXT_NEW_NOTIFICATIONS_COUNT = 'new_notifications_count'
15 16 CONTEXT_USERNAMES = 'usernames'
16 17 CONTEXT_TAGS_STR = 'tags_str'
17 18 CONTEXT_IMAGE_VIEWER = 'image_viewer'
18 19 CONTEXT_HAS_FAV_THREADS = 'has_fav_threads'
19 20 CONTEXT_POW_DIFFICULTY = 'pow_difficulty'
20 21 CONTEXT_NEW_POST_COUNT = 'new_post_count'
21 22 CONTEXT_BANNERS = 'banners'
22 23 CONTEXT_ONLY_FAVORITES = 'only_favorites'
23 24
24 25
25 26 def get_notifications(context, settings_manager):
26 27 usernames = settings_manager.get_notification_usernames()
27 28 new_notifications_count = 0
28 29 if usernames:
29 30 last_notification_id = settings_manager.get_setting(
30 31 SETTING_LAST_NOTIFICATION_ID)
31 32
32 33 new_notifications_count = Notification.objects.get_notification_posts(
33 34 usernames=usernames, last=last_notification_id).only('id').count()
34 35 context[CONTEXT_NEW_NOTIFICATIONS_COUNT] = new_notifications_count
35 36 context[CONTEXT_USERNAMES] = usernames
36 37
37 38
38 39 def get_new_post_count(context, settings_manager):
39 40 fav_threads = settings_manager.get_fav_threads()
40 41 if fav_threads:
41 42 fav_thread_ops = Post.objects.filter(id__in=fav_threads.keys()) \
42 43 .order_by('-pub_time').only('thread_id', 'pub_time')
43 44 ops = [{'op': op, 'last_id': fav_threads[str(op.id)]} for op in fav_thread_ops]
44 45 count = Thread.objects.get_new_post_count(ops)
45 46 if count > 0:
46 47 context[CONTEXT_NEW_POST_COUNT] = '(+{})'.format(count)
47 48
48 49
49 50 def user_and_ui_processor(request):
50 51 context = dict()
51 52
52 53 context[CONTEXT_PPD] = float(Post.objects.get_posts_per_day())
53 54
54 55 settings_manager = get_settings_manager(request)
55 56 fav_tags = settings_manager.get_fav_tags()
56 57
57 58 context[CONTEXT_TAGS_STR] = Tag.objects.get_tag_url_list(fav_tags)
58 59 theme = settings_manager.get_theme()
59 60 context[CONTEXT_THEME] = theme
60 61
61 62 # TODO Use static here
62 63 context[CONTEXT_THEME_CSS] = 'css/' + theme + '/base_page.css'
63 64
64 65 context[CONTEXT_VERSION] = settings.get('Version', 'Version')
65 66 context[CONTEXT_SITE_NAME] = settings.get('Version', 'SiteName')
66 67
67 68 if (settings.get_bool('Forms', 'LimitFirstPosting') and not settings_manager.get_setting('confirmed_user'))\
68 69 or settings.get_bool('Forms', 'LimitPostingSpeed'):
69 context[CONTEXT_POW_DIFFICULTY] = settings.get_int('Forms', 'PowDifficulty')
70 context[CONTEXT_POW_DIFFICULTY] = settings.get_int(SECTION_FORMS, 'PowDifficulty')
70 71
71 72 context[CONTEXT_IMAGE_VIEWER] = settings_manager.get_setting(
72 73 SETTING_IMAGE_VIEWER,
73 74 default=settings.get('View', 'DefaultImageViewer'))
74 75
75 76 context[CONTEXT_HAS_FAV_THREADS] =\
76 77 len(settings_manager.get_fav_threads()) > 0
77 78
78 79 context[CONTEXT_BANNERS] = Banner.objects.order_by('-id')
79 80 context[CONTEXT_ONLY_FAVORITES] = settings_manager.get_setting(
80 81 SETTING_ONLY_FAVORITES, False)
81 82
82 83 get_notifications(context, settings_manager)
83 84 get_new_post_count(context, settings_manager)
84 85
85 86 return context
@@ -1,325 +1,326 b''
1 1 import logging
2 2 from datetime import timedelta
3 3
4 4 from django.db import models, transaction
5 5 from django.db.models import Count, Sum, QuerySet, Q
6 6 from django.utils import timezone
7 7
8 8 import boards
9 9 from boards import settings
10 10 from boards.models import STATUS_BUMPLIMIT, STATUS_ACTIVE, STATUS_ARCHIVE
11 11 from boards.models.attachment import FILE_TYPES_IMAGE
12 12 from boards.models.post import Post
13 from boards.models.tag import Tag, DEFAULT_LOCALE, TagAlias
13 from boards.models.tag import Tag, TagAlias
14 from boards.settings import SECTION_VIEW
14 15 from boards.utils import cached_result, datetime_to_epoch
15 16
16 17 FAV_THREAD_NO_UPDATES = -1
17 18
18 19
19 20 __author__ = 'neko259'
20 21
21 22
22 23 logger = logging.getLogger(__name__)
23 24
24 25
25 26 WS_NOTIFICATION_TYPE_NEW_POST = 'new_post'
26 27 WS_NOTIFICATION_TYPE = 'notification_type'
27 28
28 29 WS_CHANNEL_THREAD = "thread:"
29 30
30 31 STATUS_CHOICES = (
31 32 (STATUS_ACTIVE, STATUS_ACTIVE),
32 33 (STATUS_BUMPLIMIT, STATUS_BUMPLIMIT),
33 34 (STATUS_ARCHIVE, STATUS_ARCHIVE),
34 35 )
35 36
36 37
37 38 class ThreadManager(models.Manager):
38 39 def process_old_threads(self):
39 40 """
40 41 Preserves maximum thread count. If there are too many threads,
41 42 archive or delete the old ones.
42 43 """
43 44 old_time_delta = settings.get_int('Messages', 'ThreadArchiveDays')
44 45 old_time = timezone.now() - timedelta(days=old_time_delta)
45 46 old_ops = Post.objects.filter(opening=True, pub_time__lte=old_time).exclude(thread__status=STATUS_ARCHIVE)
46 47
47 48 for op in old_ops:
48 49 thread = op.get_thread()
49 50 if settings.get_bool('Storage', 'ArchiveThreads'):
50 51 self._archive_thread(thread)
51 52 else:
52 53 thread.delete()
53 54 logger.info('Processed old thread {}'.format(thread))
54 55
55 56
56 57 def _archive_thread(self, thread):
57 58 thread.status = STATUS_ARCHIVE
58 59 thread.last_edit_time = timezone.now()
59 60 thread.update_posts_time()
60 61 thread.save(update_fields=['last_edit_time', 'status'])
61 62
62 63 def get_new_posts(self, datas):
63 64 query = None
64 65 # TODO Use classes instead of dicts
65 66 for data in datas:
66 67 if data['last_id'] != FAV_THREAD_NO_UPDATES:
67 68 q = (Q(id=data['op'].get_thread_id())
68 69 & Q(replies__id__gt=data['last_id']))
69 70 if query is None:
70 71 query = q
71 72 else:
72 73 query = query | q
73 74 if query is not None:
74 75 return self.filter(query).annotate(
75 76 new_post_count=Count('replies'))
76 77
77 78 def get_new_post_count(self, datas):
78 79 new_posts = self.get_new_posts(datas)
79 80 return new_posts.aggregate(total_count=Count('replies'))\
80 81 ['total_count'] if new_posts else 0
81 82
82 83
83 84 def get_thread_max_posts():
84 85 return settings.get_int('Messages', 'MaxPostsPerThread')
85 86
86 87
87 88 class Thread(models.Model):
88 89 objects = ThreadManager()
89 90
90 91 class Meta:
91 92 app_label = 'boards'
92 93
93 94 tags = models.ManyToManyField('Tag', related_name='thread_tags')
94 95 bump_time = models.DateTimeField(db_index=True)
95 96 last_edit_time = models.DateTimeField()
96 97 max_posts = models.IntegerField(default=get_thread_max_posts)
97 98 status = models.CharField(max_length=50, default=STATUS_ACTIVE,
98 99 choices=STATUS_CHOICES, db_index=True)
99 100 monochrome = models.BooleanField(default=False)
100 101 stickerpack = models.BooleanField(default=False)
101 102
102 103 def get_tags(self) -> QuerySet:
103 104 """
104 105 Gets a sorted tag list.
105 106 """
106 107
107 108 return self.tags.filter(aliases__in=TagAlias.objects.filter_localized(parent__thread_tags=self)).order_by('aliases__name')
108 109
109 110 def bump(self):
110 111 """
111 112 Bumps (moves to up) thread if possible.
112 113 """
113 114
114 115 if self.can_bump():
115 116 self.bump_time = self.last_edit_time
116 117
117 118 self.update_bump_status()
118 119
119 120 logger.info('Bumped thread %d' % self.id)
120 121
121 122 def has_post_limit(self) -> bool:
122 123 return self.max_posts > 0
123 124
124 125 def update_bump_status(self, exclude_posts=None):
125 126 if self.has_post_limit() and self.get_reply_count() >= self.max_posts:
126 127 self.status = STATUS_BUMPLIMIT
127 128 self.update_posts_time(exclude_posts=exclude_posts)
128 129
129 130 def _get_cache_key(self):
130 131 return [datetime_to_epoch(self.last_edit_time)]
131 132
132 133 @cached_result(key_method=_get_cache_key)
133 134 def get_reply_count(self) -> int:
134 135 return self.get_replies().count()
135 136
136 137 @cached_result(key_method=_get_cache_key)
137 138 def get_images_count(self) -> int:
138 139 return self.get_replies().filter(
139 140 attachments__mimetype__in=FILE_TYPES_IMAGE)\
140 141 .annotate(images_count=Count(
141 142 'attachments')).aggregate(Sum('images_count'))['images_count__sum'] or 0
142 143
143 144 @cached_result(key_method=_get_cache_key)
144 145 def get_attachment_count(self) -> int:
145 146 return self.get_replies().annotate(attachment_count=Count('attachments'))\
146 147 .aggregate(Sum('attachment_count'))['attachment_count__sum'] or 0
147 148
148 149 def can_bump(self) -> bool:
149 150 """
150 151 Checks if the thread can be bumped by replying to it.
151 152 """
152 153
153 154 return self.get_status() == STATUS_ACTIVE
154 155
155 156 def get_last_replies(self) -> QuerySet:
156 157 """
157 158 Gets several last replies, not including opening post
158 159 """
159 160
160 last_replies_count = settings.get_int('View', 'LastRepliesCount')
161 last_replies_count = settings.get_int(SECTION_VIEW, 'LastRepliesCount')
161 162
162 163 if last_replies_count > 0:
163 164 reply_count = self.get_reply_count()
164 165
165 166 if reply_count > 0:
166 167 reply_count_to_show = min(last_replies_count,
167 168 reply_count - 1)
168 169 replies = self.get_replies()
169 170 last_replies = replies[reply_count - reply_count_to_show:]
170 171
171 172 return last_replies
172 173
173 174 def get_skipped_replies_count(self) -> int:
174 175 """
175 176 Gets number of posts between opening post and last replies.
176 177 """
177 178 reply_count = self.get_reply_count()
178 last_replies_count = min(settings.get_int('View', 'LastRepliesCount'),
179 last_replies_count = min(settings.get_int(SECTION_VIEW, 'LastRepliesCount'),
179 180 reply_count - 1)
180 181 return reply_count - last_replies_count - 1
181 182
182 183 # TODO Remove argument, it is not used
183 184 def get_replies(self, view_fields_only=True) -> QuerySet:
184 185 """
185 186 Gets sorted thread posts
186 187 """
187 188 query = self.replies.order_by('pub_time').prefetch_related(
188 189 'attachments')
189 190 return query
190 191
191 192 def get_viewable_replies(self) -> QuerySet:
192 193 """
193 194 Gets replies with only fields that are used for viewing.
194 195 """
195 196 return self.get_replies().defer('text', 'last_edit_time')
196 197
197 198 def get_top_level_replies(self) -> QuerySet:
198 199 return self.get_replies().exclude(refposts__threads__in=[self])
199 200
200 201 def get_replies_with_images(self, view_fields_only=False) -> QuerySet:
201 202 """
202 203 Gets replies that have at least one image attached
203 204 """
204 205 return self.get_replies(view_fields_only).filter(
205 206 attachments__mimetype__in=FILE_TYPES_IMAGE).annotate(images_count=Count(
206 207 'attachments')).filter(images_count__gt=0)
207 208
208 209 def get_opening_post(self, only_id=False) -> Post:
209 210 """
210 211 Gets the first post of the thread
211 212 """
212 213
213 214 query = self.get_replies().filter(opening=True)
214 215 if only_id:
215 216 query = query.only('id')
216 217 opening_post = query.first()
217 218
218 219 return opening_post
219 220
220 221 @cached_result()
221 222 def get_opening_post_id(self) -> int:
222 223 """
223 224 Gets ID of the first thread post.
224 225 """
225 226
226 227 return self.get_opening_post(only_id=True).id
227 228
228 229 def get_pub_time(self):
229 230 """
230 231 Gets opening post's pub time because thread does not have its own one.
231 232 """
232 233
233 234 return self.get_opening_post().pub_time
234 235
235 236 def __str__(self):
236 237 return 'T#{}/{}'.format(self.id, self.get_opening_post())
237 238
238 239 def get_tag_url_list(self) -> list:
239 240 return boards.models.Tag.objects.get_tag_url_list(self.get_tags().all())
240 241
241 242 def update_posts_time(self, exclude_posts=None):
242 243 last_edit_time = self.last_edit_time
243 244
244 245 for post in self.replies.all():
245 246 if exclude_posts is None or post not in exclude_posts:
246 247 # Manual update is required because uids are generated on save
247 248 post.last_edit_time = last_edit_time
248 249 post.save(update_fields=['last_edit_time'])
249 250
250 251 def get_absolute_url(self):
251 252 return self.get_opening_post().get_absolute_url()
252 253
253 254 def get_required_tags(self):
254 255 return self.get_tags().filter(required=True)
255 256
256 257 def get_sections_str(self):
257 258 return Tag.objects.get_tag_url_list(self.get_required_tags())
258 259
259 260 def get_replies_newer(self, post_id):
260 261 return self.get_replies().filter(id__gt=post_id)
261 262
262 263 def is_archived(self):
263 264 return self.get_status() == STATUS_ARCHIVE
264 265
265 266 def is_bumplimit(self):
266 267 return self.get_status() == STATUS_BUMPLIMIT
267 268
268 269 def get_status(self):
269 270 return self.status
270 271
271 272 def is_monochrome(self):
272 273 return self.monochrome
273 274
274 275 def is_stickerpack(self):
275 276 return self.stickerpack
276 277
277 278 # If tags have parent, add them to the tag list
278 279 @transaction.atomic
279 280 def refresh_tags(self):
280 281 for tag in self.get_tags().all():
281 282 parents = tag.get_all_parents()
282 283 if len(parents) > 0:
283 284 self.tags.add(*parents)
284 285
285 286 def get_reply_tree(self):
286 287 replies = self.get_replies().prefetch_related('refposts')
287 288 tree = []
288 289 for reply in replies:
289 290 parents = reply.refposts.all()
290 291
291 292 found_parent = False
292 293 searching_for_index = False
293 294
294 295 if len(parents) > 0:
295 296 index = 0
296 297 parent_depth = 0
297 298
298 299 indexes_to_insert = []
299 300
300 301 for depth, element in tree:
301 302 index += 1
302 303
303 304 # If this element is next after parent on the same level,
304 305 # insert child before it
305 306 if searching_for_index and depth <= parent_depth:
306 307 indexes_to_insert.append((index - 1, parent_depth))
307 308 searching_for_index = False
308 309
309 310 if element in parents:
310 311 found_parent = True
311 312 searching_for_index = True
312 313 parent_depth = depth
313 314
314 315 if not found_parent:
315 316 tree.append((0, reply))
316 317 else:
317 318 if searching_for_index:
318 319 tree.append((parent_depth + 1, reply))
319 320
320 321 offset = 0
321 322 for last_index, parent_depth in indexes_to_insert:
322 323 tree.insert(last_index + offset, (parent_depth + 1, reply))
323 324 offset += 1
324 325
325 326 return tree
@@ -1,176 +1,178 b''
1 1 from django.core.paginator import Paginator
2 2 from django.test import TestCase
3 3
4 4 from boards import settings
5 5 from boards.models import Tag, Post, Thread, KeyPair
6 6 from boards.models.thread import STATUS_ARCHIVE
7 from boards.settings import SECTION_VIEW
7 8
8 9
9 10 class PostTests(TestCase):
10 11
11 12 def _create_post(self):
12 13 tag, created = Tag.objects.get_or_create_with_alias(name='test_tag')
13 14 return Post.objects.create_post(title='title', text='text',
14 15 tags=[tag])
15 16
16 17 def test_post_add(self):
17 18 """Test adding post"""
18 19
19 20 post = self._create_post()
20 21
21 22 self.assertIsNotNone(post, 'No post was created.')
22 23 self.assertEqual('test_tag', post.get_thread().tags.all()[0].get_name(),
23 24 'No tags were added to the post.')
24 25
25 26 def test_delete_post(self):
26 27 """Test post deletion"""
27 28
28 29 post = self._create_post()
29 30 post_id = post.id
30 31
31 32 post.delete()
32 33
33 34 self.assertFalse(Post.objects.filter(id=post_id).exists())
34 35
35 36 def test_delete_thread(self):
36 37 """Test thread deletion"""
37 38
38 39 opening_post = self._create_post()
39 40 thread = opening_post.get_thread()
40 41 reply = Post.objects.create_post("", "", thread=thread)
41 42
42 43 thread.delete()
43 44
44 45 self.assertFalse(Post.objects.filter(id=reply.id).exists(),
45 46 'Reply was not deleted with the thread.')
46 47 self.assertFalse(Post.objects.filter(id=opening_post.id).exists(),
47 48 'Opening post was not deleted with the thread.')
48 49
49 50 def test_post_to_thread(self):
50 51 """Test adding post to a thread"""
51 52
52 53 op = self._create_post()
53 54 post = Post.objects.create_post("", "", thread=op.get_thread())
54 55
55 56 self.assertIsNotNone(post, 'Reply to thread wasn\'t created')
56 57 self.assertEqual(op.get_thread().last_edit_time, post.pub_time,
57 58 'Post\'s create time doesn\'t match thread last edit'
58 59 ' time')
59 60
60 61 def test_delete_posts_by_ip(self):
61 62 """Test deleting posts with the given ip"""
62 63
63 64 post = self._create_post()
64 65 post_id = post.id
65 66
66 67 Post.objects.delete_posts_by_ip('0.0.0.0')
67 68
68 69 self.assertFalse(Post.objects.filter(id=post_id).exists())
69 70
70 71 def test_get_thread(self):
71 72 """Test getting all posts of a thread"""
72 73
73 74 opening_post = self._create_post()
74 75
75 76 for i in range(2):
76 77 Post.objects.create_post('title', 'text',
77 78 thread=opening_post.get_thread())
78 79
79 80 thread = opening_post.get_thread()
80 81
81 82 self.assertEqual(3, thread.get_replies().count())
82 83
83 84 def test_create_post_with_tag(self):
84 85 """Test adding tag to post"""
85 86
86 87 tag, created = Tag.objects.get_or_create_with_alias(name='test_tag')
87 88 post = Post.objects.create_post(title='title', text='text', tags=[tag])
88 89
89 90 thread = post.get_thread()
90 91 self.assertIsNotNone(post, 'Post not created')
91 92 self.assertTrue(tag in thread.tags.all(), 'Tag not added to thread')
92 93
93 94 def test_pages(self):
94 95 """Test that the thread list is properly split into pages"""
95 96
96 for i in range(settings.get_int('View', 'ThreadsPerPage') * 2):
97 threads_per_page = settings.get_int(SECTION_VIEW, 'ThreadsPerPage')
98 for i in range(threads_per_page * 2):
97 99 self._create_post()
98 100
99 101 all_threads = Thread.objects.exclude(status=STATUS_ARCHIVE)
100 102
101 103 paginator = Paginator(Thread.objects.exclude(status=STATUS_ARCHIVE),
102 settings.get_int('View', 'ThreadsPerPage'))
104 threads_per_page)
103 105 posts_in_second_page = paginator.page(2).object_list
104 106 first_post = posts_in_second_page[0]
105 107
106 self.assertEqual(all_threads[settings.get_int('View', 'ThreadsPerPage')].id,
108 self.assertEqual(all_threads[threads_per_page].id,
107 109 first_post.id)
108 110
109 111 def test_reflinks(self):
110 112 """
111 113 Tests that reflinks are parsed within post and connecting replies
112 114 to the replied posts.
113 115
114 116 Local reflink example: [post]123[/post]
115 117 Global reflink example: [post]key_type::key::123[/post]
116 118 """
117 119
118 120 key = KeyPair.objects.generate_key(primary=True)
119 121
120 122 tag, created = Tag.objects.get_or_create_with_alias(name='test_tag')
121 123
122 124 post = Post.objects.create_post(title='', text='', tags=[tag])
123 125 post_local_reflink = Post.objects.create_post(title='',
124 126 text='[post]%d[/post]' % post.id, thread=post.get_thread())
125 127
126 128 self.assertTrue(post_local_reflink in post.referenced_posts.all(),
127 129 'Local reflink not connecting posts.')
128 130
129 131
130 132 def test_thread_replies(self):
131 133 """
132 134 Tests that the replies can be queried from a thread in all possible
133 135 ways.
134 136 """
135 137
136 138 tag, created = Tag.objects.get_or_create_with_alias(name='test_tag')
137 139 opening_post = Post.objects.create_post(title='title', text='text',
138 140 tags=[tag])
139 141 thread = opening_post.get_thread()
140 142
141 143 Post.objects.create_post(title='title', text='text', thread=thread)
142 144 Post.objects.create_post(title='title', text='text', thread=thread)
143 145
144 146 replies = thread.get_replies()
145 147 self.assertTrue(len(replies) > 0, 'No replies found for thread.')
146 148
147 149 replies = thread.get_replies(view_fields_only=True)
148 150 self.assertTrue(len(replies) > 0,
149 151 'No replies found for thread with view fields only.')
150 152
151 153 def test_bumplimit(self):
152 154 """
153 155 Tests that the thread bumpable status is changed and post uids and
154 156 last update times are updated across all post threads.
155 157 """
156 158
157 159 op1 = Post.objects.create_post(title='title', text='text')
158 160
159 161 thread1 = op1.get_thread()
160 162 thread1.max_posts = 5
161 163 thread1.save()
162 164
163 165 uid_1 = op1.uid
164 166
165 167 # Create multi reply
166 168 Post.objects.create_post(
167 169 title='title', text='text', thread=thread1)
168 170 for i in range(6):
169 171 Post.objects.create_post(title='title', text='text',
170 172 thread=thread1)
171 173
172 174 self.assertFalse(op1.get_thread().can_bump(),
173 175 'Thread is bumpable when it should not be.')
174 176 self.assertNotEqual(
175 177 uid_1, Post.objects.get(id=op1.id).uid,
176 178 'UID of the first OP should be changed but it is not.')
@@ -1,130 +1,131 b''
1 1 from django.urls import reverse
2 2 from django.shortcuts import render
3 3
4 4 from boards import settings
5 5 from boards.abstracts.paginator import get_paginator
6 6 from boards.abstracts.settingsmanager import get_settings_manager
7 7 from boards.models import Post
8 from boards.settings import SECTION_VIEW
8 9 from boards.views.base import BaseBoardView
9 10 from boards.views.mixins import PaginatedMixin
10 11
11 POSTS_PER_PAGE = settings.get_int('View', 'PostsPerPage')
12 POSTS_PER_PAGE = settings.get_int(SECTION_VIEW, 'PostsPerPage')
12 13
13 14 PARAMETER_POSTS = 'posts'
14 15 PARAMETER_QUERIES = 'queries'
15 16
16 17 TEMPLATE = 'boards/feed.html'
17 18 DEFAULT_PAGE = 1
18 19
19 20
20 21 class FeedFilter:
21 22 @staticmethod
22 23 def get_filtered_posts(request, posts):
23 24 return posts
24 25
25 26 @staticmethod
26 27 def get_query(request):
27 28 return None
28 29
29 30
30 31 class TripcodeFilter(FeedFilter):
31 32 @staticmethod
32 33 def get_filtered_posts(request, posts):
33 34 filtered_posts = posts
34 35 tripcode = request.GET.get('tripcode', None)
35 36 if tripcode:
36 37 filtered_posts = filtered_posts.filter(tripcode=tripcode)
37 38 return filtered_posts
38 39
39 40 @staticmethod
40 41 def get_query(request):
41 42 tripcode = request.GET.get('tripcode', None)
42 43 if tripcode:
43 44 return 'Tripcode: {}'.format(tripcode)
44 45
45 46
46 47 class FavoritesFilter(FeedFilter):
47 48 @staticmethod
48 49 def get_filtered_posts(request, posts):
49 50 filtered_posts = posts
50 51
51 52 favorites = 'favorites' in request.GET
52 53 if favorites:
53 54 settings_manager = get_settings_manager(request)
54 55 fav_thread_ops = Post.objects.filter(id__in=settings_manager.get_fav_threads().keys())
55 56 fav_threads = [op.get_thread() for op in fav_thread_ops]
56 57 filtered_posts = filtered_posts.filter(thread__in=fav_threads)
57 58 return filtered_posts
58 59
59 60
60 61 class IpFilter(FeedFilter):
61 62 @staticmethod
62 63 def get_filtered_posts(request, posts):
63 64 filtered_posts = posts
64 65
65 66 ip = request.GET.get('ip', None)
66 67 if ip and request.user.has_perm('post_delete'):
67 68 filtered_posts = filtered_posts.filter(poster_ip=ip)
68 69 return filtered_posts
69 70
70 71 @staticmethod
71 72 def get_query(request):
72 73 ip = request.GET.get('ip', None)
73 74 if ip:
74 75 return 'IP: {}'.format(ip)
75 76
76 77
77 78 class ImageFilter(FeedFilter):
78 79 @staticmethod
79 80 def get_filtered_posts(request, posts):
80 81 filtered_posts = posts
81 82
82 83 image = request.GET.get('image', None)
83 84 if image:
84 85 filtered_posts = filtered_posts.filter(attachments__file=image)
85 86 return filtered_posts
86 87
87 88 @staticmethod
88 89 def get_query(request):
89 90 image = request.GET.get('image', None)
90 91 if image:
91 92 return 'File: {}'.format(image)
92 93
93 94
94 95 class FeedView(PaginatedMixin, BaseBoardView):
95 96 filters = (
96 97 TripcodeFilter,
97 98 FavoritesFilter,
98 99 IpFilter,
99 100 ImageFilter,
100 101 )
101 102
102 103 def get(self, request):
103 104 page = request.GET.get('page', DEFAULT_PAGE)
104 105
105 106 params = self.get_context_data(request=request)
106 107
107 108 settings_manager = get_settings_manager(request)
108 109
109 110 posts = Post.objects.exclude(
110 111 thread__tags__in=settings_manager.get_hidden_tags()).order_by(
111 112 '-pub_time').prefetch_related('attachments', 'thread')
112 113 queries = []
113 114 for filter in self.filters:
114 115 posts = filter.get_filtered_posts(request, posts)
115 116 query = filter.get_query(request)
116 117 if query:
117 118 queries.append(query)
118 119 params[PARAMETER_QUERIES] = queries
119 120
120 121 paginator = get_paginator(posts, POSTS_PER_PAGE)
121 122 paginator.current_page = int(page)
122 123
123 124 params[PARAMETER_POSTS] = paginator.page(page).object_list
124 125
125 126 paginator.set_url(reverse('feed'), request.GET.dict())
126 127
127 128 params.update(self.get_page_context(paginator, page))
128 129
129 130 return render(request, TEMPLATE, params)
130 131
@@ -1,41 +1,41 b''
1 1 from datetime import datetime
2 2 from datetime import timedelta
3 3
4 4 from django.db.models import Count, Q
5 5 from django.shortcuts import render
6 6 from django.utils.decorators import method_decorator
7 7 from django.views.decorators.csrf import csrf_protect
8 8
9 9 from boards import settings
10 from boards.models import Post, Tag, Attachment, STATUS_ACTIVE, TagAlias
11 from boards.models.tag import DEFAULT_LOCALE
10 from boards.models import Post, Tag, STATUS_ACTIVE, TagAlias
11 from boards.settings import SECTION_VIEW
12 12 from boards.views.base import BaseBoardView
13 13
14 14 PARAM_SECTION_STR = 'section_str'
15 15 PARAM_LATEST_THREADS = 'latest_threads'
16 16
17 17 TEMPLATE = 'boards/landing.html'
18 18
19 19
20 20 class LandingView(BaseBoardView):
21 21 @method_decorator(csrf_protect)
22 22 def get(self, request):
23 23 params = dict()
24 24
25 25 params[PARAM_SECTION_STR] = Tag.objects.get_tag_url_list(
26 26 Tag.objects.filter(Q(aliases__in=TagAlias.objects.filter_localized()) & Q(required=True))
27 27 .order_by('aliases__name'))
28 28
29 29 today = datetime.now() - timedelta(1)
30 30 ops = Post.objects.filter(thread__replies__pub_time__gt=today, opening=True, thread__status=STATUS_ACTIVE)\
31 31 .annotate(today_post_count=Count('thread__replies'))\
32 32 .order_by('-pub_time')
33 33
34 max_landing_threads = settings.get_int('View', 'MaxLandingThreads')
34 max_landing_threads = settings.get_int(SECTION_VIEW, 'MaxLandingThreads')
35 35 if max_landing_threads > 0:
36 36 ops = ops[:max_landing_threads]
37 37
38 38 params[PARAM_LATEST_THREADS] = ops
39 39
40 40 return render(request, TEMPLATE, params)
41 41
@@ -1,32 +1,33 b''
1 1 from django.shortcuts import get_object_or_404, render
2 2 from django.urls import reverse
3 3
4 4 from boards import settings
5 5 from boards.abstracts.paginator import get_paginator
6 6 from boards.models import TagAlias
7 from boards.settings import SECTION_VIEW
7 8 from boards.views.base import BaseBoardView
8 9 from boards.views.mixins import PaginatedMixin
9 10
10 IMAGES_PER_PAGE = settings.get_int('View', 'ImagesPerPageGallery')
11 IMAGES_PER_PAGE = settings.get_int(SECTION_VIEW, 'ImagesPerPageGallery')
11 12
12 13 TEMPLATE = 'boards/tag_gallery.html'
13 14
14 15
15 16 class TagGalleryView(BaseBoardView, PaginatedMixin):
16 17
17 18 def get(self, request, tag_name):
18 19 page = int(request.GET.get('page', 1))
19 20
20 21 params = dict()
21 22 tag_alias = get_object_or_404(TagAlias, name=tag_name)
22 23 tag = tag_alias.parent
23 24 params['tag'] = tag
24 25 paginator = get_paginator(tag.get_images(), IMAGES_PER_PAGE,
25 26 current_page=page)
26 27 params['paginator'] = paginator
27 28 params['images'] = paginator.page(page).object_list
28 29 paginator.set_url(reverse('tag_gallery', kwargs={'tag_name': tag_name}),
29 30 request.GET.dict())
30 31 params.update(self.get_page_context(paginator, page))
31 32
32 33 return render(request, TEMPLATE, params)
@@ -1,36 +1,37 b''
1 1 from boards import settings
2 from boards.settings import SECTION_FORMS
2 3 from boards.views.thread import ThreadView
3 4 from boards.views.mixins import FileUploadMixin
4 5
5 6 TEMPLATE_NORMAL = 'boards/thread_normal.html'
6 7
7 8 CONTEXT_BUMPLIMIT_PRG = 'bumplimit_progress'
8 9 CONTEXT_POSTS_LEFT = 'posts_left'
9 10 CONTEXT_BUMPABLE = 'bumpable'
10 11 PARAM_MAX_FILE_SIZE = 'max_file_size'
11 12 PARAM_MAX_FILES = 'max_files'
12 13
13 14
14 15 class NormalThreadView(ThreadView, FileUploadMixin):
15 16
16 17 def get_template(self):
17 18 return TEMPLATE_NORMAL
18 19
19 20 def get_mode(self):
20 21 return 'normal'
21 22
22 23 def get_data(self, thread):
23 24 params = dict()
24 25
25 26 bumpable = thread.can_bump()
26 27 params[CONTEXT_BUMPABLE] = bumpable
27 28 max_posts = thread.max_posts
28 29 if bumpable and thread.has_post_limit():
29 30 left_posts = max_posts - thread.get_reply_count()
30 31 params[CONTEXT_POSTS_LEFT] = left_posts
31 32 params[CONTEXT_BUMPLIMIT_PRG] = str(
32 33 float(left_posts) / max_posts * 100)
33 34 params[PARAM_MAX_FILE_SIZE] = self.get_max_upload_size()
34 params[PARAM_MAX_FILES] = settings.get_int('Forms', 'MaxFileCount')
35 params[PARAM_MAX_FILES] = settings.get_int(SECTION_FORMS, 'MaxFileCount')
35 36
36 37 return params
General Comments 0
You need to be logged in to leave comments. Login now