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