##// END OF EJS Templates
Merged in the 1.6 version
neko259 -
r513:4f928387 merge 1.6 default
parent child Browse files
Show More
@@ -0,0 +1,85 b''
1 # -*- coding: utf-8 -*-
2 from south.utils import datetime_utils as 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 field 'Thread.archived'
12 db.add_column(u'boards_thread', 'archived',
13 self.gf('django.db.models.fields.BooleanField')(default=0),
14 keep_default=False)
15
16
17 def backwards(self, orm):
18 # Deleting field 'Thread.archived'
19 db.delete_column(u'boards_thread', 'archived')
20
21
22 models = {
23 'boards.ban': {
24 'Meta': {'object_name': 'Ban'},
25 'can_read': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
26 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
27 'ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}),
28 'reason': ('django.db.models.fields.CharField', [], {'default': "'Auto'", 'max_length': '200'})
29 },
30 'boards.post': {
31 'Meta': {'object_name': 'Post'},
32 '_text_rendered': ('django.db.models.fields.TextField', [], {}),
33 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
34 'image': ('boards.thumbs.ImageWithThumbsField', [], {'max_length': '100', 'blank': 'True'}),
35 'image_height': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
36 'image_pre_height': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
37 'image_pre_width': ('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 'poster_ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}),
41 'poster_user_agent': ('django.db.models.fields.TextField', [], {}),
42 'pub_time': ('django.db.models.fields.DateTimeField', [], {}),
43 'referenced_posts': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'rfp+'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['boards.Post']"}),
44 'text': ('markupfield.fields.MarkupField', [], {'rendered_field': 'True'}),
45 'text_markup_type': ('django.db.models.fields.CharField', [], {'default': "'markdown'", 'max_length': '30'}),
46 'thread': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['boards.Post']", 'null': 'True'}),
47 'thread_new': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['boards.Thread']", 'null': 'True'}),
48 'title': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
49 'user': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['boards.User']", 'null': 'True'})
50 },
51 'boards.setting': {
52 'Meta': {'object_name': 'Setting'},
53 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
54 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
55 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['boards.User']"}),
56 'value': ('django.db.models.fields.CharField', [], {'max_length': '50'})
57 },
58 'boards.tag': {
59 'Meta': {'object_name': 'Tag'},
60 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
61 'linked': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['boards.Tag']", 'null': 'True', 'blank': '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': "orm['boards.Thread']"})
64 },
65 'boards.thread': {
66 'Meta': {'object_name': 'Thread'},
67 'archived': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
68 'bump_time': ('django.db.models.fields.DateTimeField', [], {}),
69 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
70 'last_edit_time': ('django.db.models.fields.DateTimeField', [], {}),
71 'replies': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'tre+'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['boards.Post']"}),
72 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['boards.Tag']", 'symmetrical': 'False'})
73 },
74 'boards.user': {
75 'Meta': {'object_name': 'User'},
76 'fav_tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['boards.Tag']", 'null': 'True', 'blank': 'True'}),
77 'fav_threads': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['boards.Post']"}),
78 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
79 'rank': ('django.db.models.fields.IntegerField', [], {}),
80 'registration_time': ('django.db.models.fields.DateTimeField', [], {}),
81 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '50'})
82 }
83 }
84
85 complete_apps = ['boards'] No newline at end of file
@@ -0,0 +1,25 b''
1 # INTRO #
2
3 The API is provided to query the data from a neaboard server by any client
4 application.
5
6 Tha data is returned in the json format and got by an http query.
7
8 # METHODS #
9
10 /api/threads/N/?offset=M&tag=O
11
12 Get a thread list. You will get N threads (required parameter) starting from
13 Mth one (optional parameter, default is 0) with the tag O (optional parameter,
14 threads with any tags are shown by default).
15
16 /api/tags/
17
18 Get all active tag list. Active tag is a tag that has at least 1 active thread
19 associated with it.
20
21 /api/thread/N/
22
23 Get all Nth thread post. N is an opening post ID for the thread.
24
25 In case of incorrect request you can get http error 404.
1 NO CONTENT: modified file, binary diff hidden
NO CONTENT: modified file, binary diff hidden
@@ -7,7 +7,7 b' msgid ""'
7 msgstr ""
7 msgstr ""
8 "Project-Id-Version: PACKAGE VERSION\n"
8 "Project-Id-Version: PACKAGE VERSION\n"
9 "Report-Msgid-Bugs-To: \n"
9 "Report-Msgid-Bugs-To: \n"
10 "POT-Creation-Date: 2013-12-24 20:39+0200\n"
10 "POT-Creation-Date: 2014-01-06 23:43+0200\n"
11 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
11 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
12 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
12 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
13 "Language-Team: LANGUAGE <LL@li.org>\n"
13 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -74,8 +74,7 b' msgstr "\xd0\xa2\xd0\xb5\xd0\xba\xd1\x81\xd1\x82 \xd0\xb8\xd0\xbb\xd0\xb8 \xd0\xba\xd0\xb0\xd1\x80\xd1\x82\xd0\xb8\xd0\xbd\xd0\xba\xd0\xb0 \xd0\xb4\xd0\xbe\xd0\xbb\xd0\xb6\xd0\xbd\xd1\x8b \xd0\xb1\xd1\x8b\xd1\x82\xd1\x8c \xd0\xb2\xd0\xb2\xd0\xb5\xd0\xb4\xd0\xb5\xd0\xbd\xd1\x8b."'
74 msgid "Wait %s seconds after last posting"
74 msgid "Wait %s seconds after last posting"
75 msgstr "Подождите %s секунд после последнего постинга"
75 msgstr "Подождите %s секунд после последнего постинга"
76
76
77 #: forms.py:163 templates/boards/post.html:61 templates/boards/tags.html:6
77 #: forms.py:163 templates/boards/tags.html:6 templates/boards/rss/post.html:10
78 #: templates/boards/rss/post.html:10
79 msgid "Tags"
78 msgid "Tags"
80 msgstr "Теги"
79 msgstr "Теги"
81
80
@@ -112,6 +111,52 b' msgstr "\xd0\x9d\xd0\xb5 \xd0\xbd\xd0\xb0\xd0\xb9\xd0\xb4\xd0\xb5\xd0\xbd\xd0\xbe"'
112 msgid "This page does not exist"
111 msgid "This page does not exist"
113 msgstr "Этой страницы не существует"
112 msgstr "Этой страницы не существует"
114
113
114 #: templates/boards/archive.html:45 templates/boards/posting_general.html:64
115 msgid "Previous page"
116 msgstr "Предыдущая страница"
117
118 #: templates/boards/archive.html:75
119 msgid "Open"
120 msgstr "Открыть"
121
122 #: templates/boards/archive.html:81 templates/boards/post.html:37
123 #: templates/boards/posting_general.html:103 templates/boards/thread.html:69
124 msgid "Delete"
125 msgstr "Удалить"
126
127 #: templates/boards/archive.html:85 templates/boards/post.html:40
128 #: templates/boards/posting_general.html:107 templates/boards/thread.html:72
129 msgid "Ban IP"
130 msgstr "Заблокировать IP"
131
132 #: templates/boards/archive.html:94 templates/boards/post.html:53
133 #: templates/boards/posting_general.html:116
134 #: templates/boards/posting_general.html:180 templates/boards/thread.html:81
135 msgid "Replies"
136 msgstr "Ответы"
137
138 #: templates/boards/archive.html:103 templates/boards/posting_general.html:125
139 #: templates/boards/thread.html:138 templates/boards/thread_gallery.html:58
140 msgid "images"
141 msgstr "изображений"
142
143 #: templates/boards/archive.html:104 templates/boards/thread.html:137
144 #: templates/boards/thread_gallery.html:57
145 msgid "replies"
146 msgstr "ответов"
147
148 #: templates/boards/archive.html:129 templates/boards/posting_general.html:203
149 msgid "Next page"
150 msgstr "Следующая страница"
151
152 #: templates/boards/archive.html:134 templates/boards/posting_general.html:208
153 msgid "No threads exist. Create the first one!"
154 msgstr "Нет тем. Создайте первую!"
155
156 #: templates/boards/archive.html:143 templates/boards/posting_general.html:235
157 msgid "Pages:"
158 msgstr "Страницы: "
159
115 #: templates/boards/authors.html:6 templates/boards/authors.html.py:12
160 #: templates/boards/authors.html:6 templates/boards/authors.html.py:12
116 msgid "Authors"
161 msgid "Authors"
117 msgstr "Авторы"
162 msgstr "Авторы"
@@ -149,12 +194,16 b' msgstr "\xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8"'
149 msgid "Login"
194 msgid "Login"
150 msgstr "Вход"
195 msgstr "Вход"
151
196
152 #: templates/boards/base.html:52
197 #: templates/boards/base.html:51
198 msgid "Archive"
199 msgstr "Архив"
200
201 #: templates/boards/base.html:53
153 #, python-format
202 #, python-format
154 msgid "Speed: %(ppd)s posts per day"
203 msgid "Speed: %(ppd)s posts per day"
155 msgstr "Скорость: %(ppd)s сообщений в день"
204 msgstr "Скорость: %(ppd)s сообщений в день"
156
205
157 #: templates/boards/base.html:54
206 #: templates/boards/base.html:55
158 msgid "Up"
207 msgid "Up"
159 msgstr "Вверх"
208 msgstr "Вверх"
160
209
@@ -166,52 +215,20 b' msgstr "ID \xd0\xbf\xd0\xbe\xd0\xbb\xd1\x8c\xd0\xb7\xd0\xbe\xd0\xb2\xd0\xb0\xd1\x82\xd0\xb5\xd0\xbb\xd1\x8f"'
166 msgid "Insert your user id above"
215 msgid "Insert your user id above"
167 msgstr "Вставьте свой ID пользователя выше"
216 msgstr "Вставьте свой ID пользователя выше"
168
217
169 #: templates/boards/post.html:35 templates/boards/posting_general.html:103
170 #: templates/boards/thread.html:68
171 msgid "Delete"
172 msgstr "Удалить"
173
174 #: templates/boards/post.html:38 templates/boards/posting_general.html:107
175 #: templates/boards/thread.html:71
176 msgid "Ban IP"
177 msgstr "Заблокировать IP"
178
179 #: templates/boards/post.html:51 templates/boards/posting_general.html:116
180 #: templates/boards/posting_general.html:180 templates/boards/thread.html:80
181 msgid "Replies"
182 msgstr "Ответы"
183
184 #: templates/boards/posting_general.html:64
185 msgid "Previous page"
186 msgstr "Предыдущая страница"
187
188 #: templates/boards/posting_general.html:97
218 #: templates/boards/posting_general.html:97
189 msgid "Reply"
219 msgid "Reply"
190 msgstr "Ответ"
220 msgstr "Ответ"
191
221
192 #: templates/boards/posting_general.html:125 templates/boards/thread.html:130
193 #: templates/boards/thread_gallery.html:52
194 msgid "images"
195 msgstr "изображений"
196
197 #: templates/boards/posting_general.html:142
222 #: templates/boards/posting_general.html:142
198 #, python-format
223 #, python-format
199 msgid "Skipped %(count)s replies. Open thread to see all replies."
224 msgid "Skipped %(count)s replies. Open thread to see all replies."
200 msgstr "Пропущено %(count)s ответов. Откройте тред, чтобы увидеть все ответы."
225 msgstr "Пропущено %(count)s ответов. Откройте тред, чтобы увидеть все ответы."
201
226
202 #: templates/boards/posting_general.html:203
203 msgid "Next page"
204 msgstr "Следующая страница"
205
206 #: templates/boards/posting_general.html:208
207 msgid "No threads exist. Create the first one!"
208 msgstr "Нет тем. Создайте первую!"
209
210 #: templates/boards/posting_general.html:214
227 #: templates/boards/posting_general.html:214
211 msgid "Create new thread"
228 msgid "Create new thread"
212 msgstr "Создать новую тему"
229 msgstr "Создать новую тему"
213
230
214 #: templates/boards/posting_general.html:218 templates/boards/thread.html:112
231 #: templates/boards/posting_general.html:218 templates/boards/thread.html:115
215 msgid "Post"
232 msgid "Post"
216 msgstr "Отправить"
233 msgstr "Отправить"
217
234
@@ -220,14 +237,10 b' msgid "Tags must be delimited by spaces.'
220 msgstr ""
237 msgstr ""
221 "Теги должны быть разделены пробелами. Текст или изображение обязательны."
238 "Теги должны быть разделены пробелами. Текст или изображение обязательны."
222
239
223 #: templates/boards/posting_general.html:225 templates/boards/thread.html:116
240 #: templates/boards/posting_general.html:225 templates/boards/thread.html:119
224 msgid "Text syntax"
241 msgid "Text syntax"
225 msgstr "Синтаксис текста"
242 msgstr "Синтаксис текста"
226
243
227 #: templates/boards/posting_general.html:235
228 msgid "Pages:"
229 msgstr "Страницы: "
230
231 #: templates/boards/settings.html:14
244 #: templates/boards/settings.html:14
232 msgid "User:"
245 msgid "User:"
233 msgstr "Пользователь:"
246 msgstr "Пользователь:"
@@ -260,27 +273,23 b' msgstr "\xd1\x82\xd0\xb5\xd0\xbc"'
260 msgid "No tags found."
273 msgid "No tags found."
261 msgstr "Теги не найдены."
274 msgstr "Теги не найдены."
262
275
263 #: templates/boards/thread.html:22 templates/boards/thread_gallery.html:20
276 #: templates/boards/thread.html:19 templates/boards/thread_gallery.html:20
264 msgid "Normal mode"
277 msgid "Normal mode"
265 msgstr "Нормальный режим"
278 msgstr "Нормальный режим"
266
279
267 #: templates/boards/thread.html:23 templates/boards/thread_gallery.html:21
280 #: templates/boards/thread.html:20 templates/boards/thread_gallery.html:21
268 msgid "Gallery mode"
281 msgid "Gallery mode"
269 msgstr "Режим галереи"
282 msgstr "Режим галереи"
270
283
271 #: templates/boards/thread.html:31
284 #: templates/boards/thread.html:28
272 msgid "posts to bumplimit"
285 msgid "posts to bumplimit"
273 msgstr "сообщений до бамплимита"
286 msgstr "сообщений до бамплимита"
274
287
275 #: templates/boards/thread.html:106
288 #: templates/boards/thread.html:109
276 msgid "Reply to thread"
289 msgid "Reply to thread"
277 msgstr "Ответить в тему"
290 msgstr "Ответить в тему"
278
291
279 #: templates/boards/thread.html:129 templates/boards/thread_gallery.html:51
292 #: templates/boards/thread.html:139 templates/boards/thread_gallery.html:59
280 msgid "replies"
281 msgstr "ответов"
282
283 #: templates/boards/thread.html:131 templates/boards/thread_gallery.html:53
284 msgid "Last update: "
293 msgid "Last update: "
285 msgstr "Последнее обновление: "
294 msgstr "Последнее обновление: "
286
295
@@ -10,7 +10,7 b' import boards'
10 AUTOLINK_PATTERN = r'(https?://\S+)'
10 AUTOLINK_PATTERN = r'(https?://\S+)'
11 QUOTE_PATTERN = r'^(?<!>)(>[^>].+)$'
11 QUOTE_PATTERN = r'^(?<!>)(>[^>].+)$'
12 REFLINK_PATTERN = r'((>>)(\d+))'
12 REFLINK_PATTERN = r'((>>)(\d+))'
13 SPOILER_PATTERN = r'%%(.+)%%'
13 SPOILER_PATTERN = r'%%([^(%%)]+)%%'
14 COMMENT_PATTERN = r'^(//(.+))'
14 COMMENT_PATTERN = r'^(//(.+))'
15 STRIKETHROUGH_PATTERN = r'~(.+)~'
15 STRIKETHROUGH_PATTERN = r'~(.+)~'
16
16
@@ -6,6 +6,7 b' import time'
6 import math
6 import math
7 import re
7 import re
8 from django.core.cache import cache
8 from django.core.cache import cache
9 from django.core.paginator import Paginator
9
10
10 from django.db import models
11 from django.db import models
11 from django.http import Http404
12 from django.http import Http404
@@ -15,6 +16,8 b' from markupfield.fields import MarkupFie'
15 from neboard import settings
16 from neboard import settings
16 from boards import thumbs
17 from boards import thumbs
17
18
19 MAX_TITLE_LENGTH = 50
20
18 APP_LABEL_BOARDS = 'boards'
21 APP_LABEL_BOARDS = 'boards'
19
22
20 CACHE_KEY_PPD = 'ppd'
23 CACHE_KEY_PPD = 'ppd'
@@ -64,7 +67,8 b' class PostManager(models.Manager):'
64 thread_new=thread,
67 thread_new=thread,
65 image=image,
68 image=image,
66 poster_ip=ip,
69 poster_ip=ip,
67 poster_user_agent=UNKNOWN_UA, # TODO Get UA at last!
70 poster_user_agent=UNKNOWN_UA, # TODO Get UA at
71 # last!
68 last_edit_time=posting_time,
72 last_edit_time=posting_time,
69 user=user)
73 user=user)
70
74
@@ -111,7 +115,7 b' class PostManager(models.Manager):'
111
115
112 # TODO Move this method to thread manager
116 # TODO Move this method to thread manager
113 def get_threads(self, tag=None, page=ALL_PAGES,
117 def get_threads(self, tag=None, page=ALL_PAGES,
114 order_by='-bump_time'):
118 order_by='-bump_time', archived=False):
115 if tag:
119 if tag:
116 threads = tag.threads
120 threads = tag.threads
117
121
@@ -120,39 +124,21 b' class PostManager(models.Manager):'
120 else:
124 else:
121 threads = Thread.objects.all()
125 threads = Thread.objects.all()
122
126
123 threads = threads.order_by(order_by)
127 threads = threads.filter(archived=archived).order_by(order_by)
124
128
125 if page != ALL_PAGES:
129 if page != ALL_PAGES:
126 thread_count = threads.count()
130 threads = Paginator(threads, settings.THREADS_PER_PAGE).page(
127
131 page).object_list
128 if page < self._get_page_count(thread_count):
129 start_thread = page * settings.THREADS_PER_PAGE
130 end_thread = min(start_thread + settings.THREADS_PER_PAGE,
131 thread_count)
132 threads = threads[start_thread:end_thread]
133
132
134 return threads
133 return threads
135
134
136 # TODO Move this method to thread manager
135 # TODO Move this method to thread manager
137 def get_thread_page_count(self, tag=None):
138 if tag:
139 threads = Thread.objects.filter(tags=tag)
140 else:
141 threads = Thread.objects.all()
142
143 return self._get_page_count(threads.count())
144
145 # TODO Move this method to thread manager
146 def _delete_old_threads(self):
136 def _delete_old_threads(self):
147 """
137 """
148 Preserves maximum thread count. If there are too many threads,
138 Preserves maximum thread count. If there are too many threads,
149 delete the old ones.
139 archive the old ones.
150 """
140 """
151
141
152 # TODO Move old threads to the archive instead of deleting them.
153 # Maybe make some 'old' field in the model to indicate the thread
154 # must not be shown and be able for replying.
155
156 threads = self.get_threads()
142 threads = self.get_threads()
157 thread_count = threads.count()
143 thread_count = threads.count()
158
144
@@ -160,7 +146,10 b' class PostManager(models.Manager):'
160 num_threads_to_delete = thread_count - settings.MAX_THREAD_COUNT
146 num_threads_to_delete = thread_count - settings.MAX_THREAD_COUNT
161 old_threads = threads[thread_count - num_threads_to_delete:]
147 old_threads = threads[thread_count - num_threads_to_delete:]
162
148
163 map(Thread.delete_with_posts, old_threads)
149 for thread in old_threads:
150 thread.archived = True
151 thread.last_edit_time = timezone.now()
152 thread.save()
164
153
165 def connect_replies(self, post):
154 def connect_replies(self, post):
166 """
155 """
@@ -176,13 +165,6 b' class PostManager(models.Manager):'
176 referenced_post.last_edit_time = post.pub_time
165 referenced_post.last_edit_time = post.pub_time
177 referenced_post.save()
166 referenced_post.save()
178
167
179 def _get_page_count(self, thread_count):
180 """
181 Get number of pages that will be needed for all threads
182 """
183
184 return int(math.ceil(thread_count / float(settings.THREADS_PER_PAGE)))
185
186 def get_posts_per_day(self):
168 def get_posts_per_day(self):
187 """
169 """
188 Get average count of posts per day for the last 7 days
170 Get average count of posts per day for the last 7 days
@@ -270,7 +252,7 b' class Post(models.Model):'
270 def get_title(self):
252 def get_title(self):
271 title = self.title
253 title = self.title
272 if len(title) == 0:
254 if len(title) == 0:
273 title = self.text.raw[:20]
255 title = self.text.rendered[:MAX_TITLE_LENGTH]
274
256
275 return title
257 return title
276
258
@@ -294,6 +276,7 b' class Thread(models.Model):'
294 last_edit_time = models.DateTimeField()
276 last_edit_time = models.DateTimeField()
295 replies = models.ManyToManyField('Post', symmetrical=False, null=True,
277 replies = models.ManyToManyField('Post', symmetrical=False, null=True,
296 blank=True, related_name='tre+')
278 blank=True, related_name='tre+')
279 archived = models.BooleanField(default=False)
297
280
298 def get_tags(self):
281 def get_tags(self):
299 """
282 """
@@ -321,6 +304,9 b' class Thread(models.Model):'
321 Check if the thread can be bumped by replying
304 Check if the thread can be bumped by replying
322 """
305 """
323
306
307 if self.archived:
308 return False
309
324 post_count = self.get_reply_count()
310 post_count = self.get_reply_count()
325
311
326 return post_count < settings.MAX_POSTS_PER_THREAD
312 return post_count < settings.MAX_POSTS_PER_THREAD
@@ -6,7 +6,10 b' from django.db.models import Count'
6
6
7 TAG_FONT_MULTIPLIER = 0.1
7 TAG_FONT_MULTIPLIER = 0.1
8 MAX_TAG_FONT = 10
8 MAX_TAG_FONT = 10
9 OPENING_POST_POPULARITY_WEIGHT = 2
9
10 OPENING_POST_POPULARITY = 0.5
11 ARCHIVE_POPULARITY = 0.01
12 REPLY_POPULARITY = 0.1
10
13
11
14
12 class TagManager(models.Manager):
15 class TagManager(models.Manager):
@@ -43,15 +46,19 b' class Tag(models.Model):'
43 def get_post_count(self):
46 def get_post_count(self):
44 return self.threads.count()
47 return self.threads.count()
45
48
46 # TODO Reenable this method after migration
49 def get_popularity(self):
47 # def get_popularity(self):
50 popularity = 0.0
48 # posts_with_tag = Thread.objects.get_threads(tag=self)
51
49 # reply_count = 0
52 for thread in self.threads.all():
50 # for post in posts_with_tag:
53 reply_count = thread.get_reply_count()
51 # reply_count += post.get_reply_count()
54
52 # reply_count += OPENING_POST_POPULARITY_WEIGHT
55 if thread.archived:
53 #
56 popularity += ARCHIVE_POPULARITY * reply_count
54 # return reply_count
57 else:
58 popularity += REPLY_POPULARITY * reply_count
59 popularity += OPENING_POST_POPULARITY
60
61 return popularity
55
62
56 def get_linked_tags(self):
63 def get_linked_tags(self):
57 tag_list = []
64 tag_list = []
@@ -75,10 +82,10 b' class Tag(models.Model):'
75 def get_font_value(self):
82 def get_font_value(self):
76 """Get tag font value to differ most popular tags in the list"""
83 """Get tag font value to differ most popular tags in the list"""
77
84
78 post_count = self.get_post_count()
85 popularity = self.get_popularity()
79 if post_count > MAX_TAG_FONT:
86 if popularity > MAX_TAG_FONT:
80 post_count = MAX_TAG_FONT
87 popularity = MAX_TAG_FONT
81
88
82 font_value = str(1 + (post_count - 1) * TAG_FONT_MULTIPLIER)
89 font_value = str(1 + (popularity - 1) * TAG_FONT_MULTIPLIER)
83
90
84 return font_value No newline at end of file
91 return font_value
@@ -43,7 +43,7 b' html {'
43 color: #fff380;
43 color: #fff380;
44 }
44 }
45
45
46 .post, .dead_post, #posts-table {
46 .post, .dead_post, .archive_post, #posts-table {
47 background: #333;
47 background: #333;
48 margin: 5px;
48 margin: 5px;
49 padding: 10px;
49 padding: 10px;
@@ -191,6 +191,10 b' blockquote {'
191 background-color: #442222;
191 background-color: #442222;
192 }
192 }
193
193
194 .archive_post {
195 background-color: #000;
196 }
197
194 .mark_btn {
198 .mark_btn {
195 border: 1px solid;
199 border: 1px solid;
196 min-width: 2ex;
200 min-width: 2ex;
@@ -381,3 +385,16 b' code {'
381 pre {
385 pre {
382 overflow: auto;
386 overflow: auto;
383 }
387 }
388
389 .img-full {
390 background: #222;
391 border: solid 1px white;
392 }
393
394 .tag_item {
395 display: inline-block;
396 background: #555;
397 border: 1px solid #ccc;
398 margin: 0.3ex;
399 padding: 0.2ex;
400 }
@@ -344,4 +344,11 b' input[type="submit"]:hover {'
344 .current_page, .current_mode {
344 .current_page, .current_mode {
345 border: solid 1px #000;
345 border: solid 1px #000;
346 padding: 2px;
346 padding: 2px;
347 } No newline at end of file
347 }
348
349 .tag_item {
350 display: inline-block;
351 border: 1px solid #ccc;
352 margin: 0.3ex;
353 padding: 0.2ex;
354 }
@@ -6,27 +6,23 b''
6 {% load static %}
6 {% load static %}
7
7
8 {% block head %}
8 {% block head %}
9 {% if tag %}
9 <title>Neboard - {% trans 'Archive' %}</title>
10 <title>Neboard - {{ tag.name }}</title>
11 {% else %}
12 <title>Neboard</title>
13 {% endif %}
14
10
15 {% if prev_page %}
11 {% if current_page.has_previous %}
16 <link rel="next" href="
12 <link rel="prev" href="
17 {% if tag %}
13 {% if tag %}
18 {% url "tag" tag_name=tag page=prev_page %}
14 {% url "tag" tag_name=tag page=current_page.previous_page_number %}
19 {% else %}
15 {% else %}
20 {% url "index" page=prev_page %}
16 {% url "index" page=current_page.previous_page_number %}
21 {% endif %}
17 {% endif %}
22 " />
18 " />
23 {% endif %}
19 {% endif %}
24 {% if next_page %}
20 {% if current_page.has_next %}
25 <link rel="next" href="
21 <link rel="next" href="
26 {% if tag %}
22 {% if tag %}
27 {% url "tag" tag_name=tag page=next_page %}
23 {% url "tag" tag_name=tag page=current_page.next_page_number %}
28 {% else %}
24 {% else %}
29 {% url "index" page=next_page %}
25 {% url "index" page=current_page.next_page_number %}
30 {% endif %}
26 {% endif %}
31 " />
27 " />
32 {% endif %}
28 {% endif %}
@@ -37,42 +33,17 b''
37
33
38 {% get_current_language as LANGUAGE_CODE %}
34 {% get_current_language as LANGUAGE_CODE %}
39
35
40 {% if tag %}
41 <div class="tag_info">
42 <h2>
43 {% if tag in user.fav_tags.all %}
44 <a href="{% url 'tag_unsubscribe' tag.name %}?next={{ request.path }}"
45 class="fav"></a>
46 {% else %}
47 <a href="{% url 'tag_subscribe' tag.name %}?next={{ request.path }}"
48 class="not_fav"></a>
49 {% endif %}
50 #{{ tag.name }}
51 </h2>
52 </div>
53 {% endif %}
54
55 {% if threads %}
36 {% if threads %}
56 {% if prev_page %}
37 {% if current_page.has_previous %}
57 <div class="page_link">
38 <div class="page_link">
58 <a href="
39 <a href="{% url "archive" page=current_page.previous_page_number %}">{% trans "Previous page" %}</a>
59 {% if tag %}
60 {% url "tag" tag_name=tag page=prev_page %}
61 {% else %}
62 {% url "index" page=prev_page %}
63 {% endif %}
64 ">{% trans "Previous page" %}</a>
65 </div>
40 </div>
66 {% endif %}
41 {% endif %}
67
42
68 {% for thread in threads %}
43 {% for thread in threads %}
69 {% cache 600 thread_short thread.id thread.thread.last_edit_time moderator LANGUAGE_CODE %}
44 {% cache 600 thread_short thread.id thread.thread.last_edit_time moderator LANGUAGE_CODE %}
70 <div class="thread">
45 <div class="thread">
71 {% if thread.bumpable %}
46 <div class="post archive_post" id="{{ thread.op.id }}">
72 <div class="post" id="{{ thread.op.id }}">
73 {% else %}
74 <div class="post dead_post" id="{{ thread.op.id }}">
75 {% endif %}
76 {% if thread.op.image %}
47 {% if thread.op.image %}
77 <div class="image">
48 <div class="image">
78 <a class="thumb"
49 <a class="thumb"
@@ -91,10 +62,10 b''
91 <span class="title">{{ thread.op.title }}</span>
62 <span class="title">{{ thread.op.title }}</span>
92 <a class="post_id" href="{% url 'thread' thread.op.id %}"
63 <a class="post_id" href="{% url 'thread' thread.op.id %}"
93 > ({{ thread.op.id }})</a>
64 > ({{ thread.op.id }})</a>
94 [{{ thread.op.pub_time }}]
65 [{{ thread.op.pub_time }}] — [{{ thread.thread.last_edit_time }}]
66
95 [<a class="link" href="
67 [<a class="link" href="
96 {% url 'thread' thread.op.id %}#form"
68 {% url 'thread' thread.op.id %}">{% trans "Open" %}</a>]
97 >{% trans "Reply" %}</a>]
98
69
99 {% if moderator %}
70 {% if moderator %}
100 <span class="moderator_info">
71 <span class="moderator_info">
@@ -122,7 +93,8 b''
122 {% endif %}
93 {% endif %}
123 </div>
94 </div>
124 <div class="metadata">
95 <div class="metadata">
125 {{ thread.thread.get_images_count }} {% trans 'images' %}.
96 {{ thread.thread.get_images_count }} {% trans 'images' %},
97 {{ thread.thread.get_reply_count }} {% trans 'replies' %}.
126 {% if thread.thread.tags %}
98 {% if thread.thread.tags %}
127 <span class="tags">
99 <span class="tags">
128 {% for tag in thread.thread.get_tags %}
100 {% for tag in thread.thread.get_tags %}
@@ -135,72 +107,13 b''
135 {% endif %}
107 {% endif %}
136 </div>
108 </div>
137 </div>
109 </div>
138 {% if thread.last_replies.exists %}
139 {% if thread.skipped_replies %}
140 <div class="skipped_replies">
141 <a href="{% url 'thread' thread.op.id %}">
142 {% blocktrans with count=thread.skipped_replies %}Skipped {{ count }} replies. Open thread to see all replies.{% endblocktrans %}
143 </a>
144 </div>
145 {% endif %}
146 <div class="last-replies">
147 {% for post in thread.last_replies %}
148 {% if thread.bumpable %}
149 <div class="post" id="{{ post.id }}">
150 {% else %}
151 <div class="post dead_post" id="{{ post.id }}">
152 {% endif %}
153 {% if post.image %}
154 <div class="image">
155 <a class="thumb"
156 href="{{ post.image.url }}"><img
157 src=" {{ post.image.url_200x150 }}"
158 alt="{{ post.id }}"
159 width="{{ post.image_pre_width }}"
160 height="{{ post.image_pre_height }}"
161 data-width="{{ post.image_width }}"
162 data-height="{{ post.image_height }}"/>
163 </a>
164 </div>
165 {% endif %}
166 <div class="message">
167 <div class="post-info">
168 <span class="title">{{ post.title }}</span>
169 <a class="post_id" href="
170 {% url 'thread' thread.op.id %}#{{ post.id }}">
171 ({{ post.id }})</a>
172 [{{ post.pub_time }}]
173 </div>
174 {% autoescape off %}
175 {{ post.text.rendered|truncatewords_html:50 }}
176 {% endautoescape %}
177 </div>
178 {% if post.is_referenced %}
179 <div class="refmap">
180 {% trans "Replies" %}:
181 {% for ref_post in post.get_sorted_referenced_posts %}
182 <a href="{% post_url ref_post.id %}">&gt;&gt;{{ ref_post.id }}</a
183 >{% if not forloop.last %},{% endif %}
184 {% endfor %}
185 </div>
186 {% endif %}
187 </div>
188 {% endfor %}
189 </div>
190 {% endif %}
191 </div>
110 </div>
192 {% endcache %}
111 {% endcache %}
193 {% endfor %}
112 {% endfor %}
194
113
195 {% if next_page %}
114 {% if current_page.has_next %}
196 <div class="page_link">
115 <div class="page_link">
197 <a href="
116 <a href="{% url "archive" page=current_page.next_page_number %}">{% trans "Next page" %}</a>
198 {% if tag %}
199 {% url "tag" tag_name=tag page=next_page %}
200 {% else %}
201 {% url "index" page=next_page %}
202 {% endif %}
203 ">{% trans "Next page" %}</a>
204 </div>
117 </div>
205 {% endif %}
118 {% endif %}
206 {% else %}
119 {% else %}
@@ -208,42 +121,20 b''
208 {% trans 'No threads exist. Create the first one!' %}</div>
121 {% trans 'No threads exist. Create the first one!' %}</div>
209 {% endif %}
122 {% endif %}
210
123
211 <div class="post-form-w">
212 <script src="{% static 'js/panel.js' %}"></script>
213 <div class="post-form">
214 <div class="form-title">{% trans "Create new thread" %}</div>
215 <form enctype="multipart/form-data" method="post">{% csrf_token %}
216 {{ form.as_div }}
217 <div class="form-submit">
218 <input type="submit" value="{% trans "Post" %}"/>
219 </div>
220 </form>
221 <div>
222 {% trans 'Tags must be delimited by spaces. Text or image is required.' %}
223 </div>
224 <div><a href="{% url "staticpage" name="help" %}">
225 {% trans 'Text syntax' %}</a></div>
226 </div>
227 </div>
228
229 {% endblock %}
124 {% endblock %}
230
125
231 {% block metapanel %}
126 {% block metapanel %}
232
127
233 <span class="metapanel">
128 <span class="metapanel">
234 <b><a href="{% url "authors" %}">Neboard</a> 1.5 Aker</b>
129 <b><a href="{% url "authors" %}">Neboard</a> 1.6 Amon</b>
235 {% trans "Pages:" %}[
130 {% trans "Pages:" %}[
236 {% for page in pages %}
131 {% for page in paginator.page_range %}
237 <a
132 <a
238 {% ifequal page current_page %}
133 {% ifequal page current_page.number %}
239 class="current_page"
134 class="current_page"
240 {% endifequal %}
135 {% endifequal %}
241 href="
136 href="
242 {% if tag %}
137 {% url "archive" page=page %}
243 {% url "tag" tag_name=tag page=page %}
244 {% else %}
245 {% url "index" page=page %}
246 {% endif %}
247 ">{{ page }}</a>
138 ">{{ page }}</a>
248 {% if not forloop.last %},{% endif %}
139 {% if not forloop.last %},{% endif %}
249 {% endfor %}
140 {% endfor %}
@@ -48,6 +48,7 b''
48 <div class="navigation_panel">
48 <div class="navigation_panel">
49 {% block metapanel %}{% endblock %}
49 {% block metapanel %}{% endblock %}
50 [<a href="{% url "login" %}">{% trans 'Login' %}</a>]
50 [<a href="{% url "login" %}">{% trans 'Login' %}</a>]
51 [<a href="{% url "archive" %}">{% trans 'Archive' %}</a>]
51 {% with ppd=posts_per_day|floatformat:2 %}
52 {% with ppd=posts_per_day|floatformat:2 %}
52 {% blocktrans %}Speed: {{ ppd }} posts per day{% endblocktrans %}
53 {% blocktrans %}Speed: {{ ppd }} posts per day{% endblocktrans %}
53 {% endwith %}
54 {% endwith %}
@@ -12,21 +12,21 b''
12 <title>Neboard</title>
12 <title>Neboard</title>
13 {% endif %}
13 {% endif %}
14
14
15 {% if prev_page %}
15 {% if current_page.has_previous %}
16 <link rel="next" href="
16 <link rel="prev" href="
17 {% if tag %}
17 {% if tag %}
18 {% url "tag" tag_name=tag page=prev_page %}
18 {% url "tag" tag_name=tag page=current_page.previous_page_number %}
19 {% else %}
19 {% else %}
20 {% url "index" page=prev_page %}
20 {% url "index" page=current_page.previous_page_number %}
21 {% endif %}
21 {% endif %}
22 " />
22 " />
23 {% endif %}
23 {% endif %}
24 {% if next_page %}
24 {% if current_page.has_next %}
25 <link rel="next" href="
25 <link rel="next" href="
26 {% if tag %}
26 {% if tag %}
27 {% url "tag" tag_name=tag page=next_page %}
27 {% url "tag" tag_name=tag page=current_page.next_page_number %}
28 {% else %}
28 {% else %}
29 {% url "index" page=next_page %}
29 {% url "index" page=current_page.next_page_number %}
30 {% endif %}
30 {% endif %}
31 " />
31 " />
32 {% endif %}
32 {% endif %}
@@ -53,13 +53,13 b''
53 {% endif %}
53 {% endif %}
54
54
55 {% if threads %}
55 {% if threads %}
56 {% if prev_page %}
56 {% if current_page.has_previous %}
57 <div class="page_link">
57 <div class="page_link">
58 <a href="
58 <a href="
59 {% if tag %}
59 {% if tag %}
60 {% url "tag" tag_name=tag page=prev_page %}
60 {% url "tag" tag_name=tag page=current_page.previous_page_number %}
61 {% else %}
61 {% else %}
62 {% url "index" page=prev_page %}
62 {% url "index" page=current_page.previous_page_number %}
63 {% endif %}
63 {% endif %}
64 ">{% trans "Previous page" %}</a>
64 ">{% trans "Previous page" %}</a>
65 </div>
65 </div>
@@ -69,9 +69,9 b''
69 {% cache 600 thread_short thread.id thread.thread.last_edit_time moderator LANGUAGE_CODE %}
69 {% cache 600 thread_short thread.id thread.thread.last_edit_time moderator LANGUAGE_CODE %}
70 <div class="thread">
70 <div class="thread">
71 {% if thread.bumpable %}
71 {% if thread.bumpable %}
72 <div class="post" id="{{ thread.op.id }}">
72 <div class="post" id="{{ thread.op.id }}">
73 {% else %}
73 {% else %}
74 <div class="post dead_post" id="{{ thread.op.id }}">
74 <div class="post dead_post" id="{{ thread.op.id }}">
75 {% endif %}
75 {% endif %}
76 {% if thread.op.image %}
76 {% if thread.op.image %}
77 <div class="image">
77 <div class="image">
@@ -192,13 +192,13 b''
192 {% endcache %}
192 {% endcache %}
193 {% endfor %}
193 {% endfor %}
194
194
195 {% if next_page %}
195 {% if current_page.has_next %}
196 <div class="page_link">
196 <div class="page_link">
197 <a href="
197 <a href="
198 {% if tag %}
198 {% if tag %}
199 {% url "tag" tag_name=tag page=next_page %}
199 {% url "tag" tag_name=tag page=current_page.next_page_number %}
200 {% else %}
200 {% else %}
201 {% url "index" page=next_page %}
201 {% url "index" page=current_page.next_page_number %}
202 {% endif %}
202 {% endif %}
203 ">{% trans "Next page" %}</a>
203 ">{% trans "Next page" %}</a>
204 </div>
204 </div>
@@ -231,11 +231,11 b''
231 {% block metapanel %}
231 {% block metapanel %}
232
232
233 <span class="metapanel">
233 <span class="metapanel">
234 <b><a href="{% url "authors" %}">Neboard</a> 1.5 Aker</b>
234 <b><a href="{% url "authors" %}">Neboard</a> 1.6 Amon</b>
235 {% trans "Pages:" %}[
235 {% trans "Pages:" %}[
236 {% for page in pages %}
236 {% for page in paginator.page_range %}
237 <a
237 <a
238 {% ifequal page current_page %}
238 {% ifequal page current_page.number %}
239 class="current_page"
239 class="current_page"
240 {% endifequal %}
240 {% endifequal %}
241 href="
241 href="
@@ -9,33 +9,34 b''
9 {% block content %}
9 {% block content %}
10
10
11 <div class="post">
11 <div class="post">
12 {% if all_tags %}
12 {% if all_tags %}
13 {% for tag in all_tags %}
13 {% for tag in all_tags %}
14 {% if tag in user.fav_tags.all %}
14 <div class="tag_item">
15 <a href="{% url 'tag_unsubscribe' tag.name %}?next={{ request.path }}"
15 {% if tag in user.fav_tags.all %}
16 class="fav"></a>
16 <a href="{% url 'tag_unsubscribe' tag.name %}?next={{ request.path }}"
17 {% else %}
17 class="fav"></a>
18 <a href="{% url 'tag_subscribe' tag.name %}?next={{ request.path }}"
18 {% else %}
19 class="not_fav"></a>
19 <a href="{% url 'tag_subscribe' tag.name %}?next={{ request.path }}"
20 {% endif %}
20 class="not_fav"></a>
21 <a class="tag" href="{% url 'tag' tag.name %}"
21 {% endif %}
22 style="font-size: {{ tag.get_font_value }}em">
22 <a class="tag" href="{% url 'tag' tag.name %}"
23 #{{ tag.name }}</a>
23 style="font-size: {{ tag.get_font_value }}em">
24 ({{ tag.get_post_count }} {% trans 'threads' %})
24 #{{ tag.name }}</a>
25 {% if tag.linked %}
25 ({{ tag.get_post_count }} {% trans 'threads' %})
26 ( +
26 {% if tag.linked %}
27 {% for linked_tag in tag.get_linked_tags %}
27 ( +
28 <a class="tag" href="{% url 'tag' linked_tag.name %}">
28 {% for linked_tag in tag.get_linked_tags %}
29 #{{ linked_tag.name }}
29 <a class="tag" href="{% url 'tag' linked_tag.name %}">
30 </a>
30 #{{ linked_tag.name }}
31 {% endfor %}
31 </a>
32 )
32 {% endfor %}
33 {% endif %}
33 )
34 <br />
34 {% endif %}
35 {% endfor %}
35 </div>
36 {% else %}
36 {% endfor %}
37 {% trans 'No tags found.' %}
37 {% else %}
38 {% endif %}
38 {% trans 'No tags found.' %}
39 {% endif %}
39 </div>
40 </div>
40
41
41 {% endblock %}
42 {% endblock %}
@@ -6,7 +6,7 b''
6 {% load board %}
6 {% load board %}
7
7
8 {% block head %}
8 {% block head %}
9 <title>Neboard - {{ thread.get_opening_post.get_title }}</title>
9 <title>Neboard - {{ thread.get_opening_post.get_title|striptags }}</title>
10 {% endblock %}
10 {% endblock %}
11
11
12 {% block content %}
12 {% block content %}
@@ -33,6 +33,8 b''
33 {% for post in posts %}
33 {% for post in posts %}
34 {% if bumpable %}
34 {% if bumpable %}
35 <div class="post" id="{{ post.id }}">
35 <div class="post" id="{{ post.id }}">
36 {% elif thread.archived %}
37 <div class="post archive_post" id="{{ post.id }}">
36 {% else %}
38 {% else %}
37 <div class="post dead_post" id="{{ post.id }}">
39 <div class="post dead_post" id="{{ post.id }}">
38 {% endif %}
40 {% endif %}
@@ -56,8 +58,10 b''
56 <a class="post_id" href="#{{ post.id }}">
58 <a class="post_id" href="#{{ post.id }}">
57 ({{ post.id }})</a>
59 ({{ post.id }})</a>
58 [{{ post.pub_time }}]
60 [{{ post.pub_time }}]
61 {% if not thread.archived %}
59 [<a href="#" onclick="javascript:addQuickReply('{{ post.id }}')
62 [<a href="#" onclick="javascript:addQuickReply('{{ post.id }}')
60 ; return false;">&gt;&gt;</a>]
63 ; return false;">&gt;&gt;</a>]
64 {% endif %}
61
65
62 {% if moderator %}
66 {% if moderator %}
63 <span class="moderator_info">
67 <span class="moderator_info">
@@ -98,6 +102,8 b''
98 </div>
102 </div>
99 {% endcache %}
103 {% endcache %}
100
104
105 {% if not thread.archived %}
106
101 <div class="post-form-w">
107 <div class="post-form-w">
102 <script src="{% static 'js/panel.js' %}"></script>
108 <script src="{% static 'js/panel.js' %}"></script>
103 <div class="form-title">{% trans "Reply to thread" %} #{{ thread.get_opening_post.id }}</div>
109 <div class="form-title">{% trans "Reply to thread" %} #{{ thread.get_opening_post.id }}</div>
@@ -114,7 +120,9 b''
114 </div>
120 </div>
115 </div>
121 </div>
116
122
117 <script src="{% static 'js/thread_update.js' %}"></script>
123 <script src="{% static 'js/thread_update.js' %}"></script>
124 {% endif %}
125
118 <script src="{% static 'js/thread.js' %}"></script>
126 <script src="{% static 'js/thread.js' %}"></script>
119
127
120 {% endspaceless %}
128 {% endspaceless %}
@@ -6,7 +6,7 b''
6 {% load board %}
6 {% load board %}
7
7
8 {% block head %}
8 {% block head %}
9 <title>Neboard - {{ thread.get_opening_post.get_title }}</title>
9 <title>Neboard - {{ thread.get_opening_post.get_title|striptags }}</title>
10 {% endblock %}
10 {% endblock %}
11
11
12 {% block content %}
12 {% block content %}
@@ -115,7 +115,7 b' class PostTests(TestCase):'
115
115
116 all_threads = Post.objects.get_threads()
116 all_threads = Post.objects.get_threads()
117
117
118 posts_in_second_page = Post.objects.get_threads(page=1)
118 posts_in_second_page = Post.objects.get_threads(page=2)
119 first_post = posts_in_second_page[0]
119 first_post = posts_in_second_page[0]
120
120
121 self.assertEqual(all_threads[settings.THREADS_PER_PAGE].id,
121 self.assertEqual(all_threads[settings.THREADS_PER_PAGE].id,
@@ -230,4 +230,4 b' class ViewTest(TestCase):'
230 self.assertEqual(HTTP_CODE_OK, response.status_code, 'Index page not '
230 self.assertEqual(HTTP_CODE_OK, response.status_code, 'Index page not '
231 'opened')
231 'opened')
232 self.assertEqual('boards/posting_general.html', response.templates[0]
232 self.assertEqual('boards/posting_general.html', response.templates[0]
233 .name, 'Index page should open posting_general template') No newline at end of file
233 .name, 'Index page should open posting_general template')
@@ -1,7 +1,7 b''
1 from django.conf.urls import patterns, url, include
1 from django.conf.urls import patterns, url, include
2 from boards import views
2 from boards import views
3 from boards.rss import AllThreadsFeed, TagThreadsFeed, ThreadPostsFeed
3 from boards.rss import AllThreadsFeed, TagThreadsFeed, ThreadPostsFeed
4 from boards.views.api import api_get_threaddiff
4 from boards.views import api
5
5
6 js_info_dict = {
6 js_info_dict = {
7 'packages': ('boards',),
7 'packages': ('boards',),
@@ -14,6 +14,9 b" urlpatterns = patterns('',"
14 # /boards/page/
14 # /boards/page/
15 url(r'^page/(?P<page>\w+)/$', views.index, name='index'),
15 url(r'^page/(?P<page>\w+)/$', views.index, name='index'),
16
16
17 url(r'^archive/$', views.archive, name='archive'),
18 url(r'^archive/page/(?P<page>\w+)/$', views.archive, name='archive'),
19
17 # login page
20 # login page
18 url(r'^login/$', views.login, name='login'),
21 url(r'^login/$', views.login, name='login'),
19
22
@@ -54,7 +57,13 b" urlpatterns = patterns('',"
54 url(r'^jsi18n/$', 'boards.views.cached_js_catalog', js_info_dict, name='js_info_dict'),
57 url(r'^jsi18n/$', 'boards.views.cached_js_catalog', js_info_dict, name='js_info_dict'),
55
58
56 # API
59 # API
57 url(r'^api/post/(?P<post_id>\w+)/$', views.get_post, name="get_post"),
60 url(r'^api/post/(?P<post_id>\w+)/$', api.get_post, name="get_post"),
58 url(r'^api/diff_thread/(?P<thread_id>\w+)/(?P<last_update_time>\w+)/$',
61 url(r'^api/diff_thread/(?P<thread_id>\w+)/(?P<last_update_time>\w+)/$',
59 api_get_threaddiff, name="get_thread_diff"),
62 api.api_get_threaddiff, name="get_thread_diff"),
63 url(r'^api/threads/(?P<count>\w+)/$', api.api_get_threads,
64 name='get_threads'),
65 url(r'api/tags/$', api.api_get_tags, name='get_tags'),
66 url(r'api/thread/(?P<opening_post_id>\w+)/$', api.api_get_thread_posts,
67 name='get_thread'),
68
60 )
69 )
@@ -1,3 +1,10 b''
1 from datetime import datetime, timedelta
2
3 from django.db.models import Count
4
5
6 OLD_USER_AGE_DAYS = 90
7
1 __author__ = 'neko259'
8 __author__ = 'neko259'
2
9
3 import hashlib
10 import hashlib
@@ -15,13 +22,14 b' from django.utils import timezone'
15 from django.db import transaction
22 from django.db import transaction
16 from django.views.decorators.cache import cache_page
23 from django.views.decorators.cache import cache_page
17 from django.views.i18n import javascript_catalog
24 from django.views.i18n import javascript_catalog
25 from django.core.paginator import Paginator
18
26
19 from boards import forms
27 from boards import forms
20 import boards
28 import boards
21 from boards import utils
29 from boards import utils
22 from boards.forms import ThreadForm, PostForm, SettingsForm, PlainErrorList, \
30 from boards.forms import ThreadForm, PostForm, SettingsForm, PlainErrorList, \
23 ThreadCaptchaForm, PostCaptchaForm, LoginForm, ModeratorSettingsForm
31 ThreadCaptchaForm, PostCaptchaForm, LoginForm, ModeratorSettingsForm
24 from boards.models import Post, Tag, Ban, User
32 from boards.models import Post, Tag, Ban, User, Thread
25 from boards.models.post import SETTING_MODERATE, REGEX_REPLY
33 from boards.models.post import SETTING_MODERATE, REGEX_REPLY
26 from boards.models.user import RANK_USER
34 from boards.models.user import RANK_USER
27 from boards import authors
35 from boards import authors
@@ -33,8 +41,10 b" BAN_REASON_SPAM = 'Autoban: spam bot'"
33 MODE_GALLERY = 'gallery'
41 MODE_GALLERY = 'gallery'
34 MODE_NORMAL = 'normal'
42 MODE_NORMAL = 'normal'
35
43
44 DEFAULT_PAGE = 1
36
45
37 def index(request, page=0):
46
47 def index(request, page=DEFAULT_PAGE):
38 context = _init_default_context(request)
48 context = _init_default_context(request)
39
49
40 if utils.need_include_captcha(request):
50 if utils.need_include_captcha(request):
@@ -64,20 +74,36 b' def index(request, page=0):'
64 # TODO Make this generic for tag and threads list pages
74 # TODO Make this generic for tag and threads list pages
65 context['threads'] = None if len(threads) == 0 else threads
75 context['threads'] = None if len(threads) == 0 else threads
66 context['form'] = form
76 context['form'] = form
67 context['current_page'] = int(page)
68
77
69 page_count = Post.objects.get_thread_page_count()
78 paginator = Paginator(Thread.objects.filter(archived=False),
70 context['pages'] = range(page_count)
79 neboard.settings.THREADS_PER_PAGE)
71 page = int(page)
80 _get_page_context(paginator, context, page)
72 if page < page_count - 1:
73 context['next_page'] = str(page + 1)
74 if page > 0:
75 context['prev_page'] = str(page - 1)
76
81
77 return render(request, 'boards/posting_general.html',
82 return render(request, 'boards/posting_general.html',
78 context)
83 context)
79
84
80
85
86 def archive(request, page=DEFAULT_PAGE):
87 """
88 Get archived posts
89 """
90
91 context = _init_default_context(request)
92
93 threads = []
94 for thread_to_show in Post.objects.get_threads(page=int(page),
95 archived=True):
96 threads.append(_get_template_thread(thread_to_show))
97
98 context['threads'] = threads
99
100 paginator = Paginator(Thread.objects.filter(archived=True),
101 neboard.settings.THREADS_PER_PAGE)
102 _get_page_context(paginator, context, page)
103
104 return render(request, 'boards/archive.html', context)
105
106
81 @transaction.atomic
107 @transaction.atomic
82 def _new_post(request, form, opening_post=None):
108 def _new_post(request, form, opening_post=None):
83 """Add a new post (in thread or as a reply)."""
109 """Add a new post (in thread or as a reply)."""
@@ -129,10 +155,10 b' def _new_post(request, form, opening_pos'
129 return redirect(thread, post_id=thread_to_show)
155 return redirect(thread, post_id=thread_to_show)
130
156
131
157
132 def tag(request, tag_name, page=0):
158 def tag(request, tag_name, page=DEFAULT_PAGE):
133 """
159 """
134 Get all tag threads. Threads are split in pages, so some page is
160 Get all tag threads. Threads are split in pages, so some page is
135 requested. Default page is 0.
161 requested.
136 """
162 """
137
163
138 tag = get_object_or_404(Tag, name=tag_name)
164 tag = get_object_or_404(Tag, name=tag_name)
@@ -157,15 +183,10 b' def tag(request, tag_name, page=0):'
157 context = _init_default_context(request)
183 context = _init_default_context(request)
158 context['threads'] = None if len(threads) == 0 else threads
184 context['threads'] = None if len(threads) == 0 else threads
159 context['tag'] = tag
185 context['tag'] = tag
160 context['current_page'] = int(page)
161
186
162 page_count = Post.objects.get_thread_page_count(tag=tag)
187 paginator = Paginator(Post.objects.get_threads(tag=tag),
163 context['pages'] = range(page_count)
188 neboard.settings.THREADS_PER_PAGE)
164 page = int(page)
189 _get_page_context(paginator, context, page)
165 if page < page_count - 1:
166 context['next_page'] = str(page + 1)
167 if page > 0:
168 context['prev_page'] = str(page - 1)
169
190
170 context['form'] = form
191 context['form'] = form
171
192
@@ -184,7 +205,12 b' def thread(request, post_id, mode=MODE_N'
184 kwargs = {}
205 kwargs = {}
185
206
186 opening_post = get_object_or_404(Post, id=post_id)
207 opening_post = get_object_or_404(Post, id=post_id)
187 if request.method == 'POST':
208
209 # If this is not OP, don't show it as it is
210 if not opening_post.is_opening():
211 raise Http404
212
213 if request.method == 'POST' and not opening_post.thread_new.archived:
188 form = postFormClass(request.POST, request.FILES,
214 form = postFormClass(request.POST, request.FILES,
189 error_class=PlainErrorList, **kwargs)
215 error_class=PlainErrorList, **kwargs)
190 form.session = request.session
216 form.session = request.session
@@ -422,20 +448,6 b' def api_get_post(request, post_id):'
422 return HttpResponse(content=json)
448 return HttpResponse(content=json)
423
449
424
450
425 def get_post(request, post_id):
426 """Get the html of a post. Used for popups."""
427
428 post = get_object_or_404(Post, id=post_id)
429 thread = post.thread_new
430
431 context = RequestContext(request)
432 context["post"] = post
433 context["can_bump"] = thread.can_bump()
434 if "truncated" in request.GET:
435 context["truncated"] = True
436
437 return render(request, 'boards/post.html', context)
438
439 @cache_page(86400)
451 @cache_page(86400)
440 def cached_js_catalog(request, domain='djangojs', packages=None):
452 def cached_js_catalog(request, domain='djangojs', packages=None):
441 return javascript_catalog(request, domain, packages)
453 return javascript_catalog(request, domain, packages)
@@ -491,10 +503,16 b' def _get_user(request):'
491 md5.update(session.session_key)
503 md5.update(session.session_key)
492 new_id = md5.hexdigest()
504 new_id = md5.hexdigest()
493
505
506 while User.objects.filter(user_id=new_id).exists():
507 md5.update(str(timezone.now()))
508 new_id = md5.hexdigest()
509
494 time_now = timezone.now()
510 time_now = timezone.now()
495 user = User.objects.create(user_id=new_id, rank=RANK_USER,
511 user = User.objects.create(user_id=new_id, rank=RANK_USER,
496 registration_time=time_now)
512 registration_time=time_now)
497
513
514 _delete_old_users()
515
498 session['user_id'] = user.id
516 session['user_id'] = user.id
499 else:
517 else:
500 user = User.objects.get(id=session['user_id'])
518 user = User.objects.get(id=session['user_id'])
@@ -562,3 +580,26 b' def _get_template_thread(thread_to_show)'
562 'last_replies': last_replies,
580 'last_replies': last_replies,
563 'skipped_replies': skipped_replies_count,
581 'skipped_replies': skipped_replies_count,
564 }
582 }
583
584
585 def _delete_old_users():
586 """
587 Delete users with no favorite tags and posted messages. These can be spam
588 bots or just old user accounts
589 """
590
591 old_registration_date = datetime.now().date() - timedelta(OLD_USER_AGE_DAYS)
592
593 for user in User.objects.annotate(tags_count=Count('fav_tags')).filter(
594 tags_count=0).filter(registration_time__lt=old_registration_date):
595 if not Post.objects.filter(user=user).exists():
596 user.delete()
597
598
599 def _get_page_context(paginator, context, page):
600 """
601 Get pagination context variables
602 """
603
604 context['paginator'] = paginator
605 context['current_page'] = paginator.page(int(page))
@@ -2,15 +2,20 b' from datetime import datetime'
2 import json
2 import json
3 from django.db import transaction
3 from django.db import transaction
4 from django.http import HttpResponse
4 from django.http import HttpResponse
5 from django.shortcuts import get_object_or_404
5 from django.shortcuts import get_object_or_404, render
6 from django.template import RequestContext
6 from django.utils import timezone
7 from django.utils import timezone
7 from boards.forms import ThreadForm, PlainErrorList
8 from boards.forms import ThreadForm, PlainErrorList
8 from boards.models import Post
9 from boards.models import Post, Thread, Tag
9 from boards.views import get_post, _datetime_to_epoch, _new_post, \
10 from boards.views import _datetime_to_epoch, _new_post, \
10 _ban_current_user
11 _ban_current_user
11
12
12 __author__ = 'neko259'
13 __author__ = 'neko259'
13
14
15 PARAMETER_TRUNCATED = 'truncated'
16 PARAMETER_TAG = 'tag'
17 PARAMETER_OFFSET = 'offset'
18
14
19
15 @transaction.atomic
20 @transaction.atomic
16 def api_get_threaddiff(request, thread_id, last_update_time):
21 def api_get_threaddiff(request, thread_id, last_update_time):
@@ -25,7 +30,7 b' def api_get_threaddiff(request, thread_i'
25 'added': [],
30 'added': [],
26 'updated': [],
31 'updated': [],
27 'last_update': None,
32 'last_update': None,
28 }
33 }
29 added_posts = Post.objects.filter(thread_new=thread,
34 added_posts = Post.objects.filter(thread_new=thread,
30 pub_time__gt=filter_time) \
35 pub_time__gt=filter_time) \
31 .order_by('pub_time')
36 .order_by('pub_time')
@@ -73,3 +78,108 b' def api_get_threaddiff(request, thread_i'
73 # }
78 # }
74 #
79 #
75 # return HttpResponse(content=json.dumps(response))
80 # return HttpResponse(content=json.dumps(response))
81
82
83 def get_post(request, post_id):
84 """
85 Get the html of a post. Used for popups. Post can be truncated if used
86 in threads list with 'truncated' get parameter.
87 """
88
89 post = get_object_or_404(Post, id=post_id)
90 thread = post.thread_new
91
92 context = RequestContext(request)
93 context['post'] = post
94 context['can_bump'] = thread.can_bump()
95 if PARAMETER_TRUNCATED in request.GET:
96 context[PARAMETER_TRUNCATED] = True
97
98 return render(request, 'boards/post.html', context)
99
100
101 # TODO Test this
102 def api_get_threads(request, count):
103 """
104 Get the JSON thread opening posts list.
105 Parameters that can be used for filtering:
106 tag, offset (from which thread to get results)
107 """
108
109 if PARAMETER_TAG in request.GET:
110 tag_name = request.GET[PARAMETER_TAG]
111 if tag_name is not None:
112 tag = get_object_or_404(Tag, name=tag_name)
113 threads = tag.threads.filter(archived=False)
114 else:
115 threads = Thread.objects.filter(archived=False)
116
117 if PARAMETER_OFFSET in request.GET:
118 offset = request.GET[PARAMETER_OFFSET]
119 offset = int(offset) if offset is not None else 0
120 else:
121 offset = 0
122
123 threads = threads.order_by('-bump_time')
124 threads = threads[offset:offset + int(count)]
125
126 opening_posts = []
127 for thread in threads:
128 opening_post = thread.get_opening_post()
129
130 # TODO Add pub time, tags, replies and images count
131 post_json = {
132 'id': opening_post.id,
133 'title': opening_post.title,
134 'text': opening_post.text.rendered,
135 }
136 if opening_post.image:
137 post_json['image'] = opening_post.image.url
138 post_json['image_preview'] = opening_post.image.url_200x150
139 opening_posts.append(post_json)
140
141 return HttpResponse(content=json.dumps(opening_posts))
142
143
144 # TODO Test this
145 def api_get_tags(request):
146 """
147 Get all tags or user tags.
148 """
149
150 # TODO Get favorite tags for the given user ID
151
152 tags = Tag.objects.get_not_empty_tags()
153 tag_names = []
154 for tag in tags:
155 tag_names.append(tag.name)
156
157 return HttpResponse(content=json.dumps(tag_names))
158
159
160 # TODO The result can be cached by the thread last update time
161 # TODO Test this
162 def api_get_thread_posts(request, opening_post_id):
163 """
164 Get the JSON array of thread posts
165 """
166
167 opening_post = get_object_or_404(Post, id=opening_post_id)
168 thread = opening_post.thread_new
169 posts = thread.get_replies()
170
171 json_post_list = []
172
173 for post in posts:
174 # TODO Add pub time and replies
175 post_json = {
176 'id': post.id,
177 'title': post.title,
178 'text': post.text.rendered,
179 }
180 if post.image:
181 post_json['image'] = post.image.url
182 post_json['image_preview'] = post.image.url_200x150
183 json_post_list.append(post_json)
184
185 return HttpResponse(content=json.dumps(json_post_list))
@@ -4,4 +4,13 b' styles.'
4 * Showing notification in page title when new posts are loaded into the open
4 * Showing notification in page title when new posts are loaded into the open
5 thread.
5 thread.
6 * Thread moderation fixes
6 * Thread moderation fixes
7 * Added new gallery with search links and image metadata No newline at end of file
7 * Added new gallery with search links and image metadata
8
9 # 1.6 Amon #
10 * Deleted threads are moved to archive instead of permanent delete
11 * User management fixes and optimizations
12 * Markdown fixes
13 * Pagination changes. Pages counter now starts from 1 instead of 0
14 * Added API for viewing threads and posts
15 * New tag popularity algorithm
16 * Tags list page changes. Now tags list is more like a tag cloud
@@ -34,8 +34,6 b' post or its part (delimited by N charact'
34 [NOT STARTED] Get thread graph image using pygraphviz
34 [NOT STARTED] Get thread graph image using pygraphviz
35 [NOT STARTED] Creating post via AJAX without reloading page
35 [NOT STARTED] Creating post via AJAX without reloading page
36 [NOT STARTED] Subscribing to tag via AJAX
36 [NOT STARTED] Subscribing to tag via AJAX
37 [NOT STARTED] Count posts by user not by current active posts, but by adding 1
38 on evety posting
39
37
40 = Bugs =
38 = Bugs =
41 [DONE] Fix bug with creating threads from tag view
39 [DONE] Fix bug with creating threads from tag view
General Comments 0
You need to be logged in to leave comments. Login now