##// 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 from django.core.management import BaseCommand
6 from django.core.management import BaseCommand
7 from boards.models import GlobalId
7 from boards.models import GlobalId
8 from boards.models.post.sync import SyncManager
8
9
9 __author__ = 'neko259'
10 __author__ = 'neko259'
10
11
@@ -18,7 +19,7 b' class Command(BaseCommand):'
18
19
19 def add_arguments(self, parser):
20 def add_arguments(self, parser):
20 parser.add_argument('url', type=str)
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 def handle(self, *args, **options):
24 def handle(self, *args, **options):
24 url = options.get('url')
25 url = options.get('url')
@@ -34,14 +35,11 b' class Command(BaseCommand):'
34 local_id=local_id)
35 local_id=local_id)
35
36
36 xml = GlobalId.objects.generate_request_get([global_id])
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 h = httplib2.Http()
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
42 SyncManager().parse_response_get(content)
43
44 print(content)
45 else:
43 else:
46 raise Exception('Invalid global ID')
44 raise Exception('Invalid global ID')
47 else:
45 else:
@@ -3,7 +3,6 b' from datetime import time as dtime'
3 import logging
3 import logging
4 import re
4 import re
5 import uuid
5 import uuid
6 import xml.etree.ElementTree as et
7
6
8 from django.core.exceptions import ObjectDoesNotExist
7 from django.core.exceptions import ObjectDoesNotExist
9 from django.core.urlresolvers import reverse
8 from django.core.urlresolvers import reverse
@@ -13,7 +12,7 b' from django.template.loader import rende'
13 from django.utils import timezone
12 from django.utils import timezone
14
13
15 from boards.mdx_neboard import Parser
14 from boards.mdx_neboard import Parser
16 from boards.models import KeyPair, GlobalId, Signature
15 from boards.models import KeyPair, GlobalId
17 from boards import settings
16 from boards import settings
18 from boards.models import PostImage
17 from boards.models import PostImage
19 from boards.models.base import Viewable
18 from boards.models.base import Viewable
@@ -22,9 +21,6 b' from boards.models.post.export import ge'
22 from boards.models.user import Notification, Ban
21 from boards.models.user import Notification, Ban
23 import boards.models.thread
22 import boards.models.thread
24
23
25
26 ENCODING_UNICODE = 'unicode'
27
28 WS_NOTIFICATION_TYPE_NEW_POST = 'new_post'
24 WS_NOTIFICATION_TYPE_NEW_POST = 'new_post'
29 WS_NOTIFICATION_TYPE = 'notification_type'
25 WS_NOTIFICATION_TYPE = 'notification_type'
30
26
@@ -48,31 +44,6 b" REGEX_GLOBAL_REPLY = re.compile(r'\\[post"
48 REGEX_URL = re.compile(r'https?\://[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,3}(/\S*)?')
44 REGEX_URL = re.compile(r'https?\://[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,3}(/\S*)?')
49 REGEX_NOTIFICATION = re.compile(r'\[user\](\w+)\[/user\]')
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 PARAMETER_TRUNCATED = 'truncated'
48 PARAMETER_TRUNCATED = 'truncated'
78 PARAMETER_TAG = 'tag'
49 PARAMETER_TAG = 'tag'
@@ -89,7 +60,6 b" PARAMETER_REPLY_LINK = 'reply_link'"
89 PARAMETER_NEED_OP_DATA = 'need_op_data'
60 PARAMETER_NEED_OP_DATA = 'need_op_data'
90
61
91 DIFF_TYPE_HTML = 'html'
62 DIFF_TYPE_HTML = 'html'
92 DIFF_TYPE_JSON = 'json'
93
63
94 REFMAP_STR = '<a href="{}">&gt;&gt;{}</a>'
64 REFMAP_STR = '<a href="{}">&gt;&gt;{}</a>'
95
65
@@ -189,98 +159,6 b' class PostManager(models.Manager):'
189 return ppd
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 class Post(models.Model, Viewable):
162 class Post(models.Model, Viewable):
285 """A post is a message."""
163 """A post is a message."""
286
164
@@ -433,7 +311,6 b' class Post(models.Model, Viewable):'
433 logging.getLogger('boards.post.delete').info(
311 logging.getLogger('boards.post.delete').info(
434 'Deleted post {}'.format(self))
312 'Deleted post {}'.format(self))
435
313
436 # TODO Implement this with OOP, e.g. use the factory and HtmlPostData class
437 def set_global_id(self, key_pair=None):
314 def set_global_id(self, key_pair=None):
438 """
315 """
439 Sets global id based on the given key pair. If no key pair is given,
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 pass
360 pass
484 return local_replied + global_replied
361 return local_replied + global_replied
485
362
486
487 def get_post_data(self, format_type=DIFF_TYPE_JSON, request=None,
363 def get_post_data(self, format_type=DIFF_TYPE_JSON, request=None,
488 include_last_update=False) -> str:
364 include_last_update=False) -> str:
489 """
365 """
@@ -10,7 +10,7 b' from boards.views.notifications import N'
10 from boards.views.search import BoardSearchView
10 from boards.views.search import BoardSearchView
11 from boards.views.static import StaticPageView
11 from boards.views.static import StaticPageView
12 from boards.views.preview import PostPreviewView
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 js_info_dict = {
16 js_info_dict = {
@@ -76,6 +76,7 b" urlpatterns = patterns('',"
76
76
77 # Sync protocol API
77 # Sync protocol API
78 url(r'^api/sync/pull/$', api.sync_pull, name='api_sync_pull'),
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 # TODO 'get' request
80 # TODO 'get' request
80
81
81 # Search
82 # Search
@@ -1,13 +1,16 b''
1 import json
1 import json
2 import logging
2 import logging
3
3
4 import xml.etree.ElementTree as ET
5
4 from django.db import transaction
6 from django.db import transaction
5 from django.http import HttpResponse
7 from django.http import HttpResponse
6 from django.shortcuts import get_object_or_404
8 from django.shortcuts import get_object_or_404
7 from django.core import serializers
9 from django.core import serializers
8
10
9 from boards.forms import PostForm, PlainErrorList
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 from boards.utils import datetime_to_epoch
14 from boards.utils import datetime_to_epoch
12 from boards.views.thread import ThreadView
15 from boards.views.thread import ThreadView
13 from boards.models.user import Notification
16 from boards.models.user import Notification
@@ -234,7 +237,7 b' def get_post_data(post_id, format_type=D'
234 # TODO Make a separate module for sync API methods
237 # TODO Make a separate module for sync API methods
235 def sync_pull(request):
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 request_xml = request.get('xml')
242 request_xml = request.get('xml')
240 if request_xml is None:
243 if request_xml is None:
@@ -242,5 +245,5 b' def sync_pull(request):'
242 else:
245 else:
243 pass # TODO Parse the XML and get filters from it
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 return HttpResponse(content=xml)
249 return HttpResponse(content=xml)
@@ -1,19 +1,23 b''
1 import xml.etree.ElementTree as et
1 import xml.etree.ElementTree as et
2 from django.http import HttpResponse, Http404
2 from django.http import HttpResponse, Http404
3 from boards.models import GlobalId, Post
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 pass
8 pass
8
9
9
10
10 def respond_get(request):
11 def response_get(request):
11 """
12 """
12 Processes a GET request with post ID list and returns the posts XML list.
13 Processes a GET request with post ID list and returns the posts XML list.
13 Request should contain an 'xml' post attribute with the actual request XML.
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 posts = []
22 posts = []
19
23
@@ -22,13 +26,13 b' def respond_get(request):'
22 for id_tag in model_tag:
26 for id_tag in model_tag:
23 try:
27 try:
24 global_id = GlobalId.from_xml_element(id_tag, existing=True)
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 except GlobalId.DoesNotExist:
30 except GlobalId.DoesNotExist:
27 # This is normal. If we don't have such GlobalId in the system,
31 # This is normal. If we don't have such GlobalId in the system,
28 # just ignore this ID and proceed to the next one.
32 # just ignore this ID and proceed to the next one.
29 pass
33 pass
30
34
31 response_xml = Post.objects.generate_response_get(posts)
35 response_xml = SyncManager().generate_response_get(posts)
32
36
33 return HttpResponse(content=response_xml)
37 return HttpResponse(content=response_xml)
34
38
@@ -40,7 +44,7 b' def get_post_sync_data(request, post_id)'
40 raise Http404()
44 raise Http404()
41
45
42 content = 'Global ID: %s\n\nXML: %s' \
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 return HttpResponse(
50 return HttpResponse(
General Comments 0
You need to be logged in to leave comments. Login now