##// END OF EJS Templates
Added an admin page for keypair. Renamed the public key to global id in the...
neko259 -
r797:7dc20073 decentral
parent child Browse files
Show More
@@ -1,31 +1,38 b''
1 from django.contrib import admin
1 from django.contrib import admin
2 from boards.models import Post, Tag, Ban, Thread
2 from boards.models import Post, Tag, Ban, Thread, KeyPair
3
3
4
4
5 class PostAdmin(admin.ModelAdmin):
5 class PostAdmin(admin.ModelAdmin):
6
6
7 list_display = ('id', 'title', 'text')
7 list_display = ('id', 'title', 'text')
8 list_filter = ('pub_time', 'thread_new')
8 list_filter = ('pub_time', 'thread_new')
9 search_fields = ('id', 'title', 'text')
9 search_fields = ('id', 'title', 'text')
10
10
11
11
12 class TagAdmin(admin.ModelAdmin):
12 class TagAdmin(admin.ModelAdmin):
13
13
14 list_display = ('name',)
14 list_display = ('name',)
15
15
16 class ThreadAdmin(admin.ModelAdmin):
16 class ThreadAdmin(admin.ModelAdmin):
17
17
18 def title(self, obj):
18 def title(self, obj):
19 return obj.get_opening_post().title
19 return obj.get_opening_post().title
20
20
21 def reply_count(self, obj):
21 def reply_count(self, obj):
22 return obj.get_reply_count()
22 return obj.get_reply_count()
23
23
24 list_display = ('id', 'title', 'reply_count', 'archived')
24 list_display = ('id', 'title', 'reply_count', 'archived')
25 list_filter = ('bump_time', 'archived')
25 list_filter = ('bump_time', 'archived')
26 search_fields = ('id', 'title')
26 search_fields = ('id', 'title')
27
27
28
29 class KeyPairAdmin(admin.ModelAdmin):
30 list_display = ('public_key', 'primary')
31 list_filter = ('primary',)
32 search_fields = ('public_key',)
33
28 admin.site.register(Post, PostAdmin)
34 admin.site.register(Post, PostAdmin)
29 admin.site.register(Tag, TagAdmin)
35 admin.site.register(Tag, TagAdmin)
30 admin.site.register(Ban)
36 admin.site.register(Ban)
31 admin.site.register(Thread, ThreadAdmin)
37 admin.site.register(Thread, ThreadAdmin)
38 admin.site.register(KeyPair, KeyPairAdmin)
@@ -1,354 +1,354 b''
1 from datetime import datetime, timedelta, date
1 from datetime import datetime, timedelta, date
2 from datetime import time as dtime
2 from datetime import time as dtime
3 import logging
3 import logging
4 import re
4 import re
5
5
6 from django.core.cache import cache
6 from django.core.cache import cache
7 from django.core.urlresolvers import reverse
7 from django.core.urlresolvers import reverse
8 from django.db import models, transaction
8 from django.db import models, transaction
9 from django.template.loader import render_to_string
9 from django.template.loader import render_to_string
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 boards.models import PostImage
13 from boards.models import PostImage
14 from boards.models.base import Viewable
14 from boards.models.base import Viewable
15 from boards.models.thread import Thread
15 from boards.models.thread import Thread
16
16
17
17
18 APP_LABEL_BOARDS = 'boards'
18 APP_LABEL_BOARDS = 'boards'
19
19
20 CACHE_KEY_PPD = 'ppd'
20 CACHE_KEY_PPD = 'ppd'
21 CACHE_KEY_POST_URL = 'post_url'
21 CACHE_KEY_POST_URL = 'post_url'
22
22
23 POSTS_PER_DAY_RANGE = 7
23 POSTS_PER_DAY_RANGE = 7
24
24
25 BAN_REASON_AUTO = 'Auto'
25 BAN_REASON_AUTO = 'Auto'
26
26
27 IMAGE_THUMB_SIZE = (200, 150)
27 IMAGE_THUMB_SIZE = (200, 150)
28
28
29 TITLE_MAX_LENGTH = 200
29 TITLE_MAX_LENGTH = 200
30
30
31 DEFAULT_MARKUP_TYPE = 'bbcode'
31 DEFAULT_MARKUP_TYPE = 'bbcode'
32
32
33 # TODO This should be removed
33 # TODO This should be removed
34 NO_IP = '0.0.0.0'
34 NO_IP = '0.0.0.0'
35
35
36 # TODO Real user agent should be saved instead of this
36 # TODO Real user agent should be saved instead of this
37 UNKNOWN_UA = ''
37 UNKNOWN_UA = ''
38
38
39 REGEX_REPLY = re.compile(r'\[post\](\d+)\[/post\]')
39 REGEX_REPLY = re.compile(r'\[post\](\d+)\[/post\]')
40
40
41 logger = logging.getLogger(__name__)
41 logger = logging.getLogger(__name__)
42
42
43
43
44 class PostManager(models.Manager):
44 class PostManager(models.Manager):
45 def create_post(self, title, text, image=None, thread=None, ip=NO_IP,
45 def create_post(self, title, text, image=None, thread=None, ip=NO_IP,
46 tags=None):
46 tags=None):
47 """
47 """
48 Creates new post
48 Creates new post
49 """
49 """
50
50
51 if not tags:
51 if not tags:
52 tags = []
52 tags = []
53
53
54 posting_time = timezone.now()
54 posting_time = timezone.now()
55 if not thread:
55 if not thread:
56 thread = Thread.objects.create(bump_time=posting_time,
56 thread = Thread.objects.create(bump_time=posting_time,
57 last_edit_time=posting_time)
57 last_edit_time=posting_time)
58 new_thread = True
58 new_thread = True
59 else:
59 else:
60 thread.bump()
60 thread.bump()
61 thread.last_edit_time = posting_time
61 thread.last_edit_time = posting_time
62 thread.save()
62 thread.save()
63 new_thread = False
63 new_thread = False
64
64
65 post = self.create(title=title,
65 post = self.create(title=title,
66 text=text,
66 text=text,
67 pub_time=posting_time,
67 pub_time=posting_time,
68 thread_new=thread,
68 thread_new=thread,
69 poster_ip=ip,
69 poster_ip=ip,
70 poster_user_agent=UNKNOWN_UA, # TODO Get UA at
70 poster_user_agent=UNKNOWN_UA, # TODO Get UA at
71 # last!
71 # last!
72 last_edit_time=posting_time)
72 last_edit_time=posting_time)
73
73
74 if image:
74 if image:
75 post_image = PostImage.objects.create(image=image)
75 post_image = PostImage.objects.create(image=image)
76 post.images.add(post_image)
76 post.images.add(post_image)
77 logger.info('Created image #%d for post #%d' % (post_image.id,
77 logger.info('Created image #%d for post #%d' % (post_image.id,
78 post.id))
78 post.id))
79
79
80 thread.replies.add(post)
80 thread.replies.add(post)
81 list(map(thread.add_tag, tags))
81 list(map(thread.add_tag, tags))
82
82
83 if new_thread:
83 if new_thread:
84 Thread.objects.process_oldest_threads()
84 Thread.objects.process_oldest_threads()
85 self.connect_replies(post)
85 self.connect_replies(post)
86
86
87 logger.info('Created post #%d with title %s' % (post.id,
87 logger.info('Created post #%d with title %s' % (post.id,
88 post.get_title()))
88 post.get_title()))
89
89
90 return post
90 return post
91
91
92 def delete_post(self, post):
92 def delete_post(self, post):
93 """
93 """
94 Deletes post and update or delete its thread
94 Deletes post and update or delete its thread
95 """
95 """
96
96
97 post_id = post.id
97 post_id = post.id
98
98
99 thread = post.get_thread()
99 thread = post.get_thread()
100
100
101 if post.is_opening():
101 if post.is_opening():
102 thread.delete()
102 thread.delete()
103 else:
103 else:
104 thread.last_edit_time = timezone.now()
104 thread.last_edit_time = timezone.now()
105 thread.save()
105 thread.save()
106
106
107 post.delete()
107 post.delete()
108
108
109 logger.info('Deleted post #%d (%s)' % (post_id, post.get_title()))
109 logger.info('Deleted post #%d (%s)' % (post_id, post.get_title()))
110
110
111 def delete_posts_by_ip(self, ip):
111 def delete_posts_by_ip(self, ip):
112 """
112 """
113 Deletes all posts of the author with same IP
113 Deletes all posts of the author with same IP
114 """
114 """
115
115
116 posts = self.filter(poster_ip=ip)
116 posts = self.filter(poster_ip=ip)
117 for post in posts:
117 for post in posts:
118 self.delete_post(post)
118 self.delete_post(post)
119
119
120 def connect_replies(self, post):
120 def connect_replies(self, post):
121 """
121 """
122 Connects replies to a post to show them as a reflink map
122 Connects replies to a post to show them as a reflink map
123 """
123 """
124
124
125 for reply_number in re.finditer(REGEX_REPLY, post.text.raw):
125 for reply_number in re.finditer(REGEX_REPLY, post.text.raw):
126 post_id = reply_number.group(1)
126 post_id = reply_number.group(1)
127 ref_post = self.filter(id=post_id)
127 ref_post = self.filter(id=post_id)
128 if ref_post.count() > 0:
128 if ref_post.count() > 0:
129 referenced_post = ref_post[0]
129 referenced_post = ref_post[0]
130 referenced_post.referenced_posts.add(post)
130 referenced_post.referenced_posts.add(post)
131 referenced_post.last_edit_time = post.pub_time
131 referenced_post.last_edit_time = post.pub_time
132 referenced_post.build_refmap()
132 referenced_post.build_refmap()
133 referenced_post.save(update_fields=['refmap', 'last_edit_time'])
133 referenced_post.save(update_fields=['refmap', 'last_edit_time'])
134
134
135 referenced_thread = referenced_post.get_thread()
135 referenced_thread = referenced_post.get_thread()
136 referenced_thread.last_edit_time = post.pub_time
136 referenced_thread.last_edit_time = post.pub_time
137 referenced_thread.save(update_fields=['last_edit_time'])
137 referenced_thread.save(update_fields=['last_edit_time'])
138
138
139 def get_posts_per_day(self):
139 def get_posts_per_day(self):
140 """
140 """
141 Gets average count of posts per day for the last 7 days
141 Gets average count of posts per day for the last 7 days
142 """
142 """
143
143
144 day_end = date.today()
144 day_end = date.today()
145 day_start = day_end - timedelta(POSTS_PER_DAY_RANGE)
145 day_start = day_end - timedelta(POSTS_PER_DAY_RANGE)
146
146
147 cache_key = CACHE_KEY_PPD + str(day_end)
147 cache_key = CACHE_KEY_PPD + str(day_end)
148 ppd = cache.get(cache_key)
148 ppd = cache.get(cache_key)
149 if ppd:
149 if ppd:
150 return ppd
150 return ppd
151
151
152 day_time_start = timezone.make_aware(datetime.combine(
152 day_time_start = timezone.make_aware(datetime.combine(
153 day_start, dtime()), timezone.get_current_timezone())
153 day_start, dtime()), timezone.get_current_timezone())
154 day_time_end = timezone.make_aware(datetime.combine(
154 day_time_end = timezone.make_aware(datetime.combine(
155 day_end, dtime()), timezone.get_current_timezone())
155 day_end, dtime()), timezone.get_current_timezone())
156
156
157 posts_per_period = float(self.filter(
157 posts_per_period = float(self.filter(
158 pub_time__lte=day_time_end,
158 pub_time__lte=day_time_end,
159 pub_time__gte=day_time_start).count())
159 pub_time__gte=day_time_start).count())
160
160
161 ppd = posts_per_period / POSTS_PER_DAY_RANGE
161 ppd = posts_per_period / POSTS_PER_DAY_RANGE
162
162
163 cache.set(cache_key, ppd)
163 cache.set(cache_key, ppd)
164 return ppd
164 return ppd
165
165
166
166
167 class Post(models.Model, Viewable):
167 class Post(models.Model, Viewable):
168 """A post is a message."""
168 """A post is a message."""
169
169
170 objects = PostManager()
170 objects = PostManager()
171
171
172 class Meta:
172 class Meta:
173 app_label = APP_LABEL_BOARDS
173 app_label = APP_LABEL_BOARDS
174 ordering = ('id',)
174 ordering = ('id',)
175
175
176 title = models.CharField(max_length=TITLE_MAX_LENGTH)
176 title = models.CharField(max_length=TITLE_MAX_LENGTH)
177 pub_time = models.DateTimeField()
177 pub_time = models.DateTimeField()
178 text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE,
178 text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE,
179 escape_html=False)
179 escape_html=False)
180
180
181 images = models.ManyToManyField(PostImage, null=True, blank=True,
181 images = models.ManyToManyField(PostImage, null=True, blank=True,
182 related_name='ip+', db_index=True)
182 related_name='ip+', db_index=True)
183
183
184 poster_ip = models.GenericIPAddressField()
184 poster_ip = models.GenericIPAddressField()
185 poster_user_agent = models.TextField()
185 poster_user_agent = models.TextField()
186
186
187 thread_new = models.ForeignKey('Thread', null=True, default=None,
187 thread_new = models.ForeignKey('Thread', null=True, default=None,
188 db_index=True)
188 db_index=True)
189 last_edit_time = models.DateTimeField()
189 last_edit_time = models.DateTimeField()
190
190
191 # Replies to the post
191 # Replies to the post
192 referenced_posts = models.ManyToManyField('Post', symmetrical=False,
192 referenced_posts = models.ManyToManyField('Post', symmetrical=False,
193 null=True,
193 null=True,
194 blank=True, related_name='rfp+',
194 blank=True, related_name='rfp+',
195 db_index=True)
195 db_index=True)
196
196
197 # Replies map. This is built from the referenced posts list to speed up
197 # Replies map. This is built from the referenced posts list to speed up
198 # page loading (no need to get all the referenced posts from the database).
198 # page loading (no need to get all the referenced posts from the database).
199 refmap = models.TextField(null=True, blank=True)
199 refmap = models.TextField(null=True, blank=True)
200
200
201 # Sender key. If the message was downloaded from another server, this
201 # Global ID with author key. If the message was downloaded from another
202 # indicates the server.
202 # server, this indicates the server.
203 public_key = models.TextField(null=True, blank=True)
203 global_id = models.TextField(null=True, blank=True)
204
204
205 def __unicode__(self):
205 def __unicode__(self):
206 return '#' + str(self.id) + ' ' + self.title + ' (' + \
206 return '#' + str(self.id) + ' ' + self.title + ' (' + \
207 self.text.raw[:50] + ')'
207 self.text.raw[:50] + ')'
208
208
209 def get_title(self):
209 def get_title(self):
210 """
210 """
211 Gets original post title or part of its text.
211 Gets original post title or part of its text.
212 """
212 """
213
213
214 title = self.title
214 title = self.title
215 if not title:
215 if not title:
216 title = self.text.rendered
216 title = self.text.rendered
217
217
218 return title
218 return title
219
219
220 def build_refmap(self):
220 def build_refmap(self):
221 """
221 """
222 Builds a replies map string from replies list. This is a cache to stop
222 Builds a replies map string from replies list. This is a cache to stop
223 the server from recalculating the map on every post show.
223 the server from recalculating the map on every post show.
224 """
224 """
225 map_string = ''
225 map_string = ''
226
226
227 first = True
227 first = True
228 for refpost in self.referenced_posts.all():
228 for refpost in self.referenced_posts.all():
229 if not first:
229 if not first:
230 map_string += ', '
230 map_string += ', '
231 map_string += '<a href="%s">&gt;&gt;%s</a>' % (refpost.get_url(),
231 map_string += '<a href="%s">&gt;&gt;%s</a>' % (refpost.get_url(),
232 refpost.id)
232 refpost.id)
233 first = False
233 first = False
234
234
235 self.refmap = map_string
235 self.refmap = map_string
236
236
237 def get_sorted_referenced_posts(self):
237 def get_sorted_referenced_posts(self):
238 return self.refmap
238 return self.refmap
239
239
240 def is_referenced(self):
240 def is_referenced(self):
241 return len(self.refmap) > 0
241 return len(self.refmap) > 0
242
242
243 def is_opening(self):
243 def is_opening(self):
244 """
244 """
245 Checks if this is an opening post or just a reply.
245 Checks if this is an opening post or just a reply.
246 """
246 """
247
247
248 return self.get_thread().get_opening_post_id() == self.id
248 return self.get_thread().get_opening_post_id() == self.id
249
249
250 @transaction.atomic
250 @transaction.atomic
251 def add_tag(self, tag):
251 def add_tag(self, tag):
252 edit_time = timezone.now()
252 edit_time = timezone.now()
253
253
254 thread = self.get_thread()
254 thread = self.get_thread()
255 thread.add_tag(tag)
255 thread.add_tag(tag)
256 self.last_edit_time = edit_time
256 self.last_edit_time = edit_time
257 self.save(update_fields=['last_edit_time'])
257 self.save(update_fields=['last_edit_time'])
258
258
259 thread.last_edit_time = edit_time
259 thread.last_edit_time = edit_time
260 thread.save(update_fields=['last_edit_time'])
260 thread.save(update_fields=['last_edit_time'])
261
261
262 @transaction.atomic
262 @transaction.atomic
263 def remove_tag(self, tag):
263 def remove_tag(self, tag):
264 edit_time = timezone.now()
264 edit_time = timezone.now()
265
265
266 thread = self.get_thread()
266 thread = self.get_thread()
267 thread.remove_tag(tag)
267 thread.remove_tag(tag)
268 self.last_edit_time = edit_time
268 self.last_edit_time = edit_time
269 self.save(update_fields=['last_edit_time'])
269 self.save(update_fields=['last_edit_time'])
270
270
271 thread.last_edit_time = edit_time
271 thread.last_edit_time = edit_time
272 thread.save(update_fields=['last_edit_time'])
272 thread.save(update_fields=['last_edit_time'])
273
273
274 def get_url(self, thread=None):
274 def get_url(self, thread=None):
275 """
275 """
276 Gets full url to the post.
276 Gets full url to the post.
277 """
277 """
278
278
279 cache_key = CACHE_KEY_POST_URL + str(self.id)
279 cache_key = CACHE_KEY_POST_URL + str(self.id)
280 link = cache.get(cache_key)
280 link = cache.get(cache_key)
281
281
282 if not link:
282 if not link:
283 if not thread:
283 if not thread:
284 thread = self.get_thread()
284 thread = self.get_thread()
285
285
286 opening_id = thread.get_opening_post_id()
286 opening_id = thread.get_opening_post_id()
287
287
288 if self.id != opening_id:
288 if self.id != opening_id:
289 link = reverse('thread', kwargs={
289 link = reverse('thread', kwargs={
290 'post_id': opening_id}) + '#' + str(self.id)
290 'post_id': opening_id}) + '#' + str(self.id)
291 else:
291 else:
292 link = reverse('thread', kwargs={'post_id': self.id})
292 link = reverse('thread', kwargs={'post_id': self.id})
293
293
294 cache.set(cache_key, link)
294 cache.set(cache_key, link)
295
295
296 return link
296 return link
297
297
298 def get_thread(self):
298 def get_thread(self):
299 """
299 """
300 Gets post's thread.
300 Gets post's thread.
301 """
301 """
302
302
303 return self.thread_new
303 return self.thread_new
304
304
305 def get_referenced_posts(self):
305 def get_referenced_posts(self):
306 return self.referenced_posts.only('id', 'thread_new')
306 return self.referenced_posts.only('id', 'thread_new')
307
307
308 def get_text(self):
308 def get_text(self):
309 return self.text
309 return self.text
310
310
311 def get_view(self, moderator=False, need_open_link=False,
311 def get_view(self, moderator=False, need_open_link=False,
312 truncated=False, *args, **kwargs):
312 truncated=False, *args, **kwargs):
313 if 'is_opening' in kwargs:
313 if 'is_opening' in kwargs:
314 is_opening = kwargs['is_opening']
314 is_opening = kwargs['is_opening']
315 else:
315 else:
316 is_opening = self.is_opening()
316 is_opening = self.is_opening()
317
317
318 if 'thread' in kwargs:
318 if 'thread' in kwargs:
319 thread = kwargs['thread']
319 thread = kwargs['thread']
320 else:
320 else:
321 thread = self.get_thread()
321 thread = self.get_thread()
322
322
323 if 'can_bump' in kwargs:
323 if 'can_bump' in kwargs:
324 can_bump = kwargs['can_bump']
324 can_bump = kwargs['can_bump']
325 else:
325 else:
326 can_bump = thread.can_bump()
326 can_bump = thread.can_bump()
327
327
328 if is_opening:
328 if is_opening:
329 opening_post_id = self.id
329 opening_post_id = self.id
330 else:
330 else:
331 opening_post_id = thread.get_opening_post_id()
331 opening_post_id = thread.get_opening_post_id()
332
332
333 return render_to_string('boards/post.html', {
333 return render_to_string('boards/post.html', {
334 'post': self,
334 'post': self,
335 'moderator': moderator,
335 'moderator': moderator,
336 'is_opening': is_opening,
336 'is_opening': is_opening,
337 'thread': thread,
337 'thread': thread,
338 'bumpable': can_bump,
338 'bumpable': can_bump,
339 'need_open_link': need_open_link,
339 'need_open_link': need_open_link,
340 'truncated': truncated,
340 'truncated': truncated,
341 'opening_post_id': opening_post_id,
341 'opening_post_id': opening_post_id,
342 })
342 })
343
343
344 def get_first_image(self):
344 def get_first_image(self):
345 return self.images.earliest('id')
345 return self.images.earliest('id')
346
346
347 def delete(self, using=None):
347 def delete(self, using=None):
348 """
348 """
349 Deletes all post images and the post itself.
349 Deletes all post images and the post itself.
350 """
350 """
351
351
352 self.images.all().delete()
352 self.images.all().delete()
353
353
354 super(Post, self).delete(using)
354 super(Post, self).delete(using)
General Comments 0
You need to be logged in to leave comments. Login now