##// END OF EJS Templates
Download attached filed to the post during sync
neko259 -
r1511:ea51d39c decentral
parent child Browse files
Show More
@@ -1,7 +1,7 b''
1 1 from django.contrib import admin
2 2 from django.utils.translation import ugettext_lazy as _
3 3 from django.core.urlresolvers import reverse
4 from boards.models import Post, Tag, Ban, Thread, Banner, PostImage, KeyPair
4 from boards.models import Post, Tag, Ban, Thread, Banner, PostImage, KeyPair, GlobalId
5 5
6 6
7 7 @admin.register(Post)
@@ -109,3 +109,16 b' class BanAdmin(admin.ModelAdmin):'
109 109 @admin.register(Banner)
110 110 class BannerAdmin(admin.ModelAdmin):
111 111 list_display = ('title', 'text')
112
113
114 @admin.register(PostImage)
115 class PostImageAdmin(admin.ModelAdmin):
116 search_fields = ('alias',)
117
118
119 @admin.register(GlobalId)
120 class GlobalIdAdmin(admin.ModelAdmin):
121 def is_linked(self, obj):
122 return Post.objects.filter(global_id=obj).exists()
123
124 list_display = ('__str__', 'is_linked',) No newline at end of file
@@ -15,7 +15,7 b' from django.utils import timezone'
15 15 from boards.abstracts.settingsmanager import get_settings_manager
16 16 from boards.abstracts.attachment_alias import get_image_by_alias
17 17 from boards.mdx_neboard import formatters
18 from boards.models.attachment.downloaders import Downloader
18 from boards.models.attachment.downloaders import download
19 19 from boards.models.post import TITLE_MAX_LENGTH
20 20 from boards.models import Tag, Post
21 21 from boards.utils import validate_file_size, get_file_mimetype, \
@@ -375,12 +375,7 b' class PostForm(NeboardForm):'
375 375 img_temp = None
376 376
377 377 try:
378 for downloader in Downloader.__subclasses__():
379 if downloader.handles(url):
380 return downloader.download(url)
381 # If nobody of the specific downloaders handles this, use generic
382 # one
383 return Downloader.download(url)
378 download(url)
384 379 except forms.ValidationError as e:
385 380 raise e
386 381 except Exception as e:
@@ -26,6 +26,7 b' class Command(BaseCommand):'
26 26
27 27 pull_url = url + 'api/sync/pull/'
28 28 get_url = url + 'api/sync/get/'
29 file_url = url[:-1]
29 30
30 31 global_id_str = options.get('global_id')
31 32 if global_id_str:
@@ -43,7 +44,7 b' class Command(BaseCommand):'
43 44 h = httplib2.Http()
44 45 response, content = h.request(get_url, method="POST", body=xml)
45 46
46 SyncManager.parse_response_get(content)
47 SyncManager.parse_response_get(content, file_url)
47 48 else:
48 49 raise Exception('Invalid global ID')
49 50 else:
@@ -72,7 +73,7 b' class Command(BaseCommand):'
72 73 h = httplib2.Http()
73 74 response, content = h.request(get_url, method="POST", body=xml)
74 75
75 SyncManager.parse_response_get(content)
76 SyncManager.parse_response_get(content, file_url)
76 77 else:
77 78 print('Nothing to get, everything synced')
78 79 else:
@@ -54,6 +54,15 b' class Downloader:'
54 54 return file
55 55
56 56
57 def download(url):
58 for downloader in Downloader.__subclasses__():
59 if downloader.handles(url):
60 return downloader.download(url)
61 # If nobody of the specific downloaders handles this, use generic
62 # one
63 return Downloader.download(url)
64
65
57 66 class YouTubeDownloader(Downloader):
58 67 @staticmethod
59 68 def download(url: str):
@@ -1,17 +1,7 b''
1 1 import logging
2 import re
3 2 import uuid
4 3
5 from django.core.exceptions import ObjectDoesNotExist
6 from django.core.urlresolvers import reverse
7 from django.db import models
8 from django.db.models import TextField, QuerySet
9 from django.template.defaultfilters import truncatewords, striptags
10 from django.template.loader import render_to_string
11 from django.utils import timezone
12 from django.dispatch import receiver
13 from django.db.models.signals import pre_save, post_save
14
4 import re
15 5 from boards import settings
16 6 from boards.abstracts.tripcode import Tripcode
17 7 from boards.mdx_neboard import get_parser
@@ -20,6 +10,16 b' from boards.models.base import Viewable'
20 10 from boards.models.post.export import get_exporter, DIFF_TYPE_JSON
21 11 from boards.models.post.manager import PostManager
22 12 from boards.models.user import Notification
13 from django.core.exceptions import ObjectDoesNotExist
14 from django.core.urlresolvers import reverse
15 from django.db import models
16 from django.db.models import TextField, QuerySet
17 from django.db.models.signals import pre_save, post_save, pre_delete, \
18 post_delete
19 from django.dispatch import receiver
20 from django.template.defaultfilters import truncatewords, striptags
21 from django.template.loader import render_to_string
22 from django.utils import timezone
23 23
24 24 CSS_CLS_HIDDEN_POST = 'hidden_post'
25 25 CSS_CLS_DEAD_POST = 'dead_post'
@@ -104,7 +104,8 b' class Post(models.Model, Viewable):'
104 104
105 105 # Global ID with author key. If the message was downloaded from another
106 106 # server, this indicates the server.
107 global_id = models.OneToOneField('GlobalId', null=True, blank=True)
107 global_id = models.OneToOneField(GlobalId, null=True, blank=True,
108 on_delete=models.CASCADE)
108 109
109 110 tripcode = models.CharField(max_length=50, blank=True, default='')
110 111 opening = models.BooleanField(db_index=True)
@@ -174,6 +175,7 b' class Post(models.Model, Viewable):'
174 175
175 176 def get_thread_id(self):
176 177 return self.thread_id
178
177 179 def get_threads(self) -> QuerySet:
178 180 """
179 181 Gets post's thread.
@@ -220,33 +222,6 b' class Post(models.Model, Viewable):'
220 222 def get_first_image(self) -> PostImage:
221 223 return self.images.earliest('id')
222 224
223 def delete(self, using=None):
224 """
225 Deletes all post images and the post itself.
226 """
227
228 for image in self.images.all():
229 image_refs_count = image.post_images.count()
230 if image_refs_count == 1:
231 image.delete()
232
233 for attachment in self.attachments.all():
234 attachment_refs_count = attachment.attachment_posts.count()
235 if attachment_refs_count == 1:
236 attachment.delete()
237
238 if self.global_id:
239 self.global_id.delete()
240
241 thread = self.get_thread()
242 thread.last_edit_time = timezone.now()
243 thread.save()
244
245 super(Post, self).delete(using)
246
247 logging.getLogger('boards.post.delete').info(
248 'Deleted post {}'.format(self))
249
250 225 def set_global_id(self, key_pair=None):
251 226 """
252 227 Sets global id based on the given key pair. If no key pair is given,
@@ -373,7 +348,7 b' class Post(models.Model, Viewable):'
373 348 absolute_post_id = str(Post.objects.get(id=post_id).global_id)
374 349 replacements[post_id] = absolute_post_id
375 350
376 text = self.get_raw_text()
351 text = self.get_raw_text() or ''
377 352 for key in replacements:
378 353 text = text.replace('[post]{}[/post]'.format(key),
379 354 '[post]{}[/post]'.format(replacements[key]))
@@ -453,3 +428,26 b' def connect_notifications(instance, **kw'
453 428 @receiver(pre_save, sender=Post)
454 429 def preparse_text(instance, **kwargs):
455 430 instance._text_rendered = get_parser().parse(instance.get_raw_text())
431
432
433 @receiver(pre_delete, sender=Post)
434 def delete_images(instance, **kwargs):
435 for image in instance.images.all():
436 image_refs_count = image.post_images.count()
437 if image_refs_count == 1:
438 image.delete()
439
440
441 @receiver(pre_delete, sender=Post)
442 def delete_attachments(instance, **kwargs):
443 for attachment in instance.attachments.all():
444 attachment_refs_count = attachment.attachment_posts.count()
445 if attachment_refs_count == 1:
446 attachment.delete()
447
448
449 @receiver(post_delete, sender=Post)
450 def update_thread_on_delete(instance, **kwargs):
451 thread = instance.get_thread()
452 thread.last_edit_time = timezone.now()
453 thread.save()
@@ -80,13 +80,8 b' class PostManager(models.Manager):'
80 80 logger.info('Created post [{}] with text [{}] by {}'.format(post,
81 81 post.get_text(),post.poster_ip))
82 82
83 # TODO Move this to other place
84 83 if file:
85 file_type = file.name.split('.')[-1].lower()
86 if file_type in IMAGE_TYPES:
87 post.images.add(PostImage.objects.create_with_hash(file))
88 else:
89 post.attachments.add(Attachment.objects.create_with_hash(file))
84 self._add_file_to_post(file, post)
90 85 for image in images:
91 86 post.images.add(image)
92 87
@@ -134,7 +129,7 b' class PostManager(models.Manager):'
134 129
135 130 @transaction.atomic
136 131 def import_post(self, title: str, text: str, pub_time: str, global_id,
137 opening_post=None, tags=list()):
132 opening_post=None, tags=list(), files=list()):
138 133 is_opening = opening_post is None
139 134 if is_opening:
140 135 thread = boards.models.thread.Thread.objects.create(
@@ -151,4 +146,15 b' class PostManager(models.Manager):'
151 146 opening=is_opening,
152 147 thread=thread)
153 148
149 # TODO Add files
150 for file in files:
151 self._add_file_to_post(file, post)
152
154 153 post.threads.add(thread)
154
155 def _add_file_to_post(self, file, post):
156 file_type = file.name.split('.')[-1].lower()
157 if file_type in IMAGE_TYPES:
158 post.images.add(PostImage.objects.create_with_hash(file))
159 else:
160 post.attachments.add(Attachment.objects.create_with_hash(file))
@@ -1,5 +1,6 b''
1 1 import xml.etree.ElementTree as et
2 2
3 from boards.models.attachment.downloaders import download
3 4 from boards.utils import get_file_mimetype
4 5 from django.db import transaction
5 6 from boards.models import KeyPair, GlobalId, Signature, Post, Tag
@@ -40,6 +41,10 b" ATTR_URL = 'url'"
40 41 STATUS_SUCCESS = 'success'
41 42
42 43
44 class SyncException(Exception):
45 pass
46
47
43 48 class SyncManager:
44 49 @staticmethod
45 50 def generate_response_get(model_list: list):
@@ -118,7 +123,7 b' class SyncManager:'
118 123
119 124 @staticmethod
120 125 @transaction.atomic
121 def parse_response_get(response_xml):
126 def parse_response_get(response_xml, hostname):
122 127 tag_root = et.fromstring(response_xml)
123 128 tag_status = tag_root.find(TAG_STATUS)
124 129 if STATUS_SUCCESS == tag_status.text:
@@ -139,8 +144,8 b' class SyncManager:'
139 144 signature.global_id = global_id
140 145 signature.save()
141 146
142 title = tag_content.find(TAG_TITLE).text
143 text = tag_content.find(TAG_TEXT).text
147 title = tag_content.find(TAG_TITLE).text or ''
148 text = tag_content.find(TAG_TEXT).text or ''
144 149 pub_time = tag_content.find(TAG_PUB_TIME).text
145 150
146 151 thread = tag_content.find(TAG_THREAD)
@@ -151,7 +156,7 b' class SyncManager:'
151 156 if exists:
152 157 opening_post = Post.objects.get(global_id=op_global_id)
153 158 else:
154 raise Exception('Load the OP first')
159 raise SyncException('Load the OP first')
155 160 else:
156 161 opening_post = None
157 162 tag_tags = tag_content.find(TAG_TAGS)
@@ -163,12 +168,23 b' class SyncManager:'
163 168 # TODO Check that the replied posts are already present
164 169 # before adding new ones
165 170
166 # TODO Get images
171 files = []
172 tag_attachments = tag_content.find(TAG_ATTACHMENTS) or list()
173 tag_refs = tag_model.find(TAG_ATTACHMENT_REFS)
174 for attachment in tag_attachments:
175 tag_ref = tag_refs.find("{}[@ref='{}']".format(
176 TAG_ATTACHMENT_REF, attachment.text))
177 url = tag_ref.get(ATTR_URL)
178 attached_file = download(hostname + url)
179 if attached_file is None:
180 raise SyncException('File was not dowloaded')
181 files.append(attached_file)
182 # TODO Check hash
167 183
168 post = Post.objects.import_post(
184 Post.objects.import_post(
169 185 title=title, text=text, pub_time=pub_time,
170 186 opening_post=opening_post, tags=tags,
171 global_id=global_id)
187 global_id=global_id, files=files)
172 188 else:
173 189 # TODO Throw an exception?
174 190 pass
@@ -210,7 +226,7 b' class SyncManager:'
210 226
211 227 if not KeyPair.objects.verify(
212 228 signature, content):
213 raise Exception('Invalid model signature for {}'.format(content))
229 raise SyncException('Invalid model signature for {}'.format(content))
214 230
215 231 signatures.append(signature)
216 232
@@ -59,7 +59,7 b' class SyncTest(TestCase):'
59 59
60 60 KeyPair.objects.generate_key(primary=True)
61 61
62 SyncManager.parse_response_get(response)
62 SyncManager.parse_response_get(response, None)
63 63 self.assertEqual(1, Post.objects.count(),
64 64 'Post was not created from XML response.')
65 65
@@ -68,7 +68,7 b' class SyncTest(TestCase):'
68 68 parsed_post.get_thread().get_tags().first().name,
69 69 'Invalid tag was parsed.')
70 70
71 SyncManager.parse_response_get(response)
71 SyncManager.parse_response_get(response, None)
72 72 self.assertEqual(1, Post.objects.count(),
73 73 'The same post was imported twice.')
74 74
@@ -1,4 +1,6 b''
1 1 import xml.etree.ElementTree as et
2 import xml.dom.minidom
3
2 4 from django.http import HttpResponse, Http404
3 5 from boards.models import GlobalId, Post
4 6 from boards.models.post.sync import SyncManager
@@ -46,9 +48,13 b' def get_post_sync_data(request, post_id)'
46 48 except Post.DoesNotExist:
47 49 raise Http404()
48 50
49 content = 'Global ID: %s\n\nXML: %s' \
50 % (post.global_id, SyncManager.generate_response_get([post]))
51 xml_str = SyncManager.generate_response_get([post])
51 52
53 xml_repr = xml.dom.minidom.parseString(xml_str)
54 xml_repr = xml_repr.toprettyxml()
55
56 content = '=Global ID=\n%s\n\n=XML=\n%s' \
57 % (post.global_id, xml_repr)
52 58
53 59 return HttpResponse(
54 60 content_type='text/plain',
General Comments 0
You need to be logged in to leave comments. Login now