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