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