##// END OF EJS Templates
Added info about attachments
neko259 -
r840:78400567 decentral
parent child Browse files
Show More
@@ -1,492 +1,498 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 import xml.etree.ElementTree as et
5 import xml.etree.ElementTree as et
6
6
7 from django.core.cache import cache
7 from django.core.cache import cache
8 from django.core.urlresolvers import reverse
8 from django.core.urlresolvers import reverse
9 from django.db import models, transaction
9 from django.db import models, transaction
10 from django.template.loader import render_to_string
10 from django.template.loader import render_to_string
11 from django.utils import timezone
11 from django.utils import timezone
12
12
13 from markupfield.fields import MarkupField
13 from markupfield.fields import MarkupField
14
14
15 from boards.models import PostImage, KeyPair, GlobalId, Signature
15 from boards.models import PostImage, KeyPair, GlobalId, Signature
16 from boards.models.base import Viewable
16 from boards.models.base import Viewable
17 from boards.models.thread import Thread
17 from boards.models.thread import Thread
18 from boards import utils
18 from boards import utils
19
19
20
20
21 APP_LABEL_BOARDS = 'boards'
21 APP_LABEL_BOARDS = 'boards'
22
22
23 CACHE_KEY_PPD = 'ppd'
23 CACHE_KEY_PPD = 'ppd'
24 CACHE_KEY_POST_URL = 'post_url'
24 CACHE_KEY_POST_URL = 'post_url'
25
25
26 POSTS_PER_DAY_RANGE = 7
26 POSTS_PER_DAY_RANGE = 7
27
27
28 BAN_REASON_AUTO = 'Auto'
28 BAN_REASON_AUTO = 'Auto'
29
29
30 IMAGE_THUMB_SIZE = (200, 150)
30 IMAGE_THUMB_SIZE = (200, 150)
31
31
32 TITLE_MAX_LENGTH = 200
32 TITLE_MAX_LENGTH = 200
33
33
34 DEFAULT_MARKUP_TYPE = 'bbcode'
34 DEFAULT_MARKUP_TYPE = 'bbcode'
35
35
36 # TODO This should be removed
36 # TODO This should be removed
37 NO_IP = '0.0.0.0'
37 NO_IP = '0.0.0.0'
38
38
39 # TODO Real user agent should be saved instead of this
39 # TODO Real user agent should be saved instead of this
40 UNKNOWN_UA = ''
40 UNKNOWN_UA = ''
41
41
42 REGEX_REPLY = re.compile(r'\[post\](\d+)\[/post\]')
42 REGEX_REPLY = re.compile(r'\[post\](\d+)\[/post\]')
43
43
44 TAG_MODEL = 'model'
44 TAG_MODEL = 'model'
45 TAG_REQUEST = 'request'
45 TAG_REQUEST = 'request'
46 TAG_RESPONSE = 'response'
46 TAG_RESPONSE = 'response'
47 TAG_ID = 'id'
47 TAG_ID = 'id'
48 TAG_STATUS = 'status'
48 TAG_STATUS = 'status'
49 TAG_MODELS = 'models'
49 TAG_MODELS = 'models'
50 TAG_TITLE = 'title'
50 TAG_TITLE = 'title'
51 TAG_TEXT = 'text'
51 TAG_TEXT = 'text'
52 TAG_THREAD = 'thread'
52 TAG_THREAD = 'thread'
53 TAG_PUB_TIME = 'pub-time'
53 TAG_PUB_TIME = 'pub-time'
54 TAG_EDIT_TIME = 'edit-time'
54 TAG_EDIT_TIME = 'edit-time'
55 TAG_PREVIOUS = 'previous'
55 TAG_PREVIOUS = 'previous'
56 TAG_NEXT = 'next'
56 TAG_NEXT = 'next'
57 TAG_SIGNATURES = 'signatures'
57 TAG_SIGNATURES = 'signatures'
58 TAG_SIGNATURE = 'signature'
58 TAG_SIGNATURE = 'signature'
59 TAG_CONTENT = 'content'
59 TAG_CONTENT = 'content'
60 TAG_ATTACHMENTS = 'attachments'
61 TAG_ATTACHMENT = 'attachment'
60
62
61 TYPE_GET = 'get'
63 TYPE_GET = 'get'
62
64
63 ATTR_VERSION = 'version'
65 ATTR_VERSION = 'version'
64 ATTR_TYPE = 'type'
66 ATTR_TYPE = 'type'
65 ATTR_NAME = 'name'
67 ATTR_NAME = 'name'
66 ATTR_VALUE = 'value'
68 ATTR_VALUE = 'value'
69 ATTR_MIMETYPE = 'mimetype'
67
70
68 STATUS_SUCCESS = 'success'
71 STATUS_SUCCESS = 'success'
69
72
70 logger = logging.getLogger(__name__)
73 logger = logging.getLogger(__name__)
71
74
72
75
73 class PostManager(models.Manager):
76 class PostManager(models.Manager):
74 def create_post(self, title, text, image=None, thread=None, ip=NO_IP,
77 def create_post(self, title, text, image=None, thread=None, ip=NO_IP,
75 tags=None):
78 tags=None):
76 """
79 """
77 Creates new post
80 Creates new post
78 """
81 """
79
82
80 if not tags:
83 if not tags:
81 tags = []
84 tags = []
82
85
83 posting_time = timezone.now()
86 posting_time = timezone.now()
84 if not thread:
87 if not thread:
85 thread = Thread.objects.create(bump_time=posting_time,
88 thread = Thread.objects.create(bump_time=posting_time,
86 last_edit_time=posting_time)
89 last_edit_time=posting_time)
87 new_thread = True
90 new_thread = True
88 else:
91 else:
89 thread.bump()
92 thread.bump()
90 thread.last_edit_time = posting_time
93 thread.last_edit_time = posting_time
91 thread.save()
94 thread.save()
92 new_thread = False
95 new_thread = False
93
96
94 post = self.create(title=title,
97 post = self.create(title=title,
95 text=text,
98 text=text,
96 pub_time=posting_time,
99 pub_time=posting_time,
97 thread_new=thread,
100 thread_new=thread,
98 poster_ip=ip,
101 poster_ip=ip,
99 poster_user_agent=UNKNOWN_UA, # TODO Get UA at
102 poster_user_agent=UNKNOWN_UA, # TODO Get UA at
100 # last!
103 # last!
101 last_edit_time=posting_time)
104 last_edit_time=posting_time)
102
105
103 post.set_global_id()
106 post.set_global_id()
104
107
105 if image:
108 if image:
106 post_image = PostImage.objects.create(image=image)
109 post_image = PostImage.objects.create(image=image)
107 post.images.add(post_image)
110 post.images.add(post_image)
108 logger.info('Created image #%d for post #%d' % (post_image.id,
111 logger.info('Created image #%d for post #%d' % (post_image.id,
109 post.id))
112 post.id))
110
113
111 thread.replies.add(post)
114 thread.replies.add(post)
112 list(map(thread.add_tag, tags))
115 list(map(thread.add_tag, tags))
113
116
114 if new_thread:
117 if new_thread:
115 Thread.objects.process_oldest_threads()
118 Thread.objects.process_oldest_threads()
116 self.connect_replies(post)
119 self.connect_replies(post)
117
120
118 logger.info('Created post #%d with title %s'
121 logger.info('Created post #%d with title %s'
119 % (post.id, post.get_title()))
122 % (post.id, post.get_title()))
120
123
121 return post
124 return post
122
125
123 def delete_post(self, post):
126 def delete_post(self, post):
124 """
127 """
125 Deletes post and update or delete its thread
128 Deletes post and update or delete its thread
126 """
129 """
127
130
128 post_id = post.id
131 post_id = post.id
129
132
130 thread = post.get_thread()
133 thread = post.get_thread()
131
134
132 if post.is_opening():
135 if post.is_opening():
133 thread.delete()
136 thread.delete()
134 else:
137 else:
135 thread.last_edit_time = timezone.now()
138 thread.last_edit_time = timezone.now()
136 thread.save()
139 thread.save()
137
140
138 post.delete()
141 post.delete()
139
142
140 logger.info('Deleted post #%d (%s)' % (post_id, post.get_title()))
143 logger.info('Deleted post #%d (%s)' % (post_id, post.get_title()))
141
144
142 def delete_posts_by_ip(self, ip):
145 def delete_posts_by_ip(self, ip):
143 """
146 """
144 Deletes all posts of the author with same IP
147 Deletes all posts of the author with same IP
145 """
148 """
146
149
147 posts = self.filter(poster_ip=ip)
150 posts = self.filter(poster_ip=ip)
148 for post in posts:
151 for post in posts:
149 self.delete_post(post)
152 self.delete_post(post)
150
153
151 def connect_replies(self, post):
154 def connect_replies(self, post):
152 """
155 """
153 Connects replies to a post to show them as a reflink map
156 Connects replies to a post to show them as a reflink map
154 """
157 """
155
158
156 for reply_number in post.get_replied_ids():
159 for reply_number in post.get_replied_ids():
157 ref_post = self.filter(id=reply_number)
160 ref_post = self.filter(id=reply_number)
158 if ref_post.count() > 0:
161 if ref_post.count() > 0:
159 referenced_post = ref_post[0]
162 referenced_post = ref_post[0]
160 referenced_post.referenced_posts.add(post)
163 referenced_post.referenced_posts.add(post)
161 referenced_post.last_edit_time = post.pub_time
164 referenced_post.last_edit_time = post.pub_time
162 referenced_post.build_refmap()
165 referenced_post.build_refmap()
163 referenced_post.save(update_fields=['refmap', 'last_edit_time'])
166 referenced_post.save(update_fields=['refmap', 'last_edit_time'])
164
167
165 referenced_thread = referenced_post.get_thread()
168 referenced_thread = referenced_post.get_thread()
166 referenced_thread.last_edit_time = post.pub_time
169 referenced_thread.last_edit_time = post.pub_time
167 referenced_thread.save(update_fields=['last_edit_time'])
170 referenced_thread.save(update_fields=['last_edit_time'])
168
171
169 def get_posts_per_day(self):
172 def get_posts_per_day(self):
170 """
173 """
171 Gets average count of posts per day for the last 7 days
174 Gets average count of posts per day for the last 7 days
172 """
175 """
173
176
174 day_end = date.today()
177 day_end = date.today()
175 day_start = day_end - timedelta(POSTS_PER_DAY_RANGE)
178 day_start = day_end - timedelta(POSTS_PER_DAY_RANGE)
176
179
177 cache_key = CACHE_KEY_PPD + str(day_end)
180 cache_key = CACHE_KEY_PPD + str(day_end)
178 ppd = cache.get(cache_key)
181 ppd = cache.get(cache_key)
179 if ppd:
182 if ppd:
180 return ppd
183 return ppd
181
184
182 day_time_start = timezone.make_aware(datetime.combine(
185 day_time_start = timezone.make_aware(datetime.combine(
183 day_start, dtime()), timezone.get_current_timezone())
186 day_start, dtime()), timezone.get_current_timezone())
184 day_time_end = timezone.make_aware(datetime.combine(
187 day_time_end = timezone.make_aware(datetime.combine(
185 day_end, dtime()), timezone.get_current_timezone())
188 day_end, dtime()), timezone.get_current_timezone())
186
189
187 posts_per_period = float(self.filter(
190 posts_per_period = float(self.filter(
188 pub_time__lte=day_time_end,
191 pub_time__lte=day_time_end,
189 pub_time__gte=day_time_start).count())
192 pub_time__gte=day_time_start).count())
190
193
191 ppd = posts_per_period / POSTS_PER_DAY_RANGE
194 ppd = posts_per_period / POSTS_PER_DAY_RANGE
192
195
193 cache.set(cache_key, ppd)
196 cache.set(cache_key, ppd)
194 return ppd
197 return ppd
195
198
196 def generate_request_get(self, model_list: list):
199 def generate_request_get(self, model_list: list):
197 """
200 """
198 Form a get request from a list of ModelId objects.
201 Form a get request from a list of ModelId objects.
199 """
202 """
200
203
201 request = et.Element(TAG_REQUEST)
204 request = et.Element(TAG_REQUEST)
202 request.set(ATTR_TYPE, TYPE_GET)
205 request.set(ATTR_TYPE, TYPE_GET)
203 request.set(ATTR_VERSION, '1.0')
206 request.set(ATTR_VERSION, '1.0')
204
207
205 model = et.SubElement(request, TAG_MODEL)
208 model = et.SubElement(request, TAG_MODEL)
206 model.set(ATTR_VERSION, '1.0')
209 model.set(ATTR_VERSION, '1.0')
207 model.set(ATTR_NAME, 'post')
210 model.set(ATTR_NAME, 'post')
208
211
209 for post in model_list:
212 for post in model_list:
210 tag_id = et.SubElement(model, TAG_ID)
213 tag_id = et.SubElement(model, TAG_ID)
211 post.global_id.to_xml_element(tag_id)
214 post.global_id.to_xml_element(tag_id)
212
215
213 return et.tostring(request, 'unicode')
216 return et.tostring(request, 'unicode')
214
217
215 def generate_response_get(self, model_list: list):
218 def generate_response_get(self, model_list: list):
216 response = et.Element(TAG_RESPONSE)
219 response = et.Element(TAG_RESPONSE)
217
220
218 status = et.SubElement(response, TAG_STATUS)
221 status = et.SubElement(response, TAG_STATUS)
219 status.text = STATUS_SUCCESS
222 status.text = STATUS_SUCCESS
220
223
221 models = et.SubElement(response, TAG_MODELS)
224 models = et.SubElement(response, TAG_MODELS)
222
225
223 for post in model_list:
226 for post in model_list:
224 model = et.SubElement(models, TAG_MODEL)
227 model = et.SubElement(models, TAG_MODEL)
225 model.set(ATTR_NAME, 'post')
228 model.set(ATTR_NAME, 'post')
226
229
227 content_tag = et.SubElement(model, TAG_CONTENT)
230 content_tag = et.SubElement(model, TAG_CONTENT)
228
231
229 tag_id = et.SubElement(content_tag, TAG_ID)
232 tag_id = et.SubElement(content_tag, TAG_ID)
230 post.global_id.to_xml_element(tag_id)
233 post.global_id.to_xml_element(tag_id)
231
234
232 title = et.SubElement(content_tag, TAG_TITLE)
235 title = et.SubElement(content_tag, TAG_TITLE)
233 title.text = post.title
236 title.text = post.title
234
237
235 text = et.SubElement(content_tag, TAG_TEXT)
238 text = et.SubElement(content_tag, TAG_TEXT)
236 text.text = post.text.raw
239 text.text = post.text.raw
237
240
238 if not post.is_opening():
241 if not post.is_opening():
239 thread = et.SubElement(content_tag, TAG_THREAD)
242 thread = et.SubElement(content_tag, TAG_THREAD)
240 thread.text = str(post.get_thread().get_opening_post_id())
243 thread.text = str(post.get_thread().get_opening_post_id())
244 else:
245 # TODO Output tags here
246 pass
241
247
242 pub_time = et.SubElement(content_tag, TAG_PUB_TIME)
248 pub_time = et.SubElement(content_tag, TAG_PUB_TIME)
243 pub_time.text = str(post.get_pub_time_epoch())
249 pub_time.text = str(post.get_pub_time_epoch())
244
250
245 signatures_tag = et.SubElement(model, TAG_SIGNATURES)
251 signatures_tag = et.SubElement(model, TAG_SIGNATURES)
246 post_signatures = post.signature.all()
252 post_signatures = post.signature.all()
247 if post_signatures:
253 if post_signatures:
248 signatures = post.signatures
254 signatures = post.signatures
249 else:
255 else:
250 # TODO Maybe the signature can be computed only once after
256 # TODO Maybe the signature can be computed only once after
251 # the post is added? Need to add some on_save signal queue
257 # the post is added? Need to add some on_save signal queue
252 # and add this there.
258 # and add this there.
253 key = KeyPair.objects.get(public_key=post.global_id.key)
259 key = KeyPair.objects.get(public_key=post.global_id.key)
254 signatures = [Signature(
260 signatures = [Signature(
255 key_type=key.key_type,
261 key_type=key.key_type,
256 key=key.public_key,
262 key=key.public_key,
257 signature=key.sign(et.tostring(model, 'unicode')),
263 signature=key.sign(et.tostring(model, 'unicode')),
258 )]
264 )]
259 for signature in signatures:
265 for signature in signatures:
260 signature_tag = et.SubElement(signatures_tag, TAG_SIGNATURE)
266 signature_tag = et.SubElement(signatures_tag, TAG_SIGNATURE)
261 signature_tag.set(ATTR_TYPE, signature.key_type)
267 signature_tag.set(ATTR_TYPE, signature.key_type)
262 signature_tag.set(ATTR_VALUE, signature.signature)
268 signature_tag.set(ATTR_VALUE, signature.signature)
263
269
264 return et.tostring(response, 'unicode')
270 return et.tostring(response, 'unicode')
265
271
266
272
267 class Post(models.Model, Viewable):
273 class Post(models.Model, Viewable):
268 """A post is a message."""
274 """A post is a message."""
269
275
270 objects = PostManager()
276 objects = PostManager()
271
277
272 class Meta:
278 class Meta:
273 app_label = APP_LABEL_BOARDS
279 app_label = APP_LABEL_BOARDS
274 ordering = ('id',)
280 ordering = ('id',)
275
281
276 title = models.CharField(max_length=TITLE_MAX_LENGTH)
282 title = models.CharField(max_length=TITLE_MAX_LENGTH)
277 pub_time = models.DateTimeField()
283 pub_time = models.DateTimeField()
278 text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE,
284 text = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE,
279 escape_html=False)
285 escape_html=False)
280
286
281 images = models.ManyToManyField(PostImage, null=True, blank=True,
287 images = models.ManyToManyField(PostImage, null=True, blank=True,
282 related_name='ip+', db_index=True)
288 related_name='ip+', db_index=True)
283
289
284 poster_ip = models.GenericIPAddressField()
290 poster_ip = models.GenericIPAddressField()
285 poster_user_agent = models.TextField()
291 poster_user_agent = models.TextField()
286
292
287 thread_new = models.ForeignKey('Thread', null=True, default=None,
293 thread_new = models.ForeignKey('Thread', null=True, default=None,
288 db_index=True)
294 db_index=True)
289 last_edit_time = models.DateTimeField()
295 last_edit_time = models.DateTimeField()
290
296
291 # Replies to the post
297 # Replies to the post
292 referenced_posts = models.ManyToManyField('Post', symmetrical=False,
298 referenced_posts = models.ManyToManyField('Post', symmetrical=False,
293 null=True,
299 null=True,
294 blank=True, related_name='rfp+',
300 blank=True, related_name='rfp+',
295 db_index=True)
301 db_index=True)
296
302
297 # Replies map. This is built from the referenced posts list to speed up
303 # Replies map. This is built from the referenced posts list to speed up
298 # page loading (no need to get all the referenced posts from the database).
304 # page loading (no need to get all the referenced posts from the database).
299 refmap = models.TextField(null=True, blank=True)
305 refmap = models.TextField(null=True, blank=True)
300
306
301 # Global ID with author key. If the message was downloaded from another
307 # Global ID with author key. If the message was downloaded from another
302 # server, this indicates the server.
308 # server, this indicates the server.
303 global_id = models.OneToOneField('GlobalId', null=True, blank=True)
309 global_id = models.OneToOneField('GlobalId', null=True, blank=True)
304
310
305 # One post can be signed by many nodes that give their trust to it
311 # One post can be signed by many nodes that give their trust to it
306 signature = models.ManyToManyField('Signature', null=True, blank=True)
312 signature = models.ManyToManyField('Signature', null=True, blank=True)
307
313
308 def __unicode__(self):
314 def __unicode__(self):
309 return '#' + str(self.id) + ' ' + self.title + ' (' + \
315 return '#' + str(self.id) + ' ' + self.title + ' (' + \
310 self.text.raw[:50] + ')'
316 self.text.raw[:50] + ')'
311
317
312 def get_title(self):
318 def get_title(self):
313 """
319 """
314 Gets original post title or part of its text.
320 Gets original post title or part of its text.
315 """
321 """
316
322
317 title = self.title
323 title = self.title
318 if not title:
324 if not title:
319 title = self.text.rendered
325 title = self.text.rendered
320
326
321 return title
327 return title
322
328
323 def build_refmap(self):
329 def build_refmap(self):
324 """
330 """
325 Builds a replies map string from replies list. This is a cache to stop
331 Builds a replies map string from replies list. This is a cache to stop
326 the server from recalculating the map on every post show.
332 the server from recalculating the map on every post show.
327 """
333 """
328 map_string = ''
334 map_string = ''
329
335
330 first = True
336 first = True
331 for refpost in self.referenced_posts.all():
337 for refpost in self.referenced_posts.all():
332 if not first:
338 if not first:
333 map_string += ', '
339 map_string += ', '
334 map_string += '<a href="%s">&gt;&gt;%s</a>' % (refpost.get_url(),
340 map_string += '<a href="%s">&gt;&gt;%s</a>' % (refpost.get_url(),
335 refpost.id)
341 refpost.id)
336 first = False
342 first = False
337
343
338 self.refmap = map_string
344 self.refmap = map_string
339
345
340 def get_sorted_referenced_posts(self):
346 def get_sorted_referenced_posts(self):
341 return self.refmap
347 return self.refmap
342
348
343 def is_referenced(self):
349 def is_referenced(self):
344 return len(self.refmap) > 0
350 return len(self.refmap) > 0
345
351
346 def is_opening(self):
352 def is_opening(self):
347 """
353 """
348 Checks if this is an opening post or just a reply.
354 Checks if this is an opening post or just a reply.
349 """
355 """
350
356
351 return self.get_thread().get_opening_post_id() == self.id
357 return self.get_thread().get_opening_post_id() == self.id
352
358
353 @transaction.atomic
359 @transaction.atomic
354 def add_tag(self, tag):
360 def add_tag(self, tag):
355 edit_time = timezone.now()
361 edit_time = timezone.now()
356
362
357 thread = self.get_thread()
363 thread = self.get_thread()
358 thread.add_tag(tag)
364 thread.add_tag(tag)
359 self.last_edit_time = edit_time
365 self.last_edit_time = edit_time
360 self.save(update_fields=['last_edit_time'])
366 self.save(update_fields=['last_edit_time'])
361
367
362 thread.last_edit_time = edit_time
368 thread.last_edit_time = edit_time
363 thread.save(update_fields=['last_edit_time'])
369 thread.save(update_fields=['last_edit_time'])
364
370
365 @transaction.atomic
371 @transaction.atomic
366 def remove_tag(self, tag):
372 def remove_tag(self, tag):
367 edit_time = timezone.now()
373 edit_time = timezone.now()
368
374
369 thread = self.get_thread()
375 thread = self.get_thread()
370 thread.remove_tag(tag)
376 thread.remove_tag(tag)
371 self.last_edit_time = edit_time
377 self.last_edit_time = edit_time
372 self.save(update_fields=['last_edit_time'])
378 self.save(update_fields=['last_edit_time'])
373
379
374 thread.last_edit_time = edit_time
380 thread.last_edit_time = edit_time
375 thread.save(update_fields=['last_edit_time'])
381 thread.save(update_fields=['last_edit_time'])
376
382
377 def get_url(self, thread=None):
383 def get_url(self, thread=None):
378 """
384 """
379 Gets full url to the post.
385 Gets full url to the post.
380 """
386 """
381
387
382 cache_key = CACHE_KEY_POST_URL + str(self.id)
388 cache_key = CACHE_KEY_POST_URL + str(self.id)
383 link = cache.get(cache_key)
389 link = cache.get(cache_key)
384
390
385 if not link:
391 if not link:
386 if not thread:
392 if not thread:
387 thread = self.get_thread()
393 thread = self.get_thread()
388
394
389 opening_id = thread.get_opening_post_id()
395 opening_id = thread.get_opening_post_id()
390
396
391 if self.id != opening_id:
397 if self.id != opening_id:
392 link = reverse('thread', kwargs={
398 link = reverse('thread', kwargs={
393 'post_id': opening_id}) + '#' + str(self.id)
399 'post_id': opening_id}) + '#' + str(self.id)
394 else:
400 else:
395 link = reverse('thread', kwargs={'post_id': self.id})
401 link = reverse('thread', kwargs={'post_id': self.id})
396
402
397 cache.set(cache_key, link)
403 cache.set(cache_key, link)
398
404
399 return link
405 return link
400
406
401 def get_thread(self):
407 def get_thread(self):
402 """
408 """
403 Gets post's thread.
409 Gets post's thread.
404 """
410 """
405
411
406 return self.thread_new
412 return self.thread_new
407
413
408 def get_referenced_posts(self):
414 def get_referenced_posts(self):
409 return self.referenced_posts.only('id', 'thread_new')
415 return self.referenced_posts.only('id', 'thread_new')
410
416
411 def get_text(self):
417 def get_text(self):
412 return self.text
418 return self.text
413
419
414 def get_view(self, moderator=False, need_open_link=False,
420 def get_view(self, moderator=False, need_open_link=False,
415 truncated=False, *args, **kwargs):
421 truncated=False, *args, **kwargs):
416 if 'is_opening' in kwargs:
422 if 'is_opening' in kwargs:
417 is_opening = kwargs['is_opening']
423 is_opening = kwargs['is_opening']
418 else:
424 else:
419 is_opening = self.is_opening()
425 is_opening = self.is_opening()
420
426
421 if 'thread' in kwargs:
427 if 'thread' in kwargs:
422 thread = kwargs['thread']
428 thread = kwargs['thread']
423 else:
429 else:
424 thread = self.get_thread()
430 thread = self.get_thread()
425
431
426 if 'can_bump' in kwargs:
432 if 'can_bump' in kwargs:
427 can_bump = kwargs['can_bump']
433 can_bump = kwargs['can_bump']
428 else:
434 else:
429 can_bump = thread.can_bump()
435 can_bump = thread.can_bump()
430
436
431 if is_opening:
437 if is_opening:
432 opening_post_id = self.id
438 opening_post_id = self.id
433 else:
439 else:
434 opening_post_id = thread.get_opening_post_id()
440 opening_post_id = thread.get_opening_post_id()
435
441
436 return render_to_string('boards/post.html', {
442 return render_to_string('boards/post.html', {
437 'post': self,
443 'post': self,
438 'moderator': moderator,
444 'moderator': moderator,
439 'is_opening': is_opening,
445 'is_opening': is_opening,
440 'thread': thread,
446 'thread': thread,
441 'bumpable': can_bump,
447 'bumpable': can_bump,
442 'need_open_link': need_open_link,
448 'need_open_link': need_open_link,
443 'truncated': truncated,
449 'truncated': truncated,
444 'opening_post_id': opening_post_id,
450 'opening_post_id': opening_post_id,
445 })
451 })
446
452
447 def get_first_image(self):
453 def get_first_image(self):
448 return self.images.earliest('id')
454 return self.images.earliest('id')
449
455
450 def delete(self, using=None):
456 def delete(self, using=None):
451 """
457 """
452 Deletes all post images and the post itself.
458 Deletes all post images and the post itself.
453 """
459 """
454
460
455 self.images.all().delete()
461 self.images.all().delete()
456 self.signature.all().delete()
462 self.signature.all().delete()
457 if self.global_id:
463 if self.global_id:
458 self.global_id.delete()
464 self.global_id.delete()
459
465
460 super(Post, self).delete(using)
466 super(Post, self).delete(using)
461
467
462 def set_global_id(self, key_pair=None):
468 def set_global_id(self, key_pair=None):
463 """
469 """
464 Sets global id based on the given key pair. If no key pair is given,
470 Sets global id based on the given key pair. If no key pair is given,
465 default one is used.
471 default one is used.
466 """
472 """
467
473
468 if key_pair:
474 if key_pair:
469 key = key_pair
475 key = key_pair
470 else:
476 else:
471 try:
477 try:
472 key = KeyPair.objects.get(primary=True)
478 key = KeyPair.objects.get(primary=True)
473 except KeyPair.DoesNotExist:
479 except KeyPair.DoesNotExist:
474 # Do not update the global id because there is no key defined
480 # Do not update the global id because there is no key defined
475 return
481 return
476 global_id = GlobalId(key_type=key.key_type,
482 global_id = GlobalId(key_type=key.key_type,
477 key=key.public_key,
483 key=key.public_key,
478 local_id = self.id)
484 local_id = self.id)
479 global_id.save()
485 global_id.save()
480
486
481 self.global_id = global_id
487 self.global_id = global_id
482
488
483 self.save(update_fields=['global_id'])
489 self.save(update_fields=['global_id'])
484
490
485 def get_pub_time_epoch(self):
491 def get_pub_time_epoch(self):
486 return utils.datetime_to_epoch(self.pub_time)
492 return utils.datetime_to_epoch(self.pub_time)
487
493
488 def get_edit_time_epoch(self):
494 def get_edit_time_epoch(self):
489 return utils.datetime_to_epoch(self.last_edit_time)
495 return utils.datetime_to_epoch(self.last_edit_time)
490
496
491 def get_replied_ids(self):
497 def get_replied_ids(self):
492 return re.findall(REGEX_REPLY, self.text.raw)
498 return re.findall(REGEX_REPLY, self.text.raw)
@@ -1,45 +1,58 b''
1 <?xml version="1.1" encoding="UTF-8" ?>
1 <?xml version="1.1" encoding="UTF-8" ?>
2 <response>
2 <response>
3 <!--
3 <!--
4 Valid statuses are 'success' and 'error'.
4 Valid statuses are 'success' and 'error'.
5 -->
5 -->
6 <status>success</status>
6 <status>success</status>
7 <models>
7 <models>
8 <model name="post">
8 <model name="post">
9 <!--
9 <!--
10 Content tag is the data that is signed by signatures and must
10 Content tag is the data that is signed by signatures and must
11 not be changed for the post from other node.
11 not be changed for the post from other node.
12 -->
12 -->
13 <content>
13 <content>
14 <id key="id1" type="ecdsa" local-id="1" />
14 <id key="id1" type="ecdsa" local-id="1" />
15 <title>13</title>
15 <title>13</title>
16 <text>Thirteen</text>
16 <text>Thirteen</text>
17 <thread>id1/12</thread>
17 <thread>id1/12</thread>
18 <pub-time>12</pub-time>
18 <pub-time>12</pub-time>
19 <!--
20 Images are saved as attachments and included in the
21 signature.
22 -->
23 <attachments>
24 <attachment mimetype="image/png" name="12345.png">
25 TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0
26 aGlzIHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1
27 c3Qgb2YgdGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0
28 aGUgY29udGludWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdl
29 LCBleGNlZWRzIHRoZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4=
30 </attachment>
31 </attachments>
19 </content>
32 </content>
20 <!--
33 <!--
21 There can be several signatures for one model. At least one
34 There can be several signatures for one model. At least one
22 signature must be made with the key used in global ID.
35 signature must be made with the key used in global ID.
23 -->
36 -->
24 <signatures>
37 <signatures>
25 <signature key="id1" type="ecdsa" value="dhefhtreh" />
38 <signature key="id1" type="ecdsa" value="dhefhtreh" />
26 <signature key="id45" type="ecdsa" value="dsgfgdhefhtreh" />
39 <signature key="id45" type="ecdsa" value="dsgfgdhefhtreh" />
27 </signatures>
40 </signatures>
28 </model>
41 </model>
29 <model name="post">
42 <model name="post">
30 <content>
43 <content>
31 <id key="id1" type="ecdsa" local-id="id2" />
44 <id key="id1" type="ecdsa" local-id="id2" />
32 <title>13</title>
45 <title>13</title>
33 <text>Thirteen</text>
46 <text>Thirteen</text>
34 <pub-time>12</pub-time>
47 <pub-time>12</pub-time>
35 <edit-time>13</edit-time>
48 <edit-time>13</edit-time>
36 <tags>
49 <tags>
37 <tag>tag1</tag>
50 <tag>tag1</tag>
38 </tags>
51 </tags>
39 </content>
52 </content>
40 <signatures>
53 <signatures>
41 <signature key="id2" type="ecdsa" value="dehdfh" />
54 <signature key="id2" type="ecdsa" value="dehdfh" />
42 </signatures>
55 </signatures>
43 </model>
56 </model>
44 </models>
57 </models>
45 </response>
58 </response>
General Comments 0
You need to be logged in to leave comments. Login now