##// END OF EJS Templates
Show updated thread's link in favorite's new multipost
neko259 -
r1365:4837cb51 default
parent child Browse files
Show More
@@ -1,339 +1,347
1 import logging
1 import logging
2 import re
2 import re
3 import uuid
3 import uuid
4
4
5 from django.core.exceptions import ObjectDoesNotExist
5 from django.core.exceptions import ObjectDoesNotExist
6 from django.core.urlresolvers import reverse
6 from django.core.urlresolvers import reverse
7 from django.db import models
7 from django.db import models
8 from django.db.models import TextField, QuerySet
8 from django.db.models import TextField, QuerySet
9 from django.template.defaultfilters import striptags, truncatewords
9 from django.template.defaultfilters import striptags, truncatewords
10 from django.template.loader import render_to_string
10 from django.template.loader import render_to_string
11 from django.utils import timezone
11 from django.utils import timezone
12
12
13 from boards import settings
13 from boards import settings
14 from boards.abstracts.tripcode import Tripcode
14 from boards.abstracts.tripcode import Tripcode
15 from boards.mdx_neboard import Parser
15 from boards.mdx_neboard import Parser
16 from boards.models import PostImage, Attachment
16 from boards.models import PostImage, Attachment
17 from boards.models.base import Viewable
17 from boards.models.base import Viewable
18 from boards.models.post.export import get_exporter, DIFF_TYPE_JSON
18 from boards.models.post.export import get_exporter, DIFF_TYPE_JSON
19 from boards.models.post.manager import PostManager
19 from boards.models.post.manager import PostManager
20 from boards.models.user import Notification
20 from boards.models.user import Notification
21
21
22 TITLE_MAX_WORDS = 10
22 TITLE_MAX_WORDS = 10
23
23
24 APP_LABEL_BOARDS = 'boards'
24 APP_LABEL_BOARDS = 'boards'
25
25
26 BAN_REASON_AUTO = 'Auto'
26 BAN_REASON_AUTO = 'Auto'
27
27
28 IMAGE_THUMB_SIZE = (200, 150)
28 IMAGE_THUMB_SIZE = (200, 150)
29
29
30 TITLE_MAX_LENGTH = 200
30 TITLE_MAX_LENGTH = 200
31
31
32 REGEX_REPLY = re.compile(r'\[post\](\d+)\[/post\]')
32 REGEX_REPLY = re.compile(r'\[post\](\d+)\[/post\]')
33 REGEX_NOTIFICATION = re.compile(r'\[user\](\w+)\[/user\]')
33 REGEX_NOTIFICATION = re.compile(r'\[user\](\w+)\[/user\]')
34
34
35 PARAMETER_TRUNCATED = 'truncated'
35 PARAMETER_TRUNCATED = 'truncated'
36 PARAMETER_TAG = 'tag'
36 PARAMETER_TAG = 'tag'
37 PARAMETER_OFFSET = 'offset'
37 PARAMETER_OFFSET = 'offset'
38 PARAMETER_DIFF_TYPE = 'type'
38 PARAMETER_DIFF_TYPE = 'type'
39 PARAMETER_CSS_CLASS = 'css_class'
39 PARAMETER_CSS_CLASS = 'css_class'
40 PARAMETER_THREAD = 'thread'
40 PARAMETER_THREAD = 'thread'
41 PARAMETER_IS_OPENING = 'is_opening'
41 PARAMETER_IS_OPENING = 'is_opening'
42 PARAMETER_MODERATOR = 'moderator'
42 PARAMETER_MODERATOR = 'moderator'
43 PARAMETER_POST = 'post'
43 PARAMETER_POST = 'post'
44 PARAMETER_OP_ID = 'opening_post_id'
44 PARAMETER_OP_ID = 'opening_post_id'
45 PARAMETER_NEED_OPEN_LINK = 'need_open_link'
45 PARAMETER_NEED_OPEN_LINK = 'need_open_link'
46 PARAMETER_REPLY_LINK = 'reply_link'
46 PARAMETER_REPLY_LINK = 'reply_link'
47 PARAMETER_NEED_OP_DATA = 'need_op_data'
47 PARAMETER_NEED_OP_DATA = 'need_op_data'
48
48
49 POST_VIEW_PARAMS = (
49 POST_VIEW_PARAMS = (
50 'need_op_data',
50 'need_op_data',
51 'reply_link',
51 'reply_link',
52 'moderator',
52 'moderator',
53 'need_open_link',
53 'need_open_link',
54 'truncated',
54 'truncated',
55 'mode_tree',
55 'mode_tree',
56 )
56 )
57
57
58
58
59 class Post(models.Model, Viewable):
59 class Post(models.Model, Viewable):
60 """A post is a message."""
60 """A post is a message."""
61
61
62 objects = PostManager()
62 objects = PostManager()
63
63
64 class Meta:
64 class Meta:
65 app_label = APP_LABEL_BOARDS
65 app_label = APP_LABEL_BOARDS
66 ordering = ('id',)
66 ordering = ('id',)
67
67
68 title = models.CharField(max_length=TITLE_MAX_LENGTH, null=True, blank=True)
68 title = models.CharField(max_length=TITLE_MAX_LENGTH, null=True, blank=True)
69 pub_time = models.DateTimeField()
69 pub_time = models.DateTimeField()
70 text = TextField(blank=True, null=True)
70 text = TextField(blank=True, null=True)
71 _text_rendered = TextField(blank=True, null=True, editable=False)
71 _text_rendered = TextField(blank=True, null=True, editable=False)
72
72
73 images = models.ManyToManyField(PostImage, null=True, blank=True,
73 images = models.ManyToManyField(PostImage, null=True, blank=True,
74 related_name='post_images', db_index=True)
74 related_name='post_images', db_index=True)
75 attachments = models.ManyToManyField(Attachment, null=True, blank=True,
75 attachments = models.ManyToManyField(Attachment, null=True, blank=True,
76 related_name='attachment_posts')
76 related_name='attachment_posts')
77
77
78 poster_ip = models.GenericIPAddressField()
78 poster_ip = models.GenericIPAddressField()
79
79
80 # TODO This field can be removed cause UID is used for update now
80 # TODO This field can be removed cause UID is used for update now
81 last_edit_time = models.DateTimeField()
81 last_edit_time = models.DateTimeField()
82
82
83 referenced_posts = models.ManyToManyField('Post', symmetrical=False,
83 referenced_posts = models.ManyToManyField('Post', symmetrical=False,
84 null=True,
84 null=True,
85 blank=True, related_name='refposts',
85 blank=True, related_name='refposts',
86 db_index=True)
86 db_index=True)
87 refmap = models.TextField(null=True, blank=True)
87 refmap = models.TextField(null=True, blank=True)
88 threads = models.ManyToManyField('Thread', db_index=True,
88 threads = models.ManyToManyField('Thread', db_index=True,
89 related_name='multi_replies')
89 related_name='multi_replies')
90 thread = models.ForeignKey('Thread', db_index=True, related_name='pt+')
90 thread = models.ForeignKey('Thread', db_index=True, related_name='pt+')
91
91
92 url = models.TextField()
92 url = models.TextField()
93 uid = models.TextField(db_index=True)
93 uid = models.TextField(db_index=True)
94
94
95 tripcode = models.CharField(max_length=50, null=True)
95 tripcode = models.CharField(max_length=50, null=True)
96 opening = models.BooleanField()
96 opening = models.BooleanField()
97
97
98 def __str__(self):
98 def __str__(self):
99 return 'P#{}/{}'.format(self.id, self.get_title())
99 return 'P#{}/{}'.format(self.id, self.get_title())
100
100
101 def get_referenced_posts(self):
101 def get_referenced_posts(self):
102 threads = self.get_threads().all()
102 threads = self.get_threads().all()
103 return self.referenced_posts.filter(threads__in=threads)\
103 return self.referenced_posts.filter(threads__in=threads)\
104 .order_by('pub_time').distinct().all()
104 .order_by('pub_time').distinct().all()
105
105
106 def get_title(self) -> str:
106 def get_title(self) -> str:
107 return self.title
107 return self.title
108
108
109 def get_title_or_text(self):
109 def get_title_or_text(self):
110 title = self.get_title()
110 title = self.get_title()
111 if not title:
111 if not title:
112 title = truncatewords(striptags(self.get_text()), TITLE_MAX_WORDS)
112 title = truncatewords(striptags(self.get_text()), TITLE_MAX_WORDS)
113
113
114 return title
114 return title
115
115
116 def build_refmap(self) -> None:
116 def build_refmap(self) -> None:
117 """
117 """
118 Builds a replies map string from replies list. This is a cache to stop
118 Builds a replies map string from replies list. This is a cache to stop
119 the server from recalculating the map on every post show.
119 the server from recalculating the map on every post show.
120 """
120 """
121
121
122 post_urls = [refpost.get_link_view()
122 post_urls = [refpost.get_link_view()
123 for refpost in self.referenced_posts.all()]
123 for refpost in self.referenced_posts.all()]
124
124
125 self.refmap = ', '.join(post_urls)
125 self.refmap = ', '.join(post_urls)
126
126
127 def is_referenced(self) -> bool:
127 def is_referenced(self) -> bool:
128 return self.refmap and len(self.refmap) > 0
128 return self.refmap and len(self.refmap) > 0
129
129
130 def is_opening(self) -> bool:
130 def is_opening(self) -> bool:
131 """
131 """
132 Checks if this is an opening post or just a reply.
132 Checks if this is an opening post or just a reply.
133 """
133 """
134
134
135 return self.opening
135 return self.opening
136
136
137 def get_absolute_url(self):
137 def get_absolute_url(self, thread=None):
138 if self.url:
138 url = None
139 return self.url
139
140 else:
140 if thread is None:
141 opening_id = self.get_thread().get_opening_post_id()
141 thread = self.get_thread()
142 post_url = reverse('thread', kwargs={'post_id': opening_id})
142
143 # Url is cached only for the "main" thread. When getting url
144 # for other threads, do it manually.
145 if self.url:
146 url = self.url
147
148 if url is None:
149 opening_id = thread.get_opening_post_id()
150 url = reverse('thread', kwargs={'post_id': opening_id})
143 if self.id != opening_id:
151 if self.id != opening_id:
144 post_url += '#' + str(self.id)
152 url += '#' + str(self.id)
145 return post_url
153
154 return url
146
155
147 def get_thread(self):
156 def get_thread(self):
148 return self.thread
157 return self.thread
149
158
150 def get_threads(self) -> QuerySet:
159 def get_threads(self) -> QuerySet:
151 """
160 """
152 Gets post's thread.
161 Gets post's thread.
153 """
162 """
154
163
155 return self.threads
164 return self.threads
156
165
157 def get_view(self, *args, **kwargs) -> str:
166 def get_view(self, *args, **kwargs) -> str:
158 """
167 """
159 Renders post's HTML view. Some of the post params can be passed over
168 Renders post's HTML view. Some of the post params can be passed over
160 kwargs for the means of caching (if we view the thread, some params
169 kwargs for the means of caching (if we view the thread, some params
161 are same for every post and don't need to be computed over and over.
170 are same for every post and don't need to be computed over and over.
162 """
171 """
163
172
164 thread = self.get_thread()
173 thread = self.get_thread()
165
174
166 css_class = 'post'
175 css_class = 'post'
167 if thread.archived:
176 if thread.archived:
168 css_class += ' archive_post'
177 css_class += ' archive_post'
169 elif not thread.can_bump():
178 elif not thread.can_bump():
170 css_class += ' dead_post'
179 css_class += ' dead_post'
171
180
172 params = dict()
181 params = dict()
173 for param in POST_VIEW_PARAMS:
182 for param in POST_VIEW_PARAMS:
174 if param in kwargs:
183 if param in kwargs:
175 params[param] = kwargs[param]
184 params[param] = kwargs[param]
176
185
177 params.update({
186 params.update({
178 PARAMETER_POST: self,
187 PARAMETER_POST: self,
179 PARAMETER_IS_OPENING: self.is_opening(),
188 PARAMETER_IS_OPENING: self.is_opening(),
180 PARAMETER_THREAD: thread,
189 PARAMETER_THREAD: thread,
181 PARAMETER_CSS_CLASS: css_class,
190 PARAMETER_CSS_CLASS: css_class,
182 })
191 })
183
192
184 return render_to_string('boards/post.html', params)
193 return render_to_string('boards/post.html', params)
185
194
186 def get_search_view(self, *args, **kwargs):
195 def get_search_view(self, *args, **kwargs):
187 return self.get_view(need_op_data=True, *args, **kwargs)
196 return self.get_view(need_op_data=True, *args, **kwargs)
188
197
189 def get_first_image(self) -> PostImage:
198 def get_first_image(self) -> PostImage:
190 return self.images.earliest('id')
199 return self.images.earliest('id')
191
200
192 def delete(self, using=None):
201 def delete(self, using=None):
193 """
202 """
194 Deletes all post images and the post itself.
203 Deletes all post images and the post itself.
195 """
204 """
196
205
197 for image in self.images.all():
206 for image in self.images.all():
198 image_refs_count = image.post_images.count()
207 image_refs_count = image.post_images.count()
199 if image_refs_count == 1:
208 if image_refs_count == 1:
200 image.delete()
209 image.delete()
201
210
202 for attachment in self.attachments.all():
211 for attachment in self.attachments.all():
203 attachment_refs_count = attachment.attachment_posts.count()
212 attachment_refs_count = attachment.attachment_posts.count()
204 if attachment_refs_count == 1:
213 if attachment_refs_count == 1:
205 attachment.delete()
214 attachment.delete()
206
215
207 thread = self.get_thread()
216 thread = self.get_thread()
208 thread.last_edit_time = timezone.now()
217 thread.last_edit_time = timezone.now()
209 thread.save()
218 thread.save()
210
219
211 super(Post, self).delete(using)
220 super(Post, self).delete(using)
212
221
213 logging.getLogger('boards.post.delete').info(
222 logging.getLogger('boards.post.delete').info(
214 'Deleted post {}'.format(self))
223 'Deleted post {}'.format(self))
215
224
216 def get_post_data(self, format_type=DIFF_TYPE_JSON, request=None,
225 def get_post_data(self, format_type=DIFF_TYPE_JSON, request=None,
217 include_last_update=False) -> str:
226 include_last_update=False) -> str:
218 """
227 """
219 Gets post HTML or JSON data that can be rendered on a page or used by
228 Gets post HTML or JSON data that can be rendered on a page or used by
220 API.
229 API.
221 """
230 """
222
231
223 return get_exporter(format_type).export(self, request,
232 return get_exporter(format_type).export(self, request,
224 include_last_update)
233 include_last_update)
225
234
226 def notify_clients(self, recursive=True):
235 def notify_clients(self, recursive=True):
227 """
236 """
228 Sends post HTML data to the thread web socket.
237 Sends post HTML data to the thread web socket.
229 """
238 """
230
239
231 if not settings.get_bool('External', 'WebsocketsEnabled'):
240 if not settings.get_bool('External', 'WebsocketsEnabled'):
232 return
241 return
233
242
234 thread_ids = list()
243 thread_ids = list()
235 for thread in self.get_threads().all():
244 for thread in self.get_threads().all():
236 thread_ids.append(thread.id)
245 thread_ids.append(thread.id)
237
246
238 thread.notify_clients()
247 thread.notify_clients()
239
248
240 if recursive:
249 if recursive:
241 for reply_number in re.finditer(REGEX_REPLY, self.get_raw_text()):
250 for reply_number in re.finditer(REGEX_REPLY, self.get_raw_text()):
242 post_id = reply_number.group(1)
251 post_id = reply_number.group(1)
243
252
244 try:
253 try:
245 ref_post = Post.objects.get(id=post_id)
254 ref_post = Post.objects.get(id=post_id)
246
255
247 if ref_post.get_threads().exclude(id__in=thread_ids).exists():
256 if ref_post.get_threads().exclude(id__in=thread_ids).exists():
248 # If post is in this thread, its thread was already notified.
257 # If post is in this thread, its thread was already notified.
249 # Otherwise, notify its thread separately.
258 # Otherwise, notify its thread separately.
250 ref_post.notify_clients(recursive=False)
259 ref_post.notify_clients(recursive=False)
251 except ObjectDoesNotExist:
260 except ObjectDoesNotExist:
252 pass
261 pass
253
262
254 def build_url(self):
263 def build_url(self):
255 self.url = self.get_absolute_url()
264 self.url = self.get_absolute_url()
256 self.save(update_fields=['url'])
265 self.save(update_fields=['url'])
257
266
258 def save(self, force_insert=False, force_update=False, using=None,
267 def save(self, force_insert=False, force_update=False, using=None,
259 update_fields=None):
268 update_fields=None):
260 self._text_rendered = Parser().parse(self.get_raw_text())
269 self._text_rendered = Parser().parse(self.get_raw_text())
261
270
262 self.uid = str(uuid.uuid4())
271 self.uid = str(uuid.uuid4())
263 if update_fields is not None and 'uid' not in update_fields:
272 if update_fields is not None and 'uid' not in update_fields:
264 update_fields += ['uid']
273 update_fields += ['uid']
265
274
266 if self.id:
275 if self.id:
267 for thread in self.get_threads().all():
276 for thread in self.get_threads().all():
268 thread.last_edit_time = self.last_edit_time
277 thread.last_edit_time = self.last_edit_time
269
278
270 thread.save(update_fields=['last_edit_time', 'bumpable'])
279 thread.save(update_fields=['last_edit_time', 'bumpable'])
271
280
272 super().save(force_insert, force_update, using, update_fields)
281 super().save(force_insert, force_update, using, update_fields)
273
282
274 def get_text(self) -> str:
283 def get_text(self) -> str:
275 return self._text_rendered
284 return self._text_rendered
276
285
277 def get_raw_text(self) -> str:
286 def get_raw_text(self) -> str:
278 return self.text
287 return self.text
279
288
280 def get_absolute_id(self) -> str:
289 def get_absolute_id(self) -> str:
281 """
290 """
282 If the post has many threads, shows its main thread OP id in the post
291 If the post has many threads, shows its main thread OP id in the post
283 ID.
292 ID.
284 """
293 """
285
294
286 if self.get_threads().count() > 1:
295 if self.get_threads().count() > 1:
287 return '{}/{}'.format(self.get_thread().get_opening_post_id(), self.id)
296 return '{}/{}'.format(self.get_thread().get_opening_post_id(), self.id)
288 else:
297 else:
289 return str(self.id)
298 return str(self.id)
290
299
291 def connect_notifications(self):
300 def connect_notifications(self):
292 for reply_number in re.finditer(REGEX_NOTIFICATION, self.get_raw_text()):
301 for reply_number in re.finditer(REGEX_NOTIFICATION, self.get_raw_text()):
293 user_name = reply_number.group(1).lower()
302 user_name = reply_number.group(1).lower()
294 Notification.objects.get_or_create(name=user_name, post=self)
303 Notification.objects.get_or_create(name=user_name, post=self)
295
304
296 def connect_replies(self):
305 def connect_replies(self):
297 """
306 """
298 Connects replies to a post to show them as a reflink map
307 Connects replies to a post to show them as a reflink map
299 """
308 """
300
309
301 for reply_number in re.finditer(REGEX_REPLY, self.get_raw_text()):
310 for reply_number in re.finditer(REGEX_REPLY, self.get_raw_text()):
302 post_id = reply_number.group(1)
311 post_id = reply_number.group(1)
303
312
304 try:
313 try:
305 referenced_post = Post.objects.get(id=post_id)
314 referenced_post = Post.objects.get(id=post_id)
306
315
307 referenced_post.referenced_posts.add(self)
316 referenced_post.referenced_posts.add(self)
308 referenced_post.last_edit_time = self.pub_time
317 referenced_post.last_edit_time = self.pub_time
309 referenced_post.build_refmap()
318 referenced_post.build_refmap()
310 referenced_post.save(update_fields=['refmap', 'last_edit_time'])
319 referenced_post.save(update_fields=['refmap', 'last_edit_time'])
311 except ObjectDoesNotExist:
320 except ObjectDoesNotExist:
312 pass
321 pass
313
322
314 def connect_threads(self, opening_posts):
323 def connect_threads(self, opening_posts):
315 for opening_post in opening_posts:
324 for opening_post in opening_posts:
316 threads = opening_post.get_threads().all()
325 threads = opening_post.get_threads().all()
317 for thread in threads:
326 for thread in threads:
318 if thread.can_bump():
327 if thread.can_bump():
319 thread.update_bump_status()
328 thread.update_bump_status()
320
329
321 thread.last_edit_time = self.last_edit_time
330 thread.last_edit_time = self.last_edit_time
322 thread.save(update_fields=['last_edit_time', 'bumpable'])
331 thread.save(update_fields=['last_edit_time', 'bumpable'])
323 self.threads.add(opening_post.get_thread())
332 self.threads.add(opening_post.get_thread())
324
333
325 def get_tripcode(self):
334 def get_tripcode(self):
326 if self.tripcode:
335 if self.tripcode:
327 return Tripcode(self.tripcode)
336 return Tripcode(self.tripcode)
328
337
329 def get_link_view(self):
338 def get_link_view(self):
330 """
339 """
331 Gets view of a reflink to the post.
340 Gets view of a reflink to the post.
332 """
341 """
333
334 result = '<a href="{}">&gt;&gt;{}</a>'.format(self.get_absolute_url(),
342 result = '<a href="{}">&gt;&gt;{}</a>'.format(self.get_absolute_url(),
335 self.id)
343 self.id)
336 if self.is_opening():
344 if self.is_opening():
337 result = '<b>{}</b>'.format(result)
345 result = '<b>{}</b>'.format(result)
338
346
339 return result
347 return result
@@ -1,293 +1,293
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.db.models import Count
6 from django.db.models import Count
7 from django.http import HttpResponse
7 from django.http import HttpResponse
8 from django.shortcuts import get_object_or_404
8 from django.shortcuts import get_object_or_404
9 from django.core import serializers
9 from django.core import serializers
10 from boards.abstracts.settingsmanager import get_settings_manager,\
10 from boards.abstracts.settingsmanager import get_settings_manager,\
11 FAV_THREAD_NO_UPDATES
11 FAV_THREAD_NO_UPDATES
12
12
13 from boards.forms import PostForm, PlainErrorList
13 from boards.forms import PostForm, PlainErrorList
14 from boards.models import Post, Thread, Tag
14 from boards.models import Post, Thread, Tag
15 from boards.utils import datetime_to_epoch
15 from boards.utils import datetime_to_epoch
16 from boards.views.thread import ThreadView
16 from boards.views.thread import ThreadView
17 from boards.models.user import Notification
17 from boards.models.user import Notification
18 from boards.mdx_neboard import Parser
18 from boards.mdx_neboard import Parser
19
19
20
20
21 __author__ = 'neko259'
21 __author__ = 'neko259'
22
22
23 PARAMETER_TRUNCATED = 'truncated'
23 PARAMETER_TRUNCATED = 'truncated'
24 PARAMETER_TAG = 'tag'
24 PARAMETER_TAG = 'tag'
25 PARAMETER_OFFSET = 'offset'
25 PARAMETER_OFFSET = 'offset'
26 PARAMETER_DIFF_TYPE = 'type'
26 PARAMETER_DIFF_TYPE = 'type'
27 PARAMETER_POST = 'post'
27 PARAMETER_POST = 'post'
28 PARAMETER_UPDATED = 'updated'
28 PARAMETER_UPDATED = 'updated'
29 PARAMETER_LAST_UPDATE = 'last_update'
29 PARAMETER_LAST_UPDATE = 'last_update'
30 PARAMETER_THREAD = 'thread'
30 PARAMETER_THREAD = 'thread'
31 PARAMETER_UIDS = 'uids'
31 PARAMETER_UIDS = 'uids'
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).strip()
49 uids_str = request.POST.get(PARAMETER_UIDS).strip()
50 uids = uids_str.split(' ')
50 uids = uids_str.split(' ')
51
51
52 opening_post = get_object_or_404(Post, id=thread_id)
52 opening_post = get_object_or_404(Post, id=thread_id)
53 thread = opening_post.get_thread()
53 thread = opening_post.get_thread()
54
54
55 json_data = {
55 json_data = {
56 PARAMETER_UPDATED: [],
56 PARAMETER_UPDATED: [],
57 PARAMETER_LAST_UPDATE: None, # TODO Maybe this can be removed already?
57 PARAMETER_LAST_UPDATE: None, # TODO Maybe this can be removed already?
58 }
58 }
59 posts = Post.objects.filter(threads__in=[thread]).exclude(uid__in=uids)
59 posts = Post.objects.filter(threads__in=[thread]).exclude(uid__in=uids)
60
60
61 diff_type = request.GET.get(PARAMETER_DIFF_TYPE, DIFF_TYPE_HTML)
61 diff_type = request.GET.get(PARAMETER_DIFF_TYPE, DIFF_TYPE_HTML)
62
62
63 for post in posts:
63 for post in posts:
64 json_data[PARAMETER_UPDATED].append(post.get_post_data(
64 json_data[PARAMETER_UPDATED].append(post.get_post_data(
65 format_type=diff_type, request=request))
65 format_type=diff_type, request=request))
66 json_data[PARAMETER_LAST_UPDATE] = str(thread.last_edit_time)
66 json_data[PARAMETER_LAST_UPDATE] = str(thread.last_edit_time)
67
67
68 # If the tag is favorite, update the counter
68 # If the tag is favorite, update the counter
69 settings_manager = get_settings_manager(request)
69 settings_manager = get_settings_manager(request)
70 favorite = settings_manager.thread_is_fav(opening_post)
70 favorite = settings_manager.thread_is_fav(opening_post)
71 if favorite:
71 if favorite:
72 settings_manager.add_or_read_fav_thread(opening_post)
72 settings_manager.add_or_read_fav_thread(opening_post)
73
73
74 return HttpResponse(content=json.dumps(json_data))
74 return HttpResponse(content=json.dumps(json_data))
75
75
76
76
77 def api_add_post(request, opening_post_id):
77 def api_add_post(request, opening_post_id):
78 """
78 """
79 Adds a post and return the JSON response for it
79 Adds a post and return the JSON response for it
80 """
80 """
81
81
82 opening_post = get_object_or_404(Post, id=opening_post_id)
82 opening_post = get_object_or_404(Post, id=opening_post_id)
83
83
84 logger.info('Adding post via api...')
84 logger.info('Adding post via api...')
85
85
86 status = STATUS_OK
86 status = STATUS_OK
87 errors = []
87 errors = []
88
88
89 if request.method == 'POST':
89 if request.method == 'POST':
90 form = PostForm(request.POST, request.FILES, error_class=PlainErrorList)
90 form = PostForm(request.POST, request.FILES, error_class=PlainErrorList)
91 form.session = request.session
91 form.session = request.session
92
92
93 if form.need_to_ban:
93 if form.need_to_ban:
94 # Ban user because he is suspected to be a bot
94 # Ban user because he is suspected to be a bot
95 # _ban_current_user(request)
95 # _ban_current_user(request)
96 status = STATUS_ERROR
96 status = STATUS_ERROR
97 if form.is_valid():
97 if form.is_valid():
98 post = ThreadView().new_post(request, form, opening_post,
98 post = ThreadView().new_post(request, form, opening_post,
99 html_response=False)
99 html_response=False)
100 if not post:
100 if not post:
101 status = STATUS_ERROR
101 status = STATUS_ERROR
102 else:
102 else:
103 logger.info('Added post #%d via api.' % post.id)
103 logger.info('Added post #%d via api.' % post.id)
104 else:
104 else:
105 status = STATUS_ERROR
105 status = STATUS_ERROR
106 errors = form.as_json_errors()
106 errors = form.as_json_errors()
107
107
108 response = {
108 response = {
109 'status': status,
109 'status': status,
110 'errors': errors,
110 'errors': errors,
111 }
111 }
112
112
113 return HttpResponse(content=json.dumps(response))
113 return HttpResponse(content=json.dumps(response))
114
114
115
115
116 def get_post(request, post_id):
116 def get_post(request, post_id):
117 """
117 """
118 Gets the html of a post. Used for popups. Post can be truncated if used
118 Gets the html of a post. Used for popups. Post can be truncated if used
119 in threads list with 'truncated' get parameter.
119 in threads list with 'truncated' get parameter.
120 """
120 """
121
121
122 post = get_object_or_404(Post, id=post_id)
122 post = get_object_or_404(Post, id=post_id)
123 truncated = PARAMETER_TRUNCATED in request.GET
123 truncated = PARAMETER_TRUNCATED in request.GET
124
124
125 return HttpResponse(content=post.get_view(truncated=truncated))
125 return HttpResponse(content=post.get_view(truncated=truncated))
126
126
127
127
128 def api_get_threads(request, count):
128 def api_get_threads(request, count):
129 """
129 """
130 Gets the JSON thread opening posts list.
130 Gets the JSON thread opening posts list.
131 Parameters that can be used for filtering:
131 Parameters that can be used for filtering:
132 tag, offset (from which thread to get results)
132 tag, offset (from which thread to get results)
133 """
133 """
134
134
135 if PARAMETER_TAG in request.GET:
135 if PARAMETER_TAG in request.GET:
136 tag_name = request.GET[PARAMETER_TAG]
136 tag_name = request.GET[PARAMETER_TAG]
137 if tag_name is not None:
137 if tag_name is not None:
138 tag = get_object_or_404(Tag, name=tag_name)
138 tag = get_object_or_404(Tag, name=tag_name)
139 threads = tag.get_threads().filter(archived=False)
139 threads = tag.get_threads().filter(archived=False)
140 else:
140 else:
141 threads = Thread.objects.filter(archived=False)
141 threads = Thread.objects.filter(archived=False)
142
142
143 if PARAMETER_OFFSET in request.GET:
143 if PARAMETER_OFFSET in request.GET:
144 offset = request.GET[PARAMETER_OFFSET]
144 offset = request.GET[PARAMETER_OFFSET]
145 offset = int(offset) if offset is not None else 0
145 offset = int(offset) if offset is not None else 0
146 else:
146 else:
147 offset = 0
147 offset = 0
148
148
149 threads = threads.order_by('-bump_time')
149 threads = threads.order_by('-bump_time')
150 threads = threads[offset:offset + int(count)]
150 threads = threads[offset:offset + int(count)]
151
151
152 opening_posts = []
152 opening_posts = []
153 for thread in threads:
153 for thread in threads:
154 opening_post = thread.get_opening_post()
154 opening_post = thread.get_opening_post()
155
155
156 # TODO Add tags, replies and images count
156 # TODO Add tags, replies and images count
157 post_data = opening_post.get_post_data(include_last_update=True)
157 post_data = opening_post.get_post_data(include_last_update=True)
158 post_data['bumpable'] = thread.can_bump()
158 post_data['bumpable'] = thread.can_bump()
159 post_data['archived'] = thread.archived
159 post_data['archived'] = thread.archived
160
160
161 opening_posts.append(post_data)
161 opening_posts.append(post_data)
162
162
163 return HttpResponse(content=json.dumps(opening_posts))
163 return HttpResponse(content=json.dumps(opening_posts))
164
164
165
165
166 # TODO Test this
166 # TODO Test this
167 def api_get_tags(request):
167 def api_get_tags(request):
168 """
168 """
169 Gets all tags or user tags.
169 Gets all tags or user tags.
170 """
170 """
171
171
172 # TODO Get favorite tags for the given user ID
172 # TODO Get favorite tags for the given user ID
173
173
174 tags = Tag.objects.get_not_empty_tags()
174 tags = Tag.objects.get_not_empty_tags()
175
175
176 term = request.GET.get('term')
176 term = request.GET.get('term')
177 if term is not None:
177 if term is not None:
178 tags = tags.filter(name__contains=term)
178 tags = tags.filter(name__contains=term)
179
179
180 tag_names = [tag.name for tag in tags]
180 tag_names = [tag.name for tag in tags]
181
181
182 return HttpResponse(content=json.dumps(tag_names))
182 return HttpResponse(content=json.dumps(tag_names))
183
183
184
184
185 # TODO The result can be cached by the thread last update time
185 # TODO The result can be cached by the thread last update time
186 # TODO Test this
186 # TODO Test this
187 def api_get_thread_posts(request, opening_post_id):
187 def api_get_thread_posts(request, opening_post_id):
188 """
188 """
189 Gets the JSON array of thread posts
189 Gets the JSON array of thread posts
190 """
190 """
191
191
192 opening_post = get_object_or_404(Post, id=opening_post_id)
192 opening_post = get_object_or_404(Post, id=opening_post_id)
193 thread = opening_post.get_thread()
193 thread = opening_post.get_thread()
194 posts = thread.get_replies()
194 posts = thread.get_replies()
195
195
196 json_data = {
196 json_data = {
197 'posts': [],
197 'posts': [],
198 'last_update': None,
198 'last_update': None,
199 }
199 }
200 json_post_list = []
200 json_post_list = []
201
201
202 for post in posts:
202 for post in posts:
203 json_post_list.append(post.get_post_data())
203 json_post_list.append(post.get_post_data())
204 json_data['last_update'] = datetime_to_epoch(thread.last_edit_time)
204 json_data['last_update'] = datetime_to_epoch(thread.last_edit_time)
205 json_data['posts'] = json_post_list
205 json_data['posts'] = json_post_list
206
206
207 return HttpResponse(content=json.dumps(json_data))
207 return HttpResponse(content=json.dumps(json_data))
208
208
209
209
210 def api_get_notifications(request, username):
210 def api_get_notifications(request, username):
211 last_notification_id_str = request.GET.get('last', None)
211 last_notification_id_str = request.GET.get('last', None)
212 last_id = int(last_notification_id_str) if last_notification_id_str is not None else None
212 last_id = int(last_notification_id_str) if last_notification_id_str is not None else None
213
213
214 posts = Notification.objects.get_notification_posts(username=username,
214 posts = Notification.objects.get_notification_posts(username=username,
215 last=last_id)
215 last=last_id)
216
216
217 json_post_list = []
217 json_post_list = []
218 for post in posts:
218 for post in posts:
219 json_post_list.append(post.get_post_data())
219 json_post_list.append(post.get_post_data())
220 return HttpResponse(content=json.dumps(json_post_list))
220 return HttpResponse(content=json.dumps(json_post_list))
221
221
222
222
223 def api_get_post(request, post_id):
223 def api_get_post(request, post_id):
224 """
224 """
225 Gets the JSON of a post. This can be
225 Gets the JSON of a post. This can be
226 used as and API for external clients.
226 used as and API for external clients.
227 """
227 """
228
228
229 post = get_object_or_404(Post, id=post_id)
229 post = get_object_or_404(Post, id=post_id)
230
230
231 json = serializers.serialize("json", [post], fields=(
231 json = serializers.serialize("json", [post], fields=(
232 "pub_time", "_text_rendered", "title", "text", "image",
232 "pub_time", "_text_rendered", "title", "text", "image",
233 "image_width", "image_height", "replies", "tags"
233 "image_width", "image_height", "replies", "tags"
234 ))
234 ))
235
235
236 return HttpResponse(content=json)
236 return HttpResponse(content=json)
237
237
238
238
239 def api_get_preview(request):
239 def api_get_preview(request):
240 raw_text = request.POST['raw_text']
240 raw_text = request.POST['raw_text']
241
241
242 parser = Parser()
242 parser = Parser()
243 return HttpResponse(content=parser.parse(parser.preparse(raw_text)))
243 return HttpResponse(content=parser.parse(parser.preparse(raw_text)))
244
244
245
245
246 def api_get_new_posts(request):
246 def api_get_new_posts(request):
247 """
247 """
248 Gets favorite threads and unread posts count.
248 Gets favorite threads and unread posts count.
249 """
249 """
250 posts = list()
250 posts = list()
251
251
252 include_posts = 'include_posts' in request.GET
252 include_posts = 'include_posts' in request.GET
253
253
254 settings_manager = get_settings_manager(request)
254 settings_manager = get_settings_manager(request)
255 fav_threads = settings_manager.get_fav_threads()
255 fav_threads = settings_manager.get_fav_threads()
256 fav_thread_ops = Post.objects.filter(id__in=fav_threads.keys())\
256 fav_thread_ops = Post.objects.filter(id__in=fav_threads.keys())\
257 .order_by('-pub_time').prefetch_related('thread')
257 .order_by('-pub_time').prefetch_related('thread')
258
258
259 ops = [{'op': op, 'last_id': fav_threads[str(op.id)]} for op in fav_thread_ops]
259 ops = [{'op': op, 'last_id': fav_threads[str(op.id)]} for op in fav_thread_ops]
260 if include_posts:
260 if include_posts:
261 new_post_threads = Thread.objects.get_new_posts(ops)
261 new_post_threads = Thread.objects.get_new_posts(ops)
262 if new_post_threads:
262 if new_post_threads:
263 thread_ids = {thread.id: thread for thread in new_post_threads}
263 thread_ids = {thread.id: thread for thread in new_post_threads}
264 else:
264 else:
265 thread_ids = dict()
265 thread_ids = dict()
266
266
267 for op in fav_thread_ops:
267 for op in fav_thread_ops:
268 fav_thread_dict = dict()
268 fav_thread_dict = dict()
269
269
270 op_thread = op.get_thread()
270 op_thread = op.get_thread()
271 if op_thread.id in thread_ids:
271 if op_thread.id in thread_ids:
272 thread = thread_ids[op_thread.id]
272 thread = thread_ids[op_thread.id]
273 new_post_count = thread.new_post_count
273 new_post_count = thread.new_post_count
274 fav_thread_dict['newest_post_link'] = thread.get_replies()\
274 fav_thread_dict['newest_post_link'] = thread.get_replies()\
275 .filter(id__gt=fav_threads[str(op.id)])\
275 .filter(id__gt=fav_threads[str(op.id)])\
276 .first().get_absolute_url()
276 .first().get_absolute_url(thread=thread)
277 else:
277 else:
278 new_post_count = 0
278 new_post_count = 0
279 fav_thread_dict['new_post_count'] = new_post_count
279 fav_thread_dict['new_post_count'] = new_post_count
280
280
281 fav_thread_dict['id'] = op.id
281 fav_thread_dict['id'] = op.id
282
282
283 fav_thread_dict['post_url'] = op.get_link_view()
283 fav_thread_dict['post_url'] = op.get_link_view()
284 fav_thread_dict['title'] = op.title
284 fav_thread_dict['title'] = op.title
285
285
286 posts.append(fav_thread_dict)
286 posts.append(fav_thread_dict)
287 else:
287 else:
288 fav_thread_dict = dict()
288 fav_thread_dict = dict()
289 fav_thread_dict['new_post_count'] = \
289 fav_thread_dict['new_post_count'] = \
290 Thread.objects.get_new_post_count(ops)
290 Thread.objects.get_new_post_count(ops)
291 posts.append(fav_thread_dict)
291 posts.append(fav_thread_dict)
292
292
293 return HttpResponse(content=json.dumps(posts))
293 return HttpResponse(content=json.dumps(posts))
General Comments 0
You need to be logged in to leave comments. Login now