##// END OF EJS Templates
Updated sync method for requesting and getting a post
neko259 -
r1177:a55da940 decentral
parent child Browse files
Show More
@@ -0,0 +1,116 b''
1 import xml.etree.ElementTree as et
2 from boards.models import KeyPair, GlobalId, Signature, Post
3
4 ENCODING_UNICODE = 'unicode'
5
6 TAG_MODEL = 'model'
7 TAG_REQUEST = 'request'
8 TAG_RESPONSE = 'response'
9 TAG_ID = 'id'
10 TAG_STATUS = 'status'
11 TAG_MODELS = 'models'
12 TAG_TITLE = 'title'
13 TAG_TEXT = 'text'
14 TAG_THREAD = 'thread'
15 TAG_PUB_TIME = 'pub-time'
16 TAG_SIGNATURES = 'signatures'
17 TAG_SIGNATURE = 'signature'
18 TAG_CONTENT = 'content'
19 TAG_ATTACHMENTS = 'attachments'
20 TAG_ATTACHMENT = 'attachment'
21
22 TYPE_GET = 'get'
23
24 ATTR_VERSION = 'version'
25 ATTR_TYPE = 'type'
26 ATTR_NAME = 'name'
27 ATTR_VALUE = 'value'
28 ATTR_MIMETYPE = 'mimetype'
29
30 STATUS_SUCCESS = 'success'
31
32
33 class SyncManager:
34 def generate_response_get(self, model_list: list):
35 response = et.Element(TAG_RESPONSE)
36
37 status = et.SubElement(response, TAG_STATUS)
38 status.text = STATUS_SUCCESS
39
40 models = et.SubElement(response, TAG_MODELS)
41
42 for post in model_list:
43 model = et.SubElement(models, TAG_MODEL)
44 model.set(ATTR_NAME, 'post')
45
46 content_tag = et.SubElement(model, TAG_CONTENT)
47
48 tag_id = et.SubElement(content_tag, TAG_ID)
49 post.global_id.to_xml_element(tag_id)
50
51 title = et.SubElement(content_tag, TAG_TITLE)
52 title.text = post.title
53
54 text = et.SubElement(content_tag, TAG_TEXT)
55 # TODO Replace local links by global ones in the text
56 text.text = post.get_raw_text()
57
58 if not post.is_opening():
59 thread = et.SubElement(content_tag, TAG_THREAD)
60 thread_id = et.SubElement(thread, TAG_ID)
61 post.get_thread().get_opening_post().global_id.to_xml_element(thread_id)
62 else:
63 # TODO Output tags here
64 pass
65
66 pub_time = et.SubElement(content_tag, TAG_PUB_TIME)
67 pub_time.text = str(post.get_pub_time_epoch())
68
69 signatures_tag = et.SubElement(model, TAG_SIGNATURES)
70 post_signatures = post.signature.all()
71 if post_signatures:
72 signatures = post.signatures
73 else:
74 # TODO Maybe the signature can be computed only once after
75 # the post is added? Need to add some on_save signal queue
76 # and add this there.
77 key = KeyPair.objects.get(public_key=post.global_id.key)
78 signatures = [Signature(
79 key_type=key.key_type,
80 key=key.public_key,
81 signature=key.sign(et.tostring(model, ENCODING_UNICODE)),
82 )]
83 for signature in signatures:
84 signature_tag = et.SubElement(signatures_tag, TAG_SIGNATURE)
85 signature_tag.set(ATTR_TYPE, signature.key_type)
86 signature_tag.set(ATTR_VALUE, signature.signature)
87
88 return et.tostring(response, ENCODING_UNICODE)
89
90 def parse_response_get(self, response_xml):
91 tag_root = et.fromstring(response_xml)
92 tag_status = tag_root.find(TAG_STATUS)
93 if STATUS_SUCCESS == tag_status.text:
94 tag_models = tag_root.find(TAG_MODELS)
95 for tag_model in tag_models:
96 tag_content = tag_model.find(TAG_CONTENT)
97 tag_id = tag_content.find(TAG_ID)
98 try:
99 GlobalId.from_xml_element(tag_id, existing=True)
100 print('Post with same ID already exists')
101 except GlobalId.DoesNotExist:
102 global_id = GlobalId.from_xml_element(tag_id)
103
104 title = tag_content.find(TAG_TITLE).text
105 text = tag_content.find(TAG_TEXT).text
106 # TODO Check that the replied posts are already present
107 # before adding new ones
108
109 # TODO Pub time, thread, tags
110
111 print(title)
112 print(text)
113 # post = Post.objects.create(title=title, text=text)
114 else:
115 # TODO Throw an exception?
116 pass
@@ -5,6 +5,7 b' import xml.etree.ElementTree as ET'
5 5
6 6 from django.core.management import BaseCommand
7 7 from boards.models import GlobalId
8 from boards.models.post.sync import SyncManager
8 9
9 10 __author__ = 'neko259'
10 11
@@ -18,7 +19,7 b' class Command(BaseCommand):'
18 19
19 20 def add_arguments(self, parser):
20 21 parser.add_argument('url', type=str)
21 #parser.add_argument('global_id', type=str) # TODO Implement this
22 parser.add_argument('global_id', type=str)
22 23
23 24 def handle(self, *args, **options):
24 25 url = options.get('url')
@@ -34,14 +35,11 b' class Command(BaseCommand):'
34 35 local_id=local_id)
35 36
36 37 xml = GlobalId.objects.generate_request_get([global_id])
37 data = {'xml': xml}
38 body = urllib.parse.urlencode(data)
38 # body = urllib.parse.urlencode(data)
39 39 h = httplib2.Http()
40 response, content = h.request(url, method="POST", body=body)
40 response, content = h.request(url, method="POST", body=xml)
41 41
42 # TODO Parse content and get the model list
43
44 print(content)
42 SyncManager().parse_response_get(content)
45 43 else:
46 44 raise Exception('Invalid global ID')
47 45 else:
@@ -3,7 +3,6 b' from datetime import time as dtime'
3 3 import logging
4 4 import re
5 5 import uuid
6 import xml.etree.ElementTree as et
7 6
8 7 from django.core.exceptions import ObjectDoesNotExist
9 8 from django.core.urlresolvers import reverse
@@ -13,7 +12,7 b' from django.template.loader import rende'
13 12 from django.utils import timezone
14 13
15 14 from boards.mdx_neboard import Parser
16 from boards.models import KeyPair, GlobalId, Signature
15 from boards.models import KeyPair, GlobalId
17 16 from boards import settings
18 17 from boards.models import PostImage
19 18 from boards.models.base import Viewable
@@ -22,9 +21,6 b' from boards.models.post.export import ge'
22 21 from boards.models.user import Notification, Ban
23 22 import boards.models.thread
24 23
25
26 ENCODING_UNICODE = 'unicode'
27
28 24 WS_NOTIFICATION_TYPE_NEW_POST = 'new_post'
29 25 WS_NOTIFICATION_TYPE = 'notification_type'
30 26
@@ -48,31 +44,6 b" REGEX_GLOBAL_REPLY = re.compile(r'\\[post"
48 44 REGEX_URL = re.compile(r'https?\://[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,3}(/\S*)?')
49 45 REGEX_NOTIFICATION = re.compile(r'\[user\](\w+)\[/user\]')
50 46
51 TAG_MODEL = 'model'
52 TAG_REQUEST = 'request'
53 TAG_RESPONSE = 'response'
54 TAG_ID = 'id'
55 TAG_STATUS = 'status'
56 TAG_MODELS = 'models'
57 TAG_TITLE = 'title'
58 TAG_TEXT = 'text'
59 TAG_THREAD = 'thread'
60 TAG_PUB_TIME = 'pub-time'
61 TAG_SIGNATURES = 'signatures'
62 TAG_SIGNATURE = 'signature'
63 TAG_CONTENT = 'content'
64 TAG_ATTACHMENTS = 'attachments'
65 TAG_ATTACHMENT = 'attachment'
66
67 TYPE_GET = 'get'
68
69 ATTR_VERSION = 'version'
70 ATTR_TYPE = 'type'
71 ATTR_NAME = 'name'
72 ATTR_VALUE = 'value'
73 ATTR_MIMETYPE = 'mimetype'
74
75 STATUS_SUCCESS = 'success'
76 47
77 48 PARAMETER_TRUNCATED = 'truncated'
78 49 PARAMETER_TAG = 'tag'
@@ -89,7 +60,6 b" PARAMETER_REPLY_LINK = 'reply_link'"
89 60 PARAMETER_NEED_OP_DATA = 'need_op_data'
90 61
91 62 DIFF_TYPE_HTML = 'html'
92 DIFF_TYPE_JSON = 'json'
93 63
94 64 REFMAP_STR = '<a href="{}">&gt;&gt;{}</a>'
95 65
@@ -189,98 +159,6 b' class PostManager(models.Manager):'
189 159 return ppd
190 160
191 161
192 # TODO Make a separate sync facade?
193 def generate_response_get(self, model_list: list):
194 response = et.Element(TAG_RESPONSE)
195
196 status = et.SubElement(response, TAG_STATUS)
197 status.text = STATUS_SUCCESS
198
199 models = et.SubElement(response, TAG_MODELS)
200
201 for post in model_list:
202 model = et.SubElement(models, TAG_MODEL)
203 model.set(ATTR_NAME, 'post')
204
205 content_tag = et.SubElement(model, TAG_CONTENT)
206
207 tag_id = et.SubElement(content_tag, TAG_ID)
208 post.global_id.to_xml_element(tag_id)
209
210 title = et.SubElement(content_tag, TAG_TITLE)
211 title.text = post.title
212
213 text = et.SubElement(content_tag, TAG_TEXT)
214 # TODO Replace local links by global ones in the text
215 text.text = post.get_raw_text()
216
217 if not post.is_opening():
218 thread = et.SubElement(content_tag, TAG_THREAD)
219 thread_id = et.SubElement(thread, TAG_ID)
220 post.get_thread().get_opening_post().global_id.to_xml_element(thread_id)
221 else:
222 # TODO Output tags here
223 pass
224
225 pub_time = et.SubElement(content_tag, TAG_PUB_TIME)
226 pub_time.text = str(post.get_pub_time_epoch())
227
228 signatures_tag = et.SubElement(model, TAG_SIGNATURES)
229 post_signatures = post.signature.all()
230 if post_signatures:
231 signatures = post.signatures
232 else:
233 # TODO Maybe the signature can be computed only once after
234 # the post is added? Need to add some on_save signal queue
235 # and add this there.
236 key = KeyPair.objects.get(public_key=post.global_id.key)
237 signatures = [Signature(
238 key_type=key.key_type,
239 key=key.public_key,
240 signature=key.sign(et.tostring(model, ENCODING_UNICODE)),
241 )]
242 for signature in signatures:
243 signature_tag = et.SubElement(signatures_tag, TAG_SIGNATURE)
244 signature_tag.set(ATTR_TYPE, signature.key_type)
245 signature_tag.set(ATTR_VALUE, signature.signature)
246
247 return et.tostring(response, ENCODING_UNICODE)
248
249 def parse_response_get(self, response_xml):
250 tag_root = et.fromstring(response_xml)
251 tag_status = tag_root[0]
252 if 'success' == tag_status.text:
253 tag_models = tag_root[1]
254 for tag_model in tag_models:
255 tag_content = tag_model[0]
256 tag_id = tag_content[1]
257 try:
258 GlobalId.from_xml_element(tag_id, existing=True)
259 # If this post already exists, just continue
260 # TODO Compare post content and update the post if necessary
261 pass
262 except GlobalId.DoesNotExist:
263 global_id = GlobalId.from_xml_element(tag_id)
264
265 title = tag_content.find(TAG_TITLE).text
266 text = tag_content.find(TAG_TEXT).text
267 # TODO Check that the replied posts are already present
268 # before adding new ones
269
270 # TODO Pub time, thread, tags
271
272 post = Post.objects.create(title=title, text=text)
273 else:
274 # TODO Throw an exception?
275 pass
276
277 # TODO Make a separate parser module and move preparser there
278 def _preparse_text(self, text: str) -> str:
279 """
280 Preparses text to change patterns like '>>' to a proper bbcode
281 tags.
282 """
283
284 162 class Post(models.Model, Viewable):
285 163 """A post is a message."""
286 164
@@ -433,7 +311,6 b' class Post(models.Model, Viewable):'
433 311 logging.getLogger('boards.post.delete').info(
434 312 'Deleted post {}'.format(self))
435 313
436 # TODO Implement this with OOP, e.g. use the factory and HtmlPostData class
437 314 def set_global_id(self, key_pair=None):
438 315 """
439 316 Sets global id based on the given key pair. If no key pair is given,
@@ -483,7 +360,6 b' class Post(models.Model, Viewable):'
483 360 pass
484 361 return local_replied + global_replied
485 362
486
487 363 def get_post_data(self, format_type=DIFF_TYPE_JSON, request=None,
488 364 include_last_update=False) -> str:
489 365 """
@@ -10,7 +10,7 b' from boards.views.notifications import N'
10 10 from boards.views.search import BoardSearchView
11 11 from boards.views.static import StaticPageView
12 12 from boards.views.preview import PostPreviewView
13 from boards.views.sync import get_post_sync_data
13 from boards.views.sync import get_post_sync_data, response_get
14 14
15 15
16 16 js_info_dict = {
@@ -76,6 +76,7 b" urlpatterns = patterns('',"
76 76
77 77 # Sync protocol API
78 78 url(r'^api/sync/pull/$', api.sync_pull, name='api_sync_pull'),
79 url(r'^api/sync/get/$', response_get, name='api_sync_pull'),
79 80 # TODO 'get' request
80 81
81 82 # Search
@@ -1,13 +1,16 b''
1 1 import json
2 2 import logging
3 3
4 import xml.etree.ElementTree as ET
5
4 6 from django.db import transaction
5 7 from django.http import HttpResponse
6 8 from django.shortcuts import get_object_or_404
7 9 from django.core import serializers
8 10
9 11 from boards.forms import PostForm, PlainErrorList
10 from boards.models import Post, Thread, Tag
12 from boards.models import Post, Thread, Tag, GlobalId
13 from boards.models.post.sync import SyncManager
11 14 from boards.utils import datetime_to_epoch
12 15 from boards.views.thread import ThreadView
13 16 from boards.models.user import Notification
@@ -234,7 +237,7 b' def get_post_data(post_id, format_type=D'
234 237 # TODO Make a separate module for sync API methods
235 238 def sync_pull(request):
236 239 """
237 Return 'get' request response for all posts.
240 Return 'pull' request response for all posts.
238 241 """
239 242 request_xml = request.get('xml')
240 243 if request_xml is None:
@@ -242,5 +245,5 b' def sync_pull(request):'
242 245 else:
243 246 pass # TODO Parse the XML and get filters from it
244 247
245 xml = Post.objects.generate_response_get(posts)
248 xml = SyncManager().generate_response_get(posts)
246 249 return HttpResponse(content=xml)
@@ -1,19 +1,23 b''
1 1 import xml.etree.ElementTree as et
2 2 from django.http import HttpResponse, Http404
3 3 from boards.models import GlobalId, Post
4 from boards.models.post.sync import SyncManager
4 5
5 6
6 def respond_pull(request):
7 def response_pull(request):
7 8 pass
8 9
9 10
10 def respond_get(request):
11 def response_get(request):
11 12 """
12 13 Processes a GET request with post ID list and returns the posts XML list.
13 14 Request should contain an 'xml' post attribute with the actual request XML.
14 15 """
15 16
16 request_xml = request.POST['xml']
17 request_xml = request.body
18
19 if request_xml is None:
20 return HttpResponse(content='Use the API')
17 21
18 22 posts = []
19 23
@@ -22,13 +26,13 b' def respond_get(request):'
22 26 for id_tag in model_tag:
23 27 try:
24 28 global_id = GlobalId.from_xml_element(id_tag, existing=True)
25 posts += Post.objects.filter(global_id=global_id)
29 posts.append(Post.objects.get(global_id=global_id))
26 30 except GlobalId.DoesNotExist:
27 31 # This is normal. If we don't have such GlobalId in the system,
28 32 # just ignore this ID and proceed to the next one.
29 33 pass
30 34
31 response_xml = Post.objects.generate_response_get(posts)
35 response_xml = SyncManager().generate_response_get(posts)
32 36
33 37 return HttpResponse(content=response_xml)
34 38
@@ -40,7 +44,7 b' def get_post_sync_data(request, post_id)'
40 44 raise Http404()
41 45
42 46 content = 'Global ID: %s\n\nXML: %s' \
43 % (post.global_id, Post.objects.generate_response_get([post]))
47 % (post.global_id, SyncManager().generate_response_get([post]))
44 48
45 49
46 50 return HttpResponse(
General Comments 0
You need to be logged in to leave comments. Login now