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