##// END OF EJS Templates
New backend for fav threads. Now only last post ids are saved, no thread ids
neko259 -
r2044:227641ed default
parent child Browse files
Show More
@@ -1,240 +1,246
1 from boards import settings
1 import boards
2 from boards.models import Tag, TagAlias, Attachment
2 from boards.models import Tag, TagAlias, Attachment
3 from boards.models.attachment import AttachmentSticker
3 from boards.models.attachment import AttachmentSticker
4 from boards.models.thread import FAV_THREAD_NO_UPDATES
5 from boards.models.user import UserSettings
4 from boards.models.user import UserSettings
6 from boards.settings import SECTION_VIEW
7
5
8 MAX_TRIPCODE_COLLISIONS = 50
6 MAX_TRIPCODE_COLLISIONS = 50
9
7
10 __author__ = 'neko259'
8 __author__ = 'neko259'
11
9
12 SESSION_SETTING = 'setting'
10 SESSION_SETTING = 'setting'
13
11
14 SETTING_THEME = 'theme'
12 SETTING_THEME = 'theme'
15 SETTING_FAVORITE_TAGS = 'favorite_tags'
13 SETTING_FAVORITE_TAGS = 'favorite_tags'
16 SETTING_FAVORITE_THREADS = 'favorite_threads'
14 SETTING_FAVORITE_THREADS = 'favorite_threads'
17 SETTING_HIDDEN_TAGS = 'hidden_tags'
15 SETTING_HIDDEN_TAGS = 'hidden_tags'
18 SETTING_USERNAME = 'username'
16 SETTING_USERNAME = 'username'
19 SETTING_LAST_NOTIFICATION_ID = 'last_notification'
17 SETTING_LAST_NOTIFICATION_ID = 'last_notification'
20 SETTING_IMAGE_VIEWER = 'image_viewer'
18 SETTING_IMAGE_VIEWER = 'image_viewer'
21 SETTING_IMAGES = 'images_aliases'
19 SETTING_IMAGES = 'images_aliases'
22 SETTING_ONLY_FAVORITES = 'only_favorites'
20 SETTING_ONLY_FAVORITES = 'only_favorites'
21 SETTING_LAST_POSTS = 'last_posts'
23
22
24 DEFAULT_THEME = 'md'
23 DEFAULT_THEME = 'md'
25
24
26
25
27 class SettingsManager:
26 class SettingsManager:
28 """
27 """
29 Base settings manager class. get_setting and set_setting methods should
28 Base settings manager class. get_setting and set_setting methods should
30 be overriden.
29 be overriden.
31 """
30 """
32 def __init__(self):
31 def __init__(self):
33 pass
32 pass
34
33
35 def get_theme(self) -> str:
34 def get_theme(self) -> str:
36 theme = self.get_setting(SETTING_THEME)
35 theme = self.get_setting(SETTING_THEME)
37 if not theme:
36 if not theme:
38 theme = DEFAULT_THEME
37 theme = DEFAULT_THEME
39 self.set_setting(SETTING_THEME, theme)
38 self.set_setting(SETTING_THEME, theme)
40
39
41 return theme
40 return theme
42
41
43 def set_theme(self, theme):
42 def set_theme(self, theme):
44 self.set_setting(SETTING_THEME, theme)
43 self.set_setting(SETTING_THEME, theme)
45
44
46 def get_setting(self, setting, default=None):
45 def get_setting(self, setting, default=None):
47 pass
46 pass
48
47
49 def set_setting(self, setting, value):
48 def set_setting(self, setting, value):
50 pass
49 pass
51
50
52 def get_fav_tags(self) -> list:
51 def get_fav_tags(self) -> list:
53 tag_names = self.get_setting(SETTING_FAVORITE_TAGS)
52 tag_names = self.get_setting(SETTING_FAVORITE_TAGS)
54 tags = []
53 tags = []
55 if tag_names:
54 if tag_names:
56 tags = list(Tag.objects.filter(aliases__in=TagAlias.objects
55 tags = list(Tag.objects.filter(aliases__in=TagAlias.objects
57 .filter_localized(parent__aliases__name__in=tag_names))
56 .filter_localized(parent__aliases__name__in=tag_names))
58 .order_by('aliases__name'))
57 .order_by('aliases__name'))
59 return tags
58 return tags
60
59
61 def add_fav_tag(self, tag):
60 def add_fav_tag(self, tag):
62 tags = self.get_setting(SETTING_FAVORITE_TAGS)
61 tags = self.get_setting(SETTING_FAVORITE_TAGS)
63 if not tags:
62 if not tags:
64 tags = [tag.get_name()]
63 tags = [tag.get_name()]
65 else:
64 else:
66 if not tag.get_name() in tags:
65 if not tag.get_name() in tags:
67 tags.append(tag.get_name())
66 tags.append(tag.get_name())
68
67
69 tags.sort()
68 tags.sort()
70 self.set_setting(SETTING_FAVORITE_TAGS, tags)
69 self.set_setting(SETTING_FAVORITE_TAGS, tags)
71
70
72 def del_fav_tag(self, tag):
71 def del_fav_tag(self, tag):
73 tags = self.get_setting(SETTING_FAVORITE_TAGS)
72 tags = self.get_setting(SETTING_FAVORITE_TAGS)
74 if tag.get_name() in tags:
73 if tag.get_name() in tags:
75 tags.remove(tag.get_name())
74 tags.remove(tag.get_name())
76 self.set_setting(SETTING_FAVORITE_TAGS, tags)
75 self.set_setting(SETTING_FAVORITE_TAGS, tags)
77
76
78 def get_hidden_tags(self) -> list:
77 def get_hidden_tags(self) -> list:
79 tag_names = self.get_setting(SETTING_HIDDEN_TAGS)
78 tag_names = self.get_setting(SETTING_HIDDEN_TAGS)
80 tags = []
79 tags = []
81 if tag_names:
80 if tag_names:
82 tags = list(Tag.objects.filter(aliases__in=TagAlias.objects
81 tags = list(Tag.objects.filter(aliases__in=TagAlias.objects
83 .filter_localized(parent__aliases__name__in=tag_names))
82 .filter_localized(parent__aliases__name__in=tag_names))
84 .order_by('aliases__name'))
83 .order_by('aliases__name'))
85
84
86 return tags
85 return tags
87
86
88 def add_hidden_tag(self, tag):
87 def add_hidden_tag(self, tag):
89 tags = self.get_setting(SETTING_HIDDEN_TAGS)
88 tags = self.get_setting(SETTING_HIDDEN_TAGS)
90 if not tags:
89 if not tags:
91 tags = [tag.get_name()]
90 tags = [tag.get_name()]
92 else:
91 else:
93 if not tag.get_name() in tags:
92 if not tag.get_name() in tags:
94 tags.append(tag.get_name())
93 tags.append(tag.get_name())
95
94
96 tags.sort()
95 tags.sort()
97 self.set_setting(SETTING_HIDDEN_TAGS, tags)
96 self.set_setting(SETTING_HIDDEN_TAGS, tags)
98
97
99 def del_hidden_tag(self, tag):
98 def del_hidden_tag(self, tag):
100 tags = self.get_setting(SETTING_HIDDEN_TAGS)
99 tags = self.get_setting(SETTING_HIDDEN_TAGS)
101 if tag.get_name() in tags:
100 if tag.get_name() in tags:
102 tags.remove(tag.get_name())
101 tags.remove(tag.get_name())
103 self.set_setting(SETTING_HIDDEN_TAGS, tags)
102 self.set_setting(SETTING_HIDDEN_TAGS, tags)
104
103
105 def get_fav_threads(self) -> dict:
106 return self.get_setting(SETTING_FAVORITE_THREADS, default=dict())
107
108 def add_or_read_fav_thread(self, opening_post):
104 def add_or_read_fav_thread(self, opening_post):
109 threads = self.get_fav_threads()
105 last_post_ids = self.get_setting(SETTING_LAST_POSTS)
106 if not last_post_ids:
107 last_post_ids = []
110
108
111 max_fav_threads = settings.get_int(SECTION_VIEW, 'MaxFavoriteThreads')
109 self.del_fav_thread(opening_post)
112 if (str(opening_post.id) in threads) or (len(threads) < max_fav_threads):
110
113 thread = opening_post.get_thread()
111 last_post_id = opening_post.get_thread().get_replies().last().id
114 # Don't check for new posts if the thread is archived already
112 last_post_ids.append(last_post_id)
115 if thread.is_archived():
113
116 last_id = FAV_THREAD_NO_UPDATES
114 self.set_setting(SETTING_LAST_POSTS, last_post_ids)
117 else:
118 last_id = thread.get_replies().last().id
119 threads[str(opening_post.id)] = last_id
120 self.set_setting(SETTING_FAVORITE_THREADS, threads)
121
115
122 def del_fav_thread(self, opening_post):
116 def del_fav_thread(self, opening_post):
123 threads = self.get_fav_threads()
117 last_posts_ids = self.get_setting(SETTING_LAST_POSTS)
124 if self.thread_is_fav(opening_post):
118
125 del threads[str(opening_post.id)]
119 for post in self.get_last_posts():
126 self.set_setting(SETTING_FAVORITE_THREADS, threads)
120 if post.get_thread() == opening_post.get_thread():
121 last_posts_ids.remove(post.id)
122
123 self.set_setting(SETTING_LAST_POSTS, last_posts_ids)
127
124
128 def thread_is_fav(self, opening_post):
125 def thread_is_fav(self, opening_post):
129 return str(opening_post.id) in self.get_fav_threads()
126 for post in self.get_last_posts():
127 if post.get_thread() == opening_post.get_thread():
128 return True
129 return False
130
130
131 def get_notification_usernames(self):
131 def get_notification_usernames(self):
132 names = set()
132 names = set()
133 name_list = self.get_setting(SETTING_USERNAME)
133 name_list = self.get_setting(SETTING_USERNAME)
134 if name_list is not None:
134 if name_list is not None:
135 name_list = name_list.strip()
135 name_list = name_list.strip()
136 if len(name_list) > 0:
136 if len(name_list) > 0:
137 names = name_list.lower().split(',')
137 names = name_list.lower().split(',')
138 names = set(name.strip() for name in names)
138 names = set(name.strip() for name in names)
139 return names
139 return names
140
140
141 def get_attachment_by_alias(self, alias):
141 def get_attachment_by_alias(self, alias):
142 images = self.get_setting(SETTING_IMAGES)
142 images = self.get_setting(SETTING_IMAGES)
143 if images and alias in images:
143 if images and alias in images:
144 try:
144 try:
145 return Attachment.objects.get(id=images.get(alias))
145 return Attachment.objects.get(id=images.get(alias))
146 except Attachment.DoesNotExist:
146 except Attachment.DoesNotExist:
147 self.remove_attachment_alias(alias)
147 self.remove_attachment_alias(alias)
148
148
149 def add_attachment_alias(self, alias, attachment):
149 def add_attachment_alias(self, alias, attachment):
150 images = self.get_setting(SETTING_IMAGES)
150 images = self.get_setting(SETTING_IMAGES)
151 if images is None:
151 if images is None:
152 images = dict()
152 images = dict()
153 images[alias] = attachment.id
153 images[alias] = attachment.id
154 self.set_setting(SETTING_IMAGES, images)
154 self.set_setting(SETTING_IMAGES, images)
155
155
156 def remove_attachment_alias(self, alias):
156 def remove_attachment_alias(self, alias):
157 images = self.get_setting(SETTING_IMAGES)
157 images = self.get_setting(SETTING_IMAGES)
158 del images[alias]
158 del images[alias]
159 self.set_setting(SETTING_IMAGES, images)
159 self.set_setting(SETTING_IMAGES, images)
160
160
161 def get_stickers(self):
161 def get_stickers(self):
162 images = self.get_setting(SETTING_IMAGES)
162 images = self.get_setting(SETTING_IMAGES)
163 stickers = []
163 stickers = []
164 if images:
164 if images:
165 for key, value in images.items():
165 for key, value in images.items():
166 try:
166 try:
167 attachment = Attachment.objects.get(id=value)
167 attachment = Attachment.objects.get(id=value)
168 stickers.append(AttachmentSticker(name=key, attachment=attachment))
168 stickers.append(AttachmentSticker(name=key, attachment=attachment))
169 except Attachment.DoesNotExist:
169 except Attachment.DoesNotExist:
170 self.remove_attachment_alias(key)
170 self.remove_attachment_alias(key)
171 return stickers
171 return stickers
172
172
173 def tag_is_fav(self, tag):
173 def tag_is_fav(self, tag):
174 fav_tag_names = self.get_setting(SETTING_FAVORITE_TAGS)
174 fav_tag_names = self.get_setting(SETTING_FAVORITE_TAGS)
175 return fav_tag_names is not None and tag.get_name() in fav_tag_names
175 return fav_tag_names is not None and tag.get_name() in fav_tag_names
176
176
177 def tag_is_hidden(self, tag):
177 def tag_is_hidden(self, tag):
178 hidden_tag_names = self.get_setting(SETTING_HIDDEN_TAGS)
178 hidden_tag_names = self.get_setting(SETTING_HIDDEN_TAGS)
179 return hidden_tag_names is not None and tag.get_name() in hidden_tag_names
179 return hidden_tag_names is not None and tag.get_name() in hidden_tag_names
180
180
181 def get_last_posts(self):
182 post_ids = self.get_setting(SETTING_LAST_POSTS) or []
183 return [boards.models.Post.objects.get(id=post_id) for post_id in post_ids]
184
181
185
182 class SessionSettingsManager(SettingsManager):
186 class SessionSettingsManager(SettingsManager):
183 """
187 """
184 Session-based settings manager. All settings are saved to the user's
188 Session-based settings manager. All settings are saved to the user's
185 session.
189 session.
186 """
190 """
187 def __init__(self, session):
191 def __init__(self, session):
188 SettingsManager.__init__(self)
192 SettingsManager.__init__(self)
189 self.session = session
193 self.session = session
190
194
191 def get_setting(self, setting, default=None):
195 def get_setting(self, setting, default=None):
192 if setting in self.session:
196 if setting in self.session:
193 return self.session[setting]
197 return self.session[setting]
194 else:
198 else:
195 self.set_setting(setting, default)
199 self.set_setting(setting, default)
196 return default
200 return default
197
201
198 def set_setting(self, setting, value):
202 def set_setting(self, setting, value):
199 self.session[setting] = value
203 self.session[setting] = value
200
204
201
205
202 class DatabaseSettingsManager(SessionSettingsManager):
206 class DatabaseSettingsManager(SessionSettingsManager):
203 def __init__(self, session):
207 def __init__(self, session):
204 super().__init__(session)
208 super().__init__(session)
209 if not session.session_key:
210 session.save()
205 self.settings, created = UserSettings.objects.get_or_create(session_key=session.session_key)
211 self.settings, created = UserSettings.objects.get_or_create(session_key=session.session_key)
206
212
207 def add_fav_tag(self, tag):
213 def add_fav_tag(self, tag):
208 self.settings.fav_tags.add(tag)
214 self.settings.fav_tags.add(tag)
209
215
210 def del_fav_tag(self, tag):
216 def del_fav_tag(self, tag):
211 self.settings.fav_tags.remove(tag)
217 self.settings.fav_tags.remove(tag)
212
218
213 def get_fav_tags(self) -> list:
219 def get_fav_tags(self) -> list:
214 return self.settings.fav_tags.filter(
220 return self.settings.fav_tags.filter(
215 aliases__in=TagAlias.objects.filter_localized())\
221 aliases__in=TagAlias.objects.filter_localized())\
216 .order_by('aliases__name')
222 .order_by('aliases__name')
217
223
218 def get_hidden_tags(self) -> list:
224 def get_hidden_tags(self) -> list:
219 return self.settings.hidden_tags.all()
225 return self.settings.hidden_tags.all()
220
226
221 def add_hidden_tag(self, tag):
227 def add_hidden_tag(self, tag):
222 self.settings.hidden_tags.add(tag)
228 self.settings.hidden_tags.add(tag)
223
229
224 def del_hidden_tag(self, tag):
230 def del_hidden_tag(self, tag):
225 self.settings.hidden_tags.remove(tag)
231 self.settings.hidden_tags.remove(tag)
226
232
227 def tag_is_fav(self, tag):
233 def tag_is_fav(self, tag):
228 return self.settings.fav_tags.filter(id=tag.id).exists()
234 return self.settings.fav_tags.filter(id=tag.id).exists()
229
235
230 def tag_is_hidden(self, tag):
236 def tag_is_hidden(self, tag):
231 return self.settings.hidden_tags.filter(id=tag.id).exists()
237 return self.settings.hidden_tags.filter(id=tag.id).exists()
232
238
233
239
234 def get_settings_manager(request) -> SettingsManager:
240 def get_settings_manager(request) -> SettingsManager:
235 """
241 """
236 Get settings manager based on the request object. Currently only
242 Get settings manager based on the request object. Currently only
237 session-based manager is supported. In the future, cookie-based or
243 session-based manager is supported. In the future, cookie-based or
238 database-based managers could be implemented.
244 database-based managers could be implemented.
239 """
245 """
240 return DatabaseSettingsManager(request.session)
246 return DatabaseSettingsManager(request.session)
@@ -1,87 +1,83
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, SECTION_VIEW, SECTION_VERSION
7 from boards.settings import SECTION_FORMS, SECTION_VIEW, SECTION_VERSION
8
8
9 THEME_CSS = 'css/{}/base_page.css'
9 THEME_CSS = 'css/{}/base_page.css'
10
10
11 CONTEXT_SITE_NAME = 'site_name'
11 CONTEXT_SITE_NAME = 'site_name'
12 CONTEXT_VERSION = 'version'
12 CONTEXT_VERSION = 'version'
13 CONTEXT_THEME_CSS = 'theme_css'
13 CONTEXT_THEME_CSS = 'theme_css'
14 CONTEXT_THEME = 'theme'
14 CONTEXT_THEME = 'theme'
15 CONTEXT_PPD = 'posts_per_day'
15 CONTEXT_PPD = 'posts_per_day'
16 CONTEXT_USER = 'user'
16 CONTEXT_USER = 'user'
17 CONTEXT_NEW_NOTIFICATIONS_COUNT = 'new_notifications_count'
17 CONTEXT_NEW_NOTIFICATIONS_COUNT = 'new_notifications_count'
18 CONTEXT_USERNAMES = 'usernames'
18 CONTEXT_USERNAMES = 'usernames'
19 CONTEXT_TAGS_STR = 'tags_str'
19 CONTEXT_TAGS_STR = 'tags_str'
20 CONTEXT_IMAGE_VIEWER = 'image_viewer'
20 CONTEXT_IMAGE_VIEWER = 'image_viewer'
21 CONTEXT_HAS_FAV_THREADS = 'has_fav_threads'
21 CONTEXT_HAS_FAV_THREADS = 'has_fav_threads'
22 CONTEXT_POW_DIFFICULTY = 'pow_difficulty'
22 CONTEXT_POW_DIFFICULTY = 'pow_difficulty'
23 CONTEXT_NEW_POST_COUNT = 'new_post_count'
23 CONTEXT_NEW_POST_COUNT = 'new_post_count'
24 CONTEXT_BANNERS = 'banners'
24 CONTEXT_BANNERS = 'banners'
25 CONTEXT_ONLY_FAVORITES = 'only_favorites'
25 CONTEXT_ONLY_FAVORITES = 'only_favorites'
26
26
27
27
28 def get_notifications(context, settings_manager):
28 def get_notifications(context, settings_manager):
29 usernames = settings_manager.get_notification_usernames()
29 usernames = settings_manager.get_notification_usernames()
30 new_notifications_count = 0
30 new_notifications_count = 0
31 if usernames:
31 if usernames:
32 last_notification_id = settings_manager.get_setting(
32 last_notification_id = settings_manager.get_setting(
33 SETTING_LAST_NOTIFICATION_ID)
33 SETTING_LAST_NOTIFICATION_ID)
34
34
35 new_notifications_count = Notification.objects.get_notification_posts(
35 new_notifications_count = Notification.objects.get_notification_posts(
36 usernames=usernames, last=last_notification_id).only('id').count()
36 usernames=usernames, last=last_notification_id).only('id').count()
37 context[CONTEXT_NEW_NOTIFICATIONS_COUNT] = new_notifications_count
37 context[CONTEXT_NEW_NOTIFICATIONS_COUNT] = new_notifications_count
38 context[CONTEXT_USERNAMES] = usernames
38 context[CONTEXT_USERNAMES] = usernames
39
39
40
40
41 def get_new_post_count(context, settings_manager):
41 def get_new_post_count(context, settings_manager):
42 fav_threads = settings_manager.get_fav_threads()
42 last_posts = settings_manager.get_last_posts()
43 if fav_threads:
43 count = Thread.objects.get_new_post_count(last_posts)
44 fav_thread_ops = Post.objects.filter(id__in=fav_threads.keys()) \
45 .order_by('-pub_time').only('thread_id', 'pub_time')
46 ops = [{'op': op, 'last_id': fav_threads[str(op.id)]} for op in fav_thread_ops]
47 count = Thread.objects.get_new_post_count(ops)
48 if count > 0:
44 if count > 0:
49 context[CONTEXT_NEW_POST_COUNT] = '(+{})'.format(count)
45 context[CONTEXT_NEW_POST_COUNT] = '(+{})'.format(count)
50
46
51
47
52 def user_and_ui_processor(request):
48 def user_and_ui_processor(request):
53 context = dict()
49 context = dict()
54
50
55 context[CONTEXT_PPD] = float(Post.objects.get_posts_per_day())
51 context[CONTEXT_PPD] = float(Post.objects.get_posts_per_day())
56
52
57 settings_manager = get_settings_manager(request)
53 settings_manager = get_settings_manager(request)
58 fav_tags = settings_manager.get_fav_tags()
54 fav_tags = settings_manager.get_fav_tags()
59
55
60 context[CONTEXT_TAGS_STR] = Tag.objects.get_tag_url_list(fav_tags)
56 context[CONTEXT_TAGS_STR] = Tag.objects.get_tag_url_list(fav_tags)
61 theme = settings_manager.get_theme()
57 theme = settings_manager.get_theme()
62 context[CONTEXT_THEME] = theme
58 context[CONTEXT_THEME] = theme
63
59
64 # TODO Use static here
60 # TODO Use static here
65 context[CONTEXT_THEME_CSS] = THEME_CSS.format(theme)
61 context[CONTEXT_THEME_CSS] = THEME_CSS.format(theme)
66
62
67 context[CONTEXT_VERSION] = settings.get(SECTION_VERSION, 'Version')
63 context[CONTEXT_VERSION] = settings.get(SECTION_VERSION, 'Version')
68 context[CONTEXT_SITE_NAME] = settings.get(SECTION_VERSION, 'SiteName')
64 context[CONTEXT_SITE_NAME] = settings.get(SECTION_VERSION, 'SiteName')
69
65
70 if settings.get_bool(SECTION_FORMS, 'LimitFirstPosting'):
66 if settings.get_bool(SECTION_FORMS, 'LimitFirstPosting'):
71 context[CONTEXT_POW_DIFFICULTY] = settings.get_int(SECTION_FORMS, 'PowDifficulty')
67 context[CONTEXT_POW_DIFFICULTY] = settings.get_int(SECTION_FORMS, 'PowDifficulty')
72
68
73 context[CONTEXT_IMAGE_VIEWER] = settings_manager.get_setting(
69 context[CONTEXT_IMAGE_VIEWER] = settings_manager.get_setting(
74 SETTING_IMAGE_VIEWER,
70 SETTING_IMAGE_VIEWER,
75 default=settings.get(SECTION_VIEW, 'DefaultImageViewer'))
71 default=settings.get(SECTION_VIEW, 'DefaultImageViewer'))
76
72
77 context[CONTEXT_HAS_FAV_THREADS] =\
73 context[CONTEXT_HAS_FAV_THREADS] =\
78 len(settings_manager.get_fav_threads()) > 0
74 len(settings_manager.get_last_posts()) > 0
79
75
80 context[CONTEXT_BANNERS] = Banner.objects.order_by('-id')
76 context[CONTEXT_BANNERS] = Banner.objects.order_by('-id')
81 context[CONTEXT_ONLY_FAVORITES] = settings_manager.get_setting(
77 context[CONTEXT_ONLY_FAVORITES] = settings_manager.get_setting(
82 SETTING_ONLY_FAVORITES, default=False)
78 SETTING_ONLY_FAVORITES, default=False)
83
79
84 get_notifications(context, settings_manager)
80 get_notifications(context, settings_manager)
85 get_new_post_count(context, settings_manager)
81 get_new_post_count(context, settings_manager)
86
82
87 return context
83 return context
@@ -1,326 +1,324
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, TagAlias
13 from boards.models.tag import Tag, TagAlias
14 from boards.settings import SECTION_VIEW
14 from boards.settings import SECTION_VIEW
15 from boards.utils import cached_result, datetime_to_epoch
15 from boards.utils import cached_result, datetime_to_epoch
16
16
17 FAV_THREAD_NO_UPDATES = -1
17 FAV_THREAD_NO_UPDATES = -1
18
18
19
19
20 __author__ = 'neko259'
20 __author__ = 'neko259'
21
21
22
22
23 logger = logging.getLogger(__name__)
23 logger = logging.getLogger(__name__)
24
24
25
25
26 WS_NOTIFICATION_TYPE_NEW_POST = 'new_post'
26 WS_NOTIFICATION_TYPE_NEW_POST = 'new_post'
27 WS_NOTIFICATION_TYPE = 'notification_type'
27 WS_NOTIFICATION_TYPE = 'notification_type'
28
28
29 WS_CHANNEL_THREAD = "thread:"
29 WS_CHANNEL_THREAD = "thread:"
30
30
31 STATUS_CHOICES = (
31 STATUS_CHOICES = (
32 (STATUS_ACTIVE, STATUS_ACTIVE),
32 (STATUS_ACTIVE, STATUS_ACTIVE),
33 (STATUS_BUMPLIMIT, STATUS_BUMPLIMIT),
33 (STATUS_BUMPLIMIT, STATUS_BUMPLIMIT),
34 (STATUS_ARCHIVE, STATUS_ARCHIVE),
34 (STATUS_ARCHIVE, STATUS_ARCHIVE),
35 )
35 )
36
36
37
37
38 class ThreadManager(models.Manager):
38 class ThreadManager(models.Manager):
39 def process_old_threads(self):
39 def process_old_threads(self):
40 """
40 """
41 Preserves maximum thread count. If there are too many threads,
41 Preserves maximum thread count. If there are too many threads,
42 archive or delete the old ones.
42 archive or delete the old ones.
43 """
43 """
44 old_time_delta = settings.get_int('Messages', 'ThreadArchiveDays')
44 old_time_delta = settings.get_int('Messages', 'ThreadArchiveDays')
45 old_time = timezone.now() - timedelta(days=old_time_delta)
45 old_time = timezone.now() - timedelta(days=old_time_delta)
46 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)
47
47
48 for op in old_ops:
48 for op in old_ops:
49 thread = op.get_thread()
49 thread = op.get_thread()
50 if settings.get_bool('Storage', 'ArchiveThreads'):
50 if settings.get_bool('Storage', 'ArchiveThreads'):
51 self._archive_thread(thread)
51 self._archive_thread(thread)
52 else:
52 else:
53 thread.delete()
53 thread.delete()
54 logger.info('Processed old thread {}'.format(thread))
54 logger.info('Processed old thread {}'.format(thread))
55
55
56
56
57 def _archive_thread(self, thread):
57 def _archive_thread(self, thread):
58 thread.status = STATUS_ARCHIVE
58 thread.status = STATUS_ARCHIVE
59 thread.last_edit_time = timezone.now()
59 thread.last_edit_time = timezone.now()
60 thread.update_posts_time()
60 thread.update_posts_time()
61 thread.save(update_fields=['last_edit_time', 'status'])
61 thread.save(update_fields=['last_edit_time', 'status'])
62
62
63 def get_new_posts(self, datas):
63 def get_new_posts(self, last_posts):
64 query = None
64 query = None
65 # TODO Use classes instead of dicts
65 for post in last_posts:
66 for data in datas:
66 if not post.get_thread().is_archived():
67 if data['last_id'] != FAV_THREAD_NO_UPDATES:
67 q = Q(id=post.thread_id) & Q(replies__id__gt=post.id)
68 q = (Q(id=data['op'].get_thread_id())
69 & Q(replies__id__gt=data['last_id']))
70 if query is None:
68 if query is None:
71 query = q
69 query = q
72 else:
70 else:
73 query = query | q
71 query = query | q
74 if query is not None:
72 if query is not None:
75 return self.filter(query).annotate(
73 return self.filter(query).annotate(
76 new_post_count=Count('replies'))
74 new_post_count=Count('replies'))
77
75
78 def get_new_post_count(self, datas):
76 def get_new_post_count(self, last_posts):
79 new_posts = self.get_new_posts(datas)
77 new_posts = self.get_new_posts(last_posts)
80 return new_posts.aggregate(total_count=Count('replies'))\
78 return new_posts.aggregate(total_count=Count('replies'))\
81 ['total_count'] if new_posts else 0
79 ['total_count'] if new_posts else 0
82
80
83
81
84 def get_thread_max_posts():
82 def get_thread_max_posts():
85 return settings.get_int('Messages', 'MaxPostsPerThread')
83 return settings.get_int('Messages', 'MaxPostsPerThread')
86
84
87
85
88 class Thread(models.Model):
86 class Thread(models.Model):
89 objects = ThreadManager()
87 objects = ThreadManager()
90
88
91 class Meta:
89 class Meta:
92 app_label = 'boards'
90 app_label = 'boards'
93
91
94 tags = models.ManyToManyField('Tag', related_name='thread_tags')
92 tags = models.ManyToManyField('Tag', related_name='thread_tags')
95 bump_time = models.DateTimeField(db_index=True)
93 bump_time = models.DateTimeField(db_index=True)
96 last_edit_time = models.DateTimeField()
94 last_edit_time = models.DateTimeField()
97 max_posts = models.IntegerField(default=get_thread_max_posts)
95 max_posts = models.IntegerField(default=get_thread_max_posts)
98 status = models.CharField(max_length=50, default=STATUS_ACTIVE,
96 status = models.CharField(max_length=50, default=STATUS_ACTIVE,
99 choices=STATUS_CHOICES, db_index=True)
97 choices=STATUS_CHOICES, db_index=True)
100 monochrome = models.BooleanField(default=False)
98 monochrome = models.BooleanField(default=False)
101 stickerpack = models.BooleanField(default=False)
99 stickerpack = models.BooleanField(default=False)
102
100
103 def get_tags(self) -> QuerySet:
101 def get_tags(self) -> QuerySet:
104 """
102 """
105 Gets a sorted tag list.
103 Gets a sorted tag list.
106 """
104 """
107
105
108 return self.tags.filter(aliases__in=TagAlias.objects.filter_localized(parent__thread_tags=self)).order_by('aliases__name')
106 return self.tags.filter(aliases__in=TagAlias.objects.filter_localized(parent__thread_tags=self)).order_by('aliases__name')
109
107
110 def bump(self):
108 def bump(self):
111 """
109 """
112 Bumps (moves to up) thread if possible.
110 Bumps (moves to up) thread if possible.
113 """
111 """
114
112
115 if self.can_bump():
113 if self.can_bump():
116 self.bump_time = self.last_edit_time
114 self.bump_time = self.last_edit_time
117
115
118 self.update_bump_status()
116 self.update_bump_status()
119
117
120 logger.info('Bumped thread %d' % self.id)
118 logger.info('Bumped thread %d' % self.id)
121
119
122 def has_post_limit(self) -> bool:
120 def has_post_limit(self) -> bool:
123 return self.max_posts > 0
121 return self.max_posts > 0
124
122
125 def update_bump_status(self, exclude_posts=None):
123 def update_bump_status(self, exclude_posts=None):
126 if self.has_post_limit() and self.get_reply_count() >= self.max_posts:
124 if self.has_post_limit() and self.get_reply_count() >= self.max_posts:
127 self.status = STATUS_BUMPLIMIT
125 self.status = STATUS_BUMPLIMIT
128 self.update_posts_time(exclude_posts=exclude_posts)
126 self.update_posts_time(exclude_posts=exclude_posts)
129
127
130 def _get_cache_key(self):
128 def _get_cache_key(self):
131 return [datetime_to_epoch(self.last_edit_time)]
129 return [datetime_to_epoch(self.last_edit_time)]
132
130
133 @cached_result(key_method=_get_cache_key)
131 @cached_result(key_method=_get_cache_key)
134 def get_reply_count(self) -> int:
132 def get_reply_count(self) -> int:
135 return self.get_replies().count()
133 return self.get_replies().count()
136
134
137 @cached_result(key_method=_get_cache_key)
135 @cached_result(key_method=_get_cache_key)
138 def get_images_count(self) -> int:
136 def get_images_count(self) -> int:
139 return self.get_replies().filter(
137 return self.get_replies().filter(
140 attachments__mimetype__in=FILE_TYPES_IMAGE)\
138 attachments__mimetype__in=FILE_TYPES_IMAGE)\
141 .annotate(images_count=Count(
139 .annotate(images_count=Count(
142 'attachments')).aggregate(Sum('images_count'))['images_count__sum'] or 0
140 'attachments')).aggregate(Sum('images_count'))['images_count__sum'] or 0
143
141
144 @cached_result(key_method=_get_cache_key)
142 @cached_result(key_method=_get_cache_key)
145 def get_attachment_count(self) -> int:
143 def get_attachment_count(self) -> int:
146 return self.get_replies().annotate(attachment_count=Count('attachments'))\
144 return self.get_replies().annotate(attachment_count=Count('attachments'))\
147 .aggregate(Sum('attachment_count'))['attachment_count__sum'] or 0
145 .aggregate(Sum('attachment_count'))['attachment_count__sum'] or 0
148
146
149 def can_bump(self) -> bool:
147 def can_bump(self) -> bool:
150 """
148 """
151 Checks if the thread can be bumped by replying to it.
149 Checks if the thread can be bumped by replying to it.
152 """
150 """
153
151
154 return self.get_status() == STATUS_ACTIVE
152 return self.get_status() == STATUS_ACTIVE
155
153
156 def get_last_replies(self) -> QuerySet:
154 def get_last_replies(self) -> QuerySet:
157 """
155 """
158 Gets several last replies, not including opening post
156 Gets several last replies, not including opening post
159 """
157 """
160
158
161 last_replies_count = settings.get_int(SECTION_VIEW, 'LastRepliesCount')
159 last_replies_count = settings.get_int(SECTION_VIEW, 'LastRepliesCount')
162
160
163 if last_replies_count > 0:
161 if last_replies_count > 0:
164 reply_count = self.get_reply_count()
162 reply_count = self.get_reply_count()
165
163
166 if reply_count > 0:
164 if reply_count > 0:
167 reply_count_to_show = min(last_replies_count,
165 reply_count_to_show = min(last_replies_count,
168 reply_count - 1)
166 reply_count - 1)
169 replies = self.get_replies()
167 replies = self.get_replies()
170 last_replies = replies[reply_count - reply_count_to_show:]
168 last_replies = replies[reply_count - reply_count_to_show:]
171
169
172 return last_replies
170 return last_replies
173
171
174 def get_skipped_replies_count(self) -> int:
172 def get_skipped_replies_count(self) -> int:
175 """
173 """
176 Gets number of posts between opening post and last replies.
174 Gets number of posts between opening post and last replies.
177 """
175 """
178 reply_count = self.get_reply_count()
176 reply_count = self.get_reply_count()
179 last_replies_count = min(settings.get_int(SECTION_VIEW, 'LastRepliesCount'),
177 last_replies_count = min(settings.get_int(SECTION_VIEW, 'LastRepliesCount'),
180 reply_count - 1)
178 reply_count - 1)
181 return reply_count - last_replies_count - 1
179 return reply_count - last_replies_count - 1
182
180
183 # TODO Remove argument, it is not used
181 # TODO Remove argument, it is not used
184 def get_replies(self, view_fields_only=True) -> QuerySet:
182 def get_replies(self, view_fields_only=True) -> QuerySet:
185 """
183 """
186 Gets sorted thread posts
184 Gets sorted thread posts
187 """
185 """
188 query = self.replies.order_by('pub_time').prefetch_related(
186 query = self.replies.order_by('pub_time').prefetch_related(
189 'attachments')
187 'attachments')
190 return query
188 return query
191
189
192 def get_viewable_replies(self) -> QuerySet:
190 def get_viewable_replies(self) -> QuerySet:
193 """
191 """
194 Gets replies with only fields that are used for viewing.
192 Gets replies with only fields that are used for viewing.
195 """
193 """
196 return self.get_replies().defer('text', 'last_edit_time')
194 return self.get_replies().defer('text', 'last_edit_time')
197
195
198 def get_top_level_replies(self) -> QuerySet:
196 def get_top_level_replies(self) -> QuerySet:
199 return self.get_replies().exclude(refposts__threads__in=[self])
197 return self.get_replies().exclude(refposts__threads__in=[self])
200
198
201 def get_replies_with_images(self, view_fields_only=False) -> QuerySet:
199 def get_replies_with_images(self, view_fields_only=False) -> QuerySet:
202 """
200 """
203 Gets replies that have at least one image attached
201 Gets replies that have at least one image attached
204 """
202 """
205 return self.get_replies(view_fields_only).filter(
203 return self.get_replies(view_fields_only).filter(
206 attachments__mimetype__in=FILE_TYPES_IMAGE).annotate(images_count=Count(
204 attachments__mimetype__in=FILE_TYPES_IMAGE).annotate(images_count=Count(
207 'attachments')).filter(images_count__gt=0)
205 'attachments')).filter(images_count__gt=0)
208
206
209 def get_opening_post(self, only_id=False) -> Post:
207 def get_opening_post(self, only_id=False) -> Post:
210 """
208 """
211 Gets the first post of the thread
209 Gets the first post of the thread
212 """
210 """
213
211
214 query = self.get_replies().filter(opening=True)
212 query = self.get_replies().filter(opening=True)
215 if only_id:
213 if only_id:
216 query = query.only('id')
214 query = query.only('id')
217 opening_post = query.first()
215 opening_post = query.first()
218
216
219 return opening_post
217 return opening_post
220
218
221 @cached_result()
219 @cached_result()
222 def get_opening_post_id(self) -> int:
220 def get_opening_post_id(self) -> int:
223 """
221 """
224 Gets ID of the first thread post.
222 Gets ID of the first thread post.
225 """
223 """
226
224
227 return self.get_opening_post(only_id=True).id
225 return self.get_opening_post(only_id=True).id
228
226
229 def get_pub_time(self):
227 def get_pub_time(self):
230 """
228 """
231 Gets opening post's pub time because thread does not have its own one.
229 Gets opening post's pub time because thread does not have its own one.
232 """
230 """
233
231
234 return self.get_opening_post().pub_time
232 return self.get_opening_post().pub_time
235
233
236 def __str__(self):
234 def __str__(self):
237 return 'T#{}/{}'.format(self.id, self.get_opening_post())
235 return 'T#{}/{}'.format(self.id, self.get_opening_post())
238
236
239 def get_tag_url_list(self) -> list:
237 def get_tag_url_list(self) -> list:
240 return boards.models.Tag.objects.get_tag_url_list(self.get_tags().all())
238 return boards.models.Tag.objects.get_tag_url_list(self.get_tags().all())
241
239
242 def update_posts_time(self, exclude_posts=None):
240 def update_posts_time(self, exclude_posts=None):
243 last_edit_time = self.last_edit_time
241 last_edit_time = self.last_edit_time
244
242
245 for post in self.replies.all():
243 for post in self.replies.all():
246 if exclude_posts is None or post not in exclude_posts:
244 if exclude_posts is None or post not in exclude_posts:
247 # Manual update is required because uids are generated on save
245 # Manual update is required because uids are generated on save
248 post.last_edit_time = last_edit_time
246 post.last_edit_time = last_edit_time
249 post.save(update_fields=['last_edit_time'])
247 post.save(update_fields=['last_edit_time'])
250
248
251 def get_absolute_url(self):
249 def get_absolute_url(self):
252 return self.get_opening_post().get_absolute_url()
250 return self.get_opening_post().get_absolute_url()
253
251
254 def get_required_tags(self):
252 def get_required_tags(self):
255 return self.get_tags().filter(required=True)
253 return self.get_tags().filter(required=True)
256
254
257 def get_sections_str(self):
255 def get_sections_str(self):
258 return Tag.objects.get_tag_url_list(self.get_required_tags())
256 return Tag.objects.get_tag_url_list(self.get_required_tags())
259
257
260 def get_replies_newer(self, post_id):
258 def get_replies_newer(self, post_id):
261 return self.get_replies().filter(id__gt=post_id)
259 return self.get_replies().filter(id__gt=post_id)
262
260
263 def is_archived(self):
261 def is_archived(self):
264 return self.get_status() == STATUS_ARCHIVE
262 return self.get_status() == STATUS_ARCHIVE
265
263
266 def is_bumplimit(self):
264 def is_bumplimit(self):
267 return self.get_status() == STATUS_BUMPLIMIT
265 return self.get_status() == STATUS_BUMPLIMIT
268
266
269 def get_status(self):
267 def get_status(self):
270 return self.status
268 return self.status
271
269
272 def is_monochrome(self):
270 def is_monochrome(self):
273 return self.monochrome
271 return self.monochrome
274
272
275 def is_stickerpack(self):
273 def is_stickerpack(self):
276 return self.stickerpack
274 return self.stickerpack
277
275
278 # If tags have parent, add them to the tag list
276 # If tags have parent, add them to the tag list
279 @transaction.atomic
277 @transaction.atomic
280 def refresh_tags(self):
278 def refresh_tags(self):
281 for tag in self.get_tags().all():
279 for tag in self.get_tags().all():
282 parents = tag.get_all_parents()
280 parents = tag.get_all_parents()
283 if len(parents) > 0:
281 if len(parents) > 0:
284 self.tags.add(*parents)
282 self.tags.add(*parents)
285
283
286 def get_reply_tree(self):
284 def get_reply_tree(self):
287 replies = self.get_replies().prefetch_related('refposts')
285 replies = self.get_replies().prefetch_related('refposts')
288 tree = []
286 tree = []
289 for reply in replies:
287 for reply in replies:
290 parents = reply.refposts.all()
288 parents = reply.refposts.all()
291
289
292 found_parent = False
290 found_parent = False
293 searching_for_index = False
291 searching_for_index = False
294
292
295 if len(parents) > 0:
293 if len(parents) > 0:
296 index = 0
294 index = 0
297 parent_depth = 0
295 parent_depth = 0
298
296
299 indexes_to_insert = []
297 indexes_to_insert = []
300
298
301 for depth, element in tree:
299 for depth, element in tree:
302 index += 1
300 index += 1
303
301
304 # If this element is next after parent on the same level,
302 # If this element is next after parent on the same level,
305 # insert child before it
303 # insert child before it
306 if searching_for_index and depth <= parent_depth:
304 if searching_for_index and depth <= parent_depth:
307 indexes_to_insert.append((index - 1, parent_depth))
305 indexes_to_insert.append((index - 1, parent_depth))
308 searching_for_index = False
306 searching_for_index = False
309
307
310 if element in parents:
308 if element in parents:
311 found_parent = True
309 found_parent = True
312 searching_for_index = True
310 searching_for_index = True
313 parent_depth = depth
311 parent_depth = depth
314
312
315 if not found_parent:
313 if not found_parent:
316 tree.append((0, reply))
314 tree.append((0, reply))
317 else:
315 else:
318 if searching_for_index:
316 if searching_for_index:
319 tree.append((parent_depth + 1, reply))
317 tree.append((parent_depth + 1, reply))
320
318
321 offset = 0
319 offset = 0
322 for last_index, parent_depth in indexes_to_insert:
320 for last_index, parent_depth in indexes_to_insert:
323 tree.insert(last_index + offset, (parent_depth + 1, reply))
321 tree.insert(last_index + offset, (parent_depth + 1, reply))
324 offset += 1
322 offset += 1
325
323
326 return tree
324 return tree
@@ -1,331 +1,329
1 import json
1 import json
2 import logging
2 import logging
3
3
4 from django.core import serializers
4 from django.core import serializers
5 from django.db import transaction
5 from django.db import transaction
6 from django.db.models import Q
6 from django.db.models import Q
7 from django.http import HttpResponse, HttpResponseBadRequest
7 from django.http import HttpResponse, HttpResponseBadRequest
8 from django.shortcuts import get_object_or_404
8 from django.shortcuts import get_object_or_404
9 from django.views.decorators.csrf import csrf_protect
9 from django.views.decorators.csrf import csrf_protect
10
10
11 from boards.abstracts.settingsmanager import get_settings_manager
11 from boards.abstracts.settingsmanager import get_settings_manager
12 from boards.forms import PostForm, PlainErrorList, ThreadForm
12 from boards.forms import PostForm, PlainErrorList, ThreadForm
13 from boards.mdx_neboard import Parser
13 from boards.mdx_neboard import Parser
14 from boards.models import Post, Thread, Tag, TagAlias
14 from boards.models import Post, Thread, Tag, TagAlias
15 from boards.models.attachment import AttachmentSticker
15 from boards.models.attachment import AttachmentSticker
16 from boards.models.thread import STATUS_ARCHIVE
16 from boards.models.thread import STATUS_ARCHIVE
17 from boards.models.user import Notification
17 from boards.models.user import Notification
18 from boards.utils import datetime_to_epoch
18 from boards.utils import datetime_to_epoch
19
19
20 __author__ = 'neko259'
20 __author__ = 'neko259'
21
21
22 PARAMETER_TRUNCATED = 'truncated'
22 PARAMETER_TRUNCATED = 'truncated'
23 PARAMETER_TAG = 'tag'
23 PARAMETER_TAG = 'tag'
24 PARAMETER_OFFSET = 'offset'
24 PARAMETER_OFFSET = 'offset'
25 PARAMETER_DIFF_TYPE = 'type'
25 PARAMETER_DIFF_TYPE = 'type'
26 PARAMETER_POST = 'post'
26 PARAMETER_POST = 'post'
27 PARAMETER_UPDATED = 'updated'
27 PARAMETER_UPDATED = 'updated'
28 PARAMETER_LAST_UPDATE = 'last_update'
28 PARAMETER_LAST_UPDATE = 'last_update'
29 PARAMETER_THREAD = 'thread'
29 PARAMETER_THREAD = 'thread'
30 PARAMETER_UIDS = 'uids'
30 PARAMETER_UIDS = 'uids'
31 PARAMETER_SUBSCRIBED = 'subscribed'
31 PARAMETER_SUBSCRIBED = 'subscribed'
32
32
33 DIFF_TYPE_HTML = 'html'
33 DIFF_TYPE_HTML = 'html'
34 DIFF_TYPE_JSON = 'json'
34 DIFF_TYPE_JSON = 'json'
35
35
36 STATUS_OK = 'ok'
36 STATUS_OK = 'ok'
37 STATUS_ERROR = 'error'
37 STATUS_ERROR = 'error'
38
38
39 logger = logging.getLogger(__name__)
39 logger = logging.getLogger(__name__)
40
40
41
41
42 @transaction.atomic
42 @transaction.atomic
43 def api_get_threaddiff(request):
43 def api_get_threaddiff(request):
44 """
44 """
45 Gets posts that were changed or added since time
45 Gets posts that were changed or added since time
46 """
46 """
47
47
48 thread_id = request.POST.get(PARAMETER_THREAD)
48 thread_id = request.POST.get(PARAMETER_THREAD)
49 uids_str = request.POST.get(PARAMETER_UIDS)
49 uids_str = request.POST.get(PARAMETER_UIDS)
50
50
51 if not thread_id or not uids_str:
51 if not thread_id or not uids_str:
52 return HttpResponse(content='Invalid request.')
52 return HttpResponse(content='Invalid request.')
53
53
54 uids = uids_str.strip().split(' ')
54 uids = uids_str.strip().split(' ')
55
55
56 opening_post = get_object_or_404(Post, id=thread_id)
56 opening_post = get_object_or_404(Post, id=thread_id)
57 thread = opening_post.get_thread()
57 thread = opening_post.get_thread()
58
58
59 json_data = {
59 json_data = {
60 PARAMETER_UPDATED: [],
60 PARAMETER_UPDATED: [],
61 PARAMETER_LAST_UPDATE: None, # TODO Maybe this can be removed already?
61 PARAMETER_LAST_UPDATE: None, # TODO Maybe this can be removed already?
62 }
62 }
63 posts = Post.objects.filter(thread=thread).exclude(uid__in=uids)
63 posts = Post.objects.filter(thread=thread).exclude(uid__in=uids)
64
64
65 diff_type = request.GET.get(PARAMETER_DIFF_TYPE, DIFF_TYPE_HTML)
65 diff_type = request.GET.get(PARAMETER_DIFF_TYPE, DIFF_TYPE_HTML)
66
66
67 for post in posts:
67 for post in posts:
68 json_data[PARAMETER_UPDATED].append(post.get_post_data(
68 json_data[PARAMETER_UPDATED].append(post.get_post_data(
69 format_type=diff_type, request=request))
69 format_type=diff_type, request=request))
70 json_data[PARAMETER_LAST_UPDATE] = str(thread.last_edit_time)
70 json_data[PARAMETER_LAST_UPDATE] = str(thread.last_edit_time)
71
71
72 settings_manager = get_settings_manager(request)
72 settings_manager = get_settings_manager(request)
73 json_data[PARAMETER_SUBSCRIBED] = str(settings_manager.thread_is_fav(opening_post))
73 json_data[PARAMETER_SUBSCRIBED] = str(settings_manager.thread_is_fav(opening_post))
74
74
75 # If the tag is favorite, update the counter
75 # If the tag is favorite, update the counter
76 settings_manager = get_settings_manager(request)
76 settings_manager = get_settings_manager(request)
77 favorite = settings_manager.thread_is_fav(opening_post)
77 favorite = settings_manager.thread_is_fav(opening_post)
78 if favorite:
78 if favorite:
79 settings_manager.add_or_read_fav_thread(opening_post)
79 settings_manager.add_or_read_fav_thread(opening_post)
80
80
81 return HttpResponse(content=json.dumps(json_data))
81 return HttpResponse(content=json.dumps(json_data))
82
82
83
83
84 @csrf_protect
84 @csrf_protect
85 def api_add_post(request, opening_post_id=None):
85 def api_add_post(request, opening_post_id=None):
86 """
86 """
87 Adds a post and return the JSON response for it
87 Adds a post and return the JSON response for it
88 """
88 """
89
89
90 if opening_post_id:
90 if opening_post_id:
91 opening_post = get_object_or_404(Post, id=opening_post_id)
91 opening_post = get_object_or_404(Post, id=opening_post_id)
92 else:
92 else:
93 opening_post = None
93 opening_post = None
94
94
95 status = STATUS_OK
95 status = STATUS_OK
96 errors = []
96 errors = []
97
97
98 post = None
98 post = None
99 if request.method == 'POST':
99 if request.method == 'POST':
100 if opening_post:
100 if opening_post:
101 form = PostForm(request.POST, request.FILES, error_class=PlainErrorList)
101 form = PostForm(request.POST, request.FILES, error_class=PlainErrorList)
102 else:
102 else:
103 form = ThreadForm(request.POST, request.FILES, error_class=PlainErrorList)
103 form = ThreadForm(request.POST, request.FILES, error_class=PlainErrorList)
104
104
105 form.session = request.session
105 form.session = request.session
106
106
107 if form.need_to_ban:
107 if form.need_to_ban:
108 # Ban user because he is suspected to be a bot
108 # Ban user because he is suspected to be a bot
109 # _ban_current_user(request)
109 # _ban_current_user(request)
110 status = STATUS_ERROR
110 status = STATUS_ERROR
111 if form.is_valid():
111 if form.is_valid():
112 post = Post.objects.create_from_form(request, form, opening_post,
112 post = Post.objects.create_from_form(request, form, opening_post,
113 html_response=False)
113 html_response=False)
114 if not post:
114 if not post:
115 status = STATUS_ERROR
115 status = STATUS_ERROR
116 else:
116 else:
117 logger.info('Added post #%d via api.' % post.id)
117 logger.info('Added post #%d via api.' % post.id)
118 else:
118 else:
119 status = STATUS_ERROR
119 status = STATUS_ERROR
120 errors = form.as_json_errors()
120 errors = form.as_json_errors()
121 else:
121 else:
122 status = STATUS_ERROR
122 status = STATUS_ERROR
123
123
124 response = {
124 response = {
125 'status': status,
125 'status': status,
126 'errors': errors,
126 'errors': errors,
127 }
127 }
128
128
129 if post:
129 if post:
130 response['post_id'] = post.id
130 response['post_id'] = post.id
131 if not opening_post:
131 if not opening_post:
132 # FIXME For now we include URL only for threads to navigate to them.
132 # FIXME For now we include URL only for threads to navigate to them.
133 # This needs to become something universal, just not yet sure how.
133 # This needs to become something universal, just not yet sure how.
134 response['url'] = post.get_absolute_url()
134 response['url'] = post.get_absolute_url()
135
135
136 return HttpResponse(content=json.dumps(response))
136 return HttpResponse(content=json.dumps(response))
137
137
138
138
139 def get_post(request, post_id):
139 def get_post(request, post_id):
140 """
140 """
141 Gets the html of a post. Used for popups. Post can be truncated if used
141 Gets the html of a post. Used for popups. Post can be truncated if used
142 in threads list with 'truncated' get parameter.
142 in threads list with 'truncated' get parameter.
143 """
143 """
144
144
145 post = get_object_or_404(Post, id=post_id)
145 post = get_object_or_404(Post, id=post_id)
146 truncated = PARAMETER_TRUNCATED in request.GET
146 truncated = PARAMETER_TRUNCATED in request.GET
147
147
148 return HttpResponse(content=post.get_view(truncated=truncated, need_op_data=True))
148 return HttpResponse(content=post.get_view(truncated=truncated, need_op_data=True))
149
149
150
150
151 def api_get_threads(request, count):
151 def api_get_threads(request, count):
152 """
152 """
153 Gets the JSON thread opening posts list.
153 Gets the JSON thread opening posts list.
154 Parameters that can be used for filtering:
154 Parameters that can be used for filtering:
155 tag, offset (from which thread to get results)
155 tag, offset (from which thread to get results)
156 """
156 """
157
157
158 if PARAMETER_TAG in request.GET:
158 if PARAMETER_TAG in request.GET:
159 tag_name = request.GET[PARAMETER_TAG]
159 tag_name = request.GET[PARAMETER_TAG]
160 if tag_name is not None:
160 if tag_name is not None:
161 tag = get_object_or_404(Tag, name=tag_name)
161 tag = get_object_or_404(Tag, name=tag_name)
162 threads = tag.get_threads().exclude(status=STATUS_ARCHIVE)
162 threads = tag.get_threads().exclude(status=STATUS_ARCHIVE)
163 else:
163 else:
164 threads = Thread.objects.exclude(status=STATUS_ARCHIVE)
164 threads = Thread.objects.exclude(status=STATUS_ARCHIVE)
165
165
166 if PARAMETER_OFFSET in request.GET:
166 if PARAMETER_OFFSET in request.GET:
167 offset = request.GET[PARAMETER_OFFSET]
167 offset = request.GET[PARAMETER_OFFSET]
168 offset = int(offset) if offset is not None else 0
168 offset = int(offset) if offset is not None else 0
169 else:
169 else:
170 offset = 0
170 offset = 0
171
171
172 threads = threads.order_by('-bump_time')
172 threads = threads.order_by('-bump_time')
173 threads = threads[offset:offset + int(count)]
173 threads = threads[offset:offset + int(count)]
174
174
175 opening_posts = []
175 opening_posts = []
176 for thread in threads:
176 for thread in threads:
177 opening_post = thread.get_opening_post()
177 opening_post = thread.get_opening_post()
178
178
179 # TODO Add tags, replies and images count
179 # TODO Add tags, replies and images count
180 post_data = opening_post.get_post_data(include_last_update=True)
180 post_data = opening_post.get_post_data(include_last_update=True)
181 post_data['status'] = thread.get_status()
181 post_data['status'] = thread.get_status()
182
182
183 opening_posts.append(post_data)
183 opening_posts.append(post_data)
184
184
185 return HttpResponse(content=json.dumps(opening_posts))
185 return HttpResponse(content=json.dumps(opening_posts))
186
186
187
187
188 # TODO Test this
188 # TODO Test this
189 def api_get_tags(request):
189 def api_get_tags(request):
190 """
190 """
191 Gets all tags or user tags.
191 Gets all tags or user tags.
192 """
192 """
193
193
194 # TODO Get favorite tags for the given user ID
194 # TODO Get favorite tags for the given user ID
195
195
196 tags = TagAlias.objects.all()
196 tags = TagAlias.objects.all()
197
197
198 term = request.GET.get('term')
198 term = request.GET.get('term')
199 if term is not None:
199 if term is not None:
200 tags = tags.filter(name__contains=term)
200 tags = tags.filter(name__contains=term)
201
201
202 tag_names = [tag.name for tag in tags]
202 tag_names = [tag.name for tag in tags]
203
203
204 return HttpResponse(content=json.dumps(tag_names))
204 return HttpResponse(content=json.dumps(tag_names))
205
205
206
206
207 def api_get_stickers(request):
207 def api_get_stickers(request):
208 term = request.GET.get('term')
208 term = request.GET.get('term')
209 if not term:
209 if not term:
210 return HttpResponseBadRequest()
210 return HttpResponseBadRequest()
211
211
212 global_stickers = AttachmentSticker.objects.filter(Q(name__icontains=term) | Q(stickerpack__name__icontains=term))
212 global_stickers = AttachmentSticker.objects.filter(Q(name__icontains=term) | Q(stickerpack__name__icontains=term))
213 local_stickers = [sticker for sticker in get_settings_manager(request).get_stickers() if term in sticker.name]
213 local_stickers = [sticker for sticker in get_settings_manager(request).get_stickers() if term in sticker.name]
214 stickers = list(global_stickers) + local_stickers
214 stickers = list(global_stickers) + local_stickers
215
215
216 image_dict = [{'thumb': sticker.attachment.get_thumb_url(),
216 image_dict = [{'thumb': sticker.attachment.get_thumb_url(),
217 'alias': str(sticker)}
217 'alias': str(sticker)}
218 for sticker in stickers]
218 for sticker in stickers]
219
219
220 return HttpResponse(content=json.dumps(image_dict))
220 return HttpResponse(content=json.dumps(image_dict))
221
221
222
222
223 # TODO The result can be cached by the thread last update time
223 # TODO The result can be cached by the thread last update time
224 # TODO Test this
224 # TODO Test this
225 def api_get_thread_posts(request, opening_post_id):
225 def api_get_thread_posts(request, opening_post_id):
226 """
226 """
227 Gets the JSON array of thread posts
227 Gets the JSON array of thread posts
228 """
228 """
229
229
230 opening_post = get_object_or_404(Post, id=opening_post_id)
230 opening_post = get_object_or_404(Post, id=opening_post_id)
231 thread = opening_post.get_thread()
231 thread = opening_post.get_thread()
232 posts = thread.get_replies()
232 posts = thread.get_replies()
233
233
234 json_data = {
234 json_data = {
235 'posts': [],
235 'posts': [],
236 'last_update': None,
236 'last_update': None,
237 }
237 }
238 json_post_list = []
238 json_post_list = []
239
239
240 for post in posts:
240 for post in posts:
241 json_post_list.append(post.get_post_data())
241 json_post_list.append(post.get_post_data())
242 json_data['last_update'] = datetime_to_epoch(thread.last_edit_time)
242 json_data['last_update'] = datetime_to_epoch(thread.last_edit_time)
243 json_data['posts'] = json_post_list
243 json_data['posts'] = json_post_list
244
244
245 return HttpResponse(content=json.dumps(json_data))
245 return HttpResponse(content=json.dumps(json_data))
246
246
247
247
248 def api_get_notifications(request, username):
248 def api_get_notifications(request, username):
249 last_notification_id_str = request.GET.get('last', None)
249 last_notification_id_str = request.GET.get('last', None)
250 last_id = int(last_notification_id_str) if last_notification_id_str is not None else None
250 last_id = int(last_notification_id_str) if last_notification_id_str is not None else None
251
251
252 posts = Notification.objects.get_notification_posts(usernames=[username],
252 posts = Notification.objects.get_notification_posts(usernames=[username],
253 last=last_id)
253 last=last_id)
254
254
255 json_post_list = []
255 json_post_list = []
256 for post in posts:
256 for post in posts:
257 json_post_list.append(post.get_post_data())
257 json_post_list.append(post.get_post_data())
258 return HttpResponse(content=json.dumps(json_post_list))
258 return HttpResponse(content=json.dumps(json_post_list))
259
259
260
260
261 def api_get_post(request, post_id):
261 def api_get_post(request, post_id):
262 """
262 """
263 Gets the JSON of a post. This can be
263 Gets the JSON of a post. This can be
264 used as and API for external clients.
264 used as and API for external clients.
265 """
265 """
266
266
267 post = get_object_or_404(Post, id=post_id)
267 post = get_object_or_404(Post, id=post_id)
268
268
269 json = serializers.serialize("json", [post], fields=(
269 json = serializers.serialize("json", [post], fields=(
270 "pub_time", "_text_rendered", "title", "text", "image",
270 "pub_time", "_text_rendered", "title", "text", "image",
271 "image_width", "image_height", "replies", "tags"
271 "image_width", "image_height", "replies", "tags"
272 ))
272 ))
273
273
274 return HttpResponse(content=json)
274 return HttpResponse(content=json)
275
275
276
276
277 def api_get_preview(request):
277 def api_get_preview(request):
278 raw_text = request.POST['raw_text']
278 raw_text = request.POST['raw_text']
279
279
280 parser = Parser()
280 parser = Parser()
281 return HttpResponse(content=parser.parse(parser.preparse(raw_text)))
281 return HttpResponse(content=parser.parse(parser.preparse(raw_text)))
282
282
283
283
284 def api_get_new_posts(request):
284 def api_get_new_posts(request):
285 """
285 """
286 Gets favorite threads and unread posts count.
286 Gets favorite threads and unread posts count.
287 """
287 """
288 posts = list()
288 posts = list()
289
289
290 include_posts = 'include_posts' in request.GET
290 include_posts = 'include_posts' in request.GET
291
291
292 settings_manager = get_settings_manager(request)
292 settings_manager = get_settings_manager(request)
293 fav_threads = settings_manager.get_fav_threads()
294 fav_thread_ops = Post.objects.filter(id__in=fav_threads.keys())\
295 .order_by('-pub_time').prefetch_related('thread')
296
293
297 ops = [{'op': op, 'last_id': fav_threads[str(op.id)]} for op in fav_thread_ops]
294 last_posts = settings_manager.get_last_posts()
298 if include_posts:
295 if include_posts:
299 new_post_threads = Thread.objects.get_new_posts(ops)
296 new_post_threads = Thread.objects.get_new_posts(last_posts)
300 if new_post_threads:
297 if new_post_threads:
301 thread_ids = {thread.id: thread for thread in new_post_threads}
298 thread_ids = {thread.id: thread for thread in new_post_threads}
302 else:
299 else:
303 thread_ids = dict()
300 thread_ids = dict()
304
301
305 for op in fav_thread_ops:
302 for post in last_posts:
306 fav_thread_dict = dict()
303 fav_thread_dict = dict()
307
304
308 op_thread = op.get_thread()
305 thread = post.get_thread()
309 if op_thread.id in thread_ids:
306 op = thread.get_opening_post()
310 thread = thread_ids[op_thread.id]
307 if thread.id in thread_ids:
308 thread = thread_ids[thread.id]
311 new_post_count = thread.new_post_count
309 new_post_count = thread.new_post_count
312 fav_thread_dict['newest_post_link'] = thread.get_replies()\
310 fav_thread_dict['newest_post_link'] = thread.get_replies()\
313 .filter(id__gt=fav_threads[str(op.id)])\
311 .filter(id__gt=post.id)\
314 .first().get_absolute_url(thread=thread)
312 .first().get_absolute_url(thread=thread)
315 else:
313 else:
316 new_post_count = 0
314 new_post_count = 0
317 fav_thread_dict['new_post_count'] = new_post_count
315 fav_thread_dict['new_post_count'] = new_post_count
318
316
319 fav_thread_dict['id'] = op.id
317 fav_thread_dict['id'] = op.id
320
318
321 fav_thread_dict['post_url'] = op.get_link_view()
319 fav_thread_dict['post_url'] = op.get_link_view()
322 fav_thread_dict['title'] = op.title
320 fav_thread_dict['title'] = op.title
323
321
324 posts.append(fav_thread_dict)
322 posts.append(fav_thread_dict)
325 else:
323 else:
326 fav_thread_dict = dict()
324 fav_thread_dict = dict()
327 fav_thread_dict['new_post_count'] = \
325 fav_thread_dict['new_post_count'] = \
328 Thread.objects.get_new_post_count(ops)
326 Thread.objects.get_new_post_count(last_posts)
329 posts.append(fav_thread_dict)
327 posts.append(fav_thread_dict)
330
328
331 return HttpResponse(content=json.dumps(posts))
329 return HttpResponse(content=json.dumps(posts))
@@ -1,132 +1,132
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.constants import PARAM_PAGE
5 from boards.abstracts.constants import PARAM_PAGE
6 from boards.abstracts.paginator import get_paginator
6 from boards.abstracts.paginator import get_paginator
7 from boards.abstracts.settingsmanager import get_settings_manager
7 from boards.abstracts.settingsmanager import get_settings_manager
8 from boards.models import Post
8 from boards.models import Post
9 from boards.settings import SECTION_VIEW
9 from boards.settings import SECTION_VIEW
10 from boards.views.base import BaseBoardView
10 from boards.views.base import BaseBoardView
11 from boards.views.mixins import PaginatedMixin
11 from boards.views.mixins import PaginatedMixin
12
12
13 POSTS_PER_PAGE = settings.get_int(SECTION_VIEW, 'PostsPerPage')
13 POSTS_PER_PAGE = settings.get_int(SECTION_VIEW, 'PostsPerPage')
14
14
15 PARAMETER_POSTS = 'posts'
15 PARAMETER_POSTS = 'posts'
16 PARAMETER_QUERIES = 'queries'
16 PARAMETER_QUERIES = 'queries'
17
17
18 TEMPLATE = 'boards/feed.html'
18 TEMPLATE = 'boards/feed.html'
19 DEFAULT_PAGE = 1
19 DEFAULT_PAGE = 1
20
20
21
21
22 class FeedFilter:
22 class FeedFilter:
23 @staticmethod
23 @staticmethod
24 def get_filtered_posts(request, posts):
24 def get_filtered_posts(request, posts):
25 return posts
25 return posts
26
26
27 @staticmethod
27 @staticmethod
28 def get_query(request):
28 def get_query(request):
29 return None
29 return None
30
30
31
31
32 class TripcodeFilter(FeedFilter):
32 class TripcodeFilter(FeedFilter):
33 @staticmethod
33 @staticmethod
34 def get_filtered_posts(request, posts):
34 def get_filtered_posts(request, posts):
35 filtered_posts = posts
35 filtered_posts = posts
36 tripcode = request.GET.get('tripcode', None)
36 tripcode = request.GET.get('tripcode', None)
37 if tripcode:
37 if tripcode:
38 filtered_posts = filtered_posts.filter(tripcode=tripcode)
38 filtered_posts = filtered_posts.filter(tripcode=tripcode)
39 return filtered_posts
39 return filtered_posts
40
40
41 @staticmethod
41 @staticmethod
42 def get_query(request):
42 def get_query(request):
43 tripcode = request.GET.get('tripcode', None)
43 tripcode = request.GET.get('tripcode', None)
44 if tripcode:
44 if tripcode:
45 return 'Tripcode: {}'.format(tripcode)
45 return 'Tripcode: {}'.format(tripcode)
46
46
47
47
48 class FavoritesFilter(FeedFilter):
48 class FavoritesFilter(FeedFilter):
49 @staticmethod
49 @staticmethod
50 def get_filtered_posts(request, posts):
50 def get_filtered_posts(request, posts):
51 filtered_posts = posts
51 filtered_posts = posts
52
52
53 favorites = 'favorites' in request.GET
53 favorites = 'favorites' in request.GET
54 if favorites:
54 if favorites:
55 settings_manager = get_settings_manager(request)
55 settings_manager = get_settings_manager(request)
56 fav_thread_ops = Post.objects.filter(id__in=settings_manager.get_fav_threads().keys())
56 last_posts = settings_manager.get_last_posts()
57 fav_threads = [op.get_thread() for op in fav_thread_ops]
57 threads = [post.get_thread() for post in last_posts]
58 filtered_posts = filtered_posts.filter(thread__in=fav_threads)
58 filtered_posts = filtered_posts.filter(thread__in=threads)
59 return filtered_posts
59 return filtered_posts
60
60
61
61
62 class IpFilter(FeedFilter):
62 class IpFilter(FeedFilter):
63 @staticmethod
63 @staticmethod
64 def get_filtered_posts(request, posts):
64 def get_filtered_posts(request, posts):
65 filtered_posts = posts
65 filtered_posts = posts
66
66
67 ip = request.GET.get('ip', None)
67 ip = request.GET.get('ip', None)
68 if ip and request.user.has_perm('post_delete'):
68 if ip and request.user.has_perm('post_delete'):
69 filtered_posts = filtered_posts.filter(poster_ip=ip)
69 filtered_posts = filtered_posts.filter(poster_ip=ip)
70 return filtered_posts
70 return filtered_posts
71
71
72 @staticmethod
72 @staticmethod
73 def get_query(request):
73 def get_query(request):
74 ip = request.GET.get('ip', None)
74 ip = request.GET.get('ip', None)
75 if ip:
75 if ip:
76 return 'IP: {}'.format(ip)
76 return 'IP: {}'.format(ip)
77
77
78
78
79 class ImageFilter(FeedFilter):
79 class ImageFilter(FeedFilter):
80 @staticmethod
80 @staticmethod
81 def get_filtered_posts(request, posts):
81 def get_filtered_posts(request, posts):
82 filtered_posts = posts
82 filtered_posts = posts
83
83
84 image = request.GET.get('image', None)
84 image = request.GET.get('image', None)
85 if image:
85 if image:
86 filtered_posts = filtered_posts.filter(attachments__file=image)
86 filtered_posts = filtered_posts.filter(attachments__file=image)
87 return filtered_posts
87 return filtered_posts
88
88
89 @staticmethod
89 @staticmethod
90 def get_query(request):
90 def get_query(request):
91 image = request.GET.get('image', None)
91 image = request.GET.get('image', None)
92 if image:
92 if image:
93 return 'File: {}'.format(image)
93 return 'File: {}'.format(image)
94
94
95
95
96 class FeedView(PaginatedMixin, BaseBoardView):
96 class FeedView(PaginatedMixin, BaseBoardView):
97 filters = (
97 filters = (
98 TripcodeFilter,
98 TripcodeFilter,
99 FavoritesFilter,
99 FavoritesFilter,
100 IpFilter,
100 IpFilter,
101 ImageFilter,
101 ImageFilter,
102 )
102 )
103
103
104 def get(self, request):
104 def get(self, request):
105 page = request.GET.get(PARAM_PAGE, DEFAULT_PAGE)
105 page = request.GET.get(PARAM_PAGE, DEFAULT_PAGE)
106
106
107 params = self.get_context_data(request=request)
107 params = self.get_context_data(request=request)
108
108
109 settings_manager = get_settings_manager(request)
109 settings_manager = get_settings_manager(request)
110
110
111 posts = Post.objects.exclude(
111 posts = Post.objects.exclude(
112 thread__tags__in=settings_manager.get_hidden_tags()).order_by(
112 thread__tags__in=settings_manager.get_hidden_tags()).order_by(
113 '-pub_time').prefetch_related('attachments', 'thread')
113 '-pub_time').prefetch_related('attachments', 'thread')
114 queries = []
114 queries = []
115 for filter in self.filters:
115 for filter in self.filters:
116 posts = filter.get_filtered_posts(request, posts)
116 posts = filter.get_filtered_posts(request, posts)
117 query = filter.get_query(request)
117 query = filter.get_query(request)
118 if query:
118 if query:
119 queries.append(query)
119 queries.append(query)
120 params[PARAMETER_QUERIES] = queries
120 params[PARAMETER_QUERIES] = queries
121
121
122 paginator = get_paginator(posts, POSTS_PER_PAGE)
122 paginator = get_paginator(posts, POSTS_PER_PAGE)
123 paginator.current_page = int(page)
123 paginator.current_page = int(page)
124
124
125 params[PARAMETER_POSTS] = paginator.page(page).object_list
125 params[PARAMETER_POSTS] = paginator.page(page).object_list
126
126
127 paginator.set_url(reverse('feed'), request.GET.dict())
127 paginator.set_url(reverse('feed'), request.GET.dict())
128
128
129 params.update(self.get_page_context(paginator, page))
129 params.update(self.get_page_context(paginator, page))
130
130
131 return render(request, TEMPLATE, params)
131 return render(request, TEMPLATE, params)
132
132
General Comments 0
You need to be logged in to leave comments. Login now