##// END OF EJS Templates
Fixed reflinks to threads parsing
neko259 -
r313:0f161354 default
parent child Browse files
Show More
@@ -1,106 +1,106 b''
1 1 from django.core.urlresolvers import reverse
2 2 import markdown
3 3 from markdown.inlinepatterns import Pattern
4 4 from markdown.util import etree
5 5 import boards
6 6
7 7 __author__ = 'neko259'
8 8
9 9
10 10 AUTOLINK_PATTERN = r'(https?://\S+)'
11 11 QUOTE_PATTERN = r'^(?<!>)(>[^>].+)$'
12 12 REFLINK_PATTERN = r'((>>)(\d+))'
13 13 SPOILER_PATTERN = r'%%(.+)%%'
14 14 COMMENT_PATTERN = r'^(//(.+))'
15 15
16 16 class AutolinkPattern(Pattern):
17 17 def handleMatch(self, m):
18 18 link_element = etree.Element('a')
19 19 href = m.group(2)
20 20 link_element.set('href', href)
21 21 link_element.text = href
22 22
23 23 return link_element
24 24
25 25
26 26 class QuotePattern(Pattern):
27 27 def handleMatch(self, m):
28 28 quote_element = etree.Element('span')
29 29 quote_element.set('class', 'quote')
30 30 quote_element.text = m.group(2)
31 31
32 32 return quote_element
33 33
34 34
35 35 class ReflinkPattern(Pattern):
36 36 def handleMatch(self, m):
37 37 post_id = m.group(4)
38 38
39 39 posts = boards.models.Post.objects.filter(id=post_id)
40 40 if posts.count() > 0:
41 41 ref_element = etree.Element('a')
42 42
43 43 post = posts[0]
44 44 if post.thread:
45 45 link = reverse(boards.views.thread, kwargs={'post_id': post.thread.id}) \
46 46 + '#' + post_id
47 47 else:
48 link = reverse(boards.views.thread, post_id=post_id)
48 link = reverse(boards.views.thread, kwargs={'post_id': post_id})
49 49
50 50 ref_element.set('href', link)
51 51 ref_element.text = m.group(2)
52 52
53 53 return ref_element
54 54
55 55
56 56 class SpoilerPattern(Pattern):
57 57 def handleMatch(self, m):
58 58 quote_element = etree.Element('span')
59 59 quote_element.set('class', 'spoiler')
60 60 quote_element.text = m.group(2)
61 61
62 62 return quote_element
63 63
64 64
65 65 class CommentPattern(Pattern):
66 66 def handleMatch(self, m):
67 67 quote_element = etree.Element('span')
68 68 quote_element.set('class', 'comment')
69 69 quote_element.text = '//' + m.group(3)
70 70
71 71 return quote_element
72 72
73 73
74 74 class NeboardMarkdown(markdown.Extension):
75 75 def extendMarkdown(self, md, md_globals):
76 76 self._add_neboard_patterns(md)
77 77 self._delete_patterns(md)
78 78
79 79 def _delete_patterns(self, md):
80 80 del md.parser.blockprocessors['quote']
81 81
82 82 del md.inlinePatterns['image_link']
83 83 del md.inlinePatterns['image_reference']
84 84
85 85 def _add_neboard_patterns(self, md):
86 86 autolink = AutolinkPattern(AUTOLINK_PATTERN, md)
87 87 quote = QuotePattern(QUOTE_PATTERN, md)
88 88 reflink = ReflinkPattern(REFLINK_PATTERN, md)
89 89 spoiler = SpoilerPattern(SPOILER_PATTERN, md)
90 90 comment = CommentPattern(COMMENT_PATTERN, md)
91 91
92 92 md.inlinePatterns[u'autolink_ext'] = autolink
93 93 md.inlinePatterns[u'spoiler'] = spoiler
94 94 md.inlinePatterns[u'comment'] = comment
95 95 md.inlinePatterns[u'reflink'] = reflink
96 96 md.inlinePatterns[u'quote'] = quote
97 97
98 98
99 99 def makeExtension(configs=None):
100 100 return NeboardMarkdown(configs=configs)
101 101
102 102 neboard_extension = makeExtension()
103 103
104 104
105 105 def markdown_extended(markup):
106 106 return markdown.markdown(markup, [neboard_extension], safe_mode=True)
@@ -1,478 +1,478 b''
1 1 import hashlib
2 2 import string
3 3 from django.core import serializers
4 4 from django.core.urlresolvers import reverse
5 5 from django.http import HttpResponseRedirect
6 6 from django.http.response import HttpResponse
7 7 from django.template import RequestContext
8 8 from django.shortcuts import render, redirect, get_object_or_404
9 9 from django.utils import timezone
10 10
11 11 from boards import forms
12 12 import boards
13 13 from boards import utils
14 14 from boards.forms import ThreadForm, PostForm, SettingsForm, PlainErrorList, \
15 15 ThreadCaptchaForm, PostCaptchaForm, LoginForm, ModeratorSettingsForm
16 16
17 17 from boards.models import Post, Tag, Ban, User, RANK_USER, SETTING_MODERATE, \
18 18 REGEX_REPLY
19 19 from boards import authors
20 20 from boards.utils import get_client_ip
21 21 import neboard
22 22 import re
23 23
24 24
25 25 def index(request, page=0):
26 26 context = _init_default_context(request)
27 27
28 28 if utils.need_include_captcha(request):
29 29 threadFormClass = ThreadCaptchaForm
30 30 kwargs = {'request': request}
31 31 else:
32 32 threadFormClass = ThreadForm
33 33 kwargs = {}
34 34
35 35 if request.method == 'POST':
36 36 form = threadFormClass(request.POST, request.FILES,
37 37 error_class=PlainErrorList, **kwargs)
38 38 form.session = request.session
39 39
40 40 if form.is_valid():
41 41 return _new_post(request, form)
42 42 if form.need_to_ban:
43 43 # Ban user because he is suspected to be a bot
44 44 _ban_current_user(request)
45 45 else:
46 46 form = threadFormClass(error_class=PlainErrorList, **kwargs)
47 47
48 48 threads = []
49 49 for thread in Post.objects.get_threads(page=int(page)):
50 50 threads.append({'thread': thread,
51 51 'bumpable': thread.can_bump()})
52 52
53 53 context['threads'] = None if len(threads) == 0 else threads
54 54 context['form'] = form
55 55 context['pages'] = range(Post.objects.get_thread_page_count())
56 56
57 57 return render(request, 'boards/posting_general.html',
58 58 context)
59 59
60 60
61 61 def _new_post(request, form, thread_id=boards.models.NO_PARENT):
62 62 """Add a new post (in thread or as a reply)."""
63 63
64 64 ip = get_client_ip(request)
65 65 is_banned = Ban.objects.filter(ip=ip).exists()
66 66
67 67 if is_banned:
68 68 return redirect(you_are_banned)
69 69
70 70 data = form.cleaned_data
71 71
72 72 title = data['title']
73 73 text = data['text']
74 74
75 75 text = _remove_invalid_links(text)
76 76
77 77 if 'image' in data.keys():
78 78 image = data['image']
79 79 else:
80 80 image = None
81 81
82 82 tags = []
83 83
84 84 new_thread = thread_id == boards.models.NO_PARENT
85 85 if new_thread:
86 86 tag_strings = data['tags']
87 87
88 88 if tag_strings:
89 89 tag_strings = tag_strings.split(' ')
90 90 for tag_name in tag_strings:
91 91 tag_name = string.lower(tag_name.strip())
92 92 if len(tag_name) > 0:
93 93 tag, created = Tag.objects.get_or_create(name=tag_name)
94 94 tags.append(tag)
95 95
96 96 linked_tags = tag.get_linked_tags()
97 97 if len(linked_tags) > 0:
98 98 tags.extend(linked_tags)
99 99
100 100 op = None if thread_id == boards.models.NO_PARENT else \
101 101 get_object_or_404(Post, id=thread_id)
102 102 post = Post.objects.create_post(title=title, text=text, ip=ip,
103 103 thread=op, image=image,
104 104 tags=tags, user=_get_user(request))
105 105
106 106 thread_to_show = (post.id if new_thread else thread_id)
107 107
108 108 if new_thread:
109 109 return redirect(thread, post_id=thread_to_show)
110 110 else:
111 111 return redirect(reverse(thread, kwargs={'post_id': thread_to_show}) +
112 112 '#' + str(post.id))
113 113
114 114
115 115 def tag(request, tag_name, page=0):
116 116 """
117 117 Get all tag threads. Threads are split in pages, so some page is
118 118 requested. Default page is 0.
119 119 """
120 120
121 121 tag = get_object_or_404(Tag, name=tag_name)
122 122 threads = []
123 123 for thread in Post.objects.get_threads(tag=tag, page=int(page)):
124 124 threads.append({'thread': thread,
125 125 'bumpable': thread.can_bump()})
126 126
127 127 if request.method == 'POST':
128 128 form = ThreadForm(request.POST, request.FILES,
129 129 error_class=PlainErrorList)
130 130 form.session = request.session
131 131
132 132 if form.is_valid():
133 133 return _new_post(request, form)
134 134 if form.need_to_ban:
135 135 # Ban user because he is suspected to be a bot
136 136 _ban_current_user(request)
137 137 else:
138 138 form = forms.ThreadForm(initial={'tags': tag_name},
139 139 error_class=PlainErrorList)
140 140
141 141 context = _init_default_context(request)
142 142 context['threads'] = None if len(threads) == 0 else threads
143 143 context['tag'] = tag
144 144 context['pages'] = range(Post.objects.get_thread_page_count(tag=tag))
145 145
146 146 context['form'] = form
147 147
148 148 return render(request, 'boards/posting_general.html',
149 149 context)
150 150
151 151
152 152 def thread(request, post_id):
153 153 """Get all thread posts"""
154 154
155 155 if utils.need_include_captcha(request):
156 156 postFormClass = PostCaptchaForm
157 157 kwargs = {'request': request}
158 158 else:
159 159 postFormClass = PostForm
160 160 kwargs = {}
161 161
162 162 if request.method == 'POST':
163 163 form = postFormClass(request.POST, request.FILES,
164 164 error_class=PlainErrorList, **kwargs)
165 165 form.session = request.session
166 166
167 167 if form.is_valid():
168 168 return _new_post(request, form, post_id)
169 169 if form.need_to_ban:
170 170 # Ban user because he is suspected to be a bot
171 171 _ban_current_user(request)
172 172 else:
173 173 form = postFormClass(error_class=PlainErrorList, **kwargs)
174 174
175 175 posts = Post.objects.get_thread(post_id)
176 176
177 177 context = _init_default_context(request)
178 178
179 179 context['posts'] = posts
180 180 context['form'] = form
181 181 context['bumpable'] = posts[0].can_bump()
182 182 if context['bumpable']:
183 183 context['posts_left'] = neboard.settings.MAX_POSTS_PER_THREAD - len(
184 184 posts)
185 185 context['bumplimit_progress'] = str(float(context['posts_left']) /
186 186 neboard.settings.MAX_POSTS_PER_THREAD * 100)
187 187
188 188 return render(request, 'boards/thread.html', context)
189 189
190 190
191 191 def login(request):
192 192 """Log in with user id"""
193 193
194 194 context = _init_default_context(request)
195 195
196 196 if request.method == 'POST':
197 197 form = LoginForm(request.POST, request.FILES,
198 198 error_class=PlainErrorList)
199 199 form.session = request.session
200 200
201 201 if form.is_valid():
202 202 user = User.objects.get(user_id=form.cleaned_data['user_id'])
203 203 request.session['user_id'] = user.id
204 204 return redirect(index)
205 205
206 206 else:
207 207 form = LoginForm()
208 208
209 209 context['form'] = form
210 210
211 211 return render(request, 'boards/login.html', context)
212 212
213 213
214 214 def settings(request):
215 215 """User's settings"""
216 216
217 217 context = _init_default_context(request)
218 218 user = _get_user(request)
219 219 is_moderator = user.is_moderator()
220 220
221 221 if request.method == 'POST':
222 222 if is_moderator:
223 223 form = ModeratorSettingsForm(request.POST,
224 224 error_class=PlainErrorList)
225 225 else:
226 226 form = SettingsForm(request.POST, error_class=PlainErrorList)
227 227
228 228 if form.is_valid():
229 229 selected_theme = form.cleaned_data['theme']
230 230
231 231 user.save_setting('theme', selected_theme)
232 232
233 233 if is_moderator:
234 234 moderate = form.cleaned_data['moderate']
235 235 user.save_setting(SETTING_MODERATE, moderate)
236 236
237 237 return redirect(settings)
238 238 else:
239 239 selected_theme = _get_theme(request)
240 240
241 241 if is_moderator:
242 242 form = ModeratorSettingsForm(initial={'theme': selected_theme,
243 243 'moderate': context['moderator']},
244 244 error_class=PlainErrorList)
245 245 else:
246 246 form = SettingsForm(initial={'theme': selected_theme},
247 247 error_class=PlainErrorList)
248 248
249 249 context['form'] = form
250 250
251 251 return render(request, 'boards/settings.html', context)
252 252
253 253
254 254 def all_tags(request):
255 255 """All tags list"""
256 256
257 257 context = _init_default_context(request)
258 258 context['all_tags'] = Tag.objects.get_not_empty_tags()
259 259
260 260 return render(request, 'boards/tags.html', context)
261 261
262 262
263 263 def jump_to_post(request, post_id):
264 264 """Determine thread in which the requested post is and open it's page"""
265 265
266 266 post = get_object_or_404(Post, id=post_id)
267 267
268 268 if not post.thread:
269 return redirect(thread, post_id=post.id)
269 return redirect(thread, post_id=post.id)
270 270 else:
271 271 return redirect(reverse(thread, kwargs={'post_id': post.thread.id})
272 272 + '#' + str(post.id))
273 273
274 274
275 275 def authors(request):
276 276 """Show authors list"""
277 277
278 278 context = _init_default_context(request)
279 279 context['authors'] = boards.authors.authors
280 280
281 281 return render(request, 'boards/authors.html', context)
282 282
283 283
284 284 def delete(request, post_id):
285 285 """Delete post"""
286 286
287 287 user = _get_user(request)
288 288 post = get_object_or_404(Post, id=post_id)
289 289
290 290 if user.is_moderator():
291 291 # TODO Show confirmation page before deletion
292 292 Post.objects.delete_post(post)
293 293
294 294 if not post.thread:
295 295 return _redirect_to_next(request)
296 296 else:
297 297 return redirect(thread, post_id=post.thread.id)
298 298
299 299
300 300 def ban(request, post_id):
301 301 """Ban user"""
302 302
303 303 user = _get_user(request)
304 304 post = get_object_or_404(Post, id=post_id)
305 305
306 306 if user.is_moderator():
307 307 # TODO Show confirmation page before ban
308 308 Ban.objects.get_or_create(ip=post.poster_ip)
309 309
310 310 return _redirect_to_next(request)
311 311
312 312
313 313 def you_are_banned(request):
314 314 """Show the page that notifies that user is banned"""
315 315
316 316 context = _init_default_context(request)
317 317 return render(request, 'boards/staticpages/banned.html', context)
318 318
319 319
320 320 def page_404(request):
321 321 """Show page 404 (not found error)"""
322 322
323 323 context = _init_default_context(request)
324 324 return render(request, 'boards/404.html', context)
325 325
326 326
327 327 def tag_subscribe(request, tag_name):
328 328 """Add tag to favorites"""
329 329
330 330 user = _get_user(request)
331 331 tag = get_object_or_404(Tag, name=tag_name)
332 332
333 333 if not tag in user.fav_tags.all():
334 334 user.fav_tags.add(tag)
335 335
336 336 return _redirect_to_next(request)
337 337
338 338
339 339 def tag_unsubscribe(request, tag_name):
340 340 """Remove tag from favorites"""
341 341
342 342 user = _get_user(request)
343 343 tag = get_object_or_404(Tag, name=tag_name)
344 344
345 345 if tag in user.fav_tags.all():
346 346 user.fav_tags.remove(tag)
347 347
348 348 return _redirect_to_next(request)
349 349
350 350
351 351 def static_page(request, name):
352 352 """Show a static page that needs only tags list and a CSS"""
353 353
354 354 context = _init_default_context(request)
355 355 return render(request, 'boards/staticpages/' + name + '.html', context)
356 356
357 357
358 358 def api_get_post(request, post_id):
359 359 """
360 360 Get the JSON of a post. This can be
361 361 used as and API for external clients.
362 362 """
363 363
364 364 post = get_object_or_404(Post, id=post_id)
365 365
366 366 json = serializers.serialize("json", [post], fields=(
367 367 "pub_time", "_text_rendered", "title", "text", "image",
368 368 "image_width", "image_height", "replies", "tags"
369 369 ))
370 370
371 371 return HttpResponse(content=json)
372 372
373 373
374 374 def get_post(request, post_id):
375 375 """Get the html of a post. Used for popups."""
376 376
377 377 post = get_object_or_404(Post, id=post_id)
378 378
379 379 context = RequestContext(request)
380 380 context["post"] = post
381 381
382 382 return render(request, 'boards/post.html', context)
383 383
384 384
385 385 def _get_theme(request, user=None):
386 386 """Get user's CSS theme"""
387 387
388 388 if not user:
389 389 user = _get_user(request)
390 390 theme = user.get_setting('theme')
391 391 if not theme:
392 392 theme = neboard.settings.DEFAULT_THEME
393 393
394 394 return theme
395 395
396 396
397 397 def _init_default_context(request):
398 398 """Create context with default values that are used in most views"""
399 399
400 400 context = RequestContext(request)
401 401
402 402 user = _get_user(request)
403 403 context['user'] = user
404 404 context['tags'] = user.get_sorted_fav_tags()
405 405
406 406 theme = _get_theme(request, user)
407 407 context['theme'] = theme
408 408 context['theme_css'] = 'css/' + theme + '/base_page.css'
409 409
410 410 # This shows the moderator panel
411 411 moderate = user.get_setting(SETTING_MODERATE)
412 412 if moderate == 'True':
413 413 context['moderator'] = user.is_moderator()
414 414 else:
415 415 context['moderator'] = False
416 416
417 417 return context
418 418
419 419
420 420 def _get_user(request):
421 421 """
422 422 Get current user from the session. If the user does not exist, create
423 423 a new one.
424 424 """
425 425
426 426 session = request.session
427 427 if not 'user_id' in session:
428 428 request.session.save()
429 429
430 430 md5 = hashlib.md5()
431 431 md5.update(session.session_key)
432 432 new_id = md5.hexdigest()
433 433
434 434 time_now = timezone.now()
435 435 user = User.objects.create(user_id=new_id, rank=RANK_USER,
436 436 registration_time=time_now)
437 437
438 438 session['user_id'] = user.id
439 439 else:
440 440 user = User.objects.get(id=session['user_id'])
441 441
442 442 return user
443 443
444 444
445 445 def _redirect_to_next(request):
446 446 """
447 447 If a 'next' parameter was specified, redirect to the next page. This is
448 448 used when the user is required to return to some page after the current
449 449 view has finished its work.
450 450 """
451 451
452 452 if 'next' in request.GET:
453 453 next_page = request.GET['next']
454 454 return HttpResponseRedirect(next_page)
455 455 else:
456 456 return redirect(index)
457 457
458 458
459 459 def _ban_current_user(request):
460 460 """Add current user to the IP ban list"""
461 461
462 462 ip = utils.get_client_ip(request)
463 463 Ban.objects.get_or_create(ip=ip)
464 464
465 465
466 466 def _remove_invalid_links(text):
467 467 """
468 468 Replace invalid links in posts so that they won't be parsed.
469 469 Invalid links are links to non-existent posts
470 470 """
471 471
472 472 for reply_number in re.finditer(REGEX_REPLY, text):
473 473 id = reply_number.group(1)
474 474 post = Post.objects.filter(id=id)
475 475 if not post.exists():
476 476 text = string.replace(text, '>>' + id, id)
477 477
478 478 return text
General Comments 0
You need to be logged in to leave comments. Login now