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