##// END OF EJS Templates
Fixed style of 'no threads' text in the main page. Added translation for this string.
neko259 -
r143:d303170c 1.1
parent child Browse files
Show More
@@ -1,134 +1,133 b''
1 import re
1 import re
2 from captcha.fields import CaptchaField
2 from captcha.fields import CaptchaField
3 from django import forms
3 from django import forms
4 from django.forms.util import ErrorList
4 from django.forms.util import ErrorList
5 from boards.models import TITLE_MAX_LENGTH
5 from boards.models import TITLE_MAX_LENGTH
6 from neboard import settings
6 from neboard import settings
7 from boards import utils
7 from boards import utils
8
8
9 class PlainErrorList(ErrorList):
9 class PlainErrorList(ErrorList):
10 def __unicode__(self):
10 def __unicode__(self):
11 return self.as_text()
11 return self.as_text()
12
12
13 def as_text(self):
13 def as_text(self):
14 return ''.join([u'(!) %s ' % e for e in self])
14 return ''.join([u'(!) %s ' % e for e in self])
15
15
16
16
17 class PostForm(forms.Form):
17 class PostForm(forms.Form):
18
18
19 MAX_TEXT_LENGTH = 10000
19 MAX_TEXT_LENGTH = 10000
20 MAX_IMAGE_SIZE = 8 * 1024 * 1024
20 MAX_IMAGE_SIZE = 8 * 1024 * 1024
21
21
22 title = forms.CharField(max_length=TITLE_MAX_LENGTH, required=False)
22 title = forms.CharField(max_length=TITLE_MAX_LENGTH, required=False)
23 text = forms.CharField(widget=forms.Textarea, required=False)
23 text = forms.CharField(widget=forms.Textarea, required=False)
24 image = forms.ImageField(required=False)
24 image = forms.ImageField(required=False)
25
25
26 def clean_title(self):
26 def clean_title(self):
27 title = self.cleaned_data['title']
27 title = self.cleaned_data['title']
28 if title:
28 if title:
29 if len(title) > TITLE_MAX_LENGTH:
29 if len(title) > TITLE_MAX_LENGTH:
30 raise forms.ValidationError('Title must have less than' +
30 raise forms.ValidationError('Title must have less than' +
31 str(TITLE_MAX_LENGTH) +
31 str(TITLE_MAX_LENGTH) +
32 ' characters.')
32 ' characters.')
33 return title
33 return title
34
34
35 def clean_text(self):
35 def clean_text(self):
36 text = self.cleaned_data['text']
36 text = self.cleaned_data['text']
37 if text:
37 if text:
38 if len(text) > self.MAX_TEXT_LENGTH:
38 if len(text) > self.MAX_TEXT_LENGTH:
39 raise forms.ValidationError('Text must have less than ' +
39 raise forms.ValidationError('Text must have less than ' +
40 str(self.MAX_TEXT_LENGTH) +
40 str(self.MAX_TEXT_LENGTH) +
41 ' characters.')
41 ' characters.')
42 return text
42 return text
43
43
44 def clean_image(self):
44 def clean_image(self):
45 image = self.cleaned_data['image']
45 image = self.cleaned_data['image']
46 if image:
46 if image:
47 if image._size > self.MAX_IMAGE_SIZE:
47 if image._size > self.MAX_IMAGE_SIZE:
48 raise forms.ValidationError('Image must be less than ' +
48 raise forms.ValidationError('Image must be less than ' +
49 str(self.MAX_IMAGE_SIZE) +
49 str(self.MAX_IMAGE_SIZE) +
50 ' bytes.')
50 ' bytes.')
51 return image
51 return image
52
52
53 def clean(self):
53 def clean(self):
54 cleaned_data = super(PostForm, self).clean()
54 cleaned_data = super(PostForm, self).clean()
55
55
56 self._clean_text_image()
56 self._clean_text_image()
57
57
58 return cleaned_data
58 return cleaned_data
59
59
60 def _clean_text_image(self):
60 def _clean_text_image(self):
61 text = self.cleaned_data.get('text')
61 text = self.cleaned_data.get('text')
62 image = self.cleaned_data.get('image')
62 image = self.cleaned_data.get('image')
63
63
64 if (not text) and (not image):
64 if (not text) and (not image):
65 error_message = 'Either text or image must be entered.'
65 error_message = 'Either text or image must be entered.'
66 self._errors['text'] = self.error_class([error_message])
66 self._errors['text'] = self.error_class([error_message])
67 self._errors['image'] = self.error_class([error_message])
67 self._errors['image'] = self.error_class([error_message])
68
68
69
69
70
71 class ThreadForm(PostForm):
70 class ThreadForm(PostForm):
72 regex_tags = re.compile(ur'^[\w\s\d]+$', re.UNICODE)
71 regex_tags = re.compile(ur'^[\w\s\d]+$', re.UNICODE)
73 tags = forms.CharField(max_length=100)
72 tags = forms.CharField(max_length=100)
74
73
75 def clean_tags(self):
74 def clean_tags(self):
76 tags = self.cleaned_data['tags']
75 tags = self.cleaned_data['tags']
77
76
78 if tags:
77 if tags:
79 if not self.regex_tags.match(tags):
78 if not self.regex_tags.match(tags):
80 raise forms.ValidationError(
79 raise forms.ValidationError(
81 'Inappropriate characters in tags.')
80 'Inappropriate characters in tags.')
82
81
83 return tags
82 return tags
84
83
85 def clean(self):
84 def clean(self):
86 cleaned_data = super(ThreadForm, self).clean()
85 cleaned_data = super(ThreadForm, self).clean()
87
86
88 return cleaned_data
87 return cleaned_data
89
88
90
89
91 class PostCaptchaForm(PostForm):
90 class PostCaptchaForm(PostForm):
92 captcha = CaptchaField()
91 captcha = CaptchaField()
93
92
94 def __init__(self, *args, **kwargs):
93 def __init__(self, *args, **kwargs):
95 self.request = kwargs['request']
94 self.request = kwargs['request']
96 del kwargs['request']
95 del kwargs['request']
97
96
98 super(PostCaptchaForm, self).__init__(*args, **kwargs)
97 super(PostCaptchaForm, self).__init__(*args, **kwargs)
99
98
100 def clean(self):
99 def clean(self):
101 cleaned_data = super(PostCaptchaForm, self).clean()
100 cleaned_data = super(PostCaptchaForm, self).clean()
102
101
103 success = self.is_valid()
102 success = self.is_valid()
104 utils.update_captcha_access(self.request, success)
103 utils.update_captcha_access(self.request, success)
105
104
106 if success:
105 if success:
107 return cleaned_data
106 return cleaned_data
108 else:
107 else:
109 raise forms.ValidationError("captcha validation failed")
108 raise forms.ValidationError("captcha validation failed")
110
109
111
110
112 class ThreadCaptchaForm(ThreadForm):
111 class ThreadCaptchaForm(ThreadForm):
113 captcha = CaptchaField()
112 captcha = CaptchaField()
114
113
115 def __init__(self, *args, **kwargs):
114 def __init__(self, *args, **kwargs):
116 self.request = kwargs['request']
115 self.request = kwargs['request']
117 del kwargs['request']
116 del kwargs['request']
118
117
119 super(ThreadCaptchaForm, self).__init__(*args, **kwargs)
118 super(ThreadCaptchaForm, self).__init__(*args, **kwargs)
120
119
121 def clean(self):
120 def clean(self):
122 cleaned_data = super(ThreadCaptchaForm, self).clean()
121 cleaned_data = super(ThreadCaptchaForm, self).clean()
123
122
124 success = self.is_valid()
123 success = self.is_valid()
125 utils.update_captcha_access(self.request, success)
124 utils.update_captcha_access(self.request, success)
126
125
127 if success:
126 if success:
128 return cleaned_data
127 return cleaned_data
129 else:
128 else:
130 raise forms.ValidationError("captcha validation failed")
129 raise forms.ValidationError("captcha validation failed")
131
130
132
131
133 class SettingsForm(forms.Form):
132 class SettingsForm(forms.Form):
134 theme = forms.ChoiceField(choices=settings.THEMES, widget=forms.RadioSelect)
133 theme = forms.ChoiceField(choices=settings.THEMES, widget=forms.RadioSelect)
1 NO CONTENT: modified file, binary diff hidden
NO CONTENT: modified file, binary diff hidden
@@ -1,205 +1,205 b''
1 # SOME DESCRIPTIVE TITLE.
1 # SOME DESCRIPTIVE TITLE.
2 # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
2 # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3 # This file is distributed under the same license as the PACKAGE package.
3 # This file is distributed under the same license as the PACKAGE package.
4 # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
4 # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
5 #
5 #
6 msgid ""
6 msgid ""
7 msgstr ""
7 msgstr ""
8 "Project-Id-Version: PACKAGE VERSION\n"
8 "Project-Id-Version: PACKAGE VERSION\n"
9 "Report-Msgid-Bugs-To: \n"
9 "Report-Msgid-Bugs-To: \n"
10 "POT-Creation-Date: 2013-08-30 18:54+0300\n"
10 "POT-Creation-Date: 2013-09-06 21:15+0300\n"
11 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
11 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
12 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
12 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
13 "Language-Team: LANGUAGE <LL@li.org>\n"
13 "Language-Team: LANGUAGE <LL@li.org>\n"
14 "Language: ru\n"
14 "Language: ru\n"
15 "MIME-Version: 1.0\n"
15 "MIME-Version: 1.0\n"
16 "Content-Type: text/plain; charset=UTF-8\n"
16 "Content-Type: text/plain; charset=UTF-8\n"
17 "Content-Transfer-Encoding: 8bit\n"
17 "Content-Transfer-Encoding: 8bit\n"
18 "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
18 "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
19 "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
19 "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
20
20
21 #: templates/boards/404.html:6
21 #: templates/boards/404.html:6
22 msgid "Not found"
22 msgid "Not found"
23 msgstr "НС найдСно"
23 msgstr "НС найдСно"
24
24
25 #: templates/boards/404.html:12
25 #: templates/boards/404.html:12
26 msgid "This page does not exist"
26 msgid "This page does not exist"
27 msgstr "Π­Ρ‚ΠΎΠΉ страницы Π½Π΅ сущСствуСт"
27 msgstr "Π­Ρ‚ΠΎΠΉ страницы Π½Π΅ сущСствуСт"
28
28
29 #: templates/boards/authors.html:6
29 #: templates/boards/authors.html:6
30 msgid "Authors"
30 msgid "Authors"
31 msgstr "Авторы"
31 msgstr "Авторы"
32
32
33 #: templates/boards/authors.html:24
33 #: templates/boards/authors.html:24
34 msgid "Distributed under the"
34 msgid "Distributed under the"
35 msgstr "РаспространяСтся ΠΏΠΎΠ΄"
35 msgstr "РаспространяСтся ΠΏΠΎΠ΄"
36
36
37 #: templates/boards/authors.html:26
37 #: templates/boards/authors.html:26
38 msgid "license"
38 msgid "license"
39 msgstr "Π»ΠΈΡ†Π΅Π½Π·ΠΈΠ΅ΠΉ"
39 msgstr "Π»ΠΈΡ†Π΅Π½Π·ΠΈΠ΅ΠΉ"
40
40
41 #: templates/boards/authors.html:28
41 #: templates/boards/authors.html:28
42 msgid "Repository"
42 msgid "Repository"
43 msgstr "Π Π΅ΠΏΠΎΠ·ΠΈΡ‚ΠΎΡ€ΠΈΠΉ"
43 msgstr "Π Π΅ΠΏΠΎΠ·ΠΈΡ‚ΠΎΡ€ΠΈΠΉ"
44
44
45 #: templates/boards/banned.html:6
45 #: templates/boards/banned.html:6
46 msgid "Banned"
46 msgid "Banned"
47 msgstr "Π—Π°Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²Π°Π½"
47 msgstr "Π—Π°Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²Π°Π½"
48
48
49 #: templates/boards/banned.html:11
49 #: templates/boards/banned.html:11
50 msgid "Your IP address has been banned. Contact the administrator"
50 msgid "Your IP address has been banned. Contact the administrator"
51 msgstr "Π’Π°Ρˆ IP адрСс Π±Ρ‹Π» Π·Π°Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²Π°Π½. Π‘Π²ΡΠΆΠΈΡ‚Π΅ΡΡŒ с администратором"
51 msgstr "Π’Π°Ρˆ IP адрСс Π±Ρ‹Π» Π·Π°Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²Π°Π½. Π‘Π²ΡΠΆΠΈΡ‚Π΅ΡΡŒ с администратором"
52
52
53 #: templates/boards/base.html:12
53 #: templates/boards/base.html:12
54 msgid "Feed"
54 msgid "Feed"
55 msgstr "Π›Π΅Π½Ρ‚Π°"
55 msgstr "Π›Π΅Π½Ρ‚Π°"
56
56
57 #: templates/boards/base.html:36
57 #: templates/boards/base.html:29
58 msgid "All threads"
58 msgid "All threads"
59 msgstr "ВсС Ρ‚Π΅ΠΌΡ‹"
59 msgstr "ВсС Ρ‚Π΅ΠΌΡ‹"
60
60
61 #: templates/boards/base.html:42
61 #: templates/boards/base.html:35
62 msgid "Settings"
62 msgid "Settings"
63 msgstr "Настройки"
63 msgstr "Настройки"
64
64
65 #: templates/boards/base.html:50
65 #: templates/boards/base.html:43
66 msgid "Up"
66 msgid "Up"
67 msgstr "Π’Π²Π΅Ρ€Ρ…"
67 msgstr "Π’Π²Π΅Ρ€Ρ…"
68
68
69 #: templates/boards/posting_general.html:18
69 #: templates/boards/posting_general.html:18
70 msgid "Tag: "
70 msgid "Tag: "
71 msgstr "Π’Π΅Π³: "
71 msgstr "Π’Π΅Π³: "
72
72
73 #: templates/boards/posting_general.html:35
73 #: templates/boards/posting_general.html:35
74 #: templates/boards/posting_general.html:81 templates/boards/thread.html:27
74 #: templates/boards/posting_general.html:89 templates/boards/thread.html:27
75 #: templates/boards/rss/post.html:5
75 #: templates/boards/rss/post.html:5
76 msgid "Post image"
76 msgid "Post image"
77 msgstr "Π˜Π·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ сообщСния"
77 msgstr "Π˜Π·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ сообщСния"
78
78
79 #: templates/boards/posting_general.html:48
79 #: templates/boards/posting_general.html:48
80 msgid "Reply"
80 msgid "Reply"
81 msgstr "ΠžΡ‚Π²Π΅Ρ‚"
81 msgstr "ΠžΡ‚Π²Π΅Ρ‚"
82
82
83 #: templates/boards/posting_general.html:52 templates/boards/thread.html:49
83 #: templates/boards/posting_general.html:54 templates/boards/thread.html:46
84 msgid "Delete"
84 msgid "Delete"
85 msgstr "Π£Π΄Π°Π»ΠΈΡ‚ΡŒ"
85 msgstr "Π£Π΄Π°Π»ΠΈΡ‚ΡŒ"
86
86
87 #: templates/boards/posting_general.html:55 templates/boards/thread.html:108
87 #: templates/boards/posting_general.html:63 templates/boards/thread.html:113
88 msgid "replies"
88 msgid "replies"
89 msgstr "ΠΎΡ‚Π²Π΅Ρ‚ΠΎΠ²"
89 msgstr "ΠΎΡ‚Π²Π΅Ρ‚ΠΎΠ²"
90
90
91 #: templates/boards/posting_general.html:56 templates/boards/thread.html:109
91 #: templates/boards/posting_general.html:64 templates/boards/thread.html:114
92 msgid "images"
92 msgid "images"
93 msgstr "ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ"
93 msgstr "ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ"
94
94
95 #: templates/boards/posting_general.html:58
95 #: templates/boards/posting_general.html:66
96 #: templates/boards/posting_general.html:131 templates/boards/thread.html:48
96 #: templates/boards/posting_general.html:139 templates/boards/thread.html:56
97 #: templates/boards/rss/post.html:10
97 #: templates/boards/rss/post.html:10
98 msgid "Tags"
98 msgid "Tags"
99 msgstr "Π’Π΅Π³ΠΈ"
99 msgstr "Π’Π΅Π³ΠΈ"
100
100
101 #: templates/boards/posting_general.html:113
101 #: templates/boards/posting_general.html:115
102 msgid "No threads exist. Create the first one!"
103 msgstr "НСт Ρ‚Π΅ΠΌ. Π‘ΠΎΠ·Π΄Π°ΠΉΡ‚Π΅ ΠΏΠ΅Ρ€Π²ΡƒΡŽ!"
104
105 #: templates/boards/posting_general.html:121
102 msgid "Create new thread"
106 msgid "Create new thread"
103 msgstr "Π‘ΠΎΠ·Π΄Π°Ρ‚ΡŒ Π½ΠΎΠ²ΡƒΡŽ Ρ‚Π΅ΠΌΡƒ"
107 msgstr "Π‘ΠΎΠ·Π΄Π°Ρ‚ΡŒ Π½ΠΎΠ²ΡƒΡŽ Ρ‚Π΅ΠΌΡƒ"
104
108
105 #: templates/boards/posting_general.html:116 templates/boards/thread.html:70
109 #: templates/boards/posting_general.html:124 templates/boards/thread.html:75
106 msgid "Title"
110 msgid "Title"
107 msgstr "Π—Π°Π³ΠΎΠ»ΠΎΠ²ΠΎΠΊ"
111 msgstr "Π—Π°Π³ΠΎΠ»ΠΎΠ²ΠΎΠΊ"
108
112
109 #: templates/boards/posting_general.html:121 templates/boards/thread.html:75
113 #: templates/boards/posting_general.html:129 templates/boards/thread.html:80
110 msgid "Text"
114 msgid "Text"
111 msgstr "ВСкст"
115 msgstr "ВСкст"
112
116
113 #: templates/boards/posting_general.html:126 templates/boards/thread.html:80
117 #: templates/boards/posting_general.html:134 templates/boards/thread.html:85
114 msgid "Image"
118 msgid "Image"
115 msgstr "Π˜Π·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅"
119 msgstr "Π˜Π·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅"
116
120
117 #: templates/boards/posting_general.html:141 templates/boards/thread.html:91
121 #: templates/boards/posting_general.html:149 templates/boards/thread.html:96
118 msgid "Post"
122 msgid "Post"
119 msgstr "ΠžΡ‚ΠΏΡ€Π°Π²ΠΈΡ‚ΡŒ"
123 msgstr "ΠžΡ‚ΠΏΡ€Π°Π²ΠΈΡ‚ΡŒ"
120
124
121 #: templates/boards/posting_general.html:143
125 #: templates/boards/posting_general.html:151
122 msgid "Tags must be delimited by spaces. Text or image is required."
126 msgid "Tags must be delimited by spaces. Text or image is required."
123 msgstr ""
127 msgstr ""
124 "Π’Π΅Π³ΠΈ Π΄ΠΎΠ»ΠΆΠ½Ρ‹ Π±Ρ‹Ρ‚ΡŒ Ρ€Π°Π·Π΄Π΅Π»Π΅Π½Ρ‹ ΠΏΡ€ΠΎΠ±Π΅Π»Π°ΠΌΠΈ. ВСкст ΠΈΠ»ΠΈ ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ ΠΎΠ±ΡΠ·Π°Ρ‚Π΅Π»ΡŒΠ½Ρ‹."
128 "Π’Π΅Π³ΠΈ Π΄ΠΎΠ»ΠΆΠ½Ρ‹ Π±Ρ‹Ρ‚ΡŒ Ρ€Π°Π·Π΄Π΅Π»Π΅Π½Ρ‹ ΠΏΡ€ΠΎΠ±Π΅Π»Π°ΠΌΠΈ. ВСкст ΠΈΠ»ΠΈ ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ ΠΎΠ±ΡΠ·Π°Ρ‚Π΅Π»ΡŒΠ½Ρ‹."
125
129
126 #: templates/boards/posting_general.html:146 templates/boards/thread.html:93
130 #: templates/boards/posting_general.html:154 templates/boards/thread.html:98
127 msgid "Basic markdown syntax."
131 msgid "Basic markdown syntax."
128 msgstr "Π‘Π°Π·ΠΎΠ²Ρ‹ΠΉ синтаксис markdown."
132 msgstr "Π‘Π°Π·ΠΎΠ²Ρ‹ΠΉ синтаксис markdown."
129
133
130 #: templates/boards/posting_general.html:156
134 #: templates/boards/posting_general.html:164
131 msgid "Pages:"
135 msgid "Pages:"
132 msgstr "Π‘Ρ‚Ρ€Π°Π½ΠΈΡ†Ρ‹: "
136 msgstr "Π‘Ρ‚Ρ€Π°Π½ΠΈΡ†Ρ‹: "
133
137
134 #: templates/boards/settings.html:12
138 #: templates/boards/settings.html:12
135 msgid "User:"
139 msgid "User:"
136 msgstr "ΠŸΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒ:"
140 msgstr "ΠŸΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒ:"
137
141
138 #: templates/boards/settings.html:14
142 #: templates/boards/settings.html:14
139 msgid "You are moderator."
143 msgid "You are moderator."
140 msgstr "Π’Ρ‹ ΠΌΠΎΠ΄Π΅Ρ€Π°Ρ‚ΠΎΡ€."
144 msgstr "Π’Ρ‹ ΠΌΠΎΠ΄Π΅Ρ€Π°Ρ‚ΠΎΡ€."
141
145
142 #: templates/boards/settings.html:20
146 #: templates/boards/settings.html:20
143 msgid "Theme"
147 msgid "Theme"
144 msgstr "Π’Π΅ΠΌΠ°"
148 msgstr "Π’Π΅ΠΌΠ°"
145
149
146 #: templates/boards/settings.html:36
150 #: templates/boards/settings.html:36
147 msgid "Save"
151 msgid "Save"
148 msgstr "Π‘ΠΎΡ…Ρ€Π°Π½ΠΈΡ‚ΡŒ"
152 msgstr "Π‘ΠΎΡ…Ρ€Π°Π½ΠΈΡ‚ΡŒ"
149
153
150 #: templates/boards/tags.html:7
154 #: templates/boards/tags.html:7
151 msgid "tags"
155 msgid "tags"
152 msgstr "Ρ‚Π΅Π³ΠΎΠ²"
156 msgstr "Ρ‚Π΅Π³ΠΎΠ²"
153
157
154 #: templates/boards/thread.html:39
158 #: templates/boards/thread.html:72
155 msgid "Get!"
156 msgstr "Π“Π΅Ρ‚!"
157
158 #: templates/boards/thread.html:67
159 msgid "Reply to thread"
159 msgid "Reply to thread"
160 msgstr "ΠžΡ‚Π²Π΅Ρ‚ΠΈΡ‚ΡŒ Π² Ρ‚Π΅ΠΌΡƒ"
160 msgstr "ΠžΡ‚Π²Π΅Ρ‚ΠΈΡ‚ΡŒ Π² Ρ‚Π΅ΠΌΡƒ"
161
161
162 #: templates/boards/thread.html:94
162 #: templates/boards/thread.html:99
163 msgid "Example: "
163 msgid "Example: "
164 msgstr "ΠŸΡ€ΠΈΠΌΠ΅Ρ€: "
164 msgstr "ΠŸΡ€ΠΈΠΌΠ΅Ρ€: "
165
165
166 #: templates/boards/thread.html:94
166 #: templates/boards/thread.html:99
167 msgid "italic"
167 msgid "italic"
168 msgstr "курсив"
168 msgstr "курсив"
169
169
170 #: templates/boards/thread.html:95
170 #: templates/boards/thread.html:100
171 msgid "bold"
171 msgid "bold"
172 msgstr "ΠΏΠΎΠ»ΡƒΠΆΠΈΡ€Π½Ρ‹ΠΉ"
172 msgstr "ΠΏΠΎΠ»ΡƒΠΆΠΈΡ€Π½Ρ‹ΠΉ"
173
173
174 #: templates/boards/thread.html:96
174 #: templates/boards/thread.html:101
175 msgid "Quotes can be inserted with"
175 msgid "Quotes can be inserted with"
176 msgstr "Π¦ΠΈΡ‚Π°Ρ‚Ρ‹ ΠΌΠΎΠ³ΡƒΡ‚ Π±Ρ‹Ρ‚ΡŒ вставлСны ΠΏΡ€ΠΈ ΠΏΠΎΠΌΠΎΡ‰ΠΈ"
176 msgstr "Π¦ΠΈΡ‚Π°Ρ‚Ρ‹ ΠΌΠΎΠ³ΡƒΡ‚ Π±Ρ‹Ρ‚ΡŒ вставлСны ΠΏΡ€ΠΈ ΠΏΠΎΠΌΠΎΡ‰ΠΈ"
177
177
178 #: templates/boards/thread.html:97
178 #: templates/boards/thread.html:102
179 msgid "Links to answers can be inserted with"
179 msgid "Links to answers can be inserted with"
180 msgstr "Бсылки Π½Π° ΠΎΡ‚Π²Π΅Ρ‚Ρ‹ ΠΌΠΎΠ³ΡƒΡ‚ Π±Ρ‹Ρ‚ΡŒ вставлСны с ΠΏΠΎΠΌΠΎΡ‰ΡŒΡŽ"
180 msgstr "Бсылки Π½Π° ΠΎΡ‚Π²Π΅Ρ‚Ρ‹ ΠΌΠΎΠ³ΡƒΡ‚ Π±Ρ‹Ρ‚ΡŒ вставлСны с ΠΏΠΎΠΌΠΎΡ‰ΡŒΡŽ"
181
181
182 #: templates/boards/thread.html:110
182 #: templates/boards/thread.html:115
183 msgid "Last update: "
183 msgid "Last update: "
184 msgstr "ПослСднСС обновлСниС: "
184 msgstr "ПослСднСС обновлСниС: "
185
185
186 #~ msgid "Get!"
186 #~ msgid "Get!"
187 #~ msgstr "Π“Π΅Ρ‚!"
187 #~ msgstr "Π“Π΅Ρ‚!"
188
188
189 #~ msgid "View"
189 #~ msgid "View"
190 #~ msgstr "ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€"
190 #~ msgstr "ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€"
191
191
192 #~ msgid "gets"
192 #~ msgid "gets"
193 #~ msgstr "Π³Π΅Ρ‚ΠΎΠ²"
193 #~ msgstr "Π³Π΅Ρ‚ΠΎΠ²"
194
194
195 #~ msgid "author"
195 #~ msgid "author"
196 #~ msgstr "Π°Π²Ρ‚ΠΎΡ€"
196 #~ msgstr "Π°Π²Ρ‚ΠΎΡ€"
197
197
198 #~ msgid "developer"
198 #~ msgid "developer"
199 #~ msgstr "Ρ€Π°Π·Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊ"
199 #~ msgstr "Ρ€Π°Π·Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊ"
200
200
201 #~ msgid "javascript developer"
201 #~ msgid "javascript developer"
202 #~ msgstr "Ρ€Π°Π·Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊ javascript"
202 #~ msgstr "Ρ€Π°Π·Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊ javascript"
203
203
204 #~ msgid "designer"
204 #~ msgid "designer"
205 #~ msgstr "Π΄ΠΈΠ·Π°ΠΉΠ½Π΅Ρ€"
205 #~ msgstr "Π΄ΠΈΠ·Π°ΠΉΠ½Π΅Ρ€"
@@ -1,328 +1,329 b''
1 import os
1 import os
2 from random import random
2 from random import random
3 import re
3 import re
4 import time
4 import time
5 import math
5 import math
6
6
7 from django.db import models
7 from django.db import models
8 from django.http import Http404
8 from django.http import Http404
9 from django.utils import timezone
9 from django.utils import timezone
10 from markupfield.fields import MarkupField
10 from markupfield.fields import MarkupField
11 from threading import Thread
11 from threading import Thread
12
12
13 from neboard import settings
13 from neboard import settings
14 import thumbs
14 import thumbs
15
15
16 IMAGE_THUMB_SIZE = (200, 150)
16 IMAGE_THUMB_SIZE = (200, 150)
17
17
18 TITLE_MAX_LENGTH = 50
18 TITLE_MAX_LENGTH = 50
19
19
20 DEFAULT_MARKUP_TYPE = 'markdown'
20 DEFAULT_MARKUP_TYPE = 'markdown'
21
21
22 NO_PARENT = -1
22 NO_PARENT = -1
23 NO_IP = '0.0.0.0'
23 NO_IP = '0.0.0.0'
24 UNKNOWN_UA = ''
24 UNKNOWN_UA = ''
25 ALL_PAGES = -1
25 ALL_PAGES = -1
26 OPENING_POST_POPULARITY_WEIGHT = 2
26 OPENING_POST_POPULARITY_WEIGHT = 2
27 IMAGES_DIRECTORY = 'images/'
27 IMAGES_DIRECTORY = 'images/'
28 FILE_EXTENSION_DELIMITER = '.'
28 FILE_EXTENSION_DELIMITER = '.'
29
29
30 RANK_ADMIN = 0
30 RANK_ADMIN = 0
31 RANK_MODERATOR = 10
31 RANK_MODERATOR = 10
32 RANK_USER = 100
32 RANK_USER = 100
33
33
34
34 class PostManager(models.Manager):
35 class PostManager(models.Manager):
35 def create_post(self, title, text, image=None, parent_id=NO_PARENT,
36 def create_post(self, title, text, image=None, parent_id=NO_PARENT,
36 ip=NO_IP, tags=None, user=None):
37 ip=NO_IP, tags=None, user=None):
37 post = self.create(title=title,
38 post = self.create(title=title,
38 text=text,
39 text=text,
39 pub_time=timezone.now(),
40 pub_time=timezone.now(),
40 parent=parent_id,
41 parent=parent_id,
41 image=image,
42 image=image,
42 poster_ip=ip,
43 poster_ip=ip,
43 poster_user_agent=UNKNOWN_UA,
44 poster_user_agent=UNKNOWN_UA,
44 last_edit_time=timezone.now(),
45 last_edit_time=timezone.now(),
45 user=user)
46 user=user)
46
47
47 if tags:
48 if tags:
48 map(post.tags.add, tags)
49 map(post.tags.add, tags)
49
50
50 if parent_id != NO_PARENT:
51 if parent_id != NO_PARENT:
51 self._bump_thread(parent_id)
52 self._bump_thread(parent_id)
52 else:
53 else:
53 self._delete_old_threads()
54 self._delete_old_threads()
54
55
55 return post
56 return post
56
57
57 def delete_post(self, post):
58 def delete_post(self, post):
58 children = self.filter(parent=post.id)
59 children = self.filter(parent=post.id)
59 for child in children:
60 for child in children:
60 self.delete_post(child)
61 self.delete_post(child)
61 post.delete()
62 post.delete()
62
63
63 def delete_posts_by_ip(self, ip):
64 def delete_posts_by_ip(self, ip):
64 posts = self.filter(poster_ip=ip)
65 posts = self.filter(poster_ip=ip)
65 for post in posts:
66 for post in posts:
66 self.delete_post(post)
67 self.delete_post(post)
67
68
68 def get_threads(self, tag=None, page=ALL_PAGES,
69 def get_threads(self, tag=None, page=ALL_PAGES,
69 order_by='-last_edit_time'):
70 order_by='-last_edit_time'):
70 if tag:
71 if tag:
71 threads = self.filter(parent=NO_PARENT, tags=tag)
72 threads = self.filter(parent=NO_PARENT, tags=tag)
72
73
73 # TODO Throw error 404 if no threads for tag found?
74 # TODO Throw error 404 if no threads for tag found?
74 else:
75 else:
75 threads = self.filter(parent=NO_PARENT)
76 threads = self.filter(parent=NO_PARENT)
76
77
77 threads = threads.order_by(order_by)
78 threads = threads.order_by(order_by)
78
79
79 if page != ALL_PAGES:
80 if page != ALL_PAGES:
80 thread_count = len(threads)
81 thread_count = len(threads)
81
82
82 if page < self.get_thread_page_count(tag=tag):
83 if page < self.get_thread_page_count(tag=tag):
83 start_thread = page * settings.THREADS_PER_PAGE
84 start_thread = page * settings.THREADS_PER_PAGE
84 end_thread = min(start_thread + settings.THREADS_PER_PAGE,
85 end_thread = min(start_thread + settings.THREADS_PER_PAGE,
85 thread_count)
86 thread_count)
86 threads = threads[start_thread:end_thread]
87 threads = threads[start_thread:end_thread]
87
88
88 return threads
89 return threads
89
90
90 def get_thread(self, opening_post_id):
91 def get_thread(self, opening_post_id):
91 try:
92 try:
92 opening_post = self.get(id=opening_post_id, parent=NO_PARENT)
93 opening_post = self.get(id=opening_post_id, parent=NO_PARENT)
93 except Post.DoesNotExist:
94 except Post.DoesNotExist:
94 raise Http404
95 raise Http404
95
96
96 if opening_post.parent == NO_PARENT:
97 if opening_post.parent == NO_PARENT:
97 replies = self.filter(parent=opening_post_id)
98 replies = self.filter(parent=opening_post_id)
98
99
99 thread = [opening_post]
100 thread = [opening_post]
100 thread.extend(replies)
101 thread.extend(replies)
101
102
102 return thread
103 return thread
103
104
104 def exists(self, post_id):
105 def exists(self, post_id):
105 posts = self.filter(id=post_id)
106 posts = self.filter(id=post_id)
106
107
107 return posts.count() > 0
108 return posts.count() > 0
108
109
109 def get_thread_page_count(self, tag=None):
110 def get_thread_page_count(self, tag=None):
110 if tag:
111 if tag:
111 threads = self.filter(parent=NO_PARENT, tags=tag)
112 threads = self.filter(parent=NO_PARENT, tags=tag)
112 else:
113 else:
113 threads = self.filter(parent=NO_PARENT)
114 threads = self.filter(parent=NO_PARENT)
114
115
115 return int(math.ceil(threads.count() / float(
116 return int(math.ceil(threads.count() / float(
116 settings.THREADS_PER_PAGE)))
117 settings.THREADS_PER_PAGE)))
117
118
118 def _delete_old_threads(self):
119 def _delete_old_threads(self):
119 """
120 """
120 Preserves maximum thread count. If there are too many threads,
121 Preserves maximum thread count. If there are too many threads,
121 delete the old ones.
122 delete the old ones.
122 """
123 """
123
124
124 # TODO Move old threads to the archive instead of deleting them.
125 # TODO Move old threads to the archive instead of deleting them.
125 # Maybe make some 'old' field in the model to indicate the thread
126 # Maybe make some 'old' field in the model to indicate the thread
126 # must not be shown and be able for replying.
127 # must not be shown and be able for replying.
127
128
128 threads = self.get_threads()
129 threads = self.get_threads()
129 thread_count = len(threads)
130 thread_count = len(threads)
130
131
131 if thread_count > settings.MAX_THREAD_COUNT:
132 if thread_count > settings.MAX_THREAD_COUNT:
132 num_threads_to_delete = thread_count - settings.MAX_THREAD_COUNT
133 num_threads_to_delete = thread_count - settings.MAX_THREAD_COUNT
133 old_threads = threads[thread_count - num_threads_to_delete:]
134 old_threads = threads[thread_count - num_threads_to_delete:]
134
135
135 for thread in old_threads:
136 for thread in old_threads:
136 self.delete_post(thread)
137 self.delete_post(thread)
137
138
138 def _bump_thread(self, thread_id):
139 def _bump_thread(self, thread_id):
139 thread = self.get(id=thread_id)
140 thread = self.get(id=thread_id)
140
141
141 if thread.can_bump():
142 if thread.can_bump():
142 thread.last_edit_time = timezone.now()
143 thread.last_edit_time = timezone.now()
143 thread.save()
144 thread.save()
144
145
145
146
146 class TagManager(models.Manager):
147 class TagManager(models.Manager):
147 def get_not_empty_tags(self):
148 def get_not_empty_tags(self):
148 all_tags = self.all().order_by('name')
149 all_tags = self.all().order_by('name')
149 tags = []
150 tags = []
150 for tag in all_tags:
151 for tag in all_tags:
151 if not tag.is_empty():
152 if not tag.is_empty():
152 tags.append(tag)
153 tags.append(tag)
153
154
154 return tags
155 return tags
155
156
156 def get_popular_tags(self):
157 def get_popular_tags(self):
157 all_tags = self.get_not_empty_tags()
158 all_tags = self.get_not_empty_tags()
158
159
159 sorted_tags = sorted(all_tags, key=lambda tag: tag.get_popularity(),
160 sorted_tags = sorted(all_tags, key=lambda tag: tag.get_popularity(),
160 reverse=True)
161 reverse=True)
161
162
162 return sorted_tags[:settings.POPULAR_TAGS]
163 return sorted_tags[:settings.POPULAR_TAGS]
163
164
164
165
165 class Tag(models.Model):
166 class Tag(models.Model):
166 """
167 """
167 A tag is a text node assigned to the post. The tag serves as a board
168 A tag is a text node assigned to the post. The tag serves as a board
168 section. There can be multiple tags for each message
169 section. There can be multiple tags for each message
169 """
170 """
170
171
171 objects = TagManager()
172 objects = TagManager()
172
173
173 name = models.CharField(max_length=100)
174 name = models.CharField(max_length=100)
174 # TODO Connect the tag to its posts to check the number of threads for
175 # TODO Connect the tag to its posts to check the number of threads for
175 # the tag.
176 # the tag.
176
177
177 def __unicode__(self):
178 def __unicode__(self):
178 return self.name
179 return self.name
179
180
180 def is_empty(self):
181 def is_empty(self):
181 return self.get_post_count() == 0
182 return self.get_post_count() == 0
182
183
183 def get_post_count(self):
184 def get_post_count(self):
184 posts_with_tag = Post.objects.get_threads(tag=self)
185 posts_with_tag = Post.objects.get_threads(tag=self)
185 return posts_with_tag.count()
186 return posts_with_tag.count()
186
187
187 def get_popularity(self):
188 def get_popularity(self):
188 posts_with_tag = Post.objects.get_threads(tag=self)
189 posts_with_tag = Post.objects.get_threads(tag=self)
189 reply_count = 0
190 reply_count = 0
190 for post in posts_with_tag:
191 for post in posts_with_tag:
191 reply_count += post.get_reply_count()
192 reply_count += post.get_reply_count()
192 reply_count += OPENING_POST_POPULARITY_WEIGHT
193 reply_count += OPENING_POST_POPULARITY_WEIGHT
193
194
194 return reply_count
195 return reply_count
195
196
196
197
197 class Post(models.Model):
198 class Post(models.Model):
198 """A post is a message."""
199 """A post is a message."""
199
200
200 objects = PostManager()
201 objects = PostManager()
201
202
202 def _update_image_filename(self, filename):
203 def _update_image_filename(self, filename):
203 """Get unique image filename"""
204 """Get unique image filename"""
204
205
205 path = IMAGES_DIRECTORY
206 path = IMAGES_DIRECTORY
206 new_name = str(int(time.mktime(time.gmtime())))
207 new_name = str(int(time.mktime(time.gmtime())))
207 new_name += str(int(random() * 1000))
208 new_name += str(int(random() * 1000))
208 new_name += FILE_EXTENSION_DELIMITER
209 new_name += FILE_EXTENSION_DELIMITER
209 new_name += filename.split(FILE_EXTENSION_DELIMITER)[-1:][0]
210 new_name += filename.split(FILE_EXTENSION_DELIMITER)[-1:][0]
210
211
211 return os.path.join(path, new_name)
212 return os.path.join(path, new_name)
212
213
213 title = models.CharField(max_length=TITLE_MAX_LENGTH)
214 title = models.CharField(max_length=TITLE_MAX_LENGTH)
214 pub_time = models.DateTimeField()
215 pub_time = models.DateTimeField()
215 text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE,
216 text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE,
216 escape_html=False)
217 escape_html=False)
217
218
218 image_width = models.IntegerField(default=0)
219 image_width = models.IntegerField(default=0)
219 image_height = models.IntegerField(default=0)
220 image_height = models.IntegerField(default=0)
220
221
221 image = thumbs.ImageWithThumbsField(upload_to=_update_image_filename,
222 image = thumbs.ImageWithThumbsField(upload_to=_update_image_filename,
222 blank=True, sizes=(IMAGE_THUMB_SIZE,),
223 blank=True, sizes=(IMAGE_THUMB_SIZE,),
223 width_field='image_width',
224 width_field='image_width',
224 height_field='image_height')
225 height_field='image_height')
225
226
226 poster_ip = models.GenericIPAddressField()
227 poster_ip = models.GenericIPAddressField()
227 poster_user_agent = models.TextField()
228 poster_user_agent = models.TextField()
228 parent = models.BigIntegerField()
229 parent = models.BigIntegerField()
229 tags = models.ManyToManyField(Tag)
230 tags = models.ManyToManyField(Tag)
230 last_edit_time = models.DateTimeField()
231 last_edit_time = models.DateTimeField()
231 user = models.ForeignKey('User', null=True, default=None)
232 user = models.ForeignKey('User', null=True, default=None)
232
233
233 def __unicode__(self):
234 def __unicode__(self):
234 return '#' + str(self.id) + ' ' + self.title + ' (' + \
235 return '#' + str(self.id) + ' ' + self.title + ' (' + \
235 self.text.raw[:50] + ')'
236 self.text.raw[:50] + ')'
236
237
237 def _get_replies(self):
238 def _get_replies(self):
238 return Post.objects.filter(parent=self.id)
239 return Post.objects.filter(parent=self.id)
239
240
240 def get_reply_count(self):
241 def get_reply_count(self):
241 return self._get_replies().count()
242 return self._get_replies().count()
242
243
243 def get_images_count(self):
244 def get_images_count(self):
244 images_count = 1 if self.image else 0
245 images_count = 1 if self.image else 0
245 for reply in self._get_replies():
246 for reply in self._get_replies():
246 if reply.image:
247 if reply.image:
247 images_count += 1
248 images_count += 1
248
249
249 return images_count
250 return images_count
250
251
251 def get_gets_count(self):
252 def get_gets_count(self):
252 gets_count = 1 if self.is_get() else 0
253 gets_count = 1 if self.is_get() else 0
253 for reply in self._get_replies():
254 for reply in self._get_replies():
254 if reply.is_get():
255 if reply.is_get():
255 gets_count += 1
256 gets_count += 1
256
257
257 return gets_count
258 return gets_count
258
259
259 def can_bump(self):
260 def can_bump(self):
260 """Check if the thread can be bumped by replying"""
261 """Check if the thread can be bumped by replying"""
261
262
262 replies_count = len(Post.objects.get_thread(self.id))
263 replies_count = len(Post.objects.get_thread(self.id))
263
264
264 return replies_count <= settings.MAX_POSTS_PER_THREAD
265 return replies_count <= settings.MAX_POSTS_PER_THREAD
265
266
266 def get_last_replies(self):
267 def get_last_replies(self):
267 if settings.LAST_REPLIES_COUNT > 0:
268 if settings.LAST_REPLIES_COUNT > 0:
268 reply_count = self.get_reply_count()
269 reply_count = self.get_reply_count()
269
270
270 if reply_count > 0:
271 if reply_count > 0:
271 reply_count_to_show = min(settings.LAST_REPLIES_COUNT,
272 reply_count_to_show = min(settings.LAST_REPLIES_COUNT,
272 reply_count)
273 reply_count)
273 last_replies = self._get_replies()[reply_count
274 last_replies = self._get_replies()[reply_count
274 - reply_count_to_show:]
275 - reply_count_to_show:]
275
276
276 return last_replies
277 return last_replies
277
278
278
279
279 class User(models.Model):
280 class User(models.Model):
280
281
281 user_id = models.CharField(max_length=50)
282 user_id = models.CharField(max_length=50)
282 rank = models.IntegerField()
283 rank = models.IntegerField()
283
284
284 registration_time = models.DateTimeField()
285 registration_time = models.DateTimeField()
285 last_access_time = models.DateTimeField()
286 last_access_time = models.DateTimeField()
286
287
287 fav_tags = models.ManyToManyField(Tag)
288 fav_tags = models.ManyToManyField(Tag)
288 fav_threads = models.ManyToManyField(Post, related_name='+')
289 fav_threads = models.ManyToManyField(Post, related_name='+')
289
290
290 def save_setting(self, name, value):
291 def save_setting(self, name, value):
291 setting, created = Setting.objects.get_or_create(name=name, user=self)
292 setting, created = Setting.objects.get_or_create(name=name, user=self)
292 setting.value = value
293 setting.value = value
293 setting.save()
294 setting.save()
294
295
295 return setting
296 return setting
296
297
297 def get_setting(self, name):
298 def get_setting(self, name):
298 settings = Setting.objects.filter(name=name, user=self)
299 settings = Setting.objects.filter(name=name, user=self)
299 if len(settings) > 0:
300 if len(settings) > 0:
300 setting = settings[0]
301 setting = settings[0]
301 else:
302 else:
302 setting = None
303 setting = None
303
304
304 if setting:
305 if setting:
305 setting_value = setting.value
306 setting_value = setting.value
306 else:
307 else:
307 setting_value = None
308 setting_value = None
308
309
309 return setting_value
310 return setting_value
310
311
311 def is_moderator(self):
312 def is_moderator(self):
312 return RANK_MODERATOR >= self.rank
313 return RANK_MODERATOR >= self.rank
313
314
314 def __unicode__(self):
315 def __unicode__(self):
315 return self.user_id
316 return self.user_id
316
317
317
318
318 class Setting(models.Model):
319 class Setting(models.Model):
319
320
320 name = models.CharField(max_length=50)
321 name = models.CharField(max_length=50)
321 value = models.CharField(max_length=50)
322 value = models.CharField(max_length=50)
322 user = models.ForeignKey(User)
323 user = models.ForeignKey(User)
323
324
324 class Ban(models.Model):
325 class Ban(models.Model):
325 ip = models.GenericIPAddressField()
326 ip = models.GenericIPAddressField()
326
327
327 def __unicode__(self):
328 def __unicode__(self):
328 return self.ip
329 return self.ip
@@ -1,176 +1,176 b''
1 {% extends "boards/base.html" %}
1 {% extends "boards/base.html" %}
2
2
3 {% load i18n %}
3 {% load i18n %}
4 {% load markup %}
4 {% load markup %}
5
5
6 {% block head %}
6 {% block head %}
7 {% if tag %}
7 {% if tag %}
8 <title>Neboard - {{ tag }}</title>
8 <title>Neboard - {{ tag }}</title>
9 {% else %}
9 {% else %}
10 <title>Neboard</title>
10 <title>Neboard</title>
11 {% endif %}
11 {% endif %}
12 {% endblock %}
12 {% endblock %}
13
13
14 {% block content %}
14 {% block content %}
15
15
16 {% if tag %}
16 {% if tag %}
17 <div class="tag_info">
17 <div class="tag_info">
18 <h2>{% trans 'Tag: ' %}{{ tag }}</h2>
18 <h2>{% trans 'Tag: ' %}{{ tag }}</h2>
19 </div>
19 </div>
20 {% endif %}
20 {% endif %}
21
21
22 {% if threads %}
22 {% if threads %}
23 {% for thread in threads %}
23 {% for thread in threads %}
24 <div class="thread">
24 <div class="thread">
25 {% if thread.can_bump %}
25 {% if thread.can_bump %}
26 <div class="post" id="{{thread.id}}">
26 <div class="post" id="{{thread.id}}">
27 {% else %}
27 {% else %}
28 <div class="post dead_post" id="{{ thread.id }}">
28 <div class="post dead_post" id="{{ thread.id }}">
29 {% endif %}
29 {% endif %}
30 {% if thread.image %}
30 {% if thread.image %}
31 <div class="image">
31 <div class="image">
32 <a class="fancy"
32 <a class="fancy"
33 href="{{ thread.image.url }}"><img
33 href="{{ thread.image.url }}"><img
34 src="{{ thread.image.url_200x150 }}"
34 src="{{ thread.image.url_200x150 }}"
35 alt="{% trans 'Post image' %}"
35 alt="{% trans 'Post image' %}"
36 data-width="{{ thread.image_width }}"
36 data-width="{{ thread.image_width }}"
37 data-height="{{ thread.image_height }}" />
37 data-height="{{ thread.image_height }}" />
38 </a>
38 </a>
39 </div>
39 </div>
40 {% endif %}
40 {% endif %}
41 <div class="message">
41 <div class="message">
42 <div class="post-info">
42 <div class="post-info">
43 <span class="title">{{ thread.title }}</span>
43 <span class="title">{{ thread.title }}</span>
44 <a class="post_id" href="{% url 'thread' thread.id %}"
44 <a class="post_id" href="{% url 'thread' thread.id %}"
45 >(#{{ thread.id }})</a>
45 >(#{{ thread.id }})</a>
46 [{{ thread.pub_time }}]
46 [{{ thread.pub_time }}]
47 [<a class="link" href="{% url 'thread' thread.id %}#form"
47 [<a class="link" href="{% url 'thread' thread.id %}#form"
48 >{% trans "Reply" %}</a>]
48 >{% trans "Reply" %}</a>]
49
49
50 {% if user.is_moderator %}
50 {% if user.is_moderator %}
51 <span class="moderator_info">
51 <span class="moderator_info">
52 ({{ thread.poster_ip }})
52 ({{ thread.poster_ip }})
53 [<a href="{% url 'delete' post_id=thread.id %}"
53 [<a href="{% url 'delete' post_id=thread.id %}"
54 >{% trans 'Delete' %}</a>]
54 >{% trans 'Delete' %}</a>]
55 </span>
55 </span>
56 {% endif %}
56 {% endif %}
57 </div>
57 </div>
58 {% autoescape off %}
58 {% autoescape off %}
59 {{ thread.text.rendered|truncatewords_html:50 }}
59 {{ thread.text.rendered|truncatewords_html:50 }}
60 {% endautoescape %}
60 {% endautoescape %}
61 </div>
61 </div>
62 <div class="metadata">
62 <div class="metadata">
63 {{ thread.get_reply_count }} {% trans 'replies' %},
63 {{ thread.get_reply_count }} {% trans 'replies' %},
64 {{ thread.get_images_count }} {% trans 'images' %}.
64 {{ thread.get_images_count }} {% trans 'images' %}.
65 {% if thread.tags.all %}
65 {% if thread.tags.all %}
66 <span class="tags">{% trans 'Tags' %}:
66 <span class="tags">{% trans 'Tags' %}:
67 {% for tag in thread.tags.all %}
67 {% for tag in thread.tags.all %}
68 <a class="tag" href="
68 <a class="tag" href="
69 {% url 'tag' tag_name=tag.name %}">
69 {% url 'tag' tag_name=tag.name %}">
70 {{ tag.name }}</a>
70 {{ tag.name }}</a>
71 {% endfor %}
71 {% endfor %}
72 </span>
72 </span>
73 {% endif %}
73 {% endif %}
74 </div>
74 </div>
75 </div>
75 </div>
76 {% if thread.get_last_replies %}
76 {% if thread.get_last_replies %}
77 <div class="last-replies">
77 <div class="last-replies">
78 {% for post in thread.get_last_replies %}
78 {% for post in thread.get_last_replies %}
79 {% if thread.can_bump %}
79 {% if thread.can_bump %}
80 <div class="post" id="{{ post.id }}">
80 <div class="post" id="{{ post.id }}">
81 {% else %}
81 {% else %}
82 <div class="post dead_post id="{{ post.id }}"">
82 <div class="post dead_post id="{{ post.id }}"">
83 {% endif %}
83 {% endif %}
84 {% if post.image %}
84 {% if post.image %}
85 <div class="image">
85 <div class="image">
86 <a class="fancy"
86 <a class="fancy"
87 href="{{ post.image.url }}"><img
87 href="{{ post.image.url }}"><img
88 src=" {{ post.image.url_200x150 }}"
88 src=" {{ post.image.url_200x150 }}"
89 alt="{% trans 'Post image' %}"
89 alt="{% trans 'Post image' %}"
90 data-width="{{ post.image_width }}"
90 data-width="{{ post.image_width }}"
91 data-height="{{ post.image_height }}"/>
91 data-height="{{ post.image_height }}"/>
92 </a>
92 </a>
93 </div>
93 </div>
94 {% endif %}
94 {% endif %}
95 <div class="message">
95 <div class="message">
96 <div class="post-info">
96 <div class="post-info">
97 <span class="title">{{ post.title }}</span>
97 <span class="title">{{ post.title }}</span>
98 <a class="post_id" href="
98 <a class="post_id" href="
99 {% url 'thread' thread.id %}#{{ post.id }}">
99 {% url 'thread' thread.id %}#{{ post.id }}">
100 (#{{ post.id }})</a>
100 (#{{ post.id }})</a>
101 [{{ post.pub_time }}]
101 [{{ post.pub_time }}]
102 </div>
102 </div>
103 {% autoescape off %}
103 {% autoescape off %}
104 {{ post.text.rendered|truncatewords_html:50 }}
104 {{ post.text.rendered|truncatewords_html:50 }}
105 {% endautoescape %}
105 {% endautoescape %}
106 </div>
106 </div>
107 </div>
107 </div>
108 {% endfor %}
108 {% endfor %}
109 </div>
109 </div>
110 {% endif %}
110 {% endif %}
111 </div>
111 </div>
112 {% endfor %}
112 {% endfor %}
113 {% else %}
113 {% else %}
114 No threads found.
114 <div class="post">
115 <hr />
115 {% trans 'No threads exist. Create the first one!' %}</div>
116 {% endif %}
116 {% endif %}
117
117
118 <form enctype="multipart/form-data" method="post">{% csrf_token %}
118 <form enctype="multipart/form-data" method="post">{% csrf_token %}
119 <div class="post-form-w">
119 <div class="post-form-w">
120
120
121 <div class="form-title">{% trans "Create new thread" %}</div>
121 <div class="form-title">{% trans "Create new thread" %}</div>
122 <div class="post-form">
122 <div class="post-form">
123 <div class="form-row">
123 <div class="form-row">
124 <div class="form-label">{% trans 'Title' %}</div>
124 <div class="form-label">{% trans 'Title' %}</div>
125 <div class="form-input">{{ form.title }}</div>
125 <div class="form-input">{{ form.title }}</div>
126 <div class="form-errors">{{ form.title.errors }}</div>
126 <div class="form-errors">{{ form.title.errors }}</div>
127 </div>
127 </div>
128 <div class="form-row">
128 <div class="form-row">
129 <div class="form-label">{% trans 'Text' %}</div>
129 <div class="form-label">{% trans 'Text' %}</div>
130 <div class="form-input">{{ form.text }}</div>
130 <div class="form-input">{{ form.text }}</div>
131 <div class="form-errors">{{ form.text.errors }}</div>
131 <div class="form-errors">{{ form.text.errors }}</div>
132 </div>
132 </div>
133 <div class="form-row">
133 <div class="form-row">
134 <div class="form-label">{% trans 'Image' %}</div>
134 <div class="form-label">{% trans 'Image' %}</div>
135 <div class="form-input">{{ form.image }}</div>
135 <div class="form-input">{{ form.image }}</div>
136 <div class="form-errors">{{ form.image.errors }}</div>
136 <div class="form-errors">{{ form.image.errors }}</div>
137 </div>
137 </div>
138 <div class="form-row">
138 <div class="form-row">
139 <div class="form-label">{% trans 'Tags' %}</div>
139 <div class="form-label">{% trans 'Tags' %}</div>
140 <div class="form-input">{{ form.tags }}</div>
140 <div class="form-input">{{ form.tags }}</div>
141 <div class="form-errors">{{ form.tags.errors }}</div>
141 <div class="form-errors">{{ form.tags.errors }}</div>
142 </div>
142 </div>
143 <div class="form-row">
143 <div class="form-row">
144 {{ form.captcha }}
144 {{ form.captcha }}
145 <div class="form-errors">{{ form.captcha.errors }}</div>
145 <div class="form-errors">{{ form.captcha.errors }}</div>
146 </div>
146 </div>
147 </div>
147 </div>
148 <div class="form-submit">
148 <div class="form-submit">
149 <input type="submit" value="{% trans "Post" %}"/></div>
149 <input type="submit" value="{% trans "Post" %}"/></div>
150 <div>
150 <div>
151 {% trans 'Tags must be delimited by spaces. Text or image is required.' %}
151 {% trans 'Tags must be delimited by spaces. Text or image is required.' %}
152 </div>
152 </div>
153 <div><a href="http://daringfireball.net/projects/markdown/basics">
153 <div><a href="http://daringfireball.net/projects/markdown/basics">
154 {% trans 'Basic markdown syntax.' %}</a></div>
154 {% trans 'Basic markdown syntax.' %}</a></div>
155 </div>
155 </div>
156 </form>
156 </form>
157
157
158 {% endblock %}
158 {% endblock %}
159
159
160 {% block metapanel %}
160 {% block metapanel %}
161
161
162 <span class="metapanel">
162 <span class="metapanel">
163 <b><a href="{% url "authors" %}">Neboard</a> pre1.0</b>
163 <b><a href="{% url "authors" %}">Neboard</a> pre1.0</b>
164 {% trans "Pages:" %}
164 {% trans "Pages:" %}
165 {% for page in pages %}
165 {% for page in pages %}
166 [<a href="
166 [<a href="
167 {% if tag %}
167 {% if tag %}
168 {% url "tag" tag_name=tag page=page %}
168 {% url "tag" tag_name=tag page=page %}
169 {% else %}
169 {% else %}
170 {% url "index" page=page %}
170 {% url "index" page=page %}
171 {% endif %}
171 {% endif %}
172 ">{{ page }}</a>]
172 ">{{ page }}</a>]
173 {% endfor %}
173 {% endfor %}
174 </span>
174 </span>
175
175
176 {% endblock %}
176 {% endblock %}
@@ -1,121 +1,118 b''
1 {% extends "boards/base.html" %}
1 {% extends "boards/base.html" %}
2
2
3 {% load i18n %}
3 {% load i18n %}
4 {% load markup %}
4 {% load markup %}
5
5
6 {% block head %}
6 {% block head %}
7 <title>Neboard - {{ posts.0.title }}</title>
7 <title>Neboard - {{ posts.0.title }}</title>
8 {% endblock %}
8 {% endblock %}
9
9
10 {% block content %}
10 {% block content %}
11 <script src="{{ STATIC_URL }}js/thread.js"></script>
11 <script src="{{ STATIC_URL }}js/thread.js"></script>
12
12
13 {% if posts %}
13 {% if posts %}
14 <div id="posts">
14 <div id="posts">
15 {% for post in posts %}
15 {% for post in posts %}
16 {% if posts.0.can_bump %}
16 {% if posts.0.can_bump %}
17 <div class="post" id="{{ post.id }}">
17 <div class="post" id="{{ post.id }}">
18 {% else %}
18 {% else %}
19 <div class="post dead_post" id="{{ post.id }}">
19 <div class="post dead_post" id="{{ post.id }}">
20 {% endif %}
20 {% endif %}
21 {% if post.image %}
21 {% if post.image %}
22 <div class="image">
22 <div class="image">
23 <a
23 <a
24 class="fancy"
24 class="fancy"
25 href="{{ post.image.url }}"><img
25 href="{{ post.image.url }}"><img
26 src="{{ post.image.url_200x150 }}"
26 src="{{ post.image.url_200x150 }}"
27 alt="{% trans 'Post image' %}"
27 alt="{% trans 'Post image' %}"
28 data-width="{{ post.image_width }}"
28 data-width="{{ post.image_width }}"
29 data-height="{{ post.image_height }}"/>
29 data-height="{{ post.image_height }}"/>
30 </a>
30 </a>
31 </div>
31 </div>
32 {% endif %}
32 {% endif %}
33 <div class="message">
33 <div class="message">
34 <div class="post-info">
34 <div class="post-info">
35 <span class="title">{{ post.title }}</span>
35 <span class="title">{{ post.title }}</span>
36 <a class="post_id" href="#{{ post.id }}">
36 <a class="post_id" href="#{{ post.id }}">
37 (#{{ post.id }})</a>
37 (#{{ post.id }})</a>
38 [{{ post.pub_time }}]
38 [{{ post.pub_time }}]
39 [<a href="#" onclick="javascript:addQuickReply('{{ post.id }}')
39 [<a href="#" onclick="javascript:addQuickReply('{{ post.id }}')
40 ; return false;">&gt;&gt;</a>]
40 ; return false;">&gt;&gt;</a>]
41
41
42 {% if user.is_moderator %}
42 {% if user.is_moderator %}
43 <span class="moderator_info">
43 <span class="moderator_info">
44 ({{ post.poster_ip }})
44 ({{ post.poster_ip }})
45 [<a href="{% url 'delete' post_id=post.id %}"
45 [<a href="{% url 'delete' post_id=post.id %}"
46 >{% trans 'Delete' %}</a>]
46 >{% trans 'Delete' %}</a>]
47 </span>
47 </span>
48 {% endif %}
48 {% endif %}
49 </div>
49 </div>
50 {% autoescape off %}
50 {% autoescape off %}
51 {{ post.text.rendered }}
51 {{ post.text.rendered }}
52 {% endautoescape %}
52 {% endautoescape %}
53 </div>
53 </div>
54 {% if post.tags.all %}
54 {% if post.tags.all %}
55 <div class="metadata">
55 <div class="metadata">
56 <span class="tags">{% trans 'Tags' %}:
56 <span class="tags">{% trans 'Tags' %}:
57 {% for tag in post.tags.all %}
57 {% for tag in post.tags.all %}
58 <a class="tag" href="{% url 'tag' tag.name %}">
58 <a class="tag" href="{% url 'tag' tag.name %}">
59 {{ tag.name }}</a>
59 {{ tag.name }}</a>
60 {% endfor %}
60 {% endfor %}
61 </span>
61 </span>
62 </div>
62 </div>
63 {% endif %}
63 {% endif %}
64 </div>
64 </div>
65 {% endfor %}
65 {% endfor %}
66 </div>
66 </div>
67 {% else %}
68 No thread found.
69 <hr />
70 {% endif %}
67 {% endif %}
71
68
72 <form id="form" enctype="multipart/form-data" method="post"
69 <form id="form" enctype="multipart/form-data" method="post"
73 >{% csrf_token %}
70 >{% csrf_token %}
74 <div class="post-form-w">
71 <div class="post-form-w">
75 <div class="form-title">{% trans "Reply to thread" %} #{{ posts.0.id }}</div>
72 <div class="form-title">{% trans "Reply to thread" %} #{{ posts.0.id }}</div>
76 <div class="post-form">
73 <div class="post-form">
77 <div class="form-row">
74 <div class="form-row">
78 <div class="form-label">{% trans 'Title' %}</div>
75 <div class="form-label">{% trans 'Title' %}</div>
79 <div class="form-input">{{ form.title }}</div>
76 <div class="form-input">{{ form.title }}</div>
80 <div class="form-errors">{{ form.title.errors }}</div>
77 <div class="form-errors">{{ form.title.errors }}</div>
81 </div>
78 </div>
82 <div class="form-row">
79 <div class="form-row">
83 <div class="form-label">{% trans 'Text' %}</div>
80 <div class="form-label">{% trans 'Text' %}</div>
84 <div class="form-input">{{ form.text }}</div>
81 <div class="form-input">{{ form.text }}</div>
85 <div class="form-errors">{{ form.text.errors }}</div>
82 <div class="form-errors">{{ form.text.errors }}</div>
86 </div>
83 </div>
87 <div class="form-row">
84 <div class="form-row">
88 <div class="form-label">{% trans 'Image' %}</div>
85 <div class="form-label">{% trans 'Image' %}</div>
89 <div class="form-input">{{ form.image }}</div>
86 <div class="form-input">{{ form.image }}</div>
90 <div class="form-errors">{{ form.image.errors }}</div>
87 <div class="form-errors">{{ form.image.errors }}</div>
91 </div>
88 </div>
92 <div class="form-row">
89 <div class="form-row">
93 {{ form.captcha }}
90 {{ form.captcha }}
94 <div class="form-errors">{{ form.captcha.errors }}</div>
91 <div class="form-errors">{{ form.captcha.errors }}</div>
95 </div>
92 </div>
96 </div>
93 </div>
97
94
98 <div class="form-submit"><input type="submit"
95 <div class="form-submit"><input type="submit"
99 value="{% trans "Post" %}"/></div>
96 value="{% trans "Post" %}"/></div>
100 <div><a href="http://daringfireball.net/projects/markdown/basics">
97 <div><a href="http://daringfireball.net/projects/markdown/basics">
101 {% trans 'Basic markdown syntax.' %}</a></div>
98 {% trans 'Basic markdown syntax.' %}</a></div>
102 <div>{% trans 'Example: ' %}*<i>{% trans 'italic' %}</i>*,
99 <div>{% trans 'Example: ' %}*<i>{% trans 'italic' %}</i>*,
103 **<b>{% trans 'bold' %}</b>**</div>
100 **<b>{% trans 'bold' %}</b>**</div>
104 <div>{% trans 'Quotes can be inserted with' %} "&gt;"</div>
101 <div>{% trans 'Quotes can be inserted with' %} "&gt;"</div>
105 <div>{% trans 'Links to answers can be inserted with' %}
102 <div>{% trans 'Links to answers can be inserted with' %}
106 "&gt;&gt;123"
103 "&gt;&gt;123"
107 </div>
104 </div>
108 </div>
105 </div>
109 </form>
106 </form>
110
107
111 {% endblock %}
108 {% endblock %}
112
109
113 {% block metapanel %}
110 {% block metapanel %}
114
111
115 <span class="metapanel">
112 <span class="metapanel">
116 {{ posts.0.get_reply_count }} {% trans 'replies' %},
113 {{ posts.0.get_reply_count }} {% trans 'replies' %},
117 {{ posts.0.get_images_count }} {% trans 'images' %}.
114 {{ posts.0.get_images_count }} {% trans 'images' %}.
118 {% trans 'Last update: ' %}{{ posts.0.last_edit_time }}
115 {% trans 'Last update: ' %}{{ posts.0.last_edit_time }}
119 </span>
116 </span>
120
117
121 {% endblock %}
118 {% endblock %}
General Comments 0
You need to be logged in to leave comments. Login now