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