##// END OF EJS Templates
Implemented RSS support. This fixes #11
neko259 -
r89:84c33124 default
parent child Browse files
Show More
@@ -0,0 +1,74 b''
1 from django.contrib.syndication.views import Feed
2 from django.core.urlresolvers import reverse
3 from django.shortcuts import get_object_or_404
4 from boards.models import Post, Tag, NO_PARENT
5 from neboard import settings
6
7 __author__ = 'neko259'
8
9
10 class AllThreadsFeed(Feed):
11 title = settings.SITE_NAME + ' - All threads'
12 link = '/'
13 description_template = 'boards/rss/post.html'
14
15 def items(self):
16 return Post.objects.get_threads(order_by='-pub_time')
17
18 def item_title(self, item):
19 return item.title
20
21 def item_link(self, item):
22 return reverse('thread', args={item.id})
23
24 def item_pubdate(self, item):
25 return item.pub_time
26
27
28 class TagThreadsFeed(Feed):
29 link = '/'
30 description_template = 'boards/rss/post.html'
31
32 def items(self, obj):
33 return Post.objects.get_threads(tag=obj, order_by='-pub_time')
34
35 def get_object(self, request, tag_name):
36 return get_object_or_404(Tag, name=tag_name)
37
38 def item_title(self, item):
39 return item.title
40
41 def item_link(self, item):
42 return reverse('thread', args={item.id})
43
44 def item_pubdate(self, item):
45 return item.pub_time
46
47 def title(self, obj):
48 return obj.name
49
50
51 class ThreadPostsFeed(Feed):
52 link = '/'
53 description_template = 'boards/rss/post.html'
54
55 def items(self, obj):
56 return Post.objects.get_thread(opening_post_id=obj)
57
58 def get_object(self, request, post_id):
59 return post_id
60
61 def item_title(self, item):
62 return item.title
63
64 def item_link(self, item):
65 if NO_PARENT == item.parent:
66 return reverse('thread', args={item.id})
67 else:
68 return reverse('thread', args={item.parent}) + "#" + str(item.id)
69
70 def item_pubdate(self, item):
71 return item.pub_time
72
73 def title(self, obj):
74 return get_object_or_404(Post, id=obj).title No newline at end of file
@@ -0,0 +1,15 b''
1 {% load i18n %}
2
3 {% if obj.image %}
4 <img src="{{ obj.image.url_200x150 }}"
5 alt="{% trans 'Post image' %}" />
6 {% endif %}
7 {{ obj.text.rendered|safe }}
8 {% if obj.tags.all %}
9 <p>
10 {% trans 'Tags' %}:
11 {% for tag in obj.tags.all %}
12 {{ tag.name }}
13 {% endfor %}
14 </p>
15 {% endif %} No newline at end of file
@@ -1,83 +1,83 b''
1 import markdown
1 import markdown
2 from markdown.inlinepatterns import Pattern
2 from markdown.inlinepatterns import Pattern
3 from markdown.util import etree
3 from markdown.util import etree
4
4
5 __author__ = 'vurdalak'
5 __author__ = 'neko259'
6
6
7
7
8 class AutolinkPattern(Pattern):
8 class AutolinkPattern(Pattern):
9 def handleMatch(self, m):
9 def handleMatch(self, m):
10 link_element = etree.Element('a')
10 link_element = etree.Element('a')
11 href = m.group(2)
11 href = m.group(2)
12 link_element.set('href', href)
12 link_element.set('href', href)
13 link_element.text = href
13 link_element.text = href
14
14
15 return link_element
15 return link_element
16
16
17
17
18 class QuotePattern(Pattern):
18 class QuotePattern(Pattern):
19 def handleMatch(self, m):
19 def handleMatch(self, m):
20 quote_element = etree.Element('span')
20 quote_element = etree.Element('span')
21 quote_element.set('class', 'quote')
21 quote_element.set('class', 'quote')
22 quote_element.text = m.group(3)
22 quote_element.text = m.group(3)
23
23
24 return quote_element
24 return quote_element
25
25
26
26
27 class ReflinkPattern(Pattern):
27 class ReflinkPattern(Pattern):
28 def handleMatch(self, m):
28 def handleMatch(self, m):
29 ref_element = etree.Element('a')
29 ref_element = etree.Element('a')
30 post_id = m.group(4)
30 post_id = m.group(4)
31 ref_element.set('href', '#' + str(post_id))
31 ref_element.set('href', '#' + str(post_id))
32 ref_element.text = '#' + post_id
32 ref_element.text = '#' + post_id
33
33
34 return ref_element
34 return ref_element
35
35
36
36
37 class SpoilerPattern(Pattern):
37 class SpoilerPattern(Pattern):
38 def handleMatch(self, m):
38 def handleMatch(self, m):
39 quote_element = etree.Element('span')
39 quote_element = etree.Element('span')
40 quote_element.set('class', 'spoiler')
40 quote_element.set('class', 'spoiler')
41 quote_element.text = m.group(2)
41 quote_element.text = m.group(2)
42
42
43 return quote_element
43 return quote_element
44
44
45
45
46 class CommentPattern(Pattern):
46 class CommentPattern(Pattern):
47 def handleMatch(self, m):
47 def handleMatch(self, m):
48 quote_element = etree.Element('span')
48 quote_element = etree.Element('span')
49 quote_element.set('class', 'comment')
49 quote_element.set('class', 'comment')
50 quote_element.text = '//' + m.group(3)
50 quote_element.text = '//' + m.group(3)
51
51
52 return quote_element
52 return quote_element
53
53
54
54
55 class NeboardMarkdown(markdown.Extension):
55 class NeboardMarkdown(markdown.Extension):
56 AUTOLINK_PATTERN = r'(https?://\S+)'
56 AUTOLINK_PATTERN = r'(https?://\S+)'
57 QUOTE_PATTERN = r'^(&gt;){1}(.+)'
57 QUOTE_PATTERN = r'^(&gt;){1}(.+)'
58 REFLINK_PATTERN = r'((&gt;){2}(\d+))'
58 REFLINK_PATTERN = r'((&gt;){2}(\d+))'
59 SPOILER_PATTERN = r'%%(.+)%%'
59 SPOILER_PATTERN = r'%%(.+)%%'
60 COMMENT_PATTERN = r'^(//(.+))'
60 COMMENT_PATTERN = r'^(//(.+))'
61
61
62 def extendMarkdown(self, md, md_globals):
62 def extendMarkdown(self, md, md_globals):
63 autolink = AutolinkPattern(self.AUTOLINK_PATTERN, md)
63 autolink = AutolinkPattern(self.AUTOLINK_PATTERN, md)
64 quote = QuotePattern(self.QUOTE_PATTERN, md)
64 quote = QuotePattern(self.QUOTE_PATTERN, md)
65 reflink = ReflinkPattern(self.REFLINK_PATTERN, md)
65 reflink = ReflinkPattern(self.REFLINK_PATTERN, md)
66 spoiler = SpoilerPattern(self.SPOILER_PATTERN, md)
66 spoiler = SpoilerPattern(self.SPOILER_PATTERN, md)
67 comment = CommentPattern(self.COMMENT_PATTERN, md)
67 comment = CommentPattern(self.COMMENT_PATTERN, md)
68
68
69 md.inlinePatterns[u'autolink_ext'] = autolink
69 md.inlinePatterns[u'autolink_ext'] = autolink
70 md.inlinePatterns.add(u'reflink', reflink, '<entity')
70 md.inlinePatterns.add(u'reflink', reflink, '<entity')
71 md.inlinePatterns.add(u'quote', quote, '<entity')
71 md.inlinePatterns.add(u'quote', quote, '<entity')
72 md.inlinePatterns[u'spoiler'] = spoiler
72 md.inlinePatterns[u'spoiler'] = spoiler
73 md.inlinePatterns[u'comment'] = comment
73 md.inlinePatterns[u'comment'] = comment
74
74
75
75
76 def makeExtension(configs=None):
76 def makeExtension(configs=None):
77 return NeboardMarkdown(configs=configs)
77 return NeboardMarkdown(configs=configs)
78
78
79 neboard_extension = makeExtension()
79 neboard_extension = makeExtension()
80
80
81
81
82 def markdown_extended(markup):
82 def markdown_extended(markup):
83 return markdown.markdown(markup, [neboard_extension])
83 return markdown.markdown(markup, [neboard_extension])
@@ -1,45 +1,46 b''
1 {% load staticfiles %}
1 {% load staticfiles %}
2 {% load i18n %}
2 {% load i18n %}
3
3
4 <!DOCTYPE html>
4 <!DOCTYPE html>
5 <html>
5 <html>
6 <head>
6 <head>
7 <link rel="stylesheet" type="text/css"
7 <link rel="stylesheet" type="text/css"
8 href="{{ STATIC_URL }}css/jquery.fancybox.css" media="all"/>
8 href="{{ STATIC_URL }}css/jquery.fancybox.css" media="all"/>
9 <link rel="stylesheet" type="text/css"
9 <link rel="stylesheet" type="text/css"
10 href="{{ STATIC_URL }}css/{{ theme }}/base_page.css" media="all"/>
10 href="{{ STATIC_URL }}css/{{ theme }}/base_page.css" media="all"/>
11 <meta name="viewport" content="width=device-width, initial-scale=1"/>
11 <meta name="viewport" content="width=device-width, initial-scale=1"/>
12 <meta charset="utf-8"/>
12 <meta charset="utf-8"/>
13 {% block head %}{% endblock %}
13 {% block head %}{% endblock %}
14 </head>
14 </head>
15 <body>
15 <body>
16 <script src="{{ STATIC_URL }}js/jquery-2.0.1.min.js"></script>
16 <script src="{{ STATIC_URL }}js/jquery-2.0.1.min.js"></script>
17 <script src="{{ STATIC_URL }}js/jquery.fancybox.pack.js"></script>
17 <script src="{{ STATIC_URL }}js/jquery.fancybox.pack.js"></script>
18 <script src="{{ STATIC_URL }}js/main.js"></script>
18 <script src="{{ STATIC_URL }}js/main.js"></script>
19 <div id="admin_panel">
19 <div id="admin_panel">
20
20
21 {% if request.session.admin == True %}
21 {% if request.session.admin == True %}
22 Admin panel TODO: Need to implement <BR />
22 Admin panel TODO: Need to implement <BR />
23 {% endif %}
23 {% endif %}
24
24
25 </div>
25 </div>
26
26
27 <div class="navigation_panel">
27 <div class="navigation_panel">
28 <a class="link" href="{% url 'index' %}">{% trans "All threads" %}</a>
28 <a class="link" href="{% url 'index' %}">{% trans "All threads" %}</a>
29 {% for tag in tags %}
29 {% for tag in tags %}
30 <a class="tag" href=" {% url 'tag' tag_name=tag.name %}">
30 <a class="tag" href=" {% url 'tag' tag_name=tag.name %}">
31 {{ tag.name }}</a>({{ tag.get_post_count }})
31 {{ tag.name }}</a>({{ tag.get_post_count }})
32 {% endfor %}
32 {% endfor %}
33 <a class="tag" href="{% url 'tags' %}">[...]</a>
33 <a class="tag" href="{% url 'tags' %}">[...]</a>
34 <a class="link" href="{% url 'settings' %}">{% trans 'Settings' %}</a>
34 <a class="link" href="{% url 'settings' %}">{% trans 'Settings' %}</a>
35 </div>
35 </div>
36
36
37 {% block content %}{% endblock %}
37 {% block content %}{% endblock %}
38
38
39 <div class="navigation_panel">
39 <div class="navigation_panel">
40 {% block metapanel %}{% endblock %}
40 {% block metapanel %}{% endblock %}
41 [<a href="rss/">RSS</a>]
41 <a class="link" href="#top">{% trans 'Up' %}</a>
42 <a class="link" href="#top">{% trans 'Up' %}</a>
42 </div>
43 </div>
43
44
44 </body>
45 </body>
45 </html> No newline at end of file
46 </html>
@@ -1,218 +1,218 b''
1 # coding=utf-8
1 # coding=utf-8
2 from django.utils.unittest import TestCase
2 from django.utils.unittest import TestCase
3 from django.test.client import Client
3 from django.test.client import Client
4
4
5 import boards
5 import boards
6
6
7 from boards.models import Post, Admin, Tag
7 from boards.models import Post, Admin, Tag
8 from neboard import settings
8 from neboard import settings
9
9
10 TEST_TEXT = 'test text'
10 TEST_TEXT = 'test text'
11
11
12 NEW_THREAD_PAGE = '/'
12 NEW_THREAD_PAGE = '/'
13 THREAD_PAGE_ONE = '/thread/1/'
13 THREAD_PAGE_ONE = '/thread/1/'
14 THREAD_PAGE = '/thread/'
14 THREAD_PAGE = '/thread/'
15 TAG_PAGE = '/tag/'
15 TAG_PAGE = '/tag/'
16 HTTP_CODE_REDIRECT = 302
16 HTTP_CODE_REDIRECT = 302
17 HTTP_CODE_OK = 200
17 HTTP_CODE_OK = 200
18 HTTP_CODE_NOT_FOUND = 404
18 HTTP_CODE_NOT_FOUND = 404
19
19
20
20
21 class BoardTests(TestCase):
21 class BoardTests(TestCase):
22 def _create_post(self):
22 def _create_post(self):
23 return Post.objects.create_post(title='title',
23 return Post.objects.create_post(title='title',
24 text='text')
24 text='text')
25
25
26 def test_post_add(self):
26 def test_post_add(self):
27 post = self._create_post()
27 post = self._create_post()
28
28
29 self.assertIsNotNone(post)
29 self.assertIsNotNone(post)
30 self.assertEqual(boards.models.NO_PARENT, post.parent)
30 self.assertEqual(boards.models.NO_PARENT, post.parent)
31
31
32 def test_delete_post(self):
32 def test_delete_post(self):
33 post = self._create_post()
33 post = self._create_post()
34 post_id = post.id
34 post_id = post.id
35
35
36 Post.objects.delete_post(post)
36 Post.objects.delete_post(post)
37
37
38 self.assertFalse(Post.objects.exists(post_id))
38 self.assertFalse(Post.objects.exists(post_id))
39
39
40 def test_delete_posts_by_ip(self):
40 def test_delete_posts_by_ip(self):
41 post = self._create_post()
41 post = self._create_post()
42 post_id = post.id
42 post_id = post.id
43
43
44 Post.objects.delete_posts_by_ip('0.0.0.0')
44 Post.objects.delete_posts_by_ip('0.0.0.0')
45
45
46 self.assertFalse(Post.objects.exists(post_id))
46 self.assertFalse(Post.objects.exists(post_id))
47
47
48 # Authentication tests
48 # Authentication tests
49
49
50 def _create_test_user(self):
50 def _create_test_user(self):
51 admin = Admin(name='test_username12313584353165',
51 admin = Admin(name='test_username12313584353165',
52 password='test_userpassword135135512')
52 password='test_userpassword135135512')
53
53
54 admin.save()
54 admin.save()
55 return admin
55 return admin
56
56
57 def test_admin_login(self):
57 def test_admin_login(self):
58 client = Client()
58 client = Client()
59
59
60 self.assertFalse('admin' in client.session)
60 self.assertFalse('admin' in client.session)
61
61
62 admin = self._create_test_user()
62 admin = self._create_test_user()
63
63
64 response = client.post('/login',
64 response = client.post('/login',
65 {'name': admin.name, 'password': admin.password})
65 {'name': admin.name, 'password': admin.password})
66
66
67 # it means that login passed and user are redirected to another page
67 # it means that login passed and user are redirected to another page
68 self.assertEqual(302, response.status_code)
68 self.assertEqual(302, response.status_code)
69
69
70 self.assertTrue('admin' in client.session)
70 self.assertTrue('admin' in client.session)
71 self.assertTrue(client.session['admin'])
71 self.assertTrue(client.session['admin'])
72
72
73 admin.delete()
73 admin.delete()
74
74
75 wrong_name = 'sd2f1s3d21fs3d21f'
75 wrong_name = 'sd2f1s3d21fs3d21f'
76 wrong_password = 'sd2f1s3d21fs3d21fsdfsd'
76 wrong_password = 'sd2f1s3d21fs3d21fsdfsd'
77
77
78 client.post('/login', {'name': wrong_name, 'password': wrong_password})
78 client.post('/login', {'name': wrong_name, 'password': wrong_password})
79 self.assertFalse(client.session['admin'])
79 self.assertFalse(client.session['admin'])
80
80
81 def test_admin_logout(self):
81 def test_admin_logout(self):
82 client = Client()
82 client = Client()
83
83
84 self.assertFalse('admin' in client.session)
84 self.assertFalse('admin' in client.session)
85
85
86 admin = self._create_test_user()
86 admin = self._create_test_user()
87
87
88 client.post('/login',
88 client.post('/login',
89 {'name': admin.name, 'password': admin.password})
89 {'name': admin.name, 'password': admin.password})
90
90
91 self.assertTrue(client.session['admin'])
91 self.assertTrue(client.session['admin'])
92
92
93 client.get('/logout')
93 client.get('/logout')
94
94
95 self.assertFalse(client.session['admin'])
95 self.assertFalse(client.session['admin'])
96
96
97 admin.delete()
97 admin.delete()
98
98
99 def test_get_thread(self):
99 def test_get_thread(self):
100 opening_post = self._create_post()
100 opening_post = self._create_post()
101 op_id = opening_post.id
101 op_id = opening_post.id
102
102
103 for i in range(0, 2):
103 for i in range(0, 2):
104 Post.objects.create_post('title', 'text',
104 Post.objects.create_post('title', 'text',
105 parent_id=op_id)
105 parent_id=op_id)
106
106
107 thread = Post.objects.get_thread(op_id)
107 thread = Post.objects.get_thread(op_id)
108
108
109 self.assertEqual(3, len(thread))
109 self.assertEqual(3, len(thread))
110
110
111 def test_create_post_with_tag(self):
111 def test_create_post_with_tag(self):
112 tag = Tag.objects.create(name='test_tag')
112 tag = Tag.objects.create(name='test_tag')
113 post = Post.objects.create_post(title='title', text='text', tags=[tag])
113 post = Post.objects.create_post(title='title', text='text', tags=[tag])
114 self.assertIsNotNone(post)
114 self.assertIsNotNone(post)
115
115
116 def test_thread_max_count(self):
116 def test_thread_max_count(self):
117 for i in range(settings.MAX_THREAD_COUNT + 1):
117 for i in range(settings.MAX_THREAD_COUNT + 1):
118 self._create_post()
118 self._create_post()
119
119
120 self.assertEqual(settings.MAX_THREAD_COUNT,
120 self.assertEqual(settings.MAX_THREAD_COUNT,
121 len(Post.objects.get_threads()))
121 len(Post.objects.get_threads()))
122
122
123 def test_get(self):
123 def test_get(self):
124 """Test if the get computes properly"""
124 """Test if the get computes properly"""
125
125
126 post = self._create_post()
126 post = self._create_post()
127
127
128 self.assertTrue(post.is_get())
128 self.assertTrue(post.is_get())
129
129
130 def test_pages(self):
130 def test_pages(self):
131 """Test that the thread list is properly split into pages"""
131 """Test that the thread list is properly split into pages"""
132
132
133 for i in range(settings.MAX_THREAD_COUNT):
133 for i in range(settings.MAX_THREAD_COUNT):
134 self._create_post()
134 self._create_post()
135
135
136 all_threads = Post.objects.get_threads()
136 all_threads = Post.objects.get_threads()
137
137
138 posts_in_second_page = Post.objects.get_threads(page=1)
138 posts_in_second_page = Post.objects.get_threads(page=1)
139 first_post = posts_in_second_page[0]
139 first_post = posts_in_second_page[0]
140
140
141 self.assertEqual(all_threads[settings.THREADS_PER_PAGE].id,
141 self.assertEqual(all_threads[settings.THREADS_PER_PAGE].id,
142 first_post.id)
142 first_post.id)
143
143
144 def test_post_validation(self):
144 def test_post_validation(self):
145 """Test the validation of the post form"""
145 """Test the validation of the post form"""
146
146
147 Post.objects.all().delete()
147 Post.objects.all().delete()
148
148
149 client = Client()
149 client = Client()
150
150
151 valid_tags = u'tag1 tag_2 Ρ‚Π΅Π³_3'
151 valid_tags = u'tag1 tag_2 Ρ‚Π΅Π³_3'
152 invalid_tags = u'$%_356 ---'
152 invalid_tags = u'$%_356 ---'
153
153
154 response = client.post(NEW_THREAD_PAGE, {'title': 'test title',
154 response = client.post(NEW_THREAD_PAGE, {'title': 'test title',
155 'text': TEST_TEXT,
155 'text': TEST_TEXT,
156 'tags': valid_tags})
156 'tags': valid_tags})
157 self.assertEqual(response.status_code, HTTP_CODE_REDIRECT,
157 self.assertEqual(response.status_code, HTTP_CODE_REDIRECT,
158 msg='Posting new message failed: got code ' +
158 msg='Posting new message failed: got code ' +
159 str(response.status_code))
159 str(response.status_code))
160
160
161 self.assertEqual(1, Post.objects.count(),
161 self.assertEqual(1, Post.objects.count(),
162 msg='No posts were created')
162 msg='No posts were created')
163
163
164 client.post(NEW_THREAD_PAGE, {'text': TEST_TEXT,
164 client.post(NEW_THREAD_PAGE, {'text': TEST_TEXT,
165 'tags': invalid_tags})
165 'tags': invalid_tags})
166 self.assertEqual(1, Post.objects.count(), msg='The validation passed '
166 self.assertEqual(1, Post.objects.count(), msg='The validation passed '
167 'where it should fail')
167 'where it should fail')
168
168
169 response = client.post(THREAD_PAGE_ONE, {'text': TEST_TEXT,
169 response = client.post(THREAD_PAGE_ONE, {'text': TEST_TEXT,
170 'tags': valid_tags})
170 'tags': valid_tags})
171 self.assertEqual(HTTP_CODE_REDIRECT, response.status_code,
171 self.assertEqual(HTTP_CODE_REDIRECT, response.status_code,
172 msg=u'Posting new message failed: got code ' +
172 msg=u'Posting new message failed: got code ' +
173 str(response.status_code))
173 str(response.status_code))
174
174
175 self.assertEqual(2, Post.objects.count(),
175 self.assertEqual(2, Post.objects.count(),
176 msg=u'No posts were created')
176 msg=u'No posts were created')
177
177
178 def test_404(self):
178 def test_404(self):
179 """Test receiving error 404 when opening a non-existent page"""
179 """Test receiving error 404 when opening a non-existent page"""
180
180
181 Post.objects.all().delete()
181 Post.objects.all().delete()
182 Tag.objects.all().delete()
182 Tag.objects.all().delete()
183
183
184 tag_name = u'test_tag'
184 tag_name = u'test_tag'
185 tags, = [Tag.objects.get_or_create(name=tag_name)]
185 tags, = [Tag.objects.get_or_create(name=tag_name)]
186 client = Client()
186 client = Client()
187
187
188 Post.objects.create_post('title', TEST_TEXT, tags=tags)
188 Post.objects.create_post('title', TEST_TEXT, tags=tags)
189
189
190 existing_post_id = Post.objects.all()[0].id
190 existing_post_id = Post.objects.all()[0].id
191 response_existing = client.get(THREAD_PAGE + str(existing_post_id) +
191 response_existing = client.get(THREAD_PAGE + str(existing_post_id) +
192 '/')
192 '/')
193 self.assertEqual(HTTP_CODE_OK, response_existing.status_code,
193 self.assertEqual(HTTP_CODE_OK, response_existing.status_code,
194 u'Cannot open existing thread')
194 u'Cannot open existing thread')
195
195
196 response_not_existing = client.get(THREAD_PAGE + str(
196 response_not_existing = client.get(THREAD_PAGE + str(
197 existing_post_id + 1) + '/')
197 existing_post_id + 1) + '/')
198 self.assertEqual(HTTP_CODE_NOT_FOUND,
198 self.assertEqual(HTTP_CODE_NOT_FOUND,
199 response_not_existing.status_code,
199 response_not_existing.status_code,
200 u'Not existing thread is opened')
200 u'Not existing thread is opened')
201
201
202 response_existing = client.get(TAG_PAGE + tag_name + '/')
202 response_existing = client.get(TAG_PAGE + tag_name + '/')
203 self.assertEqual(HTTP_CODE_OK,
203 self.assertEqual(HTTP_CODE_OK,
204 response_existing.status_code,
204 response_existing.status_code,
205 u'Cannot open existing tag')
205 u'Cannot open existing tag')
206
206
207 response_not_existing = client.get(TAG_PAGE + u'not_tag' + '/')
207 response_not_existing = client.get(TAG_PAGE + u'not_tag' + '/')
208 self.assertEqual(HTTP_CODE_NOT_FOUND,
208 self.assertEqual(HTTP_CODE_NOT_FOUND,
209 response_not_existing.status_code,
209 response_not_existing.status_code,
210 u'Not existing tag is opened')
210 u'Not existing tag is opened')
211
211
212 reply_id = Post.objects.create_post('', TEST_TEXT,
212 reply_id = Post.objects.create_post('', TEST_TEXT,
213 parent_id=existing_post_id)
213 parent_id=existing_post_id)
214 response_not_existing = client.get(THREAD_PAGE + str(
214 response_not_existing = client.get(THREAD_PAGE + str(
215 reply_id) + '/')
215 reply_id) + '/')
216 self.assertEqual(HTTP_CODE_NOT_FOUND,
216 self.assertEqual(HTTP_CODE_NOT_FOUND,
217 response_not_existing.status_code,
217 response_not_existing.status_code,
218 u'Not existing thread is opened')
218 u'Not existing thread is opened')
@@ -1,26 +1,32 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
4
4 urlpatterns = patterns('',
5 urlpatterns = patterns('',
5
6
6 # /boards/
7 # /boards/
7 url(r'^$', views.index, name='index'),
8 url(r'^$', views.index, name='index'),
8 # /boards/page/
9 # /boards/page/
9 url(r'^page/(?P<page>\w+)/$', views.index, name='index'),
10 url(r'^page/(?P<page>\w+)/$', views.index, name='index'),
10
11
11 # login page
12 # login page
12 url(r'^login$', views.login, name='login'),
13 url(r'^login$', views.login, name='login'),
13 # logout page
14 # logout page
14 url(r'^logout$', views.logout, name='logout'),
15 url(r'^logout$', views.logout, name='logout'),
15
16
16 # /boards/tag/tag_name/
17 # /boards/tag/tag_name/
17 url(r'^tag/(?P<tag_name>\w+)/$', views.tag, name='tag'),
18 url(r'^tag/(?P<tag_name>\w+)/$', views.tag, name='tag'),
18 # /boards/tag/tag_id/page/
19 # /boards/tag/tag_id/page/
19 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'),
20 # /boards/thread/
21 # /boards/thread/
21 url(r'^thread/(?P<post_id>\w+)/$', views.thread, name='thread'),
22 url(r'^thread/(?P<post_id>\w+)/$', views.thread, name='thread'),
22 # /boards/theme/theme_name/
23 # /boards/theme/theme_name/
23 url(r'^settings$', views.settings, name='settings'),
24 url(r'^settings$', views.settings, name='settings'),
24 url(r'^tags$', views.all_tags, name='tags'),
25 url(r'^tags$', views.all_tags, name='tags'),
25 url(r'^captcha/', include('captcha.urls')),
26 url(r'^captcha/', include('captcha.urls')),
27
28 # RSS feeds
29 url(r'^rss/$', AllThreadsFeed()),
30 url(r'^tag/(?P<tag_name>\w+)/rss/$', TagThreadsFeed()),
31 url(r'^thread/(?P<post_id>\w+)/rss/$', ThreadPostsFeed()),
26 ) No newline at end of file
32 )
@@ -1,195 +1,195 b''
1 # Django settings for neboard project.
1 # Django settings for neboard project.
2 import os
2 import os
3 import markdown
3 import markdown
4 from boards.mdx_neboard import markdown_extended
4 from boards.mdx_neboard import markdown_extended
5
5
6 DEBUG = True
6 DEBUG = True
7 TEMPLATE_DEBUG = DEBUG
7 TEMPLATE_DEBUG = DEBUG
8
8
9 ADMINS = (
9 ADMINS = (
10 # ('Your Name', 'your_email@example.com'),
10 # ('Your Name', 'your_email@example.com'),
11 ('admin', 'admin@example.com')
11 ('admin', 'admin@example.com')
12 )
12 )
13
13
14 MANAGERS = ADMINS
14 MANAGERS = ADMINS
15
15
16 DATABASES = {
16 DATABASES = {
17 'default': {
17 'default': {
18 'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'.
18 'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'.
19 'NAME': 'database.db', # Or path to database file if using sqlite3.
19 'NAME': 'database.db', # Or path to database file if using sqlite3.
20 'USER': '', # Not used with sqlite3.
20 'USER': '', # Not used with sqlite3.
21 'PASSWORD': '', # Not used with sqlite3.
21 'PASSWORD': '', # Not used with sqlite3.
22 'HOST': '', # Set to empty string for localhost. Not used with sqlite3.
22 'HOST': '', # Set to empty string for localhost. Not used with sqlite3.
23 'PORT': '', # Set to empty string for default. Not used with sqlite3.
23 'PORT': '', # Set to empty string for default. Not used with sqlite3.
24 }
24 }
25 }
25 }
26
26
27 # Local time zone for this installation. Choices can be found here:
27 # Local time zone for this installation. Choices can be found here:
28 # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
28 # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
29 # although not all choices may be available on all operating systems.
29 # although not all choices may be available on all operating systems.
30 # In a Windows environment this must be set to your system time zone.
30 # In a Windows environment this must be set to your system time zone.
31 TIME_ZONE = 'Europe/Kiev'
31 TIME_ZONE = 'Europe/Kiev'
32
32
33 # Language code for this installation. All choices can be found here:
33 # Language code for this installation. All choices can be found here:
34 # http://www.i18nguy.com/unicode/language-identifiers.html
34 # http://www.i18nguy.com/unicode/language-identifiers.html
35 LANGUAGE_CODE = 'ru-RU'
35 LANGUAGE_CODE = 'ru-RU'
36
36
37 SITE_ID = 1
37 SITE_ID = 1
38
38
39 # If you set this to False, Django will make some optimizations so as not
39 # If you set this to False, Django will make some optimizations so as not
40 # to load the internationalization machinery.
40 # to load the internationalization machinery.
41 USE_I18N = True
41 USE_I18N = True
42
42
43 # If you set this to False, Django will not format dates, numbers and
43 # If you set this to False, Django will not format dates, numbers and
44 # calendars according to the current locale.
44 # calendars according to the current locale.
45 USE_L10N = True
45 USE_L10N = True
46
46
47 # If you set this to False, Django will not use timezone-aware datetimes.
47 # If you set this to False, Django will not use timezone-aware datetimes.
48 USE_TZ = True
48 USE_TZ = True
49
49
50 # Absolute filesystem path to the directory that will hold user-uploaded files.
50 # Absolute filesystem path to the directory that will hold user-uploaded files.
51 # Example: "/home/media/media.lawrence.com/media/"
51 # Example: "/home/media/media.lawrence.com/media/"
52 MEDIA_ROOT = './media/'
52 MEDIA_ROOT = './media/'
53
53
54 # URL that handles the media served from MEDIA_ROOT. Make sure to use a
54 # URL that handles the media served from MEDIA_ROOT. Make sure to use a
55 # trailing slash.
55 # trailing slash.
56 # Examples: "http://media.lawrence.com/media/", "http://example.com/media/"
56 # Examples: "http://media.lawrence.com/media/", "http://example.com/media/"
57 MEDIA_URL = '/media/'
57 MEDIA_URL = '/media/'
58
58
59 # Absolute path to the directory static files should be collected to.
59 # Absolute path to the directory static files should be collected to.
60 # Don't put anything in this directory yourself; store your static files
60 # Don't put anything in this directory yourself; store your static files
61 # in apps' "static/" subdirectories and in STATICFILES_DIRS.
61 # in apps' "static/" subdirectories and in STATICFILES_DIRS.
62 # Example: "/home/media/media.lawrence.com/static/"
62 # Example: "/home/media/media.lawrence.com/static/"
63 STATIC_ROOT = ''
63 STATIC_ROOT = ''
64
64
65 # URL prefix for static files.
65 # URL prefix for static files.
66 # Example: "http://media.lawrence.com/static/"
66 # Example: "http://media.lawrence.com/static/"
67 STATIC_URL = '/static/'
67 STATIC_URL = '/static/'
68
68
69 # Additional locations of static files
69 # Additional locations of static files
70 # It is really a hack, put real paths, not related
70 # It is really a hack, put real paths, not related
71 STATICFILES_DIRS = (
71 STATICFILES_DIRS = (
72 os.path.dirname(__file__) + '/boards/static',
72 os.path.dirname(__file__) + '/boards/static',
73
73
74 # '/d/work/python/django/neboard/neboard/boards/static',
74 # '/d/work/python/django/neboard/neboard/boards/static',
75 # Put strings here, like "/home/html/static" or "C:/www/django/static".
75 # Put strings here, like "/home/html/static" or "C:/www/django/static".
76 # Always use forward slashes, even on Windows.
76 # Always use forward slashes, even on Windows.
77 # Don't forget to use absolute paths, not relative paths.
77 # Don't forget to use absolute paths, not relative paths.
78 )
78 )
79
79
80 # List of finder classes that know how to find static files in
80 # List of finder classes that know how to find static files in
81 # various locations.
81 # various locations.
82 STATICFILES_FINDERS = (
82 STATICFILES_FINDERS = (
83 'django.contrib.staticfiles.finders.FileSystemFinder',
83 'django.contrib.staticfiles.finders.FileSystemFinder',
84 'django.contrib.staticfiles.finders.AppDirectoriesFinder',
84 'django.contrib.staticfiles.finders.AppDirectoriesFinder',
85 # 'django.contrib.staticfiles.finders.DefaultStorageFinder',
85 # 'django.contrib.staticfiles.finders.DefaultStorageFinder',
86 )
86 )
87
87
88 # Make this unique, and don't share it with anybody.
88 # Make this unique, and don't share it with anybody.
89 SECRET_KEY = '@1rc$o(7=tt#kd+4s$u6wchm**z^)4x90)7f6z(i&amp;55@o11*8o'
89 SECRET_KEY = '@1rc$o(7=tt#kd+4s$u6wchm**z^)4x90)7f6z(i&amp;55@o11*8o'
90
90
91 # List of callables that know how to import templates from various sources.
91 # List of callables that know how to import templates from various sources.
92 TEMPLATE_LOADERS = (
92 TEMPLATE_LOADERS = (
93 'django.template.loaders.filesystem.Loader',
93 'django.template.loaders.filesystem.Loader',
94 'django.template.loaders.app_directories.Loader',
94 'django.template.loaders.app_directories.Loader',
95 # 'django.template.loaders.eggs.Loader',
95 # 'django.template.loaders.eggs.Loader',
96 )
96 )
97
97
98 TEMPLATE_CONTEXT_PROCESSORS = (
98 TEMPLATE_CONTEXT_PROCESSORS = (
99 'django.core.context_processors.media',
99 'django.core.context_processors.media',
100 'django.core.context_processors.static',
100 'django.core.context_processors.static',
101 'django.core.context_processors.request',
101 'django.core.context_processors.request',
102 'django.contrib.auth.context_processors.auth',
102 'django.contrib.auth.context_processors.auth',
103 )
103 )
104
104
105 MIDDLEWARE_CLASSES = (
105 MIDDLEWARE_CLASSES = (
106 'django.middleware.common.CommonMiddleware',
106 'django.middleware.common.CommonMiddleware',
107 'django.contrib.sessions.middleware.SessionMiddleware',
107 'django.contrib.sessions.middleware.SessionMiddleware',
108 # 'django.middleware.csrf.CsrfViewMiddleware',
108 # 'django.middleware.csrf.CsrfViewMiddleware',
109 'django.contrib.auth.middleware.AuthenticationMiddleware',
109 'django.contrib.auth.middleware.AuthenticationMiddleware',
110 'django.contrib.messages.middleware.MessageMiddleware',
110 'django.contrib.messages.middleware.MessageMiddleware',
111 # Uncomment the next line for simple clickjacking protection:
111 # Uncomment the next line for simple clickjacking protection:
112 # 'django.middleware.clickjacking.XFrameOptionsMiddleware',
112 # 'django.middleware.clickjacking.XFrameOptionsMiddleware',
113 )
113 )
114
114
115 ROOT_URLCONF = 'neboard.urls'
115 ROOT_URLCONF = 'neboard.urls'
116
116
117 # Python dotted path to the WSGI application used by Django's runserver.
117 # Python dotted path to the WSGI application used by Django's runserver.
118 WSGI_APPLICATION = 'neboard.wsgi.application'
118 WSGI_APPLICATION = 'neboard.wsgi.application'
119
119
120 TEMPLATE_DIRS = (
120 TEMPLATE_DIRS = (
121 # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
121 # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
122 # Always use forward slashes, even on Windows.
122 # Always use forward slashes, even on Windows.
123 # Don't forget to use absolute paths, not relative paths.
123 # Don't forget to use absolute paths, not relative paths.
124 'templates',
124 'templates',
125 )
125 )
126
126
127 INSTALLED_APPS = (
127 INSTALLED_APPS = (
128 'django.contrib.auth',
128 'django.contrib.auth',
129 'django.contrib.contenttypes',
129 'django.contrib.contenttypes',
130 'django.contrib.sessions',
130 'django.contrib.sessions',
131 'django.contrib.sites',
131 # 'django.contrib.sites',
132 'django.contrib.messages',
132 'django.contrib.messages',
133 'django.contrib.staticfiles',
133 'django.contrib.staticfiles',
134 # Uncomment the next line to enable the admin:
134 # Uncomment the next line to enable the admin:
135 'django.contrib.admin',
135 'django.contrib.admin',
136 # Uncomment the next line to enable admin documentation:
136 # Uncomment the next line to enable admin documentation:
137 # 'django.contrib.admindocs',
137 # 'django.contrib.admindocs',
138 'django.contrib.markup',
138 'django.contrib.markup',
139 'django_cleanup',
139 'django_cleanup',
140 'boards',
140 'boards',
141 'captcha',
141 'captcha',
142 )
142 )
143
143
144 # TODO: NEED DESIGN FIXES
144 # TODO: NEED DESIGN FIXES
145 CAPTCHA_OUTPUT_FORMAT = (u' %(hidden_field)s '
145 CAPTCHA_OUTPUT_FORMAT = (u' %(hidden_field)s '
146 u'<div class="form-label">%(image)s</div>'
146 u'<div class="form-label">%(image)s</div>'
147 u'<div class="form-text">%(text_field)s</div>')
147 u'<div class="form-text">%(text_field)s</div>')
148
148
149 # A sample logging configuration. The only tangible logging
149 # A sample logging configuration. The only tangible logging
150 # performed by this configuration is to send an email to
150 # performed by this configuration is to send an email to
151 # the site admins on every HTTP 500 error when DEBUG=False.
151 # the site admins on every HTTP 500 error when DEBUG=False.
152 # See http://docs.djangoproject.com/en/dev/topics/logging for
152 # See http://docs.djangoproject.com/en/dev/topics/logging for
153 # more details on how to customize your logging configuration.
153 # more details on how to customize your logging configuration.
154 LOGGING = {
154 LOGGING = {
155 'version': 1,
155 'version': 1,
156 'disable_existing_loggers': False,
156 'disable_existing_loggers': False,
157 'filters': {
157 'filters': {
158 'require_debug_false': {
158 'require_debug_false': {
159 '()': 'django.utils.log.RequireDebugFalse'
159 '()': 'django.utils.log.RequireDebugFalse'
160 }
160 }
161 },
161 },
162 'handlers': {
162 'handlers': {
163 'mail_admins': {
163 'mail_admins': {
164 'level': 'ERROR',
164 'level': 'ERROR',
165 'filters': ['require_debug_false'],
165 'filters': ['require_debug_false'],
166 'class': 'django.utils.log.AdminEmailHandler'
166 'class': 'django.utils.log.AdminEmailHandler'
167 }
167 }
168 },
168 },
169 'loggers': {
169 'loggers': {
170 'django.request': {
170 'django.request': {
171 'handlers': ['mail_admins'],
171 'handlers': ['mail_admins'],
172 'level': 'ERROR',
172 'level': 'ERROR',
173 'propagate': True,
173 'propagate': True,
174 },
174 },
175 }
175 }
176 }
176 }
177
177
178 MARKUP_FIELD_TYPES = (
178 MARKUP_FIELD_TYPES = (
179 ('markdown', markdown_extended),
179 ('markdown', markdown_extended),
180 )
180 )
181 # Custom imageboard settings
181 # Custom imageboard settings
182 MAX_POSTS_PER_THREAD = 10 # Thread bumplimit
182 MAX_POSTS_PER_THREAD = 10 # Thread bumplimit
183 MAX_THREAD_COUNT = 500 # Old threads will be deleted to preserve this count
183 MAX_THREAD_COUNT = 500 # Old threads will be deleted to preserve this count
184 THREADS_PER_PAGE = 10
184 THREADS_PER_PAGE = 10
185 SITE_NAME = 'Neboard'
185 SITE_NAME = 'Neboard'
186
186
187 THEMES = [
187 THEMES = [
188 ('md', 'Mystic Dark'),
188 ('md', 'Mystic Dark'),
189 ('sw', 'Snow White') ]
189 ('sw', 'Snow White') ]
190 DEFAULT_THEME = 'md'
190 DEFAULT_THEME = 'md'
191
191
192 POPULAR_TAGS = 10
192 POPULAR_TAGS = 10
193 LAST_REPLIES_COUNT = 3
193 LAST_REPLIES_COUNT = 3
194
194
195 ENABLE_CAPTCHA = False No newline at end of file
195 ENABLE_CAPTCHA = False
General Comments 0
You need to be logged in to leave comments. Login now