##// END OF EJS Templates
Refactored code. Added thread title if the opening post has no title.
neko259 -
r147:b7ff6a14 default
parent child Browse files
Show More
@@ -1,331 +1,326 b''
1 import os
1 import os
2 from random import random
2 from random import random
3 import re
3 import re
4 import time
4 import time
5 import math
5 import math
6
6
7 from django.db import models
7 from django.db import models
8 from django.http import Http404
8 from django.http import Http404
9 from django.utils import timezone
9 from django.utils import timezone
10 from markupfield.fields import MarkupField
10 from markupfield.fields import MarkupField
11 from threading import Thread
11 from threading import Thread
12
12
13 from neboard import settings
13 from neboard import settings
14 import thumbs
14 import thumbs
15
15
16 IMAGE_THUMB_SIZE = (200, 150)
16 IMAGE_THUMB_SIZE = (200, 150)
17
17
18 TITLE_MAX_LENGTH = 50
18 TITLE_MAX_LENGTH = 50
19
19
20 DEFAULT_MARKUP_TYPE = 'markdown'
20 DEFAULT_MARKUP_TYPE = 'markdown'
21
21
22 NO_PARENT = -1
22 NO_PARENT = -1
23 NO_IP = '0.0.0.0'
23 NO_IP = '0.0.0.0'
24 UNKNOWN_UA = ''
24 UNKNOWN_UA = ''
25 ALL_PAGES = -1
25 ALL_PAGES = -1
26 OPENING_POST_POPULARITY_WEIGHT = 2
26 OPENING_POST_POPULARITY_WEIGHT = 2
27 IMAGES_DIRECTORY = 'images/'
27 IMAGES_DIRECTORY = 'images/'
28 FILE_EXTENSION_DELIMITER = '.'
28 FILE_EXTENSION_DELIMITER = '.'
29
29
30 RANK_ADMIN = 0
30 RANK_ADMIN = 0
31 RANK_MODERATOR = 10
31 RANK_MODERATOR = 10
32 RANK_USER = 100
32 RANK_USER = 100
33
33
34
34
35 class PostManager(models.Manager):
35 class PostManager(models.Manager):
36 def create_post(self, title, text, image=None, parent_id=NO_PARENT,
36 def create_post(self, title, text, image=None, parent_id=NO_PARENT,
37 ip=NO_IP, tags=None, user=None):
37 ip=NO_IP, tags=None, user=None):
38 post = self.create(title=title,
38 post = self.create(title=title,
39 text=text,
39 text=text,
40 pub_time=timezone.now(),
40 pub_time=timezone.now(),
41 parent=parent_id,
41 parent=parent_id,
42 image=image,
42 image=image,
43 poster_ip=ip,
43 poster_ip=ip,
44 poster_user_agent=UNKNOWN_UA,
44 poster_user_agent=UNKNOWN_UA,
45 last_edit_time=timezone.now(),
45 last_edit_time=timezone.now(),
46 user=user)
46 user=user)
47
47
48 if tags:
48 if tags:
49 map(post.tags.add, tags)
49 map(post.tags.add, tags)
50
50
51 if parent_id != NO_PARENT:
51 if parent_id != NO_PARENT:
52 self._bump_thread(parent_id)
52 self._bump_thread(parent_id)
53 else:
53 else:
54 self._delete_old_threads()
54 self._delete_old_threads()
55
55
56 return post
56 return post
57
57
58 def delete_post(self, post):
58 def delete_post(self, post):
59 children = self.filter(parent=post.id)
59 children = self.filter(parent=post.id)
60 for child in children:
60
61 self.delete_post(child)
61 map(self.delete_post, children)
62 post.delete()
62 post.delete()
63
63
64 def delete_posts_by_ip(self, ip):
64 def delete_posts_by_ip(self, ip):
65 posts = self.filter(poster_ip=ip)
65 posts = self.filter(poster_ip=ip)
66 for post in posts:
66 map(self.delete_post, posts)
67 self.delete_post(post)
68
67
69 def get_threads(self, tag=None, page=ALL_PAGES,
68 def get_threads(self, tag=None, page=ALL_PAGES,
70 order_by='-last_edit_time'):
69 order_by='-last_edit_time'):
71 if tag:
70 if tag:
72 threads = self.filter(parent=NO_PARENT, tags=tag)
71 threads = self.filter(parent=NO_PARENT, tags=tag)
73
72
74 # TODO Throw error 404 if no threads for tag found?
73 # TODO Throw error 404 if no threads for tag found?
75 else:
74 else:
76 threads = self.filter(parent=NO_PARENT)
75 threads = self.filter(parent=NO_PARENT)
77
76
78 threads = threads.order_by(order_by)
77 threads = threads.order_by(order_by)
79
78
80 if page != ALL_PAGES:
79 if page != ALL_PAGES:
81 thread_count = len(threads)
80 thread_count = len(threads)
82
81
83 if page < self.get_thread_page_count(tag=tag):
82 if page < self.get_thread_page_count(tag=tag):
84 start_thread = page * settings.THREADS_PER_PAGE
83 start_thread = page * settings.THREADS_PER_PAGE
85 end_thread = min(start_thread + settings.THREADS_PER_PAGE,
84 end_thread = min(start_thread + settings.THREADS_PER_PAGE,
86 thread_count)
85 thread_count)
87 threads = threads[start_thread:end_thread]
86 threads = threads[start_thread:end_thread]
88
87
89 return threads
88 return threads
90
89
91 def get_thread(self, opening_post_id):
90 def get_thread(self, opening_post_id):
92 try:
91 try:
93 opening_post = self.get(id=opening_post_id, parent=NO_PARENT)
92 opening_post = self.get(id=opening_post_id, parent=NO_PARENT)
94 except Post.DoesNotExist:
93 except Post.DoesNotExist:
95 raise Http404
94 raise Http404
96
95
97 if opening_post.parent == NO_PARENT:
96 if opening_post.parent == NO_PARENT:
98 replies = self.filter(parent=opening_post_id)
97 replies = self.filter(parent=opening_post_id)
99
98
100 thread = [opening_post]
99 thread = [opening_post]
101 thread.extend(replies)
100 thread.extend(replies)
102
101
103 return thread
102 return thread
104
103
105 def exists(self, post_id):
104 def exists(self, post_id):
106 posts = self.filter(id=post_id)
105 posts = self.filter(id=post_id)
107
106
108 return posts.count() > 0
107 return posts.count() > 0
109
108
110 def get_thread_page_count(self, tag=None):
109 def get_thread_page_count(self, tag=None):
111 if tag:
110 if tag:
112 threads = self.filter(parent=NO_PARENT, tags=tag)
111 threads = self.filter(parent=NO_PARENT, tags=tag)
113 else:
112 else:
114 threads = self.filter(parent=NO_PARENT)
113 threads = self.filter(parent=NO_PARENT)
115
114
116 return int(math.ceil(threads.count() / float(
115 return int(math.ceil(threads.count() / float(
117 settings.THREADS_PER_PAGE)))
116 settings.THREADS_PER_PAGE)))
118
117
119 def _delete_old_threads(self):
118 def _delete_old_threads(self):
120 """
119 """
121 Preserves maximum thread count. If there are too many threads,
120 Preserves maximum thread count. If there are too many threads,
122 delete the old ones.
121 delete the old ones.
123 """
122 """
124
123
125 # TODO Move old threads to the archive instead of deleting them.
124 # TODO Move old threads to the archive instead of deleting them.
126 # Maybe make some 'old' field in the model to indicate the thread
125 # Maybe make some 'old' field in the model to indicate the thread
127 # must not be shown and be able for replying.
126 # must not be shown and be able for replying.
128
127
129 threads = self.get_threads()
128 threads = self.get_threads()
130 thread_count = len(threads)
129 thread_count = len(threads)
131
130
132 if thread_count > settings.MAX_THREAD_COUNT:
131 if thread_count > settings.MAX_THREAD_COUNT:
133 num_threads_to_delete = thread_count - settings.MAX_THREAD_COUNT
132 num_threads_to_delete = thread_count - settings.MAX_THREAD_COUNT
134 old_threads = threads[thread_count - num_threads_to_delete:]
133 old_threads = threads[thread_count - num_threads_to_delete:]
135
134
136 for thread in old_threads:
135 map(self.delete_post, old_threads)
137 self.delete_post(thread)
138
136
139 def _bump_thread(self, thread_id):
137 def _bump_thread(self, thread_id):
140 thread = self.get(id=thread_id)
138 thread = self.get(id=thread_id)
141
139
142 if thread.can_bump():
140 if thread.can_bump():
143 thread.last_edit_time = timezone.now()
141 thread.last_edit_time = timezone.now()
144 thread.save()
142 thread.save()
145
143
146
144
147 class TagManager(models.Manager):
145 class TagManager(models.Manager):
148 def get_not_empty_tags(self):
146 def get_not_empty_tags(self):
149 all_tags = self.all().order_by('name')
147 all_tags = self.all().order_by('name')
150 tags = []
148 tags = []
151 for tag in all_tags:
149 for tag in all_tags:
152 if not tag.is_empty():
150 if not tag.is_empty():
153 tags.append(tag)
151 tags.append(tag)
154
152
155 return tags
153 return tags
156
154
157 def get_popular_tags(self):
155 def get_popular_tags(self):
158 all_tags = self.get_not_empty_tags()
156 all_tags = self.get_not_empty_tags()
159
157
160 sorted_tags = sorted(all_tags, key=lambda tag: tag.get_popularity(),
158 sorted_tags = sorted(all_tags, key=lambda tag: tag.get_popularity(),
161 reverse=True)
159 reverse=True)
162
160
163 return sorted_tags[:settings.POPULAR_TAGS]
161 return sorted_tags[:settings.POPULAR_TAGS]
164
162
165
163
166 class Tag(models.Model):
164 class Tag(models.Model):
167 """
165 """
168 A tag is a text node assigned to the post. The tag serves as a board
166 A tag is a text node assigned to the post. The tag serves as a board
169 section. There can be multiple tags for each message
167 section. There can be multiple tags for each message
170 """
168 """
171
169
172 objects = TagManager()
170 objects = TagManager()
173
171
174 name = models.CharField(max_length=100)
172 name = models.CharField(max_length=100)
175 # TODO Connect the tag to its posts to check the number of threads for
176 # the tag.
177
173
178 def __unicode__(self):
174 def __unicode__(self):
179 return self.name
175 return self.name
180
176
181 def is_empty(self):
177 def is_empty(self):
182 return self.get_post_count() == 0
178 return self.get_post_count() == 0
183
179
184 def get_post_count(self):
180 def get_post_count(self):
185 posts_with_tag = Post.objects.get_threads(tag=self)
181 posts_with_tag = Post.objects.get_threads(tag=self)
186 return posts_with_tag.count()
182 return posts_with_tag.count()
187
183
188 def get_popularity(self):
184 def get_popularity(self):
189 posts_with_tag = Post.objects.get_threads(tag=self)
185 posts_with_tag = Post.objects.get_threads(tag=self)
190 reply_count = 0
186 reply_count = 0
191 for post in posts_with_tag:
187 for post in posts_with_tag:
192 reply_count += post.get_reply_count()
188 reply_count += post.get_reply_count()
193 reply_count += OPENING_POST_POPULARITY_WEIGHT
189 reply_count += OPENING_POST_POPULARITY_WEIGHT
194
190
195 return reply_count
191 return reply_count
196
192
197
193
198 class Post(models.Model):
194 class Post(models.Model):
199 """A post is a message."""
195 """A post is a message."""
200
196
201 objects = PostManager()
197 objects = PostManager()
202
198
203 def _update_image_filename(self, filename):
199 def _update_image_filename(self, filename):
204 """Get unique image filename"""
200 """Get unique image filename"""
205
201
206 path = IMAGES_DIRECTORY
202 path = IMAGES_DIRECTORY
207 new_name = str(int(time.mktime(time.gmtime())))
203 new_name = str(int(time.mktime(time.gmtime())))
208 new_name += str(int(random() * 1000))
204 new_name += str(int(random() * 1000))
209 new_name += FILE_EXTENSION_DELIMITER
205 new_name += FILE_EXTENSION_DELIMITER
210 new_name += filename.split(FILE_EXTENSION_DELIMITER)[-1:][0]
206 new_name += filename.split(FILE_EXTENSION_DELIMITER)[-1:][0]
211
207
212 return os.path.join(path, new_name)
208 return os.path.join(path, new_name)
213
209
214 title = models.CharField(max_length=TITLE_MAX_LENGTH)
210 title = models.CharField(max_length=TITLE_MAX_LENGTH)
215 pub_time = models.DateTimeField()
211 pub_time = models.DateTimeField()
216 text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE,
212 text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE,
217 escape_html=False)
213 escape_html=False)
218
214
219 image_width = models.IntegerField(default=0)
215 image_width = models.IntegerField(default=0)
220 image_height = models.IntegerField(default=0)
216 image_height = models.IntegerField(default=0)
221
217
222 image = thumbs.ImageWithThumbsField(upload_to=_update_image_filename,
218 image = thumbs.ImageWithThumbsField(upload_to=_update_image_filename,
223 blank=True, sizes=(IMAGE_THUMB_SIZE,),
219 blank=True, sizes=(IMAGE_THUMB_SIZE,),
224 width_field='image_width',
220 width_field='image_width',
225 height_field='image_height')
221 height_field='image_height')
226
222
227 poster_ip = models.GenericIPAddressField()
223 poster_ip = models.GenericIPAddressField()
228 poster_user_agent = models.TextField()
224 poster_user_agent = models.TextField()
229 parent = models.BigIntegerField()
225 parent = models.BigIntegerField()
230 tags = models.ManyToManyField(Tag)
226 tags = models.ManyToManyField(Tag)
231 last_edit_time = models.DateTimeField()
227 last_edit_time = models.DateTimeField()
232 user = models.ForeignKey('User', null=True, default=None)
228 user = models.ForeignKey('User', null=True, default=None)
233
229
234 def __unicode__(self):
230 def __unicode__(self):
235 return '#' + str(self.id) + ' ' + self.title + ' (' + \
231 return '#' + str(self.id) + ' ' + self.title + ' (' + \
236 self.text.raw[:50] + ')'
232 self.text.raw[:50] + ')'
237
233
234 def get_title(self):
235 title = self.title
236 if len(title) == 0:
237 title = self.text.raw[:20]
238
239 return title
240
238 def _get_replies(self):
241 def _get_replies(self):
239 return Post.objects.filter(parent=self.id)
242 return Post.objects.filter(parent=self.id)
240
243
241 def get_reply_count(self):
244 def get_reply_count(self):
242 return self._get_replies().count()
245 return self._get_replies().count()
243
246
244 def get_images_count(self):
247 def get_images_count(self):
245 images_count = 1 if self.image else 0
248 images_count = 1 if self.image else 0
246 for reply in self._get_replies():
249 for reply in self._get_replies():
247 if reply.image:
250 if reply.image:
248 images_count += 1
251 images_count += 1
249
252
250 return images_count
253 return images_count
251
254
252 def get_gets_count(self):
253 gets_count = 1 if self.is_get() else 0
254 for reply in self._get_replies():
255 if reply.is_get():
256 gets_count += 1
257
258 return gets_count
259
260 def can_bump(self):
255 def can_bump(self):
261 """Check if the thread can be bumped by replying"""
256 """Check if the thread can be bumped by replying"""
262
257
263 replies_count = len(Post.objects.get_thread(self.id))
258 replies_count = len(Post.objects.get_thread(self.id))
264
259
265 return replies_count <= settings.MAX_POSTS_PER_THREAD
260 return replies_count <= settings.MAX_POSTS_PER_THREAD
266
261
267 def get_last_replies(self):
262 def get_last_replies(self):
268 if settings.LAST_REPLIES_COUNT > 0:
263 if settings.LAST_REPLIES_COUNT > 0:
269 reply_count = self.get_reply_count()
264 reply_count = self.get_reply_count()
270
265
271 if reply_count > 0:
266 if reply_count > 0:
272 reply_count_to_show = min(settings.LAST_REPLIES_COUNT,
267 reply_count_to_show = min(settings.LAST_REPLIES_COUNT,
273 reply_count)
268 reply_count)
274 last_replies = self._get_replies()[reply_count
269 last_replies = self._get_replies()[reply_count
275 - reply_count_to_show:]
270 - reply_count_to_show:]
276
271
277 return last_replies
272 return last_replies
278
273
279
274
280 class User(models.Model):
275 class User(models.Model):
281
276
282 user_id = models.CharField(max_length=50)
277 user_id = models.CharField(max_length=50)
283 rank = models.IntegerField()
278 rank = models.IntegerField()
284
279
285 registration_time = models.DateTimeField()
280 registration_time = models.DateTimeField()
286 last_access_time = models.DateTimeField()
281 last_access_time = models.DateTimeField()
287
282
288 fav_tags = models.ManyToManyField(Tag, null=True, blank=True)
283 fav_tags = models.ManyToManyField(Tag, null=True, blank=True)
289 fav_threads = models.ManyToManyField(Post, related_name='+', null=True,
284 fav_threads = models.ManyToManyField(Post, related_name='+', null=True,
290 blank=True)
285 blank=True)
291
286
292 def save_setting(self, name, value):
287 def save_setting(self, name, value):
293 setting, created = Setting.objects.get_or_create(name=name, user=self)
288 setting, created = Setting.objects.get_or_create(name=name, user=self)
294 setting.value = value
289 setting.value = value
295 setting.save()
290 setting.save()
296
291
297 return setting
292 return setting
298
293
299 def get_setting(self, name):
294 def get_setting(self, name):
300 settings = Setting.objects.filter(name=name, user=self)
295 settings = Setting.objects.filter(name=name, user=self)
301 if len(settings) > 0:
296 if len(settings) > 0:
302 setting = settings[0]
297 setting = settings[0]
303 else:
298 else:
304 setting = None
299 setting = None
305
300
306 if setting:
301 if setting:
307 setting_value = setting.value
302 setting_value = setting.value
308 else:
303 else:
309 setting_value = None
304 setting_value = None
310
305
311 return setting_value
306 return setting_value
312
307
313 def is_moderator(self):
308 def is_moderator(self):
314 return RANK_MODERATOR >= self.rank
309 return RANK_MODERATOR >= self.rank
315
310
316 def __unicode__(self):
311 def __unicode__(self):
317 return self.user_id
312 return self.user_id + '(' + self.rank + ')'
318
313
319
314
320 class Setting(models.Model):
315 class Setting(models.Model):
321
316
322 name = models.CharField(max_length=50)
317 name = models.CharField(max_length=50)
323 value = models.CharField(max_length=50)
318 value = models.CharField(max_length=50)
324 user = models.ForeignKey(User)
319 user = models.ForeignKey(User)
325
320
326
321
327 class Ban(models.Model):
322 class Ban(models.Model):
328 ip = models.GenericIPAddressField()
323 ip = models.GenericIPAddressField()
329
324
330 def __unicode__(self):
325 def __unicode__(self):
331 return self.ip
326 return self.ip
@@ -1,47 +1,48 b''
1 {% load staticfiles %}
1 {% load staticfiles %}
2 {% load i18n %}
2 {% load i18n %}
3
3
4 <!DOCTYPE html>
4 <!DOCTYPE html>
5 <html>
5 <html>
6 <head>
6 <head>
7 <link rel="stylesheet" type="text/css"
7 <link rel="stylesheet" type="text/css"
8 href="{{ STATIC_URL }}css/jquery.fancybox.css" media="all"/>
8 href="{{ STATIC_URL }}css/jquery.fancybox.css" media="all"/>
9 <link rel="stylesheet" type="text/css"
9 <link rel="stylesheet" type="text/css"
10 href="{{ STATIC_URL }}css/{{ theme }}/base_page.css" media="all"/>
10 href="{{ STATIC_URL }}css/{{ theme }}/base_page.css" media="all"/>
11 <link rel="alternate" type="application/rss+xml" href="rss/" title="
11 <link rel="alternate" type="application/rss+xml" href="rss/" title="
12 {% trans 'Feed' %}"/>
12 {% trans 'Feed' %}"/>
13
13
14 <link rel="icon" type="image/png"
14 <link rel="icon" type="image/png"
15 href="{{ STATIC_URL }}favicon.png">
15 href="{{ STATIC_URL }}favicon.png">
16
16
17 <meta name="viewport" content="width=device-width, initial-scale=1"/>
17 <meta name="viewport" content="width=device-width, initial-scale=1"/>
18 <meta charset="utf-8"/>
18 <meta charset="utf-8"/>
19 {% block head %}{% endblock %}
19 {% block head %}{% endblock %}
20 </head>
20 </head>
21 <body>
21 <body>
22 <script src="{{ STATIC_URL }}js/jquery-2.0.1.min.js"></script>
22 <script src="{{ STATIC_URL }}js/jquery-2.0.1.min.js"></script>
23 <script src="{{ STATIC_URL }}js/jquery.fancybox.pack.js"></script>
23 <script src="{{ STATIC_URL }}js/jquery.fancybox.pack.js"></script>
24 <script src="{% url 'django.views.i18n.javascript_catalog' %}"></script>
24 <script src="{% url 'django.views.i18n.javascript_catalog' %}"></script>
25 <script src="{{ STATIC_URL }}js/refmaps.js"></script>
25 <script src="{{ STATIC_URL }}js/refmaps.js"></script>
26 <script src="{{ STATIC_URL }}js/main.js"></script>
26 <script src="{{ STATIC_URL }}js/main.js"></script>
27
27
28 <div class="navigation_panel">
28 <div class="navigation_panel">
29 <a class="link" href="{% url 'index' %}">{% trans "All threads" %}</a>
29 <a class="link" href="{% url 'index' %}">{% trans "All threads" %}</a>
30 {% for tag in tags %}
30 {% for tag in tags %}
31 <a class="tag" href="{% url 'tag' tag_name=tag.name %}">
31 <a class="tag" href="{% url 'tag' tag_name=tag.name %}"
32 {{ tag.name }}</a>
32 >{{ tag.name }}</a>
33 {% endfor %}
33 {% endfor %}
34 <a class="tag" href="{% url 'tags' %}">[...]</a>
34 <a class="tag" href="{% url 'tags' %}" alt="{% trans 'Tag management' %}"
35 >[...]</a>
35 <a class="link" href="{% url 'settings' %}">{% trans 'Settings' %}</a>
36 <a class="link" href="{% url 'settings' %}">{% trans 'Settings' %}</a>
36 </div>
37 </div>
37
38
38 {% block content %}{% endblock %}
39 {% block content %}{% endblock %}
39
40
40 <div class="navigation_panel">
41 <div class="navigation_panel">
41 {% block metapanel %}{% endblock %}
42 {% block metapanel %}{% endblock %}
42 [<a href="{% url "login" %}">{% trans 'Login' %}</a>]
43 [<a href="{% url "login" %}">{% trans 'Login' %}</a>]
43 <a class="link" href="#top">{% trans 'Up' %}</a>
44 <a class="link" href="#top">{% trans 'Up' %}</a>
44 </div>
45 </div>
45
46
46 </body>
47 </body>
47 </html>
48 </html>
@@ -1,177 +1,177 b''
1 {% extends "boards/base.html" %}
1 {% extends "boards/base.html" %}
2
2
3 {% load i18n %}
3 {% load i18n %}
4 {% load markup %}
4 {% load markup %}
5
5
6 {% block head %}
6 {% block head %}
7 {% if tag %}
7 {% if tag %}
8 <title>Neboard - {{ tag }}</title>
8 <title>Neboard - {{ tag }}</title>
9 {% else %}
9 {% else %}
10 <title>Neboard</title>
10 <title>Neboard</title>
11 {% endif %}
11 {% endif %}
12 {% endblock %}
12 {% endblock %}
13
13
14 {% block content %}
14 {% block content %}
15
15
16 {% if tag %}
16 {% if tag %}
17 <div class="tag_info">
17 <div class="tag_info">
18 <h2>{% trans 'Tag: ' %}{{ tag }}</h2>
18 <h2>{% trans 'Tag: ' %}{{ tag }}</h2>
19 </div>
19 </div>
20 {% endif %}
20 {% endif %}
21
21
22 {% if threads %}
22 {% if threads %}
23 {% for thread in threads %}
23 {% for thread in threads %}
24 <div class="thread">
24 <div class="thread">
25 {% if thread.can_bump %}
25 {% if thread.can_bump %}
26 <div class="post" id="{{thread.id}}">
26 <div class="post" id="{{thread.id}}">
27 {% else %}
27 {% else %}
28 <div class="post dead_post" id="{{ thread.id }}">
28 <div class="post dead_post" id="{{ thread.id }}">
29 {% endif %}
29 {% endif %}
30 {% if thread.image %}
30 {% if thread.image %}
31 <div class="image">
31 <div class="image">
32 <a class="fancy"
32 <a class="fancy"
33 href="{{ thread.image.url }}"><img
33 href="{{ thread.image.url }}"><img
34 src="{{ thread.image.url_200x150 }}"
34 src="{{ thread.image.url_200x150 }}"
35 alt="{% trans 'Post image' %}"
35 alt="{% trans 'Post image' %}"
36 data-width="{{ thread.image_width }}"
36 data-width="{{ thread.image_width }}"
37 data-height="{{ thread.image_height }}" />
37 data-height="{{ thread.image_height }}" />
38 </a>
38 </a>
39 </div>
39 </div>
40 {% endif %}
40 {% endif %}
41 <div class="message">
41 <div class="message">
42 <div class="post-info">
42 <div class="post-info">
43 <span class="title">{{ thread.title }}</span>
43 <span class="title">{{ thread.title }}</span>
44 <a class="post_id" href="{% url 'thread' thread.id %}"
44 <a class="post_id" href="{% url 'thread' thread.id %}"
45 >(#{{ thread.id }})</a>
45 >(#{{ thread.id }})</a>
46 [{{ thread.pub_time }}]
46 [{{ thread.pub_time }}]
47 [<a class="link" href="{% url 'thread' thread.id %}#form"
47 [<a class="link" href="{% url 'thread' thread.id %}#form"
48 >{% trans "Reply" %}</a>]
48 >{% trans "Reply" %}</a>]
49
49
50 {% if user.is_moderator %}
50 {% if user.is_moderator %}
51 <span class="moderator_info">
51 <span class="moderator_info">
52 ({{ thread.poster_ip }})
52 ({{ thread.poster_ip }})
53 [<a href="{% url 'delete' post_id=thread.id %}"
53 [<a href="{% url 'delete' post_id=thread.id %}"
54 >{% trans 'Delete' %}</a>]
54 >{% trans 'Delete' %}</a>]
55 </span>
55 </span>
56 {% endif %}
56 {% endif %}
57 </div>
57 </div>
58 {% autoescape off %}
58 {% autoescape off %}
59 {{ thread.text.rendered|truncatewords_html:50 }}
59 {{ thread.text.rendered|truncatewords_html:50 }}
60 {% endautoescape %}
60 {% endautoescape %}
61 </div>
61 </div>
62 <div class="metadata">
62 <div class="metadata">
63 {{ thread.get_reply_count }} {% trans 'replies' %},
63 {{ thread.get_reply_count }} {% trans 'replies' %},
64 {{ thread.get_images_count }} {% trans 'images' %}.
64 {{ thread.get_images_count }} {% trans 'images' %}.
65 {% if thread.tags.all %}
65 {% if thread.tags.all %}
66 <span class="tags">{% trans 'Tags' %}:
66 <span class="tags">{% trans 'Tags' %}:
67 {% for tag in thread.tags.all %}
67 {% for tag in thread.tags.all %}
68 <a class="tag" href="
68 <a class="tag" href="
69 {% url 'tag' tag_name=tag.name %}">
69 {% url 'tag' tag_name=tag.name %}">
70 {{ tag.name }}</a>
70 {{ tag.name }}</a>
71 {% endfor %}
71 {% endfor %}
72 </span>
72 </span>
73 {% endif %}
73 {% endif %}
74 </div>
74 </div>
75 </div>
75 </div>
76 {% if thread.get_last_replies %}
76 {% if thread.get_last_replies %}
77 <div class="last-replies">
77 <div class="last-replies">
78 {% for post in thread.get_last_replies %}
78 {% for post in thread.get_last_replies %}
79 {% if thread.can_bump %}
79 {% if thread.can_bump %}
80 <div class="post" id="{{ post.id }}">
80 <div class="post" id="{{ post.id }}">
81 {% else %}
81 {% else %}
82 <div class="post dead_post id="{{ post.id }}"">
82 <div class="post dead_post id="{{ post.id }}"">
83 {% endif %}
83 {% endif %}
84 {% if post.image %}
84 {% if post.image %}
85 <div class="image">
85 <div class="image">
86 <a class="fancy"
86 <a class="fancy"
87 href="{{ post.image.url }}"><img
87 href="{{ post.image.url }}"><img
88 src=" {{ post.image.url_200x150 }}"
88 src=" {{ post.image.url_200x150 }}"
89 alt="{% trans 'Post image' %}"
89 alt="{% trans 'Post image' %}"
90 data-width="{{ post.image_width }}"
90 data-width="{{ post.image_width }}"
91 data-height="{{ post.image_height }}"/>
91 data-height="{{ post.image_height }}"/>
92 </a>
92 </a>
93 </div>
93 </div>
94 {% endif %}
94 {% endif %}
95 <div class="message">
95 <div class="message">
96 <div class="post-info">
96 <div class="post-info">
97 <span class="title">{{ post.title }}</span>
97 <span class="title">{{ post.title }}</span>
98 <a class="post_id" href="
98 <a class="post_id" href="
99 {% url 'thread' thread.id %}#{{ post.id }}">
99 {% url 'thread' thread.id %}#{{ post.id }}">
100 (#{{ post.id }})</a>
100 (#{{ post.id }})</a>
101 [{{ post.pub_time }}]
101 [{{ post.pub_time }}]
102 </div>
102 </div>
103 {% autoescape off %}
103 {% autoescape off %}
104 {{ post.text.rendered|truncatewords_html:50 }}
104 {{ post.text.rendered|truncatewords_html:50 }}
105 {% endautoescape %}
105 {% endautoescape %}
106 </div>
106 </div>
107 </div>
107 </div>
108 {% endfor %}
108 {% endfor %}
109 </div>
109 </div>
110 {% endif %}
110 {% endif %}
111 </div>
111 </div>
112 {% endfor %}
112 {% endfor %}
113 {% else %}
113 {% else %}
114 <div class="post">
114 <div class="post">
115 {% trans 'No threads exist. Create the first one!' %}</div>
115 {% trans 'No threads exist. Create the first one!' %}</div>
116 {% endif %}
116 {% endif %}
117
117
118 <form enctype="multipart/form-data" method="post">{% csrf_token %}
118 <form enctype="multipart/form-data" method="post">{% csrf_token %}
119 <div class="post-form-w">
119 <div class="post-form-w">
120
120
121 <div class="form-title">{% trans "Create new thread" %}</div>
121 <div class="form-title">{% trans "Create new thread" %}</div>
122 <div class="post-form">
122 <div class="post-form">
123 <div class="form-row">
123 <div class="form-row">
124 <div class="form-label">{% trans 'Title' %}</div>
124 <div class="form-label">{% trans 'Title' %}</div>
125 <div class="form-input">{{ form.title }}</div>
125 <div class="form-input">{{ form.title }}</div>
126 <div class="form-errors">{{ form.title.errors }}</div>
126 <div class="form-errors">{{ form.title.errors }}</div>
127 </div>
127 </div>
128 <div class="form-row">
128 <div class="form-row">
129 <div class="form-label">{% trans 'Text' %}</div>
129 <div class="form-label">{% trans 'Text' %}</div>
130 <div class="form-input">{{ form.text }}</div>
130 <div class="form-input">{{ form.text }}</div>
131 <div class="form-errors">{{ form.text.errors }}</div>
131 <div class="form-errors">{{ form.text.errors }}</div>
132 </div>
132 </div>
133 <div class="form-row">
133 <div class="form-row">
134 <div class="form-label">{% trans 'Image' %}</div>
134 <div class="form-label">{% trans 'Image' %}</div>
135 <div class="form-input">{{ form.image }}</div>
135 <div class="form-input">{{ form.image }}</div>
136 <div class="form-errors">{{ form.image.errors }}</div>
136 <div class="form-errors">{{ form.image.errors }}</div>
137 </div>
137 </div>
138 <div class="form-row">
138 <div class="form-row">
139 <div class="form-label">{% trans 'Tags' %}</div>
139 <div class="form-label">{% trans 'Tags' %}</div>
140 <div class="form-input">{{ form.tags }}</div>
140 <div class="form-input">{{ form.tags }}</div>
141 <div class="form-errors">{{ form.tags.errors }}</div>
141 <div class="form-errors">{{ form.tags.errors }}</div>
142 </div>
142 </div>
143 <div class="form-row">
143 <div class="form-row">
144 {{ form.captcha }}
144 {{ form.captcha }}
145 <div class="form-errors">{{ form.captcha.errors }}</div>
145 <div class="form-errors">{{ form.captcha.errors }}</div>
146 </div>
146 </div>
147 </div>
147 </div>
148 <div class="form-submit">
148 <div class="form-submit">
149 <input type="submit" value="{% trans "Post" %}"/></div>
149 <input type="submit" value="{% trans "Post" %}"/></div>
150 <div>
150 <div>
151 {% trans 'Tags must be delimited by spaces. Text or image is required.' %}
151 {% trans 'Tags must be delimited by spaces. Text or image is required.' %}
152 </div>
152 </div>
153 <div><a href="http://daringfireball.net/projects/markdown/basics">
153 <div><a href="http://daringfireball.net/projects/markdown/basics">
154 {% trans 'Basic markdown syntax.' %}</a></div>
154 {% trans 'Basic markdown syntax.' %}</a></div>
155 </div>
155 </div>
156 </form>
156 </form>
157
157
158 {% endblock %}
158 {% endblock %}
159
159
160 {% block metapanel %}
160 {% block metapanel %}
161
161
162 <span class="metapanel">
162 <span class="metapanel">
163 <b><a href="{% url "authors" %}">Neboard</a> pre1.0</b>
163 <b><a href="{% url "authors" %}">Neboard</a> 1.1</b>
164 {% trans "Pages:" %}
164 {% trans "Pages:" %}
165 {% for page in pages %}
165 {% for page in pages %}
166 [<a href="
166 [<a href="
167 {% if tag %}
167 {% if tag %}
168 {% url "tag" tag_name=tag page=page %}
168 {% url "tag" tag_name=tag page=page %}
169 {% else %}
169 {% else %}
170 {% url "index" page=page %}
170 {% url "index" page=page %}
171 {% endif %}
171 {% endif %}
172 ">{{ page }}</a>]
172 ">{{ page }}</a>]
173 {% endfor %}
173 {% endfor %}
174 [<a href="rss/">RSS</a>]
174 [<a href="rss/">RSS</a>]
175 </span>
175 </span>
176
176
177 {% endblock %}
177 {% endblock %}
@@ -1,119 +1,119 b''
1 {% extends "boards/base.html" %}
1 {% extends "boards/base.html" %}
2
2
3 {% load i18n %}
3 {% load i18n %}
4 {% load markup %}
4 {% load markup %}
5
5
6 {% block head %}
6 {% block head %}
7 <title>Neboard - {{ posts.0.title }}</title>
7 <title>Neboard - {{ posts.0.get_title }}</title>
8 <script src="{{ STATIC_URL }}js/thread.js"></script>
8 {% endblock %}
9 {% endblock %}
9
10
10 {% block content %}
11 {% block content %}
11 <script src="{{ STATIC_URL }}js/thread.js"></script>
12
12
13 {% if posts %}
13 {% if posts %}
14 <div id="posts">
14 <div id="posts">
15 {% for post in posts %}
15 {% for post in posts %}
16 {% if posts.0.can_bump %}
16 {% if posts.0.can_bump %}
17 <div class="post" id="{{ post.id }}">
17 <div class="post" id="{{ post.id }}">
18 {% else %}
18 {% else %}
19 <div class="post dead_post" id="{{ post.id }}">
19 <div class="post dead_post" id="{{ post.id }}">
20 {% endif %}
20 {% endif %}
21 {% if post.image %}
21 {% if post.image %}
22 <div class="image">
22 <div class="image">
23 <a
23 <a
24 class="fancy"
24 class="fancy"
25 href="{{ post.image.url }}"><img
25 href="{{ post.image.url }}"><img
26 src="{{ post.image.url_200x150 }}"
26 src="{{ post.image.url_200x150 }}"
27 alt="{% trans 'Post image' %}"
27 alt="{% trans 'Post image' %}"
28 data-width="{{ post.image_width }}"
28 data-width="{{ post.image_width }}"
29 data-height="{{ post.image_height }}"/>
29 data-height="{{ post.image_height }}"/>
30 </a>
30 </a>
31 </div>
31 </div>
32 {% endif %}
32 {% endif %}
33 <div class="message">
33 <div class="message">
34 <div class="post-info">
34 <div class="post-info">
35 <span class="title">{{ post.title }}</span>
35 <span class="title">{{ post.title }}</span>
36 <a class="post_id" href="#{{ post.id }}">
36 <a class="post_id" href="#{{ post.id }}">
37 (#{{ post.id }})</a>
37 (#{{ post.id }})</a>
38 [{{ post.pub_time }}]
38 [{{ post.pub_time }}]
39 [<a href="#" onclick="javascript:addQuickReply('{{ post.id }}')
39 [<a href="#" onclick="javascript:addQuickReply('{{ post.id }}')
40 ; return false;">&gt;&gt;</a>]
40 ; return false;">&gt;&gt;</a>]
41
41
42 {% if user.is_moderator %}
42 {% if user.is_moderator %}
43 <span class="moderator_info">
43 <span class="moderator_info">
44 ({{ post.poster_ip }})
44 ({{ post.poster_ip }})
45 [<a href="{% url 'delete' post_id=post.id %}"
45 [<a href="{% url 'delete' post_id=post.id %}"
46 >{% trans 'Delete' %}</a>]
46 >{% trans 'Delete' %}</a>]
47 </span>
47 </span>
48 {% endif %}
48 {% endif %}
49 </div>
49 </div>
50 {% autoescape off %}
50 {% autoescape off %}
51 {{ post.text.rendered }}
51 {{ post.text.rendered }}
52 {% endautoescape %}
52 {% endautoescape %}
53 </div>
53 </div>
54 {% if post.tags.all %}
54 {% if post.tags.all %}
55 <div class="metadata">
55 <div class="metadata">
56 <span class="tags">{% trans 'Tags' %}:
56 <span class="tags">{% trans 'Tags' %}:
57 {% for tag in post.tags.all %}
57 {% for tag in post.tags.all %}
58 <a class="tag" href="{% url 'tag' tag.name %}">
58 <a class="tag" href="{% url 'tag' tag.name %}">
59 {{ tag.name }}</a>
59 {{ tag.name }}</a>
60 {% endfor %}
60 {% endfor %}
61 </span>
61 </span>
62 </div>
62 </div>
63 {% endif %}
63 {% endif %}
64 </div>
64 </div>
65 {% endfor %}
65 {% endfor %}
66 </div>
66 </div>
67 {% endif %}
67 {% endif %}
68
68
69 <form id="form" enctype="multipart/form-data" method="post"
69 <form id="form" enctype="multipart/form-data" method="post"
70 >{% csrf_token %}
70 >{% csrf_token %}
71 <div class="post-form-w">
71 <div class="post-form-w">
72 <div class="form-title">{% trans "Reply to thread" %} #{{ posts.0.id }}</div>
72 <div class="form-title">{% trans "Reply to thread" %} #{{ posts.0.id }}</div>
73 <div class="post-form">
73 <div class="post-form">
74 <div class="form-row">
74 <div class="form-row">
75 <div class="form-label">{% trans 'Title' %}</div>
75 <div class="form-label">{% trans 'Title' %}</div>
76 <div class="form-input">{{ form.title }}</div>
76 <div class="form-input">{{ form.title }}</div>
77 <div class="form-errors">{{ form.title.errors }}</div>
77 <div class="form-errors">{{ form.title.errors }}</div>
78 </div>
78 </div>
79 <div class="form-row">
79 <div class="form-row">
80 <div class="form-label">{% trans 'Text' %}</div>
80 <div class="form-label">{% trans 'Text' %}</div>
81 <div class="form-input">{{ form.text }}</div>
81 <div class="form-input">{{ form.text }}</div>
82 <div class="form-errors">{{ form.text.errors }}</div>
82 <div class="form-errors">{{ form.text.errors }}</div>
83 </div>
83 </div>
84 <div class="form-row">
84 <div class="form-row">
85 <div class="form-label">{% trans 'Image' %}</div>
85 <div class="form-label">{% trans 'Image' %}</div>
86 <div class="form-input">{{ form.image }}</div>
86 <div class="form-input">{{ form.image }}</div>
87 <div class="form-errors">{{ form.image.errors }}</div>
87 <div class="form-errors">{{ form.image.errors }}</div>
88 </div>
88 </div>
89 <div class="form-row">
89 <div class="form-row">
90 {{ form.captcha }}
90 {{ form.captcha }}
91 <div class="form-errors">{{ form.captcha.errors }}</div>
91 <div class="form-errors">{{ form.captcha.errors }}</div>
92 </div>
92 </div>
93 </div>
93 </div>
94
94
95 <div class="form-submit"><input type="submit"
95 <div class="form-submit"><input type="submit"
96 value="{% trans "Post" %}"/></div>
96 value="{% trans "Post" %}"/></div>
97 <div><a href="http://daringfireball.net/projects/markdown/basics">
97 <div><a href="http://daringfireball.net/projects/markdown/basics">
98 {% trans 'Basic markdown syntax.' %}</a></div>
98 {% trans 'Basic markdown syntax.' %}</a></div>
99 <div>{% trans 'Example: ' %}*<i>{% trans 'italic' %}</i>*,
99 <div>{% trans 'Example: ' %}*<i>{% trans 'italic' %}</i>*,
100 **<b>{% trans 'bold' %}</b>**</div>
100 **<b>{% trans 'bold' %}</b>**</div>
101 <div>{% trans 'Quotes can be inserted with' %} "&gt;"</div>
101 <div>{% trans 'Quotes can be inserted with' %} "&gt;"</div>
102 <div>{% trans 'Links to answers can be inserted with' %}
102 <div>{% trans 'Links to answers can be inserted with' %}
103 "&gt;&gt;123"
103 "&gt;&gt;123"
104 </div>
104 </div>
105 </div>
105 </div>
106 </form>
106 </form>
107
107
108 {% endblock %}
108 {% endblock %}
109
109
110 {% block metapanel %}
110 {% block metapanel %}
111
111
112 <span class="metapanel">
112 <span class="metapanel">
113 {{ posts.0.get_reply_count }} {% trans 'replies' %},
113 {{ posts.0.get_reply_count }} {% trans 'replies' %},
114 {{ posts.0.get_images_count }} {% trans 'images' %}.
114 {{ posts.0.get_images_count }} {% trans 'images' %}.
115 {% trans 'Last update: ' %}{{ posts.0.last_edit_time }}
115 {% trans 'Last update: ' %}{{ posts.0.last_edit_time }}
116 [<a href="rss/">RSS</a>]
116 [<a href="rss/">RSS</a>]
117 </span>
117 </span>
118
118
119 {% endblock %}
119 {% endblock %}
@@ -1,320 +1,323 b''
1 import hashlib
1 import hashlib
2 from django.core.urlresolvers import reverse
2 from django.core.urlresolvers import reverse
3 from django.template import RequestContext
3 from django.template import RequestContext
4 from django.shortcuts import render, redirect, get_object_or_404
4 from django.shortcuts import render, redirect, get_object_or_404
5 from django.http import HttpResponseRedirect
6 from django.utils import timezone
5 from django.utils import timezone
7
6
8 from boards import forms
7 from boards import forms
9 import boards
8 import boards
10 from boards import utils
9 from boards import utils
11 from boards.forms import ThreadForm, PostForm, SettingsForm, PlainErrorList, \
10 from boards.forms import ThreadForm, PostForm, SettingsForm, PlainErrorList, \
12 ThreadCaptchaForm, PostCaptchaForm, LoginForm
11 ThreadCaptchaForm, PostCaptchaForm, LoginForm
13
12
14 from boards.models import Post, Tag, Ban, User, RANK_USER, RANK_MODERATOR, NO_PARENT
13 from boards.models import Post, Tag, Ban, User, RANK_USER, NO_PARENT
15 from boards import authors
14 from boards import authors
16 import neboard
15 import neboard
17
16
18
17
19 def index(request, page=0):
18 def index(request, page=0):
20 context = _init_default_context(request)
19 context = _init_default_context(request)
21
20
22 if utils.need_include_captcha(request):
21 if utils.need_include_captcha(request):
23 threadFormClass = ThreadCaptchaForm
22 threadFormClass = ThreadCaptchaForm
24 kwargs = {'request': request}
23 kwargs = {'request': request}
25 else:
24 else:
26 threadFormClass = ThreadForm
25 threadFormClass = ThreadForm
27 kwargs = {}
26 kwargs = {}
28
27
29 if request.method == 'POST':
28 if request.method == 'POST':
30 form = threadFormClass(request.POST, request.FILES,
29 form = threadFormClass(request.POST, request.FILES,
31 error_class=PlainErrorList, **kwargs)
30 error_class=PlainErrorList, **kwargs)
32
31
33 if form.is_valid():
32 if form.is_valid():
34 return _new_post(request, form)
33 return _new_post(request, form)
35 else:
34 else:
36 form = threadFormClass(error_class=PlainErrorList, **kwargs)
35 form = threadFormClass(error_class=PlainErrorList, **kwargs)
37
36
38 threads = Post.objects.get_threads(page=int(page))
37 threads = Post.objects.get_threads(page=int(page))
39
38
40 context['threads'] = None if len(threads) == 0 else threads
39 context['threads'] = None if len(threads) == 0 else threads
41 context['form'] = form
40 context['form'] = form
42 context['pages'] = range(Post.objects.get_thread_page_count())
41 context['pages'] = range(Post.objects.get_thread_page_count())
43
42
44 return render(request, 'boards/posting_general.html',
43 return render(request, 'boards/posting_general.html',
45 context)
44 context)
46
45
47
46
48 def _new_post(request, form, thread_id=boards.models.NO_PARENT):
47 def _new_post(request, form, thread_id=boards.models.NO_PARENT):
49 """Add a new post (in thread or as a reply)."""
48 """Add a new post (in thread or as a reply)."""
50
49
51 ip = _get_client_ip(request)
50 ip = _get_client_ip(request)
52 is_banned = Ban.objects.filter(ip=ip).count() > 0
51 is_banned = Ban.objects.filter(ip=ip).count() > 0
53
52
54 if is_banned:
53 if is_banned:
55 return redirect(you_are_banned)
54 return redirect(you_are_banned)
56
55
57 data = form.cleaned_data
56 data = form.cleaned_data
58
57
59 title = data['title']
58 title = data['title']
60 text = data['text']
59 text = data['text']
61
60
62 if 'image' in data.keys():
61 if 'image' in data.keys():
63 image = data['image']
62 image = data['image']
64 else:
63 else:
65 image = None
64 image = None
66
65
67 tags = []
66 tags = []
68
67
69 new_thread = thread_id == boards.models.NO_PARENT
68 new_thread = thread_id == boards.models.NO_PARENT
70 if new_thread:
69 if new_thread:
71 tag_strings = data['tags']
70 tag_strings = data['tags']
72
71
73 if tag_strings:
72 if tag_strings:
74 tag_strings = tag_strings.split(' ')
73 tag_strings = tag_strings.split(' ')
75 for tag_name in tag_strings:
74 for tag_name in tag_strings:
76 tag_name = tag_name.strip()
75 tag_name = tag_name.strip()
77 if len(tag_name) > 0:
76 if len(tag_name) > 0:
78 tag, created = Tag.objects.get_or_create(name=tag_name)
77 tag, created = Tag.objects.get_or_create(name=tag_name)
79 tags.append(tag)
78 tags.append(tag)
80
79
81 # TODO Add a possibility to define a link image instead of an image file.
80 # TODO Add a possibility to define a link image instead of an image file.
82 # If a link is given, download the image automatically.
81 # If a link is given, download the image automatically.
83
82
84 post = Post.objects.create_post(title=title, text=text, ip=ip,
83 post = Post.objects.create_post(title=title, text=text, ip=ip,
85 parent_id=thread_id, image=image,
84 parent_id=thread_id, image=image,
86 tags=tags)
85 tags=tags)
87
86
88 thread_to_show = (post.id if new_thread else thread_id)
87 thread_to_show = (post.id if new_thread else thread_id)
89
88
90 if new_thread:
89 if new_thread:
91 return redirect(thread, post_id=thread_to_show)
90 return redirect(thread, post_id=thread_to_show)
92 else:
91 else:
93 return redirect(reverse(thread,
92 return redirect(reverse(thread,
94 kwargs={'post_id': thread_to_show}) + '#'
93 kwargs={'post_id': thread_to_show}) + '#'
95 + str(post.id))
94 + str(post.id))
96
95
97
96
98 def tag(request, tag_name, page=0):
97 def tag(request, tag_name, page=0):
99 """Get all tag threads (posts without a parent)."""
98 """Get all tag threads (posts without a parent)."""
100
99
101 tag = get_object_or_404(Tag, name=tag_name)
100 tag = get_object_or_404(Tag, name=tag_name)
102 threads = Post.objects.get_threads(tag=tag, page=int(page))
101 threads = Post.objects.get_threads(tag=tag, page=int(page))
103
102
104 if request.method == 'POST':
103 if request.method == 'POST':
105 form = ThreadForm(request.POST, request.FILES,
104 form = ThreadForm(request.POST, request.FILES,
106 error_class=PlainErrorList)
105 error_class=PlainErrorList)
107 if form.is_valid():
106 if form.is_valid():
108 return _new_post(request, form)
107 return _new_post(request, form)
109 else:
108 else:
110 form = forms.ThreadForm(initial={'tags': tag_name},
109 form = forms.ThreadForm(initial={'tags': tag_name},
111 error_class=PlainErrorList)
110 error_class=PlainErrorList)
112
111
113 context = _init_default_context(request)
112 context = _init_default_context(request)
114 context['threads'] = None if len(threads) == 0 else threads
113 context['threads'] = None if len(threads) == 0 else threads
115 context['tag'] = tag_name
114 context['tag'] = tag_name
116 context['pages'] = range(Post.objects.get_thread_page_count(tag=tag))
115 context['pages'] = range(Post.objects.get_thread_page_count(tag=tag))
117
116
118 context['form'] = form
117 context['form'] = form
119
118
120 return render(request, 'boards/posting_general.html',
119 return render(request, 'boards/posting_general.html',
121 context)
120 context)
122
121
123
122
124 def thread(request, post_id):
123 def thread(request, post_id):
125 """Get all thread posts"""
124 """Get all thread posts"""
126
125
127 if utils.need_include_captcha(request):
126 if utils.need_include_captcha(request):
128 postFormClass = PostCaptchaForm
127 postFormClass = PostCaptchaForm
129 kwargs = {'request': request}
128 kwargs = {'request': request}
130 else:
129 else:
131 postFormClass = PostForm
130 postFormClass = PostForm
132 kwargs = {}
131 kwargs = {}
133
132
134 if request.method == 'POST':
133 if request.method == 'POST':
135 form = postFormClass(request.POST, request.FILES,
134 form = postFormClass(request.POST, request.FILES,
136 error_class=PlainErrorList, **kwargs)
135 error_class=PlainErrorList, **kwargs)
137 if form.is_valid():
136 if form.is_valid():
138 return _new_post(request, form, post_id)
137 return _new_post(request, form, post_id)
139 else:
138 else:
140 form = postFormClass(error_class=PlainErrorList, **kwargs)
139 form = postFormClass(error_class=PlainErrorList, **kwargs)
141
140
142 posts = Post.objects.get_thread(post_id)
141 posts = Post.objects.get_thread(post_id)
143
142
144 context = _init_default_context(request)
143 context = _init_default_context(request)
145
144
146 context['posts'] = posts
145 context['posts'] = posts
147 context['form'] = form
146 context['form'] = form
148
147
149 return render(request, 'boards/thread.html', context)
148 return render(request, 'boards/thread.html', context)
150
149
151
150
152 def login(request):
151 def login(request):
153 """Log in with user id"""
152 """Log in with user id"""
154
153
155 context = _init_default_context(request)
154 context = _init_default_context(request)
156
155
157 if request.method == 'POST':
156 if request.method == 'POST':
158 form = LoginForm(request.POST, request.FILES, error_class=PlainErrorList)
157 form = LoginForm(request.POST, request.FILES,
158 error_class=PlainErrorList)
159 if form.is_valid():
159 if form.is_valid():
160 user = User.objects.get(user_id=form.cleaned_data['user_id'])
160 user = User.objects.get(user_id=form.cleaned_data['user_id'])
161 request.session['user_id'] = user.id
161 request.session['user_id'] = user.id
162 return redirect(index)
162 return redirect(index)
163
163
164 else:
164 else:
165 form = LoginForm()
165 form = LoginForm()
166
166
167 context['form'] = form
167 context['form'] = form
168
168
169 return render(request, 'boards/login.html', context)
169 return render(request, 'boards/login.html', context)
170
170
171
171
172 def settings(request):
172 def settings(request):
173 """User's settings"""
173 """User's settings"""
174
174
175 context = _init_default_context(request)
175 context = _init_default_context(request)
176
176
177 if request.method == 'POST':
177 if request.method == 'POST':
178 form = SettingsForm(request.POST)
178 form = SettingsForm(request.POST)
179 if form.is_valid():
179 if form.is_valid():
180 selected_theme = form.cleaned_data['theme']
180 selected_theme = form.cleaned_data['theme']
181
181
182 user = _get_user(request)
182 user = _get_user(request)
183 user.save_setting('theme', selected_theme)
183 user.save_setting('theme', selected_theme)
184
184
185 return redirect(settings)
185 return redirect(settings)
186 else:
186 else:
187 selected_theme = _get_theme(request)
187 selected_theme = _get_theme(request)
188 form = SettingsForm(initial={'theme': selected_theme})
188 form = SettingsForm(initial={'theme': selected_theme})
189 context['form'] = form
189 context['form'] = form
190
190
191 return render(request, 'boards/settings.html', context)
191 return render(request, 'boards/settings.html', context)
192
192
193
193
194 def all_tags(request):
194 def all_tags(request):
195 """All tags list"""
195 """All tags list"""
196
196
197 context = _init_default_context(request)
197 context = _init_default_context(request)
198 context['all_tags'] = Tag.objects.get_not_empty_tags()
198 context['all_tags'] = Tag.objects.get_not_empty_tags()
199
199
200 return render(request, 'boards/tags.html', context)
200 return render(request, 'boards/tags.html', context)
201
201
202
202
203 def jump_to_post(request, post_id):
203 def jump_to_post(request, post_id):
204 """Determine thread in which the requested post is and open it's page"""
204 """Determine thread in which the requested post is and open it's page"""
205
205
206 post = get_object_or_404(Post, id=post_id)
206 post = get_object_or_404(Post, id=post_id)
207
207
208 if boards.models.NO_PARENT == post.parent:
208 if boards.models.NO_PARENT == post.parent:
209 return redirect(thread, post_id=post.id)
209 return redirect(thread, post_id=post.id)
210 else:
210 else:
211 parent_thread = get_object_or_404(Post, id=post.parent)
211 parent_thread = get_object_or_404(Post, id=post.parent)
212 return redirect(reverse(thread, kwargs={'post_id': parent_thread.id})
212 return redirect(reverse(thread, kwargs={'post_id': parent_thread.id})
213 + '#' + str(post.id))
213 + '#' + str(post.id))
214
214
215
215
216 def authors(request):
216 def authors(request):
217 context = _init_default_context(request)
217 context = _init_default_context(request)
218 context['authors'] = boards.authors.authors
218 context['authors'] = boards.authors.authors
219
219
220 return render(request, 'boards/authors.html', context)
220 return render(request, 'boards/authors.html', context)
221
221
222
222
223 def delete(request, post_id):
223 def delete(request, post_id):
224 user = _get_user(request)
224 user = _get_user(request)
225 post = get_object_or_404(Post, id=post_id)
225 post = get_object_or_404(Post, id=post_id)
226
226
227 if user.is_moderator():
227 if user.is_moderator():
228 # TODO Show confirmation page before deletion
228 Post.objects.delete_post(post)
229 Post.objects.delete_post(post)
229
230
230 if NO_PARENT == post.parent:
231 if NO_PARENT == post.parent:
231 return redirect(index)
232 return redirect(index)
232 else:
233 else:
233 return redirect(thread, post_id=post.parent)
234 return redirect(thread, post_id=post.parent)
234
235
235
236
236 def you_are_banned(request):
237 def you_are_banned(request):
237 context = _init_default_context(request)
238 context = _init_default_context(request)
238 return render(request, 'boards/banned.html', context)
239 return render(request, 'boards/banned.html', context)
239
240
240
241
241 def page_404(request):
242 def page_404(request):
242 context = _init_default_context(request)
243 context = _init_default_context(request)
243 return render(request, 'boards/404.html', context)
244 return render(request, 'boards/404.html', context)
244
245
245
246
246 def tag_subscribe(request, tag_name):
247 def tag_subscribe(request, tag_name):
247 user = _get_user(request)
248 user = _get_user(request)
248 tag = get_object_or_404(Tag, name=tag_name)
249 tag = get_object_or_404(Tag, name=tag_name)
249
250
250 if not tag in user.fav_tags.all():
251 if not tag in user.fav_tags.all():
251 user.fav_tags.add(tag)
252 user.fav_tags.add(tag)
252
253
253 return redirect(all_tags)
254 return redirect(all_tags)
254
255
255
256
256 def tag_unsubscribe(request, tag_name):
257 def tag_unsubscribe(request, tag_name):
257 user = _get_user(request)
258 user = _get_user(request)
258 tag = get_object_or_404(Tag, name=tag_name)
259 tag = get_object_or_404(Tag, name=tag_name)
259
260
260 if tag in user.fav_tags.all():
261 if tag in user.fav_tags.all():
261 user.fav_tags.remove(tag)
262 user.fav_tags.remove(tag)
262
263
263 return redirect(all_tags)
264 return redirect(all_tags)
264
265
265
266
266 def _get_theme(request):
267 def _get_theme(request, user=None):
267 """Get user's CSS theme"""
268 """Get user's CSS theme"""
268
269
269 user = _get_user(request)
270 if not user:
271 user = _get_user(request)
270 theme = user.get_setting('theme')
272 theme = user.get_setting('theme')
271 if not theme:
273 if not theme:
272 theme = neboard.settings.DEFAULT_THEME
274 theme = neboard.settings.DEFAULT_THEME
273
275
274 return theme
276 return theme
275
277
276
278
277 def _get_client_ip(request):
279 def _get_client_ip(request):
278 x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
280 x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
279 if x_forwarded_for:
281 if x_forwarded_for:
280 ip = x_forwarded_for.split(',')[-1].strip()
282 ip = x_forwarded_for.split(',')[-1].strip()
281 else:
283 else:
282 ip = request.META.get('REMOTE_ADDR')
284 ip = request.META.get('REMOTE_ADDR')
283 return ip
285 return ip
284
286
285
287
286 def _init_default_context(request):
288 def _init_default_context(request):
287 """Create context with default values that are used in most views"""
289 """Create context with default values that are used in most views"""
288
290
289 context = RequestContext(request)
291 context = RequestContext(request)
290 context['user'] = _get_user(request)
292
291 context['tags'] = _get_user(request).fav_tags.all()
293 user = _get_user(request)
292 context['theme'] = _get_theme(request)
294 context['user'] = user
295 context['tags'] = sorted(user.fav_tags.all(), key=lambda tag: tag.name)
296 context['theme'] = _get_theme(request, user)
293
297
294 return context
298 return context
295
299
296
300
297 def _get_user(request):
301 def _get_user(request):
298 """Get current user from the session"""
302 """Get current user from the session"""
299
303
300 session = request.session
304 session = request.session
301 if not 'user_id' in session:
305 if not 'user_id' in session:
302 request.session.save()
306 request.session.save()
303
307
304 md5 = hashlib.md5()
308 md5 = hashlib.md5()
305 md5.update(session.session_key)
309 md5.update(session.session_key)
306 new_id = md5.hexdigest()
310 new_id = md5.hexdigest()
307
311
308 time_now = timezone.now()
312 time_now = timezone.now()
309 user = User.objects.create(user_id=new_id, rank=RANK_USER,
313 user = User.objects.create(user_id=new_id, rank=RANK_USER,
310 registration_time=time_now,
314 registration_time=time_now,
311 last_access_time=time_now)
315 last_access_time=time_now)
312
316
313 session['user_id'] = user.id
317 session['user_id'] = user.id
314 else:
318 else:
315 user = User.objects.get(id=session['user_id'])
319 user = User.objects.get(id=session['user_id'])
320 user.last_access_time = timezone.now()
316 user.save()
321 user.save()
317
322
318 user.last_access_time = timezone.now()
319
320 return user
323 return user
General Comments 0
You need to be logged in to leave comments. Login now