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