##// END OF EJS Templates
Added jump view to open message of any thread by id. This fixes #49
neko259 -
r98:ea50d316 default
parent child Browse files
Show More
@@ -1,85 +1,88 b''
1 from django.core.urlresolvers import reverse
1 import markdown
2 import markdown
2 from markdown.inlinepatterns import Pattern
3 from markdown.inlinepatterns import Pattern
3 from markdown.util import etree
4 from markdown.util import etree
5 import boards
4
6
5 __author__ = 'neko259'
7 __author__ = 'neko259'
6
8
7
9
8 class AutolinkPattern(Pattern):
10 class AutolinkPattern(Pattern):
9 def handleMatch(self, m):
11 def handleMatch(self, m):
10 link_element = etree.Element('a')
12 link_element = etree.Element('a')
11 href = m.group(2)
13 href = m.group(2)
12 link_element.set('href', href)
14 link_element.set('href', href)
13 link_element.text = href
15 link_element.text = href
14
16
15 return link_element
17 return link_element
16
18
17
19
18 class QuotePattern(Pattern):
20 class QuotePattern(Pattern):
19 def handleMatch(self, m):
21 def handleMatch(self, m):
20 quote_element = etree.Element('span')
22 quote_element = etree.Element('span')
21 quote_element.set('class', 'quote')
23 quote_element.set('class', 'quote')
22 quote_element.text = m.group(2)
24 quote_element.text = m.group(2)
23
25
24 return quote_element
26 return quote_element
25
27
26
28
27 class ReflinkPattern(Pattern):
29 class ReflinkPattern(Pattern):
28 def handleMatch(self, m):
30 def handleMatch(self, m):
29 ref_element = etree.Element('a')
31 ref_element = etree.Element('a')
30 post_id = m.group(4)
32 post_id = m.group(4)
31 ref_element.set('href', '#' + str(post_id))
33 ref_element.set('href', reverse(boards.views.jump_to_post,
34 kwargs={'post_id': post_id}))
32 ref_element.text = m.group(2)
35 ref_element.text = m.group(2)
33
36
34 return ref_element
37 return ref_element
35
38
36
39
37 class SpoilerPattern(Pattern):
40 class SpoilerPattern(Pattern):
38 def handleMatch(self, m):
41 def handleMatch(self, m):
39 quote_element = etree.Element('span')
42 quote_element = etree.Element('span')
40 quote_element.set('class', 'spoiler')
43 quote_element.set('class', 'spoiler')
41 quote_element.text = m.group(2)
44 quote_element.text = m.group(2)
42
45
43 return quote_element
46 return quote_element
44
47
45
48
46 class CommentPattern(Pattern):
49 class CommentPattern(Pattern):
47 def handleMatch(self, m):
50 def handleMatch(self, m):
48 quote_element = etree.Element('span')
51 quote_element = etree.Element('span')
49 quote_element.set('class', 'comment')
52 quote_element.set('class', 'comment')
50 quote_element.text = '//' + m.group(3)
53 quote_element.text = '//' + m.group(3)
51
54
52 return quote_element
55 return quote_element
53
56
54
57
55 class NeboardMarkdown(markdown.Extension):
58 class NeboardMarkdown(markdown.Extension):
56 AUTOLINK_PATTERN = r'(https?://\S+)'
59 AUTOLINK_PATTERN = r'(https?://\S+)'
57 QUOTE_PATTERN = r'^(?<!>)(>[^>]+)$'
60 QUOTE_PATTERN = r'^(?<!>)(>[^>]+)$'
58 REFLINK_PATTERN = r'((>>)(\d+))'
61 REFLINK_PATTERN = r'((>>)(\d+))'
59 SPOILER_PATTERN = r'%%(.+)%%'
62 SPOILER_PATTERN = r'%%(.+)%%'
60 COMMENT_PATTERN = r'^(//(.+))'
63 COMMENT_PATTERN = r'^(//(.+))'
61
64
62 def extendMarkdown(self, md, md_globals):
65 def extendMarkdown(self, md, md_globals):
63 autolink = AutolinkPattern(self.AUTOLINK_PATTERN, md)
66 autolink = AutolinkPattern(self.AUTOLINK_PATTERN, md)
64 quote = QuotePattern(self.QUOTE_PATTERN, md)
67 quote = QuotePattern(self.QUOTE_PATTERN, md)
65 reflink = ReflinkPattern(self.REFLINK_PATTERN, md)
68 reflink = ReflinkPattern(self.REFLINK_PATTERN, md)
66 spoiler = SpoilerPattern(self.SPOILER_PATTERN, md)
69 spoiler = SpoilerPattern(self.SPOILER_PATTERN, md)
67 comment = CommentPattern(self.COMMENT_PATTERN, md)
70 comment = CommentPattern(self.COMMENT_PATTERN, md)
68
71
69 md.inlinePatterns[u'autolink_ext'] = autolink
72 md.inlinePatterns[u'autolink_ext'] = autolink
70 md.inlinePatterns[u'spoiler'] = spoiler
73 md.inlinePatterns[u'spoiler'] = spoiler
71 md.inlinePatterns[u'comment'] = comment
74 md.inlinePatterns[u'comment'] = comment
72 md.inlinePatterns[u'reflink'] = reflink
75 md.inlinePatterns[u'reflink'] = reflink
73 md.inlinePatterns[u'quote'] = quote
76 md.inlinePatterns[u'quote'] = quote
74
77
75 del md.parser.blockprocessors['quote']
78 del md.parser.blockprocessors['quote']
76
79
77
80
78 def makeExtension(configs=None):
81 def makeExtension(configs=None):
79 return NeboardMarkdown(configs=configs)
82 return NeboardMarkdown(configs=configs)
80
83
81 neboard_extension = makeExtension()
84 neboard_extension = makeExtension()
82
85
83
86
84 def markdown_extended(markup):
87 def markdown_extended(markup):
85 return markdown.markdown(markup, [neboard_extension], safe_mode=True)
88 return markdown.markdown(markup, [neboard_extension], safe_mode=True)
@@ -1,34 +1,35 b''
1 from django.conf.urls import patterns, url, include
1 from django.conf.urls import patterns, url, include
2 from boards import views
2 from boards import views
3 from boards.rss import AllThreadsFeed, TagThreadsFeed, ThreadPostsFeed
3 from boards.rss import AllThreadsFeed, TagThreadsFeed, ThreadPostsFeed
4
4
5 urlpatterns = patterns('',
5 urlpatterns = patterns('',
6
6
7 # /boards/
7 # /boards/
8 url(r'^$', views.index, name='index'),
8 url(r'^$', views.index, name='index'),
9 # /boards/page/
9 # /boards/page/
10 url(r'^page/(?P<page>\w+)/$', views.index, name='index'),
10 url(r'^page/(?P<page>\w+)/$', views.index, name='index'),
11
11
12 # login page
12 # login page
13 url(r'^login$', views.login, name='login'),
13 url(r'^login$', views.login, name='login'),
14 # logout page
14 # logout page
15 url(r'^logout$', views.logout, name='logout'),
15 url(r'^logout$', views.logout, name='logout'),
16
16
17 # /boards/tag/tag_name/
17 # /boards/tag/tag_name/
18 url(r'^tag/(?P<tag_name>\w+)/$', views.tag, name='tag'),
18 url(r'^tag/(?P<tag_name>\w+)/$', views.tag, name='tag'),
19 # /boards/tag/tag_id/page/
19 # /boards/tag/tag_id/page/
20 url(r'^tag/(?P<tag_name>\w+)/page/(?P<page>\w+)/$', views.tag, name='tag'),
20 url(r'^tag/(?P<tag_name>\w+)/page/(?P<page>\w+)/$', views.tag, name='tag'),
21 # /boards/thread/
21 # /boards/thread/
22 url(r'^thread/(?P<post_id>\w+)/$', views.thread, name='thread'),
22 url(r'^thread/(?P<post_id>\w+)/$', views.thread, name='thread'),
23 # /boards/theme/theme_name/
23 # /boards/theme/theme_name/
24 url(r'^settings$', views.settings, name='settings'),
24 url(r'^settings$', views.settings, name='settings'),
25 url(r'^tags$', views.all_tags, name='tags'),
25 url(r'^tags$', views.all_tags, name='tags'),
26 url(r'^captcha/', include('captcha.urls')),
26 url(r'^captcha/', include('captcha.urls')),
27 url(r'^jump/(?P<post_id>\w+)', views.jump_to_post, name='jumper'),
27
28
28 # RSS feeds
29 # RSS feeds
29 url(r'^rss/$', AllThreadsFeed()),
30 url(r'^rss/$', AllThreadsFeed()),
30 url(r'^page/(?P<page>\w+)/rss/$', AllThreadsFeed()),
31 url(r'^page/(?P<page>\w+)/rss/$', AllThreadsFeed()),
31 url(r'^tag/(?P<tag_name>\w+)/rss/$', TagThreadsFeed()),
32 url(r'^tag/(?P<tag_name>\w+)/rss/$', TagThreadsFeed()),
32 url(r'^tag/(?P<tag_name>\w+)/page/(?P<page>\w+)/rss/$', TagThreadsFeed()),
33 url(r'^tag/(?P<tag_name>\w+)/page/(?P<page>\w+)/rss/$', TagThreadsFeed()),
33 url(r'^thread/(?P<post_id>\w+)/rss/$', ThreadPostsFeed()),
34 url(r'^thread/(?P<post_id>\w+)/rss/$', ThreadPostsFeed()),
34 ) No newline at end of file
35 )
@@ -1,219 +1,230 b''
1 from django.core.urlresolvers import reverse
1 from django.core.urlresolvers import reverse
2 from django.template import RequestContext
2 from django.template import RequestContext
3 from django.shortcuts import render, redirect, get_object_or_404
3 from django.shortcuts import render, redirect, get_object_or_404
4 from django.http import HttpResponseRedirect
4 from django.http import HttpResponseRedirect
5
5
6 from boards import forms
6 from boards import forms
7 import boards
7 import boards
8 from boards import utils
8 from boards import utils
9 from boards.forms import ThreadForm, PostForm, SettingsForm, PlainErrorList, \
9 from boards.forms import ThreadForm, PostForm, SettingsForm, PlainErrorList, \
10 ThreadCaptchaForm, PostCaptchaForm
10 ThreadCaptchaForm, PostCaptchaForm
11
11
12 from boards.models import Post, Admin, Tag
12 from boards.models import Post, Admin, Tag
13 import neboard
13 import neboard
14
14
15
15
16 def index(request, page=0):
16 def index(request, page=0):
17 context = RequestContext(request)
17 context = RequestContext(request)
18
18
19 if utils.need_include_captcha(request):
19 if utils.need_include_captcha(request):
20 threadFormClass = ThreadCaptchaForm
20 threadFormClass = ThreadCaptchaForm
21 kwargs = {'request': request}
21 kwargs = {'request': request}
22 else:
22 else:
23 threadFormClass = ThreadForm
23 threadFormClass = ThreadForm
24 kwargs = {}
24 kwargs = {}
25
25
26 if request.method == 'POST':
26 if request.method == 'POST':
27 form = threadFormClass(request.POST, request.FILES,
27 form = threadFormClass(request.POST, request.FILES,
28 error_class=PlainErrorList, **kwargs)
28 error_class=PlainErrorList, **kwargs)
29
29
30 if form.is_valid():
30 if form.is_valid():
31 return _new_post(request, form)
31 return _new_post(request, form)
32 else:
32 else:
33 form = threadFormClass(error_class=PlainErrorList, **kwargs)
33 form = threadFormClass(error_class=PlainErrorList, **kwargs)
34
34
35 threads = Post.objects.get_threads(page=int(page))
35 threads = Post.objects.get_threads(page=int(page))
36
36
37 # TODO Get rid of the duplicate code in index and tag views
37 # TODO Get rid of the duplicate code in index and tag views
38 context['threads'] = None if len(threads) == 0 else threads
38 context['threads'] = None if len(threads) == 0 else threads
39 context['form'] = form
39 context['form'] = form
40 context['tags'] = Tag.objects.get_popular_tags()
40 context['tags'] = Tag.objects.get_popular_tags()
41 context['theme'] = _get_theme(request)
41 context['theme'] = _get_theme(request)
42 context['pages'] = range(Post.objects.get_thread_page_count())
42 context['pages'] = range(Post.objects.get_thread_page_count())
43
43
44 return render(request, 'boards/posting_general.html',
44 return render(request, 'boards/posting_general.html',
45 context)
45 context)
46
46
47
47
48 def _new_post(request, form, thread_id=boards.models.NO_PARENT):
48 def _new_post(request, form, thread_id=boards.models.NO_PARENT):
49 """Add a new post (in thread or as a reply)."""
49 """Add a new post (in thread or as a reply)."""
50
50
51 data = form.cleaned_data
51 data = form.cleaned_data
52
52
53 title = data['title']
53 title = data['title']
54 text = data['text']
54 text = data['text']
55
55
56 if 'image' in data.keys():
56 if 'image' in data.keys():
57 image = data['image']
57 image = data['image']
58 else:
58 else:
59 image = None
59 image = None
60
60
61 ip = _get_client_ip(request)
61 ip = _get_client_ip(request)
62
62
63 tags = []
63 tags = []
64
64
65 new_thread = thread_id == boards.models.NO_PARENT
65 new_thread = thread_id == boards.models.NO_PARENT
66 if new_thread:
66 if new_thread:
67 tag_strings = data['tags']
67 tag_strings = data['tags']
68
68
69 if tag_strings:
69 if tag_strings:
70 tag_strings = tag_strings.split(' ')
70 tag_strings = tag_strings.split(' ')
71 for tag_name in tag_strings:
71 for tag_name in tag_strings:
72 tag_name = tag_name.strip()
72 tag_name = tag_name.strip()
73 if len(tag_name) > 0:
73 if len(tag_name) > 0:
74 tag, created = Tag.objects.get_or_create(name=tag_name)
74 tag, created = Tag.objects.get_or_create(name=tag_name)
75 tags.append(tag)
75 tags.append(tag)
76
76
77 # TODO Add a possibility to define a link image instead of an image file.
77 # TODO Add a possibility to define a link image instead of an image file.
78 # If a link is given, download the image automatically.
78 # If a link is given, download the image automatically.
79
79
80 post = Post.objects.create_post(title=title, text=text, ip=ip,
80 post = Post.objects.create_post(title=title, text=text, ip=ip,
81 parent_id=thread_id, image=image,
81 parent_id=thread_id, image=image,
82 tags=tags)
82 tags=tags)
83
83
84 thread_to_show = (post.id if new_thread else thread_id)
84 thread_to_show = (post.id if new_thread else thread_id)
85
85
86 if new_thread:
86 if new_thread:
87 return redirect(thread, post_id=thread_to_show)
87 return redirect(thread, post_id=thread_to_show)
88 else:
88 else:
89 return redirect(reverse(thread,
89 return redirect(reverse(thread,
90 kwargs={'post_id': thread_to_show}) + '#'
90 kwargs={'post_id': thread_to_show}) + '#'
91 + str(post.id))
91 + str(post.id))
92
92
93
93
94 def tag(request, tag_name, page=0):
94 def tag(request, tag_name, page=0):
95 """Get all tag threads (posts without a parent)."""
95 """Get all tag threads (posts without a parent)."""
96
96
97 tag = get_object_or_404(Tag, name=tag_name)
97 tag = get_object_or_404(Tag, name=tag_name)
98 threads = Post.objects.get_threads(tag=tag, page=int(page))
98 threads = Post.objects.get_threads(tag=tag, page=int(page))
99
99
100 if request.method == 'POST':
100 if request.method == 'POST':
101 form = ThreadForm(request.POST, request.FILES,
101 form = ThreadForm(request.POST, request.FILES,
102 error_class=PlainErrorList)
102 error_class=PlainErrorList)
103 if form.is_valid():
103 if form.is_valid():
104 return _new_post(request, form)
104 return _new_post(request, form)
105 else:
105 else:
106 form = forms.ThreadForm(initial={'tags': tag_name},
106 form = forms.ThreadForm(initial={'tags': tag_name},
107 error_class=PlainErrorList)
107 error_class=PlainErrorList)
108
108
109 context = RequestContext(request)
109 context = RequestContext(request)
110 context['threads'] = None if len(threads) == 0 else threads
110 context['threads'] = None if len(threads) == 0 else threads
111 context['tag'] = tag_name
111 context['tag'] = tag_name
112 context['tags'] = Tag.objects.get_popular_tags()
112 context['tags'] = Tag.objects.get_popular_tags()
113 context['theme'] = _get_theme(request)
113 context['theme'] = _get_theme(request)
114 context['pages'] = range(Post.objects.get_thread_page_count(tag=tag))
114 context['pages'] = range(Post.objects.get_thread_page_count(tag=tag))
115
115
116 context['form'] = form
116 context['form'] = form
117
117
118 return render(request, 'boards/posting_general.html',
118 return render(request, 'boards/posting_general.html',
119 context)
119 context)
120
120
121
121
122 def thread(request, post_id):
122 def thread(request, post_id):
123 """Get all thread posts"""
123 """Get all thread posts"""
124
124
125 if utils.need_include_captcha(request):
125 if utils.need_include_captcha(request):
126 postFormClass = PostCaptchaForm
126 postFormClass = PostCaptchaForm
127 kwargs = {'request': request}
127 kwargs = {'request': request}
128 else:
128 else:
129 postFormClass = PostForm
129 postFormClass = PostForm
130 kwargs = {}
130 kwargs = {}
131
131
132 if request.method == 'POST':
132 if request.method == 'POST':
133 form = postFormClass(request.POST, request.FILES,
133 form = postFormClass(request.POST, request.FILES,
134 error_class=PlainErrorList, **kwargs)
134 error_class=PlainErrorList, **kwargs)
135 if form.is_valid():
135 if form.is_valid():
136 return _new_post(request, form, post_id)
136 return _new_post(request, form, post_id)
137 else:
137 else:
138 form = postFormClass(error_class=PlainErrorList, **kwargs)
138 form = postFormClass(error_class=PlainErrorList, **kwargs)
139
139
140 posts = Post.objects.get_thread(post_id)
140 posts = Post.objects.get_thread(post_id)
141
141
142 context = RequestContext(request)
142 context = RequestContext(request)
143
143
144 context['posts'] = posts
144 context['posts'] = posts
145 context['form'] = form
145 context['form'] = form
146 context['tags'] = Tag.objects.get_popular_tags()
146 context['tags'] = Tag.objects.get_popular_tags()
147 context['theme'] = _get_theme(request)
147 context['theme'] = _get_theme(request)
148
148
149 return render(request, 'boards/thread.html', context)
149 return render(request, 'boards/thread.html', context)
150
150
151
151
152 def login(request):
152 def login(request):
153 """Log in as admin"""
153 """Log in as admin"""
154
154
155 if 'name' in request.POST and 'password' in request.POST:
155 if 'name' in request.POST and 'password' in request.POST:
156 request.session['admin'] = False
156 request.session['admin'] = False
157
157
158 isAdmin = len(Admin.objects.filter(name=request.POST['name'],
158 isAdmin = len(Admin.objects.filter(name=request.POST['name'],
159 password=request.POST[
159 password=request.POST[
160 'password'])) > 0
160 'password'])) > 0
161
161
162 if isAdmin:
162 if isAdmin:
163 request.session['admin'] = True
163 request.session['admin'] = True
164
164
165 response = HttpResponseRedirect('/')
165 response = HttpResponseRedirect('/')
166
166
167 else:
167 else:
168 response = render(request, 'boards/login.html', {'error': 'Login error'})
168 response = render(request, 'boards/login.html', {'error': 'Login error'})
169 else:
169 else:
170 response = render(request, 'boards/login.html', {})
170 response = render(request, 'boards/login.html', {})
171
171
172 return response
172 return response
173
173
174
174
175 def logout(request):
175 def logout(request):
176 request.session['admin'] = False
176 request.session['admin'] = False
177 return HttpResponseRedirect('/')
177 return HttpResponseRedirect('/')
178
178
179
179
180 def settings(request):
180 def settings(request):
181 context = RequestContext(request)
181 context = RequestContext(request)
182
182
183 if request.method == 'POST':
183 if request.method == 'POST':
184 form = SettingsForm(request.POST)
184 form = SettingsForm(request.POST)
185 if form.is_valid():
185 if form.is_valid():
186 selected_theme = form.cleaned_data['theme']
186 selected_theme = form.cleaned_data['theme']
187 request.session['theme'] = selected_theme
187 request.session['theme'] = selected_theme
188
188
189 return redirect(settings)
189 return redirect(settings)
190 else:
190 else:
191 selected_theme = _get_theme(request)
191 selected_theme = _get_theme(request)
192 form = SettingsForm(initial={'theme': selected_theme})
192 form = SettingsForm(initial={'theme': selected_theme})
193 context['form'] = form
193 context['form'] = form
194 context['tags'] = Tag.objects.get_popular_tags()
194 context['tags'] = Tag.objects.get_popular_tags()
195 context['theme'] = _get_theme(request)
195 context['theme'] = _get_theme(request)
196
196
197 return render(request, 'boards/settings.html', context)
197 return render(request, 'boards/settings.html', context)
198
198
199
199
200 def all_tags(request):
200 def all_tags(request):
201 context = RequestContext(request)
201 context = RequestContext(request)
202 context['tags'] = Tag.objects.get_popular_tags()
202 context['tags'] = Tag.objects.get_popular_tags()
203 context['theme'] = _get_theme(request)
203 context['theme'] = _get_theme(request)
204 context['all_tags'] = Tag.objects.get_not_empty_tags()
204 context['all_tags'] = Tag.objects.get_not_empty_tags()
205
205
206 return render(request, 'boards/tags.html', context)
206 return render(request, 'boards/tags.html', context)
207
207
208
208
209 def jump_to_post(request, post_id):
210 post = get_object_or_404(Post, id=post_id)
211
212 if boards.models.NO_PARENT == post.parent:
213 return redirect(thread, post_id=post.id)
214 else:
215 parent_thread = get_object_or_404(Post, id=post.parent)
216 return redirect(reverse(thread, kwargs={'post_id': parent_thread.id})
217 + '#' + str(post.id))
218
219
209 def _get_theme(request):
220 def _get_theme(request):
210 return request.session.get('theme', neboard.settings.DEFAULT_THEME)
221 return request.session.get('theme', neboard.settings.DEFAULT_THEME)
211
222
212
223
213 def _get_client_ip(request):
224 def _get_client_ip(request):
214 x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
225 x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
215 if x_forwarded_for:
226 if x_forwarded_for:
216 ip = x_forwarded_for.split(',')[-1].strip()
227 ip = x_forwarded_for.split(',')[-1].strip()
217 else:
228 else:
218 ip = request.META.get('REMOTE_ADDR')
229 ip = request.META.get('REMOTE_ADDR')
219 return ip
230 return ip
General Comments 0
You need to be logged in to leave comments. Login now