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