##// END OF EJS Templates
Don't check new posts in the archived threads
neko259 -
r1344:efdda723 default
parent child Browse files
Show More
@@ -1,166 +1,173 b''
1 from boards.models import Tag
1 from boards.models import Tag
2
2
3 MAX_TRIPCODE_COLLISIONS = 50
3 MAX_TRIPCODE_COLLISIONS = 50
4
4
5 __author__ = 'neko259'
5 __author__ = 'neko259'
6
6
7 SESSION_SETTING = 'setting'
7 SESSION_SETTING = 'setting'
8
8
9 # Remove this, it is not used any more cause there is a user's permission
9 # Remove this, it is not used any more cause there is a user's permission
10 PERMISSION_MODERATE = 'moderator'
10 PERMISSION_MODERATE = 'moderator'
11
11
12 SETTING_THEME = 'theme'
12 SETTING_THEME = 'theme'
13 SETTING_FAVORITE_TAGS = 'favorite_tags'
13 SETTING_FAVORITE_TAGS = 'favorite_tags'
14 SETTING_FAVORITE_THREADS = 'favorite_threads'
14 SETTING_FAVORITE_THREADS = 'favorite_threads'
15 SETTING_HIDDEN_TAGS = 'hidden_tags'
15 SETTING_HIDDEN_TAGS = 'hidden_tags'
16 SETTING_PERMISSIONS = 'permissions'
16 SETTING_PERMISSIONS = 'permissions'
17 SETTING_USERNAME = 'username'
17 SETTING_USERNAME = 'username'
18 SETTING_LAST_NOTIFICATION_ID = 'last_notification'
18 SETTING_LAST_NOTIFICATION_ID = 'last_notification'
19 SETTING_IMAGE_VIEWER = 'image_viewer'
19 SETTING_IMAGE_VIEWER = 'image_viewer'
20 SETTING_TRIPCODE = 'tripcode'
20 SETTING_TRIPCODE = 'tripcode'
21
21
22 FAV_THREAD_NO_UPDATES = -1
23
22 DEFAULT_THEME = 'md'
24 DEFAULT_THEME = 'md'
23
25
24
26
25 class SettingsManager:
27 class SettingsManager:
26 """
28 """
27 Base settings manager class. get_setting and set_setting methods should
29 Base settings manager class. get_setting and set_setting methods should
28 be overriden.
30 be overriden.
29 """
31 """
30 def __init__(self):
32 def __init__(self):
31 pass
33 pass
32
34
33 def get_theme(self) -> str:
35 def get_theme(self) -> str:
34 theme = self.get_setting(SETTING_THEME)
36 theme = self.get_setting(SETTING_THEME)
35 if not theme:
37 if not theme:
36 theme = DEFAULT_THEME
38 theme = DEFAULT_THEME
37 self.set_setting(SETTING_THEME, theme)
39 self.set_setting(SETTING_THEME, theme)
38
40
39 return theme
41 return theme
40
42
41 def set_theme(self, theme):
43 def set_theme(self, theme):
42 self.set_setting(SETTING_THEME, theme)
44 self.set_setting(SETTING_THEME, theme)
43
45
44 def has_permission(self, permission):
46 def has_permission(self, permission):
45 permissions = self.get_setting(SETTING_PERMISSIONS)
47 permissions = self.get_setting(SETTING_PERMISSIONS)
46 if permissions:
48 if permissions:
47 return permission in permissions
49 return permission in permissions
48 else:
50 else:
49 return False
51 return False
50
52
51 def get_setting(self, setting, default=None):
53 def get_setting(self, setting, default=None):
52 pass
54 pass
53
55
54 def set_setting(self, setting, value):
56 def set_setting(self, setting, value):
55 pass
57 pass
56
58
57 def add_permission(self, permission):
59 def add_permission(self, permission):
58 permissions = self.get_setting(SETTING_PERMISSIONS)
60 permissions = self.get_setting(SETTING_PERMISSIONS)
59 if not permissions:
61 if not permissions:
60 permissions = [permission]
62 permissions = [permission]
61 else:
63 else:
62 permissions.append(permission)
64 permissions.append(permission)
63 self.set_setting(SETTING_PERMISSIONS, permissions)
65 self.set_setting(SETTING_PERMISSIONS, permissions)
64
66
65 def del_permission(self, permission):
67 def del_permission(self, permission):
66 permissions = self.get_setting(SETTING_PERMISSIONS)
68 permissions = self.get_setting(SETTING_PERMISSIONS)
67 if not permissions:
69 if not permissions:
68 permissions = []
70 permissions = []
69 else:
71 else:
70 permissions.remove(permission)
72 permissions.remove(permission)
71 self.set_setting(SETTING_PERMISSIONS, permissions)
73 self.set_setting(SETTING_PERMISSIONS, permissions)
72
74
73 def get_fav_tags(self) -> list:
75 def get_fav_tags(self) -> list:
74 tag_names = self.get_setting(SETTING_FAVORITE_TAGS)
76 tag_names = self.get_setting(SETTING_FAVORITE_TAGS)
75 tags = []
77 tags = []
76 if tag_names:
78 if tag_names:
77 tags = list(Tag.objects.filter(name__in=tag_names))
79 tags = list(Tag.objects.filter(name__in=tag_names))
78 return tags
80 return tags
79
81
80 def add_fav_tag(self, tag):
82 def add_fav_tag(self, tag):
81 tags = self.get_setting(SETTING_FAVORITE_TAGS)
83 tags = self.get_setting(SETTING_FAVORITE_TAGS)
82 if not tags:
84 if not tags:
83 tags = [tag.name]
85 tags = [tag.name]
84 else:
86 else:
85 if not tag.name in tags:
87 if not tag.name in tags:
86 tags.append(tag.name)
88 tags.append(tag.name)
87
89
88 tags.sort()
90 tags.sort()
89 self.set_setting(SETTING_FAVORITE_TAGS, tags)
91 self.set_setting(SETTING_FAVORITE_TAGS, tags)
90
92
91 def del_fav_tag(self, tag):
93 def del_fav_tag(self, tag):
92 tags = self.get_setting(SETTING_FAVORITE_TAGS)
94 tags = self.get_setting(SETTING_FAVORITE_TAGS)
93 if tag.name in tags:
95 if tag.name in tags:
94 tags.remove(tag.name)
96 tags.remove(tag.name)
95 self.set_setting(SETTING_FAVORITE_TAGS, tags)
97 self.set_setting(SETTING_FAVORITE_TAGS, tags)
96
98
97 def get_hidden_tags(self) -> list:
99 def get_hidden_tags(self) -> list:
98 tag_names = self.get_setting(SETTING_HIDDEN_TAGS)
100 tag_names = self.get_setting(SETTING_HIDDEN_TAGS)
99 tags = []
101 tags = []
100 if tag_names:
102 if tag_names:
101 tags = list(Tag.objects.filter(name__in=tag_names))
103 tags = list(Tag.objects.filter(name__in=tag_names))
102
104
103 return tags
105 return tags
104
106
105 def add_hidden_tag(self, tag):
107 def add_hidden_tag(self, tag):
106 tags = self.get_setting(SETTING_HIDDEN_TAGS)
108 tags = self.get_setting(SETTING_HIDDEN_TAGS)
107 if not tags:
109 if not tags:
108 tags = [tag.name]
110 tags = [tag.name]
109 else:
111 else:
110 if not tag.name in tags:
112 if not tag.name in tags:
111 tags.append(tag.name)
113 tags.append(tag.name)
112
114
113 tags.sort()
115 tags.sort()
114 self.set_setting(SETTING_HIDDEN_TAGS, tags)
116 self.set_setting(SETTING_HIDDEN_TAGS, tags)
115
117
116 def del_hidden_tag(self, tag):
118 def del_hidden_tag(self, tag):
117 tags = self.get_setting(SETTING_HIDDEN_TAGS)
119 tags = self.get_setting(SETTING_HIDDEN_TAGS)
118 if tag.name in tags:
120 if tag.name in tags:
119 tags.remove(tag.name)
121 tags.remove(tag.name)
120 self.set_setting(SETTING_HIDDEN_TAGS, tags)
122 self.set_setting(SETTING_HIDDEN_TAGS, tags)
121
123
122 def get_fav_threads(self) -> dict:
124 def get_fav_threads(self) -> dict:
123 return self.get_setting(SETTING_FAVORITE_THREADS, default=dict())
125 return self.get_setting(SETTING_FAVORITE_THREADS, default=dict())
124
126
125 def add_or_read_fav_thread(self, opening_post):
127 def add_or_read_fav_thread(self, opening_post):
126 threads = self.get_fav_threads()
128 threads = self.get_fav_threads()
127 threads[str(opening_post.id)] = opening_post.get_thread().get_replies()\
129 thread = opening_post.get_thread()
128 .last().id
130 # Don't check for new posts if the thread is archived already
131 if thread.is_archived():
132 last_id = FAV_THREAD_NO_UPDATES
133 else:
134 last_id = thread.get_replies().last().id
135 threads[str(opening_post.id)] = last_id
129 self.set_setting(SETTING_FAVORITE_THREADS, threads)
136 self.set_setting(SETTING_FAVORITE_THREADS, threads)
130
137
131 def del_fav_thread(self, opening_post):
138 def del_fav_thread(self, opening_post):
132 threads = self.get_fav_threads()
139 threads = self.get_fav_threads()
133 if self.thread_is_fav(opening_post):
140 if self.thread_is_fav(opening_post):
134 del threads[str(opening_post.id)]
141 del threads[str(opening_post.id)]
135 self.set_setting(SETTING_FAVORITE_THREADS, threads)
142 self.set_setting(SETTING_FAVORITE_THREADS, threads)
136
143
137 def thread_is_fav(self, opening_post):
144 def thread_is_fav(self, opening_post):
138 return str(opening_post.id) in self.get_fav_threads()
145 return str(opening_post.id) in self.get_fav_threads()
139
146
140 class SessionSettingsManager(SettingsManager):
147 class SessionSettingsManager(SettingsManager):
141 """
148 """
142 Session-based settings manager. All settings are saved to the user's
149 Session-based settings manager. All settings are saved to the user's
143 session.
150 session.
144 """
151 """
145 def __init__(self, session):
152 def __init__(self, session):
146 SettingsManager.__init__(self)
153 SettingsManager.__init__(self)
147 self.session = session
154 self.session = session
148
155
149 def get_setting(self, setting, default=None):
156 def get_setting(self, setting, default=None):
150 if setting in self.session:
157 if setting in self.session:
151 return self.session[setting]
158 return self.session[setting]
152 else:
159 else:
153 self.set_setting(setting, default)
160 self.set_setting(setting, default)
154 return default
161 return default
155
162
156 def set_setting(self, setting, value):
163 def set_setting(self, setting, value):
157 self.session[setting] = value
164 self.session[setting] = value
158
165
159
166
160 def get_settings_manager(request) -> SettingsManager:
167 def get_settings_manager(request) -> SettingsManager:
161 """
168 """
162 Get settings manager based on the request object. Currently only
169 Get settings manager based on the request object. Currently only
163 session-based manager is supported. In the future, cookie-based or
170 session-based manager is supported. In the future, cookie-based or
164 database-based managers could be implemented.
171 database-based managers could be implemented.
165 """
172 """
166 return SessionSettingsManager(request.session)
173 return SessionSettingsManager(request.session)
@@ -1,235 +1,238 b''
1 import logging
1 import logging
2 from adjacent import Client
2 from adjacent import Client
3
3
4 from django.db.models import Count, Sum, QuerySet
4 from django.db.models import Count, Sum, QuerySet
5 from django.utils import timezone
5 from django.utils import timezone
6 from django.db import models
6 from django.db import models
7
7
8 from boards import settings
8 from boards import settings
9 import boards
9 import boards
10 from boards.utils import cached_result, datetime_to_epoch
10 from boards.utils import cached_result, datetime_to_epoch
11 from boards.models.post import Post
11 from boards.models.post import Post
12 from boards.models.tag import Tag
12 from boards.models.tag import Tag
13
13
14
14
15 __author__ = 'neko259'
15 __author__ = 'neko259'
16
16
17
17
18 logger = logging.getLogger(__name__)
18 logger = logging.getLogger(__name__)
19
19
20
20
21 WS_NOTIFICATION_TYPE_NEW_POST = 'new_post'
21 WS_NOTIFICATION_TYPE_NEW_POST = 'new_post'
22 WS_NOTIFICATION_TYPE = 'notification_type'
22 WS_NOTIFICATION_TYPE = 'notification_type'
23
23
24 WS_CHANNEL_THREAD = "thread:"
24 WS_CHANNEL_THREAD = "thread:"
25
25
26
26
27 class ThreadManager(models.Manager):
27 class ThreadManager(models.Manager):
28 def process_oldest_threads(self):
28 def process_oldest_threads(self):
29 """
29 """
30 Preserves maximum thread count. If there are too many threads,
30 Preserves maximum thread count. If there are too many threads,
31 archive or delete the old ones.
31 archive or delete the old ones.
32 """
32 """
33
33
34 threads = Thread.objects.filter(archived=False).order_by('-bump_time')
34 threads = Thread.objects.filter(archived=False).order_by('-bump_time')
35 thread_count = threads.count()
35 thread_count = threads.count()
36
36
37 max_thread_count = settings.get_int('Messages', 'MaxThreadCount')
37 max_thread_count = settings.get_int('Messages', 'MaxThreadCount')
38 if thread_count > max_thread_count:
38 if thread_count > max_thread_count:
39 num_threads_to_delete = thread_count - max_thread_count
39 num_threads_to_delete = thread_count - max_thread_count
40 old_threads = threads[thread_count - num_threads_to_delete:]
40 old_threads = threads[thread_count - num_threads_to_delete:]
41
41
42 for thread in old_threads:
42 for thread in old_threads:
43 if settings.get_bool('Storage', 'ArchiveThreads'):
43 if settings.get_bool('Storage', 'ArchiveThreads'):
44 self._archive_thread(thread)
44 self._archive_thread(thread)
45 else:
45 else:
46 thread.delete()
46 thread.delete()
47
47
48 logger.info('Processed %d old threads' % num_threads_to_delete)
48 logger.info('Processed %d old threads' % num_threads_to_delete)
49
49
50 def _archive_thread(self, thread):
50 def _archive_thread(self, thread):
51 thread.archived = True
51 thread.archived = True
52 thread.bumpable = False
52 thread.bumpable = False
53 thread.last_edit_time = timezone.now()
53 thread.last_edit_time = timezone.now()
54 thread.update_posts_time()
54 thread.update_posts_time()
55 thread.save(update_fields=['archived', 'last_edit_time', 'bumpable'])
55 thread.save(update_fields=['archived', 'last_edit_time', 'bumpable'])
56
56
57
57
58 def get_thread_max_posts():
58 def get_thread_max_posts():
59 return settings.get_int('Messages', 'MaxPostsPerThread')
59 return settings.get_int('Messages', 'MaxPostsPerThread')
60
60
61
61
62 class Thread(models.Model):
62 class Thread(models.Model):
63 objects = ThreadManager()
63 objects = ThreadManager()
64
64
65 class Meta:
65 class Meta:
66 app_label = 'boards'
66 app_label = 'boards'
67
67
68 tags = models.ManyToManyField('Tag', related_name='thread_tags')
68 tags = models.ManyToManyField('Tag', related_name='thread_tags')
69 bump_time = models.DateTimeField(db_index=True)
69 bump_time = models.DateTimeField(db_index=True)
70 last_edit_time = models.DateTimeField()
70 last_edit_time = models.DateTimeField()
71 archived = models.BooleanField(default=False)
71 archived = models.BooleanField(default=False)
72 bumpable = models.BooleanField(default=True)
72 bumpable = models.BooleanField(default=True)
73 max_posts = models.IntegerField(default=get_thread_max_posts)
73 max_posts = models.IntegerField(default=get_thread_max_posts)
74
74
75 def get_tags(self) -> QuerySet:
75 def get_tags(self) -> QuerySet:
76 """
76 """
77 Gets a sorted tag list.
77 Gets a sorted tag list.
78 """
78 """
79
79
80 return self.tags.order_by('name')
80 return self.tags.order_by('name')
81
81
82 def bump(self):
82 def bump(self):
83 """
83 """
84 Bumps (moves to up) thread if possible.
84 Bumps (moves to up) thread if possible.
85 """
85 """
86
86
87 if self.can_bump():
87 if self.can_bump():
88 self.bump_time = self.last_edit_time
88 self.bump_time = self.last_edit_time
89
89
90 self.update_bump_status()
90 self.update_bump_status()
91
91
92 logger.info('Bumped thread %d' % self.id)
92 logger.info('Bumped thread %d' % self.id)
93
93
94 def has_post_limit(self) -> bool:
94 def has_post_limit(self) -> bool:
95 return self.max_posts > 0
95 return self.max_posts > 0
96
96
97 def update_bump_status(self, exclude_posts=None):
97 def update_bump_status(self, exclude_posts=None):
98 if self.has_post_limit() and self.get_reply_count() >= self.max_posts:
98 if self.has_post_limit() and self.get_reply_count() >= self.max_posts:
99 self.bumpable = False
99 self.bumpable = False
100 self.update_posts_time(exclude_posts=exclude_posts)
100 self.update_posts_time(exclude_posts=exclude_posts)
101
101
102 def _get_cache_key(self):
102 def _get_cache_key(self):
103 return [datetime_to_epoch(self.last_edit_time)]
103 return [datetime_to_epoch(self.last_edit_time)]
104
104
105 @cached_result(key_method=_get_cache_key)
105 @cached_result(key_method=_get_cache_key)
106 def get_reply_count(self) -> int:
106 def get_reply_count(self) -> int:
107 return self.get_replies().count()
107 return self.get_replies().count()
108
108
109 @cached_result(key_method=_get_cache_key)
109 @cached_result(key_method=_get_cache_key)
110 def get_images_count(self) -> int:
110 def get_images_count(self) -> int:
111 return self.get_replies().annotate(images_count=Count(
111 return self.get_replies().annotate(images_count=Count(
112 'images')).aggregate(Sum('images_count'))['images_count__sum']
112 'images')).aggregate(Sum('images_count'))['images_count__sum']
113
113
114 def can_bump(self) -> bool:
114 def can_bump(self) -> bool:
115 """
115 """
116 Checks if the thread can be bumped by replying to it.
116 Checks if the thread can be bumped by replying to it.
117 """
117 """
118
118
119 return self.bumpable and not self.archived
119 return self.bumpable and not self.is_archived()
120
120
121 def get_last_replies(self) -> QuerySet:
121 def get_last_replies(self) -> QuerySet:
122 """
122 """
123 Gets several last replies, not including opening post
123 Gets several last replies, not including opening post
124 """
124 """
125
125
126 last_replies_count = settings.get_int('View', 'LastRepliesCount')
126 last_replies_count = settings.get_int('View', 'LastRepliesCount')
127
127
128 if last_replies_count > 0:
128 if last_replies_count > 0:
129 reply_count = self.get_reply_count()
129 reply_count = self.get_reply_count()
130
130
131 if reply_count > 0:
131 if reply_count > 0:
132 reply_count_to_show = min(last_replies_count,
132 reply_count_to_show = min(last_replies_count,
133 reply_count - 1)
133 reply_count - 1)
134 replies = self.get_replies()
134 replies = self.get_replies()
135 last_replies = replies[reply_count - reply_count_to_show:]
135 last_replies = replies[reply_count - reply_count_to_show:]
136
136
137 return last_replies
137 return last_replies
138
138
139 def get_skipped_replies_count(self) -> int:
139 def get_skipped_replies_count(self) -> int:
140 """
140 """
141 Gets number of posts between opening post and last replies.
141 Gets number of posts between opening post and last replies.
142 """
142 """
143 reply_count = self.get_reply_count()
143 reply_count = self.get_reply_count()
144 last_replies_count = min(settings.get_int('View', 'LastRepliesCount'),
144 last_replies_count = min(settings.get_int('View', 'LastRepliesCount'),
145 reply_count - 1)
145 reply_count - 1)
146 return reply_count - last_replies_count - 1
146 return reply_count - last_replies_count - 1
147
147
148 def get_replies(self, view_fields_only=False) -> QuerySet:
148 def get_replies(self, view_fields_only=False) -> QuerySet:
149 """
149 """
150 Gets sorted thread posts
150 Gets sorted thread posts
151 """
151 """
152
152
153 query = Post.objects.filter(threads__in=[self])
153 query = Post.objects.filter(threads__in=[self])
154 query = query.order_by('pub_time').prefetch_related(
154 query = query.order_by('pub_time').prefetch_related(
155 'images', 'thread', 'threads', 'attachments')
155 'images', 'thread', 'threads', 'attachments')
156 if view_fields_only:
156 if view_fields_only:
157 query = query.defer('poster_ip')
157 query = query.defer('poster_ip')
158 return query.all()
158 return query.all()
159
159
160 def get_top_level_replies(self) -> QuerySet:
160 def get_top_level_replies(self) -> QuerySet:
161 return self.get_replies().exclude(refposts__threads__in=[self])
161 return self.get_replies().exclude(refposts__threads__in=[self])
162
162
163 def get_replies_with_images(self, view_fields_only=False) -> QuerySet:
163 def get_replies_with_images(self, view_fields_only=False) -> QuerySet:
164 """
164 """
165 Gets replies that have at least one image attached
165 Gets replies that have at least one image attached
166 """
166 """
167
167
168 return self.get_replies(view_fields_only).annotate(images_count=Count(
168 return self.get_replies(view_fields_only).annotate(images_count=Count(
169 'images')).filter(images_count__gt=0)
169 'images')).filter(images_count__gt=0)
170
170
171 def get_opening_post(self, only_id=False) -> Post:
171 def get_opening_post(self, only_id=False) -> Post:
172 """
172 """
173 Gets the first post of the thread
173 Gets the first post of the thread
174 """
174 """
175
175
176 query = self.get_replies().order_by('pub_time')
176 query = self.get_replies().order_by('pub_time')
177 if only_id:
177 if only_id:
178 query = query.only('id')
178 query = query.only('id')
179 opening_post = query.first()
179 opening_post = query.first()
180
180
181 return opening_post
181 return opening_post
182
182
183 @cached_result()
183 @cached_result()
184 def get_opening_post_id(self) -> int:
184 def get_opening_post_id(self) -> int:
185 """
185 """
186 Gets ID of the first thread post.
186 Gets ID of the first thread post.
187 """
187 """
188
188
189 return self.get_opening_post(only_id=True).id
189 return self.get_opening_post(only_id=True).id
190
190
191 def get_pub_time(self):
191 def get_pub_time(self):
192 """
192 """
193 Gets opening post's pub time because thread does not have its own one.
193 Gets opening post's pub time because thread does not have its own one.
194 """
194 """
195
195
196 return self.get_opening_post().pub_time
196 return self.get_opening_post().pub_time
197
197
198 def __str__(self):
198 def __str__(self):
199 return 'T#{}/{}'.format(self.id, self.get_opening_post_id())
199 return 'T#{}/{}'.format(self.id, self.get_opening_post_id())
200
200
201 def get_tag_url_list(self) -> list:
201 def get_tag_url_list(self) -> list:
202 return boards.models.Tag.objects.get_tag_url_list(self.get_tags())
202 return boards.models.Tag.objects.get_tag_url_list(self.get_tags())
203
203
204 def update_posts_time(self, exclude_posts=None):
204 def update_posts_time(self, exclude_posts=None):
205 last_edit_time = self.last_edit_time
205 last_edit_time = self.last_edit_time
206
206
207 for post in self.post_set.all():
207 for post in self.post_set.all():
208 if exclude_posts is None or post not in exclude_posts:
208 if exclude_posts is None or post not in exclude_posts:
209 # Manual update is required because uids are generated on save
209 # Manual update is required because uids are generated on save
210 post.last_edit_time = last_edit_time
210 post.last_edit_time = last_edit_time
211 post.save(update_fields=['last_edit_time'])
211 post.save(update_fields=['last_edit_time'])
212
212
213 post.get_threads().update(last_edit_time=last_edit_time)
213 post.get_threads().update(last_edit_time=last_edit_time)
214
214
215 def notify_clients(self):
215 def notify_clients(self):
216 if not settings.get_bool('External', 'WebsocketsEnabled'):
216 if not settings.get_bool('External', 'WebsocketsEnabled'):
217 return
217 return
218
218
219 client = Client()
219 client = Client()
220
220
221 channel_name = WS_CHANNEL_THREAD + str(self.get_opening_post_id())
221 channel_name = WS_CHANNEL_THREAD + str(self.get_opening_post_id())
222 client.publish(channel_name, {
222 client.publish(channel_name, {
223 WS_NOTIFICATION_TYPE: WS_NOTIFICATION_TYPE_NEW_POST,
223 WS_NOTIFICATION_TYPE: WS_NOTIFICATION_TYPE_NEW_POST,
224 })
224 })
225 client.send()
225 client.send()
226
226
227 def get_absolute_url(self):
227 def get_absolute_url(self):
228 return self.get_opening_post().get_absolute_url()
228 return self.get_opening_post().get_absolute_url()
229
229
230 def get_required_tags(self):
230 def get_required_tags(self):
231 return self.get_tags().filter(required=True)
231 return self.get_tags().filter(required=True)
232
232
233 def get_replies_newer(self, post_id):
233 def get_replies_newer(self, post_id):
234 return self.get_replies().filter(id__gt=post_id)
234 return self.get_replies().filter(id__gt=post_id)
235
235
236 def is_archived(self):
237 return self.archived
238
@@ -1,274 +1,280 b''
1 from collections import OrderedDict
1 from collections import OrderedDict
2 import json
2 import json
3 import logging
3 import logging
4
4
5 from django.db import transaction
5 from django.db import transaction
6 from django.http import HttpResponse
6 from django.http import HttpResponse
7 from django.shortcuts import get_object_or_404
7 from django.shortcuts import get_object_or_404
8 from django.core import serializers
8 from django.core import serializers
9 from boards.abstracts.settingsmanager import get_settings_manager
9 from boards.abstracts.settingsmanager import get_settings_manager,\
10 FAV_THREAD_NO_UPDATES
10
11
11 from boards.forms import PostForm, PlainErrorList
12 from boards.forms import PostForm, PlainErrorList
12 from boards.models import Post, Thread, Tag
13 from boards.models import Post, Thread, Tag
13 from boards.utils import datetime_to_epoch
14 from boards.utils import datetime_to_epoch
14 from boards.views.thread import ThreadView
15 from boards.views.thread import ThreadView
15 from boards.models.user import Notification
16 from boards.models.user import Notification
16 from boards.mdx_neboard import Parser
17 from boards.mdx_neboard import Parser
17
18
18
19
19 __author__ = 'neko259'
20 __author__ = 'neko259'
20
21
21 PARAMETER_TRUNCATED = 'truncated'
22 PARAMETER_TRUNCATED = 'truncated'
22 PARAMETER_TAG = 'tag'
23 PARAMETER_TAG = 'tag'
23 PARAMETER_OFFSET = 'offset'
24 PARAMETER_OFFSET = 'offset'
24 PARAMETER_DIFF_TYPE = 'type'
25 PARAMETER_DIFF_TYPE = 'type'
25 PARAMETER_POST = 'post'
26 PARAMETER_POST = 'post'
26 PARAMETER_UPDATED = 'updated'
27 PARAMETER_UPDATED = 'updated'
27 PARAMETER_LAST_UPDATE = 'last_update'
28 PARAMETER_LAST_UPDATE = 'last_update'
28 PARAMETER_THREAD = 'thread'
29 PARAMETER_THREAD = 'thread'
29 PARAMETER_UIDS = 'uids'
30 PARAMETER_UIDS = 'uids'
30
31
31 DIFF_TYPE_HTML = 'html'
32 DIFF_TYPE_HTML = 'html'
32 DIFF_TYPE_JSON = 'json'
33 DIFF_TYPE_JSON = 'json'
33
34
34 STATUS_OK = 'ok'
35 STATUS_OK = 'ok'
35 STATUS_ERROR = 'error'
36 STATUS_ERROR = 'error'
36
37
37 logger = logging.getLogger(__name__)
38 logger = logging.getLogger(__name__)
38
39
39
40
40 @transaction.atomic
41 @transaction.atomic
41 def api_get_threaddiff(request):
42 def api_get_threaddiff(request):
42 """
43 """
43 Gets posts that were changed or added since time
44 Gets posts that were changed or added since time
44 """
45 """
45
46
46 thread_id = request.POST.get(PARAMETER_THREAD)
47 thread_id = request.POST.get(PARAMETER_THREAD)
47 uids_str = request.POST.get(PARAMETER_UIDS).strip()
48 uids_str = request.POST.get(PARAMETER_UIDS).strip()
48 uids = uids_str.split(' ')
49 uids = uids_str.split(' ')
49
50
50 opening_post = get_object_or_404(Post, id=thread_id)
51 opening_post = get_object_or_404(Post, id=thread_id)
51 thread = opening_post.get_thread()
52 thread = opening_post.get_thread()
52
53
53 json_data = {
54 json_data = {
54 PARAMETER_UPDATED: [],
55 PARAMETER_UPDATED: [],
55 PARAMETER_LAST_UPDATE: None, # TODO Maybe this can be removed already?
56 PARAMETER_LAST_UPDATE: None, # TODO Maybe this can be removed already?
56 }
57 }
57 posts = Post.objects.filter(threads__in=[thread]).exclude(uid__in=uids)
58 posts = Post.objects.filter(threads__in=[thread]).exclude(uid__in=uids)
58
59
59 diff_type = request.GET.get(PARAMETER_DIFF_TYPE, DIFF_TYPE_HTML)
60 diff_type = request.GET.get(PARAMETER_DIFF_TYPE, DIFF_TYPE_HTML)
60
61
61 for post in posts:
62 for post in posts:
62 json_data[PARAMETER_UPDATED].append(post.get_post_data(
63 json_data[PARAMETER_UPDATED].append(post.get_post_data(
63 format_type=diff_type, request=request))
64 format_type=diff_type, request=request))
64 json_data[PARAMETER_LAST_UPDATE] = str(thread.last_edit_time)
65 json_data[PARAMETER_LAST_UPDATE] = str(thread.last_edit_time)
65
66
66 # If the tag is favorite, update the counter
67 # If the tag is favorite, update the counter
67 settings_manager = get_settings_manager(request)
68 settings_manager = get_settings_manager(request)
68 favorite = settings_manager.thread_is_fav(opening_post)
69 favorite = settings_manager.thread_is_fav(opening_post)
69 if favorite:
70 if favorite:
70 settings_manager.add_or_read_fav_thread(opening_post)
71 settings_manager.add_or_read_fav_thread(opening_post)
71
72
72 return HttpResponse(content=json.dumps(json_data))
73 return HttpResponse(content=json.dumps(json_data))
73
74
74
75
75 def api_add_post(request, opening_post_id):
76 def api_add_post(request, opening_post_id):
76 """
77 """
77 Adds a post and return the JSON response for it
78 Adds a post and return the JSON response for it
78 """
79 """
79
80
80 opening_post = get_object_or_404(Post, id=opening_post_id)
81 opening_post = get_object_or_404(Post, id=opening_post_id)
81
82
82 logger.info('Adding post via api...')
83 logger.info('Adding post via api...')
83
84
84 status = STATUS_OK
85 status = STATUS_OK
85 errors = []
86 errors = []
86
87
87 if request.method == 'POST':
88 if request.method == 'POST':
88 form = PostForm(request.POST, request.FILES, error_class=PlainErrorList)
89 form = PostForm(request.POST, request.FILES, error_class=PlainErrorList)
89 form.session = request.session
90 form.session = request.session
90
91
91 if form.need_to_ban:
92 if form.need_to_ban:
92 # Ban user because he is suspected to be a bot
93 # Ban user because he is suspected to be a bot
93 # _ban_current_user(request)
94 # _ban_current_user(request)
94 status = STATUS_ERROR
95 status = STATUS_ERROR
95 if form.is_valid():
96 if form.is_valid():
96 post = ThreadView().new_post(request, form, opening_post,
97 post = ThreadView().new_post(request, form, opening_post,
97 html_response=False)
98 html_response=False)
98 if not post:
99 if not post:
99 status = STATUS_ERROR
100 status = STATUS_ERROR
100 else:
101 else:
101 logger.info('Added post #%d via api.' % post.id)
102 logger.info('Added post #%d via api.' % post.id)
102 else:
103 else:
103 status = STATUS_ERROR
104 status = STATUS_ERROR
104 errors = form.as_json_errors()
105 errors = form.as_json_errors()
105
106
106 response = {
107 response = {
107 'status': status,
108 'status': status,
108 'errors': errors,
109 'errors': errors,
109 }
110 }
110
111
111 return HttpResponse(content=json.dumps(response))
112 return HttpResponse(content=json.dumps(response))
112
113
113
114
114 def get_post(request, post_id):
115 def get_post(request, post_id):
115 """
116 """
116 Gets the html of a post. Used for popups. Post can be truncated if used
117 Gets the html of a post. Used for popups. Post can be truncated if used
117 in threads list with 'truncated' get parameter.
118 in threads list with 'truncated' get parameter.
118 """
119 """
119
120
120 post = get_object_or_404(Post, id=post_id)
121 post = get_object_or_404(Post, id=post_id)
121 truncated = PARAMETER_TRUNCATED in request.GET
122 truncated = PARAMETER_TRUNCATED in request.GET
122
123
123 return HttpResponse(content=post.get_view(truncated=truncated))
124 return HttpResponse(content=post.get_view(truncated=truncated))
124
125
125
126
126 def api_get_threads(request, count):
127 def api_get_threads(request, count):
127 """
128 """
128 Gets the JSON thread opening posts list.
129 Gets the JSON thread opening posts list.
129 Parameters that can be used for filtering:
130 Parameters that can be used for filtering:
130 tag, offset (from which thread to get results)
131 tag, offset (from which thread to get results)
131 """
132 """
132
133
133 if PARAMETER_TAG in request.GET:
134 if PARAMETER_TAG in request.GET:
134 tag_name = request.GET[PARAMETER_TAG]
135 tag_name = request.GET[PARAMETER_TAG]
135 if tag_name is not None:
136 if tag_name is not None:
136 tag = get_object_or_404(Tag, name=tag_name)
137 tag = get_object_or_404(Tag, name=tag_name)
137 threads = tag.get_threads().filter(archived=False)
138 threads = tag.get_threads().filter(archived=False)
138 else:
139 else:
139 threads = Thread.objects.filter(archived=False)
140 threads = Thread.objects.filter(archived=False)
140
141
141 if PARAMETER_OFFSET in request.GET:
142 if PARAMETER_OFFSET in request.GET:
142 offset = request.GET[PARAMETER_OFFSET]
143 offset = request.GET[PARAMETER_OFFSET]
143 offset = int(offset) if offset is not None else 0
144 offset = int(offset) if offset is not None else 0
144 else:
145 else:
145 offset = 0
146 offset = 0
146
147
147 threads = threads.order_by('-bump_time')
148 threads = threads.order_by('-bump_time')
148 threads = threads[offset:offset + int(count)]
149 threads = threads[offset:offset + int(count)]
149
150
150 opening_posts = []
151 opening_posts = []
151 for thread in threads:
152 for thread in threads:
152 opening_post = thread.get_opening_post()
153 opening_post = thread.get_opening_post()
153
154
154 # TODO Add tags, replies and images count
155 # TODO Add tags, replies and images count
155 post_data = opening_post.get_post_data(include_last_update=True)
156 post_data = opening_post.get_post_data(include_last_update=True)
156 post_data['bumpable'] = thread.can_bump()
157 post_data['bumpable'] = thread.can_bump()
157 post_data['archived'] = thread.archived
158 post_data['archived'] = thread.archived
158
159
159 opening_posts.append(post_data)
160 opening_posts.append(post_data)
160
161
161 return HttpResponse(content=json.dumps(opening_posts))
162 return HttpResponse(content=json.dumps(opening_posts))
162
163
163
164
164 # TODO Test this
165 # TODO Test this
165 def api_get_tags(request):
166 def api_get_tags(request):
166 """
167 """
167 Gets all tags or user tags.
168 Gets all tags or user tags.
168 """
169 """
169
170
170 # TODO Get favorite tags for the given user ID
171 # TODO Get favorite tags for the given user ID
171
172
172 tags = Tag.objects.get_not_empty_tags()
173 tags = Tag.objects.get_not_empty_tags()
173
174
174 term = request.GET.get('term')
175 term = request.GET.get('term')
175 if term is not None:
176 if term is not None:
176 tags = tags.filter(name__contains=term)
177 tags = tags.filter(name__contains=term)
177
178
178 tag_names = [tag.name for tag in tags]
179 tag_names = [tag.name for tag in tags]
179
180
180 return HttpResponse(content=json.dumps(tag_names))
181 return HttpResponse(content=json.dumps(tag_names))
181
182
182
183
183 # TODO The result can be cached by the thread last update time
184 # TODO The result can be cached by the thread last update time
184 # TODO Test this
185 # TODO Test this
185 def api_get_thread_posts(request, opening_post_id):
186 def api_get_thread_posts(request, opening_post_id):
186 """
187 """
187 Gets the JSON array of thread posts
188 Gets the JSON array of thread posts
188 """
189 """
189
190
190 opening_post = get_object_or_404(Post, id=opening_post_id)
191 opening_post = get_object_or_404(Post, id=opening_post_id)
191 thread = opening_post.get_thread()
192 thread = opening_post.get_thread()
192 posts = thread.get_replies()
193 posts = thread.get_replies()
193
194
194 json_data = {
195 json_data = {
195 'posts': [],
196 'posts': [],
196 'last_update': None,
197 'last_update': None,
197 }
198 }
198 json_post_list = []
199 json_post_list = []
199
200
200 for post in posts:
201 for post in posts:
201 json_post_list.append(post.get_post_data())
202 json_post_list.append(post.get_post_data())
202 json_data['last_update'] = datetime_to_epoch(thread.last_edit_time)
203 json_data['last_update'] = datetime_to_epoch(thread.last_edit_time)
203 json_data['posts'] = json_post_list
204 json_data['posts'] = json_post_list
204
205
205 return HttpResponse(content=json.dumps(json_data))
206 return HttpResponse(content=json.dumps(json_data))
206
207
207
208
208 def api_get_notifications(request, username):
209 def api_get_notifications(request, username):
209 last_notification_id_str = request.GET.get('last', None)
210 last_notification_id_str = request.GET.get('last', None)
210 last_id = int(last_notification_id_str) if last_notification_id_str is not None else None
211 last_id = int(last_notification_id_str) if last_notification_id_str is not None else None
211
212
212 posts = Notification.objects.get_notification_posts(username=username,
213 posts = Notification.objects.get_notification_posts(username=username,
213 last=last_id)
214 last=last_id)
214
215
215 json_post_list = []
216 json_post_list = []
216 for post in posts:
217 for post in posts:
217 json_post_list.append(post.get_post_data())
218 json_post_list.append(post.get_post_data())
218 return HttpResponse(content=json.dumps(json_post_list))
219 return HttpResponse(content=json.dumps(json_post_list))
219
220
220
221
221 def api_get_post(request, post_id):
222 def api_get_post(request, post_id):
222 """
223 """
223 Gets the JSON of a post. This can be
224 Gets the JSON of a post. This can be
224 used as and API for external clients.
225 used as and API for external clients.
225 """
226 """
226
227
227 post = get_object_or_404(Post, id=post_id)
228 post = get_object_or_404(Post, id=post_id)
228
229
229 json = serializers.serialize("json", [post], fields=(
230 json = serializers.serialize("json", [post], fields=(
230 "pub_time", "_text_rendered", "title", "text", "image",
231 "pub_time", "_text_rendered", "title", "text", "image",
231 "image_width", "image_height", "replies", "tags"
232 "image_width", "image_height", "replies", "tags"
232 ))
233 ))
233
234
234 return HttpResponse(content=json)
235 return HttpResponse(content=json)
235
236
236
237
237 def api_get_preview(request):
238 def api_get_preview(request):
238 raw_text = request.POST['raw_text']
239 raw_text = request.POST['raw_text']
239
240
240 parser = Parser()
241 parser = Parser()
241 return HttpResponse(content=parser.parse(parser.preparse(raw_text)))
242 return HttpResponse(content=parser.parse(parser.preparse(raw_text)))
242
243
243
244
244 def api_get_new_posts(request):
245 def api_get_new_posts(request):
245 """
246 """
246 Gets favorite threads and unread posts count.
247 Gets favorite threads and unread posts count.
247 """
248 """
248 posts = list()
249 posts = list()
249
250
250 include_posts = 'include_posts' in request.GET
251 include_posts = 'include_posts' in request.GET
251
252
252 settings_manager = get_settings_manager(request)
253 settings_manager = get_settings_manager(request)
253 fav_threads = settings_manager.get_fav_threads()
254 fav_threads = settings_manager.get_fav_threads()
254 fav_thread_ops = Post.objects.filter(id__in=fav_threads.keys())\
255 fav_thread_ops = Post.objects.filter(id__in=fav_threads.keys())\
255 .order_by('-pub_time').prefetch_related('thread')
256 .order_by('-pub_time').prefetch_related('thread')
256
257
257 for op in fav_thread_ops:
258 for op in fav_thread_ops:
258 last_read_post = fav_threads[str(op.id)]
259 last_read_post = fav_threads[str(op.id)]
259 new_posts = op.get_thread().get_replies_newer(last_read_post)
260
260 new_post_count = new_posts.count()
261 if last_read_post == FAV_THREAD_NO_UPDATES:
262 new_post_count = 0
263 else:
264 new_posts = op.get_thread().get_replies_newer(last_read_post)
265 new_post_count = new_posts.count()
266
261 fav_thread_dict = dict()
267 fav_thread_dict = dict()
262 fav_thread_dict['id'] = op.id
268 fav_thread_dict['id'] = op.id
263 fav_thread_dict['new_post_count'] = new_post_count
269 fav_thread_dict['new_post_count'] = new_post_count
264
270
265 if include_posts:
271 if include_posts:
266 fav_thread_dict['post_url'] = op.get_link_view()
272 fav_thread_dict['post_url'] = op.get_link_view()
267 fav_thread_dict['title'] = op.title
273 fav_thread_dict['title'] = op.title
268 if new_post_count > 0:
274 if new_post_count > 0:
269 fav_thread_dict['newest_post_link'] = new_posts.first()\
275 fav_thread_dict['newest_post_link'] = new_posts.first()\
270 .get_absolute_url()
276 .get_absolute_url()
271
277
272 posts.append(fav_thread_dict)
278 posts.append(fav_thread_dict)
273
279
274 return HttpResponse(content=json.dumps(posts))
280 return HttpResponse(content=json.dumps(posts))
General Comments 0
You need to be logged in to leave comments. Login now