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