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 |
|
|
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 |
r |
|
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 |
|
|
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 |
|
|
44 | if count > 0: | |
49 |
|
|
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_ |
|
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, |
|
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, |
|
76 | def get_new_post_count(self, last_posts): | |
79 |
new_posts = self.get_new_posts( |
|
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( |
|
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 |
|
302 | for post in last_posts: | |
306 | fav_thread_dict = dict() |
|
303 | fav_thread_dict = dict() | |
307 |
|
304 | |||
308 |
|
|
305 | thread = post.get_thread() | |
309 | if op_thread.id in thread_ids: |
|
306 | op = thread.get_opening_post() | |
310 |
|
|
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= |
|
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( |
|
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 |
|
|
57 | threads = [post.get_thread() for post in last_posts] | |
58 |
filtered_posts = filtered_posts.filter(thread__in= |
|
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