##// END OF EJS Templates
Added login functionality.
neko259 -
r144:7af95110 1.1
parent child Browse files
Show More
@@ -1,133 +1,151 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, User
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 class ThreadForm(PostForm):
70 class ThreadForm(PostForm):
71 regex_tags = re.compile(ur'^[\w\s\d]+$', re.UNICODE)
71 regex_tags = re.compile(ur'^[\w\s\d]+$', re.UNICODE)
72 tags = forms.CharField(max_length=100)
72 tags = forms.CharField(max_length=100)
73
73
74 def clean_tags(self):
74 def clean_tags(self):
75 tags = self.cleaned_data['tags']
75 tags = self.cleaned_data['tags']
76
76
77 if tags:
77 if tags:
78 if not self.regex_tags.match(tags):
78 if not self.regex_tags.match(tags):
79 raise forms.ValidationError(
79 raise forms.ValidationError(
80 'Inappropriate characters in tags.')
80 'Inappropriate characters in tags.')
81
81
82 return tags
82 return tags
83
83
84 def clean(self):
84 def clean(self):
85 cleaned_data = super(ThreadForm, self).clean()
85 cleaned_data = super(ThreadForm, self).clean()
86
86
87 return cleaned_data
87 return cleaned_data
88
88
89
89
90 class PostCaptchaForm(PostForm):
90 class PostCaptchaForm(PostForm):
91 captcha = CaptchaField()
91 captcha = CaptchaField()
92
92
93 def __init__(self, *args, **kwargs):
93 def __init__(self, *args, **kwargs):
94 self.request = kwargs['request']
94 self.request = kwargs['request']
95 del kwargs['request']
95 del kwargs['request']
96
96
97 super(PostCaptchaForm, self).__init__(*args, **kwargs)
97 super(PostCaptchaForm, self).__init__(*args, **kwargs)
98
98
99 def clean(self):
99 def clean(self):
100 cleaned_data = super(PostCaptchaForm, self).clean()
100 cleaned_data = super(PostCaptchaForm, self).clean()
101
101
102 success = self.is_valid()
102 success = self.is_valid()
103 utils.update_captcha_access(self.request, success)
103 utils.update_captcha_access(self.request, success)
104
104
105 if success:
105 if success:
106 return cleaned_data
106 return cleaned_data
107 else:
107 else:
108 raise forms.ValidationError("captcha validation failed")
108 raise forms.ValidationError("captcha validation failed")
109
109
110
110
111 class ThreadCaptchaForm(ThreadForm):
111 class ThreadCaptchaForm(ThreadForm):
112 captcha = CaptchaField()
112 captcha = CaptchaField()
113
113
114 def __init__(self, *args, **kwargs):
114 def __init__(self, *args, **kwargs):
115 self.request = kwargs['request']
115 self.request = kwargs['request']
116 del kwargs['request']
116 del kwargs['request']
117
117
118 super(ThreadCaptchaForm, self).__init__(*args, **kwargs)
118 super(ThreadCaptchaForm, self).__init__(*args, **kwargs)
119
119
120 def clean(self):
120 def clean(self):
121 cleaned_data = super(ThreadCaptchaForm, self).clean()
121 cleaned_data = super(ThreadCaptchaForm, self).clean()
122
122
123 success = self.is_valid()
123 success = self.is_valid()
124 utils.update_captcha_access(self.request, success)
124 utils.update_captcha_access(self.request, success)
125
125
126 if success:
126 if success:
127 return cleaned_data
127 return cleaned_data
128 else:
128 else:
129 raise forms.ValidationError("captcha validation failed")
129 raise forms.ValidationError("captcha validation failed")
130
130
131
131
132 class SettingsForm(forms.Form):
132 class SettingsForm(forms.Form):
133 theme = forms.ChoiceField(choices=settings.THEMES, widget=forms.RadioSelect)
133 theme = forms.ChoiceField(choices=settings.THEMES, widget=forms.RadioSelect)
134
135
136 class LoginForm(forms.Form):
137 user_id = forms.CharField()
138
139 def clean_user_id(self):
140 user_id = self.cleaned_data['user_id']
141 if user_id:
142 users = User.objects.filter(user_id=user_id)
143 if len(users) == 0:
144 raise forms.ValidationError('No such user found')
145
146 return user_id
147
148 def clean(self):
149 cleaned_data = super(LoginForm, self).clean()
150
151 return cleaned_data No newline at end of file
1 NO CONTENT: modified file, binary diff hidden
NO CONTENT: modified file, binary diff hidden
@@ -1,205 +1,218 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-09-06 21:15+0300\n"
10 "POT-Creation-Date: 2013-09-06 21:49+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:29
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:35
61 #: templates/boards/base.html:35
62 msgid "Settings"
62 msgid "Settings"
63 msgstr "Настройки"
63 msgstr "Настройки"
64
64
65 #: templates/boards/base.html:42 templates/boards/login.html:6
66 #: templates/boards/login.html.py:21
67 msgid "Login"
68 msgstr "Π’Ρ…ΠΎΠ΄"
69
65 #: templates/boards/base.html:43
70 #: templates/boards/base.html:43
66 msgid "Up"
71 msgid "Up"
67 msgstr "Π’Π²Π΅Ρ€Ρ…"
72 msgstr "Π’Π²Π΅Ρ€Ρ…"
68
73
74 #: templates/boards/login.html:15
75 msgid "User ID"
76 msgstr "ID ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ"
77
78 #: templates/boards/login.html:24
79 msgid "Insert your user id above"
80 msgstr "Π’ΡΡ‚Π°Π²ΡŒΡ‚Π΅ свой ID ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ Π²Ρ‹ΡˆΠ΅"
81
69 #: templates/boards/posting_general.html:18
82 #: templates/boards/posting_general.html:18
70 msgid "Tag: "
83 msgid "Tag: "
71 msgstr "Π’Π΅Π³: "
84 msgstr "Π’Π΅Π³: "
72
85
73 #: templates/boards/posting_general.html:35
86 #: templates/boards/posting_general.html:35
74 #: templates/boards/posting_general.html:89 templates/boards/thread.html:27
87 #: templates/boards/posting_general.html:89 templates/boards/thread.html:27
75 #: templates/boards/rss/post.html:5
88 #: templates/boards/rss/post.html:5
76 msgid "Post image"
89 msgid "Post image"
77 msgstr "Π˜Π·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ сообщСния"
90 msgstr "Π˜Π·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ сообщСния"
78
91
79 #: templates/boards/posting_general.html:48
92 #: templates/boards/posting_general.html:48
80 msgid "Reply"
93 msgid "Reply"
81 msgstr "ΠžΡ‚Π²Π΅Ρ‚"
94 msgstr "ΠžΡ‚Π²Π΅Ρ‚"
82
95
83 #: templates/boards/posting_general.html:54 templates/boards/thread.html:46
96 #: templates/boards/posting_general.html:54 templates/boards/thread.html:46
84 msgid "Delete"
97 msgid "Delete"
85 msgstr "Π£Π΄Π°Π»ΠΈΡ‚ΡŒ"
98 msgstr "Π£Π΄Π°Π»ΠΈΡ‚ΡŒ"
86
99
87 #: templates/boards/posting_general.html:63 templates/boards/thread.html:113
100 #: templates/boards/posting_general.html:63 templates/boards/thread.html:113
88 msgid "replies"
101 msgid "replies"
89 msgstr "ΠΎΡ‚Π²Π΅Ρ‚ΠΎΠ²"
102 msgstr "ΠΎΡ‚Π²Π΅Ρ‚ΠΎΠ²"
90
103
91 #: templates/boards/posting_general.html:64 templates/boards/thread.html:114
104 #: templates/boards/posting_general.html:64 templates/boards/thread.html:114
92 msgid "images"
105 msgid "images"
93 msgstr "ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ"
106 msgstr "ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠΉ"
94
107
95 #: templates/boards/posting_general.html:66
108 #: templates/boards/posting_general.html:66
96 #: templates/boards/posting_general.html:139 templates/boards/thread.html:56
109 #: templates/boards/posting_general.html:139 templates/boards/thread.html:56
97 #: templates/boards/rss/post.html:10
110 #: templates/boards/rss/post.html:10
98 msgid "Tags"
111 msgid "Tags"
99 msgstr "Π’Π΅Π³ΠΈ"
112 msgstr "Π’Π΅Π³ΠΈ"
100
113
101 #: templates/boards/posting_general.html:115
114 #: templates/boards/posting_general.html:115
102 msgid "No threads exist. Create the first one!"
115 msgid "No threads exist. Create the first one!"
103 msgstr "НСт Ρ‚Π΅ΠΌ. Π‘ΠΎΠ·Π΄Π°ΠΉΡ‚Π΅ ΠΏΠ΅Ρ€Π²ΡƒΡŽ!"
116 msgstr "НСт Ρ‚Π΅ΠΌ. Π‘ΠΎΠ·Π΄Π°ΠΉΡ‚Π΅ ΠΏΠ΅Ρ€Π²ΡƒΡŽ!"
104
117
105 #: templates/boards/posting_general.html:121
118 #: templates/boards/posting_general.html:121
106 msgid "Create new thread"
119 msgid "Create new thread"
107 msgstr "Π‘ΠΎΠ·Π΄Π°Ρ‚ΡŒ Π½ΠΎΠ²ΡƒΡŽ Ρ‚Π΅ΠΌΡƒ"
120 msgstr "Π‘ΠΎΠ·Π΄Π°Ρ‚ΡŒ Π½ΠΎΠ²ΡƒΡŽ Ρ‚Π΅ΠΌΡƒ"
108
121
109 #: templates/boards/posting_general.html:124 templates/boards/thread.html:75
122 #: templates/boards/posting_general.html:124 templates/boards/thread.html:75
110 msgid "Title"
123 msgid "Title"
111 msgstr "Π—Π°Π³ΠΎΠ»ΠΎΠ²ΠΎΠΊ"
124 msgstr "Π—Π°Π³ΠΎΠ»ΠΎΠ²ΠΎΠΊ"
112
125
113 #: templates/boards/posting_general.html:129 templates/boards/thread.html:80
126 #: templates/boards/posting_general.html:129 templates/boards/thread.html:80
114 msgid "Text"
127 msgid "Text"
115 msgstr "ВСкст"
128 msgstr "ВСкст"
116
129
117 #: templates/boards/posting_general.html:134 templates/boards/thread.html:85
130 #: templates/boards/posting_general.html:134 templates/boards/thread.html:85
118 msgid "Image"
131 msgid "Image"
119 msgstr "Π˜Π·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅"
132 msgstr "Π˜Π·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅"
120
133
121 #: templates/boards/posting_general.html:149 templates/boards/thread.html:96
134 #: templates/boards/posting_general.html:149 templates/boards/thread.html:96
122 msgid "Post"
135 msgid "Post"
123 msgstr "ΠžΡ‚ΠΏΡ€Π°Π²ΠΈΡ‚ΡŒ"
136 msgstr "ΠžΡ‚ΠΏΡ€Π°Π²ΠΈΡ‚ΡŒ"
124
137
125 #: templates/boards/posting_general.html:151
138 #: templates/boards/posting_general.html:151
126 msgid "Tags must be delimited by spaces. Text or image is required."
139 msgid "Tags must be delimited by spaces. Text or image is required."
127 msgstr ""
140 msgstr ""
128 "Π’Π΅Π³ΠΈ Π΄ΠΎΠ»ΠΆΠ½Ρ‹ Π±Ρ‹Ρ‚ΡŒ Ρ€Π°Π·Π΄Π΅Π»Π΅Π½Ρ‹ ΠΏΡ€ΠΎΠ±Π΅Π»Π°ΠΌΠΈ. ВСкст ΠΈΠ»ΠΈ ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ ΠΎΠ±ΡΠ·Π°Ρ‚Π΅Π»ΡŒΠ½Ρ‹."
141 "Π’Π΅Π³ΠΈ Π΄ΠΎΠ»ΠΆΠ½Ρ‹ Π±Ρ‹Ρ‚ΡŒ Ρ€Π°Π·Π΄Π΅Π»Π΅Π½Ρ‹ ΠΏΡ€ΠΎΠ±Π΅Π»Π°ΠΌΠΈ. ВСкст ΠΈΠ»ΠΈ ΠΈΠ·ΠΎΠ±Ρ€Π°ΠΆΠ΅Π½ΠΈΠ΅ ΠΎΠ±ΡΠ·Π°Ρ‚Π΅Π»ΡŒΠ½Ρ‹."
129
142
130 #: templates/boards/posting_general.html:154 templates/boards/thread.html:98
143 #: templates/boards/posting_general.html:154 templates/boards/thread.html:98
131 msgid "Basic markdown syntax."
144 msgid "Basic markdown syntax."
132 msgstr "Π‘Π°Π·ΠΎΠ²Ρ‹ΠΉ синтаксис markdown."
145 msgstr "Π‘Π°Π·ΠΎΠ²Ρ‹ΠΉ синтаксис markdown."
133
146
134 #: templates/boards/posting_general.html:164
147 #: templates/boards/posting_general.html:164
135 msgid "Pages:"
148 msgid "Pages:"
136 msgstr "Π‘Ρ‚Ρ€Π°Π½ΠΈΡ†Ρ‹: "
149 msgstr "Π‘Ρ‚Ρ€Π°Π½ΠΈΡ†Ρ‹: "
137
150
138 #: templates/boards/settings.html:12
151 #: templates/boards/settings.html:12
139 msgid "User:"
152 msgid "User:"
140 msgstr "ΠŸΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒ:"
153 msgstr "ΠŸΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒ:"
141
154
142 #: templates/boards/settings.html:14
155 #: templates/boards/settings.html:14
143 msgid "You are moderator."
156 msgid "You are moderator."
144 msgstr "Π’Ρ‹ ΠΌΠΎΠ΄Π΅Ρ€Π°Ρ‚ΠΎΡ€."
157 msgstr "Π’Ρ‹ ΠΌΠΎΠ΄Π΅Ρ€Π°Ρ‚ΠΎΡ€."
145
158
146 #: templates/boards/settings.html:20
159 #: templates/boards/settings.html:20
147 msgid "Theme"
160 msgid "Theme"
148 msgstr "Π’Π΅ΠΌΠ°"
161 msgstr "Π’Π΅ΠΌΠ°"
149
162
150 #: templates/boards/settings.html:36
163 #: templates/boards/settings.html:36
151 msgid "Save"
164 msgid "Save"
152 msgstr "Π‘ΠΎΡ…Ρ€Π°Π½ΠΈΡ‚ΡŒ"
165 msgstr "Π‘ΠΎΡ…Ρ€Π°Π½ΠΈΡ‚ΡŒ"
153
166
154 #: templates/boards/tags.html:7
167 #: templates/boards/tags.html:7
155 msgid "tags"
168 msgid "tags"
156 msgstr "Ρ‚Π΅Π³ΠΎΠ²"
169 msgstr "Ρ‚Π΅Π³ΠΎΠ²"
157
170
158 #: templates/boards/thread.html:72
171 #: templates/boards/thread.html:72
159 msgid "Reply to thread"
172 msgid "Reply to thread"
160 msgstr "ΠžΡ‚Π²Π΅Ρ‚ΠΈΡ‚ΡŒ Π² Ρ‚Π΅ΠΌΡƒ"
173 msgstr "ΠžΡ‚Π²Π΅Ρ‚ΠΈΡ‚ΡŒ Π² Ρ‚Π΅ΠΌΡƒ"
161
174
162 #: templates/boards/thread.html:99
175 #: templates/boards/thread.html:99
163 msgid "Example: "
176 msgid "Example: "
164 msgstr "ΠŸΡ€ΠΈΠΌΠ΅Ρ€: "
177 msgstr "ΠŸΡ€ΠΈΠΌΠ΅Ρ€: "
165
178
166 #: templates/boards/thread.html:99
179 #: templates/boards/thread.html:99
167 msgid "italic"
180 msgid "italic"
168 msgstr "курсив"
181 msgstr "курсив"
169
182
170 #: templates/boards/thread.html:100
183 #: templates/boards/thread.html:100
171 msgid "bold"
184 msgid "bold"
172 msgstr "ΠΏΠΎΠ»ΡƒΠΆΠΈΡ€Π½Ρ‹ΠΉ"
185 msgstr "ΠΏΠΎΠ»ΡƒΠΆΠΈΡ€Π½Ρ‹ΠΉ"
173
186
174 #: templates/boards/thread.html:101
187 #: templates/boards/thread.html:101
175 msgid "Quotes can be inserted with"
188 msgid "Quotes can be inserted with"
176 msgstr "Π¦ΠΈΡ‚Π°Ρ‚Ρ‹ ΠΌΠΎΠ³ΡƒΡ‚ Π±Ρ‹Ρ‚ΡŒ вставлСны ΠΏΡ€ΠΈ ΠΏΠΎΠΌΠΎΡ‰ΠΈ"
189 msgstr "Π¦ΠΈΡ‚Π°Ρ‚Ρ‹ ΠΌΠΎΠ³ΡƒΡ‚ Π±Ρ‹Ρ‚ΡŒ вставлСны ΠΏΡ€ΠΈ ΠΏΠΎΠΌΠΎΡ‰ΠΈ"
177
190
178 #: templates/boards/thread.html:102
191 #: templates/boards/thread.html:102
179 msgid "Links to answers can be inserted with"
192 msgid "Links to answers can be inserted with"
180 msgstr "Бсылки Π½Π° ΠΎΡ‚Π²Π΅Ρ‚Ρ‹ ΠΌΠΎΠ³ΡƒΡ‚ Π±Ρ‹Ρ‚ΡŒ вставлСны с ΠΏΠΎΠΌΠΎΡ‰ΡŒΡŽ"
193 msgstr "Бсылки Π½Π° ΠΎΡ‚Π²Π΅Ρ‚Ρ‹ ΠΌΠΎΠ³ΡƒΡ‚ Π±Ρ‹Ρ‚ΡŒ вставлСны с ΠΏΠΎΠΌΠΎΡ‰ΡŒΡŽ"
181
194
182 #: templates/boards/thread.html:115
195 #: templates/boards/thread.html:115
183 msgid "Last update: "
196 msgid "Last update: "
184 msgstr "ПослСднСС обновлСниС: "
197 msgstr "ПослСднСС обновлСниС: "
185
198
186 #~ msgid "Get!"
199 #~ msgid "Get!"
187 #~ msgstr "Π“Π΅Ρ‚!"
200 #~ msgstr "Π“Π΅Ρ‚!"
188
201
189 #~ msgid "View"
202 #~ msgid "View"
190 #~ msgstr "ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€"
203 #~ msgstr "ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€"
191
204
192 #~ msgid "gets"
205 #~ msgid "gets"
193 #~ msgstr "Π³Π΅Ρ‚ΠΎΠ²"
206 #~ msgstr "Π³Π΅Ρ‚ΠΎΠ²"
194
207
195 #~ msgid "author"
208 #~ msgid "author"
196 #~ msgstr "Π°Π²Ρ‚ΠΎΡ€"
209 #~ msgstr "Π°Π²Ρ‚ΠΎΡ€"
197
210
198 #~ msgid "developer"
211 #~ msgid "developer"
199 #~ msgstr "Ρ€Π°Π·Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊ"
212 #~ msgstr "Ρ€Π°Π·Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊ"
200
213
201 #~ msgid "javascript developer"
214 #~ msgid "javascript developer"
202 #~ msgstr "Ρ€Π°Π·Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊ javascript"
215 #~ msgstr "Ρ€Π°Π·Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊ javascript"
203
216
204 #~ msgid "designer"
217 #~ msgid "designer"
205 #~ msgstr "Π΄ΠΈΠ·Π°ΠΉΠ½Π΅Ρ€"
218 #~ msgstr "Π΄ΠΈΠ·Π°ΠΉΠ½Π΅Ρ€"
@@ -1,329 +1,331 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
35 class PostManager(models.Manager):
35 class PostManager(models.Manager):
36 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,
37 ip=NO_IP, tags=None, user=None):
37 ip=NO_IP, tags=None, user=None):
38 post = self.create(title=title,
38 post = self.create(title=title,
39 text=text,
39 text=text,
40 pub_time=timezone.now(),
40 pub_time=timezone.now(),
41 parent=parent_id,
41 parent=parent_id,
42 image=image,
42 image=image,
43 poster_ip=ip,
43 poster_ip=ip,
44 poster_user_agent=UNKNOWN_UA,
44 poster_user_agent=UNKNOWN_UA,
45 last_edit_time=timezone.now(),
45 last_edit_time=timezone.now(),
46 user=user)
46 user=user)
47
47
48 if tags:
48 if tags:
49 map(post.tags.add, tags)
49 map(post.tags.add, tags)
50
50
51 if parent_id != NO_PARENT:
51 if parent_id != NO_PARENT:
52 self._bump_thread(parent_id)
52 self._bump_thread(parent_id)
53 else:
53 else:
54 self._delete_old_threads()
54 self._delete_old_threads()
55
55
56 return post
56 return post
57
57
58 def delete_post(self, post):
58 def delete_post(self, post):
59 children = self.filter(parent=post.id)
59 children = self.filter(parent=post.id)
60 for child in children:
60 for child in children:
61 self.delete_post(child)
61 self.delete_post(child)
62 post.delete()
62 post.delete()
63
63
64 def delete_posts_by_ip(self, ip):
64 def delete_posts_by_ip(self, ip):
65 posts = self.filter(poster_ip=ip)
65 posts = self.filter(poster_ip=ip)
66 for post in posts:
66 for post in posts:
67 self.delete_post(post)
67 self.delete_post(post)
68
68
69 def get_threads(self, tag=None, page=ALL_PAGES,
69 def get_threads(self, tag=None, page=ALL_PAGES,
70 order_by='-last_edit_time'):
70 order_by='-last_edit_time'):
71 if tag:
71 if tag:
72 threads = self.filter(parent=NO_PARENT, tags=tag)
72 threads = self.filter(parent=NO_PARENT, tags=tag)
73
73
74 # TODO Throw error 404 if no threads for tag found?
74 # TODO Throw error 404 if no threads for tag found?
75 else:
75 else:
76 threads = self.filter(parent=NO_PARENT)
76 threads = self.filter(parent=NO_PARENT)
77
77
78 threads = threads.order_by(order_by)
78 threads = threads.order_by(order_by)
79
79
80 if page != ALL_PAGES:
80 if page != ALL_PAGES:
81 thread_count = len(threads)
81 thread_count = len(threads)
82
82
83 if page < self.get_thread_page_count(tag=tag):
83 if page < self.get_thread_page_count(tag=tag):
84 start_thread = page * settings.THREADS_PER_PAGE
84 start_thread = page * settings.THREADS_PER_PAGE
85 end_thread = min(start_thread + settings.THREADS_PER_PAGE,
85 end_thread = min(start_thread + settings.THREADS_PER_PAGE,
86 thread_count)
86 thread_count)
87 threads = threads[start_thread:end_thread]
87 threads = threads[start_thread:end_thread]
88
88
89 return threads
89 return threads
90
90
91 def get_thread(self, opening_post_id):
91 def get_thread(self, opening_post_id):
92 try:
92 try:
93 opening_post = self.get(id=opening_post_id, parent=NO_PARENT)
93 opening_post = self.get(id=opening_post_id, parent=NO_PARENT)
94 except Post.DoesNotExist:
94 except Post.DoesNotExist:
95 raise Http404
95 raise Http404
96
96
97 if opening_post.parent == NO_PARENT:
97 if opening_post.parent == NO_PARENT:
98 replies = self.filter(parent=opening_post_id)
98 replies = self.filter(parent=opening_post_id)
99
99
100 thread = [opening_post]
100 thread = [opening_post]
101 thread.extend(replies)
101 thread.extend(replies)
102
102
103 return thread
103 return thread
104
104
105 def exists(self, post_id):
105 def exists(self, post_id):
106 posts = self.filter(id=post_id)
106 posts = self.filter(id=post_id)
107
107
108 return posts.count() > 0
108 return posts.count() > 0
109
109
110 def get_thread_page_count(self, tag=None):
110 def get_thread_page_count(self, tag=None):
111 if tag:
111 if tag:
112 threads = self.filter(parent=NO_PARENT, tags=tag)
112 threads = self.filter(parent=NO_PARENT, tags=tag)
113 else:
113 else:
114 threads = self.filter(parent=NO_PARENT)
114 threads = self.filter(parent=NO_PARENT)
115
115
116 return int(math.ceil(threads.count() / float(
116 return int(math.ceil(threads.count() / float(
117 settings.THREADS_PER_PAGE)))
117 settings.THREADS_PER_PAGE)))
118
118
119 def _delete_old_threads(self):
119 def _delete_old_threads(self):
120 """
120 """
121 Preserves maximum thread count. If there are too many threads,
121 Preserves maximum thread count. If there are too many threads,
122 delete the old ones.
122 delete the old ones.
123 """
123 """
124
124
125 # TODO Move old threads to the archive instead of deleting them.
125 # TODO Move old threads to the archive instead of deleting them.
126 # 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
127 # must not be shown and be able for replying.
127 # must not be shown and be able for replying.
128
128
129 threads = self.get_threads()
129 threads = self.get_threads()
130 thread_count = len(threads)
130 thread_count = len(threads)
131
131
132 if thread_count > settings.MAX_THREAD_COUNT:
132 if thread_count > settings.MAX_THREAD_COUNT:
133 num_threads_to_delete = thread_count - settings.MAX_THREAD_COUNT
133 num_threads_to_delete = thread_count - settings.MAX_THREAD_COUNT
134 old_threads = threads[thread_count - num_threads_to_delete:]
134 old_threads = threads[thread_count - num_threads_to_delete:]
135
135
136 for thread in old_threads:
136 for thread in old_threads:
137 self.delete_post(thread)
137 self.delete_post(thread)
138
138
139 def _bump_thread(self, thread_id):
139 def _bump_thread(self, thread_id):
140 thread = self.get(id=thread_id)
140 thread = self.get(id=thread_id)
141
141
142 if thread.can_bump():
142 if thread.can_bump():
143 thread.last_edit_time = timezone.now()
143 thread.last_edit_time = timezone.now()
144 thread.save()
144 thread.save()
145
145
146
146
147 class TagManager(models.Manager):
147 class TagManager(models.Manager):
148 def get_not_empty_tags(self):
148 def get_not_empty_tags(self):
149 all_tags = self.all().order_by('name')
149 all_tags = self.all().order_by('name')
150 tags = []
150 tags = []
151 for tag in all_tags:
151 for tag in all_tags:
152 if not tag.is_empty():
152 if not tag.is_empty():
153 tags.append(tag)
153 tags.append(tag)
154
154
155 return tags
155 return tags
156
156
157 def get_popular_tags(self):
157 def get_popular_tags(self):
158 all_tags = self.get_not_empty_tags()
158 all_tags = self.get_not_empty_tags()
159
159
160 sorted_tags = sorted(all_tags, key=lambda tag: tag.get_popularity(),
160 sorted_tags = sorted(all_tags, key=lambda tag: tag.get_popularity(),
161 reverse=True)
161 reverse=True)
162
162
163 return sorted_tags[:settings.POPULAR_TAGS]
163 return sorted_tags[:settings.POPULAR_TAGS]
164
164
165
165
166 class Tag(models.Model):
166 class Tag(models.Model):
167 """
167 """
168 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
169 section. There can be multiple tags for each message
169 section. There can be multiple tags for each message
170 """
170 """
171
171
172 objects = TagManager()
172 objects = TagManager()
173
173
174 name = models.CharField(max_length=100)
174 name = models.CharField(max_length=100)
175 # 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
176 # the tag.
176 # the tag.
177
177
178 def __unicode__(self):
178 def __unicode__(self):
179 return self.name
179 return self.name
180
180
181 def is_empty(self):
181 def is_empty(self):
182 return self.get_post_count() == 0
182 return self.get_post_count() == 0
183
183
184 def get_post_count(self):
184 def get_post_count(self):
185 posts_with_tag = Post.objects.get_threads(tag=self)
185 posts_with_tag = Post.objects.get_threads(tag=self)
186 return posts_with_tag.count()
186 return posts_with_tag.count()
187
187
188 def get_popularity(self):
188 def get_popularity(self):
189 posts_with_tag = Post.objects.get_threads(tag=self)
189 posts_with_tag = Post.objects.get_threads(tag=self)
190 reply_count = 0
190 reply_count = 0
191 for post in posts_with_tag:
191 for post in posts_with_tag:
192 reply_count += post.get_reply_count()
192 reply_count += post.get_reply_count()
193 reply_count += OPENING_POST_POPULARITY_WEIGHT
193 reply_count += OPENING_POST_POPULARITY_WEIGHT
194
194
195 return reply_count
195 return reply_count
196
196
197
197
198 class Post(models.Model):
198 class Post(models.Model):
199 """A post is a message."""
199 """A post is a message."""
200
200
201 objects = PostManager()
201 objects = PostManager()
202
202
203 def _update_image_filename(self, filename):
203 def _update_image_filename(self, filename):
204 """Get unique image filename"""
204 """Get unique image filename"""
205
205
206 path = IMAGES_DIRECTORY
206 path = IMAGES_DIRECTORY
207 new_name = str(int(time.mktime(time.gmtime())))
207 new_name = str(int(time.mktime(time.gmtime())))
208 new_name += str(int(random() * 1000))
208 new_name += str(int(random() * 1000))
209 new_name += FILE_EXTENSION_DELIMITER
209 new_name += FILE_EXTENSION_DELIMITER
210 new_name += filename.split(FILE_EXTENSION_DELIMITER)[-1:][0]
210 new_name += filename.split(FILE_EXTENSION_DELIMITER)[-1:][0]
211
211
212 return os.path.join(path, new_name)
212 return os.path.join(path, new_name)
213
213
214 title = models.CharField(max_length=TITLE_MAX_LENGTH)
214 title = models.CharField(max_length=TITLE_MAX_LENGTH)
215 pub_time = models.DateTimeField()
215 pub_time = models.DateTimeField()
216 text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE,
216 text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE,
217 escape_html=False)
217 escape_html=False)
218
218
219 image_width = models.IntegerField(default=0)
219 image_width = models.IntegerField(default=0)
220 image_height = models.IntegerField(default=0)
220 image_height = models.IntegerField(default=0)
221
221
222 image = thumbs.ImageWithThumbsField(upload_to=_update_image_filename,
222 image = thumbs.ImageWithThumbsField(upload_to=_update_image_filename,
223 blank=True, sizes=(IMAGE_THUMB_SIZE,),
223 blank=True, sizes=(IMAGE_THUMB_SIZE,),
224 width_field='image_width',
224 width_field='image_width',
225 height_field='image_height')
225 height_field='image_height')
226
226
227 poster_ip = models.GenericIPAddressField()
227 poster_ip = models.GenericIPAddressField()
228 poster_user_agent = models.TextField()
228 poster_user_agent = models.TextField()
229 parent = models.BigIntegerField()
229 parent = models.BigIntegerField()
230 tags = models.ManyToManyField(Tag)
230 tags = models.ManyToManyField(Tag)
231 last_edit_time = models.DateTimeField()
231 last_edit_time = models.DateTimeField()
232 user = models.ForeignKey('User', null=True, default=None)
232 user = models.ForeignKey('User', null=True, default=None)
233
233
234 def __unicode__(self):
234 def __unicode__(self):
235 return '#' + str(self.id) + ' ' + self.title + ' (' + \
235 return '#' + str(self.id) + ' ' + self.title + ' (' + \
236 self.text.raw[:50] + ')'
236 self.text.raw[:50] + ')'
237
237
238 def _get_replies(self):
238 def _get_replies(self):
239 return Post.objects.filter(parent=self.id)
239 return Post.objects.filter(parent=self.id)
240
240
241 def get_reply_count(self):
241 def get_reply_count(self):
242 return self._get_replies().count()
242 return self._get_replies().count()
243
243
244 def get_images_count(self):
244 def get_images_count(self):
245 images_count = 1 if self.image else 0
245 images_count = 1 if self.image else 0
246 for reply in self._get_replies():
246 for reply in self._get_replies():
247 if reply.image:
247 if reply.image:
248 images_count += 1
248 images_count += 1
249
249
250 return images_count
250 return images_count
251
251
252 def get_gets_count(self):
252 def get_gets_count(self):
253 gets_count = 1 if self.is_get() else 0
253 gets_count = 1 if self.is_get() else 0
254 for reply in self._get_replies():
254 for reply in self._get_replies():
255 if reply.is_get():
255 if reply.is_get():
256 gets_count += 1
256 gets_count += 1
257
257
258 return gets_count
258 return gets_count
259
259
260 def can_bump(self):
260 def can_bump(self):
261 """Check if the thread can be bumped by replying"""
261 """Check if the thread can be bumped by replying"""
262
262
263 replies_count = len(Post.objects.get_thread(self.id))
263 replies_count = len(Post.objects.get_thread(self.id))
264
264
265 return replies_count <= settings.MAX_POSTS_PER_THREAD
265 return replies_count <= settings.MAX_POSTS_PER_THREAD
266
266
267 def get_last_replies(self):
267 def get_last_replies(self):
268 if settings.LAST_REPLIES_COUNT > 0:
268 if settings.LAST_REPLIES_COUNT > 0:
269 reply_count = self.get_reply_count()
269 reply_count = self.get_reply_count()
270
270
271 if reply_count > 0:
271 if reply_count > 0:
272 reply_count_to_show = min(settings.LAST_REPLIES_COUNT,
272 reply_count_to_show = min(settings.LAST_REPLIES_COUNT,
273 reply_count)
273 reply_count)
274 last_replies = self._get_replies()[reply_count
274 last_replies = self._get_replies()[reply_count
275 - reply_count_to_show:]
275 - reply_count_to_show:]
276
276
277 return last_replies
277 return last_replies
278
278
279
279
280 class User(models.Model):
280 class User(models.Model):
281
281
282 user_id = models.CharField(max_length=50)
282 user_id = models.CharField(max_length=50)
283 rank = models.IntegerField()
283 rank = models.IntegerField()
284
284
285 registration_time = models.DateTimeField()
285 registration_time = models.DateTimeField()
286 last_access_time = models.DateTimeField()
286 last_access_time = models.DateTimeField()
287
287
288 fav_tags = models.ManyToManyField(Tag)
288 fav_tags = models.ManyToManyField(Tag, null=True, blank=True)
289 fav_threads = models.ManyToManyField(Post, related_name='+')
289 fav_threads = models.ManyToManyField(Post, related_name='+', null=True,
290 blank=True)
290
291
291 def save_setting(self, name, value):
292 def save_setting(self, name, value):
292 setting, created = Setting.objects.get_or_create(name=name, user=self)
293 setting, created = Setting.objects.get_or_create(name=name, user=self)
293 setting.value = value
294 setting.value = value
294 setting.save()
295 setting.save()
295
296
296 return setting
297 return setting
297
298
298 def get_setting(self, name):
299 def get_setting(self, name):
299 settings = Setting.objects.filter(name=name, user=self)
300 settings = Setting.objects.filter(name=name, user=self)
300 if len(settings) > 0:
301 if len(settings) > 0:
301 setting = settings[0]
302 setting = settings[0]
302 else:
303 else:
303 setting = None
304 setting = None
304
305
305 if setting:
306 if setting:
306 setting_value = setting.value
307 setting_value = setting.value
307 else:
308 else:
308 setting_value = None
309 setting_value = None
309
310
310 return setting_value
311 return setting_value
311
312
312 def is_moderator(self):
313 def is_moderator(self):
313 return RANK_MODERATOR >= self.rank
314 return RANK_MODERATOR >= self.rank
314
315
315 def __unicode__(self):
316 def __unicode__(self):
316 return self.user_id
317 return self.user_id
317
318
318
319
319 class Setting(models.Model):
320 class Setting(models.Model):
320
321
321 name = models.CharField(max_length=50)
322 name = models.CharField(max_length=50)
322 value = models.CharField(max_length=50)
323 value = models.CharField(max_length=50)
323 user = models.ForeignKey(User)
324 user = models.ForeignKey(User)
324
325
326
325 class Ban(models.Model):
327 class Ban(models.Model):
326 ip = models.GenericIPAddressField()
328 ip = models.GenericIPAddressField()
327
329
328 def __unicode__(self):
330 def __unicode__(self):
329 return self.ip
331 return self.ip
@@ -1,290 +1,290 b''
1 html {
1 html {
2 background: #555;
2 background: #555;
3 color: #ffffff;
3 color: #ffffff;
4 }
4 }
5
5
6 #admin_panel {
6 #admin_panel {
7 background: #FF0000;
7 background: #FF0000;
8 color: #00FF00
8 color: #00FF00
9 }
9 }
10
10
11 .input_field {
11 .input_field {
12
12
13 }
13 }
14
14
15 .input_field_name {
15 .input_field_name {
16
16
17 }
17 }
18
18
19 .input_field_error {
19 .input_field_error {
20 color: #FF0000;
20 color: #FF0000;
21 }
21 }
22
22
23
23
24 .title {
24 .title {
25 font-weight: bold;
25 font-weight: bold;
26 color: #ffcc00;
26 color: #ffcc00;
27 }
27 }
28
28
29 .link, a {
29 .link, a {
30 color: #afdcec;
30 color: #afdcec;
31 }
31 }
32
32
33 .block {
33 .block {
34 display: inline-block;
34 display: inline-block;
35 vertical-align: top;
35 vertical-align: top;
36 }
36 }
37
37
38 .tag {
38 .tag {
39 color: #b4cfec;
39 color: #b4cfec;
40 }
40 }
41
41
42 .post_id {
42 .post_id {
43 color: #fff380;
43 color: #fff380;
44 }
44 }
45
45
46 .post, .dead_post {
46 .post, .dead_post {
47 background: #333;
47 background: #333;
48 margin: 5px;
48 margin: 5px;
49 padding: 10px;
49 padding: 10px;
50 border: solid 1px #888;
50 border: solid 1px #888;
51 clear: left;
51 clear: left;
52 word-wrap: break-word;
52 word-wrap: break-word;
53 }
53 }
54
54
55 .metadata {
55 .metadata {
56 padding: 5px;
56 padding: 5px;
57 margin-top: 10px;
57 margin-top: 10px;
58 border: solid 1px #666;
58 border: solid 1px #666;
59 font-size: 0.9em;
59 font-size: 0.9em;
60 color: #ddd;
60 color: #ddd;
61 display: table;
61 display: table;
62 }
62 }
63
63
64 .navigation_panel, .tag_info {
64 .navigation_panel, .tag_info {
65 background: #444;
65 background: #444;
66 margin: 5px;
66 margin: 5px;
67 padding: 10px;
67 padding: 10px;
68 border: solid 1px #888;
68 border: solid 1px #888;
69 color: #eee;
69 color: #eee;
70 }
70 }
71
71
72 .navigation_panel .link {
72 .navigation_panel .link {
73 border-right: 1px solid #fff;
73 border-right: 1px solid #fff;
74 font-weight: bold;
74 font-weight: bold;
75 margin-right: 1ex;
75 margin-right: 1ex;
76 padding-right: 1ex;
76 padding-right: 1ex;
77 }
77 }
78 .navigation_panel .link:last-child {
78 .navigation_panel .link:last-child {
79 border-left: 1px solid #fff;
79 border-left: 1px solid #fff;
80 border-right: none;
80 border-right: none;
81 float: right;
81 float: right;
82 margin-left: 1ex;
82 margin-left: 1ex;
83 margin-right: 0;
83 margin-right: 0;
84 padding-left: 1ex;
84 padding-left: 1ex;
85 padding-right: 0;
85 padding-right: 0;
86 }
86 }
87
87
88 .navigation_panel::after, .post::after {
88 .navigation_panel::after, .post::after {
89 clear: both;
89 clear: both;
90 content: ".";
90 content: ".";
91 display: block;
91 display: block;
92 height: 0;
92 height: 0;
93 line-height: 0;
93 line-height: 0;
94 visibility: hidden;
94 visibility: hidden;
95 }
95 }
96
96
97 p {
97 p {
98 margin-top: .5em;
98 margin-top: .5em;
99 margin-bottom: .5em;
99 margin-bottom: .5em;
100 }
100 }
101
101
102 .post-form-w {
102 .post-form-w {
103 display: table;
103 display: table;
104 background: #333344;
104 background: #333344;
105 border: solid 1px #888;
105 border: solid 1px #888;
106 color: #fff;
106 color: #fff;
107 padding: 10px;
107 padding: 10px;
108 margin: 5px
108 margin: 5px
109 }
109 }
110
110
111 .form-row {
111 .form-row {
112 display: table-row;
112 display: table-row;
113 }
113 }
114
114
115 .form-label, .form-input, .form-errors {
115 .form-label, .form-input, .form-errors {
116 display: table-cell;
116 display: table-cell;
117 }
117 }
118
118
119 .form-label {
119 .form-label {
120 padding: .25em 1ex .25em 0;
120 padding: .25em 1ex .25em 0;
121 vertical-align: top;
121 vertical-align: top;
122 }
122 }
123
123
124 .form-input {
124 .form-input {
125 padding: .25em 0;
125 padding: .25em 0;
126 }
126 }
127
127
128 .form-errors {
128 .form-errors {
129 padding-left: 1ex;
129 padding-left: 1ex;
130 font-weight: bold;
130 font-weight: bold;
131 vertical-align: top;
131 vertical-align: middle;
132 }
132 }
133
133
134 .post-form input, .post-form textarea {
134 .post-form input, .post-form textarea {
135 background: #333;
135 background: #333;
136 color: #fff;
136 color: #fff;
137 border: solid 1px;
137 border: solid 1px;
138 padding: 0;
138 padding: 0;
139 width: 100%;
139 width: 100%;
140 }
140 }
141
141
142 .form-submit {
142 .form-submit {
143 border-bottom: 2px solid #ddd;
143 border-bottom: 2px solid #ddd;
144 margin-bottom: .5em;
144 margin-bottom: .5em;
145 padding-bottom: .5em;
145 padding-bottom: .5em;
146 }
146 }
147
147
148 .form-title {
148 .form-title {
149 font-weight: bold;
149 font-weight: bold;
150 }
150 }
151
151
152 input[type="submit"] {
152 input[type="submit"] {
153 background: #222;
153 background: #222;
154 border: solid 1px #fff;
154 border: solid 1px #fff;
155 color: #fff;
155 color: #fff;
156 }
156 }
157
157
158 blockquote {
158 blockquote {
159 border-left: solid 2px;
159 border-left: solid 2px;
160 padding-left: 5px;
160 padding-left: 5px;
161 color: #B1FB17;
161 color: #B1FB17;
162 margin: 0;
162 margin: 0;
163 }
163 }
164
164
165 .post > .image {
165 .post > .image {
166 float: left;
166 float: left;
167 margin: 0 1ex .5ex 0;
167 margin: 0 1ex .5ex 0;
168 min-width: 1px;
168 min-width: 1px;
169 text-align: center;
169 text-align: center;
170 display: table-row;
170 display: table-row;
171 }
171 }
172
172
173 .post > .metadata {
173 .post > .metadata {
174 clear: left;
174 clear: left;
175 }
175 }
176
176
177 .get {
177 .get {
178 font-weight: bold;
178 font-weight: bold;
179 color: #d55;
179 color: #d55;
180 }
180 }
181
181
182 * {
182 * {
183 text-decoration: none;
183 text-decoration: none;
184 }
184 }
185
185
186 .dead_post {
186 .dead_post {
187 background-color: #442222;
187 background-color: #442222;
188 }
188 }
189
189
190 .quote {
190 .quote {
191 color: #92cf38;
191 color: #92cf38;
192 font-style: italic;
192 font-style: italic;
193 }
193 }
194
194
195 .spoiler {
195 .spoiler {
196 background: white;
196 background: white;
197 color: white;
197 color: white;
198 }
198 }
199
199
200 .spoiler:hover {
200 .spoiler:hover {
201 color: black;
201 color: black;
202 }
202 }
203
203
204 .comment {
204 .comment {
205 color: #eb2;
205 color: #eb2;
206 font-style: italic;
206 font-style: italic;
207 }
207 }
208
208
209 a:hover {
209 a:hover {
210 text-decoration: underline;
210 text-decoration: underline;
211 }
211 }
212
212
213 .last-replies {
213 .last-replies {
214 margin-left: 3ex;
214 margin-left: 3ex;
215 }
215 }
216
216
217 .thread {
217 .thread {
218 margin-bottom: 3ex;
218 margin-bottom: 3ex;
219 }
219 }
220
220
221 .post:target {
221 .post:target {
222 border: solid 2px white;
222 border: solid 2px white;
223 }
223 }
224
224
225 pre{
225 pre{
226 white-space:pre-wrap
226 white-space:pre-wrap
227 }
227 }
228
228
229 li {
229 li {
230 list-style-position: inside;
230 list-style-position: inside;
231 }
231 }
232
232
233 .fancybox-skin {
233 .fancybox-skin {
234 position: relative;
234 position: relative;
235 background-color: #fff;
235 background-color: #fff;
236 color: #ddd;
236 color: #ddd;
237 text-shadow: none;
237 text-shadow: none;
238 }
238 }
239
239
240 .fancybox-image {
240 .fancybox-image {
241 border: 1px solid black;
241 border: 1px solid black;
242 }
242 }
243
243
244 .image-mode-tab {
244 .image-mode-tab {
245 background: #444;
245 background: #444;
246 color: #eee;
246 color: #eee;
247 display: table;
247 display: table;
248 margin: 5px;
248 margin: 5px;
249 padding: 5px;
249 padding: 5px;
250 border: 1px solid #888;
250 border: 1px solid #888;
251 }
251 }
252
252
253 .image-mode-tab > label {
253 .image-mode-tab > label {
254 margin: 0 1ex;
254 margin: 0 1ex;
255 }
255 }
256
256
257 .image-mode-tab > label > input {
257 .image-mode-tab > label > input {
258 margin-right: .5ex;
258 margin-right: .5ex;
259 }
259 }
260
260
261 #posts-table {
261 #posts-table {
262 margin: 5px;
262 margin: 5px;
263 }
263 }
264
264
265 .tag_info {
265 .tag_info {
266 display: table;
266 display: table;
267 }
267 }
268
268
269 .tag_info > h2 {
269 .tag_info > h2 {
270 margin: 0;
270 margin: 0;
271 }
271 }
272
272
273 .post-info {
273 .post-info {
274 color: #ddd;
274 color: #ddd;
275 }
275 }
276
276
277 .moderator_info {
277 .moderator_info {
278 color: #e99d41;
278 color: #e99d41;
279 float: right;
279 float: right;
280 }
280 }
281
281
282 .refmap {
282 .refmap {
283 font-size: 0.9em;
283 font-size: 0.9em;
284 color: #ccc;
284 color: #ccc;
285 margin-top: 1em;
285 margin-top: 1em;
286 }
286 }
287
287
288 input[type="submit"]:hover {
288 input[type="submit"]:hover {
289 background: #555;
289 background: #555;
290 }
290 }
@@ -1,47 +1,47 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 <link rel="alternate" type="application/rss+xml" href="rss/" title="
11 <link rel="alternate" type="application/rss+xml" href="rss/" title="
12 {% trans 'Feed' %}"/>
12 {% trans 'Feed' %}"/>
13
13
14 <link rel="icon" type="image/png"
14 <link rel="icon" type="image/png"
15 href="{{ STATIC_URL }}favicon.png">
15 href="{{ STATIC_URL }}favicon.png">
16
16
17 <meta name="viewport" content="width=device-width, initial-scale=1"/>
17 <meta name="viewport" content="width=device-width, initial-scale=1"/>
18 <meta charset="utf-8"/>
18 <meta charset="utf-8"/>
19 {% block head %}{% endblock %}
19 {% block head %}{% endblock %}
20 </head>
20 </head>
21 <body>
21 <body>
22 <script src="{{ STATIC_URL }}js/jquery-2.0.1.min.js"></script>
22 <script src="{{ STATIC_URL }}js/jquery-2.0.1.min.js"></script>
23 <script src="{{ STATIC_URL }}js/jquery.fancybox.pack.js"></script>
23 <script src="{{ STATIC_URL }}js/jquery.fancybox.pack.js"></script>
24 <script src="{% url 'django.views.i18n.javascript_catalog' %}"></script>
24 <script src="{% url 'django.views.i18n.javascript_catalog' %}"></script>
25 <script src="{{ STATIC_URL }}js/refmaps.js"></script>
25 <script src="{{ STATIC_URL }}js/refmaps.js"></script>
26 <script src="{{ STATIC_URL }}js/main.js"></script>
26 <script src="{{ STATIC_URL }}js/main.js"></script>
27
27
28 <div class="navigation_panel">
28 <div class="navigation_panel">
29 <a class="link" href="{% url 'index' %}">{% trans "All threads" %}</a>
29 <a class="link" href="{% url 'index' %}">{% trans "All threads" %}</a>
30 {% for tag in tags %}
30 {% for tag in tags %}
31 <a class="tag" href="{% url 'tag' tag_name=tag.name %}">
31 <a class="tag" href="{% url 'tag' tag_name=tag.name %}">
32 {{ tag.name }}</a>({{ tag.get_post_count }})
32 {{ tag.name }}</a>({{ tag.get_post_count }})
33 {% endfor %}
33 {% endfor %}
34 <a class="tag" href="{% url 'tags' %}">[...]</a>
34 <a class="tag" href="{% url 'tags' %}">[...]</a>
35 <a class="link" href="{% url 'settings' %}">{% trans 'Settings' %}</a>
35 <a class="link" href="{% url 'settings' %}">{% trans 'Settings' %}</a>
36 </div>
36 </div>
37
37
38 {% block content %}{% endblock %}
38 {% block content %}{% endblock %}
39
39
40 <div class="navigation_panel">
40 <div class="navigation_panel">
41 {% block metapanel %}{% endblock %}
41 {% block metapanel %}{% endblock %}
42 [<a href="rss/">RSS</a>]
42 [<a href="{% url "login" %}">{% trans 'Login' %}</a>]
43 <a class="link" href="#top">{% trans 'Up' %}</a>
43 <a class="link" href="#top">{% trans 'Up' %}</a>
44 </div>
44 </div>
45
45
46 </body>
46 </body>
47 </html>
47 </html>
@@ -1,22 +1,29 b''
1 <!DOCTYPE html>
1 {% extends "boards/base.html" %}
2 <html>
2
3 <head>
3 {% load i18n %}
4 <link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}css/md/login.css" media="all"/>
4
5 <title>Login page</title>
5 {% block head %}
6 </head>
6 <title>{% trans 'Login' %}</title>
7 {% endblock %}
8
9 {% block content %}
7
10
8 <body>
11 <form enctype="multipart/form-data" method="post">
9 {% if error != none%}
12 <div class="post-form-w">
10 <span id="error_message">
13 <div class="post-form">
11 {{ error }}
14 <div class="form-row">
12 </span>
15 <div class="form-label">{% trans 'User ID' %}</div>
13 {% endif %}
16 <div class="form-input">{{ form.user_id }}</div>
17 <div class="form-errors">{{ form.user_id.errors }}</div>
18 </div>
19 </div>
20 <div class="form-submit">
21 <input type="submit" value="{% trans "Login" %}"/>
22 </div>
23 <div>
24 {% trans 'Insert your user id above' %}
25 </div>
26 </div>
27 </form>
14
28
15 <form action="login" method="POST">{% csrf_token %}
29 {% endblock %} No newline at end of file
16
17 Login: <input type="text" name="name"><br />
18 Password: <input type="password" name="password"><br />
19 <input type="submit">
20 </form>
21 </body>
22 </html> No newline at end of file
@@ -1,176 +1,177 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 <div class="post">
114 <div class="post">
115 {% trans 'No threads exist. Create the first one!' %}</div>
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 [<a href="rss/">RSS</a>]
174 </span>
175 </span>
175
176
176 {% endblock %}
177 {% endblock %}
@@ -1,118 +1,119 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 {% endif %}
67 {% endif %}
68
68
69 <form id="form" enctype="multipart/form-data" method="post"
69 <form id="form" enctype="multipart/form-data" method="post"
70 >{% csrf_token %}
70 >{% csrf_token %}
71 <div class="post-form-w">
71 <div class="post-form-w">
72 <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>
73 <div class="post-form">
73 <div class="post-form">
74 <div class="form-row">
74 <div class="form-row">
75 <div class="form-label">{% trans 'Title' %}</div>
75 <div class="form-label">{% trans 'Title' %}</div>
76 <div class="form-input">{{ form.title }}</div>
76 <div class="form-input">{{ form.title }}</div>
77 <div class="form-errors">{{ form.title.errors }}</div>
77 <div class="form-errors">{{ form.title.errors }}</div>
78 </div>
78 </div>
79 <div class="form-row">
79 <div class="form-row">
80 <div class="form-label">{% trans 'Text' %}</div>
80 <div class="form-label">{% trans 'Text' %}</div>
81 <div class="form-input">{{ form.text }}</div>
81 <div class="form-input">{{ form.text }}</div>
82 <div class="form-errors">{{ form.text.errors }}</div>
82 <div class="form-errors">{{ form.text.errors }}</div>
83 </div>
83 </div>
84 <div class="form-row">
84 <div class="form-row">
85 <div class="form-label">{% trans 'Image' %}</div>
85 <div class="form-label">{% trans 'Image' %}</div>
86 <div class="form-input">{{ form.image }}</div>
86 <div class="form-input">{{ form.image }}</div>
87 <div class="form-errors">{{ form.image.errors }}</div>
87 <div class="form-errors">{{ form.image.errors }}</div>
88 </div>
88 </div>
89 <div class="form-row">
89 <div class="form-row">
90 {{ form.captcha }}
90 {{ form.captcha }}
91 <div class="form-errors">{{ form.captcha.errors }}</div>
91 <div class="form-errors">{{ form.captcha.errors }}</div>
92 </div>
92 </div>
93 </div>
93 </div>
94
94
95 <div class="form-submit"><input type="submit"
95 <div class="form-submit"><input type="submit"
96 value="{% trans "Post" %}"/></div>
96 value="{% trans "Post" %}"/></div>
97 <div><a href="http://daringfireball.net/projects/markdown/basics">
97 <div><a href="http://daringfireball.net/projects/markdown/basics">
98 {% trans 'Basic markdown syntax.' %}</a></div>
98 {% trans 'Basic markdown syntax.' %}</a></div>
99 <div>{% trans 'Example: ' %}*<i>{% trans 'italic' %}</i>*,
99 <div>{% trans 'Example: ' %}*<i>{% trans 'italic' %}</i>*,
100 **<b>{% trans 'bold' %}</b>**</div>
100 **<b>{% trans 'bold' %}</b>**</div>
101 <div>{% trans 'Quotes can be inserted with' %} "&gt;"</div>
101 <div>{% trans 'Quotes can be inserted with' %} "&gt;"</div>
102 <div>{% trans 'Links to answers can be inserted with' %}
102 <div>{% trans 'Links to answers can be inserted with' %}
103 "&gt;&gt;123"
103 "&gt;&gt;123"
104 </div>
104 </div>
105 </div>
105 </div>
106 </form>
106 </form>
107
107
108 {% endblock %}
108 {% endblock %}
109
109
110 {% block metapanel %}
110 {% block metapanel %}
111
111
112 <span class="metapanel">
112 <span class="metapanel">
113 {{ posts.0.get_reply_count }} {% trans 'replies' %},
113 {{ posts.0.get_reply_count }} {% trans 'replies' %},
114 {{ posts.0.get_images_count }} {% trans 'images' %}.
114 {{ posts.0.get_images_count }} {% trans 'images' %}.
115 {% trans 'Last update: ' %}{{ posts.0.last_edit_time }}
115 {% trans 'Last update: ' %}{{ posts.0.last_edit_time }}
116 [<a href="rss/">RSS</a>]
116 </span>
117 </span>
117
118
118 {% endblock %}
119 {% endblock %}
@@ -1,44 +1,42 b''
1 from django.conf.urls import patterns, url, include
1 from django.conf.urls import patterns, url, include
2 from boards import views
2 from boards import views
3 from boards.rss import AllThreadsFeed, TagThreadsFeed, ThreadPostsFeed
3 from boards.rss import AllThreadsFeed, TagThreadsFeed, ThreadPostsFeed
4
4
5 js_info_dict = {
5 js_info_dict = {
6 'packages': ('boards',),
6 'packages': ('boards',),
7 }
7 }
8
8
9 urlpatterns = patterns('',
9 urlpatterns = patterns('',
10
10
11 # /boards/
11 # /boards/
12 url(r'^$', views.index, name='index'),
12 url(r'^$', views.index, name='index'),
13 # /boards/page/
13 # /boards/page/
14 url(r'^page/(?P<page>\w+)/$', views.index, name='index'),
14 url(r'^page/(?P<page>\w+)/$', views.index, name='index'),
15
15
16 # login page
16 # login page
17 url(r'^login$', views.login, name='login'),
17 url(r'^login/$', views.login, name='login'),
18 # logout page
19 url(r'^logout$', views.logout, name='logout'),
20
18
21 # /boards/tag/tag_name/
19 # /boards/tag/tag_name/
22 url(r'^tag/(?P<tag_name>\w+)/$', views.tag, name='tag'),
20 url(r'^tag/(?P<tag_name>\w+)/$', views.tag, name='tag'),
23 # /boards/tag/tag_id/page/
21 # /boards/tag/tag_id/page/
24 url(r'^tag/(?P<tag_name>\w+)/page/(?P<page>\w+)/$', views.tag, name='tag'),
22 url(r'^tag/(?P<tag_name>\w+)/page/(?P<page>\w+)/$', views.tag, name='tag'),
25 # /boards/thread/
23 # /boards/thread/
26 url(r'^thread/(?P<post_id>\w+)/$', views.thread, name='thread'),
24 url(r'^thread/(?P<post_id>\w+)/$', views.thread, name='thread'),
27 # /boards/theme/theme_name/
25 # /boards/theme/theme_name/
28 url(r'^settings$', views.settings, name='settings'),
26 url(r'^settings/$', views.settings, name='settings'),
29 url(r'^tags$', views.all_tags, name='tags'),
27 url(r'^tags/$', views.all_tags, name='tags'),
30 url(r'^captcha/', include('captcha.urls')),
28 url(r'^captcha/', include('captcha.urls')),
31 url(r'^jump/(?P<post_id>\w+)/$', views.jump_to_post, name='jumper'),
29 url(r'^jump/(?P<post_id>\w+)/$', views.jump_to_post, name='jumper'),
32 url(r'^authors/$', views.authors, name='authors'),
30 url(r'^authors/$', views.authors, name='authors'),
33 url(r'^delete/(?P<post_id>\w+)/$', views.delete, name='delete'),
31 url(r'^delete/(?P<post_id>\w+)/$', views.delete, name='delete'),
34 url(r'^banned/$', views.you_are_banned, name='banned'),
32 url(r'^banned/$', views.you_are_banned, name='banned'),
35
33
36 # RSS feeds
34 # RSS feeds
37 url(r'^rss/$', AllThreadsFeed()),
35 url(r'^rss/$', AllThreadsFeed()),
38 url(r'^page/(?P<page>\w+)/rss/$', AllThreadsFeed()),
36 url(r'^page/(?P<page>\w+)/rss/$', AllThreadsFeed()),
39 url(r'^tag/(?P<tag_name>\w+)/rss/$', TagThreadsFeed()),
37 url(r'^tag/(?P<tag_name>\w+)/rss/$', TagThreadsFeed()),
40 url(r'^tag/(?P<tag_name>\w+)/page/(?P<page>\w+)/rss/$', TagThreadsFeed()),
38 url(r'^tag/(?P<tag_name>\w+)/page/(?P<page>\w+)/rss/$', TagThreadsFeed()),
41 url(r'^thread/(?P<post_id>\w+)/rss/$', ThreadPostsFeed()),
39 url(r'^thread/(?P<post_id>\w+)/rss/$', ThreadPostsFeed()),
42
40
43 url(r'^jsi18n/$', 'django.views.i18n.javascript_catalog', js_info_dict),
41 url(r'^jsi18n/$', 'django.views.i18n.javascript_catalog', js_info_dict),
44 )
42 )
@@ -1,307 +1,299 b''
1 import hashlib
1 import hashlib
2 from django.core.urlresolvers import reverse
2 from django.core.urlresolvers import reverse
3 from django.template import RequestContext
3 from django.template import RequestContext
4 from django.shortcuts import render, redirect, get_object_or_404
4 from django.shortcuts import render, redirect, get_object_or_404
5 from django.http import HttpResponseRedirect
5 from django.http import HttpResponseRedirect
6 from django.utils import timezone
6 from django.utils import timezone
7
7
8 from boards import forms
8 from boards import forms
9 import boards
9 import boards
10 from boards import utils
10 from boards import utils
11 from boards.forms import ThreadForm, PostForm, SettingsForm, PlainErrorList, \
11 from boards.forms import ThreadForm, PostForm, SettingsForm, PlainErrorList, \
12 ThreadCaptchaForm, PostCaptchaForm
12 ThreadCaptchaForm, PostCaptchaForm, LoginForm
13
13
14 from boards.models import Post, Tag, Ban, User, RANK_USER, RANK_MODERATOR, NO_PARENT
14 from boards.models import Post, Tag, Ban, User, RANK_USER, RANK_MODERATOR, NO_PARENT
15 from boards import authors
15 from boards import authors
16 import neboard
16 import neboard
17
17
18
18
19 def index(request, page=0):
19 def index(request, page=0):
20 context = _init_default_context(request)
20 context = _init_default_context(request)
21
21
22 if utils.need_include_captcha(request):
22 if utils.need_include_captcha(request):
23 threadFormClass = ThreadCaptchaForm
23 threadFormClass = ThreadCaptchaForm
24 kwargs = {'request': request}
24 kwargs = {'request': request}
25 else:
25 else:
26 threadFormClass = ThreadForm
26 threadFormClass = ThreadForm
27 kwargs = {}
27 kwargs = {}
28
28
29 if request.method == 'POST':
29 if request.method == 'POST':
30 form = threadFormClass(request.POST, request.FILES,
30 form = threadFormClass(request.POST, request.FILES,
31 error_class=PlainErrorList, **kwargs)
31 error_class=PlainErrorList, **kwargs)
32
32
33 if form.is_valid():
33 if form.is_valid():
34 return _new_post(request, form)
34 return _new_post(request, form)
35 else:
35 else:
36 form = threadFormClass(error_class=PlainErrorList, **kwargs)
36 form = threadFormClass(error_class=PlainErrorList, **kwargs)
37
37
38 threads = Post.objects.get_threads(page=int(page))
38 threads = Post.objects.get_threads(page=int(page))
39
39
40 context['threads'] = None if len(threads) == 0 else threads
40 context['threads'] = None if len(threads) == 0 else threads
41 context['form'] = form
41 context['form'] = form
42 context['pages'] = range(Post.objects.get_thread_page_count())
42 context['pages'] = range(Post.objects.get_thread_page_count())
43
43
44 return render(request, 'boards/posting_general.html',
44 return render(request, 'boards/posting_general.html',
45 context)
45 context)
46
46
47
47
48 def _new_post(request, form, thread_id=boards.models.NO_PARENT):
48 def _new_post(request, form, thread_id=boards.models.NO_PARENT):
49 """Add a new post (in thread or as a reply)."""
49 """Add a new post (in thread or as a reply)."""
50
50
51 ip = _get_client_ip(request)
51 ip = _get_client_ip(request)
52 is_banned = Ban.objects.filter(ip=ip).count() > 0
52 is_banned = Ban.objects.filter(ip=ip).count() > 0
53
53
54 if is_banned:
54 if is_banned:
55 return redirect(you_are_banned)
55 return redirect(you_are_banned)
56
56
57 data = form.cleaned_data
57 data = form.cleaned_data
58
58
59 title = data['title']
59 title = data['title']
60 text = data['text']
60 text = data['text']
61
61
62 if 'image' in data.keys():
62 if 'image' in data.keys():
63 image = data['image']
63 image = data['image']
64 else:
64 else:
65 image = None
65 image = None
66
66
67 tags = []
67 tags = []
68
68
69 new_thread = thread_id == boards.models.NO_PARENT
69 new_thread = thread_id == boards.models.NO_PARENT
70 if new_thread:
70 if new_thread:
71 tag_strings = data['tags']
71 tag_strings = data['tags']
72
72
73 if tag_strings:
73 if tag_strings:
74 tag_strings = tag_strings.split(' ')
74 tag_strings = tag_strings.split(' ')
75 for tag_name in tag_strings:
75 for tag_name in tag_strings:
76 tag_name = tag_name.strip()
76 tag_name = tag_name.strip()
77 if len(tag_name) > 0:
77 if len(tag_name) > 0:
78 tag, created = Tag.objects.get_or_create(name=tag_name)
78 tag, created = Tag.objects.get_or_create(name=tag_name)
79 tags.append(tag)
79 tags.append(tag)
80
80
81 # TODO Add a possibility to define a link image instead of an image file.
81 # TODO Add a possibility to define a link image instead of an image file.
82 # If a link is given, download the image automatically.
82 # If a link is given, download the image automatically.
83
83
84 post = Post.objects.create_post(title=title, text=text, ip=ip,
84 post = Post.objects.create_post(title=title, text=text, ip=ip,
85 parent_id=thread_id, image=image,
85 parent_id=thread_id, image=image,
86 tags=tags)
86 tags=tags)
87
87
88 thread_to_show = (post.id if new_thread else thread_id)
88 thread_to_show = (post.id if new_thread else thread_id)
89
89
90 if new_thread:
90 if new_thread:
91 return redirect(thread, post_id=thread_to_show)
91 return redirect(thread, post_id=thread_to_show)
92 else:
92 else:
93 return redirect(reverse(thread,
93 return redirect(reverse(thread,
94 kwargs={'post_id': thread_to_show}) + '#'
94 kwargs={'post_id': thread_to_show}) + '#'
95 + str(post.id))
95 + str(post.id))
96
96
97
97
98 def tag(request, tag_name, page=0):
98 def tag(request, tag_name, page=0):
99 """Get all tag threads (posts without a parent)."""
99 """Get all tag threads (posts without a parent)."""
100
100
101 tag = get_object_or_404(Tag, name=tag_name)
101 tag = get_object_or_404(Tag, name=tag_name)
102 threads = Post.objects.get_threads(tag=tag, page=int(page))
102 threads = Post.objects.get_threads(tag=tag, page=int(page))
103
103
104 if request.method == 'POST':
104 if request.method == 'POST':
105 form = ThreadForm(request.POST, request.FILES,
105 form = ThreadForm(request.POST, request.FILES,
106 error_class=PlainErrorList)
106 error_class=PlainErrorList)
107 if form.is_valid():
107 if form.is_valid():
108 return _new_post(request, form)
108 return _new_post(request, form)
109 else:
109 else:
110 form = forms.ThreadForm(initial={'tags': tag_name},
110 form = forms.ThreadForm(initial={'tags': tag_name},
111 error_class=PlainErrorList)
111 error_class=PlainErrorList)
112
112
113 context = _init_default_context(request)
113 context = _init_default_context(request)
114 context['threads'] = None if len(threads) == 0 else threads
114 context['threads'] = None if len(threads) == 0 else threads
115 context['tag'] = tag_name
115 context['tag'] = tag_name
116 context['pages'] = range(Post.objects.get_thread_page_count(tag=tag))
116 context['pages'] = range(Post.objects.get_thread_page_count(tag=tag))
117
117
118 context['form'] = form
118 context['form'] = form
119
119
120 return render(request, 'boards/posting_general.html',
120 return render(request, 'boards/posting_general.html',
121 context)
121 context)
122
122
123
123
124 def thread(request, post_id):
124 def thread(request, post_id):
125 """Get all thread posts"""
125 """Get all thread posts"""
126
126
127 if utils.need_include_captcha(request):
127 if utils.need_include_captcha(request):
128 postFormClass = PostCaptchaForm
128 postFormClass = PostCaptchaForm
129 kwargs = {'request': request}
129 kwargs = {'request': request}
130 else:
130 else:
131 postFormClass = PostForm
131 postFormClass = PostForm
132 kwargs = {}
132 kwargs = {}
133
133
134 if request.method == 'POST':
134 if request.method == 'POST':
135 form = postFormClass(request.POST, request.FILES,
135 form = postFormClass(request.POST, request.FILES,
136 error_class=PlainErrorList, **kwargs)
136 error_class=PlainErrorList, **kwargs)
137 if form.is_valid():
137 if form.is_valid():
138 return _new_post(request, form, post_id)
138 return _new_post(request, form, post_id)
139 else:
139 else:
140 form = postFormClass(error_class=PlainErrorList, **kwargs)
140 form = postFormClass(error_class=PlainErrorList, **kwargs)
141
141
142 posts = Post.objects.get_thread(post_id)
142 posts = Post.objects.get_thread(post_id)
143
143
144 context = _init_default_context(request)
144 context = _init_default_context(request)
145
145
146 context['posts'] = posts
146 context['posts'] = posts
147 context['form'] = form
147 context['form'] = form
148
148
149 return render(request, 'boards/thread.html', context)
149 return render(request, 'boards/thread.html', context)
150
150
151
151
152 def login(request):
152 def login(request):
153 """Log in as admin"""
153 """Log in with user id"""
154
155 if 'name' in request.POST and 'password' in request.POST:
156 request.session['admin'] = False
157
154
158 isAdmin = len(Admin.objects.filter(name=request.POST['name'],
155 context = _init_default_context(request)
159 password=request.POST[
160 'password'])) > 0
161
162 if isAdmin:
163 request.session['admin'] = True
164
156
165 response = HttpResponseRedirect('/')
157 if request.method == 'POST':
166
158 form = LoginForm(request.POST, request.FILES, error_class=PlainErrorList)
167 else:
159 if form.is_valid():
168 response = render(request, 'boards/login.html', {'error': 'Login error'})
160 user = User.objects.get(user_id=form.cleaned_data['user_id'])
169 else:
161 request.session['user_id'] = user.id
170 response = render(request, 'boards/login.html', {})
162 return redirect(index)
171
163
172 return response
164 else:
173
165 form = LoginForm()
174
166
175 def logout(request):
167 context['form'] = form
176 request.session['admin'] = False
168
177 return HttpResponseRedirect('/')
169 return render(request, 'boards/login.html', context)
178
170
179
171
180 def settings(request):
172 def settings(request):
181 """User's settings"""
173 """User's settings"""
182
174
183 context = _init_default_context(request)
175 context = _init_default_context(request)
184
176
185 if request.method == 'POST':
177 if request.method == 'POST':
186 form = SettingsForm(request.POST)
178 form = SettingsForm(request.POST)
187 if form.is_valid():
179 if form.is_valid():
188 selected_theme = form.cleaned_data['theme']
180 selected_theme = form.cleaned_data['theme']
189
181
190 user = _get_user(request)
182 user = _get_user(request)
191 user.save_setting('theme', selected_theme)
183 user.save_setting('theme', selected_theme)
192
184
193 return redirect(settings)
185 return redirect(settings)
194 else:
186 else:
195 selected_theme = _get_theme(request)
187 selected_theme = _get_theme(request)
196 form = SettingsForm(initial={'theme': selected_theme})
188 form = SettingsForm(initial={'theme': selected_theme})
197 context['form'] = form
189 context['form'] = form
198
190
199 return render(request, 'boards/settings.html', context)
191 return render(request, 'boards/settings.html', context)
200
192
201
193
202 def all_tags(request):
194 def all_tags(request):
203 """All tags list"""
195 """All tags list"""
204
196
205 context = _init_default_context(request)
197 context = _init_default_context(request)
206 context['all_tags'] = Tag.objects.get_not_empty_tags()
198 context['all_tags'] = Tag.objects.get_not_empty_tags()
207
199
208 return render(request, 'boards/tags.html', context)
200 return render(request, 'boards/tags.html', context)
209
201
210
202
211 def jump_to_post(request, post_id):
203 def jump_to_post(request, post_id):
212 """Determine thread in which the requested post is and open it's page"""
204 """Determine thread in which the requested post is and open it's page"""
213
205
214 post = get_object_or_404(Post, id=post_id)
206 post = get_object_or_404(Post, id=post_id)
215
207
216 if boards.models.NO_PARENT == post.parent:
208 if boards.models.NO_PARENT == post.parent:
217 return redirect(thread, post_id=post.id)
209 return redirect(thread, post_id=post.id)
218 else:
210 else:
219 parent_thread = get_object_or_404(Post, id=post.parent)
211 parent_thread = get_object_or_404(Post, id=post.parent)
220 return redirect(reverse(thread, kwargs={'post_id': parent_thread.id})
212 return redirect(reverse(thread, kwargs={'post_id': parent_thread.id})
221 + '#' + str(post.id))
213 + '#' + str(post.id))
222
214
223
215
224 def authors(request):
216 def authors(request):
225 context = _init_default_context(request)
217 context = _init_default_context(request)
226 context['authors'] = boards.authors.authors
218 context['authors'] = boards.authors.authors
227
219
228 return render(request, 'boards/authors.html', context)
220 return render(request, 'boards/authors.html', context)
229
221
230
222
231 def delete(request, post_id):
223 def delete(request, post_id):
232 user = _get_user(request)
224 user = _get_user(request)
233 post = get_object_or_404(Post, id=post_id)
225 post = get_object_or_404(Post, id=post_id)
234
226
235 if user.is_moderator():
227 if user.is_moderator():
236 Post.objects.delete_post(post)
228 Post.objects.delete_post(post)
237
229
238 if NO_PARENT == post.parent:
230 if NO_PARENT == post.parent:
239 return redirect(index)
231 return redirect(index)
240 else:
232 else:
241 return redirect(thread, post_id=post.parent)
233 return redirect(thread, post_id=post.parent)
242
234
243 def you_are_banned(request):
235 def you_are_banned(request):
244 context = _init_default_context(request)
236 context = _init_default_context(request)
245 return render(request, 'boards/banned.html', context)
237 return render(request, 'boards/banned.html', context)
246
238
247
239
248 def page_404(request):
240 def page_404(request):
249 context = _init_default_context(request)
241 context = _init_default_context(request)
250 return render(request, 'boards/404.html', context)
242 return render(request, 'boards/404.html', context)
251
243
252
244
253 def _get_theme(request):
245 def _get_theme(request):
254 """Get user's CSS theme"""
246 """Get user's CSS theme"""
255
247
256 user = _get_user(request)
248 user = _get_user(request)
257 theme = user.get_setting('theme')
249 theme = user.get_setting('theme')
258 if not theme:
250 if not theme:
259 theme = neboard.settings.DEFAULT_THEME
251 theme = neboard.settings.DEFAULT_THEME
260
252
261 return theme
253 return theme
262
254
263
255
264 def _get_client_ip(request):
256 def _get_client_ip(request):
265 x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
257 x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
266 if x_forwarded_for:
258 if x_forwarded_for:
267 ip = x_forwarded_for.split(',')[-1].strip()
259 ip = x_forwarded_for.split(',')[-1].strip()
268 else:
260 else:
269 ip = request.META.get('REMOTE_ADDR')
261 ip = request.META.get('REMOTE_ADDR')
270 return ip
262 return ip
271
263
272
264
273 def _init_default_context(request):
265 def _init_default_context(request):
274 """Create context with default values that are used in most views"""
266 """Create context with default values that are used in most views"""
275
267
276 context = RequestContext(request)
268 context = RequestContext(request)
277 context['tags'] = Tag.objects.get_popular_tags()
269 context['tags'] = Tag.objects.get_popular_tags()
278 context['theme'] = _get_theme(request)
270 context['theme'] = _get_theme(request)
279 context['user'] = _get_user(request)
271 context['user'] = _get_user(request)
280
272
281 return context
273 return context
282
274
283
275
284 def _get_user(request):
276 def _get_user(request):
285 """Get current user from the session"""
277 """Get current user from the session"""
286
278
287 session = request.session
279 session = request.session
288 if not 'user_id' in session:
280 if not 'user_id' in session:
289 request.session.save()
281 request.session.save()
290
282
291 md5 = hashlib.md5()
283 md5 = hashlib.md5()
292 md5.update(session.session_key)
284 md5.update(session.session_key)
293 new_id = md5.hexdigest()
285 new_id = md5.hexdigest()
294
286
295 time_now = timezone.now()
287 time_now = timezone.now()
296 user = User.objects.create(user_id=new_id, rank=RANK_USER,
288 user = User.objects.create(user_id=new_id, rank=RANK_USER,
297 registration_time=time_now,
289 registration_time=time_now,
298 last_access_time=time_now)
290 last_access_time=time_now)
299
291
300 session['user_id'] = user.id
292 session['user_id'] = user.id
301 else:
293 else:
302 user = User.objects.get(id=session['user_id'])
294 user = User.objects.get(id=session['user_id'])
303 user.save()
295 user.save()
304
296
305 user.last_access_time = timezone.now()
297 user.last_access_time = timezone.now()
306
298
307 return user
299 return user
General Comments 0
You need to be logged in to leave comments. Login now