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