##// END OF EJS Templates
Rewriting views to class-based
neko259 -
r542:8b7899f5 1.7-dev
parent child Browse files
Show More
@@ -0,0 +1,134 b''
1 import string
2
3 from django.core.paginator import Paginator
4 from django.core.urlresolvers import reverse
5 from django.db import transaction
6 from django.shortcuts import render, redirect
7
8 from boards import utils
9 from boards.forms import ThreadForm, PlainErrorList
10 from boards.models import Post, Thread, Ban, Tag
11 from boards.views.banned import BannedView
12 from boards.views.base import BaseBoardView, PARAMETER_FORM
13 from boards.views.posting_mixin import PostMixin
14 import neboard
15
16 PARAMETER_CURRENT_PAGE = 'current_page'
17
18 PARAMETER_PAGINATOR = 'paginator'
19
20 PARAMETER_THREADS = 'threads'
21
22 TEMPLATE = 'boards/posting_general.html'
23 DEFAULT_PAGE = 1
24
25
26 class AllThreadsView(PostMixin, BaseBoardView):
27
28 def get(self, request, page=DEFAULT_PAGE):
29 context = self.get_context_data(request=request)
30
31 form = ThreadForm(error_class=PlainErrorList)
32
33 paginator = Paginator(self.get_threads(),
34 neboard.settings.THREADS_PER_PAGE)
35
36 threads = paginator.page(page).object_list
37
38 context[PARAMETER_THREADS] = threads
39 context[PARAMETER_FORM] = form
40
41 self._get_page_context(paginator, context, page)
42
43 return render(request, TEMPLATE, context)
44
45 def post(self, request, page=DEFAULT_PAGE):
46 context = self.get_context_data(request=request)
47
48 form = ThreadForm(request.POST, request.FILES,
49 error_class=PlainErrorList)
50 form.session = request.session
51
52 if form.is_valid():
53 return self._new_post(request, form)
54 if form.need_to_ban:
55 # Ban user because he is suspected to be a bot
56 self._ban_current_user(request)
57
58 paginator = Paginator(self.get_threads(),
59 neboard.settings.THREADS_PER_PAGE)
60
61 threads = paginator.page(page).object_list
62
63 context[PARAMETER_THREADS] = threads
64 context[PARAMETER_FORM] = form
65
66 self._get_page_context(paginator, context, page)
67
68 return render(request, TEMPLATE, context)
69
70 @staticmethod
71 def _get_page_context(paginator, context, page):
72 """
73 Get pagination context variables
74 """
75
76 context[PARAMETER_PAGINATOR] = paginator
77 context[PARAMETER_CURRENT_PAGE] = paginator.page(int(page))
78
79 # TODO This method should be refactored
80 @transaction.atomic
81 def _new_post(self, request, form, opening_post=None, html_response=True):
82 """
83 Add a new thread opening post.
84 """
85
86 ip = utils.get_client_ip(request)
87 is_banned = Ban.objects.filter(ip=ip).exists()
88
89 if is_banned:
90 if html_response:
91 return redirect(BannedView().as_view())
92 else:
93 return
94
95 data = form.cleaned_data
96
97 title = data['title']
98 text = data['text']
99
100 text = self._remove_invalid_links(text)
101
102 if 'image' in data.keys():
103 image = data['image']
104 else:
105 image = None
106
107 tags = []
108
109 tag_strings = data['tags']
110
111 if tag_strings:
112 tag_strings = tag_strings.split(' ')
113 for tag_name in tag_strings:
114 tag_name = string.lower(tag_name.strip())
115 if len(tag_name) > 0:
116 tag, created = Tag.objects.get_or_create(name=tag_name)
117 tags.append(tag)
118
119 post = Post.objects.create_post(title=title, text=text, ip=ip,
120 image=image, tags=tags,
121 user=self._get_user(request))
122
123 thread_to_show = (opening_post.id if opening_post else post.id)
124
125 if html_response:
126 if opening_post:
127 return redirect(
128 reverse('thread', kwargs={'post_id': thread_to_show}) +
129 '#' + str(post.id))
130 else:
131 return redirect('thread', post_id=thread_to_show)
132
133 def get_threads(self):
134 return Thread.objects.filter(archived=False) No newline at end of file
@@ -0,0 +1,10 b''
1 from boards.models import Thread
2 from boards.views.all_threads import AllThreadsView
3
4 __author__ = 'neko259'
5
6
7 class ArchiveView(AllThreadsView):
8
9 def get_threads(self):
10 return Thread.objects.filter(archived=True) No newline at end of file
@@ -0,0 +1,16 b''
1 from django.shortcuts import get_object_or_404, render
2 from boards import utils
3 from boards.models import Ban
4 from boards.views.base import BaseBoardView
5
6
7 class BannedView(BaseBoardView):
8
9 def get(self, request):
10 """Show the page that notifies that user is banned"""
11
12 context = self.get_context_data(request)
13
14 ban = get_object_or_404(Ban, ip=utils.get_client_ip(request))
15 context['ban_reason'] = ban.reason
16 return render(request, 'boards/staticpages/banned.html', context)
@@ -0,0 +1,125 b''
1 from datetime import datetime, timedelta
2 import hashlib
3 from django.db import transaction
4 from django.db.models import Count
5 from django.template import RequestContext
6 from django.utils import timezone
7 from django.views.generic import View
8 from boards import utils
9 from boards.models import User, Post
10 from boards.models.post import SETTING_MODERATE
11 from boards.models.user import RANK_USER, Ban
12 import neboard
13
14 BAN_REASON_SPAM = 'Autoban: spam bot'
15
16 OLD_USER_AGE_DAYS = 90
17
18 PARAMETER_FORM = 'form'
19
20
21 class BaseBoardView(View):
22
23 def get_context_data(self, **kwargs):
24 request = kwargs['request']
25 context = self._default_context(request)
26
27 context['version'] = neboard.settings.VERSION
28 context['site_name'] = neboard.settings.SITE_NAME
29
30 return context
31
32 def _default_context(self, request):
33 """Create context with default values that are used in most views"""
34
35 context = RequestContext(request)
36
37 user = self._get_user(request)
38 context['user'] = user
39 context['tags'] = user.get_sorted_fav_tags()
40 context['posts_per_day'] = float(Post.objects.get_posts_per_day())
41
42 theme = self._get_theme(request, user)
43 context['theme'] = theme
44 context['theme_css'] = 'css/' + theme + '/base_page.css'
45
46 # This shows the moderator panel
47 moderate = user.get_setting(SETTING_MODERATE)
48 if moderate == 'True':
49 context['moderator'] = user.is_moderator()
50 else:
51 context['moderator'] = False
52
53 return context
54
55 def _get_user(self, request):
56 """
57 Get current user from the session. If the user does not exist, create
58 a new one.
59 """
60
61 session = request.session
62 if not 'user_id' in session:
63 request.session.save()
64
65 md5 = hashlib.md5()
66 md5.update(session.session_key)
67 new_id = md5.hexdigest()
68
69 while User.objects.filter(user_id=new_id).exists():
70 md5.update(str(timezone.now()))
71 new_id = md5.hexdigest()
72
73 time_now = timezone.now()
74 user = User.objects.create(user_id=new_id, rank=RANK_USER,
75 registration_time=time_now)
76
77 self._delete_old_users()
78
79 session['user_id'] = user.id
80 else:
81 user = User.objects.get(id=session['user_id'])
82
83 return user
84
85 def _get_theme(self, request, user=None):
86 """
87 Get user's CSS theme
88 """
89
90 if not user:
91 user = self._get_user(request)
92 theme = user.get_setting('theme')
93 if not theme:
94 theme = neboard.settings.DEFAULT_THEME
95
96 return theme
97
98 def _delete_old_users(self):
99 """
100 Delete users with no favorite tags and posted messages. These can be spam
101 bots or just old user accounts
102 """
103
104 old_registration_date = datetime.now().date() - timedelta(
105 OLD_USER_AGE_DAYS)
106
107 for user in User.objects.annotate(tags_count=Count('fav_tags')).filter(
108 tags_count=0).filter(
109 registration_time__lt=old_registration_date):
110 if not Post.objects.filter(user=user).exists():
111 user.delete()
112
113 @transaction.atomic
114 def _ban_current_user(self, request):
115 """
116 Add current user to the IP ban list
117 """
118
119 ip = utils.get_client_ip(request)
120 ban, created = Ban.objects.get_or_create(ip=ip)
121 if created:
122 ban.can_read = False
123 ban.reason = BAN_REASON_SPAM
124 ban.save()
125
@@ -0,0 +1,25 b''
1 import re
2 import string
3
4 from boards.models import Post
5 from boards.models.post import REGEX_REPLY
6
7 REFLINK_PREFIX = '>>'
8
9
10 class PostMixin:
11
12 @staticmethod
13 def _remove_invalid_links(text):
14 """
15 Replace invalid links in posts so that they won't be parsed.
16 Invalid links are links to non-existent posts
17 """
18
19 for reply_number in re.finditer(REGEX_REPLY, text):
20 post_id = reply_number.group(1)
21 post = Post.objects.filter(id=post_id)
22 if not post.exists():
23 text = string.replace(text, REFLINK_PREFIX + post_id, post_id)
24
25 return text
@@ -0,0 +1,28 b''
1 from django.shortcuts import get_object_or_404
2 from boards.models import Tag, Post
3 from boards.views.all_threads import AllThreadsView, DEFAULT_PAGE
4
5 __author__ = 'neko259'
6
7
8 class TagView(AllThreadsView):
9
10 tag_name = None
11
12 def get_threads(self):
13 tag = get_object_or_404(Tag, name=self.tag_name)
14
15 return tag.threads.filter(archived=False)
16
17 def get_context_data(self, **kwargs):
18 context = super(TagView, self).get_context_data(**kwargs)
19
20 tag = get_object_or_404(Tag, name=self.tag_name)
21 context['tag'] = tag
22
23 return context
24
25 def get(self, request, tag_name, page=DEFAULT_PAGE):
26 self.tag_name = tag_name
27
28 return super(TagView, self).get(request, page) No newline at end of file
@@ -0,0 +1,149 b''
1 import string
2 from django.core.urlresolvers import reverse
3 from django.db import transaction
4 from django.http import Http404
5 from django.shortcuts import get_object_or_404, render, redirect
6 from boards import utils
7 from boards.forms import PostForm, PlainErrorList
8 from boards.models import Post, Ban, Tag
9 from boards.views.banned import BannedView
10 from boards.views.base import BaseBoardView, PARAMETER_FORM
11 from boards.views.posting_mixin import PostMixin
12 import neboard
13
14 MODE_GALLERY = 'gallery'
15 MODE_NORMAL = 'normal'
16
17
18 class ThreadView(BaseBoardView, PostMixin):
19
20 def get(self, request, post_id, mode=MODE_NORMAL):
21 opening_post = get_object_or_404(Post, id=post_id)
22
23 # If this is not OP, don't show it as it is
24 if not opening_post.is_opening():
25 raise Http404
26
27 form = PostForm(error_class=PlainErrorList)
28
29 thread_to_show = opening_post.thread_new
30
31 context = self.get_context_data(request=request)
32
33 posts = thread_to_show.get_replies()
34 context[PARAMETER_FORM] = form
35 context["last_update"] = utils.datetime_to_epoch(
36 thread_to_show.last_edit_time)
37 context["thread"] = thread_to_show
38
39 if MODE_NORMAL == mode:
40 context['bumpable'] = thread_to_show.can_bump()
41 if context['bumpable']:
42 context['posts_left'] = neboard.settings.MAX_POSTS_PER_THREAD - posts \
43 .count()
44 context['bumplimit_progress'] = str(
45 float(context['posts_left']) /
46 neboard.settings.MAX_POSTS_PER_THREAD * 100)
47
48 context['posts'] = posts
49
50 document = 'boards/thread.html'
51 elif MODE_GALLERY == mode:
52 context['posts'] = posts.filter(image_width__gt=0)
53
54 document = 'boards/thread_gallery.html'
55 else:
56 raise Http404
57
58 return render(request, document, context)
59
60 def post(self, request, post_id, mode=MODE_NORMAL):
61 opening_post = get_object_or_404(Post, id=post_id)
62
63 # If this is not OP, don't show it as it is
64 if not opening_post.is_opening():
65 raise Http404
66
67 if not opening_post.thread_new.archived:
68 form = PostForm(request.POST, request.FILES,
69 error_class=PlainErrorList)
70 form.session = request.session
71
72 if form.is_valid():
73 return self.new_post(request, form, opening_post)
74 if form.need_to_ban:
75 # Ban user because he is suspected to be a bot
76 self._ban_current_user(request)
77
78 thread_to_show = opening_post.thread_new
79
80 context = self.get_context_data(request=request)
81
82 posts = thread_to_show.get_replies()
83 context[PARAMETER_FORM] = form
84 context["last_update"] = utils.datetime_to_epoch(
85 thread_to_show.last_edit_time)
86 context["thread"] = thread_to_show
87
88 if MODE_NORMAL == mode:
89 context['bumpable'] = thread_to_show.can_bump()
90 if context['bumpable']:
91 context['posts_left'] = neboard.settings.MAX_POSTS_PER_THREAD - posts \
92 .count()
93 context['bumplimit_progress'] = str(
94 float(context['posts_left']) /
95 neboard.settings.MAX_POSTS_PER_THREAD * 100)
96
97 context['posts'] = posts
98
99 document = 'boards/thread.html'
100 elif MODE_GALLERY == mode:
101 context['posts'] = posts.filter(image_width__gt=0)
102
103 document = 'boards/thread_gallery.html'
104 else:
105 raise Http404
106
107 return render(request, document, context)
108
109 @transaction.atomic
110 def new_post(self, request, form, opening_post=None, html_response=True):
111 """Add a new post (in thread or as a reply)."""
112
113 ip = utils.get_client_ip(request)
114 is_banned = Ban.objects.filter(ip=ip).exists()
115
116 if is_banned:
117 if html_response:
118 return redirect(BannedView().as_view())
119 else:
120 return
121
122 data = form.cleaned_data
123
124 title = data['title']
125 text = data['text']
126
127 text = self._remove_invalid_links(text)
128
129 if 'image' in data.keys():
130 image = data['image']
131 else:
132 image = None
133
134 tags = []
135
136 post_thread = opening_post.thread_new
137
138 post = Post.objects.create_post(title=title, text=text, ip=ip,
139 thread=post_thread, image=image,
140 tags=tags,
141 user=self._get_user(request))
142
143 thread_to_show = (opening_post.id if opening_post else post.id)
144
145 if html_response:
146 if opening_post:
147 return redirect(reverse('thread',
148 kwargs={'post_id': thread_to_show}) + '#'
149 + str(post.id)) No newline at end of file
@@ -17,14 +17,14 b' class BanMiddleware:'
17
17
18 def process_view(self, request, view_func, view_args, view_kwargs):
18 def process_view(self, request, view_func, view_args, view_kwargs):
19
19
20 if view_func != views.you_are_banned:
20 if view_func != views.banned.BannedView.as_view:
21 ip = utils.get_client_ip(request)
21 ip = utils.get_client_ip(request)
22 bans = Ban.objects.filter(ip=ip)
22 bans = Ban.objects.filter(ip=ip)
23
23
24 if bans.exists():
24 if bans.exists():
25 ban = bans[0]
25 ban = bans[0]
26 if not ban.can_read:
26 if not ban.can_read:
27 return redirect(views.you_are_banned)
27 return redirect('banned')
28
28
29
29
30 class MinifyHTMLMiddleware(object):
30 class MinifyHTMLMiddleware(object):
@@ -356,6 +356,10 b' class Thread(models.Model):'
356
356
357 return last_replies
357 return last_replies
358
358
359 def get_skipped_replies_count(self):
360 last_replies = self.get_last_replies()
361 return self.get_reply_count() - len(last_replies) - 1
362
359 def get_replies(self):
363 def get_replies(self):
360 """
364 """
361 Get sorted thread posts
365 Get sorted thread posts
@@ -3,7 +3,7 b''
3 {% load i18n %}
3 {% load i18n %}
4
4
5 {% block head %}
5 {% block head %}
6 <title>{% trans 'Login' %}</title>
6 <title>{% trans 'Login' %} - {{ site_name }}</title>
7 {% endblock %}
7 {% endblock %}
8
8
9 {% block content %}
9 {% block content %}
@@ -2,9 +2,10 b''
2 {% load board %}
2 {% load board %}
3
3
4 {% spaceless %}
4 {% spaceless %}
5 {% if post.thread_new.archived %}
5 {% with thread=post.thread_new %}
6 {% if thread.archived %}
6 <div class="post archive_post" id="{{ post.id }}">
7 <div class="post archive_post" id="{{ post.id }}">
7 {% elif post.thread_new.can_bump %}
8 {% elif thread.can_bump %}
8 <div class="post" id="{{ post.id }}">
9 <div class="post" id="{{ post.id }}">
9 {% else %}
10 {% else %}
10 <div class="post dead_post" id="{{ post.id }}">
11 <div class="post dead_post" id="{{ post.id }}">
@@ -30,13 +31,19 b''
30 <a class="post_id" href="{% post_url post.id %}">
31 <a class="post_id" href="{% post_url post.id %}">
31 ({{ post.id }}) </a>
32 ({{ post.id }}) </a>
32 [<span class="pub_time">{{ post.pub_time }}</span>]
33 [<span class="pub_time">{{ post.pub_time }}</span>]
34 {% if thread.archived %}
35 — [{{ thread.bump_time }}]
36 {% endif %}
33 {% if not truncated %}
37 {% if not truncated %}
34 [<a href="#" onclick="javascript:addQuickReply('{{ post.id }}')
38 [<a href="#" onclick="javascript:addQuickReply('{{ post.id }}')
35 ; return false;">&gt;&gt;</a>]
39 ; return false;">&gt;&gt;</a>]
36 {% endif %}
40 {% endif %}
37 {% if post.is_opening and need_open_link %}
41 {% if post.is_opening and need_open_link %}
38 [<a class="link" href="
42 {% if post.thread_new.archived %}
39 {% url 'thread' post.id %}#form">{% trans "Reply" %}</a>]
43 [<a class="link" href="{% url 'thread' post.id %}">{% trans "Open" %}</a>]
44 {% else %}
45 [<a class="link" href="{% url 'thread' post.id %}#form">{% trans "Reply" %}</a>]
46 {% endif %}
40 {% endif %}
47 {% endif %}
41
48
42 {% if moderator %}
49 {% if moderator %}
@@ -66,13 +73,13 b''
66 </div>
73 </div>
67 {% endif %}
74 {% endif %}
68 </div>
75 </div>
69 {% if post.is_opening and post.thread_new.tags.exists %}
76 {% if post.is_opening and thread.tags.exists %}
70 <div class="metadata">
77 <div class="metadata">
71 {% if post.is_opening and need_open_link %}
78 {% if post.is_opening and need_open_link %}
72 {{ post.thread_new.get_images_count }} {% trans 'images' %}.
79 {{ thread.get_images_count }} {% trans 'images' %}.
73 {% endif %}
80 {% endif %}
74 <span class="tags">
81 <span class="tags">
75 {% for tag in post.thread_new.get_tags %}
82 {% for tag in thread.get_tags %}
76 <a class="tag" href="{% url 'tag' tag.name %}">
83 <a class="tag" href="{% url 'tag' tag.name %}">
77 #{{ tag.name }}</a>{% if not forloop.last %},{% endif %}
84 #{{ tag.name }}</a>{% if not forloop.last %},{% endif %}
78 {% endfor %}
85 {% endfor %}
@@ -80,4 +87,5 b''
80 </div>
87 </div>
81 {% endif %}
88 {% endif %}
82 </div>
89 </div>
90 {% endwith %}
83 {% endspaceless %}
91 {% endspaceless %}
@@ -7,9 +7,9 b''
7
7
8 {% block head %}
8 {% block head %}
9 {% if tag %}
9 {% if tag %}
10 <title>Neboard - {{ tag.name }}</title>
10 <title>{{ tag.name }} - {{ site_name }}</title>
11 {% else %}
11 {% else %}
12 <title>Neboard</title>
12 <title>{{ site_name }}</title>
13 {% endif %}
13 {% endif %}
14
14
15 {% if current_page.has_previous %}
15 {% if current_page.has_previous %}
@@ -66,23 +66,25 b''
66 {% endif %}
66 {% endif %}
67
67
68 {% for thread in threads %}
68 {% for thread in threads %}
69 {% cache 600 thread_short thread.thread.id thread.thread.last_edit_time moderator LANGUAGE_CODE %}
69 {% cache 600 thread_short thread.id thread.last_edit_time moderator LANGUAGE_CODE %}
70 <div class="thread">
70 <div class="thread">
71 {% post_view_truncated thread.op True moderator %}
71 {% post_view_truncated thread.get_opening_post True moderator %}
72 {% if thread.last_replies.exists %}
72 {% if not thread.archived %}
73 {% if thread.skipped_replies %}
73 {% if thread.get_last_replies.exists %}
74 {% if thread.get_skipped_replies_count %}
74 <div class="skipped_replies">
75 <div class="skipped_replies">
75 <a href="{% url 'thread' thread.op.id %}">
76 <a href="{% url 'thread' thread.get_opening_post.id %}">
76 {% blocktrans with count=thread.skipped_replies %}Skipped {{ count }} replies. Open thread to see all replies.{% endblocktrans %}
77 {% blocktrans with count=thread.get_skipped_replies_count %}Skipped {{ count }} replies. Open thread to see all replies.{% endblocktrans %}
77 </a>
78 </a>
78 </div>
79 </div>
79 {% endif %}
80 {% endif %}
80 <div class="last-replies">
81 <div class="last-replies">
81 {% for post in thread.last_replies %}
82 {% for post in thread.get_last_replies %}
82 {% post_view_truncated post moderator=moderator%}
83 {% post_view_truncated post moderator=moderator%}
83 {% endfor %}
84 {% endfor %}
84 </div>
85 </div>
85 {% endif %}
86 {% endif %}
87 {% endif %}
86 </div>
88 </div>
87 {% endcache %}
89 {% endcache %}
88 {% endfor %}
90 {% endfor %}
@@ -126,7 +128,7 b''
126 {% block metapanel %}
128 {% block metapanel %}
127
129
128 <span class="metapanel">
130 <span class="metapanel">
129 <b><a href="{% url "authors" %}">Neboard</a> 1.6 Amon</b>
131 <b><a href="{% url "authors" %}">{{ site_name }}</a> {{ version }}</b>
130 {% trans "Pages:" %}[
132 {% trans "Pages:" %}[
131 {% for page in paginator.page_range %}
133 {% for page in paginator.page_range %}
132 <a
134 <a
@@ -4,7 +4,7 b''
4 {% load humanize %}
4 {% load humanize %}
5
5
6 {% block head %}
6 {% block head %}
7 <title>Neboard settings</title>
7 <title>{% trans 'Settings' %} - {{ site_name }}</title>
8 {% endblock %}
8 {% endblock %}
9
9
10 {% block content %}
10 {% block content %}
@@ -6,7 +6,8 b''
6 {% load board %}
6 {% load board %}
7
7
8 {% block head %}
8 {% block head %}
9 <title>{{ thread.get_opening_post.get_title|striptags|truncatewords:10 }} - Neboard</title>
9 <title>{{ thread.get_opening_post.get_title|striptags|truncatewords:10 }}
10 - {{ site_name }}</title>
10 {% endblock %}
11 {% endblock %}
11
12
12 {% block content %}
13 {% block content %}
@@ -6,7 +6,8 b''
6 {% load board %}
6 {% load board %}
7
7
8 {% block head %}
8 {% block head %}
9 <title>{{ thread.get_opening_post.get_title|striptags|truncatewords:10 }} - Neboard</title>
9 <title>{{ thread.get_opening_post.get_title|striptags|truncatewords:10 }}
10 - {{ site_name }}</title>
10 {% endblock %}
11 {% endblock %}
11
12
12 {% block content %}
13 {% block content %}
@@ -25,11 +25,11 b' def post_url(*args, **kwargs):'
25 post = get_object_or_404(Post, id=post_id)
25 post = get_object_or_404(Post, id=post_id)
26
26
27 if not post.is_opening():
27 if not post.is_opening():
28 link = reverse(thread, kwargs={
28 link = reverse('thread', kwargs={
29 'post_id': post.thread_new.get_opening_post().id}) + '#' + str(
29 'post_id': post.thread_new.get_opening_post().id}) + '#' + str(
30 post_id)
30 post_id)
31 else:
31 else:
32 link = reverse(thread, kwargs={'post_id': post_id})
32 link = reverse('thread', kwargs={'post_id': post_id})
33
33
34 return link
34 return link
35
35
@@ -1,7 +1,7 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 from boards.views import api
4 from boards.views import api, tag_threads, all_threads, archived_threads
5
5
6 js_info_dict = {
6 js_info_dict = {
7 'packages': ('boards',),
7 'packages': ('boards',),
@@ -10,20 +10,24 b' js_info_dict = {'
10 urlpatterns = patterns('',
10 urlpatterns = patterns('',
11
11
12 # /boards/
12 # /boards/
13 url(r'^$', views.index, name='index'),
13 url(r'^$', all_threads.AllThreadsView.as_view(), name='index'),
14 # /boards/page/
14 # /boards/page/
15 url(r'^page/(?P<page>\w+)/$', views.index, name='index'),
15 url(r'^page/(?P<page>\w+)/$', all_threads.AllThreadsView.as_view(),
16 name='index'),
16
17
17 url(r'^archive/$', views.archive, name='archive'),
18 url(r'^archive/$', archived_threads.ArchiveView.as_view(), name='archive'),
18 url(r'^archive/page/(?P<page>\w+)/$', views.archive, name='archive'),
19 url(r'^archive/page/(?P<page>\w+)/$',
20 archived_threads.ArchiveView.as_view(), name='archive'),
19
21
20 # login page
22 # login page
21 url(r'^login/$', views.login, name='login'),
23 url(r'^login/$', views.login, name='login'),
22
24
23 # /boards/tag/tag_name/
25 # /boards/tag/tag_name/
24 url(r'^tag/(?P<tag_name>\w+)/$', views.tag, name='tag'),
26 url(r'^tag/(?P<tag_name>\w+)/$', tag_threads.TagView.as_view(),
27 name='tag'),
25 # /boards/tag/tag_id/page/
28 # /boards/tag/tag_id/page/
26 url(r'^tag/(?P<tag_name>\w+)/page/(?P<page>\w+)/$', views.tag, name='tag'),
29 url(r'^tag/(?P<tag_name>\w+)/page/(?P<page>\w+)/$',
30 tag_threads.TagView.as_view(), name='tag'),
27
31
28 # /boards/tag/tag_name/unsubscribe/
32 # /boards/tag/tag_name/unsubscribe/
29 url(r'^tag/(?P<tag_name>\w+)/subscribe/$', views.tag_subscribe,
33 url(r'^tag/(?P<tag_name>\w+)/subscribe/$', views.tag_subscribe,
@@ -33,8 +37,10 b" urlpatterns = patterns('',"
33 name='tag_unsubscribe'),
37 name='tag_unsubscribe'),
34
38
35 # /boards/thread/
39 # /boards/thread/
36 url(r'^thread/(?P<post_id>\w+)/$', views.thread, name='thread'),
40 url(r'^thread/(?P<post_id>\w+)/$', views.thread.ThreadView.as_view(),
37 url(r'^thread/(?P<post_id>\w+)/(?P<mode>\w+)/$', views.thread, name='thread_mode'),
41 name='thread'),
42 url(r'^thread/(?P<post_id>\w+)/(?P<mode>\w+)/$', views.thread.ThreadView
43 .as_view(), name='thread_mode'),
38 url(r'^settings/$', views.settings, name='settings'),
44 url(r'^settings/$', views.settings, name='settings'),
39 url(r'^tags/$', views.all_tags, name='tags'),
45 url(r'^tags/$', views.all_tags, name='tags'),
40 url(r'^captcha/', include('captcha.urls')),
46 url(r'^captcha/', include('captcha.urls')),
@@ -43,7 +49,7 b" urlpatterns = patterns('',"
43 url(r'^delete/(?P<post_id>\w+)/$', views.delete, name='delete'),
49 url(r'^delete/(?P<post_id>\w+)/$', views.delete, name='delete'),
44 url(r'^ban/(?P<post_id>\w+)/$', views.ban, name='ban'),
50 url(r'^ban/(?P<post_id>\w+)/$', views.ban, name='ban'),
45
51
46 url(r'^banned/$', views.you_are_banned, name='banned'),
52 url(r'^banned/$', views.banned.BannedView.as_view, name='banned'),
47 url(r'^staticpage/(?P<name>\w+)/$', views.static_page, name='staticpage'),
53 url(r'^staticpage/(?P<name>\w+)/$', views.static_page, name='staticpage'),
48
54
49 # RSS feeds
55 # RSS feeds
@@ -62,9 +68,9 b" urlpatterns = patterns('',"
62 api.api_get_threaddiff, name="get_thread_diff"),
68 api.api_get_threaddiff, name="get_thread_diff"),
63 url(r'^api/threads/(?P<count>\w+)/$', api.api_get_threads,
69 url(r'^api/threads/(?P<count>\w+)/$', api.api_get_threads,
64 name='get_threads'),
70 name='get_threads'),
65 url(r'api/tags/$', api.api_get_tags, name='get_tags'),
71 url(r'^api/tags/$', api.api_get_tags, name='get_tags'),
66 url(r'api/thread/(?P<opening_post_id>\w+)/$', api.api_get_thread_posts,
72 url(r'^api/thread/(?P<opening_post_id>\w+)/$', api.api_get_thread_posts,
67 name='get_thread'),
73 name='get_thread'),
68 url(r'api/add_post/(?P<opening_post_id>\w+)/$', api.api_add_post, name='add_post'),
74 url(r'^api/add_post/(?P<opening_post_id>\w+)/$', api.api_add_post, name='add_post'),
69
75
70 )
76 )
@@ -1,6 +1,7 b''
1 """
1 """
2 This module contains helper functions and helper classes.
2 This module contains helper functions and helper classes.
3 """
3 """
4 from django.utils import timezone
4
5
5 from neboard import settings
6 from neboard import settings
6 import time
7 import time
@@ -70,4 +71,10 b' def get_client_ip(request):'
70 ip = x_forwarded_for.split(',')[-1].strip()
71 ip = x_forwarded_for.split(',')[-1].strip()
71 else:
72 else:
72 ip = request.META.get('REMOTE_ADDR')
73 ip = request.META.get('REMOTE_ADDR')
73 return ip No newline at end of file
74 return ip
75
76
77 def datetime_to_epoch(datetime):
78 return int(time.mktime(timezone.localtime(
79 datetime,timezone.get_current_timezone()).timetuple())
80 * 1000000 + datetime.microsecond) No newline at end of file
@@ -2,19 +2,15 b' from datetime import datetime, timedelta'
2
2
3 from django.db.models import Count
3 from django.db.models import Count
4
4
5
6 OLD_USER_AGE_DAYS = 90
7
8 __author__ = 'neko259'
5 __author__ = 'neko259'
9
6
10 import hashlib
7 import hashlib
11 import string
8 import string
12 import time
13 import re
9 import re
14
10
15 from django.core import serializers
11 from django.core import serializers
16 from django.core.urlresolvers import reverse
12 from django.core.urlresolvers import reverse
17 from django.http import HttpResponseRedirect, Http404
13 from django.http import HttpResponseRedirect
18 from django.http.response import HttpResponse
14 from django.http.response import HttpResponse
19 from django.template import RequestContext
15 from django.template import RequestContext
20 from django.shortcuts import render, redirect, get_object_or_404
16 from django.shortcuts import render, redirect, get_object_or_404
@@ -22,242 +18,25 b' from django.utils import timezone'
22 from django.db import transaction
18 from django.db import transaction
23 from django.views.decorators.cache import cache_page
19 from django.views.decorators.cache import cache_page
24 from django.views.i18n import javascript_catalog
20 from django.views.i18n import javascript_catalog
25 from django.core.paginator import Paginator
26
21
27 from boards import forms
28 import boards
22 import boards
29 from boards import utils
23 from boards import utils
30 from boards.forms import ThreadForm, PostForm, SettingsForm, PlainErrorList, \
24 from boards.forms import SettingsForm, PlainErrorList, \
31 ThreadCaptchaForm, PostCaptchaForm, LoginForm, ModeratorSettingsForm
25 LoginForm, ModeratorSettingsForm
32 from boards.models import Post, Tag, Ban, User, Thread
26 from boards.models import Post, Tag, Ban, User
33 from boards.models.post import SETTING_MODERATE, REGEX_REPLY
27 from boards.models.post import SETTING_MODERATE, REGEX_REPLY
34 from boards.models.user import RANK_USER
28 from boards.models.user import RANK_USER
35 from boards import authors
29 from boards import authors
36 from boards.utils import get_client_ip
37 import neboard
30 import neboard
38
31
32 import all_threads
33
39
34
40 BAN_REASON_SPAM = 'Autoban: spam bot'
35 BAN_REASON_SPAM = 'Autoban: spam bot'
41 MODE_GALLERY = 'gallery'
42 MODE_NORMAL = 'normal'
43
36
44 DEFAULT_PAGE = 1
37 DEFAULT_PAGE = 1
45
38
46
39
47 def index(request, page=DEFAULT_PAGE):
48 context = _init_default_context(request)
49
50 if utils.need_include_captcha(request):
51 threadFormClass = ThreadCaptchaForm
52 kwargs = {'request': request}
53 else:
54 threadFormClass = ThreadForm
55 kwargs = {}
56
57 if request.method == 'POST':
58 form = threadFormClass(request.POST, request.FILES,
59 error_class=PlainErrorList, **kwargs)
60 form.session = request.session
61
62 if form.is_valid():
63 return _new_post(request, form)
64 if form.need_to_ban:
65 # Ban user because he is suspected to be a bot
66 _ban_current_user(request)
67 else:
68 form = threadFormClass(error_class=PlainErrorList, **kwargs)
69
70 threads = []
71 for thread_to_show in Post.objects.get_threads(page=int(page)):
72 threads.append(_get_template_thread(thread_to_show))
73
74 # TODO Make this generic for tag and threads list pages
75 context['threads'] = None if len(threads) == 0 else threads
76 context['form'] = form
77
78 paginator = Paginator(Thread.objects.filter(archived=False),
79 neboard.settings.THREADS_PER_PAGE)
80 _get_page_context(paginator, context, page)
81
82 return render(request, 'boards/posting_general.html',
83 context)
84
85
86 def archive(request, page=DEFAULT_PAGE):
87 """
88 Get archived posts
89 """
90
91 context = _init_default_context(request)
92
93 threads = []
94 for thread_to_show in Post.objects.get_threads(page=int(page),
95 archived=True):
96 threads.append(_get_template_thread(thread_to_show))
97
98 context['threads'] = threads
99
100 paginator = Paginator(Thread.objects.filter(archived=True),
101 neboard.settings.THREADS_PER_PAGE)
102 _get_page_context(paginator, context, page)
103
104 return render(request, 'boards/archive.html', context)
105
106
107 @transaction.atomic
108 def _new_post(request, form, opening_post=None, html_response=True):
109 """Add a new post (in thread or as a reply)."""
110
111 ip = get_client_ip(request)
112 is_banned = Ban.objects.filter(ip=ip).exists()
113
114 if is_banned:
115 if html_response:
116 return redirect(you_are_banned)
117 else:
118 return
119
120 data = form.cleaned_data
121
122 title = data['title']
123 text = data['text']
124
125 text = _remove_invalid_links(text)
126
127 if 'image' in data.keys():
128 image = data['image']
129 else:
130 image = None
131
132 tags = []
133
134 if not opening_post:
135 tag_strings = data['tags']
136
137 if tag_strings:
138 tag_strings = tag_strings.split(' ')
139 for tag_name in tag_strings:
140 tag_name = string.lower(tag_name.strip())
141 if len(tag_name) > 0:
142 tag, created = Tag.objects.get_or_create(name=tag_name)
143 tags.append(tag)
144 post_thread = None
145 else:
146 post_thread = opening_post.thread_new
147
148 post = Post.objects.create_post(title=title, text=text, ip=ip,
149 thread=post_thread, image=image,
150 tags=tags, user=_get_user(request))
151
152 thread_to_show = (opening_post.id if opening_post else post.id)
153
154 if html_response:
155 if opening_post:
156 return redirect(reverse(thread, kwargs={'post_id': thread_to_show}) +
157 '#' + str(post.id))
158 else:
159 return redirect(thread, post_id=thread_to_show)
160
161
162 def tag(request, tag_name, page=DEFAULT_PAGE):
163 """
164 Get all tag threads. Threads are split in pages, so some page is
165 requested.
166 """
167
168 tag = get_object_or_404(Tag, name=tag_name)
169 threads = []
170 for thread_to_show in Post.objects.get_threads(page=int(page), tag=tag):
171 threads.append(_get_template_thread(thread_to_show))
172
173 if request.method == 'POST':
174 form = ThreadForm(request.POST, request.FILES,
175 error_class=PlainErrorList)
176 form.session = request.session
177
178 if form.is_valid():
179 return _new_post(request, form)
180 if form.need_to_ban:
181 # Ban user because he is suspected to be a bot
182 _ban_current_user(request)
183 else:
184 form = forms.ThreadForm(initial={'tags': tag_name},
185 error_class=PlainErrorList)
186
187 context = _init_default_context(request)
188 context['threads'] = None if len(threads) == 0 else threads
189 context['tag'] = tag
190
191 paginator = Paginator(Post.objects.get_threads(tag=tag),
192 neboard.settings.THREADS_PER_PAGE)
193 _get_page_context(paginator, context, page)
194
195 context['form'] = form
196
197 return render(request, 'boards/posting_general.html',
198 context)
199
200
201 def thread(request, post_id, mode=MODE_NORMAL):
202 """Get all thread posts"""
203
204 if utils.need_include_captcha(request):
205 postFormClass = PostCaptchaForm
206 kwargs = {'request': request}
207 else:
208 postFormClass = PostForm
209 kwargs = {}
210
211 opening_post = get_object_or_404(Post, id=post_id)
212
213 # If this is not OP, don't show it as it is
214 if not opening_post.is_opening():
215 raise Http404
216
217 if request.method == 'POST' and not opening_post.thread_new.archived:
218 form = postFormClass(request.POST, request.FILES,
219 error_class=PlainErrorList, **kwargs)
220 form.session = request.session
221
222 if form.is_valid():
223 return _new_post(request, form, opening_post)
224 if form.need_to_ban:
225 # Ban user because he is suspected to be a bot
226 _ban_current_user(request)
227 else:
228 form = postFormClass(error_class=PlainErrorList, **kwargs)
229
230 thread_to_show = opening_post.thread_new
231
232 context = _init_default_context(request)
233
234 posts = thread_to_show.get_replies()
235 context['form'] = form
236 context["last_update"] = _datetime_to_epoch(thread_to_show.last_edit_time)
237 context["thread"] = thread_to_show
238
239 if MODE_NORMAL == mode:
240 context['bumpable'] = thread_to_show.can_bump()
241 if context['bumpable']:
242 context['posts_left'] = neboard.settings.MAX_POSTS_PER_THREAD - posts \
243 .count()
244 context['bumplimit_progress'] = str(
245 float(context['posts_left']) /
246 neboard.settings.MAX_POSTS_PER_THREAD * 100)
247
248 context['posts'] = posts
249
250 document = 'boards/thread.html'
251 elif MODE_GALLERY == mode:
252 context['posts'] = posts.filter(image_width__gt=0)
253
254 document = 'boards/thread_gallery.html'
255 else:
256 raise Http404
257
258 return render(request, document, context)
259
260
261 def login(request):
40 def login(request):
262 """Log in with user id"""
41 """Log in with user id"""
263
42
@@ -271,7 +50,7 b' def login(request):'
271 if form.is_valid():
50 if form.is_valid():
272 user = User.objects.get(user_id=form.cleaned_data['user_id'])
51 user = User.objects.get(user_id=form.cleaned_data['user_id'])
273 request.session['user_id'] = user.id
52 request.session['user_id'] = user.id
274 return redirect(index)
53 return redirect('index')
275
54
276 else:
55 else:
277 form = LoginForm()
56 form = LoginForm()
@@ -337,9 +116,9 b' def jump_to_post(request, post_id):'
337 post = get_object_or_404(Post, id=post_id)
116 post = get_object_or_404(Post, id=post_id)
338
117
339 if not post.thread:
118 if not post.thread:
340 return redirect(thread, post_id=post.id)
119 return redirect('thread', post_id=post.id)
341 else:
120 else:
342 return redirect(reverse(thread, kwargs={'post_id': post.thread.id})
121 return redirect(reverse('thread', kwargs={'post_id': post.thread.id})
343 + '#' + str(post.id))
122 + '#' + str(post.id))
344
123
345
124
@@ -366,7 +145,7 b' def delete(request, post_id):'
366 if not post.thread:
145 if not post.thread:
367 return _redirect_to_next(request)
146 return _redirect_to_next(request)
368 else:
147 else:
369 return redirect(thread, post_id=post.thread.id)
148 return redirect('thread', post_id=post.thread.id)
370
149
371
150
372 @transaction.atomic
151 @transaction.atomic
@@ -386,16 +165,6 b' def ban(request, post_id):'
386 return _redirect_to_next(request)
165 return _redirect_to_next(request)
387
166
388
167
389 def you_are_banned(request):
390 """Show the page that notifies that user is banned"""
391
392 context = _init_default_context(request)
393
394 ban = get_object_or_404(Ban, ip=utils.get_client_ip(request))
395 context['ban_reason'] = ban.reason
396 return render(request, 'boards/staticpages/banned.html', context)
397
398
399 def page_404(request):
168 def page_404(request):
400 """Show page 404 (not found error)"""
169 """Show page 404 (not found error)"""
401
170
@@ -457,6 +226,8 b" def cached_js_catalog(request, domain='d"
457 return javascript_catalog(request, domain, packages)
226 return javascript_catalog(request, domain, packages)
458
227
459
228
229 # TODO This method is deprecated and should be removed after switching to
230 # class-based view
460 def _get_theme(request, user=None):
231 def _get_theme(request, user=None):
461 """Get user's CSS theme"""
232 """Get user's CSS theme"""
462
233
@@ -469,6 +240,8 b' def _get_theme(request, user=None):'
469 return theme
240 return theme
470
241
471
242
243 # TODO This method is deprecated and should be removed after switching to
244 # class-based view
472 def _init_default_context(request):
245 def _init_default_context(request):
473 """Create context with default values that are used in most views"""
246 """Create context with default values that are used in most views"""
474
247
@@ -493,6 +266,8 b' def _init_default_context(request):'
493 return context
266 return context
494
267
495
268
269 # TODO This method is deprecated and should be removed after switching to
270 # class-based view
496 def _get_user(request):
271 def _get_user(request):
497 """
272 """
498 Get current user from the session. If the user does not exist, create
273 Get current user from the session. If the user does not exist, create
@@ -515,7 +290,8 b' def _get_user(request):'
515 user = User.objects.create(user_id=new_id, rank=RANK_USER,
290 user = User.objects.create(user_id=new_id, rank=RANK_USER,
516 registration_time=time_now)
291 registration_time=time_now)
517
292
518 _delete_old_users()
293 # TODO This is just a test. This method should be removed
294 # _delete_old_users()
519
295
520 session['user_id'] = user.id
296 session['user_id'] = user.id
521 else:
297 else:
@@ -535,7 +311,7 b' def _redirect_to_next(request):'
535 next_page = request.GET['next']
311 next_page = request.GET['next']
536 return HttpResponseRedirect(next_page)
312 return HttpResponseRedirect(next_page)
537 else:
313 else:
538 return redirect(index)
314 return redirect('index')
539
315
540
316
541 @transaction.atomic
317 @transaction.atomic
@@ -565,12 +341,6 b' def _remove_invalid_links(text):'
565 return text
341 return text
566
342
567
343
568 def _datetime_to_epoch(datetime):
569 return int(time.mktime(timezone.localtime(
570 datetime,timezone.get_current_timezone()).timetuple())
571 * 1000000 + datetime.microsecond)
572
573
574 def _get_template_thread(thread_to_show):
344 def _get_template_thread(thread_to_show):
575 """Get template values for thread"""
345 """Get template values for thread"""
576
346
@@ -584,26 +354,3 b' def _get_template_thread(thread_to_show)'
584 'last_replies': last_replies,
354 'last_replies': last_replies,
585 'skipped_replies': skipped_replies_count,
355 'skipped_replies': skipped_replies_count,
586 }
356 }
587
588
589 def _delete_old_users():
590 """
591 Delete users with no favorite tags and posted messages. These can be spam
592 bots or just old user accounts
593 """
594
595 old_registration_date = datetime.now().date() - timedelta(OLD_USER_AGE_DAYS)
596
597 for user in User.objects.annotate(tags_count=Count('fav_tags')).filter(
598 tags_count=0).filter(registration_time__lt=old_registration_date):
599 if not Post.objects.filter(user=user).exists():
600 user.delete()
601
602
603 def _get_page_context(paginator, context, page):
604 """
605 Get pagination context variables
606 """
607
608 context['paginator'] = paginator
609 context['current_page'] = paginator.page(int(page))
@@ -7,8 +7,8 b' from django.template import RequestConte'
7 from django.utils import timezone
7 from django.utils import timezone
8 from boards.forms import PostForm, PlainErrorList
8 from boards.forms import PostForm, PlainErrorList
9 from boards.models import Post, Thread, Tag
9 from boards.models import Post, Thread, Tag
10 from boards.views import _datetime_to_epoch, _new_post, \
10 from boards.utils import datetime_to_epoch
11 _ban_current_user
11 from boards.views.thread import ThreadView
12
12
13 __author__ = 'neko259'
13 __author__ = 'neko259'
14
14
@@ -53,7 +53,7 b' def api_get_threaddiff(request, thread_i'
53 json_data['added'].append(_get_post_data(post.id, diff_type, request))
53 json_data['added'].append(_get_post_data(post.id, diff_type, request))
54 for post in updated_posts:
54 for post in updated_posts:
55 json_data['updated'].append(_get_post_data(post.id, diff_type, request))
55 json_data['updated'].append(_get_post_data(post.id, diff_type, request))
56 json_data['last_update'] = _datetime_to_epoch(thread.last_edit_time)
56 json_data['last_update'] = datetime_to_epoch(thread.last_edit_time)
57
57
58 return HttpResponse(content=json.dumps(json_data))
58 return HttpResponse(content=json.dumps(json_data))
59
59
@@ -78,7 +78,8 b' def api_add_post(request, opening_post_i'
78 # _ban_current_user(request)
78 # _ban_current_user(request)
79 # status = STATUS_ERROR
79 # status = STATUS_ERROR
80 if form.is_valid():
80 if form.is_valid():
81 _new_post(request, form, opening_post, html_response=False)
81 ThreadView().new_post(request, form, opening_post,
82 html_response=False)
82 else:
83 else:
83 status = STATUS_ERROR
84 status = STATUS_ERROR
84 errors = form.as_json_errors()
85 errors = form.as_json_errors()
@@ -178,7 +179,7 b' def api_get_thread_posts(request, openin'
178
179
179 for post in posts:
180 for post in posts:
180 json_post_list.append(_get_post_data(post.id))
181 json_post_list.append(_get_post_data(post.id))
181 json_data['last_update'] = _datetime_to_epoch(thread.last_edit_time)
182 json_data['last_update'] = datetime_to_epoch(thread.last_edit_time)
182 json_data['posts'] = json_post_list
183 json_data['posts'] = json_post_list
183
184
184 return HttpResponse(content=json.dumps(json_data))
185 return HttpResponse(content=json.dumps(json_data))
@@ -200,6 +201,6 b' def _get_post_data(post_id, format_type='
200 post_json['image'] = post.image.url
201 post_json['image'] = post.image.url
201 post_json['image_preview'] = post.image.url_200x150
202 post_json['image_preview'] = post.image.url_200x150
202 if include_last_update:
203 if include_last_update:
203 post_json['bump_time'] = _datetime_to_epoch(
204 post_json['bump_time'] = datetime_to_epoch(
204 post.thread_new.bump_time)
205 post.thread_new.bump_time)
205 return post_json
206 return post_json
@@ -223,6 +223,8 b' POSTING_DELAY = 20 # seconds'
223
223
224 COMPRESS_HTML = True
224 COMPRESS_HTML = True
225
225
226 VERSION = '1.7 Anubis'
227
226 # Debug mode middlewares
228 # Debug mode middlewares
227 if DEBUG:
229 if DEBUG:
228 MIDDLEWARE_CLASSES += (
230 MIDDLEWARE_CLASSES += (
1 NO CONTENT: file was removed
NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now