##// END OF EJS Templates
Run thread update when connecting to websocket to get missed posts if the...
neko259 -
r892:6155490b default
parent child Browse files
Show More
@@ -1,285 +1,339 b''
1 /*
1 /*
2 @licstart The following is the entire license notice for the
2 @licstart The following is the entire license notice for the
3 JavaScript code in this page.
3 JavaScript code in this page.
4
4
5
5
6 Copyright (C) 2013 neko259
6 Copyright (C) 2013 neko259
7
7
8 The JavaScript code in this page is free software: you can
8 The JavaScript code in this page is free software: you can
9 redistribute it and/or modify it under the terms of the GNU
9 redistribute it and/or modify it under the terms of the GNU
10 General Public License (GNU GPL) as published by the Free Software
10 General Public License (GNU GPL) as published by the Free Software
11 Foundation, either version 3 of the License, or (at your option)
11 Foundation, either version 3 of the License, or (at your option)
12 any later version. The code is distributed WITHOUT ANY WARRANTY;
12 any later version. The code is distributed WITHOUT ANY WARRANTY;
13 without even the implied warranty of MERCHANTABILITY or FITNESS
13 without even the implied warranty of MERCHANTABILITY or FITNESS
14 FOR A PARTICULAR PURPOSE. See the GNU GPL for more details.
14 FOR A PARTICULAR PURPOSE. See the GNU GPL for more details.
15
15
16 As additional permission under GNU GPL version 3 section 7, you
16 As additional permission under GNU GPL version 3 section 7, you
17 may distribute non-source (e.g., minimized or compacted) forms of
17 may distribute non-source (e.g., minimized or compacted) forms of
18 that code without the copy of the GNU GPL normally required by
18 that code without the copy of the GNU GPL normally required by
19 section 4, provided you include this license notice and a URL
19 section 4, provided you include this license notice and a URL
20 through which recipients can access the Corresponding Source.
20 through which recipients can access the Corresponding Source.
21
21
22 @licend The above is the entire license notice
22 @licend The above is the entire license notice
23 for the JavaScript code in this page.
23 for the JavaScript code in this page.
24 */
24 */
25
25
26 var wsUrl = 'ws://localhost:9090/connection/websocket';
26 var wsUrl = 'ws://localhost:9090/connection/websocket';
27 var wsUser = '';
27 var wsUser = '';
28
28
29 var loading = false;
29 var loading = false;
30 var lastUpdateTime = null;
30 var lastUpdateTime = null;
31 var unreadPosts = 0;
31 var unreadPosts = 0;
32 var documentOriginalTitle = '';
32
33
33 // Thread ID does not change, can be stored one time
34 // Thread ID does not change, can be stored one time
34 var threadId = $('div.thread').children('.post').first().attr('id');
35 var threadId = $('div.thread').children('.post').first().attr('id');
35
36
36 function connectWebsocket() {
37 function connectWebsocket() {
37 var metapanel = $('.metapanel')[0];
38 var metapanel = $('.metapanel')[0];
38
39
39 var wsHost = metapanel.getAttribute('data-ws-host');
40 var wsHost = metapanel.getAttribute('data-ws-host');
40 var wsPort = metapanel.getAttribute('data-ws-port');
41 var wsPort = metapanel.getAttribute('data-ws-port');
41
42
42 if (wsHost.length > 0 && wsPort.length > 0)
43 if (wsHost.length > 0 && wsPort.length > 0)
43 var centrifuge = new Centrifuge({
44 var centrifuge = new Centrifuge({
44 "url": 'ws://' + wsHost + ':' + wsPort + "/connection/websocket",
45 "url": 'ws://' + wsHost + ':' + wsPort + "/connection/websocket",
45 "project": metapanel.getAttribute('data-ws-project'),
46 "project": metapanel.getAttribute('data-ws-project'),
46 "user": wsUser,
47 "user": wsUser,
47 "timestamp": metapanel.getAttribute('data-last-update'),
48 "timestamp": metapanel.getAttribute('data-last-update'),
48 "token": metapanel.getAttribute('data-ws-token'),
49 "token": metapanel.getAttribute('data-ws-token'),
49 "debug": false
50 "debug": false
50 });
51 });
51
52
52 centrifuge.on('error', function(error_message) {
53 centrifuge.on('error', function(error_message) {
53 console.log("Error connecting to websocket server.");
54 console.log("Error connecting to websocket server.");
54 return false;
55 return false;
55 });
56 });
56
57
57 centrifuge.on('connect', function() {
58 centrifuge.on('connect', function() {
58 var channelName = 'thread:' + threadId;
59 var channelName = 'thread:' + threadId;
59 centrifuge.subscribe(channelName, function(message) {
60 centrifuge.subscribe(channelName, function(message) {
60 var postHtml = message.data['html'];
61 var postHtml = message.data['html'];
61 var isAdded = (message.data['diff_type'] === 'added');
62 var isAdded = (message.data['diff_type'] === 'added');
62
63
63 if (postHtml) {
64 if (postHtml) {
64 updatePost(postHtml, isAdded);
65 updatePost(postHtml, isAdded);
65 }
66 }
66 });
67 });
67
68
69 // For the case we closed the browser and missed some updates
70 getThreadDiff();
68 $('#autoupdate').text('[+]');
71 $('#autoupdate').text('[+]');
69 });
72 });
70
73
71 centrifuge.connect();
74 centrifuge.connect();
72
75
73 return true;
76 return true;
74 }
77 }
75
78
79 /**
80 * Get diff of the posts from the current thread timestamp.
81 * This is required if the browser was closed and some post updates were
82 * missed.
83 */
84 function getThreadDiff() {
85 var lastPost = threadPosts.last();
86 var threadId = threadPosts.first().attr('id');
87
88 var diffUrl = '/api/diff_thread/' + threadId + '/' + lastUpdateTime + '/';
89
90 lastUpdateTime = $('.metapanel').attr('data-last-update');
91
92 $.getJSON(diffUrl)
93 .success(function(data) {
94 var bottom = isPageBottom();
95 var addedPosts = data.added;
96
97 for (var i = 0; i < addedPosts.length; i++) {
98 var postText = addedPosts[i];
99 var post = $(postText);
100
101 updatePost(post, true)
102
103 lastPost = post;
104 }
105
106 var updatedPosts = data.updated;
107
108 for (var i = 0; i < updatedPosts.length; i++) {
109 var postText = updatedPosts[i];
110 var post = $(postText);
111
112 updatePost(post, false)
113 }
114
115 // TODO Process removed posts if any
116
117 lastUpdateTime = data.last_update;
118 })
119 }
120
121 /**
122 * Add or update the post on thml page.
123 */
76 function updatePost(postHtml, isAdded) {
124 function updatePost(postHtml, isAdded) {
77 // This needs to be set on start because the page is scrolled after posts
125 // This needs to be set on start because the page is scrolled after posts
78 // are added or updated
126 // are added or updated
79 var bottom = isPageBottom();
127 var bottom = isPageBottom();
80
128
81 var post = $(postHtml);
129 var post = $(postHtml);
82
130
83 var threadPosts = $('div.thread').children('.post');
131 var threadPosts = $('div.thread').children('.post');
84
132
85 var lastUpdate = '';
133 var lastUpdate = '';
86
134
87 if (isAdded) {
135 if (isAdded) {
88 var lastPost = threadPosts.last();
136 var lastPost = threadPosts.last();
89
137
90 post.appendTo(lastPost.parent());
138 post.appendTo(lastPost.parent());
91
139
92 updateBumplimitProgress(1);
140 updateBumplimitProgress(1);
93 showNewPostsTitle(1);
141 showNewPostsTitle(1);
94
142
95 lastUpdate = post.children('.post-info').first()
143 lastUpdate = post.children('.post-info').first()
96 .children('.pub_time').first().text();
144 .children('.pub_time').first().text();
97
145
98 if (bottom) {
146 if (bottom) {
99 scrollToBottom();
147 scrollToBottom();
100 }
148 }
101 } else {
149 } else {
102 var postId = post.attr('id');
150 var postId = post.attr('id');
103
151
104 var oldPost = $('div.thread').children('.post[id=' + postId + ']');
152 var oldPost = $('div.thread').children('.post[id=' + postId + ']');
105
153
106 oldPost.replaceWith(post);
154 oldPost.replaceWith(post);
107 }
155 }
108
156
109 processNewPost(post);
157 processNewPost(post);
110 updateMetadataPanel(lastUpdate)
158 updateMetadataPanel(lastUpdate)
111 }
159 }
112
160
161 /**
162 * Initiate a blinking animation on a node to show it was updated.
163 */
113 function blink(node) {
164 function blink(node) {
114 var blinkCount = 2;
165 var blinkCount = 2;
115
166
116 var nodeToAnimate = node;
167 var nodeToAnimate = node;
117 for (var i = 0; i < blinkCount; i++) {
168 for (var i = 0; i < blinkCount; i++) {
118 nodeToAnimate = nodeToAnimate.fadeTo('fast', 0.5).fadeTo('fast', 1.0);
169 nodeToAnimate = nodeToAnimate.fadeTo('fast', 0.5).fadeTo('fast', 1.0);
119 }
170 }
120 }
171 }
121
172
122 function isPageBottom() {
173 function isPageBottom() {
123 var scroll = $(window).scrollTop() / ($(document).height()
174 var scroll = $(window).scrollTop() / ($(document).height()
124 - $(window).height());
175 - $(window).height());
125
176
126 return scroll == 1
177 return scroll == 1
127 }
178 }
128
179
129 function initAutoupdate() {
180 function initAutoupdate() {
130 return connectWebsocket();
181 return connectWebsocket();
131 }
182 }
132
183
133 function getReplyCount() {
184 function getReplyCount() {
134 return $('.thread').children('.post').length
185 return $('.thread').children('.post').length
135 }
186 }
136
187
137 function getImageCount() {
188 function getImageCount() {
138 return $('.thread').find('img').length
189 return $('.thread').find('img').length
139 }
190 }
140
191
192 /**
193 * Update post count, images count and last update time in the metadata
194 * panel.
195 */
141 function updateMetadataPanel(lastUpdate) {
196 function updateMetadataPanel(lastUpdate) {
142 var replyCountField = $('#reply-count');
197 var replyCountField = $('#reply-count');
143 var imageCountField = $('#image-count');
198 var imageCountField = $('#image-count');
144
199
145 replyCountField.text(getReplyCount());
200 replyCountField.text(getReplyCount());
146 imageCountField.text(getImageCount());
201 imageCountField.text(getImageCount());
147
202
148 if (lastUpdate !== '') {
203 if (lastUpdate !== '') {
149 var lastUpdateField = $('#last-update');
204 var lastUpdateField = $('#last-update');
150 lastUpdateField.text(lastUpdate);
205 lastUpdateField.text(lastUpdate);
151 blink(lastUpdateField);
206 blink(lastUpdateField);
152 }
207 }
153
208
154 blink(replyCountField);
209 blink(replyCountField);
155 blink(imageCountField);
210 blink(imageCountField);
156 }
211 }
157
212
158 /**
213 /**
159 * Update bumplimit progress bar
214 * Update bumplimit progress bar
160 */
215 */
161 function updateBumplimitProgress(postDelta) {
216 function updateBumplimitProgress(postDelta) {
162 var progressBar = $('#bumplimit_progress');
217 var progressBar = $('#bumplimit_progress');
163 if (progressBar) {
218 if (progressBar) {
164 var postsToLimitElement = $('#left_to_limit');
219 var postsToLimitElement = $('#left_to_limit');
165
220
166 var oldPostsToLimit = parseInt(postsToLimitElement.text());
221 var oldPostsToLimit = parseInt(postsToLimitElement.text());
167 var postCount = getReplyCount();
222 var postCount = getReplyCount();
168 var bumplimit = postCount - postDelta + oldPostsToLimit;
223 var bumplimit = postCount - postDelta + oldPostsToLimit;
169
224
170 var newPostsToLimit = bumplimit - postCount;
225 var newPostsToLimit = bumplimit - postCount;
171 if (newPostsToLimit <= 0) {
226 if (newPostsToLimit <= 0) {
172 $('.bar-bg').remove();
227 $('.bar-bg').remove();
173 $('.thread').children('.post').addClass('dead_post');
228 $('.thread').children('.post').addClass('dead_post');
174 } else {
229 } else {
175 postsToLimitElement.text(newPostsToLimit);
230 postsToLimitElement.text(newPostsToLimit);
176 progressBar.width((100 - postCount / bumplimit * 100.0) + '%');
231 progressBar.width((100 - postCount / bumplimit * 100.0) + '%');
177 }
232 }
178 }
233 }
179 }
234 }
180
235
181 var documentOriginalTitle = '';
182 /**
236 /**
183 * Show 'new posts' text in the title if the document is not visible to a user
237 * Show 'new posts' text in the title if the document is not visible to a user
184 */
238 */
185 function showNewPostsTitle(newPostCount) {
239 function showNewPostsTitle(newPostCount) {
186 if (document.hidden) {
240 if (document.hidden) {
187 if (documentOriginalTitle === '') {
241 if (documentOriginalTitle === '') {
188 documentOriginalTitle = document.title;
242 documentOriginalTitle = document.title;
189 }
243 }
190 unreadPosts = unreadPosts + newPostCount;
244 unreadPosts = unreadPosts + newPostCount;
191 document.title = '[' + unreadPosts + '] ' + documentOriginalTitle;
245 document.title = '[' + unreadPosts + '] ' + documentOriginalTitle;
192
246
193 document.addEventListener('visibilitychange', function() {
247 document.addEventListener('visibilitychange', function() {
194 if (documentOriginalTitle !== '') {
248 if (documentOriginalTitle !== '') {
195 document.title = documentOriginalTitle;
249 document.title = documentOriginalTitle;
196 documentOriginalTitle = '';
250 documentOriginalTitle = '';
197 unreadPosts = 0;
251 unreadPosts = 0;
198 }
252 }
199
253
200 document.removeEventListener('visibilitychange', null);
254 document.removeEventListener('visibilitychange', null);
201 });
255 });
202 }
256 }
203 }
257 }
204
258
205 /**
259 /**
206 * Clear all entered values in the form fields
260 * Clear all entered values in the form fields
207 */
261 */
208 function resetForm(form) {
262 function resetForm(form) {
209 form.find('input:text, input:password, input:file, select, textarea').val('');
263 form.find('input:text, input:password, input:file, select, textarea').val('');
210 form.find('input:radio, input:checkbox')
264 form.find('input:radio, input:checkbox')
211 .removeAttr('checked').removeAttr('selected');
265 .removeAttr('checked').removeAttr('selected');
212 $('.file_wrap').find('.file-thumb').remove();
266 $('.file_wrap').find('.file-thumb').remove();
213 }
267 }
214
268
215 /**
269 /**
216 * When the form is posted, this method will be run as a callback
270 * When the form is posted, this method will be run as a callback
217 */
271 */
218 function updateOnPost(response, statusText, xhr, form) {
272 function updateOnPost(response, statusText, xhr, form) {
219 var json = $.parseJSON(response);
273 var json = $.parseJSON(response);
220 var status = json.status;
274 var status = json.status;
221
275
222 showAsErrors(form, '');
276 showAsErrors(form, '');
223
277
224 if (status === 'ok') {
278 if (status === 'ok') {
225 resetForm(form);
279 resetForm(form);
226 } else {
280 } else {
227 var errors = json.errors;
281 var errors = json.errors;
228 for (var i = 0; i < errors.length; i++) {
282 for (var i = 0; i < errors.length; i++) {
229 var fieldErrors = errors[i];
283 var fieldErrors = errors[i];
230
284
231 var error = fieldErrors.errors;
285 var error = fieldErrors.errors;
232
286
233 showAsErrors(form, error);
287 showAsErrors(form, error);
234 }
288 }
235 }
289 }
236
290
237 scrollToBottom();
291 scrollToBottom();
238 }
292 }
239
293
240 /**
294 /**
241 * Show text in the errors row of the form.
295 * Show text in the errors row of the form.
242 * @param form
296 * @param form
243 * @param text
297 * @param text
244 */
298 */
245 function showAsErrors(form, text) {
299 function showAsErrors(form, text) {
246 form.children('.form-errors').remove();
300 form.children('.form-errors').remove();
247
301
248 if (text.length > 0) {
302 if (text.length > 0) {
249 var errorList = $('<div class="form-errors">' + text
303 var errorList = $('<div class="form-errors">' + text
250 + '<div>');
304 + '<div>');
251 errorList.appendTo(form);
305 errorList.appendTo(form);
252 }
306 }
253 }
307 }
254
308
255 /**
309 /**
256 * Run js methods that are usually run on the document, on the new post
310 * Run js methods that are usually run on the document, on the new post
257 */
311 */
258 function processNewPost(post) {
312 function processNewPost(post) {
259 addRefLinkPreview(post[0]);
313 addRefLinkPreview(post[0]);
260 highlightCode(post);
314 highlightCode(post);
261 blink(post);
315 blink(post);
262 }
316 }
263
317
264 $(document).ready(function(){
318 $(document).ready(function(){
265 if ('WebSocket' in window) {
319 if ('WebSocket' in window) {
266 if (initAutoupdate()) {
320 if (initAutoupdate()) {
267 // Post form data over AJAX
321 // Post form data over AJAX
268 var threadId = $('div.thread').children('.post').first().attr('id');
322 var threadId = $('div.thread').children('.post').first().attr('id');
269
323
270 var form = $('#form');
324 var form = $('#form');
271
325
272 var options = {
326 var options = {
273 beforeSubmit: function(arr, $form, options) {
327 beforeSubmit: function(arr, $form, options) {
274 showAsErrors($('form'), gettext('Sending message...'));
328 showAsErrors($('form'), gettext('Sending message...'));
275 },
329 },
276 success: updateOnPost,
330 success: updateOnPost,
277 url: '/api/add_post/' + threadId + '/'
331 url: '/api/add_post/' + threadId + '/'
278 };
332 };
279
333
280 form.ajaxForm(options);
334 form.ajaxForm(options);
281
335
282 resetForm(form);
336 resetForm(form);
283 }
337 }
284 }
338 }
285 });
339 });
@@ -1,159 +1,157 b''
1 from django.core.urlresolvers import reverse
1 from django.core.urlresolvers import reverse
2 from django.db import transaction
2 from django.db import transaction
3 from django.http import Http404
3 from django.http import Http404
4 from django.shortcuts import get_object_or_404, render, redirect
4 from django.shortcuts import get_object_or_404, render, redirect
5 from django.views.decorators.cache import never_cache, cache_control
6 from django.views.generic.edit import FormMixin
5 from django.views.generic.edit import FormMixin
7
6
8 from boards import utils, settings
7 from boards import utils, settings
9 from boards.forms import PostForm, PlainErrorList
8 from boards.forms import PostForm, PlainErrorList
10 from boards.models import Post, Ban
9 from boards.models import Post, Ban
11 from boards.views.banned import BannedView
10 from boards.views.banned import BannedView
12 from boards.views.base import BaseBoardView, CONTEXT_FORM
11 from boards.views.base import BaseBoardView, CONTEXT_FORM
13 from boards.views.posting_mixin import PostMixin
12 from boards.views.posting_mixin import PostMixin
14 import neboard
13 import neboard
15
14
16 TEMPLATE_GALLERY = 'boards/thread_gallery.html'
15 TEMPLATE_GALLERY = 'boards/thread_gallery.html'
17 TEMPLATE_NORMAL = 'boards/thread.html'
16 TEMPLATE_NORMAL = 'boards/thread.html'
18
17
19 CONTEXT_POSTS = 'posts'
18 CONTEXT_POSTS = 'posts'
20 CONTEXT_OP = 'opening_post'
19 CONTEXT_OP = 'opening_post'
21 CONTEXT_BUMPLIMIT_PRG = 'bumplimit_progress'
20 CONTEXT_BUMPLIMIT_PRG = 'bumplimit_progress'
22 CONTEXT_POSTS_LEFT = 'posts_left'
21 CONTEXT_POSTS_LEFT = 'posts_left'
23 CONTEXT_LASTUPDATE = "last_update"
22 CONTEXT_LASTUPDATE = "last_update"
24 CONTEXT_MAX_REPLIES = 'max_replies'
23 CONTEXT_MAX_REPLIES = 'max_replies'
25 CONTEXT_THREAD = 'thread'
24 CONTEXT_THREAD = 'thread'
26 CONTEXT_BUMPABLE = 'bumpable'
25 CONTEXT_BUMPABLE = 'bumpable'
27 CONTEXT_WS_TOKEN = 'ws_token'
26 CONTEXT_WS_TOKEN = 'ws_token'
28 CONTEXT_WS_PROJECT = 'ws_project'
27 CONTEXT_WS_PROJECT = 'ws_project'
29 CONTEXT_WS_HOST = 'ws_host'
28 CONTEXT_WS_HOST = 'ws_host'
30 CONTEXT_WS_PORT = 'ws_port'
29 CONTEXT_WS_PORT = 'ws_port'
31
30
32 FORM_TITLE = 'title'
31 FORM_TITLE = 'title'
33 FORM_TEXT = 'text'
32 FORM_TEXT = 'text'
34 FORM_IMAGE = 'image'
33 FORM_IMAGE = 'image'
35
34
36 MODE_GALLERY = 'gallery'
35 MODE_GALLERY = 'gallery'
37 MODE_NORMAL = 'normal'
36 MODE_NORMAL = 'normal'
38
37
39
38
40 class ThreadView(BaseBoardView, PostMixin, FormMixin):
39 class ThreadView(BaseBoardView, PostMixin, FormMixin):
41
40
42 @cache_control(no_cache=True)
43 def get(self, request, post_id, mode=MODE_NORMAL, form=None):
41 def get(self, request, post_id, mode=MODE_NORMAL, form=None):
44 try:
42 try:
45 opening_post = Post.objects.filter(id=post_id).only('thread_new')[0]
43 opening_post = Post.objects.filter(id=post_id).only('thread_new')[0]
46 except IndexError:
44 except IndexError:
47 raise Http404
45 raise Http404
48
46
49 # If this is not OP, don't show it as it is
47 # If this is not OP, don't show it as it is
50 if not opening_post or not opening_post.is_opening():
48 if not opening_post or not opening_post.is_opening():
51 raise Http404
49 raise Http404
52
50
53 if not form:
51 if not form:
54 form = PostForm(error_class=PlainErrorList)
52 form = PostForm(error_class=PlainErrorList)
55
53
56 thread_to_show = opening_post.get_thread()
54 thread_to_show = opening_post.get_thread()
57
55
58 context = self.get_context_data(request=request)
56 context = self.get_context_data(request=request)
59
57
60 context[CONTEXT_FORM] = form
58 context[CONTEXT_FORM] = form
61 context[CONTEXT_LASTUPDATE] = str(utils.datetime_to_epoch(
59 context[CONTEXT_LASTUPDATE] = str(utils.datetime_to_epoch(
62 thread_to_show.last_edit_time))
60 thread_to_show.last_edit_time))
63 context[CONTEXT_THREAD] = thread_to_show
61 context[CONTEXT_THREAD] = thread_to_show
64 context[CONTEXT_MAX_REPLIES] = settings.MAX_POSTS_PER_THREAD
62 context[CONTEXT_MAX_REPLIES] = settings.MAX_POSTS_PER_THREAD
65
63
66 if settings.WEBSOCKETS_ENABLED:
64 if settings.WEBSOCKETS_ENABLED:
67 context[CONTEXT_WS_TOKEN] = utils.get_websocket_token(
65 context[CONTEXT_WS_TOKEN] = utils.get_websocket_token(
68 timestamp=context[CONTEXT_LASTUPDATE])
66 timestamp=context[CONTEXT_LASTUPDATE])
69 context[CONTEXT_WS_PROJECT] = neboard.settings.CENTRIFUGE_PROJECT_ID
67 context[CONTEXT_WS_PROJECT] = neboard.settings.CENTRIFUGE_PROJECT_ID
70 context[CONTEXT_WS_HOST] = request.get_host().split(':')[0]
68 context[CONTEXT_WS_HOST] = request.get_host().split(':')[0]
71 context[CONTEXT_WS_PORT] = neboard.settings.CENTRIFUGE_PORT
69 context[CONTEXT_WS_PORT] = neboard.settings.CENTRIFUGE_PORT
72
70
73 # TODO Move this to subclasses: NormalThreadView, GalleryThreadView etc
71 # TODO Move this to subclasses: NormalThreadView, GalleryThreadView etc
74 if MODE_NORMAL == mode:
72 if MODE_NORMAL == mode:
75 bumpable = thread_to_show.can_bump()
73 bumpable = thread_to_show.can_bump()
76 context[CONTEXT_BUMPABLE] = bumpable
74 context[CONTEXT_BUMPABLE] = bumpable
77 if bumpable:
75 if bumpable:
78 left_posts = settings.MAX_POSTS_PER_THREAD \
76 left_posts = settings.MAX_POSTS_PER_THREAD \
79 - thread_to_show.get_reply_count()
77 - thread_to_show.get_reply_count()
80 context[CONTEXT_POSTS_LEFT] = left_posts
78 context[CONTEXT_POSTS_LEFT] = left_posts
81 context[CONTEXT_BUMPLIMIT_PRG] = str(
79 context[CONTEXT_BUMPLIMIT_PRG] = str(
82 float(left_posts) / settings.MAX_POSTS_PER_THREAD * 100)
80 float(left_posts) / settings.MAX_POSTS_PER_THREAD * 100)
83
81
84 context[CONTEXT_OP] = opening_post
82 context[CONTEXT_OP] = opening_post
85
83
86 document = TEMPLATE_NORMAL
84 document = TEMPLATE_NORMAL
87 elif MODE_GALLERY == mode:
85 elif MODE_GALLERY == mode:
88 context[CONTEXT_POSTS] = thread_to_show.get_replies_with_images(
86 context[CONTEXT_POSTS] = thread_to_show.get_replies_with_images(
89 view_fields_only=True)
87 view_fields_only=True)
90
88
91 document = TEMPLATE_GALLERY
89 document = TEMPLATE_GALLERY
92 else:
90 else:
93 raise Http404
91 raise Http404
94
92
95 # TODO Use dict here
93 # TODO Use dict here
96 return render(request, document, context_instance=context)
94 return render(request, document, context_instance=context)
97
95
98 def post(self, request, post_id, mode=MODE_NORMAL):
96 def post(self, request, post_id, mode=MODE_NORMAL):
99 opening_post = get_object_or_404(Post, id=post_id)
97 opening_post = get_object_or_404(Post, id=post_id)
100
98
101 # If this is not OP, don't show it as it is
99 # If this is not OP, don't show it as it is
102 if not opening_post.is_opening():
100 if not opening_post.is_opening():
103 raise Http404
101 raise Http404
104
102
105 if not opening_post.get_thread().archived:
103 if not opening_post.get_thread().archived:
106 form = PostForm(request.POST, request.FILES,
104 form = PostForm(request.POST, request.FILES,
107 error_class=PlainErrorList)
105 error_class=PlainErrorList)
108 form.session = request.session
106 form.session = request.session
109
107
110 if form.is_valid():
108 if form.is_valid():
111 return self.new_post(request, form, opening_post)
109 return self.new_post(request, form, opening_post)
112 if form.need_to_ban:
110 if form.need_to_ban:
113 # Ban user because he is suspected to be a bot
111 # Ban user because he is suspected to be a bot
114 self._ban_current_user(request)
112 self._ban_current_user(request)
115
113
116 return self.get(request, post_id, mode, form)
114 return self.get(request, post_id, mode, form)
117
115
118 @transaction.atomic
116 @transaction.atomic
119 def new_post(self, request, form, opening_post=None, html_response=True):
117 def new_post(self, request, form, opening_post=None, html_response=True):
120 """Add a new post (in thread or as a reply)."""
118 """Add a new post (in thread or as a reply)."""
121
119
122 ip = utils.get_client_ip(request)
120 ip = utils.get_client_ip(request)
123 is_banned = Ban.objects.filter(ip=ip).exists()
121 is_banned = Ban.objects.filter(ip=ip).exists()
124
122
125 if is_banned:
123 if is_banned:
126 if html_response:
124 if html_response:
127 return redirect(BannedView().as_view())
125 return redirect(BannedView().as_view())
128 else:
126 else:
129 return None
127 return None
130
128
131 data = form.cleaned_data
129 data = form.cleaned_data
132
130
133 title = data[FORM_TITLE]
131 title = data[FORM_TITLE]
134 text = data[FORM_TEXT]
132 text = data[FORM_TEXT]
135
133
136 text = self._remove_invalid_links(text)
134 text = self._remove_invalid_links(text)
137
135
138 if FORM_IMAGE in list(data.keys()):
136 if FORM_IMAGE in list(data.keys()):
139 image = data[FORM_IMAGE]
137 image = data[FORM_IMAGE]
140 else:
138 else:
141 image = None
139 image = None
142
140
143 tags = []
141 tags = []
144
142
145 post_thread = opening_post.get_thread()
143 post_thread = opening_post.get_thread()
146
144
147 post = Post.objects.create_post(title=title, text=text, image=image,
145 post = Post.objects.create_post(title=title, text=text, image=image,
148 thread=post_thread, ip=ip, tags=tags)
146 thread=post_thread, ip=ip, tags=tags)
149 post.send_to_websocket(request)
147 post.send_to_websocket(request)
150
148
151 thread_to_show = (opening_post.id if opening_post else post.id)
149 thread_to_show = (opening_post.id if opening_post else post.id)
152
150
153 if html_response:
151 if html_response:
154 if opening_post:
152 if opening_post:
155 return redirect(
153 return redirect(
156 reverse('thread', kwargs={'post_id': thread_to_show})
154 reverse('thread', kwargs={'post_id': thread_to_show})
157 + '#' + str(post.id))
155 + '#' + str(post.id))
158 else:
156 else:
159 return post
157 return post
General Comments 0
You need to be logged in to leave comments. Login now