##// END OF EJS Templates
Update thread by time, not post id. This will help with getting updated posts
neko259 -
r363:69d0a4bf thread_autoupdate
parent child Browse files
Show More
@@ -1,80 +1,81 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 function addGalleryPanel() {
26 function addGalleryPanel() {
27 var gallery = $('a[class="thumb"]').clone(true),
27 var gallery = $('a[class="thumb"]').clone(true),
28 normal = $('.post').clone(true);
28 normal = $('.post').clone(true);
29
29
30 $('.navigation_panel').filter(':first').after(
30 $('.navigation_panel').filter(':first').after(
31 '<div class="image-mode-tab" role="radiogroup" aria-label="Image mode2">' +
31 '<div class="image-mode-tab" role="radiogroup" aria-label="Image mode2">' +
32 '<label><input type="radio" class="image-mode-normal" name="image-mode" value="0" checked="checked"/>'+ gettext('Normal') +'</label>' +
32 '<label><input type="radio" class="image-mode-normal" name="image-mode" value="0" checked="checked"/>'+ gettext('Normal') +'</label>' +
33 '<label><input type="radio" class="image-mode-table" name="image-mode" value="1"/>'+ gettext('Gallery') +'</label>' +
33 '<label><input type="radio" class="image-mode-table" name="image-mode" value="1"/>'+ gettext('Gallery') +'</label>' +
34 '</div>'
34 '</div>'
35 );
35 );
36
36
37 $('input[name="image-mode"]').change(function() {
37 $('input[name="image-mode"]').change(function() {
38 //gallery mode
38 //gallery mode
39 if($(this).val() === '1') {
39 if($(this).val() === '1') {
40 $('.thread').replaceWith(
40 $('.thread').replaceWith(
41 $('<div id="posts-table"></div>').append(gallery)
41 $('<div id="posts-table"></div>').append(gallery)
42 );
42 );
43 }
43 }
44 //normal mode
44 //normal mode
45 else {
45 else {
46 $('#posts-table').replaceWith(
46 $('#posts-table').replaceWith(
47 $('<div class="thread"></div>').append(normal)
47 $('<div class="thread"></div>').append(normal)
48 );
48 );
49 }
49 }
50 });
50 });
51 }
51 }
52
52
53 function moveCaretToEnd(el) {
53 function moveCaretToEnd(el) {
54 if (typeof el.selectionStart == "number") {
54 if (typeof el.selectionStart == "number") {
55 el.selectionStart = el.selectionEnd = el.value.length;
55 el.selectionStart = el.selectionEnd = el.value.length;
56 } else if (typeof el.createTextRange != "undefined") {
56 } else if (typeof el.createTextRange != "undefined") {
57 el.focus();
57 el.focus();
58 var range = el.createTextRange();
58 var range = el.createTextRange();
59 range.collapse(false);
59 range.collapse(false);
60 range.select();
60 range.select();
61 }
61 }
62 }
62 }
63
63
64 function addQuickReply(postId) {
64 function addQuickReply(postId) {
65 var textToAdd = '>>' + postId + '\n\n';
65 var textToAdd = '>>' + postId + '\n\n';
66 var textAreaId = '#id_text';
66 var textAreaId = '#id_text';
67 $(textAreaId).val($(textAreaId).val()+ textToAdd);
67 $(textAreaId).val($(textAreaId).val()+ textToAdd);
68
68
69 var textarea = document.getElementById('id_text');
69 var textarea = document.getElementById('id_text');
70 $(textAreaId).focus();
70 $(textAreaId).focus();
71 moveCaretToEnd(textarea);
71 moveCaretToEnd(textarea);
72
72
73 $("html, body").animate({ scrollTop: $(textAreaId).offset().top }, "slow");
73 $("html, body").animate({ scrollTop: $(textAreaId).offset().top }, "slow");
74 }
74 }
75
75
76
76
77
77
78 $(document).ready(function(){
78 $(document).ready(function(){
79 addGalleryPanel();
79 addGalleryPanel();
80 initAutoupdate();
80 });
81 });
@@ -1,37 +1,63 b''
1 var THREAD_UPDATE_DELAY = 10000;
1 var THREAD_UPDATE_DELAY = 10000;
2
2
3 var loading = false;
3 var loading = false;
4 var lastUpdateTime = null;
5
6 function blink(node) {
7 var blinkCount = 2;
8 var blinkDelay = 250;
9
10 var nodeToAnimate = node;
11 for (var i = 0; i < blinkCount; i++) {
12 nodeToAnimate = nodeToAnimate.fadeOut(blinkDelay).fadeIn(blinkDelay);
13 }
14 }
4
15
5 function updateThread() {
16 function updateThread() {
6 if (loading) {
17 if (loading) {
7 return;
18 return;
8 }
19 }
9
20
10 loading = true;
21 loading = true;
11
22
12 var threadPosts = $('div.thread').children('.post');
23 var threadPosts = $('div.thread').children('.post');
13
24
14 var lastPost = threadPosts.last();
25 var lastPost = threadPosts.last();
15 var threadId = threadPosts.first().attr('id');
26 var threadId = threadPosts.first().attr('id');
16 var lastPostId = lastPost.attr('id');
17
27
18 var diffUrl = '/api/diff_thread/' + threadId + '/' + lastPostId + '/';
28 var diffUrl = '/api/diff_thread/' + threadId + '/' + lastUpdateTime + '/';
19 $.getJSON(diffUrl)
29 $.getJSON(diffUrl)
20 .success(function(data) {
30 .success(function(data) {
21 var addedPosts = data.added;
31 var addedPosts = data.added;
22
32
23 for (var i = 0; i < addedPosts.length; i++) {
33 for (var i = 0; i < addedPosts.length; i++) {
24 var postText = addedPosts[i];
34 var postText = addedPosts[i];
25
35
26 var post = $(postText).hide();
36 var post = $(postText);
27 post.appendTo(lastPost.parent()).show('slow');
37 post.appendTo(lastPost.parent());
28 addRefLinkPreview(post[0]);
38 addRefLinkPreview(post[0]);
29
39
30 lastPost = post;
40 lastPost = post;
41 blink(post);
42
31 }
43 }
32
44
45 // TODO Process updated and deleted posts
46
47 lastUpdateTime = data.last_update;
48 loading = false;
49 })
50 .error(function(data) {
51 // TODO Show error message that server is unavailable?
52
33 loading = false;
53 loading = false;
34 });
54 });
35 }
55 }
36
56
57 function initAutoupdate() {
58 loading = false;
59
60 lastUpdateTime = $('.metapanel').attr('data-last-update');
61
37 setInterval(updateThread, THREAD_UPDATE_DELAY);
62 setInterval(updateThread, THREAD_UPDATE_DELAY);
63 }
@@ -1,164 +1,164 b''
1 {% extends "boards/base.html" %}
1 {% extends "boards/base.html" %}
2
2
3 {% load i18n %}
3 {% load i18n %}
4 {% load markup %}
4 {% load markup %}
5 {% load cache %}
5 {% load cache %}
6 {% load static from staticfiles %}
6 {% load static from staticfiles %}
7 {% load board %}
7 {% load board %}
8
8
9 {% block head %}
9 {% block head %}
10 <title>Neboard - {{ posts.0.get_title }}</title>
10 <title>Neboard - {{ posts.0.get_title }}</title>
11 {% endblock %}
11 {% endblock %}
12
12
13 {% block content %}
13 {% block content %}
14 {% get_current_language as LANGUAGE_CODE %}
14 {% get_current_language as LANGUAGE_CODE %}
15
15
16 <script src="{% static 'js/thread_update.js' %}"></script>
16 <script src="{% static 'js/thread_update.js' %}"></script>
17 <script src="{% static 'js/thread.js' %}"></script>
17 <script src="{% static 'js/thread.js' %}"></script>
18
18
19 {% if posts %}
19 {% if posts %}
20 {% cache 600 thread_view posts.0.last_edit_time moderator LANGUAGE_CODE %}
20 {% cache 600 thread_view posts.0.last_edit_time moderator LANGUAGE_CODE %}
21 {% if bumpable %}
21 {% if bumpable %}
22 <div class="bar-bg">
22 <div class="bar-bg">
23 <div class="bar-value" style="width:{{ bumplimit_progress }}%">
23 <div class="bar-value" style="width:{{ bumplimit_progress }}%">
24 </div>
24 </div>
25 <div class="bar-text">
25 <div class="bar-text">
26 {{ posts_left }} {% trans 'posts to bumplimit' %}
26 {{ posts_left }} {% trans 'posts to bumplimit' %}
27 </div>
27 </div>
28 </div>
28 </div>
29 {% endif %}
29 {% endif %}
30 <div class="thread">
30 <div class="thread">
31 {% for post in posts %}
31 {% for post in posts %}
32 {% if bumpable %}
32 {% if bumpable %}
33 <div class="post" id="{{ post.id }}">
33 <div class="post" id="{{ post.id }}">
34 {% else %}
34 {% else %}
35 <div class="post dead_post" id="{{ post.id }}">
35 <div class="post dead_post" id="{{ post.id }}">
36 {% endif %}
36 {% endif %}
37 {% if post.image %}
37 {% if post.image %}
38 <div class="image">
38 <div class="image">
39 <a
39 <a
40 class="thumb"
40 class="thumb"
41 href="{{ post.image.url }}"><img
41 href="{{ post.image.url }}"><img
42 src="{{ post.image.url_200x150 }}"
42 src="{{ post.image.url_200x150 }}"
43 alt="{{ post.id }}"
43 alt="{{ post.id }}"
44 data-width="{{ post.image_width }}"
44 data-width="{{ post.image_width }}"
45 data-height="{{ post.image_height }}"/>
45 data-height="{{ post.image_height }}"/>
46 </a>
46 </a>
47 </div>
47 </div>
48 {% endif %}
48 {% endif %}
49 <div class="message">
49 <div class="message">
50 <div class="post-info">
50 <div class="post-info">
51 <span class="title">{{ post.title }}</span>
51 <span class="title">{{ post.title }}</span>
52 <a class="post_id" href="#{{ post.id }}">
52 <a class="post_id" href="#{{ post.id }}">
53 ({{ post.id }})</a>
53 ({{ post.id }})</a>
54 [{{ post.pub_time }}]
54 [{{ post.pub_time }}]
55 [<a href="#" onclick="javascript:addQuickReply('{{ post.id }}')
55 [<a href="#" onclick="javascript:addQuickReply('{{ post.id }}')
56 ; return false;">&gt;&gt;</a>]
56 ; return false;">&gt;&gt;</a>]
57
57
58 {% if moderator %}
58 {% if moderator %}
59 <span class="moderator_info">
59 <span class="moderator_info">
60 [<a href="{% url 'delete' post_id=post.id %}"
60 [<a href="{% url 'delete' post_id=post.id %}"
61 >{% trans 'Delete' %}</a>]
61 >{% trans 'Delete' %}</a>]
62 ({{ post.poster_ip }})
62 ({{ post.poster_ip }})
63 [<a href="{% url 'ban' post_id=post.id %}?next={{ request.path }}"
63 [<a href="{% url 'ban' post_id=post.id %}?next={{ request.path }}"
64 >{% trans 'Ban IP' %}</a>]
64 >{% trans 'Ban IP' %}</a>]
65 </span>
65 </span>
66 {% endif %}
66 {% endif %}
67 </div>
67 </div>
68 {% autoescape off %}
68 {% autoescape off %}
69 {{ post.text.rendered }}
69 {{ post.text.rendered }}
70 {% endautoescape %}
70 {% endautoescape %}
71 {% if post.is_referenced %}
71 {% if post.is_referenced %}
72 <div class="refmap">
72 <div class="refmap">
73 {% trans "Replies" %}:
73 {% trans "Replies" %}:
74 {% for ref_post in post.get_sorted_referenced_posts %}
74 {% for ref_post in post.get_sorted_referenced_posts %}
75 <a href="{% post_url ref_post.id %}">&gt;&gt;{{ ref_post.id }}</a
75 <a href="{% post_url ref_post.id %}">&gt;&gt;{{ ref_post.id }}</a
76 >{% if not forloop.last %},{% endif %}
76 >{% if not forloop.last %},{% endif %}
77 {% endfor %}
77 {% endfor %}
78 </div>
78 </div>
79 {% endif %}
79 {% endif %}
80 </div>
80 </div>
81 {% if forloop.first %}
81 {% if forloop.first %}
82 <div class="metadata">
82 <div class="metadata">
83 <span class="tags">
83 <span class="tags">
84 {% for tag in post.get_tags %}
84 {% for tag in post.get_tags %}
85 <a class="tag" href="{% url 'tag' tag.name %}">
85 <a class="tag" href="{% url 'tag' tag.name %}">
86 #{{ tag.name }}</a
86 #{{ tag.name }}</a
87 >{% if not forloop.last %},{% endif %}
87 >{% if not forloop.last %},{% endif %}
88 {% endfor %}
88 {% endfor %}
89 </span>
89 </span>
90 </div>
90 </div>
91 {% endif %}
91 {% endif %}
92 </div>
92 </div>
93 {% endfor %}
93 {% endfor %}
94 </div>
94 </div>
95 {% endcache %}
95 {% endcache %}
96 {% endif %}
96 {% endif %}
97
97
98 <form id="form" enctype="multipart/form-data" method="post"
98 <form id="form" enctype="multipart/form-data" method="post"
99 >{% csrf_token %}
99 >{% csrf_token %}
100 <div class="post-form-w">
100 <div class="post-form-w">
101 <div class="form-title">{% trans "Reply to thread" %} #{{ posts.0.id }}</div>
101 <div class="form-title">{% trans "Reply to thread" %} #{{ posts.0.id }}</div>
102 <div class="post-form">
102 <div class="post-form">
103 <div class="form-row">
103 <div class="form-row">
104 <div class="form-label">{% trans 'Title' %}</div>
104 <div class="form-label">{% trans 'Title' %}</div>
105 <div class="form-input">{{ form.title }}</div>
105 <div class="form-input">{{ form.title }}</div>
106 <div class="form-errors">{{ form.title.errors }}</div>
106 <div class="form-errors">{{ form.title.errors }}</div>
107 </div>
107 </div>
108 <div class="form-row">
108 <div class="form-row">
109 <div class="form-label">{% trans 'Formatting' %}</div>
109 <div class="form-label">{% trans 'Formatting' %}</div>
110 <div class="form-input" id="mark_panel">
110 <div class="form-input" id="mark_panel">
111 <span class="mark_btn" id="quote"><span class="quote">&gt;{% trans 'quote' %}</span></span>
111 <span class="mark_btn" id="quote"><span class="quote">&gt;{% trans 'quote' %}</span></span>
112 <span class="mark_btn" id="italic"><i>{% trans 'italic' %}</i></span>
112 <span class="mark_btn" id="italic"><i>{% trans 'italic' %}</i></span>
113 <span class="mark_btn" id="bold"><b>{% trans 'bold' %}</b></span>
113 <span class="mark_btn" id="bold"><b>{% trans 'bold' %}</b></span>
114 <span class="mark_btn" id="spoiler"><span class="spoiler">{% trans 'spoiler' %}</span></span>
114 <span class="mark_btn" id="spoiler"><span class="spoiler">{% trans 'spoiler' %}</span></span>
115 <span class="mark_btn" id="comment"><span class="comment">// {% trans 'comment' %}</span></span>
115 <span class="mark_btn" id="comment"><span class="comment">// {% trans 'comment' %}</span></span>
116 </div>
116 </div>
117 </div>
117 </div>
118 <div class="form-row">
118 <div class="form-row">
119 <div class="form-label">{% trans 'Text' %}</div>
119 <div class="form-label">{% trans 'Text' %}</div>
120 <div class="form-input">{{ form.text }}</div>
120 <div class="form-input">{{ form.text }}</div>
121 <div class="form-errors">{{ form.text.errors }}</div>
121 <div class="form-errors">{{ form.text.errors }}</div>
122 </div>
122 </div>
123 <div class="form-row">
123 <div class="form-row">
124 <div class="form-label">{% trans 'Image' %}</div>
124 <div class="form-label">{% trans 'Image' %}</div>
125 <div class="form-input">{{ form.image }}</div>
125 <div class="form-input">{{ form.image }}</div>
126 <div class="form-errors">{{ form.image.errors }}</div>
126 <div class="form-errors">{{ form.image.errors }}</div>
127 </div>
127 </div>
128 <div class="form-row form-email">
128 <div class="form-row form-email">
129 <div class="form-label">{% trans 'e-mail' %}</div>
129 <div class="form-label">{% trans 'e-mail' %}</div>
130 <div class="form-input">{{ form.email }}</div>
130 <div class="form-input">{{ form.email }}</div>
131 <div class="form-errors">{{ form.email.errors }}</div>
131 <div class="form-errors">{{ form.email.errors }}</div>
132 </div>
132 </div>
133 <div class="form-row">
133 <div class="form-row">
134 {{ form.captcha }}
134 {{ form.captcha }}
135 <div class="form-errors">{{ form.captcha.errors }}</div>
135 <div class="form-errors">{{ form.captcha.errors }}</div>
136 </div>
136 </div>
137 <div class="form-row">
137 <div class="form-row">
138 <div class="form-errors">{{ form.other.errors }}</div>
138 <div class="form-errors">{{ form.other.errors }}</div>
139 </div>
139 </div>
140 </div>
140 </div>
141
141
142 <div class="form-submit"><input type="submit"
142 <div class="form-submit"><input type="submit"
143 value="{% trans "Post" %}"/></div>
143 value="{% trans "Post" %}"/></div>
144 <div><a href="{% url "staticpage" name="help" %}">
144 <div><a href="{% url "staticpage" name="help" %}">
145 {% trans 'Text syntax' %}</a></div>
145 {% trans 'Text syntax' %}</a></div>
146 </div>
146 </div>
147 </form>
147 </form>
148
148
149 {% endblock %}
149 {% endblock %}
150
150
151 {% block metapanel %}
151 {% block metapanel %}
152
152
153 {% get_current_language as LANGUAGE_CODE %}
153 {% get_current_language as LANGUAGE_CODE %}
154
154
155 <span class="metapanel">
155 <span class="metapanel" data-last-update="{{ last_update }}">
156 {% cache 600 thread_meta posts.0.last_edit_time moderator LANGUAGE_CODE %}
156 {% cache 600 thread_meta posts.0.last_edit_time moderator LANGUAGE_CODE %}
157 {{ posts.0.get_reply_count }} {% trans 'replies' %},
157 {{ posts.0.get_reply_count }} {% trans 'replies' %},
158 {{ posts.0.get_images_count }} {% trans 'images' %}.
158 {{ posts.0.get_images_count }} {% trans 'images' %}.
159 {% trans 'Last update: ' %}{{ posts.0.last_edit_time }}
159 {% trans 'Last update: ' %}{{ posts.0.last_edit_time }}
160 [<a href="rss/">RSS</a>]
160 [<a href="rss/">RSS</a>]
161 {% endcache %}
161 {% endcache %}
162 </span>
162 </span>
163
163
164 {% endblock %}
164 {% endblock %}
@@ -1,58 +1,58 b''
1 from django.conf.urls import patterns, url, include
1 from django.conf.urls import patterns, url, include
2 from boards import views
2 from boards import views
3 from boards.rss import AllThreadsFeed, TagThreadsFeed, ThreadPostsFeed
3 from boards.rss import AllThreadsFeed, TagThreadsFeed, ThreadPostsFeed
4
4
5 js_info_dict = {
5 js_info_dict = {
6 'packages': ('boards',),
6 'packages': ('boards',),
7 }
7 }
8
8
9 urlpatterns = patterns('',
9 urlpatterns = patterns('',
10
10
11 # /boards/
11 # /boards/
12 url(r'^$', views.index, name='index'),
12 url(r'^$', views.index, name='index'),
13 # /boards/page/
13 # /boards/page/
14 url(r'^page/(?P<page>\w+)/$', views.index, name='index'),
14 url(r'^page/(?P<page>\w+)/$', views.index, name='index'),
15
15
16 # login page
16 # login page
17 url(r'^login/$', views.login, name='login'),
17 url(r'^login/$', views.login, name='login'),
18
18
19 # /boards/tag/tag_name/
19 # /boards/tag/tag_name/
20 url(r'^tag/(?P<tag_name>\w+)/$', views.tag, name='tag'),
20 url(r'^tag/(?P<tag_name>\w+)/$', views.tag, name='tag'),
21 # /boards/tag/tag_id/page/
21 # /boards/tag/tag_id/page/
22 url(r'^tag/(?P<tag_name>\w+)/page/(?P<page>\w+)/$', views.tag, name='tag'),
22 url(r'^tag/(?P<tag_name>\w+)/page/(?P<page>\w+)/$', views.tag, name='tag'),
23
23
24 # /boards/tag/tag_name/unsubscribe/
24 # /boards/tag/tag_name/unsubscribe/
25 url(r'^tag/(?P<tag_name>\w+)/subscribe/$', views.tag_subscribe,
25 url(r'^tag/(?P<tag_name>\w+)/subscribe/$', views.tag_subscribe,
26 name='tag_subscribe'),
26 name='tag_subscribe'),
27 # /boards/tag/tag_name/unsubscribe/
27 # /boards/tag/tag_name/unsubscribe/
28 url(r'^tag/(?P<tag_name>\w+)/unsubscribe/$', views.tag_unsubscribe,
28 url(r'^tag/(?P<tag_name>\w+)/unsubscribe/$', views.tag_unsubscribe,
29 name='tag_unsubscribe'),
29 name='tag_unsubscribe'),
30
30
31 # /boards/thread/
31 # /boards/thread/
32 url(r'^thread/(?P<post_id>\w+)/$', views.thread, name='thread'),
32 url(r'^thread/(?P<post_id>\w+)/$', views.thread, name='thread'),
33 url(r'^settings/$', views.settings, name='settings'),
33 url(r'^settings/$', views.settings, name='settings'),
34 url(r'^tags/$', views.all_tags, name='tags'),
34 url(r'^tags/$', views.all_tags, name='tags'),
35 url(r'^captcha/', include('captcha.urls')),
35 url(r'^captcha/', include('captcha.urls')),
36 url(r'^jump/(?P<post_id>\w+)/$', views.jump_to_post, name='jumper'),
36 url(r'^jump/(?P<post_id>\w+)/$', views.jump_to_post, name='jumper'),
37 url(r'^authors/$', views.authors, name='authors'),
37 url(r'^authors/$', views.authors, name='authors'),
38 url(r'^delete/(?P<post_id>\w+)/$', views.delete, name='delete'),
38 url(r'^delete/(?P<post_id>\w+)/$', views.delete, name='delete'),
39 url(r'^ban/(?P<post_id>\w+)/$', views.ban, name='ban'),
39 url(r'^ban/(?P<post_id>\w+)/$', views.ban, name='ban'),
40
40
41 url(r'^banned/$', views.you_are_banned, name='banned'),
41 url(r'^banned/$', views.you_are_banned, name='banned'),
42 url(r'^staticpage/(?P<name>\w+)/$', views.static_page, name='staticpage'),
42 url(r'^staticpage/(?P<name>\w+)/$', views.static_page, name='staticpage'),
43
43
44 # RSS feeds
44 # RSS feeds
45 url(r'^rss/$', AllThreadsFeed()),
45 url(r'^rss/$', AllThreadsFeed()),
46 url(r'^page/(?P<page>\w+)/rss/$', AllThreadsFeed()),
46 url(r'^page/(?P<page>\w+)/rss/$', AllThreadsFeed()),
47 url(r'^tag/(?P<tag_name>\w+)/rss/$', TagThreadsFeed()),
47 url(r'^tag/(?P<tag_name>\w+)/rss/$', TagThreadsFeed()),
48 url(r'^tag/(?P<tag_name>\w+)/page/(?P<page>\w+)/rss/$', TagThreadsFeed()),
48 url(r'^tag/(?P<tag_name>\w+)/page/(?P<page>\w+)/rss/$', TagThreadsFeed()),
49 url(r'^thread/(?P<post_id>\w+)/rss/$', ThreadPostsFeed()),
49 url(r'^thread/(?P<post_id>\w+)/rss/$', ThreadPostsFeed()),
50
50
51 # i18n
51 # i18n
52 url(r'^jsi18n/$', 'django.views.i18n.javascript_catalog', js_info_dict),
52 url(r'^jsi18n/$', 'django.views.i18n.javascript_catalog', js_info_dict),
53
53
54 # API
54 # API
55 url(r'^api/post/(?P<post_id>\w+)/$', views.get_post, name="get_post"),
55 url(r'^api/post/(?P<post_id>\w+)/$', views.get_post, name="get_post"),
56 url(r'^api/diff_thread/(?P<thread_id>\w+)/(?P<last_post_id>\w+)/$',
56 url(r'^api/diff_thread/(?P<thread_id>\w+)/(?P<last_update_time>\w+)/$',
57 views.api_get_threaddiff, name="get_thread_diff"),
57 views.api_get_threaddiff, name="get_thread_diff"),
58 )
58 )
@@ -1,536 +1,544 b''
1 import hashlib
1 import hashlib
2 import json
2 import json
3 import string
3 import string
4 import time
5
6 from datetime import datetime
7
4 from django.core import serializers
8 from django.core import serializers
5 from django.core.urlresolvers import reverse
9 from django.core.urlresolvers import reverse
6 from django.http import HttpResponseRedirect
10 from django.http import HttpResponseRedirect
7 from django.http.response import HttpResponse
11 from django.http.response import HttpResponse
8 from django.template import RequestContext
12 from django.template import RequestContext
9 from django.shortcuts import render, redirect, get_object_or_404
13 from django.shortcuts import render, redirect, get_object_or_404
10 from django.utils import timezone
14 from django.utils import timezone
11 from django.db import transaction
15 from django.db import transaction
12
16
13 from boards import forms
17 from boards import forms
14 import boards
18 import boards
15 from boards import utils
19 from boards import utils
16 from boards.forms import ThreadForm, PostForm, SettingsForm, PlainErrorList, \
20 from boards.forms import ThreadForm, PostForm, SettingsForm, PlainErrorList, \
17 ThreadCaptchaForm, PostCaptchaForm, LoginForm, ModeratorSettingsForm
21 ThreadCaptchaForm, PostCaptchaForm, LoginForm, ModeratorSettingsForm
18
22
19 from boards.models import Post, Tag, Ban, User, RANK_USER, SETTING_MODERATE, \
23 from boards.models import Post, Tag, Ban, User, RANK_USER, SETTING_MODERATE, \
20 REGEX_REPLY
24 REGEX_REPLY
21 from boards import authors
25 from boards import authors
22 from boards.utils import get_client_ip
26 from boards.utils import get_client_ip
23 import neboard
27 import neboard
24 import re
28 import re
25
29
26 BAN_REASON_SPAM = 'Autoban: spam bot'
30 BAN_REASON_SPAM = 'Autoban: spam bot'
27
31
28
32
29 def index(request, page=0):
33 def index(request, page=0):
30 context = _init_default_context(request)
34 context = _init_default_context(request)
31
35
32 if utils.need_include_captcha(request):
36 if utils.need_include_captcha(request):
33 threadFormClass = ThreadCaptchaForm
37 threadFormClass = ThreadCaptchaForm
34 kwargs = {'request': request}
38 kwargs = {'request': request}
35 else:
39 else:
36 threadFormClass = ThreadForm
40 threadFormClass = ThreadForm
37 kwargs = {}
41 kwargs = {}
38
42
39 if request.method == 'POST':
43 if request.method == 'POST':
40 form = threadFormClass(request.POST, request.FILES,
44 form = threadFormClass(request.POST, request.FILES,
41 error_class=PlainErrorList, **kwargs)
45 error_class=PlainErrorList, **kwargs)
42 form.session = request.session
46 form.session = request.session
43
47
44 if form.is_valid():
48 if form.is_valid():
45 return _new_post(request, form)
49 return _new_post(request, form)
46 if form.need_to_ban:
50 if form.need_to_ban:
47 # Ban user because he is suspected to be a bot
51 # Ban user because he is suspected to be a bot
48 _ban_current_user(request)
52 _ban_current_user(request)
49 else:
53 else:
50 form = threadFormClass(error_class=PlainErrorList, **kwargs)
54 form = threadFormClass(error_class=PlainErrorList, **kwargs)
51
55
52 threads = []
56 threads = []
53 for thread in Post.objects.get_threads(page=int(page)):
57 for thread in Post.objects.get_threads(page=int(page)):
54 threads.append({
58 threads.append({
55 'thread': thread,
59 'thread': thread,
56 'bumpable': thread.can_bump(),
60 'bumpable': thread.can_bump(),
57 'last_replies': thread.get_last_replies(),
61 'last_replies': thread.get_last_replies(),
58 })
62 })
59
63
60 # TODO Make this generic for tag and threads list pages
64 # TODO Make this generic for tag and threads list pages
61 context['threads'] = None if len(threads) == 0 else threads
65 context['threads'] = None if len(threads) == 0 else threads
62 context['form'] = form
66 context['form'] = form
63
67
64 page_count = Post.objects.get_thread_page_count()
68 page_count = Post.objects.get_thread_page_count()
65 context['pages'] = range(page_count)
69 context['pages'] = range(page_count)
66 page = int(page)
70 page = int(page)
67 if page < page_count - 1:
71 if page < page_count - 1:
68 context['next_page'] = str(page + 1)
72 context['next_page'] = str(page + 1)
69 if page > 0:
73 if page > 0:
70 context['prev_page'] = str(page - 1)
74 context['prev_page'] = str(page - 1)
71
75
72 return render(request, 'boards/posting_general.html',
76 return render(request, 'boards/posting_general.html',
73 context)
77 context)
74
78
75
79
76 @transaction.commit_on_success
80 @transaction.commit_on_success
77 def _new_post(request, form, thread_id=boards.models.NO_PARENT):
81 def _new_post(request, form, thread_id=boards.models.NO_PARENT):
78 """Add a new post (in thread or as a reply)."""
82 """Add a new post (in thread or as a reply)."""
79
83
80 ip = get_client_ip(request)
84 ip = get_client_ip(request)
81 is_banned = Ban.objects.filter(ip=ip).exists()
85 is_banned = Ban.objects.filter(ip=ip).exists()
82
86
83 if is_banned:
87 if is_banned:
84 return redirect(you_are_banned)
88 return redirect(you_are_banned)
85
89
86 data = form.cleaned_data
90 data = form.cleaned_data
87
91
88 title = data['title']
92 title = data['title']
89 text = data['text']
93 text = data['text']
90
94
91 text = _remove_invalid_links(text)
95 text = _remove_invalid_links(text)
92
96
93 if 'image' in data.keys():
97 if 'image' in data.keys():
94 image = data['image']
98 image = data['image']
95 else:
99 else:
96 image = None
100 image = None
97
101
98 tags = []
102 tags = []
99
103
100 new_thread = thread_id == boards.models.NO_PARENT
104 new_thread = thread_id == boards.models.NO_PARENT
101 if new_thread:
105 if new_thread:
102 tag_strings = data['tags']
106 tag_strings = data['tags']
103
107
104 if tag_strings:
108 if tag_strings:
105 tag_strings = tag_strings.split(' ')
109 tag_strings = tag_strings.split(' ')
106 for tag_name in tag_strings:
110 for tag_name in tag_strings:
107 tag_name = string.lower(tag_name.strip())
111 tag_name = string.lower(tag_name.strip())
108 if len(tag_name) > 0:
112 if len(tag_name) > 0:
109 tag, created = Tag.objects.get_or_create(name=tag_name)
113 tag, created = Tag.objects.get_or_create(name=tag_name)
110 tags.append(tag)
114 tags.append(tag)
111
115
112 linked_tags = tag.get_linked_tags()
116 linked_tags = tag.get_linked_tags()
113 if len(linked_tags) > 0:
117 if len(linked_tags) > 0:
114 tags.extend(linked_tags)
118 tags.extend(linked_tags)
115
119
116 op = None if thread_id == boards.models.NO_PARENT else \
120 op = None if thread_id == boards.models.NO_PARENT else \
117 get_object_or_404(Post, id=thread_id)
121 get_object_or_404(Post, id=thread_id)
118 post = Post.objects.create_post(title=title, text=text, ip=ip,
122 post = Post.objects.create_post(title=title, text=text, ip=ip,
119 thread=op, image=image,
123 thread=op, image=image,
120 tags=tags, user=_get_user(request))
124 tags=tags, user=_get_user(request))
121
125
122 thread_to_show = (post.id if new_thread else thread_id)
126 thread_to_show = (post.id if new_thread else thread_id)
123
127
124 if new_thread:
128 if new_thread:
125 return redirect(thread, post_id=thread_to_show)
129 return redirect(thread, post_id=thread_to_show)
126 else:
130 else:
127 return redirect(reverse(thread, kwargs={'post_id': thread_to_show}) +
131 return redirect(reverse(thread, kwargs={'post_id': thread_to_show}) +
128 '#' + str(post.id))
132 '#' + str(post.id))
129
133
130
134
131 def tag(request, tag_name, page=0):
135 def tag(request, tag_name, page=0):
132 """
136 """
133 Get all tag threads. Threads are split in pages, so some page is
137 Get all tag threads. Threads are split in pages, so some page is
134 requested. Default page is 0.
138 requested. Default page is 0.
135 """
139 """
136
140
137 tag = get_object_or_404(Tag, name=tag_name)
141 tag = get_object_or_404(Tag, name=tag_name)
138 threads = []
142 threads = []
139 for thread in Post.objects.get_threads(tag=tag, page=int(page)):
143 for thread in Post.objects.get_threads(tag=tag, page=int(page)):
140 threads.append({
144 threads.append({
141 'thread': thread,
145 'thread': thread,
142 'bumpable': thread.can_bump(),
146 'bumpable': thread.can_bump(),
143 'last_replies': thread.get_last_replies(),
147 'last_replies': thread.get_last_replies(),
144 })
148 })
145
149
146 if request.method == 'POST':
150 if request.method == 'POST':
147 form = ThreadForm(request.POST, request.FILES,
151 form = ThreadForm(request.POST, request.FILES,
148 error_class=PlainErrorList)
152 error_class=PlainErrorList)
149 form.session = request.session
153 form.session = request.session
150
154
151 if form.is_valid():
155 if form.is_valid():
152 return _new_post(request, form)
156 return _new_post(request, form)
153 if form.need_to_ban:
157 if form.need_to_ban:
154 # Ban user because he is suspected to be a bot
158 # Ban user because he is suspected to be a bot
155 _ban_current_user(request)
159 _ban_current_user(request)
156 else:
160 else:
157 form = forms.ThreadForm(initial={'tags': tag_name},
161 form = forms.ThreadForm(initial={'tags': tag_name},
158 error_class=PlainErrorList)
162 error_class=PlainErrorList)
159
163
160 context = _init_default_context(request)
164 context = _init_default_context(request)
161 context['threads'] = None if len(threads) == 0 else threads
165 context['threads'] = None if len(threads) == 0 else threads
162 context['tag'] = tag
166 context['tag'] = tag
163
167
164 page_count = Post.objects.get_thread_page_count(tag=tag)
168 page_count = Post.objects.get_thread_page_count(tag=tag)
165 context['pages'] = range(page_count)
169 context['pages'] = range(page_count)
166 page = int(page)
170 page = int(page)
167 if page < page_count - 1:
171 if page < page_count - 1:
168 context['next_page'] = str(page + 1)
172 context['next_page'] = str(page + 1)
169 if page > 0:
173 if page > 0:
170 context['prev_page'] = str(page - 1)
174 context['prev_page'] = str(page - 1)
171
175
172 context['form'] = form
176 context['form'] = form
173
177
174 return render(request, 'boards/posting_general.html',
178 return render(request, 'boards/posting_general.html',
175 context)
179 context)
176
180
177
181
178 def thread(request, post_id):
182 def thread(request, post_id):
179 """Get all thread posts"""
183 """Get all thread posts"""
180
184
181 if utils.need_include_captcha(request):
185 if utils.need_include_captcha(request):
182 postFormClass = PostCaptchaForm
186 postFormClass = PostCaptchaForm
183 kwargs = {'request': request}
187 kwargs = {'request': request}
184 else:
188 else:
185 postFormClass = PostForm
189 postFormClass = PostForm
186 kwargs = {}
190 kwargs = {}
187
191
188 if request.method == 'POST':
192 if request.method == 'POST':
189 form = postFormClass(request.POST, request.FILES,
193 form = postFormClass(request.POST, request.FILES,
190 error_class=PlainErrorList, **kwargs)
194 error_class=PlainErrorList, **kwargs)
191 form.session = request.session
195 form.session = request.session
192
196
193 if form.is_valid():
197 if form.is_valid():
194 return _new_post(request, form, post_id)
198 return _new_post(request, form, post_id)
195 if form.need_to_ban:
199 if form.need_to_ban:
196 # Ban user because he is suspected to be a bot
200 # Ban user because he is suspected to be a bot
197 _ban_current_user(request)
201 _ban_current_user(request)
198 else:
202 else:
199 form = postFormClass(error_class=PlainErrorList, **kwargs)
203 form = postFormClass(error_class=PlainErrorList, **kwargs)
200
204
201 posts = Post.objects.get_thread(post_id)
205 posts = Post.objects.get_thread(post_id)
202
206
203 context = _init_default_context(request)
207 context = _init_default_context(request)
204
208
205 context['posts'] = posts
209 context['posts'] = posts
206 context['form'] = form
210 context['form'] = form
207 context['bumpable'] = posts[0].can_bump()
211 context['bumpable'] = posts[0].can_bump()
208 if context['bumpable']:
212 if context['bumpable']:
209 context['posts_left'] = neboard.settings.MAX_POSTS_PER_THREAD - len(
213 context['posts_left'] = neboard.settings.MAX_POSTS_PER_THREAD - len(
210 posts)
214 posts)
211 context['bumplimit_progress'] = str(
215 context['bumplimit_progress'] = str(
212 float(context['posts_left']) /
216 float(context['posts_left']) /
213 neboard.settings.MAX_POSTS_PER_THREAD * 100)
217 neboard.settings.MAX_POSTS_PER_THREAD * 100)
218 context["last_update"] = int(time.time() * 1000)
214
219
215 return render(request, 'boards/thread.html', context)
220 return render(request, 'boards/thread.html', context)
216
221
217
222
218 def login(request):
223 def login(request):
219 """Log in with user id"""
224 """Log in with user id"""
220
225
221 context = _init_default_context(request)
226 context = _init_default_context(request)
222
227
223 if request.method == 'POST':
228 if request.method == 'POST':
224 form = LoginForm(request.POST, request.FILES,
229 form = LoginForm(request.POST, request.FILES,
225 error_class=PlainErrorList)
230 error_class=PlainErrorList)
226 form.session = request.session
231 form.session = request.session
227
232
228 if form.is_valid():
233 if form.is_valid():
229 user = User.objects.get(user_id=form.cleaned_data['user_id'])
234 user = User.objects.get(user_id=form.cleaned_data['user_id'])
230 request.session['user_id'] = user.id
235 request.session['user_id'] = user.id
231 return redirect(index)
236 return redirect(index)
232
237
233 else:
238 else:
234 form = LoginForm()
239 form = LoginForm()
235
240
236 context['form'] = form
241 context['form'] = form
237
242
238 return render(request, 'boards/login.html', context)
243 return render(request, 'boards/login.html', context)
239
244
240
245
241 def settings(request):
246 def settings(request):
242 """User's settings"""
247 """User's settings"""
243
248
244 context = _init_default_context(request)
249 context = _init_default_context(request)
245 user = _get_user(request)
250 user = _get_user(request)
246 is_moderator = user.is_moderator()
251 is_moderator = user.is_moderator()
247
252
248 if request.method == 'POST':
253 if request.method == 'POST':
249 with transaction.commit_on_success():
254 with transaction.commit_on_success():
250 if is_moderator:
255 if is_moderator:
251 form = ModeratorSettingsForm(request.POST,
256 form = ModeratorSettingsForm(request.POST,
252 error_class=PlainErrorList)
257 error_class=PlainErrorList)
253 else:
258 else:
254 form = SettingsForm(request.POST, error_class=PlainErrorList)
259 form = SettingsForm(request.POST, error_class=PlainErrorList)
255
260
256 if form.is_valid():
261 if form.is_valid():
257 selected_theme = form.cleaned_data['theme']
262 selected_theme = form.cleaned_data['theme']
258
263
259 user.save_setting('theme', selected_theme)
264 user.save_setting('theme', selected_theme)
260
265
261 if is_moderator:
266 if is_moderator:
262 moderate = form.cleaned_data['moderate']
267 moderate = form.cleaned_data['moderate']
263 user.save_setting(SETTING_MODERATE, moderate)
268 user.save_setting(SETTING_MODERATE, moderate)
264
269
265 return redirect(settings)
270 return redirect(settings)
266 else:
271 else:
267 selected_theme = _get_theme(request)
272 selected_theme = _get_theme(request)
268
273
269 if is_moderator:
274 if is_moderator:
270 form = ModeratorSettingsForm(initial={'theme': selected_theme,
275 form = ModeratorSettingsForm(initial={'theme': selected_theme,
271 'moderate': context['moderator']},
276 'moderate': context['moderator']},
272 error_class=PlainErrorList)
277 error_class=PlainErrorList)
273 else:
278 else:
274 form = SettingsForm(initial={'theme': selected_theme},
279 form = SettingsForm(initial={'theme': selected_theme},
275 error_class=PlainErrorList)
280 error_class=PlainErrorList)
276
281
277 context['form'] = form
282 context['form'] = form
278
283
279 return render(request, 'boards/settings.html', context)
284 return render(request, 'boards/settings.html', context)
280
285
281
286
282 def all_tags(request):
287 def all_tags(request):
283 """All tags list"""
288 """All tags list"""
284
289
285 context = _init_default_context(request)
290 context = _init_default_context(request)
286 context['all_tags'] = Tag.objects.get_not_empty_tags()
291 context['all_tags'] = Tag.objects.get_not_empty_tags()
287
292
288 return render(request, 'boards/tags.html', context)
293 return render(request, 'boards/tags.html', context)
289
294
290
295
291 def jump_to_post(request, post_id):
296 def jump_to_post(request, post_id):
292 """Determine thread in which the requested post is and open it's page"""
297 """Determine thread in which the requested post is and open it's page"""
293
298
294 post = get_object_or_404(Post, id=post_id)
299 post = get_object_or_404(Post, id=post_id)
295
300
296 if not post.thread:
301 if not post.thread:
297 return redirect(thread, post_id=post.id)
302 return redirect(thread, post_id=post.id)
298 else:
303 else:
299 return redirect(reverse(thread, kwargs={'post_id': post.thread.id})
304 return redirect(reverse(thread, kwargs={'post_id': post.thread.id})
300 + '#' + str(post.id))
305 + '#' + str(post.id))
301
306
302
307
303 def authors(request):
308 def authors(request):
304 """Show authors list"""
309 """Show authors list"""
305
310
306 context = _init_default_context(request)
311 context = _init_default_context(request)
307 context['authors'] = boards.authors.authors
312 context['authors'] = boards.authors.authors
308
313
309 return render(request, 'boards/authors.html', context)
314 return render(request, 'boards/authors.html', context)
310
315
311
316
312 @transaction.commit_on_success
317 @transaction.commit_on_success
313 def delete(request, post_id):
318 def delete(request, post_id):
314 """Delete post"""
319 """Delete post"""
315
320
316 user = _get_user(request)
321 user = _get_user(request)
317 post = get_object_or_404(Post, id=post_id)
322 post = get_object_or_404(Post, id=post_id)
318
323
319 if user.is_moderator():
324 if user.is_moderator():
320 # TODO Show confirmation page before deletion
325 # TODO Show confirmation page before deletion
321 Post.objects.delete_post(post)
326 Post.objects.delete_post(post)
322
327
323 if not post.thread:
328 if not post.thread:
324 return _redirect_to_next(request)
329 return _redirect_to_next(request)
325 else:
330 else:
326 return redirect(thread, post_id=post.thread.id)
331 return redirect(thread, post_id=post.thread.id)
327
332
328
333
329 @transaction.commit_on_success
334 @transaction.commit_on_success
330 def ban(request, post_id):
335 def ban(request, post_id):
331 """Ban user"""
336 """Ban user"""
332
337
333 user = _get_user(request)
338 user = _get_user(request)
334 post = get_object_or_404(Post, id=post_id)
339 post = get_object_or_404(Post, id=post_id)
335
340
336 if user.is_moderator():
341 if user.is_moderator():
337 # TODO Show confirmation page before ban
342 # TODO Show confirmation page before ban
338 ban, created = Ban.objects.get_or_create(ip=post.poster_ip)
343 ban, created = Ban.objects.get_or_create(ip=post.poster_ip)
339 if created:
344 if created:
340 ban.reason = 'Banned for post ' + str(post_id)
345 ban.reason = 'Banned for post ' + str(post_id)
341 ban.save()
346 ban.save()
342
347
343 return _redirect_to_next(request)
348 return _redirect_to_next(request)
344
349
345
350
346 def you_are_banned(request):
351 def you_are_banned(request):
347 """Show the page that notifies that user is banned"""
352 """Show the page that notifies that user is banned"""
348
353
349 context = _init_default_context(request)
354 context = _init_default_context(request)
350
355
351 ban = get_object_or_404(Ban, ip=utils.get_client_ip(request))
356 ban = get_object_or_404(Ban, ip=utils.get_client_ip(request))
352 context['ban_reason'] = ban.reason
357 context['ban_reason'] = ban.reason
353 return render(request, 'boards/staticpages/banned.html', context)
358 return render(request, 'boards/staticpages/banned.html', context)
354
359
355
360
356 def page_404(request):
361 def page_404(request):
357 """Show page 404 (not found error)"""
362 """Show page 404 (not found error)"""
358
363
359 context = _init_default_context(request)
364 context = _init_default_context(request)
360 return render(request, 'boards/404.html', context)
365 return render(request, 'boards/404.html', context)
361
366
362
367
363 @transaction.commit_on_success
368 @transaction.commit_on_success
364 def tag_subscribe(request, tag_name):
369 def tag_subscribe(request, tag_name):
365 """Add tag to favorites"""
370 """Add tag to favorites"""
366
371
367 user = _get_user(request)
372 user = _get_user(request)
368 tag = get_object_or_404(Tag, name=tag_name)
373 tag = get_object_or_404(Tag, name=tag_name)
369
374
370 if not tag in user.fav_tags.all():
375 if not tag in user.fav_tags.all():
371 user.add_tag(tag)
376 user.add_tag(tag)
372
377
373 return _redirect_to_next(request)
378 return _redirect_to_next(request)
374
379
375
380
376 @transaction.commit_on_success
381 @transaction.commit_on_success
377 def tag_unsubscribe(request, tag_name):
382 def tag_unsubscribe(request, tag_name):
378 """Remove tag from favorites"""
383 """Remove tag from favorites"""
379
384
380 user = _get_user(request)
385 user = _get_user(request)
381 tag = get_object_or_404(Tag, name=tag_name)
386 tag = get_object_or_404(Tag, name=tag_name)
382
387
383 if tag in user.fav_tags.all():
388 if tag in user.fav_tags.all():
384 user.remove_tag(tag)
389 user.remove_tag(tag)
385
390
386 return _redirect_to_next(request)
391 return _redirect_to_next(request)
387
392
388
393
389 def static_page(request, name):
394 def static_page(request, name):
390 """Show a static page that needs only tags list and a CSS"""
395 """Show a static page that needs only tags list and a CSS"""
391
396
392 context = _init_default_context(request)
397 context = _init_default_context(request)
393 return render(request, 'boards/staticpages/' + name + '.html', context)
398 return render(request, 'boards/staticpages/' + name + '.html', context)
394
399
395
400
396 def api_get_post(request, post_id):
401 def api_get_post(request, post_id):
397 """
402 """
398 Get the JSON of a post. This can be
403 Get the JSON of a post. This can be
399 used as and API for external clients.
404 used as and API for external clients.
400 """
405 """
401
406
402 post = get_object_or_404(Post, id=post_id)
407 post = get_object_or_404(Post, id=post_id)
403
408
404 json = serializers.serialize("json", [post], fields=(
409 json = serializers.serialize("json", [post], fields=(
405 "pub_time", "_text_rendered", "title", "text", "image",
410 "pub_time", "_text_rendered", "title", "text", "image",
406 "image_width", "image_height", "replies", "tags"
411 "image_width", "image_height", "replies", "tags"
407 ))
412 ))
408
413
409 return HttpResponse(content=json)
414 return HttpResponse(content=json)
410
415
411
416
412 def api_get_threaddiff(request, thread_id, last_post_id):
417 def api_get_threaddiff(request, thread_id, last_update_time):
413 thread = get_object_or_404(Post, id=thread_id)
418 thread = get_object_or_404(Post, id=thread_id)
414 posts = Post.objects.filter(thread=thread, id__gt=last_post_id)
419
420 filter_time = datetime.fromtimestamp(float(last_update_time) / 1000)
421 added_posts = Post.objects.filter(thread=thread, pub_time__gt=filter_time)
415
422
416 json_data = {
423 json_data = {
417 'added': []
424 'added': [],
425 'last_update' : int(time.time() * 1000),
418 }
426 }
419 for post in posts:
427 for post in added_posts:
420 json_data['added'].append(get_post(request, post.id).content.strip())
428 json_data['added'].append(get_post(request, post.id).content.strip())
421
429
422 return HttpResponse(content=json.dumps(json_data))
430 return HttpResponse(content=json.dumps(json_data))
423
431
424
432
425 def get_post(request, post_id):
433 def get_post(request, post_id):
426 """Get the html of a post. Used for popups."""
434 """Get the html of a post. Used for popups."""
427
435
428 post = get_object_or_404(Post, id=post_id)
436 post = get_object_or_404(Post, id=post_id)
429 thread = post.thread
437 thread = post.thread
430
438
431 context = RequestContext(request)
439 context = RequestContext(request)
432 context["post"] = post
440 context["post"] = post
433 context["can_bump"] = thread.can_bump()
441 context["can_bump"] = thread.can_bump()
434
442
435 return render(request, 'boards/post.html', context)
443 return render(request, 'boards/post.html', context)
436
444
437
445
438 def _get_theme(request, user=None):
446 def _get_theme(request, user=None):
439 """Get user's CSS theme"""
447 """Get user's CSS theme"""
440
448
441 if not user:
449 if not user:
442 user = _get_user(request)
450 user = _get_user(request)
443 theme = user.get_setting('theme')
451 theme = user.get_setting('theme')
444 if not theme:
452 if not theme:
445 theme = neboard.settings.DEFAULT_THEME
453 theme = neboard.settings.DEFAULT_THEME
446
454
447 return theme
455 return theme
448
456
449
457
450 def _init_default_context(request):
458 def _init_default_context(request):
451 """Create context with default values that are used in most views"""
459 """Create context with default values that are used in most views"""
452
460
453 context = RequestContext(request)
461 context = RequestContext(request)
454
462
455 user = _get_user(request)
463 user = _get_user(request)
456 context['user'] = user
464 context['user'] = user
457 context['tags'] = user.get_sorted_fav_tags()
465 context['tags'] = user.get_sorted_fav_tags()
458
466
459 theme = _get_theme(request, user)
467 theme = _get_theme(request, user)
460 context['theme'] = theme
468 context['theme'] = theme
461 context['theme_css'] = 'css/' + theme + '/base_page.css'
469 context['theme_css'] = 'css/' + theme + '/base_page.css'
462
470
463 # This shows the moderator panel
471 # This shows the moderator panel
464 moderate = user.get_setting(SETTING_MODERATE)
472 moderate = user.get_setting(SETTING_MODERATE)
465 if moderate == 'True':
473 if moderate == 'True':
466 context['moderator'] = user.is_moderator()
474 context['moderator'] = user.is_moderator()
467 else:
475 else:
468 context['moderator'] = False
476 context['moderator'] = False
469
477
470 return context
478 return context
471
479
472
480
473 def _get_user(request):
481 def _get_user(request):
474 """
482 """
475 Get current user from the session. If the user does not exist, create
483 Get current user from the session. If the user does not exist, create
476 a new one.
484 a new one.
477 """
485 """
478
486
479 session = request.session
487 session = request.session
480 if not 'user_id' in session:
488 if not 'user_id' in session:
481 request.session.save()
489 request.session.save()
482
490
483 md5 = hashlib.md5()
491 md5 = hashlib.md5()
484 md5.update(session.session_key)
492 md5.update(session.session_key)
485 new_id = md5.hexdigest()
493 new_id = md5.hexdigest()
486
494
487 time_now = timezone.now()
495 time_now = timezone.now()
488 user = User.objects.create(user_id=new_id, rank=RANK_USER,
496 user = User.objects.create(user_id=new_id, rank=RANK_USER,
489 registration_time=time_now)
497 registration_time=time_now)
490
498
491 session['user_id'] = user.id
499 session['user_id'] = user.id
492 else:
500 else:
493 user = User.objects.get(id=session['user_id'])
501 user = User.objects.get(id=session['user_id'])
494
502
495 return user
503 return user
496
504
497
505
498 def _redirect_to_next(request):
506 def _redirect_to_next(request):
499 """
507 """
500 If a 'next' parameter was specified, redirect to the next page. This is
508 If a 'next' parameter was specified, redirect to the next page. This is
501 used when the user is required to return to some page after the current
509 used when the user is required to return to some page after the current
502 view has finished its work.
510 view has finished its work.
503 """
511 """
504
512
505 if 'next' in request.GET:
513 if 'next' in request.GET:
506 next_page = request.GET['next']
514 next_page = request.GET['next']
507 return HttpResponseRedirect(next_page)
515 return HttpResponseRedirect(next_page)
508 else:
516 else:
509 return redirect(index)
517 return redirect(index)
510
518
511
519
512 @transaction.commit_on_success
520 @transaction.commit_on_success
513 def _ban_current_user(request):
521 def _ban_current_user(request):
514 """Add current user to the IP ban list"""
522 """Add current user to the IP ban list"""
515
523
516 ip = utils.get_client_ip(request)
524 ip = utils.get_client_ip(request)
517 ban, created = Ban.objects.get_or_create(ip=ip)
525 ban, created = Ban.objects.get_or_create(ip=ip)
518 if created:
526 if created:
519 ban.can_read = False
527 ban.can_read = False
520 ban.reason = BAN_REASON_SPAM
528 ban.reason = BAN_REASON_SPAM
521 ban.save()
529 ban.save()
522
530
523
531
524 def _remove_invalid_links(text):
532 def _remove_invalid_links(text):
525 """
533 """
526 Replace invalid links in posts so that they won't be parsed.
534 Replace invalid links in posts so that they won't be parsed.
527 Invalid links are links to non-existent posts
535 Invalid links are links to non-existent posts
528 """
536 """
529
537
530 for reply_number in re.finditer(REGEX_REPLY, text):
538 for reply_number in re.finditer(REGEX_REPLY, text):
531 post_id = reply_number.group(1)
539 post_id = reply_number.group(1)
532 post = Post.objects.filter(id=post_id)
540 post = Post.objects.filter(id=post_id)
533 if not post.exists():
541 if not post.exists():
534 text = string.replace(text, '>>' + id, id)
542 text = string.replace(text, '>>' + id, id)
535
543
536 return text
544 return text
General Comments 0
You need to be logged in to leave comments. Login now