##// END OF EJS Templates
Refactoring
neko259 -
r917:3aa27a4d default
parent child Browse files
Show More
@@ -1,438 +1,438
1 1 from datetime import datetime, timedelta, date
2 2 from datetime import time as dtime
3 3 import logging
4 4 import re
5 5
6 6 from adjacent import Client
7 7 from django.core.cache import cache
8 8 from django.core.urlresolvers import reverse
9 9 from django.db import models, transaction
10 10 from django.db.models import TextField
11 from django.template import RequestContext
12 11 from django.template.loader import render_to_string
13 12 from django.utils import timezone
14 13
15 14 from boards import settings
16 15 from boards.mdx_neboard import bbcode_extended
17 16 from boards.models import PostImage
18 17 from boards.models.base import Viewable
19 18 from boards.models.thread import Thread
20 19 from boards.utils import datetime_to_epoch
21 20
21
22 22 WS_NOTIFICATION_TYPE_NEW_POST = 'new_post'
23 23 WS_NOTIFICATION_TYPE = 'notification_type'
24 24
25 25 WS_CHANNEL_THREAD = "thread:"
26 26
27 27 APP_LABEL_BOARDS = 'boards'
28 28
29 29 CACHE_KEY_PPD = 'ppd'
30 30 CACHE_KEY_POST_URL = 'post_url'
31 31
32 32 POSTS_PER_DAY_RANGE = 7
33 33
34 34 BAN_REASON_AUTO = 'Auto'
35 35
36 36 IMAGE_THUMB_SIZE = (200, 150)
37 37
38 38 TITLE_MAX_LENGTH = 200
39 39
40 40 # TODO This should be removed
41 41 NO_IP = '0.0.0.0'
42 42
43 43 # TODO Real user agent should be saved instead of this
44 44 UNKNOWN_UA = ''
45 45
46 46 REGEX_REPLY = re.compile(r'\[post\](\d+)\[/post\]')
47 47
48 48 PARAMETER_TRUNCATED = 'truncated'
49 49 PARAMETER_TAG = 'tag'
50 50 PARAMETER_OFFSET = 'offset'
51 51 PARAMETER_DIFF_TYPE = 'type'
52 PARAMETER_BUMPABLE = 'bumpable'
53 PARAMETER_THREAD = 'thread'
54 PARAMETER_IS_OPENING = 'is_opening'
55 PARAMETER_MODERATOR = 'moderator'
56 PARAMETER_POST = 'post'
57 PARAMETER_OP_ID = 'opening_post_id'
58 PARAMETER_NEED_OPEN_LINK = 'need_open_link'
52 59
53 60 DIFF_TYPE_HTML = 'html'
54 61 DIFF_TYPE_JSON = 'json'
55 62
56 63 PREPARSE_PATTERNS = {
57 64 r'>>(\d+)': r'[post]\1[/post]', # Reflink ">>123"
58 65 r'^>(.+)': r'[quote]\1[/quote]', # Quote ">text"
59 66 r'^//(.+)': r'[comment]\1[/comment]', # Comment "//text"
60 67 }
61 68
62 69
63 70 class PostManager(models.Manager):
64 71 @transaction.atomic
65 72 def create_post(self, title: str, text: str, image=None, thread=None,
66 73 ip=NO_IP, tags: list=None):
67 74 """
68 75 Creates new post
69 76 """
70 77
71 78 if not tags:
72 79 tags = []
73 80
74 81 posting_time = timezone.now()
75 82 if not thread:
76 83 thread = Thread.objects.create(bump_time=posting_time,
77 84 last_edit_time=posting_time)
78 85 new_thread = True
79 86 else:
80 87 new_thread = False
81 88
82 89 pre_text = self._preparse_text(text)
83 90
84 91 post = self.create(title=title,
85 92 text=pre_text,
86 93 pub_time=posting_time,
87 94 thread_new=thread,
88 95 poster_ip=ip,
89 96 poster_user_agent=UNKNOWN_UA, # TODO Get UA at
90 97 # last!
91 98 last_edit_time=posting_time)
92 99
93 100 logger = logging.getLogger('boards.post.create')
94 101
95 102 logger.info('Created post {} by {}'.format(
96 103 post, post.poster_ip))
97 104
98 105 if image:
99 106 post_image = PostImage.objects.create(image=image)
100 107 post.images.add(post_image)
101 108 logger.info('Created image #{} for post #{}'.format(
102 109 post_image.id, post.id))
103 110
104 111 thread.replies.add(post)
105 112 list(map(thread.add_tag, tags))
106 113
107 114 if new_thread:
108 115 Thread.objects.process_oldest_threads()
109 116 else:
110 117 thread.bump()
111 118 thread.last_edit_time = posting_time
112 119 thread.save()
113 120
114 121 self.connect_replies(post)
115 122
116 123 return post
117 124
118 125 def delete_posts_by_ip(self, ip):
119 126 """
120 127 Deletes all posts of the author with same IP
121 128 """
122 129
123 130 posts = self.filter(poster_ip=ip)
124 131 for post in posts:
125 132 post.delete()
126 133
127 134 def connect_replies(self, post):
128 135 """
129 136 Connects replies to a post to show them as a reflink map
130 137 """
131 138
132 139 for reply_number in re.finditer(REGEX_REPLY, post.get_raw_text()):
133 140 post_id = reply_number.group(1)
134 141 ref_post = self.filter(id=post_id)
135 142 if ref_post.count() > 0:
136 143 referenced_post = ref_post[0]
137 144 referenced_post.referenced_posts.add(post)
138 145 referenced_post.last_edit_time = post.pub_time
139 146 referenced_post.build_refmap()
140 147 referenced_post.save(update_fields=['refmap', 'last_edit_time'])
141 148
142 149 referenced_thread = referenced_post.get_thread()
143 150 referenced_thread.last_edit_time = post.pub_time
144 151 referenced_thread.save(update_fields=['last_edit_time'])
145 152
146 153 def get_posts_per_day(self):
147 154 """
148 155 Gets average count of posts per day for the last 7 days
149 156 """
150 157
151 158 day_end = date.today()
152 159 day_start = day_end - timedelta(POSTS_PER_DAY_RANGE)
153 160
154 161 cache_key = CACHE_KEY_PPD + str(day_end)
155 162 ppd = cache.get(cache_key)
156 163 if ppd:
157 164 return ppd
158 165
159 166 day_time_start = timezone.make_aware(datetime.combine(
160 167 day_start, dtime()), timezone.get_current_timezone())
161 168 day_time_end = timezone.make_aware(datetime.combine(
162 169 day_end, dtime()), timezone.get_current_timezone())
163 170
164 171 posts_per_period = float(self.filter(
165 172 pub_time__lte=day_time_end,
166 173 pub_time__gte=day_time_start).count())
167 174
168 175 ppd = posts_per_period / POSTS_PER_DAY_RANGE
169 176
170 177 cache.set(cache_key, ppd)
171 178 return ppd
172 179
173 180 def _preparse_text(self, text):
174 181 """
175 182 Preparses text to change patterns like '>>' to a proper bbcode
176 183 tags.
177 184 """
178 185
179 186 for key, value in PREPARSE_PATTERNS.items():
180 187 text = re.sub(key, value, text, flags=re.MULTILINE)
181 188
182 189 return text
183 190
184 191
185 192 class Post(models.Model, Viewable):
186 193 """A post is a message."""
187 194
188 195 objects = PostManager()
189 196
190 197 class Meta:
191 198 app_label = APP_LABEL_BOARDS
192 199 ordering = ('id',)
193 200
194 201 title = models.CharField(max_length=TITLE_MAX_LENGTH)
195 202 pub_time = models.DateTimeField()
196 203 text = TextField(blank=True, null=True)
197 204 _text_rendered = TextField(blank=True, null=True, editable=False)
198 205
199 206 images = models.ManyToManyField(PostImage, null=True, blank=True,
200 207 related_name='ip+', db_index=True)
201 208
202 209 poster_ip = models.GenericIPAddressField()
203 210 poster_user_agent = models.TextField()
204 211
205 212 thread_new = models.ForeignKey('Thread', null=True, default=None,
206 213 db_index=True)
207 214 last_edit_time = models.DateTimeField()
208 215
209 216 referenced_posts = models.ManyToManyField('Post', symmetrical=False,
210 217 null=True,
211 218 blank=True, related_name='rfp+',
212 219 db_index=True)
213 220 refmap = models.TextField(null=True, blank=True)
214 221
215 222 def __str__(self):
216 223 return 'P#{}/{}'.format(self.id, self.title)
217 224
218 225 def get_title(self) -> str:
219 226 """
220 227 Gets original post title or part of its text.
221 228 """
222 229
223 230 title = self.title
224 231 if not title:
225 232 title = self.get_text()
226 233
227 234 return title
228 235
229 236 def build_refmap(self) -> None:
230 237 """
231 238 Builds a replies map string from replies list. This is a cache to stop
232 239 the server from recalculating the map on every post show.
233 240 """
234 241 map_string = ''
235 242
236 243 first = True
237 244 for refpost in self.referenced_posts.all():
238 245 if not first:
239 246 map_string += ', '
240 247 map_string += '<a href="%s">&gt;&gt;%s</a>' % (refpost.get_url(),
241 248 refpost.id)
242 249 first = False
243 250
244 251 self.refmap = map_string
245 252
246 253 def get_sorted_referenced_posts(self):
247 254 return self.refmap
248 255
249 256 def is_referenced(self) -> bool:
250 257 if not self.refmap:
251 258 return False
252 259 else:
253 260 return len(self.refmap) > 0
254 261
255 262 def is_opening(self) -> bool:
256 263 """
257 264 Checks if this is an opening post or just a reply.
258 265 """
259 266
260 267 return self.get_thread().get_opening_post_id() == self.id
261 268
262 269 @transaction.atomic
263 270 def add_tag(self, tag):
264 271 edit_time = timezone.now()
265 272
266 273 thread = self.get_thread()
267 274 thread.add_tag(tag)
268 275 self.last_edit_time = edit_time
269 276 self.save(update_fields=['last_edit_time'])
270 277
271 278 thread.last_edit_time = edit_time
272 279 thread.save(update_fields=['last_edit_time'])
273 280
274 281 def get_url(self, thread=None):
275 282 """
276 283 Gets full url to the post.
277 284 """
278 285
279 286 cache_key = CACHE_KEY_POST_URL + str(self.id)
280 287 link = cache.get(cache_key)
281 288
282 289 if not link:
283 290 if not thread:
284 291 thread = self.get_thread()
285 292
286 293 opening_id = thread.get_opening_post_id()
287 294
288 295 if self.id != opening_id:
289 296 link = reverse('thread', kwargs={
290 297 'post_id': opening_id}) + '#' + str(self.id)
291 298 else:
292 299 link = reverse('thread', kwargs={'post_id': self.id})
293 300
294 301 cache.set(cache_key, link)
295 302
296 303 return link
297 304
298 305 def get_thread(self) -> Thread:
299 306 """
300 307 Gets post's thread.
301 308 """
302 309
303 310 return self.thread_new
304 311
305 312 def get_referenced_posts(self):
306 313 return self.referenced_posts.only('id', 'thread_new')
307 314
308 315 def get_view(self, moderator=False, need_open_link=False,
309 316 truncated=False, *args, **kwargs):
310 if 'is_opening' in kwargs:
311 is_opening = kwargs['is_opening']
312 else:
313 is_opening = self.is_opening()
317 """
318 Renders post's HTML view. Some of the post params can be passed over
319 kwargs for the means of caching (if we view the thread, some params
320 are same for every post and don't need to be computed over and over.
321 """
314 322
315 if 'thread' in kwargs:
316 thread = kwargs['thread']
317 else:
318 thread = self.get_thread()
319
320 if 'can_bump' in kwargs:
321 can_bump = kwargs['can_bump']
322 else:
323 can_bump = thread.can_bump()
323 is_opening = kwargs.get(PARAMETER_IS_OPENING, self.is_opening())
324 thread = kwargs.get(PARAMETER_THREAD, self.get_thread())
325 can_bump = kwargs.get(PARAMETER_BUMPABLE, thread.can_bump())
324 326
325 327 if is_opening:
326 328 opening_post_id = self.id
327 329 else:
328 330 opening_post_id = thread.get_opening_post_id()
329 331
330 332 return render_to_string('boards/post.html', {
331 'post': self,
332 'moderator': moderator,
333 'is_opening': is_opening,
334 'thread': thread,
335 'bumpable': can_bump,
336 'need_open_link': need_open_link,
337 'truncated': truncated,
338 'opening_post_id': opening_post_id,
333 PARAMETER_POST: self,
334 PARAMETER_MODERATOR: moderator,
335 PARAMETER_IS_OPENING: is_opening,
336 PARAMETER_THREAD: thread,
337 PARAMETER_BUMPABLE: can_bump,
338 PARAMETER_NEED_OPEN_LINK: need_open_link,
339 PARAMETER_TRUNCATED: truncated,
340 PARAMETER_OP_ID: opening_post_id,
339 341 })
340 342
341 343 def get_first_image(self) -> PostImage:
342 344 return self.images.earliest('id')
343 345
344 346 def delete(self, using=None):
345 347 """
346 348 Deletes all post images and the post itself. If the post is opening,
347 349 thread with all posts is deleted.
348 350 """
349 351
350 352 self.images.all().delete()
351 353
352 354 if self.is_opening():
353 355 self.get_thread().delete()
354 356 else:
355 357 thread = self.get_thread()
356 358 thread.last_edit_time = timezone.now()
357 359 thread.save()
358 360
359 361 super(Post, self).delete(using)
360 362
361 363 logging.getLogger('boards.post.delete').info(
362 364 'Deleted post {}'.format(self))
363 365
364 366 def get_post_data(self, format_type=DIFF_TYPE_JSON, request=None,
365 367 include_last_update=False):
366 368 """
367 369 Gets post HTML or JSON data that can be rendered on a page or used by
368 370 API.
369 371 """
370 372
371 373 if format_type == DIFF_TYPE_HTML:
372 context = RequestContext(request)
373 context['post'] = self
374 params = dict()
375 params['post'] = self
374 376 if PARAMETER_TRUNCATED in request.GET:
375 context[PARAMETER_TRUNCATED] = True
377 params[PARAMETER_TRUNCATED] = True
376 378
377 # TODO Use dict here
378 return render_to_string('boards/api_post.html',
379 context_instance=context)
379 return render_to_string('boards/api_post.html', params)
380 380 elif format_type == DIFF_TYPE_JSON:
381 381 post_json = {
382 382 'id': self.id,
383 383 'title': self.title,
384 384 'text': self._text_rendered,
385 385 }
386 386 if self.images.exists():
387 387 post_image = self.get_first_image()
388 388 post_json['image'] = post_image.image.url
389 389 post_json['image_preview'] = post_image.image.url_200x150
390 390 if include_last_update:
391 391 post_json['bump_time'] = datetime_to_epoch(
392 392 self.thread_new.bump_time)
393 393 return post_json
394 394
395 395 def send_to_websocket(self, request, recursive=True):
396 396 """
397 397 Sends post HTML data to the thread web socket.
398 398 """
399 399
400 400 if not settings.WEBSOCKETS_ENABLED:
401 401 return
402 402
403 403 client = Client()
404 404
405 405 thread = self.get_thread()
406 406 thread_id = thread.id
407 407 channel_name = WS_CHANNEL_THREAD + str(thread.get_opening_post_id())
408 408 client.publish(channel_name, {
409 409 WS_NOTIFICATION_TYPE: WS_NOTIFICATION_TYPE_NEW_POST,
410 410 })
411 411 client.send()
412 412
413 413 logger = logging.getLogger('boards.post.websocket')
414 414
415 415 logger.info('Sent notification from post #{} to channel {}'.format(
416 416 self.id, channel_name))
417 417
418 418 if recursive:
419 419 for reply_number in re.finditer(REGEX_REPLY, self.get_raw_text()):
420 420 post_id = reply_number.group(1)
421 421 ref_post = Post.objects.filter(id=post_id)[0]
422 422
423 423 # If post is in this thread, its thread was already notified.
424 424 # Otherwise, notify its thread separately.
425 425 if ref_post.thread_new_id != thread_id:
426 426 ref_post.send_to_websocket(request, recursive=False)
427 427
428 428 def save(self, force_insert=False, force_update=False, using=None,
429 429 update_fields=None):
430 430 self._text_rendered = bbcode_extended(self.get_raw_text())
431 431
432 432 super().save(force_insert, force_update, using, update_fields)
433 433
434 434 def get_text(self) -> str:
435 435 return self._text_rendered
436 436
437 437 def get_raw_text(self) -> str:
438 438 return self.text
@@ -1,96 +1,96
1 1 {% extends "boards/base.html" %}
2 2
3 3 {% load i18n %}
4 4 {% load cache %}
5 5 {% load static from staticfiles %}
6 6 {% load board %}
7 7
8 8 {% block head %}
9 9 <title>{{ opening_post.get_title|striptags|truncatewords:10 }}
10 10 - {{ site_name }}</title>
11 11 {% endblock %}
12 12
13 13 {% block content %}
14 14 {% get_current_language as LANGUAGE_CODE %}
15 15
16 16 {% cache 600 thread_view thread.id thread.last_edit_time moderator LANGUAGE_CODE %}
17 17
18 18 <div class="image-mode-tab">
19 19 <a class="current_mode" href="{% url 'thread' opening_post.id %}">{% trans 'Normal mode' %}</a>,
20 20 <a href="{% url 'thread_mode' opening_post.id 'gallery' %}">{% trans 'Gallery mode' %}</a>
21 21 </div>
22 22
23 23 {% if bumpable %}
24 24 <div class="bar-bg">
25 25 <div class="bar-value" style="width:{{ bumplimit_progress }}%" id="bumplimit_progress">
26 26 </div>
27 27 <div class="bar-text">
28 28 <span id="left_to_limit">{{ posts_left }}</span> {% trans 'posts to bumplimit' %}
29 29 </div>
30 30 </div>
31 31 {% endif %}
32 32
33 33 <div class="thread">
34 34 {% with can_bump=thread.can_bump %}
35 35 {% for post in thread.get_replies %}
36 36 {% with is_opening=forloop.first %}
37 {% post_view post moderator=moderator is_opening=is_opening thread=thread can_bump=can_bump opening_post_id=opening_post.id %}
37 {% post_view post moderator=moderator is_opening=is_opening thread=thread bumpable=can_bump opening_post_id=opening_post.id %}
38 38 {% endwith %}
39 39 {% endfor %}
40 40 {% endwith %}
41 41 </div>
42 42
43 43 {% if not thread.archived %}
44 44 <div class="post-form-w" id="form">
45 45 <script src="{% static 'js/panel.js' %}"></script>
46 46 <div class="form-title">{% trans "Reply to thread" %} #{{ opening_post.id }}</div>
47 47 <div class="post-form" id="compact-form">
48 48 <div class="swappable-form-full">
49 49 <form enctype="multipart/form-data" method="post"
50 50 >{% csrf_token %}
51 51 <div class="compact-form-text"></div>
52 52 {{ form.as_div }}
53 53 <div class="form-submit">
54 54 <input type="submit" value="{% trans "Post" %}"/>
55 55 </div>
56 56 </form>
57 57 </div>
58 58 <a onclick="swapForm(); return false;" href="#">
59 59 {% trans 'Switch mode' %}
60 60 </a>
61 61 <div><a href="{% url "staticpage" name="help" %}">
62 62 {% trans 'Text syntax' %}</a></div>
63 63 </div>
64 64 </div>
65 65
66 66 <script src="{% static 'js/jquery.form.min.js' %}"></script>
67 67 <script src="{% static 'js/thread_update.js' %}"></script>
68 68 <script src="{% static 'js/3party/centrifuge.js' %}"></script>
69 69 {% endif %}
70 70
71 71 <script src="{% static 'js/form.js' %}"></script>
72 72 <script src="{% static 'js/thread.js' %}"></script>
73 73
74 74 {% endcache %}
75 75 {% endblock %}
76 76
77 77 {% block metapanel %}
78 78
79 79 {% get_current_language as LANGUAGE_CODE %}
80 80
81 81 <span class="metapanel"
82 82 data-last-update="{{ last_update }}"
83 83 data-ws-token="{{ ws_token }}"
84 84 data-ws-project="{{ ws_project }}"
85 85 data-ws-host="{{ ws_host }}"
86 86 data-ws-port="{{ ws_port }}">
87 87 {% cache 600 thread_meta thread.last_edit_time moderator LANGUAGE_CODE %}
88 88 <span id="autoupdate">[-]</span>
89 89 <span id="reply-count">{{ thread.get_reply_count }}</span>/{{ max_replies }} {% trans 'messages' %},
90 90 <span id="image-count">{{ thread.get_images_count }}</span> {% trans 'images' %}.
91 91 {% trans 'Last update: ' %}<span id="last-update">{{ thread.last_edit_time }}</span>
92 92 [<a href="rss/">RSS</a>]
93 93 {% endcache %}
94 94 </span>
95 95
96 96 {% endblock %}
@@ -1,143 +1,139
1 1 from django.db import transaction
2 2 from django.shortcuts import render, redirect
3 3
4 4 from boards import utils, settings
5 5 from boards.abstracts.paginator import get_paginator
6 6 from boards.abstracts.settingsmanager import get_settings_manager
7 7 from boards.forms import ThreadForm, PlainErrorList
8 8 from boards.models import Post, Thread, Ban, Tag
9 9 from boards.views.banned import BannedView
10 10 from boards.views.base import BaseBoardView, CONTEXT_FORM
11 11 from boards.views.posting_mixin import PostMixin
12 12
13 13
14 14 FORM_TAGS = 'tags'
15 15 FORM_TEXT = 'text'
16 16 FORM_TITLE = 'title'
17 17 FORM_IMAGE = 'image'
18 18
19 19 TAG_DELIMITER = ' '
20 20
21 21 PARAMETER_CURRENT_PAGE = 'current_page'
22 22 PARAMETER_PAGINATOR = 'paginator'
23 23 PARAMETER_THREADS = 'threads'
24 24
25 25 TEMPLATE = 'boards/posting_general.html'
26 26 DEFAULT_PAGE = 1
27 27
28 28
29 29 class AllThreadsView(PostMixin, BaseBoardView):
30 30
31 31 def __init__(self):
32 32 self.settings_manager = None
33 33 super(AllThreadsView, self).__init__()
34 34
35 35 def get(self, request, page=DEFAULT_PAGE, form=None):
36 36 context = self.get_context_data(request=request)
37 37
38 38 if not form:
39 39 form = ThreadForm(error_class=PlainErrorList)
40 40
41 41 self.settings_manager = get_settings_manager(request)
42 42 paginator = get_paginator(self.get_threads(),
43 43 settings.THREADS_PER_PAGE)
44 44 paginator.current_page = int(page)
45 45
46 46 threads = paginator.page(page).object_list
47 47
48 48 context[PARAMETER_THREADS] = threads
49 49 context[CONTEXT_FORM] = form
50 50
51 51 self._get_page_context(paginator, context, page)
52 52
53 53 # TODO Use dict here
54 54 return render(request, TEMPLATE, context_instance=context)
55 55
56 56 def post(self, request, page=DEFAULT_PAGE):
57 57 form = ThreadForm(request.POST, request.FILES,
58 58 error_class=PlainErrorList)
59 59 form.session = request.session
60 60
61 61 if form.is_valid():
62 62 return self.create_thread(request, form)
63 63 if form.need_to_ban:
64 64 # Ban user because he is suspected to be a bot
65 65 self._ban_current_user(request)
66 66
67 67 return self.get(request, page, form)
68 68
69 69 @staticmethod
70 70 def _get_page_context(paginator, context, page):
71 71 """
72 72 Get pagination context variables
73 73 """
74 74
75 75 context[PARAMETER_PAGINATOR] = paginator
76 76 context[PARAMETER_CURRENT_PAGE] = paginator.page(int(page))
77 77
78 78 @staticmethod
79 79 def parse_tags_string(tag_strings):
80 80 """
81 81 Parses tag list string and returns tag object list.
82 82 """
83 83
84 84 tags = []
85 85
86 86 if tag_strings:
87 87 tag_strings = tag_strings.split(TAG_DELIMITER)
88 88 for tag_name in tag_strings:
89 89 tag_name = tag_name.strip().lower()
90 90 if len(tag_name) > 0:
91 91 tag, created = Tag.objects.get_or_create(name=tag_name)
92 92 tags.append(tag)
93 93
94 94 return tags
95 95
96 96 @transaction.atomic
97 97 def create_thread(self, request, form, html_response=True):
98 98 """
99 99 Creates a new thread with an opening post.
100 100 """
101 101
102 102 ip = utils.get_client_ip(request)
103 103 is_banned = Ban.objects.filter(ip=ip).exists()
104 104
105 105 if is_banned:
106 106 if html_response:
107 107 return redirect(BannedView().as_view())
108 108 else:
109 109 return
110 110
111 111 data = form.cleaned_data
112 112
113 113 title = data[FORM_TITLE]
114 114 text = data[FORM_TEXT]
115 image = data.get(FORM_IMAGE)
115 116
116 117 text = self._remove_invalid_links(text)
117 118
118 if FORM_IMAGE in list(data.keys()):
119 image = data[FORM_IMAGE]
120 else:
121 image = None
122
123 119 tag_strings = data[FORM_TAGS]
124 120
125 121 tags = self.parse_tags_string(tag_strings)
126 122
127 123 post = Post.objects.create_post(title=title, text=text, image=image,
128 124 ip=ip, tags=tags)
129 125
130 126 # This is required to update the threads to which posts we have replied
131 127 # when creating this one
132 128 post.send_to_websocket(request)
133 129
134 130 if html_response:
135 131 return redirect(post.get_url())
136 132
137 133 def get_threads(self):
138 134 """
139 135 Gets list of threads that will be shown on a page.
140 136 """
141 137
142 138 return Thread.objects.all().order_by('-bump_time')\
143 139 .exclude(tags__in=self.settings_manager.get_hidden_tags())
@@ -1,225 +1,223
1 1 from datetime import datetime
2 2 import json
3 3 import logging
4 4 from django.db import transaction
5 5 from django.http import HttpResponse
6 6 from django.shortcuts import get_object_or_404, render
7 7 from django.template import RequestContext
8 8 from django.utils import timezone
9 9 from django.core import serializers
10 10
11 11 from boards.forms import PostForm, PlainErrorList
12 12 from boards.models import Post, Thread, Tag
13 13 from boards.utils import datetime_to_epoch
14 14 from boards.views.thread import ThreadView
15 15
16 16 __author__ = 'neko259'
17 17
18 18 PARAMETER_TRUNCATED = 'truncated'
19 19 PARAMETER_TAG = 'tag'
20 20 PARAMETER_OFFSET = 'offset'
21 21 PARAMETER_DIFF_TYPE = 'type'
22 22
23 23 DIFF_TYPE_HTML = 'html'
24 24 DIFF_TYPE_JSON = 'json'
25 25
26 26 STATUS_OK = 'ok'
27 27 STATUS_ERROR = 'error'
28 28
29 29 logger = logging.getLogger(__name__)
30 30
31 31
32 32 @transaction.atomic
33 33 def api_get_threaddiff(request, thread_id, last_update_time):
34 34 """
35 35 Gets posts that were changed or added since time
36 36 """
37 37
38 38 thread = get_object_or_404(Post, id=thread_id).get_thread()
39 39
40 40 # Add 1 to ensure we don't load the same post over and over
41 41 last_update_timestamp = float(last_update_time) + 1
42 42
43 43 filter_time = datetime.fromtimestamp(last_update_timestamp / 1000000,
44 44 timezone.get_current_timezone())
45 45
46 46 json_data = {
47 47 'added': [],
48 48 'updated': [],
49 49 'last_update': None,
50 50 }
51 51 added_posts = Post.objects.filter(thread_new=thread,
52 52 pub_time__gt=filter_time) \
53 53 .order_by('pub_time')
54 54 updated_posts = Post.objects.filter(thread_new=thread,
55 55 pub_time__lte=filter_time,
56 56 last_edit_time__gt=filter_time)
57 57
58 diff_type = DIFF_TYPE_HTML
59 if PARAMETER_DIFF_TYPE in request.GET:
60 diff_type = request.GET[PARAMETER_DIFF_TYPE]
58 diff_type = request.GET.get(PARAMETER_DIFF_TYPE, DIFF_TYPE_HTML)
61 59
62 60 for post in added_posts:
63 61 json_data['added'].append(get_post_data(post.id, diff_type, request))
64 62 for post in updated_posts:
65 63 json_data['updated'].append(get_post_data(post.id, diff_type, request))
66 64 json_data['last_update'] = datetime_to_epoch(thread.last_edit_time)
67 65
68 66 return HttpResponse(content=json.dumps(json_data))
69 67
70 68
71 69 def api_add_post(request, opening_post_id):
72 70 """
73 71 Adds a post and return the JSON response for it
74 72 """
75 73
76 74 opening_post = get_object_or_404(Post, id=opening_post_id)
77 75
78 76 logger.info('Adding post via api...')
79 77
80 78 status = STATUS_OK
81 79 errors = []
82 80
83 81 if request.method == 'POST':
84 82 form = PostForm(request.POST, request.FILES, error_class=PlainErrorList)
85 83 form.session = request.session
86 84
87 85 if form.need_to_ban:
88 86 # Ban user because he is suspected to be a bot
89 87 # _ban_current_user(request)
90 88 status = STATUS_ERROR
91 89 if form.is_valid():
92 90 post = ThreadView().new_post(request, form, opening_post,
93 91 html_response=False)
94 92 if not post:
95 93 status = STATUS_ERROR
96 94 else:
97 95 logger.info('Added post #%d via api.' % post.id)
98 96 else:
99 97 status = STATUS_ERROR
100 98 errors = form.as_json_errors()
101 99
102 100 response = {
103 101 'status': status,
104 102 'errors': errors,
105 103 }
106 104
107 105 return HttpResponse(content=json.dumps(response))
108 106
109 107
110 108 def get_post(request, post_id):
111 109 """
112 110 Gets the html of a post. Used for popups. Post can be truncated if used
113 111 in threads list with 'truncated' get parameter.
114 112 """
115 113
116 114 post = get_object_or_404(Post, id=post_id)
117 115
118 116 context = RequestContext(request)
119 117 context['post'] = post
120 118 if PARAMETER_TRUNCATED in request.GET:
121 119 context[PARAMETER_TRUNCATED] = True
122 120
123 121 # TODO Use dict here
124 122 return render(request, 'boards/api_post.html', context_instance=context)
125 123
126 124
127 125 # TODO Test this
128 126 def api_get_threads(request, count):
129 127 """
130 128 Gets the JSON thread opening posts list.
131 129 Parameters that can be used for filtering:
132 130 tag, offset (from which thread to get results)
133 131 """
134 132
135 133 if PARAMETER_TAG in request.GET:
136 134 tag_name = request.GET[PARAMETER_TAG]
137 135 if tag_name is not None:
138 136 tag = get_object_or_404(Tag, name=tag_name)
139 137 threads = tag.get_threads().filter(archived=False)
140 138 else:
141 139 threads = Thread.objects.filter(archived=False)
142 140
143 141 if PARAMETER_OFFSET in request.GET:
144 142 offset = request.GET[PARAMETER_OFFSET]
145 143 offset = int(offset) if offset is not None else 0
146 144 else:
147 145 offset = 0
148 146
149 147 threads = threads.order_by('-bump_time')
150 148 threads = threads[offset:offset + int(count)]
151 149
152 150 opening_posts = []
153 151 for thread in threads:
154 152 opening_post = thread.get_opening_post()
155 153
156 154 # TODO Add tags, replies and images count
157 155 opening_posts.append(get_post_data(opening_post.id,
158 156 include_last_update=True))
159 157
160 158 return HttpResponse(content=json.dumps(opening_posts))
161 159
162 160
163 161 # TODO Test this
164 162 def api_get_tags(request):
165 163 """
166 164 Gets all tags or user tags.
167 165 """
168 166
169 167 # TODO Get favorite tags for the given user ID
170 168
171 169 tags = Tag.objects.get_not_empty_tags()
172 170 tag_names = []
173 171 for tag in tags:
174 172 tag_names.append(tag.name)
175 173
176 174 return HttpResponse(content=json.dumps(tag_names))
177 175
178 176
179 177 # TODO The result can be cached by the thread last update time
180 178 # TODO Test this
181 179 def api_get_thread_posts(request, opening_post_id):
182 180 """
183 181 Gets the JSON array of thread posts
184 182 """
185 183
186 184 opening_post = get_object_or_404(Post, id=opening_post_id)
187 185 thread = opening_post.get_thread()
188 186 posts = thread.get_replies()
189 187
190 188 json_data = {
191 189 'posts': [],
192 190 'last_update': None,
193 191 }
194 192 json_post_list = []
195 193
196 194 for post in posts:
197 195 json_post_list.append(get_post_data(post.id))
198 196 json_data['last_update'] = datetime_to_epoch(thread.last_edit_time)
199 197 json_data['posts'] = json_post_list
200 198
201 199 return HttpResponse(content=json.dumps(json_data))
202 200
203 201
204 202 def api_get_post(request, post_id):
205 203 """
206 204 Gets the JSON of a post. This can be
207 205 used as and API for external clients.
208 206 """
209 207
210 208 post = get_object_or_404(Post, id=post_id)
211 209
212 210 json = serializers.serialize("json", [post], fields=(
213 211 "pub_time", "_text_rendered", "title", "text", "image",
214 212 "image_width", "image_height", "replies", "tags"
215 213 ))
216 214
217 215 return HttpResponse(content=json)
218 216
219 217
220 218 # TODO Remove this method and use post method directly
221 219 def get_post_data(post_id, format_type=DIFF_TYPE_JSON, request=None,
222 220 include_last_update=False):
223 221 post = get_object_or_404(Post, id=post_id)
224 222 return post.get_post_data(format_type=format_type, request=request,
225 223 include_last_update=include_last_update)
@@ -1,41 +1,43
1 1 from django.shortcuts import render
2 from django.template import RequestContext
3 2 from django.views.generic import View
4 3 from haystack.query import SearchQuerySet
4
5 5 from boards.abstracts.paginator import get_paginator
6 6 from boards.forms import SearchForm, PlainErrorList
7 7
8
9 MIN_QUERY_LENGTH = 3
10 RESULTS_PER_PAGE = 10
11
8 12 FORM_QUERY = 'query'
9 13
10 14 CONTEXT_QUERY = 'query'
11 15 CONTEXT_FORM = 'form'
12 16 CONTEXT_PAGE = 'page'
13 17
14 18 REQUEST_PAGE = 'page'
15 19
16 20 __author__ = 'neko259'
17 21
18 22 TEMPLATE = 'search/search.html'
19 23
20 24
21 25 class BoardSearchView(View):
22 26 def get(self, request):
23 context = RequestContext(request)
27 params = dict()
28
24 29 form = SearchForm(request.GET, error_class=PlainErrorList)
25 context[CONTEXT_FORM] = form
30 params[CONTEXT_FORM] = form
26 31
27 32 if form.is_valid():
28 33 query = form.cleaned_data[FORM_QUERY]
29 if len(query) >= 3:
34 if len(query) >= MIN_QUERY_LENGTH:
30 35 results = SearchQuerySet().auto_query(query).order_by('-id')
31 paginator = get_paginator(results, 10)
36 paginator = get_paginator(results, RESULTS_PER_PAGE)
32 37
33 if REQUEST_PAGE in request.GET:
34 page = int(request.GET[REQUEST_PAGE])
35 else:
36 page = 1
37 context[CONTEXT_PAGE] = paginator.page(page)
38 context[CONTEXT_QUERY] = query
38 page = int(request.GET.get(REQUEST_PAGE, '1'))
39 39
40 # TODO Use dict here
41 return render(request, TEMPLATE, context_instance=context)
40 params[CONTEXT_PAGE] = paginator.page(page)
41 params[CONTEXT_QUERY] = query
42
43 return render(request, TEMPLATE, params)
General Comments 0
You need to be logged in to leave comments. Login now